/* 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 *\}\}) *(\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="http://up.wiki.x.io/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"> ' +
'<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"> ' +
'<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> | ' +
'<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)</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(/–/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, '"'),
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>