Jump to content

User:NguoiDungKhongDinhDanh/WhatLinksHereSnippets.js

From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// For attribution: [[User:Bradv/Scripts/WhatLinksHereSnippets.js]]
/* jshint maxerr: 9999, undef: true, unused: true, quotmark: single */
/* globals $, mw */

(() => {
	if (mw.config.get('wgAction') !=='view' || mw.config.get('wgCanonicalSpecialPageName') !== 'Whatlinkshere') {
		return;
	}

	mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.util', 'oojs-ui-core']).done(() => {
		'use strict';
	
		const api = new mw.Api();
		
		const IS_TEMPLATE_MESSAGE_NAME = 'istemplate';
		const classes = Object.freeze({
			SHOW_HIDE: 'showsnippets-showhide',
			SNIPPETS_SHOWN: 'showsnippets-shown',
			SNIPPET: 'showsnippets-snippet',
			HIGHLIGHT: 'showsnippets-highlight'
		});
		
		mw.util.addCSS(`
			.${classes.SHOW_HIDE} {
				float: right;
			}
			.${classes.SNIPPET} {
				display: none;
				padding: 0.5em;
				margin: 0 0 0.5em 1em;
				font-size: 0.8em;
			}
			body.${classes.SNIPPETS_SHOWN} .${classes.SNIPPET} {
				display: revert;
			}
			.${classes.HIGHLIGHT} {
				background-color: #ffff99;
			}
		`);

		setUpButtons();
		
		function setUpButtons() {
			const showButton = new OO.ui.ButtonWidget({
				classes: [classes.SHOW_HIDE],
				label: 'Show snippets',
				flags: ['primary', 'progressive']
			});
			const hideButton = new OO.ui.ButtonWidget({
				classes: [classes.SHOW_HIDE],
				label: 'Hide snippets',
				flags: ['secondary']
			});
			
			hideButton.$element.hide();
			
			const $pagerBar = $('#mw-content-text > .mw-pager-navigation-bar').first();
			const $showingNItemsParagraph = $pagerBar.prev();
			$showingNItemsParagraph.before(showButton.$element).before(hideButton.$element);
			
			function toggleButtonsAndBody() {
				showButton.$element.toggle();
				hideButton.$element.toggle();
				$('body').toggleClass(classes.SNIPPETS_SHOWN);
			}
			
			showButton.$element.click(async () => {
				toggleButtonsAndBody();
				
				if ($(`.${classes.SNIPPET}`).length === 0) {
					const relevantPageName = mw.config.get('wgRelevantPageName');
					const regexes = makeRegexes(relevantPageName);
					
					console.log(regexes);

					await api.loadMessagesIfMissing(IS_TEMPLATE_MESSAGE_NAME);
					loadSnippets(regexes);
				}
			});
			
			hideButton.$element.click(() => {
				toggleButtonsAndBody();
			});
		}
		
		async function loadSnippets(regexes) {
			const $rows = $('#mw-whatlinkshere-list > li');
			
			for (const row of $rows) {
				await loadSnippet($(row), regexes);
			}
		}
		
		async function loadSnippet($row, regexes) {
			const pageName = $row.find('a:only-child').text();
			
			console.log(pageName);
			
			const text = await getWikitext(pageName) ?? '';
			const transcluded = rowIsForTransclusion($row);
			
			const [matches, highlight] = (
				transcluded ?
				[text.match(regexes.transclusions), regexes.transclusionsHighlight] :
				[text.match(regexes.links), regexes.linksHighlight]
			);
			
			const highlightedMatches = (matches ?? []).map(match => {
				const sanitized = match.trim().replaceAll('<', '&lt;').replaceAll('>', '&gt;');
				
				return sanitized.replace(highlight, match => `<span class="${classes.HIGHLIGHT}">${match}</span>`);
			});
			
			for (const match of highlightedMatches) {
				const $snippet = $('<pre>').addClass(classes.SNIPPET).html(match);
				const $sublist = $row.children('ul');
				
				if ($sublist.length) {
					$snippet.insertBefore($sublist);
				} else {
					$snippet.appendTo($row);
				}
			}
		}

		async function getWikitext(pageName) {
			try {
				const response = await api.get({
					action: 'parse',
					page: pageName,
					prop: 'wikitext',
					format: 'json',
					formatversion: 2
				});
				return response.parse.wikitext;
			} catch {
				return undefined;
			}
		}
		
		function rowIsForTransclusion($row) {
			const $textNodes = $row.contents().filter((_, node) => node.nodeType === Node.TEXT_NODE);
			const isTemplate = mw.message(IS_TEMPLATE_MESSAGE_NAME).text();
			
			return $textNodes.text().includes(isTemplate);
		}
		
		function makeRegexes(rawPageName) {
			const namespaceAliasesToIDs = mw.config.get('wgNamespaceIds');
			const pageName = mw.Title.newFromText(rawPageName);
			const escape = mw.util.escapeRegExp;

			function ofEitherCase(character) {
				const upper = mw.Title.phpCharToUpper(character);
				const lower = character.toLowerCase();

				return `[${escape(upper + lower)}]`;
			}

			const titlePattern = (() => {
				const words = pageName.title.split('_');
				const firstWord = words[0];
				const firstCharacter = firstWord[0];

				const firstWordEscaped = `${ofEitherCase(firstCharacter)}${escape(firstWord.slice(1))}`;

				return [firstWordEscaped, ...words.slice(1).map(escape)].join('[_ ]+');
			})();

			const namespacePattern = (() => {
				const aliases = Object.keys(namespaceAliasesToIDs).filter(
					alias => namespaceAliasesToIDs[alias] === pageName.namespace
				);

				const escapedAliases = aliases.map(alias => {
					const words = alias.split('_');
					const escapedWords = words.map(word => word.split('').map(ofEitherCase).join(''));
					
					return escapedWords.join('[_ ]+');
				});
				
				return `(?:${escapedAliases.join('|')})`;
			})();

			const pageIsTemplate = pageName.namespace === namespaceAliasesToIDs.template;
			const transclusion = (
				pageIsTemplate ?
				String.raw`\{\{[_ ]*(?:(?::[_ ]*)?${namespacePattern}[_ ]*:[_ ]*)?${titlePattern}[_ ]*(?:[|][\s\S]*?)?}}` :
				String.raw`\{\{[_ ]*${namespacePattern}[_ ]*:[_ ]*${titlePattern}[_ ]*(?:[|][\s\S]*?)?}}`
			);

			const pageIsInMainspace = pageName.namespace === 0;
			const link = (
				pageIsInMainspace ?
				String.raw`\[\[[_ ]*(?::[_ ]*)?${titlePattern}[_ ]*(?:#[^|\[\]\n]+)?(?:\|[^\[\]\n]+)?]]` :
				String.raw`\[\[[_ ]*(?::[_ ]*)?${namespacePattern}[_ ]*:[_ ]*${titlePattern}[_ ]*(?:#[^|\[\]\n]+)?(?:\|[^\[\]\n]+)?]]`
			);

			return {
				transclusions: new RegExp(String.raw`^.*?${transclusion}.*$`, 'gim'),
				transclusionsHighlight: new RegExp(transclusion, 'gi'),
				links: new RegExp(String.raw`^.*?${link}.*$`, 'gim'),
				linksHighlight: new RegExp(link, 'gi')
			};
		}
	});
})();