MediaWiki:Gadget-catfixRegrouper.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)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
// {{documentation}}
// <nowiki>
// implicit dependencies : mediawiki.Title, ext.gadget.catfixRegrouper-Data
/* jshint maxerr:1048576, strict:true, undef:true, latedef:true, esversion:6 */
/* global $, mw, RegrouperMetadata */

/* data is in [[MediaWiki:Gadget-catfixRegrouper-Data.js]] */

// characters to ignore when looking for the initial in a title.
// this is embedded inside a RegExp character class
const REGROUPER_INITIALS = "-";

function safeUpperCase(text, dottedDotlessI) {
	if (dottedDotlessI)
		return text.replace(/i/g, "İ").toUpperCase();
	else
		return text.toUpperCase();
}

function safeLowerCase(text, dottedDotlessI) {
	if (dottedDotlessI)
		return text.replace(/I/g, "ı").toLowerCase();
	else
		return text.toLowerCase();
}

function titleCase(text, lang, sc) {
	return safeUpperCase(text.charAt(0), this.dottedDotlessI)
		 + safeLowerCase(text.substring(1), this.dottedDotlessI);
}

function defaultGroup(title, lang, sc) {
	if (title.length < 1) return undefined;
	const cleaned = title.replace(this._clean_regex, "");
	if (this.initials) {
		const initialMatch = cleaned.match(this._initials_regex);
		if (initialMatch) {
			return titleCase(initialMatch[0], lang, sc);
		}
		if (!this.initialFallback) return undefined;
	}

	title = cleaned || title;
	return titleCase(title.charAt(0), lang, sc);
}

function makeGroup(groupText) {
	const groupDiv = document.createElement("div");
	groupDiv.className = "mw-category-group";
	const groupH3 = document.createElement("h3");
	groupH3.textContent = groupText;
	groupDiv.append(groupH3);
	const groupUl = document.createElement("ul");
	groupDiv.append(groupUl);
	return [groupDiv, groupUl];
}

function getLiText(el) {
	const child = $(el).find("a, span").first();
	const rawText = el.textContent || el.innerText;
	return (child.length > 0 && child.text()) || rawText;
}

jQuery(() => {
	'use strict';

	let catfix;

	// Apply only to pages in the Category namespace
	// containing an element with the id "catfix".
	// Set window.disableCatfixRegrouper to true to prevent this script from running.
	if (!(!window.disableCatfixRegrouper
				&& mw.config.get('wgNamespaceNumber') === 14
				&& (catfix = $(".catfix")).length))
		return;
	catfix = catfix[0];
	
	const catfixSpan = catfix.querySelector("span");
	if (!catfixSpan) return;
		
	const regrouperMeta = new RegrouperMetadata();

	// Get the language name and script catfix.
	const langName = decodeURIComponent(catfix.getAttribute("data-anchor").replace(/_/g, " "));
	catfix = catfix.getElementsByTagName("*")[0] || document.createElement("span");

	const lang = catfixSpan.getAttribute("lang");
	const defaultSc = catfixSpan.classList[0] || "None";
	const cachedScriptData = {};

	if (!lang)
		return;

	const UNPREFIXED_NAMESPACES = ["", "Talk", "Citations"];
	const PREFIXED_NAMESPACES = ["Appendix", "Appendix talk", "Reconstruction", "Reconstruction talk"];

	function isEntry(namespaceName, pageName) {
		// main, Talk, Citations,
		// Reconstruction/Appendix (Talk) if it starts with language name and "/"
		return UNPREFIXED_NAMESPACES.indexOf(namespaceName) !== -1
			|| (PREFIXED_NAMESPACES.indexOf(namespaceName) !== -1
				&& pageName.slice(0, langName.length + 1) === langName + "/");
	}

	const formattedNamespaces = mw.config.get("wgFormattedNamespaces");
	const regrouperData = regrouperMeta.getByLang(lang);
	if (!regrouperData) return;

	// set up stuff for the default regrouper
	regrouperData._clean_regex = new RegExp("^[" + ((regrouperData.ignoreAdd || "") + (regrouperData.ignore || REGROUPER_INITIALS)) + "]+");
	if (regrouperData.initials)
		regrouperData._initials_regex = new RegExp("^" + regrouperData.initials.source, regrouperData.initials.flags);

	const groupFunction = regrouperData.group;
	const detectScriptFunction = regrouperData.detectScript;

	function getGroup(pageTitle, oldGroup) {
		const titleobj = new mw.Title(pageTitle);
		const namespaceId = titleobj.getNamespaceId();
		const namespaceName = formattedNamespaces[namespaceId];
		const pageName = titleobj.getMainText();

		if (!isEntry(namespaceName, pageName))
			return oldGroup;

		// verify language prefix if the namespace should have one
		let formattedTitle = pageName;
		const langPrefix = langName + "/";
		if (PREFIXED_NAMESPACES.indexOf(namespaceName) !== -1) {
			if (formattedTitle.startsWith(langPrefix)) {
				formattedTitle = formattedTitle.substring(langPrefix.length);
			} else {
				return oldGroup;
			}
		}

		// ignore unsupported titles unless the language data requests otherwise
		if (formattedTitle.startsWith("Unsupported titles/") && !regrouperData.unsupported)
			return oldGroup;

		// script detection
		let sc = defaultSc;
		if (detectScriptFunction)
			sc = detectScriptFunction.call(regrouperData, formattedTitle, lang, sc, namespaceId) || sc;

		let scData = cachedScriptData[sc];
		if (!scData)
			scData = cachedScriptData[sc] = regrouperMeta.getBySc(sc) || {};

		let newGroup = true;
		// the language group function may return true to fall back
		if (groupFunction)
			newGroup = groupFunction.call(regrouperData, formattedTitle, lang, sc, namespaceId);
		if (newGroup === true && scData.group)
			newGroup = scData.group.call(regrouperData, formattedTitle, lang, sc, namespaceId);
		if (newGroup === true)
			newGroup = defaultGroup.call(regrouperData, formattedTitle, lang, sc, namespaceId);

		return newGroup || oldGroup;
	}

	const GROUP_QUERY = "#mw-pages > .mw-content-ltr .mw-category-group";

	let regroupOk = true;
	const regroupData = new Map();
	// Process each group in the category listing.
	jQuery(GROUP_QUERY)
		.each(function () {
			// Get the existing group.
			const group = $(this).find("h3").first().text();
			if (!group) {
				// Failed to get group -- something has gone wrong.
				regroupOk = false;
				return;
			}

			$(this).find("li")
				.each(function () {
					try {
						const liText = getLiText(this);
						const newGroup = getGroup(liText, group);
						regroupData.set(liText, newGroup);
					} catch (e) {
						console.error(e);
						regroupOk = false;
					}
				});
		});

	// Find the existing groups, which we will delete.
	const groups = jQuery(GROUP_QUERY);
	// Cannot regroup if there are no groups.
	if (!groups.length) return;

	const parent = groups.first().parent()[0];
	if (!parent) return;
	const fragment = document.createDocumentFragment();

	if (regroupOk) {
		let lastGroup, groupUl;
		jQuery(GROUP_QUERY + " li")
			.each(function () {
				const liText = getLiText(this);
				const newGroup = regroupData.get(liText) || "";
				if (lastGroup !== newGroup) {
					const elements = makeGroup(newGroup);
					const groupDiv = elements[0];
					fragment.appendChild(groupDiv);
					groupUl = elements[1];
					lastGroup = newGroup;
				}
				groupUl.appendChild(this);
			});

		groups.remove();
		parent.appendChild(fragment);
	}
});

// </nowiki>