MediaWiki:Gadget-nearby.js

From Linguifex
Jump to navigation Jump to search

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
// <nowiki>
(async () => {

// Do a preliminary check to avoid running the gadget unnecessarily.
if ([...document.querySelectorAll("#catlinks li")].some(cat => cat.textContent.endsWith(" lemmas"))) {
	mw.util.addCSS(`
		.nearby-entries-box {
			padding: 6px;
			margin-top: 10px;
			background: var(--wikt-palette-palergreen);
			width: fit-content;
			max-width: 100%;
			border: 1.5px solid var(--wikt-palette-forestgreen);
			border-radius: 2px;
			overflow-wrap: break-word;
		}
	`);
	
	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.
	const 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;
	};

	// Build pageCategorySortkeys, a map from each page category to its associated sortkey.
	let pageCategorySortkeys = new Map();
	let continueParam;

	while (true) {
		let params = {
			action: "query",
			format: "json",
			cllimit: "max",
			clprop: "sortkey",
			prop: "categories",
			titles: pageName
		};

		if (continueParam)
			params.clcontinue = continueParam;

		let response = await actionAPI.get(params);

		for (let category of Object.values(response.query.pages)[0].categories)
			pageCategorySortkeys.set(category.title, category.sortkeyprefix);

		if (!response.continue) break;
		continueParam = response.continue.clcontinue;
	}

	document.querySelectorAll(".mw-heading2 h2").forEach(async h2 => {
		let language = h2.id.replaceAll("_", " ");
		let category = `Category:${language} lemmas`;
		if (!pageCategorySortkeys.has(category)) return; // 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)
		};
		
		let titles = (await actionAPI.get(params)).query.categorymembers
			.filter(page => page.ns !== 14) // Filter out namespace 14 (Category:)
			.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);

		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";
			entriesBehind = (await actionAPI.get(params)).query.categorymembers
				.filter(page => page.ns !== 14)
				.map(page => page.title)
				.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.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 (let 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 (let 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);
		}
	});
}

})();
// </nowiki>