Jump to content

MediaWiki:Gadget-CategoryJumpTo.js

From Wiktionary, the free dictionary

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.


// <nowiki>
/* jshint maxerr:1048576, strict:true, undef:true, latedef:true, esversion:6 */
/* global $, mw, OO */

/**
 * Adds a jump box to category pages.
 *
 * Author(s): Surjection
 * Last updated: 2024-12-19
 */

const mwApi = new mw.Api({ ajax: { headers: { "Api-User-Agent": "CategoryJumpTo gadget by [[User:Surjection]]" } } });

const strings = {
	"en": {
		wiktLangCode: "en",
		jumpToPage: "Jump to page",
		jump: "Jump",
		jumpError: "There was an error while jumping. If this reoccurs, please report it on $1.",
		greasePit: "the Grease Pit",
	}
};

const NS_PROJECT = 4;
const GREASE_PIT_NAME = "Grease pit";
const userLanguage = mw.config.get("wgUserLanguage");
const REQUIRE_TOC = false;
const REQUIRE_CATEGORY_PAGING = true;

const resolveString = (strings, stringKey, defaultValue) => {
	try {
		if (defaultValue == null && stringKey)
			defaultValue = stringKey;
		if (strings == null)
			return defaultValue;
		// try to use user language first
		if (strings[userLanguage] != null) {
			if (stringKey == null)
				return strings[userLanguage];
			else if (strings[userLanguage][stringKey] != null)
				return strings[userLanguage][stringKey];
		}
		// fall back to en
		if (strings.en != null) {
			if (stringKey == null)
				return strings.en;
			else if (strings.en[stringKey] != null)
				return strings.en[stringKey];
		}
		return defaultValue;
	} catch (_) {
		// invalid strings object, etc.
		return defaultValue;
	}
};

const createSearchBox = (jumpCallback) => {
	const outerDiv = $('<div>');
	outerDiv.attr("style", "display: inline-block; margin-top: 0.5em; border: solid 1px var(--border-color-base, gray); border-collapse: collapse;");

	const table = $('<table>');
	outerDiv.append(table);

	const body = $('<div class="center"></div>');
	table.append(body.wrap('<td></td>').parent().wrap('<tr></tr>').parent().wrap('<tbody></tbody>').parent());

	const MAX_LENGTH = 256;
	const textInput = new OO.ui.TextInputWidget({ value: "", maxLength: MAX_LENGTH });
	const jumpButton = new OO.ui.ButtonWidget({ label: resolveString(strings, "jumpToPage", "Jump to page") });

	const submit = () => {
		textInput.setDisabled(true);
		jumpCallback(textInput.getValue().slice(0, MAX_LENGTH)).finally(() => {
			textInput.setDisabled(false);
		});
	};

	textInput.on("enter", () => submit());
	jumpButton.on("click", () => submit());

	const fieldset = new OO.ui.FieldsetLayout({
		items: [
			new OO.ui.ActionFieldLayout(textInput, jumpButton)
		]
	});
	body.append(fieldset.$element);

	return outerDiv;
};

const htmlUnescape = (text) => new DOMParser().parseFromString(text, "text/html").body.textContent;

const getSortKey = (languageCode, text) =>
	new Promise((resolve, reject) => {
		const escapedText = text.replace(/[\x00-\x1F\x21-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]/g, (m) => `&#${m.charCodeAt(0)};`);
		const wikitext = `{{sortkey|${languageCode}|${escapedText}}}`;
		const params =  {
			action: "expandtemplates",
			format: "json",
			prop: "wikitext",
			text: wikitext
		};
		mwApi.post(params).then(response => {
			resolve(htmlUnescape(response.expandtemplates.wikitext));
		}).catch((e) => {
			reject(e);
		});
	});

const addJumpBox = () => {
	const catfix = $(".catfix");
	if (!catfix.length) return;

	let languageCode = catfix.find("span[lang]");
	if (!languageCode.length) return;
	languageCode = languageCode.attr("lang");
	
	// do not display if there are no pages
	if (!$("#mw-pages").length) return;
	
	const toc = $("#toc");
	if (REQUIRE_TOC && !toc.length) return;
	if (REQUIRE_CATEGORY_PAGING && !$('#mw-pages a[href*="pagefrom="], #mw-pages a[href*="pageuntil="]').length) return;

	const searchBox = createSearchBox((string) =>
		new Promise((resolve, reject) => {
			getSortKey(languageCode, string).then((sortKey) => {
				const url = new URL(window.location.href);
				url.searchParams.delete("pagefrom");
				url.searchParams.set("from", sortKey);
				resolve();
				window.location.href = url.href;
			}).catch(() => {
				const greasePitUrl = new mw.Title(GREASE_PIT_NAME, NS_PROJECT).getUrl();
				const greasePitLink = $("<a>").attr("href", greasePitUrl).text(resolveString(strings, "greasePit"));
				const errorMessage = $("<span>").text(resolveString(strings, "jumpError"));
				errorMessage.html(errorMessage.html().replace('\$1', greasePitLink[0].outerHTML));
				mw.notification.notify(errorMessage, { "type": "error" });
				reject();
			});
		})
	);
	const target = toc.length ? toc : catfix;

	searchBox.wrap("<div>").parent().insertBefore(target.first());
};

addJumpBox();

// </nowiki>