MediaWiki:Gadget-Hotkeys.js

mw.hook('ext.CodeMirror.switch').add(function(cmEnabled, cm) {	var openCurly = '{{'; // prevents MediaWiki from doing its unwated black magic where JS strings surrounded by –  are counted as transclusions	cm = cm[0].CodeMirror;	cm.setOption("extraKeys", { "Ctrl-Alt-T": function { var termContext = getTermContext; insertText(openCurly+'Term|'+termContext+'|', '}}'); },		"Ctrl-Alt-P": function { var termContext = getTermContext; insertText(openCurly+'Plural|'+termContext+'|', '}}'); },		"Ctrl-Alt-I": function { var termContext = getTermContext; var currentPage = mw.config.get('wgTitle'); insertText(openCurly+'Term|'+termContext+'|'+currentPage+'}}'); }	});	function insertText(textBeforeCursor, textAfterCursor) {		var cursorPos = cm.getCursor;		textBeforeCursor = textBeforeCursor || ;		textAfterCursor = textAfterCursor || ;		var selection = cm.getSelection;		var replacement = textBeforeCursor + selection + textAfterCursor;		cm.replaceSelection(replacement);

if (selection == '') { cursorPos.ch = cursorPos.ch + textBeforeCursor.length; cm.setCursor(cursorPos); }	}	function getTermContext { var game = getCurrentGameContext; if (game === null) { // if context not loaded yet, don't infer any game return ''; }		else if (game === '') { //if context loaded but no current context exists, use "Series" return 'Series'; }		else { return getLatestVersion(game); }	}	/** CONTEXT **/ var gameContextRegex = null; var gameToLatestVersion = {}; (function loadGames {		new mw.Api.get({ action: 'cargoquery', format: 'json', limit: 'max', tables: 'Games', fields: 'code,supersededBy', order_by: 'canonOrder' }).then(function(result) { gameContextRegex = ''; var gameTokens = result.cargoquery.map(function(queryResult) {				var game = queryResult.title;				gameToLatestVersion[game.code] = game.supersededBy || game.code;				var gameCapture = '(' + escapeRegExp(game.code) + ')';				// Each sub-expression represents a game-based template which can be used to infer the current game context.				return [					openCurly + gameCapture + '}}',					openCurly + gameCapture + '\\|-}}',					openCurly + 'Term\\|' + gameCapture + '\\|',					openCurly + 'Plural\\|' + gameCapture + '\\|',				].join('|');			}); gameContextRegex = new RegExp(gameTokens.join('|'), 'g'); });	});	function escapeRegExp(string) { return string.replace(/[{}.*+\-?^$|[\]\\]/g, '\\$&'); }	/**	 * Attemps to infer the current game that should be inserted into text by looking at the templates used before it (,, OoT:, etc.) * @returns a game code (e.g. OoT, MM, BotW), or an empty string if none found. Returns nil if games haven't been loaded yet. **/ 	function getCurrentGameContext { if (!gameContextRegex) { // list of games hasn't loaded yet return null; }		var gameMatch = getLastMatchBeforeCursor(gameContextRegex); if (!gameMatch) { return ''; }		var headingMatch = getLastMatchBeforeCursor(/[^=]={2,3}[^=]+={2,3}/g); // match L2 or L3 heading if (headingMatch && headingMatch.index > gameMatch.index) { //Effectively: L2 and L3 headings clear the game context return ''; }		var captures = gameMatch.filter(Boolean); // remove empty capture groups return captures[1]; }	function getLastMatchBeforeCursor(regex) { var text = cm.getRange({line: 0, char: 0}, cm.getCursor); var it = text.matchAll(regex); var lastMatch; var result = it.next; while(!result.done) { lastMatch = result.value; result = it.next; }		return lastMatch; }	function getLatestVersion(game) { return gameToLatestVersion && gameToLatestVersion[game] || game; } });