Jump to content

User:R'n'B/missingredirapp.js

From Wikipedia, the free encyclopedia
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
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>