Jump to content

User:R'n'B/missingredirapp.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>
/* missingredirs.js : Wikipedia app to fix broken {{redirect}} templates
 * Copyright (c) 2011 [[en:User:R'n'B]]
 *  Creative Commons Attribution-ShareAlike License applies
 * Requires wrappi.js, MediaWiki 1.17, and 
 *  jQuery 1.4.2 (included with MediaWiki)
 */
// Version 0.1; alpha release
/*global console, mw, jQuery, importScript */
(function ($) {
	// pseudo-global variables
	var api, 
		app,
		startup,
		originalcontent,
		onready,
		re_escape,
		firstinsensitive,
		putresponse,
		missinglist = [],
		prefix = mw.config.get("wgFormattedNamespaces")[10]	+ ":",
		prefixlen = prefix.length,
		redirtemplates = ["Redirect", "Redirect-acronym", "Redirect2",
			"Redirect3", "Redirect4", "Redirect6", "Redirect10"],
		redircount = redirtemplates.length,
		stylesheet;
	//
	importScript("User:R'n'B/wrappi.js");
	re_escape = function re_escape(pattern) {
		//	Escape all non-alphanumeric characters in pattern.
		var i, s = [], c, len = pattern.length;
		for (i = 0; i < len; i += 1) {
			c = pattern.charAt(i);
			if (( c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
				|| (c >= '0' && c <= '9') || c === ' ') {
				s[i] = c;
			} else {
				s[i] = "\\" + c;
			}
		}
		return s.join("");
	};
	firstinsensitive = function firstinsensitive(title) {
		var t = re_escape(title), f = t.charAt(0);
		if (f.toUpperCase() === f.toLowerCase()) {
			return t;
		}
        return ("[" + f.toUpperCase() + f.toLowerCase() + "]" + t.slice(1));
	};
	putresponse = function putresponse(response) {
		if (response.parse && response.parse.text
				&& response.parse.text["*"]) {
			$('#mr-render').html(response.parse.text["*"]);
		} else {
			$('#mr-render').html(
				"<strong class=\"error\">"
				+ "Error: could not parse.</strong>");
		}
	};
	// the application object
	app = mw.RnB.missingredirapp = {
		abort: false,
		ready: 0, // setup is finished when this reaches redircount + 1
		// populate global vars; this can run before document.ready
		setup: function () {
			// get list of titles of articles in [[Category:Missing redirects]]
			api = new mw.RnB.Wiki();
			api.request({ 
				action: 'query',
				list: 'categorymembers',
				cmtitle: 'Category:Missing redirects',
				cmnamespace: '0',
				cmsort: 'timestamp',
				cmlimit: 'max'
				}, function (response) {
					$.each(response.query.categorymembers, function(i, cmitem) {
						missinglist.push(cmitem.title);
					});
					app.ready += 1;
				}
			);
			// get list of all synonyms for [[Template:Redirect]] etc.
			$.each(redirtemplates, function (i, name) {
				api.request({
					action: 'query',
					list: 'backlinks',
					bltitle: prefix + name,
					blnamespace: '10',
					blfilterredir: 'redirects',
					bllimit: 'max'
					}, function (response) {
						if (response.query && response.query.backlinks) {
							$.each(response.query.backlinks,
									function(j, blitem) {
								var t = blitem.title;
								if (t.slice(0, prefixlen) === prefix) {
									redirtemplates.push(t.slice(prefixlen));
								} else {
									mw.log("Error: " + t + 
										" received as redirect to [[" + prefix
										+ name + "]]");
								}
							});
						}
						app.ready += 1;
					}
				);
			});
		},
		nextpage: function () {
			if (! missinglist) {
				app.exit();
				return;
			}
			app.title = missinglist.shift();
			// load the wikitext
			if (! app.title) {
				app.exit();
				return;
			}
			api.request({
				action: 'query',
				titles: app.title,
				prop: 'info|revisions',
				inprop: 'watched',
				intoken: 'edit',
				rvprop: 'content|timestamp'
				}, app.pageloaded
			);
			$('#mr-pagetitle').text(app.title);
			$('#mr-source').text('Loading ...');
			$('#mr-render').text('Loading ...');
			$('#mr-options').html('');
		},
		pageloaded: function (response, query) {
			// after page loads, retrieve its text and look for redir template
			app.templateRx = /\{\{\s*(.+?)\s*(\|.*?)?\}\}/g;
			//	need to create a new RegExp object for each page in order
			//	to track position of last match
			$.each(response.query.pages, function (id, pageobj) {
				if (pageobj.title === app.title) {
					app.edittoken = pageobj.edittoken;
					app.wikitext = pageobj.revisions[0]["*"];
					app.timestamp = pageobj.revisions[0].timestamp;
					app.watched = !(pageobj.watched === undefined);
					app.nexttemplate();
					return false;
				}
			});
		},
		nexttemplate: function () {
			// find next matching template on loaded page
			var found = false,
				match_t,
				tname,
				tparams,
				parampattern = /\|\s*([^\|])*\s*/g;
			while ((match_t = app.templateRx.exec(app.wikitext)) !== null) {
				tname = match_t[1].replace(/_+/g, ' ');
				if (tname.slice(0, prefixlen) === prefix) {
					// in case user entered {{Template:Redirect|Foo}}
					tname = tname.slice(prefixlen);
				}
				tname = tname.charAt(0).toUpperCase() + tname.slice(1);
				if (redirtemplates.indexOf(tname) !== -1) {
					$('#mr-source').text(match_t[0]);
					app.hatnote = match_t[0];
					found = true;
					api.request({
						action: 'parse',
						text: match_t[0]
					}, putresponse);
					// find and display valid redirects
					api.request({
						action: 'query',
						list: 'backlinks',
						bltitle: app.title,
						blnamespace: '0',
						blfilterredir: 'redirects',
						bllimit: 'max'
						}, app.showchoices);
					break;
				}
			}
			if (! found) {
				app.nextpage();
			}
		},
		showchoices: function (response) {
			var h,
				optionindex = 0;
			if (response.query && response.query.backlinks
					&& response.query.backlinks.length > 0) {
				$('#mr-options').html(
					'<div style="float: left; padding-top: 0.5em">'
					+ '<strong>Change to:</strong></div>'
					+ '<div id="mr-changeto" style="float:left;'
					+ ' padding-left:20px; padding-right:20px;'
					+ ' overflow:auto"></div>'
					+ '<br clear="all" />').resizable();
				$.each(response.query.backlinks,
						function(i, blitem) {
					var t = blitem.title;
					$('#mr-changeto').append(
						$('<input>').attr("type", 'radio')
							.attr("name", "mr-radio")
							.attr("value", t)
							.attr("id", "mr-radio"
								+ optionindex.toString())
						).append($('<label>')
							.attr("for", "mr-radio"
								+ optionindex.toString())
							.text(t)
						).append($('<br>'));
					optionindex += 1;
				});
				h = 42 * optionindex + 2; // tentative height
				h = (h > window.innerHeight - 430) ? window.innerHeight - 430 : h;
				h = (h < 42) ? 42 : h;
				$('#mr-changeto').width($('#content').width() - 128)
					.height(h);
			}
			// more options
			$('#mr-options').append(
				$('<input>').attr("type", "radio").attr("name", 'mr-radio')
					.attr("value", "#Remove")
					.attr("id", "mr-radio" + optionindex.toString())
				).append($('<label>')
					.attr("for", "mr-radio"	+ optionindex.toString())
					.text("Remove hatnote")
				).append($('<br>'));
			optionindex += 1;
			$('#mr-options').append(
				$('<input>').attr("type", "radio").attr("name", 'mr-radio')
					.attr("value", "#Create")
					.attr("id", "mr-radio" + optionindex.toString())
				).append($('<label>')
					.attr("for", "mr-radio" + optionindex.toString())
					.text("Create missing redirect")
				).append($('<br>'));
			optionindex += 1;
			$('#mr-options').append(
				$('<input>').attr("type", "radio").attr("name", 'mr-radio')
					.attr("value", "#Edit")
					.attr("id", "mr-radio" + optionindex.toString())
				).append($('<label>')
					.attr("for", "mr-radio" + optionindex.toString())
					.text("Manually edit hatnote")
				).append($('<br>'));
			optionindex += 1;
			$('#mr-options').append(
				$('<input>').attr("type", "radio").attr("name", "mr-radio")
					.attr("value", "#Skip")
					.attr("id", "mr-radio" + optionindex.toString())
				).append($('<label>')
					.attr("for", "mr-radio" + optionindex.toString())
					.text("Skip this hatnote")
				).append($('<br>'));
			optionindex += 1;
			$('#mr-options').append(
				$('<input>').attr("type", "radio").attr("name", "mr-radio")
					.attr("value", "#Quit")
					.attr("id", "mr-radio" + optionindex.toString())
				).append($('<label>')
					.attr("for", "mr-radio" + optionindex.toString())
					.text("Exit the app")
				).append($('<br>'));
			$('#mr-options').buttonset();
			$('#mr-options input').change(app.onchoice);
		},
		onchoice: function (evt) {
			// user has selected one of the buttons
			switch (evt.target.value) {
				case "#Remove":
					app.removehatnote();
					break;
				case "#Create":
					app.createredirect();
					break;
				case "#Edit":
					app.edithatnote(); // TODO
					break;
				case "#Skip":
					app.nexttemplate();
					break;
				case "#Quit":
					app.exit();
					break;
				default:
					app.changehatnote(evt.target.value);
			}
		},
		exit: function () {
			// user clicked "Exit" button
			$('#content').html(originalcontent);
			stylesheet.disabled = true;
			app.abort = true;
		},
		insertsummary: function () {
			// add form elements for edit summary
			$('#content').append($('<div>').attr("id", "bodyContent"));
			$('#bodyContent').html('<div class="editOptions">\n'
+ '<span id="wpSummaryLabel" class="mw-summary">'
+ '<label for="wpSummary"><span style="text-align: left;">'
+ '<a title="" href="/wiki/Help:Edit_summary">Edit summary</a>'
+ ' <small>(Briefly describe the changes you have made)</small>'
+ '</span></label></span>'
+ ' <input type="text" name="wpSummary" value="" accesskey="b" '
+         'title="Enter a short summary [alt-shift-b]" size="60" maxlength="250" id="wpSummary" class="mw-summary">'
+ '<div class="editCheckboxes"><input type="checkbox" id="wpMinoredit" accesskey="i" name="wpMinoredit">&nbsp;'
+ '<label title="Mark this as a minor edit [alt-shift-i]" id="mw-editpage-minoredit" for="wpMinoredit">'
+   'This is a minor edit <span id="minoredit_helplink">(<a title="Help:Minor edit" href="/wiki/Help:Minor_edit">what\'s this?</a>)</span></label>'
+ '<input type="checkbox" id="wpWatchthis" accesskey="w" name="wpWatchthis">&nbsp;'
+   '<label title="Add this page to your watchlist [alt-shift-w]" id="mw-editpage-watch" for="wpWatchthis">Watch this page</label></div>'
+ '<div class="editButtons">'
+ '<input type="button" title="Save your changes [alt-shift-s]" accesskey="s" value="Save page" name="wpSave" id="wpSave">'
+ '<span class="editHelp"><a id="mw-editform-cancel" title="">Cancel</a></span>'
+ '</div></div>'
			);
			if (app.watched || mw.user.options.get("watchdefault")) {
				$('#wpWatchthis').attr("checked", "checked");
			}
			$('#wpMinoredit').attr("checked", "checked");
			$('input[name="wpSave"]').click(app.savepage);
			$('#mw-editform-cancel').click(function () {
				$('#bodyContent').remove();
				$('input[name="mr-radio"]:checked').prop("checked", false);
			});
		},
		createredirect: function (replacement) {
			// don't change the hatnote, create the missing redirect instead
			var newhatnote,
				paramRx = /\|(.*?)(\}\}|\|)/,
				match = paramRx.exec(app.hatnote);
			if (match) {
				app.redirect = match[1].trim();
				app.insertsummary();
				$('#bodyContent').prepend(
					$('<textarea>').attr("rows", "3").attr("cols", "80")
						.attr("id", "mr-redirect").attr("name", "mr-redirect")
						.text("#REDIRECT [[" + app.title + "]]")
				).prepend(
					$('<h1>').text(app.redirect)
				);
				$('input[name="wpSummary"]').val('Create missing redirect');
				$('#wpMinoredit').prop("checked", false);
				if (mw.user.options.get("watchcreations")) {
					$('#wpWatchthis').attr("checked", "checked");
				}
				$('input[name="wpSave"]').unbind().click(app.createpage);
			} else {
				mw.log("No parameter found!");
				app.nexttemplate();
			}
		},
		createpage: function () {
			// save the new redirect
			var t = app.redirect,
				params = {
					action: 'edit',
					title: app.redirect,
					text: $('#mr-redirect').text(),
					token: app.edittoken,
					createonly: '',
					summary: $('input[name="wpSummary"]').val()
				};
			if ($('#wpMinoredit:checked').length) {
				params.minor = "";
			}
			params.watchlist = $('#wpWatchthis:checked').length ?
				"watch" : "unwatch";
			api.request(params,
				function () {mw.log("Page [[" + t + "]] created");});
			$('#bodyContent').remove();
			app.nexttemplate();
		},
		changehatnote: function (replacement) {
			// replace the missing parameter with replacement, and save
			var newhatnote,
				paramRx = /\|(.*?)(\}\}|\|)/,
				match = paramRx.exec(app.hatnote);
			if (match) {
				newhatnote = app.hatnote.replace(match[1], replacement);
				app.newtext = app.wikitext.replace(app.hatnote, newhatnote);
				$('#mr-source').append($('<br>')).append(
					$('<span>').css("color", "#27AA7A")
						.text(newhatnote)
				);
				app.insertsummary();
				$('input[name="wpSummary"]').val(
					'Change hatnote to use valid redirect');
			} else {
				mw.log("No parameter found!");
				app.nexttemplate();
			}
		},
		edithatnote: function () {
			// allow user to rewrite the hatnote in a text box
			var oldtext = $('#mr-source').text();
			$('#mr-source').html("").append(
				$('<input>').attr("type", "text")
					.attr("name", "mr-hatnote")
					.attr("id", "mr-hatnote")
					.attr("size", "75")
					.val(oldtext)
					.css("margin", "0.25em")
			);
			app.insertsummary();
		},
		removehatnote: function () {
			// remove the hatnote from the page text, and save
			app.newtext = app.wikitext.replace(app.hatnote, "<<@@>>\n");
			app.newtext = app.newtext.replace(/\s*<<@@>>\s*/, "\n");
			app.newtext = app.newtext.replace(/^\n+/, '');
			app.insertsummary();
			$('input[name="wpSummary"]').val(
				'Remove hatnote; no such redirect exists');
		},
		savepage: function () {
			var t = app.title,
				params = {
					action: 'edit',
					title: t,
					text: app.newtext,
					token: app.edittoken,
					basetimestamp: app.timestamp,
					summary: $('input[name="wpSummary"]').val()
				};
			if ($('#mr-source input').length) {
				params.text = app.wikitext.replace(app.hatnote,
					$('#mr-source input').val());
			}
			if ($('#wpMinoredit:checked').length) {
				params.minor = "";
			}
			if ($('#wpWatchthis:checked').length) {
				params.watchlist = "watch";
			}
			api.request(params,
				function () {mw.log("Page [[" + t + "]] saved");}
			);
			$('#bodyContent').remove();
			app.nexttemplate();
		}
	};
	onready = function () {
		// wait until app.ready reaches redircount + 1
		if (app.ready < redircount + 1) {
			window.setTimeout(onready, 100);
			return;
		}
		originalcontent = $('#content').html();	// existing <div id="content">
		// set up the page for the application
		stylesheet = mw.util.addCSS(
			'#content { border: 3px solid }\n' +
			'#content h1 { background-color: #27AA7A; color: white; ' + 
				'font-size: 125%; text-align: center; margin-bottom: 0 }\n' + 
			'#content h2 { font-size: 150%; }' +
			'#mr-source { font-family: monospace; font-size: 110% } ' +
			'#mr-render { border: 1px solid #27AA7A; font-size: 90%; ' +
				'padding: 0.25em; }'
		);
		$('#content').html("<h1>Missing redirect fixer</h1>" +
			"<h2 id=\"mr-pagetitle\"></h2>"	+ 
			"<div id=\"mr-source\"></div>" +
			"<div id=\"mr-render\"></div>" +
			"<div id=\"mr-options\" style=\"font-size: 83%\"></div>");
		if (missinglist) {
			app.nextpage();
		} else {
			app.exit();
		}
	};
	$(document).ready(onready);
	startup = function () {
		if (mw.RnB && mw.RnB.Wiki) {
			app.setup();
		} else {
			setTimeout(startup, 100);
		}
	};
	startup();
} (jQuery));
// </nowiki>