User:Gary/reviewer summary.js

/*	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 = $(' '); var debug = $(' '); reviewerSummary.append(debug).append(' Reviewer Summary '); reviewerSummary.append($(' ').append('(').append($(' ').append('0')).append(' nominations processed)')); var notes = $('To copy the table, highlight the text, and then copy and paste it into an edit box. Wrap the text in &lt;pre&gt; tags so that the spacing is retained. Wait until it says "all X nominations processed" above before copying the table. Click on table headers to sort.Numbers in brackets indicate the average per article."Bytes" indicates approximately the total number of characters added/removed. A negative amount here is possible, if the user removed more text than they added, such as in the case of performing maintenance work. '); reviewerSummary.append(notes); // body var body = $(' '); var bodyTable = $(' '); // (? ) reviewerSummary.append(body).append(bodyTable).append(' '); 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)) + ') '); editAverage.contents.eq(0).replaceWith(editAverageText); sizeAverageText = $('<span title="' + (newSize + ' / ' + newArticles) + '"> (' + addCommas((newSize / newArticles).toFixed(1)) + ') '); 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 = $(' '); username = $('<td id="username-' + formattedUsername + '"> ').append('<a href="/wiki/User:' + formattedUsername + '">' + user + '</a>').append(padding.clone); articles = $('<td id="articles-' + formattedUsername + '"><abbr title="' + userArticles + '">' + numberOfArticles + ' ').append(padding.clone); edits = $('<td id="edits-' + formattedUsername + '"> ').append(' ' + numberOfEdits + ' ').append('<span id="edit-average-' + formattedUsername + '"> ').append(padding.clone); size = $('<td id="size-' + formattedUsername + '"> ').append(' ' + addCommas(numberOfSize) + ' ').append('<span id="size-average-' + formattedUsername + '"> ').append(padding.clone); $('#reviewer-table').parent.append($('<tr id="row-' + formattedUsername + '"> ').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);