User:R'n'B/birthdeath.js

/* birthdeath.js : Wikipedia app to add birth and/or death dates to * a human name disambiguation page * Copyright (c) 2011 en:User:R'n'B * Creative Commons Attribution-ShareAlike License applies * Requires wrappi.js, MediaWiki 1.18, and jQuery 1.6 (included with MediaWiki) * */ // Version 0.1; alpha release /*global console, mw, jQuery, importScript, importStylesheet */ (function ($) {	// pseudo-global variables	var api,		app,		onready,		original,		startup,		linkRx = /\[\[([^\]\|\[#<>\{\}]*)(\|.*?)?\]\]/, // groups: title, anchor		bornRx = / *\( *[Bb](?:orn|\.) +(\d+(?: *BC)?) *\) */,		diedRx = / *\( *[Dd](?:ied|\.) +(\d+(?: *BC)?) *\) */,		datesRx = / *\( *(\d+(?: *BC)?) *(?:[\-\u2013\u2014]|–|\{\{ *ndash *\}\}) *(\d+(?: *BC)?) *\) */,		birthcats = /Category:(\d{1,4}(?: BC)?) births/,		deathcats = /Category:(\d{1,4}(?: BC)?) deaths/;	importScript('User:Cacycle/diff.js');	importScript("User:R'n'B/wrappi.js");	importStylesheet("User:R'n'B/birthdeath.css");	onready = function { // page setup that doesn't require API access		var cpointer = function  { $(this).css("cursor", "pointer"); },			cdefault = function  { $(this).css("cursor", "default"); };		original = {			'#content': $('#content').html		};		$('#content').empty.addClass("bd-content")		.append( $(' Human name disambiguation page fixer ') .append($(' ')					.append($(''))				) ).append( $(' ') .text('(Loading...)') ).append( $(' ').html( ' '+ ' '+ ' '+ ''+ ''+ ' '+ ' ' + ' ' + ' ' + '' + ' ' + ' ' + ' ' + ' ' + ' Content that violates any copyrights will be deleted. Encyclopedic content ' + 'must be verifiable. ' + ' By clicking the "Save Page" button, you agree to the ' + '' + 'Terms of Use, and you irrevocably agree to release your contribution under ' + 'the ' + 'CC-BY-SA 3.0 License and the ' + 'GFDL. ' + 'You agree that a hyperlink or URL is sufficient attribution under the Creative Commons license. ' + ' ' + ' ' + ' ' + ' ' + '<a title="Help:Edit summary" href="/wiki/Help:Edit_summary">Edit summary</a> ' + ' (Briefly describe the changes you have made) ' + ' ' + '<input type="text" name="wpSummary" value="" accesskey="b" title="Enter a short summary [alt-shift-b]" size="60" tabindex="1" maxlength="250" id="wpSummary" class="mw-summary">'+ ' ' + '<input type="checkbox" id="wpMinoredit" accesskey="i" tabindex="3" value="1" name="wpMinoredit"> ' + '<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>) ' + ' ' + '<input type="checkbox" id="wpWatchthis" accesskey="w" tabindex="4" checked="checked" value="1" name="wpWatchthis"> ' + '<label title="Add this page to your watchlist [alt-shift-w]" id="mw-editpage-watch" for="wpWatchthis">Watch this page ' + ' ' + ' ' + '<input type="button" title="Save your changes [alt-shift-s]" accesskey="s" value="Save page" tabindex="5" name="wpSave" id="wpSave">' + '<input type="button" title="Preview your changes; please use this before saving. [alt-shift-p]" accesskey="p" value="Show preview" tabindex="6" name="wpPreview" id="wpPreview">' + '<input type="button" title="Show which changes you made to the text [alt-shift-v]" accesskey="v" value="Show changes" tabindex="7" name="wpDiff" id="wpDiff">' + ' <a id="mw-editform-exit" title="Exit this application" >Cancel</a> | ' + '<a id="mw-editform-skip" title="Go to next unchecked page">Skip</a> | ' + '<a href="/wiki/Wikipedia:Cheatsheet" target="helpwindow">Editing help</a> (opens in new window) ' + ' ' + ' ' + ' ' + ' <small id="mw-wikimedia-editpage-tos-summary">If you do not want your ' + 'writing to be edited, used, and redistributed at will, then do not submit it ' + 'here. All text that you did not write yourself, except brief excerpts, must ' + 'be available under terms consistent with Wikipedia\'s ' + '<a title="foundation:Terms of Use" class="extiw" href="http://wikimediafoundation.org/wiki/Terms_of_Use">Terms of Use</a> '+ 'before you submit it. ' + ' ' + ' ' + ' ' + ' ' + ' Start from ' + '<input type="text" name="startfrom" id="startfrom" class="text ui-widget-content ui-corner-all" value="' + app.startfrom + '" /> ' + ' Skip pages without suggested changes ' + '<input type="checkbox" name="skipunchanged" id="skipunchanged" value="" class="ui-widget-content ui-corner-all" />' + ' ' + ' ' + ' '			)		);		// assign actions to form elements $('#bd-close').click(app.exit).hover(cpointer, cdefault); $('#wikEdDiffButton').click(app.showdiff); $('#wpSave').click(app.savepage); $('#wpPreview').attr("disabled", true); $('#wpDiff').click(app.showdiff); $('#mw-editform-exit').click(app.exit).hover(cpointer, cdefault); $('#mw-editform-skip').click(app.loadnext).hover(cpointer, cdefault); };	app = mw.RnB.birthdeath = { startfrom: mw.cookie.get('birthdeathstart') || '!', setup: function { // initialization that does require API access api = new mw.RnB.Wiki; $('#dialog-form').dialog({				autoOpen: true,				modal: true,				buttons: {					'Go': function {						app.startfrom = $('#startfrom').val;						app.skipunchanged = $('#skipunchanged:checked').length;							// will be 1 if checked, 0 if not						$(this).dialog("close");						app.go;					},					'Cancel': function {						$(this).dialog("close");						app.exit;					}				}			}); },		go: function { var copy = {}; $('#dialog-form').dialog("close"); // retrieve list of hndis pages app.cmquery = { action: 'query', rawcontinue: '', generator: 'categorymembers', gcmtitle: 'Category:Human name disambiguation pages', gcmnamespace: '0', gcmstartsortkeyprefix: app.startfrom, gcmlimit: '20', indexpageids: true, prop: 'categories|info|revisions', clcategories: "Category:Human name disambiguation pages", cllimit: 'max', clprop: 'sortkey', inprop: 'watched', intoken: 'edit', rvprop: 'content|timestamp' };			app.catmemlist = []; $('body').css("cursor", "wait"); $.extend(copy, app.cmquery); api.request(copy, app.listrecvd); },		listrecvd: function (response, query) { // received list of category members from server var pagelist = [], rq = response.query, rqc = response["query-continue"]; app.cmquery = $.extend({}, query); if (rqc && rqc.categorymembers) { app.cmquery.gcmcontinue = rqc.categorymembers.gcmcontinue; } else { delete app.cmquery.gcmcontinue; }			if (rq && rq.pages) { if (rq.pageids) { // server provides index for sorting the results $.each(rq.pageids, function {						var item = rq.pages[this];						if (item.categories) {							pagelist.push({ sortkey: item.categories[0].sortkeyprefix, text: item.revisions[0]['*'], timestamp: item.revisions[0].timestamp, title: item.title, token: item.edittoken, watched: (item.watched !== undefined) });						}					});				} else { // we have to sort ourselves, using the sortkey $.each(rq.pages, function {						if (this.categories) {							pagelist.push({ sortkey: this.categories[0].sortkeyprefix, text: this.revisions[0]['*'], timestamp: this.revisions[0].timestamp, title: this.title, token: this.edittoken, watched: (this.watched !== undefined) });						}					});					pagelist.sort(function (a, b) {						var as = a.sortkey,							bs = b.sortkey;						return as < bs ? -1 : as > bs ? 1 : 0;					}); }			}			$.merge(app.catmemlist, pagelist); if (! query.gcmcontinue) { // this is the first chunk received, not a continuation app.loadnext; }		},		loadnext: function { // load next disambig page and start processing var q,				page = app.currentpage = app.catmemlist.shift; app.currentlinks = {}; app.currentredirs = {}; if (! page) { app.exit; return; }			mw.cookie.set("birthdeathstart", page.sortkey, {expires: 3652}); $('#firstHeading').text(page.title); // load the pages linked from this disambiguation page api.request(				{	action: 'query',					generator: 'links',					rawcontinue: '',					titles: page.title,					redirects: true,					gplnamespace: '0',					gpllimit: 'max',					prop: 'info|revisions|categories',					rvprop: 'content',					cllimit: 'max'				}, app.processlinks			); $('#wpTextbox1').hide.val(app.currentpage.text); $('#wikEdDiffDiv').hide; $('#wpMinoredit').attr("checked", "checked"); if (page.watched || mw.user.options.get("watchdefault")) { $('#wpWatchthis').attr("checked", "checked"); } else { $('#wpWatchthis').prop("checked", false); }			$('input[name="wpSummary"]').val(				"Add birth/death dates to hndis entries, from linked article(s)"); $('body').css("cursor", "default"); // continue query in background if list is almost empty if (app.catmemlist.length < 5 && app.cmquery.gcmcontinue) { q = $.extend({}, app.cmquery); api.request(q, app.listrecvd); delete app.cmquery.gcmcontinue; }		},		processlinks: function (response, query) { // go through blue links on page and propose changes to text var rq = response.query, rqc = response["query-continue"], lines = app.currentpage.text.split("\n"), links = {}, newtext, redirs = {}, len = lines.length, i; if (rqc && rqc.links) { // unlikely any hndis page would contain more than 500 links, // but just in case... query.gplcontinue = rqc.links.gplcontinue; api.request(query, app.processlinks); }			if (rq && rq.redirects) { // create a map from redirect sources to target pages $.each(rq.redirects, function {					redirs[this.from] = this.to;				}); $.extend(app.currentredirs, redirs); }			if (rq && rq.pages) { // index links by linked page title $.each(rq.pages, function {					links[this.title] = this;				}); $.extend(app.currentlinks, links); // go through each line in disambig page, see if it starts with // a blue link $.each(lines, function (index) {					var thislink,						born = null,						born_already = null,						died = null,						died_already = null,						m = this.match(linkRx),						n,						revised = this.slice(0),						bold,						newtext; // copy of line					if (m !== null && m.length > 1) {						// link found; find corresponding object in links						thislink = redirs[m[1]]							? links[redirs[m[1]]]							: links[m[1]];						if (thislink === undefined) {							// invalid link, probably to a category or iw							return;						}						if (thislink.missing !== undefined) {							// this is a red link, skip it							return;						}						// blue link found; try to find birth/death year						// in categories						$.each(thislink.categories, function { var m1 = birthcats.exec(this.title), m2 = deathcats.exec(this.title); if (m1 !== null) { born = m1[1]; }							if (m2 !== null) { died = m2[1]; }						});						// here we could, if desired, look for birth/death						// years in other places, like 						// ...						if (born === null && died === null) {							return;						}						// birth and/or death year found;						// make sure the current line doesn't already						// contain them						n = bornRx.exec(this);						if (n !== null) {							born_already = n[1];						}						n = diedRx.exec(this);						if (n !== null) {							died_already = n[1];						}						n = datesRx.exec(this);						if (n !== null) {							born_already = n[1];							died_already = n[2];						}						if (born && !born_already) {							if (died && !died_already) {								// supply both birth and death years								newtext = " (" + born + "–" + died + ")";							} else {								// supply birth year only								if (died_already) {									newtext = " (" + born + "–" + died_already + ")";								} else {									newtext = " (born " + born + ")"; }							}						} else if (died && !died_already) { if (born_already) { newtext = " (" + born_already + "–" + died + ")"; } else { newtext = " (died " + died + ")"; }						}						if (newtext) { bold = "" + m[0] + ""; if (revised.indexOf(bold) !== -1) { revised = revised.replace(bold, bold + newtext); } else { revised = revised.replace(m[0], m[0] + newtext); }						}					}					lines[index] = revised; });			}			newtext = lines.join('\n');			if (app.skipunchanged && newtext == app.currentpage.text) {				return app.loadnext;			}			//cosmetic changes			newtext = newtext				.replace(/–/g, '–')				.replace(/\{\{ *ndash *\}\}/g, '–')				.replace(/\( *b\. */i, '(born ')				.replace(/\( *d\. */i, '(died ')				.replace(/\{\{ *[Rr]efer *(\|.*?)?\}\}/, '')				.replace(/\n*(\{\{[Hh]ndis.*?\}\})\n*/, '\n\n$1\n\n')				.replace(/\n*$/, '\n');			$('#firstHeading').append( $(' ').text("Skip").attr("id", "topskip") .css("font-size", "50%") );			$('#topskip').button.click(app.loadnext);			$('#wpTextbox1').val(newtext).show;			app.showdiff;		},		showdiff: function {			// refresh the WikEdDiff display			var m,				link,				linkedpage,				text = app.currentpage.text,				diff = window.WDiffString(text, $('#wpTextbox1').val),				allLinkRx = /\[\[([^\]\|\[#<>\{\}]*)(\|.*?)?\]\]([a-z]*)/g;					// same as linkRx, but with /g flag			// find each link in the text, and if it matches an article link			// from the database, wrap it in an <a> or element			while ((m = allLinkRx.exec(text)) !== null) {				// linked title = m[1]				// anchor = m[2]				// trail = m[3]				linkedpage = app.currentredirs[m[1]]							? app.currentlinks[app.currentredirs[m[1]]]							: app.currentlinks[m[1]];				if (linkedpage !== undefined && linkedpage.missing !== undefined) {					link = $('  ')						.addClass("new")						.attr("title", (m[2] ? m[2].replace(/^\|/, '') : m[1]) + (m[3] || '') + ' (page does not exist)')						// FIXME above is wrong, doesn't account for pipe tricks!						.append($(' ').css("color", "black").text(m[0]));				} else {					link = $('<a></a>')						.addClass("link")						.attr("href", mw.config.get('wgArticlePath') .replace('$1',									mw.util.wikiUrlencode(m[1]).replace(/ /g, '_')								) ).attr("target", "_blank")						.attr("title", (m[2] ? m[2].replace(/^\|/, '') : m[1]) + (m[3] || ''))						// FIXME above is wrong, doesn't account for pipe tricks!						.append($(' ').css("color", "black").text(m[0]));					if (app.currentredirs[m[1]]) {						link.addClass("mw-redirect")							.attr("title", link.attr("title") + ' (redirect to ' 								+ app.currentredirs[m[1 + ']])');					}				}				// FIXME doesn't work if there are 2 identical links				diff = diff.replace( m[0].replace(/"/g, '&quot;'),					link.wrap(" ").parent.html				);			}			$('#wikEdDiffDiv').html(diff).show;		},		savepage: function {			var t = app.currentpage.title,				params = {					action: 'edit',					title: t,					text: $('#wpTextbox1').val,					token: app.currentpage.token,					basetimestamp: app.currentpage.timestamp,					summary: $('input[name="wpSummary"]').val				};			if ($('#wpMinoredit:checked').length) {				params.minor = "";			}			if ($('#wpWatchthis:checked').length) {				params.watchlist = "watch";			}			api.request(params,				function  {mw.log("Page " + t + " saved");}			);			app.loadnext;		},		exit: function  {			// Restore original contents of page			$.each(original, function(elem, content) {				$(elem).html(content);			});			$('#content').removeClass("bd-content");		}	};	mw.loader.using(		['jquery.ui', 'mediawiki.cookie',		 'jquery.ui',		 'jquery.ui'], function { var startup = function { if (mw.RnB && mw.RnB.Wiki) { $(document).ready(app.setup); } else { setTimeout(startup, 100); }			};			$(document).ready(onready); startup; }	); } (jQuery)); //