Jump to content

MediaWiki:Common.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.

This script executes for every user, anonymous or logged-in, using any skin. Additions to this script should be kept to a minimum, as it is a major single point of failure, and for the sake of allowing per-user customisations. Preferably write a gadget instead.

To make sure your script executes after this one, make it depend on the ResourceLoader module site. See mw:ResourceLoader/Developing with ResourceLoader for details.

Note that this code does not run on the mobile website: see MediaWiki:Mobile.js.

See also: Special:Gadgets.


/**
 * Keep code in MediaWiki:Common.js to a minimum as it is unconditionally
 * loaded for all users on every wiki page. If possible create a gadget that is
 * enabled by default instead of adding it here (since gadgets are fully
 * optimized ResourceLoader modules with possibility to add dependencies etc.)
 *
 * Since Common.js isn't a gadget, there is no place to declare its
 * dependencies, so we have to lazy load them with mw.loader.using on demand and
 * then execute the rest in the callback. In most cases these dependencies will
 * be loaded (or loading) already and the callback will not be delayed. In case a
 * dependency hasn't arrived yet it'll make sure those are loaded before this.
 */
"use strict";
// [[Category:Wiktionary scripts]] <nowiki>
/*jshint shadow:true, undef:true, latedef:true, unused:true, esversion:3 */
/*global window, jQuery, mw, importScript, importStylesheet, $ */

/** [[WT:PREFS]] v2.0 **/
// Note: copied into Mobile.js - please keep in sync
try {
	(function () {
		var prefs;
		try {
			prefs = window.localStorage.getItem("AGprefs");
		} catch (e) {
			prefs = jQuery.cookie("AGprefs");
		}

		prefs = prefs && JSON.parse(prefs);

		if (mw.config.get("wgUserGroups").indexOf("autoconfirmed") !== -1) return;

		if (mw.config.get("wgUserGroups").indexOf("user") === -1) {
			// XXX: [[Wiktionary:Preferences/V2]] is just a temporary page

			mw.loader.using(["mediawiki.util"], function () {
				mw.util.addPortletLink(
					mw.config.get("skin") === "vector-2022"
						? "p-vector-user-menu-overflow"
						: "p-personal",
					mw.util.getUrl("Wiktionary:Preferences for users without an account"),
					"Preferences",
					"pt-agprefs",
					"Personalise Wiktionary (settings are kept per-browser).",
					"",
					document.getElementById("pt-createaccount-2") ||
						document.getElementById("pt-createaccount")
				);
			});

			if (
				mw.config.get("wgAction") === "view" &&
				mw.config.get("wgPageName") ===
					"Wiktionary:Preferences_for_users_without_an_account"
			) {
				mw.loader.load("ext.gadget.AGprefs"); // [[MediaWiki:Gadget-AGprefs.js]]
			}
		}

		if (!prefs) return;

		mw.loader.state(
			"wiktionary_this_gadget_has_been_disabled_by_the_user",
			"missing"
		);
		for (var key in prefs.modules) {
			if (prefs.modules[key]) {
				mw.loader.load([key]);
			} else {
				// unavoidable race condition. to prevent it, every enabled-by-default gadget should have "site" as a dependency
				if (mw.loader.getState(key) !== "ready") {
					mw.loader.moduleRegistry[key].dependencies.push(
						"wiktionary_this_gadget_has_been_disabled_by_the_user"
					);
					mw.loader.state(key, "missing");
				} else {
					// XXX
					mw.log.warn(
						key +
							" could not be disabled; make sure it has 'site' declared as a dependency"
					);
				}
			}
		}

		for (var key in prefs.sheets) {
			importStylesheet("MediaWiki:Gadget-" + key);
		}

		for (var key in prefs.scripts) {
			importScript("MediaWiki:Gadget-" + key);
		}

		if (mw.config.get("wgUserGroups").indexOf("user") !== -1)
			mw.loader.using(["mediawiki.api"], function () {
				var changes = [];
				for (var key in prefs.gadgets)
					changes.push(
						"gadget-" + key + "=" + (prefs.gadgets[key] ? "1" : "0")
					);

				new mw.Api()
					.postWithToken("options", {
						action: "options",
						change: changes.join("|"),
					})
					.then(function () {
						jQuery.cookie("AGprefs", null);
						try {
							window.localStorage.removeItem("AGprefs");
						} catch (e) {
							/* */
						}
						mw.notify(
							jQuery(
								'<b>Your <a href="/wiki/Wiktionary:Preferences/V2">per-browser preferences</a> have been migrated</b><br/><br/>' +
									'From now on, you should use your <a href="/wiki/Special:Preferences">user preferences page</a>. ' +
									"Preferences will no longer apply after you log out."
							)
						);
					});
			});
	})();
} catch (e) {
	mw.log.warn(e);
}

