User:Schnark/mostEdited.js

/** * User:Schnark/mostEdited.js * * User script to show the pages which were edited most in the last time * For a documentation see User:Schnark/mostEdited * For the history behind this script see User:Schnark/October 2011 Coding Challenge * * @author Michael Müller (User:Schnark) * @license GPL [//www.mediawiki.org/w/COPYING] (+ 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 ( $ ) {

// since there is no good way to get messages in a user script (yet), jsut put them here

/** * @var {object} fallback language * Please note the following things: * 1. en is fallback in any way * 2. fallback languages will be resolved only one step * 3. every fallback *must* have an entry in the messages structure * 4. this sucks */ var languageFallback = { 'bar': 'de', 'de-at': 'de', 'de-ch': 'de', 'de-formal': 'de', 'dsb': 'de', 'frr': 'de', 'gsw': 'de', 'hsb': 'de', 'ksh': 'de', 'lb': 'de', 'nds': 'de', 'pdc': 'de', 'pdt': 'de', 'pfl': 'de', 'sli': 'de', 'stq': 'de', 'vmf': 'de' };

/** * @var {object} messages for a language */

var messages = { en: { // from core 'minutes': 'NaN $1 minutess', 'hours': 'NaN $1 hourss', 'days': 'NaN $1 dayss', 'allpagessubmit': 'Go', 'namespace': 'Namespace:', 'invert': 'Invert selection', 'tooltip-invert': 'Check this box to hide changes within the selected namespace (and the associated namespace if checked)', 'namespace_association': 'Associated namespace', 'tooltip-namespace_association': 'Check this box to also include the talk or subject namespace associated with the selected namespace', 'blanknamespace': '(Main)', 'namespacesall': 'all', 'rc-change-size': '$1', 'pagetitle': '$1 - ',

// own messages 'mostedited-legend': 'Most edited pages options', // legend for options on BlankPage with action=mostedited 'mostedited': 'Most edited pages', // link in sidebar and title of the page 'tooltip-n-mostedited': 'Shows the most edited pages', // tooltip for link in sidebar 'mostedited-submit': 'Show most edited pages', // submit button in RecentChanges 'mostedited-time': 'Time:', // label for time selection 'mostedited-edits': 'NaN $1 editss ($2 minor NaN editss)', // $1 - total number of edits to the page/section, $2 - number of minor edits 'mostedited-users': 'NaN $1 userss ($2 anonymous NaN userss)', // $1 - total number of different editors, $2 - number of anonymous editors 'mostedited-size': 'Size change: $1', // $1 - formatted number 'mostedited-no-pages': 'There are no pages with $1 or more edits in the selected period.', // $1 - number of edits a page must have at least to get shown 'mostedited-increasing': 'The number of edits seems to be increasing.', 'mostedited-unchanging': 'The number of edits seems not to change.', 'mostedited-decreasing': 'The number of edits seems to be decreasing.', 'mostedited-changed-period': '(changed to: $1)', 'mostedited-changed-period-tooltip': 'The period had to be shortened because there are too many edits.' },

de: { 'minutes': 'NaN $1 Minutens', 'hours': 'NaN $1 Stundens', 'days': 'NaN $1 Tages', 'allpagessubmit': 'Anwenden', 'namespace': 'Namensraum:', 'invert': 'Auswahl umkehren', 'tooltip-invert': 'Dieses Auswahlfeld anklicken, um Änderungen im gewählten Namensraum und, sofern ausgewählt, dem entsprechenden zugehörigen Namensraum auszublenden', 'namespace_association': 'Zugeordneter Namensraum', 'tooltip-namespace_association': 'Dieses Auswahlfeld anklicken, um den deiner Auswahl zugehörigen Diskussionsnamensraum, oder im umgekehrten Fall, den zugehörigen Namensraum, mit einzubeziehen', 'blanknamespace': '(Seiten)', 'namespacesall': 'alle', 'rc-change-size': '$1 NaN Bytess', 'pagetitle': '$1 – ', 'mostedited-legend': 'Anzeigeptionen', 'mostedited': 'Meiste Änderungen', 'tooltip-n-mostedited': 'Zeigt die Seiten mit den meisten Änderungen an', 'mostedited-submit': 'Zeige Seiten mit meisten Änderungen', 'mostedited-time': 'Zeit:', 'mostedited-edits': 'NaN $1 Bearbeitungens ($2 kleinere NaN Bearbeitungens)', 'mostedited-users': '$1 Benutzer (NaN $2 anonymes)', 'mostedited-size': 'Größenänderung: $1', 'mostedited-no-pages': 'Keine Seite wurde im ausgewählten Zeitraum $1 Mal oder häufiger bearbeitet.', 'mostedited-increasing': 'Die Anzahl der Bearbeitungen scheint zuzunehmen.', 'mostedited-unchanging': 'Die Anzahl der Bearbeitungen scheint gleich zu bleiben.', 'mostedited-decreasing': 'Die Anzahl der Bearbeitungen scheint abzunehmen.', 'mostedited-changed-period': '(geändert in: $1)', 'mostedited-changed-period-tooltip': 'Die Zeit musste gekürzt werden, da zu viele Bearbeitungen stattfanden.' },

'de-ch': { 'mostedited-size': 'Grössenänderung: $1' }

};

// set messages for the user's language // @TODO once there is a better way to do this (new Gadget extension etc.) switch to that way mw.messages.set( messages.en ); if ( mw.config.get( 'wgUserLanguage' ) in languageFallback ) { mw.messages.set( messages[languageFallback[mw.config.get( 'wgUserLanguage' )]] ); } if ( mw.config.get( 'wgUserLanguage' ) in messages ) { mw.messages.set( messages[mw.config.get( 'wgUserLanguage' )] ); } // allow users to bind to this event to set messages for their language $( document ).trigger( 'mostedited-setmessages' );

/** * @var {object} pagesList contains information for every edited page in the form * 'Pagename': { *	oldsize: 1234, // size of oldest version *	newsize: 4321, // size of newest version *	edits: 123, // number of edits *	minor: 23, // number of minor edits *	users: ['A', 'B'], // all editors *	anons: 5, // number of anonymous editors *	time: 987654321, // sum of all timestamps (in seconds before now) *	sections: { // data for each section *		'Section A': { *			edits: 12, *			minor: 2, *			users: ['A'], *			anons: 2, *			time: 87654321 *		} *	} * } */

var pagesList = {};

/** * @var {number} current time, time of first edit (milliseconds since 1970-01-01) */ var currTime = 0, firstTime = 0;

// helper and format functions

/** * gets a message (as an HTML fragment), overridden with a wikitext parser when needed * @param msgName {string} * @param parameters {string} * @return {string} */ function msg ( /* msgName, parameters */ ) { return mw.msg.apply( mw, arguments ); }

/** * converts a timestamp (YYYY-MM-DDTHH:MM:SSZ) into milliseconds since 1970-01-01 * @param timestamp {string} * @return {number} */ function getTime ( timestamp ) { return ( new Date( timestamp.slice( 0, 4 ), timestamp.slice( 5, 7 ) - 1, timestamp.slice( 8, 10 ), timestamp.slice( 11, 13 ), timestamp.slice( 14, 16 ), timestamp.slice( 17, 19 ) ) ).getTime; }

/** * 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' ); }

/** * formats a size change (see ChangesList.php for the original) * @TODO commafy * @param diff {number} difference between old and new size * @return {string} formatted HTML */ function showCharacterDifference ( diff ) { var cssClass, sign = ''; if ( diff < 0 ) { cssClass = 'mw-plusminus-neg'; } else if ( diff > 0 ) { sign = '+'; cssClass = 'mw-plusminus-pos'; } else { cssClass = 'mw-plusminus-null'; }	return mw.html.element( 'span',		{'class': cssClass, dir: 'ltr'},		msg( 'rc-change-size', sign + diff ) ); }

/** * determins whether the edits are increasing or decreasing * @param data {object} object with entries edits and time * @return {string} arrow symbol in a with class and tooltip */ function getTrend ( data ) { var	avgTime = data.time / data.edits, ratio = avgTime / (firstTime - currTime), cssClass, tooltipMsg, arrow; if ( ratio < 0.4 ) { cssClass = 'mw-plusminus-pos'; tooltipMsg = 'mostedited-increasing'; arrow = $( 'body' ).is( '.rtl' ) ? '↖' : '↗';	} else if ( ratio < 0.6 ) { cssClass = 'mw-plusminus-null'; tooltipMsg = 'mostedited-unchanging'; arrow = $( 'body' ).is( '.rtl' ) ? '←' : '→';	} else { cssClass = 'mw-plusminus-neg'; tooltipMsg = 'mostedited-decreasing'; arrow = $( 'body' ).is( '.rtl' ) ? '↙' : '↘';	}	return mw.html.element( 'span', {'class': cssClass, title: msg( tooltipMsg )}, arrow ); }

/** * formats a period of time * @param hours {number} hours * @return {string} formatted period */ function formatPeriod ( hours ) { var dayString = , hourString = , minuteString = ''; if ( hours >= 24 ) { var days = Math.floor( hours / 24 ); hours -= days * 24; hours = Math.round( hours ); if ( hours === 24 ) { days += 1; hours = 0; }		dayString = msg( 'days', days ); if ( hours > 0 ) { hourString = ' ' + msg( 'hours', hours ); }		return dayString + hourString; } else { var	wholeHours = Math.floor( hours ), minutes = Math.round( 60 * ( hours - wholeHours ) ); if ( minutes === 60 ) { wholeHours += 1; minutes = 0; }		if ( wholeHours > 0 ) { hourString = msg( 'hours', wholeHours ); }		if ( wholeHours === 0 || minutes > 0 ) { minuteString = msg( 'minutes', minutes ); }		if ( hourString !==  && minuteString !==  ) { hourString += ' '; }		return hourString + minuteString; } }

// main

/** * 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 end {string} end 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, called with one parameter: *         true if all edits until end were retrieved, false if aborted earlier * @param start {string} start time (timestamp), leave empty for first call */ function getAPIRecentChanges ( end, namespaces, maxcalls, callback, start ) { var data = { action: 'query', rawcontinue: '', list: 'recentchanges', rcend: end, rclimit: 'max', rcprop: 'user|comment|title|sizes|flags|timestamp', rctype: 'edit|new', format: 'json' };	if ( start ) { data.rcstart = start; } else { data.meta = 'siteinfo'; data.siprop = 'general'; }	if ( namespaces ) { data.rcnamespace = namespaces; }	$.getJSON( mw.util.wikiScript( 'api' ), data, function ( json ) {		if ( json && json.query && json.query.general ) {			currTime = getTime( json.query.general.time );		}		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,						time: 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, time: 0 };					}				}				var time = getTime( edit.timestamp ); firstTime = time; 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++; }					}				}				pagesList[edit.title].time += (time - currTime); if ( section ) { pagesList[edit.title].sections[section].time += (time - currTime); }			}		}		if ( json && json['query-continue'] && json['query-continue'].recentchanges ) { if ( maxcalls > 1 ) { getAPIRecentChanges( end, namespaces, maxcalls - 1, callback, json['query-continue'].recentchanges.rcstart ); } else { callback( false ); }		} else { callback( true ); }	} ); }

