MediaWiki:Gadget-CodeLinks.js

/*jshint undef:true, latedef:true */ /*global mw, jQuery */

jQuery(function CodeLinksIIFE { 'use strict';

// by John Gruber, from https://daringfireball.net/2010/07/improved_regex_for_matching_urls var URLRegExp = /\b((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s<>]+|\(([^\s<>]+|(\([^\s<>]+\)))*\))+(?:\(([^\s<>]+|(\([^\s<>]+\)))*\)|[^\s`!\[\]{};:'".,<>?«»“”‘’]))/i;

function processComment(node) { var wikilinkMatch, templateMatch, URLMatch, textNode = node.firstChild; // always a text node. while (		(wikilinkMatch = /\[\[([^|{}\[\]\n]+)?(?:\|.*?)?]]/.exec(textNode.data))		|| (templateMatch = /(\{\{(?:#invoke:)?)([^|{}\[\]\n#]+)(?=\||}})/i.exec(textNode.data))		|| (URLMatch = URLRegExp.exec(textNode.data))	) { var link = document.createElement('a'), start = (wikilinkMatch || templateMatch || URLMatch).index, linkText; link.classList.add("code-link"); if (URLMatch) { var URL = URLMatch[0]; link.href = URL; linkText = URL; } else { var fullPageName; if (wikilinkMatch) { linkText = wikilinkMatch[0]; fullPageName = wikilinkMatch[1]; } else if (templateMatch) { var prefix = templateMatch[1], pageName = templateMatch[2]; linkText = pageName; fullPageName = (prefix === '{{#invoke:' ? 'Module:' : 'Template:') + pageName; link.title = fullPageName; start += prefix.length; }			link.href = mw.util.getUrl(fullPageName); }		var beforeLink = textNode.data.substring(0, start), afterLink = textNode.data.substring(start + linkText.length); textNode.data = afterLink; link.appendChild(document.createTextNode(linkText)); node.insertBefore(link, textNode); node.insertBefore(document.createTextNode(beforeLink), link); // ensure all matches are null at beginning of loop body; is this necessary? wikilinkMatch = templateMatch = URLMatch = null; } }

function each(coll, walk) { return Array.prototype.forEach.call(coll, walk); }

var commentClasses = [ 'c', 'c1', 'cm' ]; each(document.getElementsByClassName('mw-highlight'), function (codeBlock) {	each(commentClasses, function (commentClass) { each(codeBlock.getElementsByClassName(commentClass), processComment); }); });

// Link module names after `require` and `mw.loadData`, and tracking page // names after `require("Module:debug").track`. var copyArray = Array.from ? Array.from.bind(Array) : function copyArray(array) { return Array.prototype.slice.call(array); };

var classes = { identifier: "n", functionName: "nb", singleQuoteString: "s1", doubleQuoteString: "s2", };

var trackingPageElements = [], moduleNames = [], dataModuleNames = [];

var functionNames = document.getElementsByClassName(classes.functionName);

Array.prototype.forEach.call(functionNames, function (functionName) {	var text = functionName.firstChild.nodeValue;	if (text !== "require")		return;	var next = functionName.nextElementSibling;	var nextText = next && next.firstChild && next.firstChild.nodeValue;	var hasParenthesis = nextText === "("; if (hasParenthesis) { next = next.nextElementSibling; nextText = next && next.firstChild && next.firstChild.nodeValue; }	var classList = next.classList; if (!(classList.contains(classes.singleQuoteString) || classList.contains(classes.doubleQuoteString))) return; var string = next; var stringValue = nextText; if (!stringValue) return; next = next.nextElementSibling; nextText = next && next.firstChild && next.firstChild.nodeValue; if (hasParenthesis && nextText && nextText[0] !== ")")		return;	moduleNames.push(string);

// FIXME!!! Tracking code has been moved to Module:debug/track. The following needs updating // to handle calls of the form require("Module:debug/track")("foo"). if (hasParenthesis ? nextText === ")." : nextText === "." && (/^["']mod(?:ule)?:(.+)["']$/i.exec(stringValue) || [])[1] === "debug") {		next = next.nextElementSibling;		nextText = next && next.firstChild && next.firstChild.nodeValue;		if (nextText !== "track")			return;		next = next.nextElementSibling;		if (!next) return;		nextText = next && next.firstChild && next.firstChild.nodeValue;		var trackWithParenthesis = false;		if (nextText === "(") { next = next.nextElementSibling; trackWithParenthesis = true; }		classList = next.classList; if (!(classList.contains(classes.singleQuoteString) || classList.contains(classes.doubleQuoteString))) return; nextText = next && next.firstChild && next.firstChild.nodeValue; if (!nextText) return; // If there was a parenthesis on one side – `track("...")` rather than // `track "..."` – make sure there's a matching parenthesis on the other side. if (trackWithParenthesis) { var after = next.nextElementSibling; var afterText = after && after.firstChild && after.firstChild.nodeValue; if (afterText !== ")")				return;		}		trackingPageElements.push(next);	} });

var strings = copyArray(document.getElementsByClassName(classes.singleQuoteString)) .concat(copyArray(document.getElementsByClassName(classes.doubleQuoteString)));

Array.prototype.forEach.call(strings, function (string) {	if (moduleNames.indexOf(string) !== -1 || trackingPageElements.indexOf(string) !== -1)		return;	var stringValue = string.firstChild.nodeValue;	if (!/^["'](?:module|mod):/i.test(stringValue))		return;	var prev = string.previousElementSibling;	var prevText = prev && prev.firstChild && prev.firstChild.nodeValue;	if (prevText === "(") {		var next = string.nextElementSibling;		var nextText = next && next.firstChild && next.firstChild.nodeValue;		if (!(nextText && nextText[0] === ")"))			return;		prev = prev.previousElementSibling;		prevText = prev && prev.firstChild && prev.firstChild.nodeValue;	}	if (prevText !== "loadData")		return;	prev = prev.previousElementSibling;	prevText = prev && prev.firstChild && prev.firstChild.nodeValue;	if (prevText !== ".")		return;	prev = prev.previousElementSibling;	prevText = prev && prev.firstChild && prev.firstChild.nodeValue; if (prevText !== "mw") return; dataModuleNames.push(string); });

if (moduleNames.length > 0 || trackingPageElements.length > 0	mw.loader.using("mediawiki.util").done(function { function addLink(element, page) { if (!(element instanceof Element)) throw new TypeError("Expected Element object"); var link = document.createElement("a"); link.href = mw.util.getUrl(page); // put text node from element inside link var firstChild = element.firstChild; if (!(firstChild instanceof Text)) throw new TypeError("Expected Text object"); link.appendChild(firstChild); element.appendChild(link); // put link inside syntax-highlighted string }		// Link module names to module pages, or to the section in the Scribunto // manual. moduleNames.concat(dataModuleNames).forEach(function (module) {			var link = document.createElement("a");			var stringValue = module.firstChild.nodeValue;			var moduleName = stringValue.substring(1, stringValue.length - 1);			var linkPage = /^mod(?:ule)?:/i.test(moduleName)				? moduleName				: "mw:Extension:Scribunto/Lua reference manual#" + moduleName;			addLink(module, linkPage);		}); // Link tracking pages to Special:WhatLinksHere. trackingPageElements.forEach(function (trackingPage) {			var text = trackingPage.firstChild && trackingPage.firstChild.nodeValue;			if (!text)				return;			var trackingCode = text.substring(1, text.length - 1);			addLink(trackingPage, "Special:WhatLinksHere/Wiktionary:Tracking/" + trackingCode);		}); }); }
 * dataModuleNames.length > 0) {

});