// Append #English to all links in definition sense lines
// Note: copied into Mobile.js - please keep in sync
mw.loader.using("user").then(function () {
	if (!window.noDefinitionLineFragmentAddition)
		$(function () {
			var ns = mw.config.get("wgNamespaceNumber");
			var re = /[#?:]/;
			// Run this code in the main or Reconstruction namespaces,
			// and on the Main Page (for WOTD and FWOTD),
			// and in the Appendix namespace when there is a lemma or non-lemma form category.
			if (
				ns === 0 ||
				ns === 118 ||
				(ns === 4 && mw.config.get("wgTitle") === "Main Page") ||
				(ns === 100 &&
					mw.config.get("wgCategories").some(function (c) {
						return / (?:lemmas|non-lemma forms)$/.test(c);
					}))
			) {
				// Look for links inside a numbered list (<ol> tag).
				// Must use [lang|=en] rather than :lang(en) because, at least in Firefox,
				// :lang(en) always matches wherever it is put in a selector
				// because all text is within <html lang="en">.
				document
					.querySelectorAll(
						".mw-parser-output ol a:not(.mw-parser-output [lang] a), .mw-parser-output ol [lang|=en] a"
					)
					.forEach(function (el) {
						// Only append to local existing main-namespace links without an existing anchor
						var href = el.getAttribute("href");
						if (href && !re.test(href) && href[1] !== "/") {
							el.href += "#English";
						}
					});
			}
		});
});

mw.loader.using("mediawiki.util").done(function () {
	/** &withmodule= query parameter **/
	if (mw.util.getParamValue("withmodule"))
		mw.loader.load(mw.util.getParamValue("withmodule").split(","));

	/** &preloadtext= and &preloadminor= **/
	if (mw.config.get("wgAction") === "edit")
		jQuery(document).ready(function () {
			var wpTextbox1 = document.getElementById("wpTextbox1");
			var wpMinoredit = document.getElementById("wpMinoredit");
			if (!wpTextbox1) return;

			var preloadtext = mw.util.getParamValue("preloadtext");
			var preloadminor = mw.util.getParamValue("preloadminor");

			if (preloadtext && !wpTextbox1.value) wpTextbox1.value = preloadtext;
			if (preloadminor !== null && wpMinoredit)
				wpMinoredit.checked = !/^(0|false|no|)$/i.test(preloadminor);
		});

	/** Monthly subpages; see [[Template:discussion recent months|discussion recent months]] **/
	/*  See also: [[Special:AbuseFilter/43]]  */
	if (
		/^Wiktionary:(Beer_parlour|Grease_pit|Tea_room|Etymology_scriptorium|Information_desk)$/.test(
			mw.config.get("wgPageName")
		)
	)
		jQuery(document).ready(function () {
			var nNSR = document
				.getElementById("new-section-redirect")
				.getElementsByTagName("a")[0];
			var caAddSection = document.getElementById("ca-addsection");
			if (!caAddSection) {
				caAddSection = mw.util.addPortletLink(
					mw.config.get("skin") === "vector" ? "p-views" : "p-cactions",
					nNSR.href,
					"+",
					"ca-addsection",
					"Start a new section",
					"+",
					document.getElementById("ca-history")
				);
			} else {
				if (caAddSection.tagName === "A") {
					caAddSection.href = nNSR.href;
				} else {
					caAddSection.getElementsByTagName("a")[0].href = nNSR.href;
				}
			}
		});
});

// On category pages, remove "0 c" or "0 e" from parentheses in listing of
// subcategories.
document
	.querySelectorAll(".CategoryTreeItem bdi + span")
	.forEach(function (elem) {
		elem.innerHTML = elem.innerHTML.replace(/(?:, )?(?<!\d)0 [ce](?:, )?/, "");
	});

// == "Did you mean" auto redirect ==
/**
 * This will redirect in 3 seconds if a link enclosed in id="did-you-mean"
 * is found, and add the text "Auto-redirected from X" under the top header
 * if a rdfrom is passed in the get parameters.
 * Pages with wynn ([[ƿ]]) will be redirected immediately.
 **/

$.when(mw.loader.using(["user", "mediawiki.util"]), $.ready).done(function () {
	if (window.disableAutoRedirect) return;

	var rdFromValue = mw.util.getParamValue("rdfrom");
	if (rdFromValue) {
		$("#siteSub").after(
			$("<div>")
				.attr("id", "contentSub")
				.append(document.createTextNode("(Auto-redirected from "))
				.append(
					$("<a>", {
						href: mw.util.getUrl(rdFromValue, { redirect: "no" }),
						addClass: "new",
					}).text(rdFromValue)
				)
				.append(document.createTextNode(")"))
		);
	} else {
		// Redirect as quickly as possible from [[ƿ]] title to [[w]] title.
		var pageTitle = mw.config.get("wgTitle");
		var isWynnTitle = /ƿ/i.test(pageTitle);
		var didYouMean = $("#did-you-mean a").html();
		var target = didYouMean
			? didYouMean
			: isWynnTitle
			? $("#go-to-search-page a").html()
			: null;
		var timeout = isWynnTitle ? 0 : 3000;
		window.setTimeout(function () {
			var canRedirect = mw.util.getParamValue("redirect") != "no";
			var action = mw.config.get("wgAction");

			if (
				target &&
				target !== pageTitle &&
				canRedirect &&
				!window.disableAutoRedirect &&
				(action == "view" || (isWynnTitle && action == "edit")) &&
				mw.config.get("wgArticleId") === 0 &&
				mw.config.get("wgNamespaceNumber") === 0 &&
				!/Redirected from/.test(jQuery("#contentSub").html())
			) {
				window.location = mw.util.getUrl(target, { rdfrom: pageTitle });
			}
		}, timeout);
	}
});

/* ==Page specific extensions== */
/* ===[[Wiktionary:Main Page]]=== */
// Note: copied into Mobile.js - please keep in sync
mw.loader.using("mediawiki.util", function () {
	// Hide the title and "Redirected from" (maybe we should keep the redirected from so's people update their bookmarks ;)
	// Broken in IE!
	if (
		mw.config.get("wgIsMainPage") &&
		!(
			mw.config.get("wgAction") === "view" ||
			mw.config.get("wgAction") === "submit"
		)
	) {
		mw.util.addCSS(".firstHeading { display: block !important; }");
		mw.util.addCSS("#contentSub { display: inline !important; }");
	}

	if (mw.config.get("wgIsMainPage")) {
		$(function () {
			mw.util.addPortletLink(
				"p-lang",
				"//meta.wikimedia.org/wiki/Wiktionary#List_of_Wiktionaries",
				"Complete list",
				"interwiki-completelist",
				"Complete list of Wiktionaries"
			);
		});

		// Workaround for [[phab:T335552]].
		document.querySelector(".mw-searchInput").autocapitalize = "off";
	}
});

/* ===[[Special:Search]]=== */
// [[MediaWiki:FindTrans.js]], formerly [[User:Yair rand/FindTrans.js]]
if (mw.config.get("wgPageName") === "Special:Search") {
	mw.loader.load(
		"/w/index.php?title=MediaWiki:FindTrans.js&action=raw&ctype=text/javascript"
	);
}

/* ===[[Wiktionary:Fonts/list]]=== */
if (
	mw.config.get("wgAction") === "view" &&
	mw.config.get("wgPageName") === "Wiktionary:Fonts/list"
) {
	mw.loader.load("ext.gadget.InteractiveFontList"); // [[MediaWiki:Gadget-InteractiveFontList.js]]
}

/* ===[[Wiktionary:Gadget preferences]]=== */
// Note: copied into Mobile.js - please keep in sync
if (
	mw.config.get("wgAction") === "view" &&
	mw.config.get("wgPageName") === "Wiktionary:Gadget_preferences"
) {
	mw.loader.load("ext.gadget.WiktGadgetPrefsPage"); // [[MediaWiki:Gadget-WiktGadgetPrefsPage.js]]
}

/* == [[WT:FEED]] == */
// used to be [[User:Conrad.Irwin/feedback.js]]
$.when(mw.loader.using("mediawiki.util"), $.ready).done(function () {
	var fb_comment_url = mw.util.getUrl("Wiktionary:Feedback", {
		action: "edit",
		section: "new",
		preload: "Wiktionary:Feedback/preload",
		editintro: "Wiktionary:Feedback/intro",
		preloadtitle: "[[:" + mw.config.get("wgPageName") + "]]",
	});

	var fb_comment = "If you have time, leave us a note.";

	if (mw.config.get("skin") === "vector-2022") {
		var vectorMenuContent = $("<a>")
			.attr("href", fb_comment_url)
			.text(fb_comment)
			.appendTo($("<li>").addClass("mw-list-item"))
			.parent()
			.appendTo($("<ul>").addClass("vector-menu-content-list"))
			.parent()
			.appendTo($("<div>").addClass("vector-menu-content"))
			.parent();
		var vectorMenuHeading = $("<div>")
			.addClass("vector-menu-heading")
			.text("Feedback");
		var vectorMenu = $("<div>")
			.addClass("vector-menu mw-portlet mw-portlet-navigation")
			.attr("id", "p-feedback")
			.append(vectorMenuHeading)
			.append(vectorMenuContent);
		vectorMenu.appendTo($("#vector-main-menu"));
	} else {
		$("<a>")
			.attr("href", fb_comment_url)
			.text(fb_comment)
			.appendTo($("<p>").css("font-size", "80%"))
			.parent()
			.appendTo($("<div>").addClass("body"))
			.parent()
			.before($("<h3>Feedback</h3>"))
			.appendTo($("<div>").addClass("portal expanded").attr("id", "p-feedback"))
			.parent()
			.appendTo($("#mw-panel"));
	}
});

/* == Toggle functionality only failed test == */
// all tests for module testcases
$(function () {
	$("table.unit-tests th.unit-tests-img-corner").on("click", function () {
		$(this).closest("table.unit-tests").toggleClass("unit-tests-hide-passing");
	});
});

// Text after → on history pages is plain wikitext but on the page it is expanded. Fixing only [[Template:temp]]
// Also update it in change summaries
$(function () {
	if (/\.7B\.7Btemp\.7C(.*?)\.7D\.7D/.test(location.href)) {
		window.location = location.href.replace(/\.7B\.7Btemp.7C/g, ".7B.7B");
	}

	if (mw.config.get("wgAction") !== "edit") return;
	if (!/[?&]section=\d/.test(location.href)) return;
	var wpSummary = document.getElementById("wpSummary");
	if (!wpSummary) return;
	if (wpSummary.value.substr(0, 3) !== "/* ") return;
	if (wpSummary.value.substr(wpSummary.value.length - 4) !== " */ ") return;
	wpSummary.value = wpSummary.value.replace(/\{\{temp(late)?\|/g, "{{");
});

// Various fixes for the trees generated by [[Module:family tree]].
$(function () {
	// Show the top toggle.
	$(".familytree-toptoggle").css("display", "");

	// Initialize the text of the toggles.
	$(".familytree")
		.get()
		.forEach(function (tree) {
			var isCollapsed = tree.classList.contains("mw-collapsed");
			var toggles = $(tree).find(".familytree-toggle");
			if (toggles[0]) {
				var customToggleClass = Array.prototype.filter.call(
					toggles[0].classList,
					function (className) {
						return className.indexOf("mw-customtoggle") === 0;
					}
				)[0];
				if (customToggleClass) {
					var toggledElement = $(
						"#" +
							customToggleClass.replace(
								"mw-customtoggle",
								"mw-customcollapsible"
							)
					);
					if (toggledElement) {
						toggles.html(
							toggledElement.data(isCollapsed ? "expandtext" : "collapsetext")
						);
					}
				}
			}
		});

	// Change the text in the custom toggles generated by [[Module:family tree]]
	// when they are clicked.
	$(".mw-collapsible").on(
		"beforeExpand.mw-collapsible beforeCollapse.mw-collapsible",
		function (event) {
			if (this.id.indexOf("mw-customcollapsible") === 0) {
				var toggle = $(
					"." + this.id.replace("mw-customcollapsible", "mw-customtoggle")
				);
				if (event.type === "beforeExpand") {
					toggle.html(this.dataset.collapsetext);
				} else {
					// beforeCollapse
					toggle.html(this.dataset.expandtext);
				}
				event.stopPropagation();
			}
		}
	);
});

/* == RFC, RFD, RFV category lists == */
// Replaces full category name in [[WT:RFC]], [[WT:RFD]], [[WT:RFV]]
// with name of language:
// Category:Requests for cleanup in English entries → English
$(".tagged-rfcs, .tagged-rfds, .tagged-rfvs")
	.find(".CategoryTreeItem a")
	.html(function (i, content) {
		return content.replace(
			/^Requests for (?:cleanup|deletion|verification) in (.+?) entries$/,
			"$1"
		);
	});

// XXX is the following code redundant to the above?
if (mw.config.get("wgTitle").indexOf("by language") != -1)
	$("a.CategoryTreeLabelCategory").text(function (index, content) {
		return content.replace(
			/^Requests for (?:verification|deletion|cleanup) in (.+) entries$/,
			"$1"
		);
	});

/* == Make it easy to update language name-to-code and code-to-name data modules == */
// This regex will always match.
if (
	[
		"Module:languages/code_to_canonical_name",
		"Module:languages/canonical_names",
		"Module:etymology_languages/code_to_canonical_name",
		"Module:etymology_languages/canonical_names",
		"Module:families/code_to_canonical_name",
		"Module:families/canonical_names",
		"Module:Hani-sortkey/data/serialized",
	].indexOf(mw.config.get("wgPageName").match(/^[^\/]*(?:\/[^\/]+)?/)[0]) !== -1
)
	importScript("MediaWiki:UpdateLanguageNameAndCode.js");

/* == WikiHiero kludge == */
$("table.mw-hiero-outer")
	.first()
	.each(function () {
		importScript("MediaWiki:WikiHieroTempFix.js");
	});

/* == Hide certain parts of the page from Google snippets == */
$(".mw-editsection, #toc, #catlinks").attr("data-nosnippet", "");
if (window.navigator.userAgent.toLowerCase().includes("googlebot")) {
	$(".mw-editsection, #toc, #catlinks").css("display", "none");
}

// </nowiki>
// The rest of the scripts are at [[MediaWiki:Gadget-legacy.js]].
// Most of them should be converted into gadgets as time and resources allow.