Jump to content

User:Kephir/gadgets/sagittarius.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.
// <nowiki>
/*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 () { // <nowiki>
'use strict';

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

importStylesheet('User:Kephir/gadgets/sagittarius.css');

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 = {
	"from": "Source",
	"to"  : "Target",
	"misc": "Miscellaneous"
};

var templateAliases = {
	"R to anchor"  : "R to section",
	"R to subtopic": "R to related topic",
	
	"This is a redirect.": "This is a redirect",
	"Redr"               : "This is a redirect",

	"Rcat shell"    : "Redirect category shell",
	"RCAT shell"    : "Redirect category shell",
	"redirect shell": "Redirect category shell",
	"rcatsh"        : "Redirect category shell",
	
	"": ""
};

var wgNamespaceIds = mw.config.get('wgNamespaceIds');

var redirectTemplates = {
	/* source */
	"R restricted": {
		"group": "from",
		"label": "Name unavailable due to technical restrictions"
	},
	"R from initialism": {
		"group": "from",
		"label": "Initialism",
		"auto": "capitals"
	},
	"R from alternative spelling": {
		"group": "from",
		"label": "Alternative spelling with same pronunciation"
	},
	"R from other capitalisation": {
		"group": "from",
		"label": "Other capitalisation"
	},
	"R from misspelling": {
		"group": "from",
		"label": "Misspelling",
		"implies": "R unprintworthy"
	},
	"R from modification": {
		"group": "from",
		"label": "Modification (punctuation or word order)"
	},
	"R from plural": {
		"group": "from",
		"label": "Plural"
	},
	"R to plural": {
		"group": "to",
		"label": "Plural"
	},
	"R from name and country": {
		"group": "from",
		"label": "Name, Country/State/Province"
	},
	"R from long name": {
		"group": "from",
		"label": "Long name"
	},
	"R from alternative name": {
		"group": "from",
		"label": "Alternative name (nickname, etc.)"
	},
	"R from synonym": {
		"group": "from",
		"label": "Synonym"
	},
	"R from former name": {
		"group": "from",
		"label": "Former name with no historical significance"
	},
	"R from historic name": {
		"group": "from",
		"label": "Historic name",
		"implies": ["R printworthy"]
	},
	"R from subtopic": {
		"group": "from",
		"label": "Sub-topic"
	},
	"R from scientific name": {
		"group": "from",
		"label": "Scientific name",
		"implies": ["R printworthy"]
	},
	"R to scientific name": {
		"group": "to",
		"label": "Scientific name"
	},
	"R from alternative scientific name": {
		"group": "from",
		"label": "Alternative scientific name"
	},
	"R from molecular formula": {
		"group": "from",
		"label": "Molecular formula"
	},
	"R from alternative language": {
		"group": "from",
		"label": "Alternative language"
	},
	"R from ASCII": {
		"group": "from",
		"label": "ASCII-only title",
		"auto": "from-ascii-to-unicode"
	},
	"R from Unicode": {
		"group": "from",
		"label": "Single Unicode character"
	},
	"R from diacritics": {
		"group": "from",
		"label": "Title with diacritics",
		"conflict": "R from title without diacritics"
	},
	"R from title without diacritics": {
		"group": "from",
		"label": "Title without diacritics",
		"conflict": "R from diacritics"
	},
	"R from surname": {
		"group": "from",
		"label": "Surname",
	},
	"R from CamelCase": {
		"group": "from",
		"label": "Old-style CamelCase name"
	},
	"R from unnecessary disambiguation": {
		"group": "from",
		"label": "Redundant disambiguation"
	},
	"R to decade": {
		"group": "to",
		"label": "Decade article"
	},
	"R to disambiguation page": {
		"group": "to",
		"label": "Disambiguation page"
	},
	"R from ambiguous page": {
		"group": "from",
		"label": "Ambiguous title"
	},
	"R from EXIF": {
		"group": "from",
		"label": "Title derived from EXIF data"		
	},
	"R to list entry": {
		"group": "to",
		"label": "List entry"
	},
	"R from member": {
		"group": "from",
		"label": "Member of the target organisation"
	},
	"R to related topic": {
		"group": "to",
		"label": "Related topic"
	},
	"R from related word": {
		"group": "from",
		"label": "Related word"
	},
	"R from school": {
		"group": "from",
		"label": "School"
	},
	"R to section": {
		"group": "to",
		"label": "Section",
		"auto": "anchor"
	},
	"R from shortcut": {
		"group": "from",
		"label": "Shortcut",
		"conflict": "R from template shortcut"
	},
	"R from template shortcut": {
		"group": "from",
		"label": "Template shortcut",
		"conflict": "R from shortcut"
	},
	"R from song": {
		"group": "from",
		"label": "Song (to artist article)"
	},
	"R from book": {
		"group": "from",
		"label": "Book (to author or publisher)"
	},
	"R from duplicated article": {
		"group": "from",
		"label": "Duplicated article"
	},
	"R from merge": {
		"group": "from",
		"label": "Merged article"
	},
	"R from move": {
		"group": "from",
		"label": "Moved title" 
	},
	"R from album": {
		"group": "from",
		"label": "Album name"
	},
	"R from mathematical symbol or equation": {
		"group": "from",
		"label": "Mathematical symbol or equation"
	},
	"R to category": {
		"group": "to",
		"label": "Category namespace (from other namespaces)",
		"target-namespace": wgNamespaceIds.category,
		"xnr-only": true
	},
	"R to help": {
		"group": "to",
		"label": "Help namespace",
		"target-namespace": wgNamespaceIds.help
	},
	"R to main": {
		"group": "to",
		"label": "Main namespace (from other namespaces)",
		"target-namespace": 0,
		"xnr-only": true
	},
	"R to portal": {
		"group": "to",
		"label": "Portal namespace",
		"target-namespace": wgNamespaceIds.portal
	},
	"R to talk": {
		"group": "to",
		"label": "Talk namespace",
		"target-talk": true
	},
	"R to template": {
		"group": "to",
		"label": "Template namespace (from other namespaces)",
		"target-namespace": wgNamespaceIds.template,
		"xnr-only": true
	},
	"R to user": {
		"group": "to",
		"label": "User namespace",
		"target-namespace": wgNamespaceIds.user
	},
	"R to project": {
		"group": "to",
		"label": "Wikipedia namespace",
		"target-namespace": wgNamespaceIds.project
	},

	/* miscellanea */
	"R with old history": {
		"group": "misc",
		"label": "Has old history"
	},
	"R for convenience": {
		"group": "misc",
		"label": "Purely for convenience"
	},
	"R with possibilities": {
		"group": "misc",
		"label": "Might be worthy of own article",
		"conflict": [
			"R unprintworthy",
			"R from subtopic without possibilities",
			"R from highway in region without possibilities"
		]
	},
	"R unprintworthy": {
		"group": "misc",
		"label": "Unprintworthy",
		"conflict": "R printworthy"
	},
	"R printworthy": {
		"group": "misc",
		"label": "Printworthy",
		"conflict": "R unprintworthy"
	},

	"": void(0) // for convenience
};
delete redirectTemplates[""];

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)))
		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('{{' + key + '}}\n');
			else
				tail += '{{' + key + '}}\n';
		
	}
	if (wrapped.length)
		markup += "{{Redirect category shell|\n" + wrapped.join("") + "}}\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(["About Sagittarius"], mw.util.getUrl("User:Kephir/gadgets/sagittarius"))
			], {
				'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, '%$1')), // 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 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 + ']] ([[User:Kephir/gadgets/sagittarius|♐]])',
					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 Sagittarius'
	], {
		"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 + ']] ([[User:Kephir/gadgets/sagittarius|♐]])',
					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 + ']] ([[User:Kephir/gadgets/sagittarius|♐]])';
			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 Sagittarius"
	}));

	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 + ']] ([[User:Kephir/gadgets/sagittarius|♐]])';
				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';
	}
}

});
// </nowiki>