User:MJL/Archer.js

// This is a modified version of User:Sam Sailor/Scripts/Sagittarius+.js (Special:Diff/899155791) // Such script itself was a modified version of User:Kephir/gadgets/sagittarius.js (Special:PermaLink/785248045) // Some code was also stolen from User:Wugapodes/Capricorn.js (Special:Permalink/1043412413) // Docs: User:MJL/Archer //

/*jshint undef:true, latedef:true, shadow:true, loopfunc:true, scripturl:true, undef:true */ /*globals jQuery, mw, importStylesheet */ mw.loader.using(['jquery.suggestions', 'mediawiki.api', 'mediawiki.Title', 'mediawiki.action.view.redirectPage'], function { // 'use strict';

if (mw.config.get('wgNamespaceNumber') < 0) return;

function normaliseAnchor(anchor) { function encodeCodePoint(c) { if (c === 0x20) return '_'; if (c < 0x80) { return '.' + c.toString(16).toUpperCase; } else if (c < 0x800) { return '.' + (0xc0 | (c >>>  6)        ).toString(16).toUpperCase + '.' + (0x80 | ( c        & 0x3f)).toString(16).toUpperCase; } else if (c < 0x10000) { return '.' + (0xe0 | (c >>> 12)        ).toString(16).toUpperCase + '.' + (0x80 | ((c >>> 6) & 0x3f)).toString(16).toUpperCase + '.' + (0x80 | ( c        & 0x3f)).toString(16).toUpperCase; } else if (c < 0x200000) { return '.' + (0xf0 | (c >>> 18)        ).toString(16).toUpperCase + '.' + (0x80 | ((c >>> 12) & 0x3f)).toString(16).toUpperCase + '.' + (0x80 | ((c >>> 6) & 0x3f)).toString(16).toUpperCase + '.' + (0x80 | ( c        & 0x3f)).toString(16).toUpperCase; } else if (c < 0x4000000) { return '.' + (0xf8 | (c >>> 24)        ).toString(16).toUpperCase + '.' + (0x80 | ((c >>> 18) & 0x3f)).toString(16).toUpperCase + '.' + (0x80 | ((c >>> 12) & 0x3f)).toString(16).toUpperCase + '.' + (0x80 | ((c >>> 6) & 0x3f)).toString(16).toUpperCase + '.' + (0x80 | ( c        & 0x3f)).toString(16).toUpperCase; } else if (c < 0x80000000) { return '.' + (0xfc | (c >>> 30)        ).toString(16).toUpperCase + '.' + (0x80 | ((c >>> 24) & 0x3f)).toString(16).toUpperCase + '.' + (0x80 | ((c >>> 18) & 0x3f)).toString(16).toUpperCase + '.' + (0x80 | ((c >>> 12) & 0x3f)).toString(16).toUpperCase + '.' + (0x80 | ((c >>> 6) & 0x3f)).toString(16).toUpperCase + '.' + (0x80 | ( c        & 0x3f)).toString(16).toUpperCase; }	}

// "." is not escaped! return anchor.replace(/[^0-9A-Za-z_:\.]/g, function (m) { /* [\ud800-\udbff][\udc00-\dfff]| */		if (m.length === 2) { // surrogate pair			return encodeCodePoint((m.charCodeAt(0) & 0x3ff) << 10 | m.charCodeAt(1) & 0x3ff);		} else {			return encodeCodePoint(m.charCodeAt(0));		}	}); }

function normaliseTitle(title) { try { var t = new mw.Title(title); return t.getPrefixedText; } catch (e) { return null; } }

function el(tag, child, attr, events) { var node = document.createElement(tag); if (child) { if ((typeof child === 'string') || (typeof child.length !== 'number')) child = [child]; for (var i = 0; i < child.length; ++i) { var ch = child[i]; if ((ch === void(null)) || (ch === null)) continue; else if (typeof ch !== 'object') ch = document.createTextNode(String(ch)); node.appendChild(ch); }	}

if (attr) for (var key in attr) { if ((attr[key] === void(0)) || (attr[key] === null)) continue; node.setAttribute(key, String(attr[key])); }

if (events) for (var key in events) { var handler = events[key]; if ((key === 'input') && (window.oninput === void(0))) { key = 'change'; }		node.addEventListener(key, handler, false); }

return node; }

function link(child, href, attr, ev) { attr = attr || {}; ev = ev || {}; if (typeof attr === 'string') { attr = { "title": attr }; }	if (typeof href === 'string') attr.href = href; else { attr.href = 'javascript:void(null);'; ev.click = href; }	return el('a', child, attr, ev); }

var templateGroups = { "fromabbreviationcapitalisationandgrammar": "From – abbreviation, capitalisation, and grammar", "frompartsofspeach": "From parts of speach", "fromspelling": "From – spelling", "fromalternativenamesgeneral": "From alternative names, general", "fromalternativenamesgeography": "From alternative names, geography", "fromalternativenamespeople": "From alternative names, people", "fromalternativenamesscientific": "From alternative names, scientific", "fromalternativenamestechnical": "From alternative names, technical", "frompostalinfo": "From postal information", "fromcomics": "From comics", "fromdisambiguations": "From disambiguations", "fromfiction": "From fiction", "frommiddleearth": "From Middle Earth", "fromisocodes": "From ISO codes", "fromrelatedinfo": "From related info", "frommusic": "From music", "fromsocialmedia": "From social media", "fromrelatedpeople": "From related people", "fromtransportation": "From – transportation", "frommergersduplicatesandmoves": "From mergers, duplicates, and moves", "fromnavigation": "From – navigation", "togrammarpunctuationandspelling": "To – grammar, punctuation, and spelling", "toalternativenames": "To alternative names", "tonavigationanddisambiguation": "To – navigation and disambiguation", "tonamespaces": "To namespaces", "tocomics": "To comics", "tomiddleearth": "To Middle Earth", "totime": "To time articles", "totemplates": "To Wikipedia templates", "tomiscellaneous": "To miscellaneous", "misc": "Miscellaneous" };

function mainCallback(aliasJSON, templateJSON) { var templateAliases = aliasJSON; var redirectTemplates = templateJSON; //console.log(templateAliases); //console.log(redirectTemplates); // 	'use strict'; importStylesheet('User:MJL/Archer/Kephir.css'); var wgNamespaceIds = mw.config.get('wgNamespaceIds'); var api = new mw.Api; var contentText = document.getElementById('mw-content-text'); var firstHeading = document.getElementById('firstHeading'); var redirMsg = contentText.getElementsByClassName('redirectMsg')[0]; var uiWrapper = el('div'); var edittoken = null; function MarkupBlob(markup) { if (!markup) { this.target = ''; this.rcatt = {}; this.tail = ''; } else this.parse(markup); }

MarkupBlob.prototype.parse = function (markup) { var rdrx = /^#REDIRECT\s*\[\[\s*([^\|{}[\]]+?)\s*]]\s*/i; var tprx = /^\s*{{([A-Za-z ]+)((?:\|(?:[^|{}]*|{{[^|}]*}})+)*)}}\s*/i; var m;		if (!(m = rdrx.exec(markup.trim))) throw new Error('Not a redirect'); markup = markup.substr(m[0].length); this.target = m[1]; this.rcatt = {}; out: while ((m = tprx.exec(markup))) { var alias = normaliseTitle(m[1]); while (templateAliases[alias]) alias = templateAliases[alias]; // hopefully there are no loops. if (alias === "This is a redirect") { var params = m[2].split('|'); for (var j = 0; j < params.length; ++j) { if (!params[j]) continue; if (params[j].indexOf('=') !== -1) break out; alias = normaliseTitle("R " + params[j]); while (templateAliases[alias]) alias = templateAliases[alias]; // hopefully there are still no loops. if (alias in redirectTemplates) this.rcatt[alias] = true; else break out; }			} else if (alias === "Redirect category shell") { var mm, rr = //g; while (mm = rr.exec(m[2])) { alias = normaliseTitle(mm[1]); while (templateAliases[alias]) alias = templateAliases[alias]; if (alias in redirectTemplates) this.rcatt[alias] = true; }			} else if (alias in redirectTemplates) { if (m[2]) // TODO break; this.rcatt[alias] = true; } else { break; }			markup = markup.substr(m[0].length); }		this.tail = markup; };	MarkupBlob.prototype.toString = function { var markup = '#REDIRECT ' + this.target + ''; var tail = ''; var wrapped = []; for (var key in this.rcatt) { if (this.rcatt[key]) if ((wrapped.length < 6) && /^R\s+/.test(key)) wrapped.push('\n'); else tail += '\n'; }		if (wrapped.length) markup += "\n\n"; markup += tail + '\n'; markup += this.tail; return markup; };	function buildTagList(rcatt) { function makeCheckBox(key) { return el('label', [				el('input', null, { type: "checkbox", checked: (key in rcatt) ? "checked" : null, }, {					change: function (ev) { rcatt[key] = this.checked; }				}),				' ',				redirectTemplates[key].label			], {				"title": redirectTemplates[key].tooltip			}); }		var list = el('dl', null, { "class": "tag-list" }); var group = {}; for (var key in templateGroups) { list.appendChild(el('dt', templateGroups[key])); list.appendChild(el('dd', group[key] = el('ul'))); }		for (var key in redirectTemplates) { var label = makeCheckBox(key); group[redirectTemplates[key].group].appendChild(el('li', label)); }		return list; }	function buildEditingUI(mblob, saveCallback) { var statusbar; var needsCheck = true; var doSave; var uiLink, uiTarget; mblob = mblob || new MarkupBlob; function setStatus(status) { while (statusbar.firstChild) statusbar.removeChild(statusbar.firstChild); if (status) { if (typeof status === 'string') statusbar.appendChild(document.createTextNode(status)); else { for (var j = 0; j < status.length; ++j) { if (typeof status[j] === 'string') statusbar.appendChild(document.createTextNode(status[j])); else statusbar.appendChild(status[j]); }				}			}		}		function inputChanged(ev) { /*jshint validthis:true */ try { mblob.target = this.value; var t = new mw.Title(this.value); var frag = t.getFragment ? '#' + normaliseAnchor(t.getFragment) : ''; uiLink.href = mw.util.getUrl(t.getPrefixedDb, { redirect: "no" }) + frag; setStatus; } catch (e) { setStatus('Invalid title.'); if(uiLink) uiLink.href = 'javascript:void(0);'; }			needsCheck = true; }		var uiStatusLine; var ui = el('form', [			el('div', [ el('ul', [					el('li', [ uiTarget = el('input', null, {							'type': 'text',							'class': 'redirectText',							'value': mblob.target						}, {							'input': inputChanged,							'change': inputChanged,							'blur': function (ev) { // i would not have to write this, if it were not for jQuery. seriously.								if (mblob.target === this.value)									return;								inputChanged.call(this, ev);							}						}) ])				], { 'class': 'redirectText' }) ], { 'class': 'redirectMsg' }),			buildTagList(mblob.rcatt),			uiStatusLine = el('p', [ statusbar = el('span', [], {					'class': 'status-line'				}), el('span', [					link(["Statistics for this page"], 'https://tools.wmflabs.org/pageviews?project=en.wikipedia.org&pages=' + encodeURIComponent(mw.config.get('wgPageName'))),					' • ',					link(["WP:TMR"], mw.util.getUrl("Wikipedia:Template messages/Redirect pages")),					' • ',					link(["About Archer"], mw.util.getUrl("User:MJL/Archer"))				], {					'style': 'float: right;'				}) ])		], {			'action': 'javascript:void(0)',			'class': 'kephir-sagittarius-editor'		}, {			'submit': function (ev) {				ev.preventDefault;				ui.doCheck(saveCallback);			}		}); ui.statusLine = uiStatusLine; var sectCache = {}; var $uiTarget = jQuery(uiTarget); $uiTarget.suggestions({			submitOnClick: false,			delay: 500,			fetch: function (query) {				$uiTarget.suggestions('suggestions', []);				if (query.indexOf('#') !== -1) {					var title = query.substr(0, query.indexOf('#'));					var sect = query.substr(query.indexOf('#') + 1);					if (sectCache[title]) {						var normSect = normaliseAnchor(sect);						$uiTarget.suggestions('suggestions', sectCache[title].filter(function (item) {								var norm = normaliseAnchor(item.anchor);								return norm.substr(0, normSect.length) === normSect;							}) );						return;						}					api.get({ action: 'parse', page: title, prop: 'sections|properties', redirects: '1' }).then(function (result) { if (result.parse.redirects && result.parse.redirects.length) { // XXX return; }						var disambig = false; // XXX var normSect = normaliseAnchor(sect); sectCache[title] = result.parse.sections.map(function (item) {							return {								anchor: item.anchor,								title: title + '#' + decodeURIComponent(item.anchor.replace(/_/g, ' ').replace(/\.([0-9A-Fa-f][0-9A-Fa-f])/g, '%')), // XXX: hack								disambig: disambig,								toString: function {									return this.title;								}							};						}); $uiTarget.suggestions('suggestions',							sectCache[title].filter(function (item) { var norm = normaliseAnchor(item.anchor); return norm.substr(0, normSect.length) === normSect; })						);					});					return;				}				api.get({ action: 'query', generator: 'allpages', gapprefix: query, gaplimit: 16, prop: 'info|pageprops', }).then(function (result) { var pglist = []; for (var pgid in result.query.pages) { var page = result.query.pages[pgid]; pglist.push({							title: page.title,							pageid: page.pageid,							disambig: page.pageprops && ('disambiguation' in page.pageprops),							redirect: 'redirect' in page,							toString: function {								return this.title;							}						}); }					$uiTarget.suggestions('suggestions', pglist); });			},			result: {				render: function (item, content) {					var elm = this[0];					elm.appendChild(el('span', [item.title], {						style: item.redirect ? 'font-style: italic' : ''					}));					if (item.disambig)						elm.appendChild(el('small', [' (disambiguation page)']));					if (item.redirect)						elm.appendChild(el('small', [' (redirect)']));				},				select: function ($textbox) {					var item = this.data('text');					var textbox = $textbox[0];					textbox.value = item.title;					if (item.redirect) {						api.get({ action: 'query', pageids: item.pageid, redirects: '1' }).then(function (result) { var redir = result.query.redirects.pop; textbox.value = redir.to + (redir.tofragment ? '#' + redir.tofragment : ''); });					}					return true;				}			}		}); ui.doCheck = function (callback) { var that = this; if (!/^\s*[^\|{}[\]]+\s*$/.test(mblob.target)) { setStatus(['Error: the target page name is invalid.']); return; }			if (needsCheck) { var oldTarget = mblob.target; var normTarget; try { normTarget = new mw.Title(oldTarget); } catch (e) { setStatus(['"', oldTarget, '" is not a valid page name. Try again to proceed anyway.']); return; }				setStatus(['Checking target validity...']); needsCheck = false; api.get({					action: 'parse',					page: oldTarget = mblob.target,					prop: 'sections',					redirects: '1'				}, {					success: function (result) {						var m;						if (result.error) {							if (result.error.code === 'missingtitle') {								setStatus([ 'Error: The target page "',									link([normTarget.getPrefixedText], mw.util.getUrl(normTarget.getPrefixedText, { "class": "new" })),									'" does not exist. Try again to proceed anyway.' ]);							} else {								setStatus([ 'API error: "',									result.error.info,									'" [code: ', el('code', [result.error.code]), ']' ]);							}							return;						}						if (result.parse.redirects && result.parse.redirects[0]) {							var newTarget = result.parse.redirects[0].to + (result.parse.redirects[0].tofragment ? "#" + result.parse.redirects[0].tofragment : "");							setStatus([ 'Error: The target page "',								link([normTarget.getPrefixedText], mw.util.getUrl(normTarget.getPrefixedText, { redirect: "no" })),								'" is already a redirect to "',								link([newTarget], mw.util.getUrl(newTarget, { redirect: "no" })),								'". Try again to proceed anyway, or ', link(['retarget this redirect to point there directly'], function {									uiTarget.value = mblob.target = newTarget +										((!result.parse.redirects[0].tofragment && normTarget.fragment) ? '#' + normTarget.fragment : '');									needsCheck = true;								}), '.'							]);							return;						}						if (normTarget.fragment) { // we have a section link							var sect = normaliseAnchor(normTarget.fragment);							var isValidSect = false;							var sectlist = result.parse.sections;							for (var j = 0; j < sectlist.length; ++j) {								if (sectlist[j].anchor === sect)									isValidSect = true;							}							if (!isValidSect) {								setStatus([ 'Error: The target page "',									link([normTarget.getPrefixedText], mw.util.getUrl(normTarget.getPrefixedText, { redirect: "no" })),									'" does not have a section called "',									normTarget.fragment,									'". Try again to proceed anyway.' ]);								return;							}						}						callback(setStatus);					}				}); return; }			callback(setStatus); };		return ui; }	if ((mw.config.get('wgAction') === 'view') && (mw.config.get('wgArticleId') === 0)) { // nonexistent page. uiWrapper.appendChild(el('div', [ link(['Create a redirect'], function {				while (uiWrapper.hasChildNodes)					uiWrapper.removeChild(uiWrapper.firstChild);				var mblob = new MarkupBlob;				var ui = buildEditingUI(mblob, function (setStatus) { setStatus(['Saving...']); api.post({						action: 'edit',						title: mw.config.get('wgPageName'),						token: mw.user.tokens.get('csrfToken'),						createonly: 1,						summary: 'Redirecting to ' + mblob.target + ' (Archer)',						text: mblob.toString					}, {						success: function (result) {							if (result.error) {								setStatus([ 'API error: "',									result.error.info,									'" [code: ', el('code', [result.error.code]), ']' ]);								return;							}							setStatus(['Saved. Reloading page...']);							if (/redirect=no/.test(location.href)) // XXX								location.reload;							else								location.search = location.search ? location.search + '&redirect=no' : '?redirect=no';						}					}); });				ui.statusLine.insertBefore(el('input', null, {					type: 'submit',					value: 'Save'				}), ui.statusLine.firstChild);				uiWrapper.appendChild(ui);			}), ' from this page with Archer' ], {			"class": "kephir-sagittarius-invite" }));		contentText.parentNode.insertBefore(uiWrapper, contentText); } else if ((mw.config.get('wgAction') === 'view') && mw.config.get('wgIsRedirect') && redirMsg) { // start editor immediately uiWrapper.appendChild(el('div', ['Loading page source…'], { "class": "kephir-sagittarius-loading" }));		contentText.insertBefore(uiWrapper, contentText.firstChild); api.get({			action: 'query',			prop: 'info|revisions',			rvprop: 'timestamp|content',			pageids: mw.config.get('wgArticleId'),			rvstartid: mw.config.get('wgRevisionId'),			rvlimit: 1,			rvdir: 'older',			intoken: 'edit',		}, {			success: function (result) {				if (result.error) {					uiWrapper.appendChild(el('div', [						'API error: "',						result.error.info,						'" [code: ', el('code', [result.error.code]), ']. Reload to try again.'					], {						"class": "kephir-sagittarius-error"					}));					return;				}				while (uiWrapper.hasChildNodes)					uiWrapper.removeChild(uiWrapper.firstChild);				var page = result.query.pages[mw.config.get('wgArticleId')];				var mblob;				var token = page.edittoken;				try {					mblob = new MarkupBlob(page.revisions[0]['*']);				} catch(e) {					uiWrapper.appendChild(el('div', ['Error: unable to parse page. Edit the source manually.'], {						"class": "kephir-sagittarius-error" }));					return; }				redirMsg.parentNode.removeChild(redirMsg); var ui = buildEditingUI(mblob, function (setStatus) {					setStatus(['Saving...']);					api.post({ action: 'edit', title: mw.config.get('wgPageName'), basetimestamp: page.revisions[0].timestamp, token: mw.user.tokens.get('csrfToken'), summary: 'Redirecting to ' + mblob.target + ' (Archer)', text: mblob.toString }, {						success: function (result) { if (result.error) { setStatus([									'API error: "',									result.error.info,									'" [code: ', el('code', [result.error.code]), ']'								]); return; }							setStatus(['Saved. Reloading page...']); if (/redirect=no/.test(location.href)) // XXX location.reload; else location.search = location.search ? location.search + '&redirect=no' : '?redirect=no'; }					});								});				ui.statusLine.insertBefore(el('input', null, { type: 'submit', value: 'Save' }), ui.statusLine.firstChild); uiWrapper.appendChild(ui); }		});	} else if ((mw.config.get('wgPageContentModel') === 'wikitext') && ((mw.config.get('wgAction') === 'edit') || (mw.config.get('wgAction') === 'submit'))) {		if (mw.util.getParamValue('section'))			return;		var editform = document.getElementById('editform');		if (editform.wpTextbox1.readOnly)			return;		var uiPivot = document.getElementsByClassName('wikiEditor-ui')[0];		var ui, mblob;		firstHeading.appendChild(document.createTextNode(' '));		firstHeading.appendChild(link(['♐'], function {			if (ui && ui.parentNode)				ui.parentNode.removeChild(ui);			try {				mblob = new MarkupBlob(editform.wpTextbox1.value);			} catch (e) {				alert("Error: unable to parse page. This page is probably not a redirect.");				return;			}			ui = buildEditingUI(mblob, function  { editform.wpSummary.value = 'Redirecting to ' + mblob.target + ' (Archer)'; editform.wpTextbox1.value = mblob.toString; mblob = null; ui.style.display = 'none'; uiPivot.style.display = ''; });			ui.style.display = 'none';			ui.statusLine.insertBefore(el('input', null, {				type: "button",				value: "Cancel",			}, {				click: function {					mblob = null;					ui.style.display = 'none';					uiPivot.style.display = ;								}			}), ui.statusLine.firstChild);			ui.statusLine.insertBefore(el('input', null, {				type: "submit",				value: "Check"			}), ui.statusLine.firstChild);			uiPivot.parentNode.insertBefore(ui, uiPivot);			uiPivot.style.display = 'none';			ui.style.display = ;		}, {			"class": "kephir-sagittarius-editlink",			"title": "Edit this redirect with Archer"		}));		var submitButton;		var inputs = editform.getElementsByTagName('input');		for (var i = 0; i < inputs.length; ++i) {			inputs[i].addEventListener('click', function (ev) { submitButton = this; }, false);		}		editform.addEventListener('submit', function (ev) { if (submitButton !== editform.wpSave) return; if (mblob) { ev.preventDefault; ev.stopImmediatePropagation; ui.doCheck(function (setStatus) {					setStatus(['Proceeding with saving...']);					editform.wpTextbox1.value = mblob.toString;					editform.wpSummary.value = 'Redirecting to ' + mblob.target + ' (Archer)';					mblob = null;					editform.submit;				}); }		}, false);	}	if (!window.kephirSagittariusFollowCategoryRedirects)	if ((mw.config.get('wgAction') === 'view') && (mw.config.get('wgNamespaceNumber') === wgNamespaceIds.category)) {		var pagesList = document.getElementById('mw-pages').getElementsByClassName('mw-redirect');		for (var i = 0; i < pagesList.length; ++i) {			pagesList[i].href += '?redirect=no';		}	} }

function abortConditions { if (window.location.href.includes('&diff=')) { throw 'Archer does not run when viewing page diffs. Please revert before editing redirect.'; }	if (mw.config.get('wgNamespaceNumber') < 0) { throw 'Page is in a virtual namespace. Archer aborts.'; } }

function ArcherMain { $.getJSON("https://en.wikipedia.org/w/index.php?title=User:MJL/Archer/Aliases.json&action=raw&ctype=application/json", function(aliasJSON) {		$.getJSON("https://en.wikipedia.org/w/index.php?title=User:MJL/Archer/Templates.json&action=raw&ctype=application/json",function(templateJSON) { mw.loader.using(['jquery.suggestions', 'mediawiki.api', 'mediawiki.Title', 'mediawiki.action.view.redirectPage'], function {				mainCallback(aliasJSON,templateJSON);			}); });	}); }

var api = new mw.Api;

api.get({	action: "query",	format: "json",	prop: "info",	formatversion: 2,	titles: mw.config.get('wgPageName') }, {	success: function (result) {		try {			abortConditions;		} catch(abortMessage) {			console.info(abortMessage)			return;		}		if (result.query.pages[0].redirect || (window.location.href.includes('&redlink=1'))) {			ArcherMain;		} else {			console.debug('Page is not a redirect.')			return;		}	} });

});

// /*