User:Schnark/mostEdited.js

/** * User:Schnark/mostEdited.js * * User script to show the pages which were edited most in the last time * * @author Michael Müller (User:Schnark) * @license GPL (+ CC-BY-SA as all pages in this wiki) * * * Copyright (C) 2011 Michael Müller * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to * * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA  02110-1301, USA. * * */ /*global jQuery: true, mw: true */ ( function ( $ ) {

/** * replace mw.html.element with a version that accepts boolean attributes * @TODO remove this once MW 1.19 is used */ var oldMwHtmlElement = mw.html.element; mw.html.element = function ( name, attrs, contents ) { var newAttrs = {}; for ( var attrName in attrs ) { var v = attrs[attrName]; // Convert name=true, to name=name if ( v === true ) { v = attrName; // Skip name=false } else if ( v === false ) { continue; }		newAttrs[attrName] = '' + v;	} return oldMwHtmlElement.apply( mw.html, [name, newAttrs, contents] ); };

/** * pagesList contains information for every edited page in the form * 'Pagename': { *	oldsize: 1234, *	newsize: 4321, *	edits: 123, *	minor: 23, *	users: ['A', 'B'], *	anons: 5, *	sections: { *		'Section A': { *			edits: 12, *			minor: 2, *			users: ['A'], *			anons: 2 *		} *	} * } */

var pagesList = {};

/** * This function gets all recent changes in the namespaces starting at the start time. * After the last API call has been done it will call the callback function * @param start {string} start time (timestamp) * @param namespaces {string} namespaces to show, either empty for all or something like '0|1|5' * @param maxcalls {number} maximal number of API calls * @param callback {function} function after the last API call */ function getAPIRecentChanges ( start, namespaces, maxcalls, callback ) { var data = { action: 'query', list: 'recentchanges', rcend: start, rclimit: 'max', rcprop: 'user|comment|title|sizes|flags', format: 'json' };	if ( namespaces ) { data.rcnamespace = namespaces; }	$.getJSON( mw.util.wikiScript( 'api' ), data, function ( json ) {		if ( json && json.query && json.query.recentchanges ) {			var rc = json.query.recentchanges;			for ( var i = 0; i < rc.length; i++ ) {				var edit = rc[i];				if ( !( edit.title in pagesList ) ) {					pagesList[edit.title] = {						newsize: edit.newlen, // the first is the latest edit, so newlen is the most recent size						edits: 0,						minor: 0,						users: [],						anons: 0,						sections: {}					};				}				var section = /^\/\*\s*(.*?)\s*\*\//.exec( edit.comment ); // title of the section				if ( section ) {					section = section[1];				}				if ( section ) {					if ( !( section in pagesList[edit.title].sections ) ) {						pagesList[edit.title].sections[section] = {							edits: 0,							minor: 0,							users: [],							anons: 0						};					}				}				pagesList[edit.title].edits++; // increment edits				if ( section ) { pagesList[edit.title].sections[section].edits++; }				if ( edit.minor === '' ) { // increment minor edits pagesList[edit.title].minor++; if ( section ) { pagesList[edit.title].sections[section].minor++; }				}				pagesList[edit.title].oldsize = edit.oldlen; // update oldlen for every edit, only the earliest (= last) is interesting if ( $.inArray( edit.user, pagesList[edit.title].users ) === -1 ) { // store if new user pagesList[edit.title].users.push( edit.user ); if ( edit.anon === '' ) { pagesList[edit.title].anons++; }				}				if ( section ) { if ( $.inArray( edit.user, pagesList[edit.title].sections[section].users ) === -1 ) { pagesList[edit.title].sections[section].users.push( edit.user ); if ( edit.anon === '' ) { pagesList[edit.title].sections[section].anons++; }					}				}			}		}		if ( maxcalls > 1 && json && json['query-continue'] && json['query-continue'].recentchanges ) { getAPIRecentChanges( json['query-continue'].recentchanges.rcstart, namespaces, maxcalls - 1, callback ); } else { callback; }	} ); }

/** * get the pages/sections with the most edits * @param data {object} data about the number of edits, entries must have the form *	'Name': {edits: 123} * @param count {number} number of pages/sections to get * @return {array} list of the pages/sections with the most edits (decreasing order) */

function getMostEdited ( data, count ) { var items = []; for ( var item in data ) { items.push( item ); }	items.sort( function ( a, b ) {		return data[b].edits - data[a].edits;	} ); return items.slice( 0, count ); }

/** * encodes a section name to be used as anchor for a link to this section * @param section {string} title of the section * @return {string} encoded anchor */

function encodeSectionLink ( section ) { return mw.util.rawurlencode( section.replace( / /g, '_' ) ) .replace( /%3A/g, ':' ) .replace( /%/g, '.' ) .replace( /^([^a-zA-Z])/, 'x$1' ); }

/** * get HTML for one entry * @param page {string} name of the page to get HTML for * @param count {number} number of sections to show * @return {string} HTML */

function generatePageHTML ( page, count ) { var	data = pagesList[page], html = mw.html.element( 'h2', {}, new mw.html.Raw( mw.html.element( 'a', {href: mw.util.wikiGetlink( page ), title: page}, page ) ) ); html += mw.html.element( 'ul', {}, new mw.html.Raw( mw.html.element( 'li', {}, data.edits + ' edits (' + data.minor + ' minor edits)' ) + // FIXME localize mw.html.element( 'li', {}, data.users.length + ' users (' + data.anons + ' anons)' ) + // FIXME localize mw.html.element( 'li', {}, data.newsize - data.oldsize + ' bytes' ) ) ); // FIXME localize var sections = getMostEdited( data.sections, count ); for ( var i = 0; i < sections.length; i++ ) { html += mw.html.element( 'h3', {}, new mw.html.Raw( mw.html.element( 'a',				{href: mw.util.wikiGetlink( page ) + '#' + encodeSectionLink( sections[i] )},				sections[i] ) ) ); html += mw.html.element( 'ul', {}, new mw.html.Raw( mw.html.element( 'li', {}, data.sections[sections[i]].edits + ' edits (' + data.sections[sections[i]].minor + ' minor edits)' ) + // FIXME localize mw.html.element( 'li', {}, data.sections[sections[i]].users.length + ' users (' + data.sections[sections[i]].anons + ' anons)' ) ) ); // FIXME localize }	return html; }

/** * get HTML for complete list * @param countPages {number} number of pages to show * @param countSections {number} number of sections to show for each page * @return {string} HTML */

function generateHTML ( countPages, countSections ) { var	pages = getMostEdited ( pagesList, countPages ), html = ''; for ( var i = 0; i < pages.length; i++ ) { html += generatePageHTML( pages[i], countSections ); }	return html; }

/** * get HTML for header * @return {string} HTML */

function generateHeaderHTML { $( '#firstHeading' ).text( 'Most edited pages' ); // FIXME localize var	html = '', legend = '', labelTime = '', selectTime = '', labelNamespaces = '', selectNamespaces = '', invert = '', associated = '', submit = '', optionsTime = [], optionsNamespaces = [], formattedNamespaces = mw.config.get( 'wgFormattedNamespaces' ); legend = mw.html.element( 'legend', {}, 'Most edited pages options' ); // FIXME localize labelTime = mw.html.element( 'label', {'for': 'time'}, 'Time:' ); // FIXME localize optionsTime = [ // FIXME localize, read from URL mw.html.element( 'option', {value: 0.25}, '15 minutes' ), mw.html.element( 'option', {value: 0.5}, '30 minutes' ), mw.html.element( 'option', {value: 1}, '1 hour' ), mw.html.element( 'option', {value: 2}, '2 hours' ), mw.html.element( 'option', {value: 24}, '1 day' ), mw.html.element( 'option', {value: 720}, '1 month (testing only)' ) ];	selectTime = mw.html.element( 'select',		{id: 'time', name: 'time', 'class': 'timeselector'},		new mw.html.Raw( optionsTime.join( '' ) ) ); labelNamespaces = mw.html.element( 'label', {'for': 'namespace'}, 'Namespace:' ); // FIXME localize optionsNamespaces.push( mw.html.element( 'option', {value: ''}, 'all' ) ); // FIXME localize for ( var i in formattedNamespaces ) { if ( i < 0 ) { continue; }		var namespace = formattedNamespaces[i]; if ( namespace === '' ) { namespace = '(Main)'; // FIXME localize }		optionsNamespaces.push( mw.html.element( 'option', {value: i, selected: mw.util.getParamValue( 'namespace' ) === i}, // both are strings namespace ) ); }	selectNamespaces = mw.html.element( 'select',		{id: 'namespace', name: 'namespace', 'class': 'namespaceselector'},		new mw.html.Raw( optionsNamespaces.join( '' ) ) ); invert = mw.html.element( 'input', {name: 'invert', value: 1, id: 'nsinvert', checked: mw.util.getParamValue( 'invert' ) === '1',			title: 'Check this box to hide changes to pages within the selected namespace (and the associated namespace if checked)', // FIXME localize			type: 'checkbox' } ) + ' ' +		mw.html.element( 'label', {'for': 'nsinvert',			title: 'Check this box to hide changes to pages within the selected namespace (and the associated namespace if checked)'},			'Invert selection' ); // FIXME localize associated = mw.html.element( 'input', {name: 'associated', value: 1, id: 'nsassociated', checked: mw.util.getParamValue( 'associated' ) === '1',			title: 'Check this box to also include the talk or subject namespace associated with the selected namespace', // FIXME localize			type: 'checkbox' } ) + ' ' +		mw.html.element( 'label', {'for': 'nsassociated',			title: 'Check this box to also include the talk or subject namespace associated with the selected namespace'},			'Associated namespace' ); // FIXME localize submit = mw.html.element( 'input', {type: 'button', id: 'submitButton', value: 'Go'} ); // FIXME localize html += ' ' + // structure copied from HTML of Special:RecentChanges legend + ' ';	html += mw.html.element( 'div', {id: 'mostEditedContainer'} ); return html; }

/** * called when user clicks submit button */

function submitQuery { var	namespace = $( '#namespace option:selected' ).val, invert = $( '#nsinvert' ).prop( 'checked' ), associated = $( '#nsassociated' ).prop( 'checked' ), namespaces, hours = $( '#time option:selected' ).val, ago = new Date( ( new Date ).getTime - ( hours * 60 * 60 * 1000 ) ), start = String( ago.getUTCFullYear ) + String( ago.getUTCMonth + 101 ).substr( 1 ) + String( ago.getUTCDate + 100 ).substr( 1 ) + String( ago.getUTCHours + 100 ).substr ( 1 ) + String( ago.getUTCMinutes + 100 ).substr( 1 ) + String( ago.getUTCSeconds + 100 ).substr( 1 ); if ( namespace === '' ) { namespaces = ''; // all } else { namespace = Number( namespace ); var list = [namespace]; if ( associated ) { list.push(				( namespace % 2 === 0 ) ?					namespace + 1 :					namespace - 1 ); }		if ( invert ) { namespaces = []; var formattedNamespaces = mw.config.get( 'wgFormattedNamespaces' ); for ( var i in formattedNamespaces ) { if ( i >= 0 && $.inArray( Number( i ), list ) === -1 ) { namespaces.push( i ); }			}			namespaces = namespaces.join( '|' ); } else { namespaces = list.join( '|' ); }	}	$( '#submitButton' ).prop( 'disabled', true ); //$( '#mostEditedContainer' ).empty.injectSpinner( 'mostedited' ); pagesList = {}; // empty getAPIRecentChanges( start, namespaces, 5, function { // FIXME configure		$( '#mostEditedContainer' ).html( generateHTML( 10, 3 ) ); // FIXME configure		//$.removeSpinner( 'mostedited' );		$( '#submitButton' ).prop( 'disabled', false );	} ); }

/** * initialises the interface on Special:Blankpage, Special:RecentChanges and the sidebar everywhere */

function initBlankpage { document.title = 'Most edited pages'; // FIXME localize mw.util.$content.html( generateHeaderHTML ); mw.loader.load( 'mediawiki.special.recentchanges' ); // enables/disables checkboxes $( '#submitButton' ).click( submitQuery ).click; }

function initRecentchanges { var $button = $( mw.html.element( 'input', {type: 'button', value: 'Show most edited pages'} ) ) // FIXME localize .click( function {			var	namespace = $( '#namespace option:selected' ).val,				invert = $( '#nsinvert' ).prop( 'checked' ) ? '1' : '0',				associated = $( 'nsassociated' ).prop( 'checked' ) ? '1' : '0';			document.location.href = mw.util.wikiGetlink( 'Special:BlankPage' ) + '?' +				$.param( {action: 'mostedited', namespace: namespace, invert: invert, associated: associated} );		} ); $( 'input[type="submit"]' ).eq( 0 ).after( $button ); // FIXME breaks when there is another submit button before it }

function initSidebar { mw.util.addPortletLink( 'p-navigation',		mw.util.wikiGetlink( 'Special:BlankPage' ) + '?action=mostedited',		'Most edited', // FIXME localize		'n-mostedited',		'Shows the most edited pages', // FIXME localize		null, // access key		'#n-recentchanges' ); }

$( initSidebar );

if ( mw.config.get( 'wgCanonicalSpecialPageName' ) === 'Recentchanges' ) { $( initRecentchanges ); }

if ( mw.config.get( 'wgCanonicalSpecialPageName' ) === 'Blankpage' &&	mw.util.getParamValue( 'action' ) === 'mostedited' ) {	/* mw.loader.using( 'jquery.spinner',		function { */			$( initBlankpage );		//} ); }

} )( jQuery ); //