MediaWiki:Gadget-nearby.js

From Wiktionary, the free dictionary
(Redirected from User:Dixtosa/nearby.js)
Jump to navigation Jump to search

Note: You may have to bypass your browser’s cache to see the changes. In addition, after saving a sitewide CSS file such as MediaWiki:Common.css, it will take 5-10 minutes before the changes take effect, even if you clear your cache.

  • Mozilla / Firefox / Safari: hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (Command-R on a Macintosh);
  • Konqueror and Chrome: click Reload or press F5;
  • Opera: clear the cache in Tools → Preferences;
  • Internet Explorer: hold Ctrl while clicking Refresh, or press Ctrl-F5.


// Do a preliminary check to avoid running the gadget unnecessarily.
if ([0, 100, 118].includes(mw.config.values.wgNamespaceNumber) && Array.from(document.querySelectorAll("#catlinks li")).some(cat => cat.textContent.endsWith(" lemmas"))) {
	let actionAPI = new mw.Api({ ajax: { headers: { "Api-User-Agent": "Gadget developed by [[User:Ioaxxere]]" } } });
	let pageName = mw.config.values.wgPageName;
	const N_ENTRIES = 3; // the number of preview entries on each side of the current page

	// Given a page title (no underscores), return the prettified title.
	function makeDisplayTitle(rawTitle) {
		rawTitle = rawTitle.replaceAll("_", " ");

		if (rawTitle.startsWith("Reconstruction:"))
			return "*" + rawTitle.split("/").pop();

		if (rawTitle.startsWith("Appendix:"))
			return rawTitle.split("/").pop();

		if (rawTitle.startsWith("Unsupported titles/"))
			return convertUnsupportedTitle(rawTitle); // from [[MediaWiki:Gadget-UnsupportedTitles.js]]

		return rawTitle;
	}

	let pageCategorySortkeys = new Map();

	// This function recursively builds pageCategorySortkeys, which is a map from each page category to its associated sortkey.
	function categoryQueries(continueParam) {
		let params = {
			action: "query",
			format: "json",
			cllimit: "max",
			clprop: "sortkey",
			prop: "categories",
			titles: pageName
		};

		if (continueParam)
			params.clcontinue = continueParam;

		return actionAPI.get(params).then(response => {
			for (let category of response.query.pages[Object.keys(response.query.pages)[0]].categories) {
				pageCategorySortkeys.set(category.title, category.sortkeyprefix);
			}
			if (response.continue)
				return categoryQueries(response.continue.clcontinue);
		});
	}

	categoryQueries().then(() => {
		for (let h2 of document.querySelectorAll(".mw-heading2 h2")) {
			let language = h2.id.replaceAll("_", " ");
			let category = "Category:" + language + " lemmas";
			if (!pageCategorySortkeys.has(category)) continue; // skip non-lemmas and malformed entries

			let params = {
				action: "query",
				cmlimit: 40, // arbitrary; we don't know how many we need ahead of time
				format: "json",
				list: "categorymembers",
				cmsort: "sortkey",
				cmtitle: category,
				cmstartsortkeyprefix: pageCategorySortkeys.get(category)
			};

			actionAPI.get(params).then(response => {
				// Filter out category members from namespace 14 (Category:)
				let titles = response.query.categorymembers.filter(page => page.ns !== 14).map(page => page.title);

				// pageName is guaranteed to be in this list unless there are over `cmlimit` pages with the same sortkey.
				let currPagePosition = titles.indexOf(pageName.replaceAll("_", " "));

				let entriesAhead = titles.slice(currPagePosition + 1, currPagePosition + N_ENTRIES + 1);
				let entriesBehind = titles.slice(Math.max(currPagePosition - N_ENTRIES, 0), currPagePosition);

				let secondQuery = Promise.resolve();
				if (entriesBehind.length < N_ENTRIES) {
					// If entriesBehind isn't yet long enough, switch direction and get the entries coming before.
					// There are no duplicates between the first and second queries.
					params.cmdir = "desc";
					secondQuery = secondQuery.then(() => actionAPI.get(params));
				}

				secondQuery.then(secondResponse => {
					if (secondResponse) {
						let secondTitles = secondResponse.query.categorymembers.filter(page => page.ns !== 14).map(page => page.title);
						entriesBehind = secondTitles.slice(0, N_ENTRIES - entriesBehind.length).reverse().concat(entriesBehind);
					}

					// Display nearby entries on the page.
					let nearbyEntriesDiv = document.createElement("div");
					nearbyEntriesDiv.className = "nearby-entries-box";
					nearbyEntriesDiv.style = "padding: 6px; margin-top: 10px; background: var(--wikt-palette-palergreen, #f8fffa); width: fit-content; max-width: 100%; border: 1.5px solid var(--wikt-palette-forestgreen, #235923); border-radius: 2px; overflow-wrap: break-word";
					nearbyEntriesDiv.innerText = "» ";

					// Need to account for both HTML formats.
					if (h2.parentElement.nextElementSibling.tagName === "SECTION")
						h2.parentElement.nextElementSibling.prepend(nearbyEntriesDiv);
					else
						h2.parentElement.parentElement.insertBefore(nearbyEntriesDiv, h2.parentElement.nextElementSibling);

					for (entry of entriesBehind) {
						let entryLink = document.createElement("a");
						entryLink.href = "/wiki/" + mw.util.wikiUrlencode(entry) + "#" + h2.id;
						entryLink.title = entry;
						entryLink.textContent = makeDisplayTitle(entry);
						nearbyEntriesDiv.append(entryLink, " • ");
					}

					let selflink = document.createElement("a");
					selflink.className = "mw-selflink selflink";
					selflink.textContent = makeDisplayTitle(pageName);
					nearbyEntriesDiv.append(selflink);

					for (entry of entriesAhead) {
						let entryLink = document.createElement("a");
						entryLink.href = "/wiki/" + mw.util.wikiUrlencode(entry) + "#" + h2.id;
						entryLink.title = entry;
						entryLink.textContent = makeDisplayTitle(entry);
						nearbyEntriesDiv.append(" • ", entryLink);
					}
				});
			})
		}
	});
}