MediaWiki:Gadget-catfixRegrouper.js

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.

Fixes subheadings within category lists.

Uses data in MediaWiki:Gadget-catfixRegrouper-Data.js.


// {{documentation}}
// <nowiki>
// implicit dependencies : mediawiki.Title, ext.gadget.catfixRegrouper-Data
/* jshint maxerr:1048576, strict:true, undef:true, latedef:true, es5:true */
/* 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
var 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;
	var cleaned = title.replace(this._clean_regex, "");
	if (this.initials) {
		var 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) {
	var groupDiv = document.createElement("div");
	groupDiv.className = "mw-category-group";
	var groupH3 = document.createElement("h3");
	groupH3.textContent = groupText;
	groupDiv.append(groupH3);
	var groupUl = document.createElement("ul");
	groupDiv.append(groupUl);
	return [groupDiv, groupUl];
}

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

jQuery(function () {
	'use strict';

	var 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 = document.getElementById("catfix"))))
		return;
		
	var regrouperMeta = new RegrouperMetadata();

	// Get the language name and script catfix.
	var langName = catfix.className.split("CATFIX-")[1];
	catfix = catfix.getElementsByTagName("*")[0] || document.createElement("span");

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

	if (!lang)
		return;

	var UNPREFIXED_NAMESPACES = ["", "Talk", "Citations"];
	var 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 + "/");
	}

	var formattedNamespaces = mw.config.get("wgFormattedNamespaces");
	var 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);

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

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

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

		// verify language prefix if the namespace should have one
		var 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
		if (detectScriptFunction)
			sc = detectScriptFunction.call(regrouperData, formattedTitle, lang, sc, namespaceId) || sc;

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

		var newGroup;
		if (groupFunction)
			newGroup = groupFunction.call(regrouperData, formattedTitle, lang, sc, namespaceId);
		else if (scData.group)
			newGroup = scData.group.call(regrouperData, formattedTitle, lang, sc, namespaceId);
		else
			newGroup = defaultGroup.call(regrouperData, formattedTitle, lang, sc, namespaceId);

		return newGroup || oldGroup;
	}

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

	var regroupOk = true;
	var regroupData = new Map();
	// Process each group in the category listing.
	jQuery(GROUP_QUERY)
		.each(function () {
			// Get the existing group.
			var 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 {
						var liText = getLiText(this);
						var newGroup = getGroup(liText, group);
						regroupData.set(liText, newGroup);
					} catch (e) {
						console.error(e);
						regroupOk = false;
					}
				});
		});

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

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

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

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

// </nowiki>