Butun axtardiqlarinizi tapmaq ucun buraya: DAXIL OLUN
  Mp4 Mp3 Axtar Yukle
  Video Axtar Yukle
  Shekil Axtar Yukle
  Informasiya Melumat Axtar
  Hazir Inshalar Toplusu
  AZERI CHAT + Tanishliq
  Saglamliq Tibbi Melumat
  Whatsapp Plus Yukle(Yeni)

  • Ana səhifə
  • Təsadüfi
  • Yaxınlıqdakılar
  • Daxil ol
  • Nizamlamalar
İndi ianə et Əgər Vikipediya sizin üçün faydalıdırsa, bu gün ianə edin.
Learn more

MediaViki:Gadget-catsuggest.js

  • İnterfeys
  • Müzakirə
Etdiyiniz dəyişikliklərin yayımlanandan sonra effekt verməsi üçün brauzerinizin keşini təmizləməyə ehtiyacınız ola bilər. Bunun üçün Chrome, Firefox, Edge, yaxud Safari istifadəçisisinizsə, klaviaturanızın Shift düyməsini sıxaraq brauzerin ⟳ səhifə yeniləmək düyməsini klik edə bilərsiniz.
mw.loader
	.using([
		'vue',
		'@wikimedia/codex',
		'mediawiki.ForeignApi',
		'mediawiki.util',
		'mediawiki.storage',
	])
	.then(function (require) {
		const allowedNamespaces = [0, 4, 12, 14, 100]; // Məqalə, Vikipediya, Kömək, Kateqoriya, Portal
		if (!allowedNamespaces.includes(mw.config.get('wgNamespaceNumber'))) return;
		if (mw.config.get('wgAction') !== 'view') return;
		const { createMwApp } = require('vue');
		const {
			CdxButton,
			CdxDialog,
			CdxProgressBar,
			CdxIcon,
			CdxCheckbox,
			CdxMessage,
			CdxTextInput,
			CdxToggleSwitch,
			CdxTabs,
			CdxTab,
			CdxProgressIndicator,
		} = require('@wikimedia/codex');
		const api = new mw.Api({ userAgent: 'CatSuggest/azwiki' });
		const wikidataApi = new mw.ForeignApi(
			'https://www.wikidata.org/w/api.php',
			{ userAgent: 'CatSuggest/azwiki' },
		);

		const app = createMwApp({
			data: () => ({
				dialogShown: false,
				loading: false,
				suggestions: [],
				qid: mw.config.get('wgWikibaseItemId'),
				status: 'idle', // idle, loading, results, editing, success
				selectedSuggestions: [],
				error: null,
				sitelinks: [],
				modeSelectedSites: {
					members: [],
					parentCats: [],
				},
				siteFilter: '',
				debouncedSiteFilter: '',
				siteFilterTimeout: null,
				mode:
					mw.config.get('wgNamespaceNumber') === 14 ? 'members' : 'parentCats', // Default to members for Categories, parentCats for others
				isCategoryPage: mw.config.get('wgNamespaceNumber') === 14,
				localStatePromise: null,
				currentProcessingTitle: '',
				processedCount: 0,
				totalProcessingCount: 0,
			}),
			computed: {
				canFetch() {
					return this.selectedSites.length > 0 && !this.loading;
				},
				allSelected: {
					get() {
						return (
							this.sitelinks.length > 0 &&
							this.selectedSites.length === this.sitelinks.length
						);
					},
					set(value) {
						if (value) {
							this.selectedSites = this.sitelinks.map((s) => s.site);
						} else {
							this.selectedSites = [];
						}
					},
				},
				showSearch() {
					return this.sitelinks.length > 10;
				},
				filteredSitelinks() {
					if (!this.debouncedSiteFilter) return this.sitelinks;
					const filter = this.debouncedSiteFilter.toLowerCase();
					return this.sitelinks.filter(
						(s) =>
							s.name.toLowerCase().includes(filter) ||
							s.lang.toLowerCase().includes(filter),
					);
				},
				selectedSites: {
					get() {
						return this.modeSelectedSites[this.mode];
					},
					set(value) {
						this.modeSelectedSites[this.mode] = value;
					},
				},
				gridStyle() {
					const count = this.sitelinks.length;
					const columns = Math.max(2, Math.min(3, Math.ceil(count / 10)));
					return {
						display: 'grid',
						gridTemplateColumns: `repeat(${columns}, 1fr)`,
						gap: '8px',
						padding: '8px',
						border: '1px solid var(--border-color-base, #a2a9b1)',
						borderRadius: '2px',
						maxHeight: '200px',
						overflowY: 'auto',
					};
				},
				suggestedPages() {
					return this.suggestions.filter((s) => !s.isCategory);
				},
				selectedCount() {
					return this.suggestions.filter((s) => s.selected).length;
				},
				suggestedSubcats() {
					return this.suggestions.filter((s) => s.isCategory);
				},
				estimatedTime() {
					const count = this.selectedSites.length;
					if (count === 0) return 0;
					// 1s baseline for pre-fetch + wikidata + redundancy check
					// + (number of batches - 1) * 0.5s sleep
					// + overhead per batch
					const batches = Math.ceil(count / 3);
					const sleepTime = (batches - 1) * 0.5;
					const networkTime = batches * 0.3 + 0.8;
					return Math.ceil(sleepTime + networkTime);
				},
			},

			methods: {
				toggleDialog() {
					this.dialogShown = !this.dialogShown;
					if (this.dialogShown) {
						if (this.sitelinks.length === 0) {
							this.initGadget();
						}
						this.fetchLocalState();
					}
				},
				async initGadget() {
					if (!this.qid) {
						this.error = 'Bu səhifə Vikidatada tapılmadı.';
						return;
					}

					this.loading = true;
					this.status = 'loading';
					try {
						if (!this.qid) {
							this.error = 'Bu səhifə Vikidatada tapılmadı.';
							this.status = 'idle';
							return;
						}

						const wdResp = await wikidataApi.get({
							action: 'wbgetentities',
							ids: this.qid,
							props: 'sitelinks',
							format: 'json',
						});

						const entity = wdResp.entities[this.qid];
						if (!entity || !entity.sitelinks) {
							this.error = 'Bu səhifənin interviki keçidi mövcud deyil.';
							this.status = 'idle';
							return;
						}

						const cacheKeyLang = 'catsuggest-languages-map';
						let langMap = {};
						const cachedLang = mw.storage.get(cacheKeyLang);

						if (cachedLang) {
							try {
								langMap = JSON.parse(cachedLang);
							} catch (e) {
								mw.storage.remove(cacheKeyLang);
							}
						}

						if (Object.keys(langMap).length === 0) {
							const langResp = await api.get({
								action: 'query',
								meta: 'siteinfo',
								siprop: 'languages',
								format: 'json',
							});

							if (langResp.query && langResp.query.languages) {
								langResp.query.languages.forEach((l) => {
									langMap[l.code] = l['*'];
								});
								// Cache for 365 days (31536000 seconds)
								mw.storage.set(cacheKeyLang, JSON.stringify(langMap), 31536000);
							}
						}

						const excludedSites = [
							'commonswiki',
							'metawiki',
							'specieswiki',
							'wikidatawiki',
							'incubatorwiki',
							'foundationwiki',
							'testwiki',
							'test2wiki',
							'sourceswiki',
						];
						this.sitelinks = Object.values(entity.sitelinks)
							.filter(
								(s) =>
									s.site.endsWith('wiki') &&
									s.site !== 'azwiki' &&
									!excludedSites.includes(s.site),
							)
							.map((s) => {
								const langCode = s.site
									.replace(/wiki$/, '')
									.replaceAll('_', '-');
								return {
									site: s.site,
									title: s.title,
									lang: langCode,
									name: langMap[langCode] || langCode,
								};
							});

						this.sitelinks.sort((a, b) => a.lang.localeCompare(b.lang));

						if (this.sitelinks.length === 0) {
							this.error = 'Digər dillərdə uyğun məqalə tapılmadı.';
							this.status = 'idle';
							return;
						}

						// Default to enwiki and ruwiki if available
						const defaultSites = [];
						if (this.sitelinks.some((s) => s.site === 'enwiki'))
							defaultSites.push('enwiki');
						if (this.sitelinks.some((s) => s.site === 'ruwiki'))
							defaultSites.push('ruwiki');

						if (defaultSites.length > 0) {
							this.modeSelectedSites.members = [...defaultSites];
							this.modeSelectedSites.parentCats = [...defaultSites];
						}

						this.fetchLocalState();
						this.status = 'idle';
					} catch (e) {
						console.error('CatSuggest init error:', e);
						this.error =
							'Sitelink məlumatları alınarkən xəta baş verdi: ' +
							(e.message || e);
					} finally {
						this.loading = false;
					}
				},

				fetchLocalState() {
					const mode = this.mode;
					const pageName = mw.config.get('wgPageName');
					const qid = this.qid;

					// Return existing promise if still valid for same mode/page
					if (
						this.localStatePromise &&
						this.localStatePromise._meta === `${mode}-${pageName}`
					) {
						return this.localStatePromise;
					}

					const promise = (async () => {
						if (mode === 'members') {
							const cacheKey = `catsuggest-tree-${pageName}`;
							const cached = mw.storage.get(cacheKey);
							if (cached) {
								try {
									const data = JSON.parse(cached);
									if (data && data.members && data.subcats) {
										return {
											members: new Set(data.members),
											subcats: new Set(data.subcats),
										};
									}
								} catch (e) {
									mw.storage.remove(cacheKey);
								}
							}

							const catMemResp = await api.get({
								action: 'query',
								list: 'categorymembers',
								cmtitle: pageName,
								cmtype: 'page|subcat',
								cmlimit: 'max',
								format: 'json',
							});
							const memberSet = new Set();
							const subcatSet = new Set();
							const depth1Subcats = [];
							if (catMemResp.query && catMemResp.query.categorymembers) {
								catMemResp.query.categorymembers.forEach((m) => {
									memberSet.add(m.title);
									if (m.ns === 14) {
										subcatSet.add(m.title);
										depth1Subcats.push(m.title);
									}
								});
							}

							// Depth 2
							if (depth1Subcats.length > 0) {
								const batchSize = 5;
								for (let i = 0; i < depth1Subcats.length; i += batchSize) {
									const batch = depth1Subcats.slice(i, i + batchSize);
									const depth2Promises = batch.map((cat) =>
										api.get({
											action: 'query',
											list: 'categorymembers',
											cmtitle: cat,
											cmtype: 'page|subcat',
											cmlimit: 'max',
											format: 'json',
										}),
									);
									const depth2Results = await Promise.all(depth2Promises);
									depth2Results.forEach((resp) => {
										if (resp.query && resp.query.categorymembers) {
											resp.query.categorymembers.forEach((m) => {
												memberSet.add(m.title);
												if (m.ns === 14) subcatSet.add(m.title);
											});
										}
									});
								}
							}

							// Cache for 3 days
							mw.storage.set(
								cacheKey,
								JSON.stringify({
									members: Array.from(memberSet),
									subcats: Array.from(subcatSet),
								}),
								259200,
							);

							return { members: memberSet, subcats: subcatSet };
						} else {
							// mode === 'parentCats'
							let currentCats = mw.config.get('wgCategories');
							if (currentCats) {
								return currentCats.map(
									(c) => 'Kateqoriya:' + c.replaceAll('_', ' '),
								);
							} else {
								const catResp = await api.get({
									action: 'query',
									titles: pageName,
									prop: 'categories',
									cllimit: 'max',
									format: 'json',
								});
								const pages = catResp.query ? catResp.query.pages : {};
								const page = Object.values(pages)[0];
								return page && page.categories
									? page.categories.map((c) => c.title)
									: [];
							}
						}
					})();

					promise._meta = `${mode}-${pageName}`;
					this.localStatePromise = promise;
					return promise;
				},

				async fetchSuggestions() {
					if (this.selectedSites.length === 0) return;

					this.loading = true;
					this.status = 'loading';
					this.suggestions = [];
					this.error = null;

					const globalQidSet = new Set();
					const qidToSites = new Map();
					const fetchConcurrency = 3;
					const sleep = (ms) =>
						new Promise((resolve) => setTimeout(resolve, ms));

					try {
						const sitesToProcess = [...this.selectedSites];

						// 1. Parallelize local members fetch with foreign wiki fetching
						const localMembersPromise = this.fetchLocalState();

						const processLanguage = async (site) => {
							const siteData = this.sitelinks.find((s) => s.site === site);
							if (!siteData) return;
							const lang = siteData.lang;
							const foreignApi = new mw.ForeignApi(
								`https://${lang}.wikipedia.org/w/api.php`,
								{ userAgent: 'CatSuggest/azwiki' },
							);

							if (this.mode === 'parentCats') {
								const cacheKey = `catsuggest-cache-${this.qid}-${site}`;
								const cached = mw.storage.get(cacheKey);
								if (cached) {
									try {
										const cachedItems = JSON.parse(cached);
										if (
											Array.isArray(cachedItems) &&
											cachedItems.length > 0 &&
											typeof cachedItems[0] === 'string' &&
											cachedItems[0].startsWith('Q')
										) {
											cachedItems.forEach((qid) => {
												globalQidSet.add(qid);
												if (!qidToSites.has(qid))
													qidToSites.set(qid, new Set());
												qidToSites.get(qid).add(lang);
											});
											return;
										}
										mw.storage.remove(cacheKey);
									} catch (e) {
										mw.storage.remove(cacheKey);
									}
								}

								const genResp = await foreignApi.get({
									action: 'query',
									generator: 'categories',
									titles: siteData.title,
									gcllimit: 'max',
									gclshow: '!hidden',
									prop: 'pageprops',
									ppprop: 'wikibase_item',
									format: 'json',
								});

								if (!genResp.query || !genResp.query.pages) return;
								const catQidsForCache = [];
								Object.values(genResp.query.pages).forEach((p) => {
									if (p.pageprops && p.pageprops.wikibase_item) {
										const qid = p.pageprops.wikibase_item;
										globalQidSet.add(qid);
										catQidsForCache.push(qid);
										if (!qidToSites.has(qid)) qidToSites.set(qid, new Set());
										qidToSites.get(qid).add(lang);
									}
								});
								if (catQidsForCache.length > 0) {
									mw.storage.set(
										cacheKey,
										JSON.stringify(catQidsForCache),
										300,
									);
								}
							} else {
								// Mode: members (Optimized with generator)
								const genResp = await foreignApi.get({
									action: 'query',
									generator: 'categorymembers',
									gcmtitle: siteData.title,
									gcmlimit: 'max',
									gcmtype: 'page|subcat',
									prop: 'pageprops',
									ppprop: 'wikibase_item',
									format: 'json',
								});

								if (!genResp.query || !genResp.query.pages) return;
								Object.values(genResp.query.pages).forEach((p) => {
									if (p.pageprops && p.pageprops.wikibase_item) {
										const qid = p.pageprops.wikibase_item;
										globalQidSet.add(qid);
										if (!qidToSites.has(qid)) qidToSites.set(qid, new Set());
										qidToSites.get(qid).add(lang);
									}
								});
							}
						};

						for (let i = 0; i < sitesToProcess.length; i += fetchConcurrency) {
							const batch = sitesToProcess.slice(i, i + fetchConcurrency);
							await Promise.all(batch.map((site) => processLanguage(site)));
							if (i + fetchConcurrency < sitesToProcess.length)
								await sleep(500);
						}

						const localData = await localMembersPromise;

						if (globalQidSet.size === 0) {
							this.error =
								this.mode === 'parentCats'
									? 'Seçilmiş dillərdə kateqoriya tapılmadı.'
									: 'Seçilmiş dillərdə bu kateqoriyanın üzvü tapılmadı.';
							this.status = 'idle';
							return;
						}

						if (this.mode === 'parentCats') {
							const excludedQids = new Set([
								'Q6697530', // Kateqoriya:İnsanlar
								'Q9507857', // Kateqoriya:Kişi
								'Q1410641', // Kateqoriya:Kişilər'
								'Q1410688', // Kateqoriya:Qadın
								'Q7473085', // Kateqoriya:Qadınlar

								'Q7098184', // Kateqoriya:SSRİ
								'Q7031845', // Kateqoriya:Türklər

								'Q2944440', // Kateqoriya:Vikipediya:Qaralama halında olan məqalələr
							]);
							excludedQids.forEach((qid) => globalQidSet.delete(qid));
						}

						const allQids = Array.from(globalQidSet);
						const batchSize = 50;
						const resolvePromises = [];
						for (let i = 0; i < allQids.length; i += batchSize) {
							const batch = allQids.slice(i, i + batchSize);
							resolvePromises.push(
								wikidataApi.get({
									action: 'wbgetentities',
									ids: batch.join('|'),
									props: 'sitelinks',
									sitefilter: 'azwiki',
									format: 'json',
								}),
							);
						}

						const resolveResults = await Promise.all(resolvePromises);
						const rawResults = [];
						resolveResults.forEach((wdResp) => {
							if (!wdResp.entities) return;
							Object.entries(wdResp.entities).forEach(([qid, ent]) => {
								if (ent.sitelinks && ent.sitelinks.azwiki) {
									rawResults.push({
										qid: qid,
										title: ent.sitelinks.azwiki.title,
										sources: Array.from(qidToSites.get(qid) || []),
									});
								}
							});
						});

						if (rawResults.length === 0) {
							this.error = 'Azvikidə uyğun məqalə/kateqoriya tapılmadı.';
							this.status = 'idle';
							return;
						}

						if (this.mode === 'parentCats') {
							let currentCats = mw.config.get('wgCategories');
							if (currentCats) {
								currentCats = currentCats.map(
									(c) => 'Kateqoriya:' + c.replaceAll('_', ' '),
								);
							} else {
								const catResp = await api.get({
									action: 'query',
									titles: mw.config.get('wgPageName'),
									prop: 'categories',
									cllimit: 'max',
									format: 'json',
								});
								const pages = catResp.query ? catResp.query.pages : {};
								const page = Object.values(pages)[0];
								currentCats =
									page && page.categories
										? page.categories.map((c) => c.title)
										: [];
							}

							const suggestionTitles = rawResults.map((r) => r.title);
							const allTitlesToCheck = [
								...new Set([...suggestionTitles, ...currentCats]),
							];
							const allParentsSet = new Set();

							const batchPromises = [];
							for (let i = 0; i < allTitlesToCheck.length; i += 50) {
								const batch = allTitlesToCheck.slice(i, i + 50);
								batchPromises.push(
									api.get({
										action: 'query',
										titles: batch.join('|'),
										prop: 'categories',
										cllimit: 'max',
										format: 'json',
									}),
								);
							}

							const batchResults = await Promise.all(batchPromises);
							batchResults.forEach((parentResp) => {
								if (parentResp.query && parentResp.query.pages) {
									Object.values(parentResp.query.pages).forEach((p) => {
										if (p.categories)
											p.categories.forEach((c) => allParentsSet.add(c.title));
									});
								}
							});

							const seenTitles = new Set();
							this.suggestions = rawResults
								.filter((r) => {
									const isRedundant = allParentsSet.has(r.title);
									const alreadyOnPage = currentCats.includes(r.title);
									if (
										!alreadyOnPage &&
										!isRedundant &&
										!seenTitles.has(r.title)
									) {
										seenTitles.add(r.title);
										return true;
									}
									return false;
								})
								.map((r) => ({
									title: r.title,
									selected: true,
									sources: r.sources,
								}));
						} else {
							// Mode: members
							const { members: currentMembersSet, subcats: currentSubcatsSet } =
								localData;
							const suggestionTitles = rawResults.map((r) => r.title);
							const allPagesCats = new Map();

							const catFetchPromises = [];
							for (let i = 0; i < suggestionTitles.length; i += 50) {
								const batch = suggestionTitles.slice(i, i + 50);
								catFetchPromises.push(
									api.get({
										action: 'query',
										titles: batch.join('|'),
										prop: 'categories',
										cllimit: 'max',
										format: 'json',
									}),
								);
							}

							const catFetchResults = await Promise.all(catFetchPromises);
							catFetchResults.forEach((catsResp) => {
								if (catsResp.query && catsResp.query.pages) {
									Object.values(catsResp.query.pages).forEach((p) => {
										if (p.categories) {
											allPagesCats.set(
												p.title,
												p.categories.map((c) => c.title),
											);
										}
									});
								}
							});

							const seenTitles = new Set();
							this.suggestions = rawResults
								.filter((item) => {
									if (
										currentMembersSet.has(item.title) ||
										seenTitles.has(item.title)
									)
										return false;
									seenTitles.add(item.title);

									const itemCats = allPagesCats.get(item.title) || [];
									const isRedundant = itemCats.some((c) =>
										currentSubcatsSet.has(c),
									);

									return !isRedundant;
								})
								.map((item) => ({
									title: item.title,
									selected: true,
									sources: item.sources,
									isCategory: item.title.startsWith('Kateqoriya:'),
								}));
						}

						this.suggestions.sort((a, b) =>
							a.title
								.replace(/^(Kateqoriya|Category):/i, '')
								.localeCompare(b.title.replace(/^(Kateqoriya|Category):/i, '')),
						);
						if (this.suggestions.length === 0)
							this.error = 'Yeni təklif tapılmadı.';

						// Silent optimization: unselect sites that yielded 0 new suggestions in current mode
						const activeLangs = new Set();
						this.suggestions.forEach((s) =>
							s.sources.forEach((l) => activeLangs.add(l)),
						);
						this.modeSelectedSites[this.mode] = this.modeSelectedSites[
							this.mode
						].filter((site) => {
							const sl = this.sitelinks.find((link) => link.site === site);
							return sl && activeLangs.has(sl.lang);
						});

						this.status = 'results';
					} catch (e) {
						console.error('Xəta:', e);
						this.error = 'Xəta baş verdi: ' + (e.message || e);
						this.status = 'idle';
					} finally {
						this.loading = false;
					}
				},
				async applyChanges() {
					if (this.mode === 'members') {
						await this.addCategoryToArticles();
					} else {
						await this.addParentCategories();
					}
				},
				async addParentCategories() {
					const titlesToAdd = this.suggestions
						.filter((s) => s.selected)
						.map((s) => s.title);
					if (titlesToAdd.length === 0) return;

					const pageTitle = mw.config.get('wgPageName');
					this.status = 'editing';
					this.loading = true;
					this.totalProcessingCount = 1;
					this.processedCount = 0;
					this.currentProcessingTitle = pageTitle;

					try {
						const catText =
							'\n' +
							titlesToAdd
								.map((t) => `[[${t.replaceAll('_', ' ')}]]`)
								.join('\n');

						await api.postWithEditToken({
							action: 'edit',
							title: pageTitle,
							appendtext: catText,
							summary: `[[Vikipediya:Qadcetlər/Kateqoriya təklifçisi|Kateqoriya təklifçisi]] vasitəsilə +${titlesToAdd.length} kateqoriya`,
							tags: 'catsuggest',
							format: 'json',
						});

						this.processedCount = 1;
						this.status = 'success';
					} catch (e) {
						console.error('CatSuggest edit error:', e);
						this.error =
							'Kateqoriyaları əlavə edərkən xəta baş verdi: ' +
							(e.message || e);
						this.status = 'results';
					} finally {
						this.loading = false;
					}
				},
				async addCategoryToArticles() {
					const selectedItems = this.suggestions.filter((s) => s.selected);
					if (selectedItems.length === 0) return;

					this.status = 'editing';
					this.loading = true;
					this.totalProcessingCount = selectedItems.length;
					this.processedCount = 0;

					try {
						const categoryTag = `\n[[${mw.config.get('wgPageName').replaceAll('_', ' ')}]]`;
						const sleep = (ms) =>
							new Promise((resolve) => setTimeout(resolve, ms));

						for (const item of selectedItems) {
							this.currentProcessingTitle = item.title;
							try {
								await api.postWithEditToken({
									action: 'edit',
									title: item.title,
									appendtext: categoryTag,
									summary: `[[Vikipediya:Qadcetlər/Kateqoriya təklifçisi|Kateqoriya təklifçisi]] vasitəsilə +[[${mw.config.get('wgPageName').replaceAll('_', ' ')}]]`,
									tags: 'catsuggest',
									format: 'json',
								});
								this.processedCount++;
								await sleep(300); // Stagger edits
							} catch (e) {
								console.error(`Error adding category to ${item.title}:`, e);
							}
						}

						this.status = 'success';
					} catch (e) {
						console.error('Batch edit error:', e);
						this.error =
							'Məqalələri  edərkən xəta baş verdi: ' + (e.message || e);
						this.status = 'results';
					} finally {
						this.loading = false;
					}
				},
				getPageUrl(title) {
					return mw.util.getUrl(title);
				},
				handleClose() {
					if (this.status === 'success') {
						location.reload();
					} else {
						this.dialogShown = false;
					}
				},
			},
			template: `
				<cdx-dialog
					v-model:open="dialogShown"
					:title="mode === 'members' ? 'Məqalə və altkateqoriya təklifləri' : 'Kateqoriya təklifləri'"
					:use-close-button="true"
					close-button-label="Bağla"
					@update:open="handleClose"
				>
					<template #default>
						<!-- Main Selection UI -->
						<div v-if="status === 'idle' || status === 'loading' || status === 'results'">
							<div v-if="isCategoryPage" style="margin-bottom: 16px;">
								<cdx-tabs v-model:active="mode">
									<cdx-tab name="members" label="Məqalə və altkateqoriya" />
									<cdx-tab name="parentCats" label="Kateqoriya" />
								</cdx-tabs>
							</div>

							<p v-if="mode === 'parentCats'">Digər dillərdəki məqalələrə əlavə edilmiş kateqoriyalar əsasında təkliflər.</p>
							<p v-else>Digər dillərdəki eyni kateqoriyanın üzvləri əsasında bu kateqoriyaya əlavə edilə biləcək məqalələr və altkateqoriyalar.</p>
							
							<div v-if="sitelinks.length > 0 && (status === 'idle' || status === 'loading' || status === 'results')" style="margin-bottom: 12px;">
								<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
									<p style="margin: 0; font-weight: bold;">i seçin:</p>
									<cdx-toggle-switch v-model="allSelected" :disabled="loading">
										Bütün dilləri seç ({{ sitelinks.length }})
									</cdx-toggle-switch>
								</div>
								<div v-if="showSearch" style="margin-bottom: 12px;">
									<cdx-text-input
										v-model="siteFilter"
										placeholder="də axtar..."
										aria-label="də axtar"
										:clearable="true"
									>
										<template #start-icon>
											<cdx-icon name="search" />
										</template>
									</cdx-text-input>
								</div>
								<div :style="gridStyle">
									<div v-for="site in filteredSitelinks" :key="site.site">
										<cdx-checkbox v-model="selectedSites" :inputValue="site.site" :disabled="loading">
											{{ site.name }} ({{ site.lang }})
										</cdx-checkbox>
									</div>
								</div>
								<cdx-message v-if="selectedSites.length > 4" :type="selectedSites.length === sitelinks.length ? 'warning' : 'info'" style="margin-top: 10px; margin-bottom: 10px;">
									{{ selectedSites.length }} dil seçilib. Axtarışın təxminən **{{ estimatedTime }} saniyə** çəkəcəyi gözlənilir.
								</cdx-message>
							</div>

							<div v-if="status === 'loading'">
								<cdx-progress-bar aria-label="Yüklənir..." />
								<p style="text-align: center; margin-top: 8px;">Təkliflər axtarılır...</p>
							</div>
							
							<div v-else-if="error" style="color: var(--color-error, #bf3c2c); padding: 8px; border: 1px solid currentColor; border-radius: 2px;">
								{{ error }}
							</div>

							<div v-if="status === 'results'">
								<p v-if="suggestions.length > 0">Seçilmiş dillər əsasında aşağıdakı təkliflər təqdim olunur:</p>
								<div style="margin-top: 8px;">
									<template v-if="mode === 'members'">
										<div v-if="suggestedPages.length > 0">
											<h5 style="margin: 8px 0 4px 0; border-bottom: 1px solid var(--border-color-subtle, #eaecf0);">Məqalələr ({{ suggestedPages.length }})</h5>
											<div v-for="item in suggestedPages" :key="item.title" style="margin-bottom: 4px;">
												<cdx-checkbox v-model="item.selected">
													<a :href="getPageUrl(item.title)" target="_blank" style="color: var(--color-progressive, #36c); text-decoration: none;" @click.stop>{{ item.title }}</a>
													<span v-if="item.sources && item.sources.length" style="font-size: 0.8em; color: var(--color-subtle, #72777d); margin-left: 4px;">
														[{{ item.sources.join(', ') }}]
													</span>
												</cdx-checkbox>
											</div>
										</div>
										<div v-if="suggestedSubcats.length > 0">
											<h5 style="margin: 12px 0 4px 0; border-bottom: 1px solid var(--border-color-subtle, #eaecf0);">Altkateqoriyalar ({{ suggestedSubcats.length }})</h5>
											<div v-for="item in suggestedSubcats" :key="item.title" style="margin-bottom: 4px;">
												<cdx-checkbox v-model="item.selected">
													<a :href="getPageUrl(item.title)" target="_blank" style="color: var(--color-progressive, #36c); text-decoration: none;" @click.stop>{{ item.title }}</a>
													<span v-if="item.sources && item.sources.length" style="font-size: 0.8em; color: var(--color-subtle, #72777d); margin-left: 4px;">
														[{{ item.sources.join(', ') }}]
													</span>
												</cdx-checkbox>
											</div>
										</div>
									</template>
									<template v-else>
										<div v-for="cat in suggestions" :key="cat.title" style="margin-bottom: 4px;">
											<cdx-checkbox v-model="cat.selected">
												<a :href="getPageUrl(cat.title)" target="_blank" style="color: var(--color-progressive, #36c); text-decoration: none;" @click.stop>{{ cat.title }}</a>
												<span v-if="cat.sources && cat.sources.length" style="font-size: 0.8em; color: var(--color-subtle, #72777d); margin-left: 4px;">
													[{{ cat.sources.join(', ') }}]
												</span>
											</cdx-checkbox>
										</div>
									</template>
								</div>
							</div>
						</div>

						<!-- Progress Screen -->
						<div v-else-if="status === 'editing'" style="text-align: center; padding: 20px 0;">
							<cdx-progress-indicator show-label>
								{{ currentProcessingTitle }} ({{ processedCount }}/{{ totalProcessingCount }})
							</cdx-progress-indicator>
						</div>

						<!-- Success Screen -->
						<div v-else-if="status === 'success'" style="text-align: center; padding: 20px 0;">
							<cdx-icon name="check-circle" action="success" size="large" />
							<p style="color: var(--color-success, #14866d); font-weight: bold; font-size: 1.2em; margin-top: 12px;">Kateqoriyalar uğurla əlavə edildi!</p>
							<p style="margin-top: 8px;">Dəyişiklikləri görmək üçün bu pəncərəni bağlayın.</p>
						</div>
					</template>
					<template #footer>
						<div style="display: flex; justify-content: flex-end; gap: 8px; width: 100%;">
							<cdx-button size="medium" @click="handleClose">{{ status === 'success' ? 'Bağla' : 'İmtina' }}</cdx-button>
							<cdx-button 
								v-if="status === 'idle' || status === 'results'" 
								size="medium" 
								action="progressive" 
								weight="normal" 
								:disabled="!canFetch"
								@click="fetchSuggestions"
							>Təklifləri al</cdx-button>
							<cdx-button 
								v-if="status === 'results' && suggestions.length > 0" 
								size="medium" 
								action="progressive" 
								weight="primary"
								:disabled="selectedCount === 0"
								@click="applyChanges"
							>Əlavə et ({{ selectedCount }})</cdx-button>
						</div>
					</template>
				</cdx-dialog>
			`,
			mounted() {
				// Inject button below categories section
				const catLinks = document.getElementById('catlinks');
				if (catLinks) {
					const btnContainer = document.createElement('div');
					btnContainer.id = 'catsuggest-btn-container';
					btnContainer.style.marginTop = '10px';
					btnContainer.style.textAlign = 'left';
					catLinks.parentNode.insertBefore(btnContainer, catLinks.nextSibling);

					const { createApp } = require('vue');
					const btnApp = createApp({
						components: { 'cdx-button': CdxButton },
						template: `<cdx-button size="medium" action="progressive" @click="openDialog">${mw.config.get('wgNamespaceNumber') === 14 ? 'Təkliflər' : 'Kateqoriya təklifləri'}</cdx-button>`,
						methods: {
							openDialog: () => {
								this.toggleDialog();
							},
						},
					});
					btnApp.mount(btnContainer);
				}
			},
			watch: {
				mode() {
					// Reset state when mode changes via tabs or buttons
					this.suggestions = [];
					this.status = 'idle';
					this.error = null;
				},
				siteFilter(newVal) {
					if (this.siteFilterTimeout) clearTimeout(this.siteFilterTimeout);
					this.siteFilterTimeout = setTimeout(() => {
						this.debouncedSiteFilter = newVal;
					}, 150);
				},
			},
			components: {
				'cdx-button': CdxButton,
				'cdx-dialog': CdxDialog,
				'cdx-progress-bar': CdxProgressBar,
				'cdx-icon': CdxIcon,
				'cdx-checkbox': CdxCheckbox,
				'cdx-message': CdxMessage,
				'cdx-text-input': CdxTextInput,
				'cdx-toggle-switch': CdxToggleSwitch,
				'cdx-tabs': CdxTabs,
				'cdx-tab': CdxTab,
				'cdx-progress-indicator': CdxProgressIndicator,
			},
		});

		const container = document.createElement('div');
		document.body.appendChild(container);
		app.mount(container);
	});
Mənbə — "https://az.wikipedia.org/w/index.php?title=MediaViki:Gadget-catsuggest.js&oldid=8497931"
Informasiya Melumat Axtar