Jump to content

User:R'n'B/birthdeath.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.
/* 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)
 * <nowiki>
 */
// 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;|\{\{ *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(
			$('<div class="bd-banner">Human name disambiguation page fixer</div>')
				.append($('<div style="float: right; margin: 0px 0.25em"></div>')
					.append($('<img alt="Close" id="bd-close"' +
'src="http://bits.wikimedia.org/skins-1.17/common/images/closewindow.png">'))
				)
		).append(
			$('<h1 id="firstHeading" class="firstHeading"></h1>')
				.text('(Loading...)')
		).append(
			$('<div id="bodyContent"></div>').html(
'<div id="wikEdDiffWrapper" class="wikEdDiffWrapper">'+
'<div id="wikEdDiffDiv" class="wikEdDiffDiv"></div>'+
'<div id="wikEdDiffButtonWrapper" class="wikEdDiffButtonWrapper">'+
'<button id="wikEdDiffButton" title="Refresh diff view" class="wikEdDiffButton">'+
'<img id="wikEdDiffButtonImg" src="/media/wikipedia/commons/c/c6/WikEdDiff.png" title="Refresh diff view" alt="wikEdDiff">'+
'</button></div>'+
'</div><!-- wikEdDiffWrapper -->' +
'<form id="editform">' +
'<div class="wikiEditor-ui-text">' +
'<textarea style="" rows="25" cols="80" id="wpTextbox1" accesskey="," tabindex="1">' +
'</textarea>' +
'</div>' +
'<div style="clear: both;"></div>' +
'<div id="editpage-copywarn">' +
'<p>Content that violates any copyrights will be deleted. Encyclopedic content ' +
'must be <b><a title="Wikipedia:Verifiability"' +
' href="/wiki/Wikipedia:Verifiability">verifiable</a></b>.</p>' +
'<p>By clicking the "Save Page" button, you agree to the ' +
'<a title="wmf:Terms of Use" class="extiw" href="http://wikimediafoundation.org/wiki/Terms_of_Use">' +
'Terms of Use</a>, and you irrevocably agree to release your contribution under ' +
'the <a title="Wikipedia:Text of Creative Commons Attribution-ShareAlike 3.0 Unported License"' +
' href="/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License">' +
'CC-BY-SA 3.0 License</a> and the ' +
'<a title="Wikipedia:Text of the GNU Free Documentation License"' +
' href="/wiki/Wikipedia:Text_of_the_GNU_Free_Documentation_License">GFDL</a>. ' +
'You agree that a hyperlink or URL is sufficient attribution under the Creative Commons license.</p>' +
'</div><!-- editpage-copywarn -->' +
'<div class="editOptions">' +
'<span id="wpSummaryLabel" class="mw-summary">' +
'<label for="wpSummary"><span style="text-align: left;">' + 
'<a title="Help:Edit summary" 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" tabindex="1" maxlength="250" id="wpSummary" class="mw-summary">'+
'<div class="editCheckboxes">' +
'<input type="checkbox" id="wpMinoredit" accesskey="i" tabindex="3" value="1" 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" tabindex="4" checked="checked" value="1" 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><!-- editCheckboxes -->' +
'<div class="editButtons">' +
'<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">' +
'<span class="editHelp"><a id="mw-editform-exit" title="Exit this application" >Cancel</a>&nbsp;| ' +
'<a id="mw-editform-skip" title="Go to next unchecked page">Skip</a>&nbsp;| ' +
'<a href="/wiki/Wikipedia:Cheatsheet" target="helpwindow">Editing help</a> (opens in new window)</span>' +
'</div><!-- editButtons -->' +
'</div><!-- editOptions -->' +
'<div class="mw-tos-summary">' +
'<p><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 ' +
'<b><a title="foundation:Terms of Use" class="extiw" href="http://wikimediafoundation.org/wiki/Terms_of_Use">Terms of Use</a></b> '+
'before you submit it.</small>' +
'</p></div><!-- mw-tos-summary --></form><!-- editform -->' +
'<div id="dialog-form" title="Human name disambiguation page fixer">' +
'<form>' +
'<fieldset>' +
'<label for="startfrom">Start from</label>' +
'<input type="text" name="startfrom" id="startfrom" class="text ui-widget-content ui-corner-all" value="' + app.startfrom + '" /><br />' +
'<label for="skipunchanged">Skip pages without suggested changes</label>' +
'<input type="checkbox" name="skipunchanged" id="skipunchanged" value="" class="ui-widget-content ui-corner-all" />' +
'</fieldset>' +
'</form>' +
'</div><!-- dialog-form -->'
			)
		);
		// 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 {{Persondata}}
						// ...
						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(/&ndash;/g, '–')
				.replace(/\{\{ *ndash *\}\}/g, '–')
				.replace(/\( *b\. */i, '(born ')
				.replace(/\( *d\. */i, '(died ')
				.replace(/\{\{ *[Rr]efer *(\|.*?)?\}\}/, '{{subst:refer$1}}')
				.replace(/\n*(\{\{[Hh]ndis.*?\}\})\n*/, '\n\n$1\n\n')
				.replace(/\n*$/, '\n');
			$('#firstHeading').append(
				$('<span></span>').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 <span> 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 = $('<span></span>')
						.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($('<span></span>').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($('<span></span>').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("<span>").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));
// </nowiki>