/** * 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 * @param edits {number} number of edits needed at least to output a page/section * @return {array} list of the pages/sections with the most edits (decreasing order) */ function getMostEdited ( data, count, edits ) { var items = []; for ( var item in data ) { items.push( item ); }	items.sort( function ( a, b ) {		return data[b].edits - data[a].edits;	} ); var output = items.slice( 0, count ); while ( output.length > 0 && data[output[output.length - 1]].edits < edits ) { output.pop; }	return output; }

// functions generating HTML

/** * get HTML for one entry * @param page {string} name of the page to get HTML for * @param count {number} number of sections to show * @param edits {number} number of edits a section must have at least * @return {string} HTML */ function generatePageHTML ( page, count, edits ) { var	data = pagesList[page], html = mw.html.element( 'h2', {}, new mw.html.Raw( mw.html.element( 'a', {href: mw.util.getUrl( page ), title: page}, page ) + getTrend( data ) ) ); html += mw.html.element( 'ul', {}, new mw.html.Raw( mw.html.element( 'li', {}, new mw.html.Raw( msg( 'mostedited-edits', data.edits, data.minor ) ) ) + mw.html.element( 'li', {}, new mw.html.Raw( msg( 'mostedited-users', data.users.length, data.anons ) ) ) + mw.html.element( 'li', {}, new mw.html.Raw( //FIXME: showCharacterDifference returns an HTML fragment which is escaped by the parser from mediawiki.jqueryMsg.js in MediaWiki 1.19+, //but not in 1.18. Since the parser from mediawiki.js never escapes HTML (at least not in 1.19 or earlier) use mw.msg here instead of msg. mw.msg( 'mostedited-size', showCharacterDifference( data.newsize - data.oldsize ) ) ) ) ) ); var sections = getMostEdited( data.sections, count, edits ); for ( var i = 0; i < sections.length; i++ ) { var section = sections[i], sectionData = data.sections[section]; html += mw.html.element( 'h3', {}, new mw.html.Raw( mw.html.element( 'a',				{href: mw.util.getUrl( page ) + '#' + encodeSectionLink( section )},				section ) + getTrend( sectionData ) ) ); html += mw.html.element( 'ul', {}, new mw.html.Raw( mw.html.element( 'li', {}, new mw.html.Raw( msg( 'mostedited-edits', sectionData.edits, sectionData.minor ) ) ) + mw.html.element( 'li', {}, new mw.html.Raw( msg( 'mostedited-users', sectionData.users.length, sectionData.anons ) ) ) ) ); }	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 * @param editPages {number} number of edits a page must have at least * @param editSections {number} number of edits a section must have at least * @return {string} HTML */ function generateHTML ( countPages, countSections, editPages, editSections ) { var	pages = getMostEdited ( pagesList, countPages, editPages ), html = ''; if ( pages.length === 0 ) { html += msg( 'mostedited-no-pages', editPages ); } else { for ( var i = 0; i < pages.length; i++ ) { html += generatePageHTML( pages[i], countSections, editSections ); }	}	return html; }

/** * get HTML for header * @return {string} HTML */ function generateHeaderHTML { $( '#firstHeading' ).text( msg( 'mostedited' ) ); var	i, html = '', legend = '', labelTime = '', selectTime = '', labelNamespaces = '', selectNamespaces = '', invert = '', associated = '', submit = '', hours, times = [], optionsTime = [], optionsNamespaces = [], formattedNamespaces = mw.config.get( 'wgFormattedNamespaces' ); legend = mw.html.element( 'legend', {}, msg( 'mostedited-legend' ) ); labelTime = mw.html.element( 'label', {'for': 'time'}, msg( 'mostedited-time' ) );

hours = parseFloat( mw.util.getParamValue( 'hours' ) || '0', 10 ); if ( hours <= 0 ) { hours = 1; }	times = [0.25, 0.5, 1, 2, 24]; if ( $.inArray( hours, times ) === -1 ) { times.push( hours ); times.sort( function ( a, b ) {			return a - b;		} ); }	for ( i = 0; i < times.length; i++ ) { optionsTime.push( mw.html.element( 'option', {value: times[i], selected: times[i] === hours}, formatPeriod( times[i] ) ) ); }	selectTime = mw.html.element( 'select',		{id: 'time', name: 'time', 'class': 'timeselector'},		new mw.html.Raw( optionsTime.join( '' ) ) ); labelNamespaces = mw.html.element( 'label', {'for': 'namespace'}, msg( 'namespace' ) ); optionsNamespaces.push( mw.html.element( 'option', {value: ''}, msg( 'namespacesall' ) ) ); for ( i in formattedNamespaces ) { if ( i < 0 ) { continue; }		var namespace = formattedNamespaces[i]; if ( namespace === '' ) { namespace = msg( 'blanknamespace' ); }		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', type: 'checkbox', title: msg( 'tooltip-invert' ),			checked: mw.util.getParamValue( 'invert' ) === '1'} ) + ' ' +		mw.html.element( 'label', {'for': 'nsinvert', title: msg( 'tooltip-invert' )}, msg( 'invert' ) ); associated = mw.html.element( 'input',		{name: 'associated', value: 1, id: 'nsassociated', type: 'checkbox', title: msg( 'tooltip-namespace_association' ),			checked: mw.util.getParamValue( 'associated' ) === '1'} ) + ' ' +		mw.html.element( 'label',           {'for': 'nsassociated', title: msg( 'tooltip-namespace_association' )},            msg( 'namespace_association' ) ); submit = mw.html.element( 'input', {type: 'button', id: 'submitButton', value: msg( 'allpagessubmit' )} ); html += ' ' + // structure copied from HTML of Special:RecentChanges legend + ' ';	html += mw.html.element( 'div', {id: 'mostEditedContainer'} ); return html; }

// functions to interact with user

/** * reads user input from form elements and URL, sets default * @return {object} object with all parameters */ function readUserInput { 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 * 3600000 ) ), end = String( ago.getUTCFullYear ) + '-' + String( ago.getUTCMonth + 101 ).substr( 1 ) + '-' + String( ago.getUTCDate + 100 ).substr( 1 ) + 'T' + String( ago.getUTCHours + 100 ).substr ( 1 ) + ':' + String( ago.getUTCMinutes + 100 ).substr( 1 ) + ':' + String( ago.getUTCSeconds + 100 ).substr( 1 ) + 'Z'; 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( '|' ); }	}	return { namespaces: namespaces, end: end, maxCalls: mw.util.getParamValue( 'max-calls' ) || 5, limit: mw.util.getParamValue( 'limit' ) || 10, sectionLimit: mw.util.getParamValue( 'section-limit' ) || 3, edits: mw.util.getParamValue( 'edits' ) || 2, sectionEdits: mw.util.getParamValue( 'section-edits' ) || 2 }; }

/** * called when user clicks submit button */ function submitQuery { var params = readUserInput; $( '#submitButton' ).prop( 'disabled', true ); // $( '#mostEditedContainer' ).empty.injectSpinner( 'mostedited' ); This needs MW 1.19, so use old wikibits.js	window.injectSpinner( $( '#mostEditedContainer' ).empty.get( 0 ), 'mostedited' ); pagesList = {}; // empty getAPIRecentChanges( params.end, params.namespaces, params.maxCalls, function ( done ) {		var $realTime = $( '#mostedited-real-time' );		if ( done ) {			firstTime = getTime( params.end );			$realTime.text( '' );		} else {			$realTime.html( mw.html.element( 'span',				{title: msg( 'mostedited-changed-period-tooltip' )},				msg( 'mostedited-changed-period', formatPeriod( ( currTime - firstTime ) / 3600000 ) ) ) );		}		$( '#mostEditedContainer' ).html( generateHTML( params.limit, params.sectionLimit, params.edits, params.sectionEdits ) );		// $.removeSpinner( 'mostedited' ); This needs MW 1.19, so use old wikibits.js		window.removeSpinner( 'mostedited' );		$( '#submitButton' ).prop( 'disabled', false );	} ); }

// initialise

/** * initialises the interface on Special:Blankpage */ function initBlankpage { document.title = msg( 'pagetitle', msg( 'mostedited' ) ); var $content = mw.util.$content.children( 'p' ); // don't clear away subtitle, newtalk and jumpto if ( $content.length !== 1 ) { $content = mw.util.$content; }	$content.html( generateHeaderHTML ); mw.loader.load( 'mediawiki.special.recentchanges' ); // enables/disables checkboxes $( '#submitButton' ).click( submitQuery ).click; }

/** * initialises the interface on Special:RecentChanges */ function initRecentchanges { var $button = $( mw.html.element( 'input', {type: 'button', value: msg( 'mostedited-submit' )} ) ) .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.getUrl( '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 }

/** * initialises the sidebar everywhere */ function initSidebar { var portlet = $( '#n-recentchanges' ).parents( '.portlet, .portal' ).attr( 'id' ) || 'p-navigation'; mw.util.addPortletLink( portlet,		mw.util.getUrl( 'Special:BlankPage' ) + '?action=mostedited',		msg( 'mostedited' ),		'n-mostedited',		msg( 'tooltip-n-mostedited' ),		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( [ 'mediawiki.jqueryMsg' /*, 'jquery.spinner' this needs MW 1.19 */ ],		function { /** * replace msg with a version that handles  and NaN undefineds * this is only needed here, not in the other cases, where the original mw.msg is just fine * @TODO remove this once MediaWiki handles this itself */			msg = mw.jqueryMsg.getMessageFunction( { magic: { 'SITENAME': mw.config.get( 'wgSiteName' ) }			} );			$( initBlankpage );		} ); }

} )( jQuery ); //