User:R'n'B/dplupdate.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
Documentation for this user script can be added at User:R'n'B/dplupdate. |
/* dplupdate.js : Script to update monthly Disambiguation Pages with Links page
* Copyright (c) 2012-22 [[en:User:R'n'B]]
* Creative Commons Attribution-ShareAlike License applies
* Requires wrappi.js, MediaWiki 1.17, and jQuery 1.7 (included with MediaWiki)
* /
// Version 0.13
/*global console, mw, jQuery, importScript, OO */
/*jshint multistr: true */
(function ($) {
var api, old_wikitext, wikitext, timestamp,
donetext = [], notdonetext = [], nolongertext = [],
continued = {},
DABCOUNT = 1000,
LOWCOUNT = 1, // mark Done if less than this many links
HIGHCOUNT = 10, // mark not Done if more than this many links
progress = 0,
pages_done = 0,
nolonger_dabs = 0,
editsummary = "Update count", // default
pagedata = {
'todo': {}, 'done': {},
'todocount': 0, 'donecount': 0,
'todolinks': 0, 'donelinks': 0
},
pattern = /^# (?:\[\[File:[^\]]*\]\])*\[\[([^\]]+)\]\]: ([0-9,]+) (\[\[Special:[^\n]+)$/gm,
pageid = mw.config.get('wgArticleId'),
fmt = function (num) {
while (num.match(/\d{4}/) !== null) {
num = num.replace(/(\d)(\d\d\d)(,|$)/, "$1,$2$3");
}
return num;
},
load_page = function () {
// retrieve the wikitext of this page
api.request({
'action': 'query',
'pageids': pageid,
'prop': 'revisions',
'rvprop': 'content|timestamp'
},
read_page
);
},
read_page = function (response) {
// create an internal representation of the page lists
var todomark, donemark, match, title, links, fmtnum;
mw.RnB.pagedata = pagedata;
mw.RnB.wikitext = wikitext = response.query.pages[pageid].revisions[0]['*'];
mw.RnB.continued = continued;
timestamp = response.query.pages[pageid].revisions[0].timestamp;
old_wikitext = wikitext;
todomark = wikitext.indexOf("===To do===");
donemark = wikitext.indexOf("===Done===");
while ((match = pattern.exec(wikitext)) !== null) {
title = match[1].trim();
links = parseInt(match[2].replace(/,/g, ''), 10);
if (match.index > todomark && match.index < donemark) {
if (pagedata.todo[title]) {
OO.ui.alert('Page "' + title + '" appears twice in the "To do" section.', {title: 'Duplicate page error'} );
} else {
pagedata.todo[title] = links;
pagedata.todocount += 1;
pagedata.todolinks += links;
}
} else if (match.index > donemark) {
if (pagedata.done[title]) {
OO.ui.alert('Page "' + title + '" appears twice in the "Done" section.', {title: 'Duplicate page error'} );
} else if (pagedata.todo[title]) {
OO.ui.alert('Page "' + title + '" appears in both the "To do" and "Done" sections.', {title: 'Duplicate page error'} );
} else {
pagedata.done[title] = links;
pagedata.donecount += 1;
pagedata.donelinks += links;
}
}
}
if (pagedata.todocount + pagedata.donecount !== DABCOUNT) {
fmtnum = fmt((pagedata.todocount + pagedata.donecount).toString());
OO.ui.alert('Parsed link counts for ' + fmtnum + ' pages.', {title: 'Page count error'} );
return;
}
},
update_totals = function () {
var progress1 = /out of a total of ([0-9,]+) links, approximately ([0-9,]+) have currently been fixed/,
progress2 = /\{\{Progress bar\|([0-9]+)\|total=([0-9]+)\|width=60%\}\}/,
m1 = progress1.exec(wikitext),
m2 = progress2.exec(wikitext),
p1, p2, num1, num2;
mw.log(pagedata.donelinks, "out of", pagedata.todolinks + pagedata.donelinks);
if (m1 === null || m2 === null) {
OO.ui.alert('Unable to parse link counts from "Progress" section of page.', {title: 'Format error'} );
return;
}
if (m2[1] === pagedata.donelinks.toString()) {
// no change, so don't update
return;
}
num1 = (pagedata.todolinks + pagedata.donelinks).toString();
num2 = pagedata.donelinks.toString();
p1 = ("out of a total of " + fmt(num1) +
" links, approximately " + fmt(num2) +
" have currently been fixed");
p2 = ("{{Progress bar|" + num2 +
"|total=" + num1 + "|width=60%}}");
wikitext = wikitext.replace(progress1, p1).replace(progress2, p2);
// ask server to generate the diff
api.request({
action: "compare",
fromid: pageid,
toslots: "main",
"totext-main": wikitext,
"tocontentformat-main": "text/x-wiki",
prop: "diff"
}, showdiff);
},
showdiff = function (response) {
var original = mw.util.$content.html(),
diff = response.compare['*'],
diffhtml = (
'<table class="diff diff-contentalign-left">\
<colgroup>\
<col class="diff-marker">\
<col class="diff-content">\
<col class="diff-marker">\
<col class="diff-content">\
</colgroup>\
<tbody>\
<tr valign="top">\
<td class="diff-otitle" colspan="2">Latest revision</td>\
<td class="diff-ntitle" colspan="2">Your text</td>\
</tr>' + diff + '</table>');
if (! diff) {
return;
}
mw.util.$content.html(diffhtml);
OO.ui.prompt('Accept these changes?', {textInput: {value: editsummary}}).done(
function (result) {
if (result !== null) {
// save the page
api.request({
action: 'edit',
title: mw.config.get("wgPageName"),
text: wikitext,
summary: result,
token: mw.user.tokens.get("csrfToken"),
basetimestamp: timestamp
}, page_saved);
} else {
mw.util.$content.html(original);
wikitext = old_wikitext;
}
}
);
},
page_saved = function (response) {
// load edited page
if (response.edit.result === "Success") {
window.location.reload();
return;
}
OO.ui.alert("Error saving page: '" + response.edit.toString() + "'", {title: "API error"} );
mw.log(response);
},
minmax = function (min, val, max) {
if (val < min) return min;
if (val > max) return max;
return val;
},
lines = 2.5,
progressDialog,
update_pages = function () {
// dispatch backlinks requests for all linked pages
var titlelist = [],
t;
// this is going to take a while, so set up progress dialog
function ProgressDialog( config ) {
ProgressDialog.super.call( this, config );
}
OO.inheritClass( ProgressDialog, OO.ui.Dialog );
ProgressDialog.static.name = 'progressDialog';
ProgressDialog.prototype.initialize = function () {
ProgressDialog.super.prototype.initialize.call( this );
this.content = new OO.ui.PanelLayout( { padded: true, expanded: true, scrollable: true } );
this.content.$element.append( '<p>Now updating backlink counts to disambig pages. Press Escape key to cancel.</p>');
this.progbar = new OO.ui.ProgressBarWidget({ id: 'dplprogress', progress: 0 });
this.progbar.toggle(true);
this.content.$element.append(this.progbar.$element);
this.content.$element.append($('<div id="dpllog"></div>'));
this.$body.append( this.content.$element );
};
ProgressDialog.prototype.getBodyHeight = function () {
return this.content.$element.outerHeight( true ) * minmax(3, lines, 12);
};
progressDialog = new ProgressDialog( {size: 'larger'} );
// Create and append a window manager, which opens and closes the window.
var windowManager = new OO.ui.WindowManager();
$( document.body ).append( windowManager.$element );
windowManager.addWindows( [ progressDialog ] );
windowManager.openWindow( progressDialog );
for (t in pagedata.todo) {
if (pagedata.todo.hasOwnProperty(t)) {
titlelist.push(t);
}
}
for (t in pagedata.done) {
if (pagedata.done.hasOwnProperty(t)) {
titlelist.push(t);
}
}
while (titlelist.length) {
t = titlelist.shift();
// retrieve backlinks, also categories and page info to
// confirm whether this is a redirect and/or no longer a dab
api.request({
action: 'query',
prop: 'info|categories',
titles: t,
indexpageids: "",
redirects: "",
list: 'backlinks',
bltitle: t,
blnamespace: 0,
blredirect: "",
bllimit: "max"
}, process_count);
}
},
process_count = function (response, query) {
// count the eligible backlinks from the API response
var item, redir, len, redirlen, ititle, rd, donemark,
page = response.query.pages[response.query.pageids[0]],
bl = response.query.backlinks,
count = 0,
dabtitle = query.bltitle,
isredirect = false,
hasdabcat = false,
conceptdab = false,
p = new RegExp('# ((?:\\[\\[File:[^\\]]*\\]\\])*)\\[\\[' +
mw.util.escapeRegExp(dabtitle) +
'\\]\\]: ([0-9,]+) (\\[\\[Special:[^\\n]+)\\n'),
m = p.exec(wikitext);
if (! continued[dabtitle]) {
continued[dabtitle] = {};
}
// first check that this is still a dab
if (response.query.redirects) {
isredirect = true;
}
if (page.categories) {
len = page.categories.length;
for (item = 0; item < len; item += 1) {
if (page.categories[item].title ===
"Category:All article disambiguation pages") {
hasdabcat = true;
}
if (page.categories[item].title ===
"Category:Disambiguation pages to be converted to broad concept articles") {
conceptdab = true;
}
}
}
if (! hasdabcat) {
// no need to count backlinks if no longer a disambig page
if (pagedata.todo[dabtitle]) {
// move to done section
wikitext = wikitext.replace(p, '').concat(
'\n# ' + m[1] + '[[' + dabtitle + ']]: ' + m[2] + " " + m[3] +
" - no longer a disambig page");
if (dabtitle === "State") { mw.log(wikitext); }
pagedata.done[dabtitle] = pagedata.todo[dabtitle];
pagedata.todocount -= 1;
pagedata.donecount += 1;
pagedata.todolinks -= pagedata.todo[dabtitle];
pagedata.donelinks += pagedata.todo[dabtitle];
delete pagedata.todo[dabtitle];
$("<p></p>").text(
"[[" + dabtitle + "]] is no longer a dab, moved to 'done'")
.appendTo($('#dpllog'));
lines += 0.9;
progressDialog.updateSize();
nolongertext.push("[[" + dabtitle + "]]");
nolonger_dabs += 1;
}
progress += 1;
progressDialog.progbar.setProgress(progress * 100 / DABCOUNT);
mw.log("Progress = " + progress);
if (progress === DABCOUNT) {
check_done();
}
return;
}
// save results
len = bl.length;
for (item = 0; item < len; item += 1) {
ititle = bl[item].title;
if (bl[item].redirlinks) {
rd = bl[item].redirlinks;
redirlen = rd.length;
if (! continued[dabtitle][ititle]) {
continued[dabtitle][ititle] = {};
}
for (redir = 0; redir < redirlen; redir += 1) {
continued[dabtitle][ititle][rd[redir].title] = true;
}
} else {
if (! bl[item].hasOwnProperty('redirect')) {
continued[dabtitle][ititle] = true;
}
}
}
if (response['query-continue'] && response['query-continue'].blcontinue) {
// continue query
query.blcontinue = response['query-continue'].blcontinue;
query.rawcontinue = "";
api.request(query, process_count);
return;
}
if (conceptdab) {
// mark, if not already marked
if (m[3].indexOf("DABCONCEPT") === -1) {
wikitext = wikitext.replace(p, $.trim(m[0].slice(0, -1)) +
" – tagged as [[WP:DABCONCEPT]]\n");
}
}
// count the eligible links
bl = continued[dabtitle];
for (item in bl) {
if (bl.hasOwnProperty(item)) {
if (bl[item] === true) {
if (item !== dabtitle && !dabtitle.endswith("(disambiguation)")) {
count += 1;
}
} else {
// item is a redirect with its own backlinks
if (! item.endswith("(disambiguation)")) {
for (rd in bl[item]) {
if (bl[item].hasOwnProperty(rd)) {
count += 1;
}
}
}
}
}
}
// see if dabtitle needs to be moved to the other section
if (pagedata.todo[dabtitle] && count < LOWCOUNT) {
// this is done
wikitext = wikitext.replace(p, '').concat(
'\n# ' + m[1] + '[[' + dabtitle + ']]: ' + m[2] + " " + m[3]);
pagedata.done[dabtitle] = pagedata.todo[dabtitle];
pagedata.todocount -= 1;
pagedata.donecount += 1;
pagedata.todolinks -= pagedata.todo[dabtitle];
pagedata.donelinks += pagedata.todo[dabtitle];
delete pagedata.todo[dabtitle];
$("<p></p>").text(
"[[" + dabtitle + "]] (" + count + " links) moved to 'done'")
.appendTo($('#dpllog'));
lines += 0.9;
progressDialog.updateSize();
donetext.push("[[" + dabtitle + "]]");
pages_done += 1;
} else if (pagedata.done[dabtitle] && count > HIGHCOUNT) {
// this is un-done
wikitext = wikitext.replace(p, '');
donemark = wikitext.indexOf("===Done===");
wikitext = wikitext.slice(0, donemark-1) +
('# ' + m[1] + '[[' + dabtitle + ']]: ' + m[2] + " " + m[3] + '\n') +
wikitext.slice(donemark-1);
pagedata.todo[dabtitle] = pagedata.done[dabtitle];
pagedata.donecount -= 1;
pagedata.todocount += 1;
pagedata.donelinks -= pagedata.done[dabtitle];
pagedata.todolinks += pagedata.done[dabtitle];
delete pagedata.done[dabtitle];
mw.log("Info:", dabtitle, "moved back to 'to do'");
$("<p></p>").text(
"[[" + dabtitle + "]] (" + count + " links) moved back to 'To do'")
.appendTo($('#dpllog'));
lines += 0.9;
progressDialog.updateSize();
notdonetext.push("[[" + dabtitle + "]]");
}
if (pagedata.todo[dabtitle] && isredirect) {
$("<p></p>").text(
"[[" + dabtitle + "]] is now a redirect to [[" +
response.query.redirects[0].to + "]]"
).appendTo($('#dpllog'));
lines += 0.9;
}
progress += 1;
progressDialog.progbar.setProgress(progress * 100 / DABCOUNT);
mw.log("Progress = " + progress);
if (progress === DABCOUNT) {
progressDialog.close();
check_done();
}
},
check_done = function () {
// this should be called by the progress bar when it hits 100%
var es1 = '', es2 = '', es3 = '',
jointhem = function (s1, s2) {
return (s1 && s2) ? (s1 + "; " + s2) : (s1 || s2);
};
mw.log("check_done called");
$('#progressdiv').dialog('destroy');
editsummary = "";
if (donetext.length > 0) {
es1 = donetext.join(", ") + " done";
}
if (nolongertext.length > 0) {
es2 = nolongertext.join(", ") + " no longer " +
((nolongertext.length > 1) ? "dabs" : "a dab");
}
if (notdonetext.length > 0) {
es3 = notdonetext.join(", ") + " still " +
((notdonetext.length > 1) ? "have" : "has") + " links to fix";
}
// create tentative editsummary
editsummary = jointhem(jointhem(es1, es2), es3);
if (editsummary.length > 480 && nolonger_dabs > 0) {
es2 = nolonger_dabs.toString() +
" no longer " + ((nolonger_dabs > 1) ? "dabs" : "a dab");
editsummary = jointhem(jointhem(es1, es2), es3);
}
if (editsummary.length > 480 && pages_done > 0) {
es1 = pages_done.toString() + " page" +
((pages_done > 1) ? "s" : "") + " done";
editsummary = jointhem(jointhem(es1, es2), es3);
}
if (editsummary === "") {
editsummary = "Update count";
} else {
editsummary += "; update count";
}
update_totals();
};
String.prototype.endswith = function (substring) {
// return true if substring matches the end of this
return (this.slice(-substring.length) === substring);
};
$(function () { // on document ready:
var startup = function () {
if (mw.RnB && mw.RnB.Wiki) {
mw.loader.using([
'oojs-ui-core',
'oojs-ui-windows',
'mediawiki.diff.styles'
],
function () {
mw.util.addPortletLink("p-tb", "#",
"Update totals only", "tb-totals");
mw.util.addPortletLink("p-tb", "#",
"Update pages and totals", "tb-update");
$('#tb-totals').click(function (e) {
update_totals();
e.preventDefault();
});
$('#tb-update').click(function (e) {
update_pages();
e.preventDefault();
});
api = new mw.RnB.Wiki();
load_page();
});
} else {
setTimeout(startup, 100);
}
};
startup();
});
}(jQuery) );