Benutzer:Schnark/js/mostEdited.js
aus Wikipedia, der freien Enzyklopädie
< Benutzer:Schnark | js
Hinweis: Leere nach dem Veröffentlichen den Browser-Cache, um die Änderungen sehen zu können.
- Firefox/Safari: Umschalttaste drücken und gleichzeitig Aktualisieren anklicken oder entweder Strg+F5 oder Strg+R (⌘+R auf dem Mac) drücken
- Google Chrome: Umschalttaste+Strg+R (⌘+Umschalttaste+R auf dem Mac) drücken
- Internet Explorer/Edge: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
- Opera: Strg+F5
/**
* Dokumentation unter [[Benutzer:Schnark/js/mostEdited]]
* Original unter [[mw:User:Schnark/mostEdited]]
* <nowiki>
*/
/*global mediaWiki*/
//jscs:disable disallowSpacesInsideParentheses, disallowSpacesInsideArrayBrackets
//jscs:disable disallowSpacesInsideObjectBrackets, disallowSpaceAfterLineComment
( function ( $, mw ) {
//virtual indent
'use strict';
var messages, pagesList, currTime, firstTime;
// since there is no good way to get messages in a user script (yet), jsut put them here
/**
* @var {object} messages for a language
*/
//jscs:disable maximumLineLength
messages = {
en: {
// from core
'minutes': '{{PLURAL:$1|$1 minute|$1 minutes}}',
'hours': '{{PLURAL:$1|$1 hour|$1 hours}}',
'days': '{{PLURAL:$1|$1 day|$1 days}}',
'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',
'rc-change-size-new': '$1 {{PLURAL:$1|byte|bytes}} after change',
'pagetitle': '$1 - {{SITENAME}}',
// 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': '{{PLURAL:$1|$1 edit|$1 edits}} ($2 minor {{PLURAL:$2|edit|edits}})', // $1 - total number of edits to the page/section, $2 - number of minor edits
'mostedited-users': '{{PLURAL:$1|$1 user|$1 users}} ($2 anonymous {{PLURAL:$2|user|users}})', // $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.',
'mostedited-show': 'more options', //toggle
'mostedited-hide': 'less options',
'mostedited-option-sort': 'Sort by:', //label for sort
'mostedited-option-sort-edits': 'Number of edits',
'mostedited-option-sort-users': 'Number of users',
'mostedited-option-sort-size': 'Size of changes',
'mostedited-option-max-calls': 'Maximal number of API-calls:', //label for max-calls
'mostedited-option-limit': 'Maximal number of pages to show:', //label for limit
'mostedited-option-section-limit': 'Maximal number of sections to show for each page:', //label for section-limit
'mostedited-option-edits': 'Minimal number of edits to show a page:', //label for edits
'mostedited-option-section-edits': 'Minimal number of edits to show a section:', //label for section-edits
'mostedited-reload': 'Reload', //button to reload with current parameters
'mostedited-tooltip-reload': 'Reload this page to get a bookmarkable URL.' //tooltip
},
de: {
'minutes': '{{PLURAL:$1|$1 Minute|$1 Minuten}}',
'hours': '{{PLURAL:$1|$1 Stunde|$1 Stunden}}',
'days': '{{PLURAL:$1|$1 Tag|$1 Tage}}',
'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': 'Zugehöriger 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 {{PLURAL:$1|Byte|Bytes}}',
'rc-change-size-new': '$1 {{PLURAL:$1|Byte|Byte}} nach der Änderung',
'pagetitle': '$1 – {{SITENAME}}',
'mostedited-legend': 'Anzeigeoptionen',
'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': '{{PLURAL:$1|$1 Bearbeitung|$1 Bearbeitungen}} ($2 kleinere {{PLURAL:$2|Bearbeitung|Bearbeitungen}})',
'mostedited-users': '$1 Benutzer ({{PLURAL:$2|$2 anonymer|$2 anonyme}})',
'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.',
'mostedited-show': 'mehr Optionen',
'mostedited-hide': 'weniger Optionen',
'mostedited-option-sort': 'Sortieren nach:',
'mostedited-option-sort-edits': 'Anzahl der Bearbeitungen',
'mostedited-option-sort-users': 'Anzahl der Benutzer',
'mostedited-option-sort-size': 'Größe der Änderungen',
'mostedited-option-max-calls': 'Maximale Zahl von API-Aufrufen:',
'mostedited-option-limit': 'Maximale Zahl angezeigter Seiten:',
'mostedited-option-section-limit': 'Maximale Zahl angezeigter Abschnitte pro Seite:',
'mostedited-option-edits': 'Minimale Zahl an Bearbeitungen pro Seite:',
'mostedited-option-section-edits': 'Minimale Zahl an Bearbeitungen pro Abschnitt:',
'mostedited-reload': 'Neu laden',
'mostedited-tooltip-reload': 'Lade die Seite erneut, um ein Lesezeichen auf die Seite setzen zu können.'
},
'de-ch': {
'mostedited-size': 'Grössenänderung: $1',
'mostedited-option-sort-size': 'Grösse der Änderungen'
},
'de-formal': {
'mostedited-tooltip-reload': 'Laden Sie die Seite erneut, um ein Lesezeichen auf die Seite setzen zu können.'
}
};
//jscs:enable maximumLineLength
/**
* set messages for the user's language
* @param l10n {object} Object with messages for each language
* @param keep {array} Messages that shouldn't be overridden if present
* (because they are from core and could be loaded by some other script)
*/
function initL10N ( l10n, keep ) {
var i, chain = mw.language.getFallbackLanguageChain();
keep = $.grep( mw.messages.get( keep ), function ( val ) {
return val !== null;
} );
for ( i = chain.length - 1; i >= 0; i-- ) {
if ( chain[ i ] in l10n ) {
mw.messages.set( l10n[ chain[ i ] ] );
}
}
mw.messages.set( keep );
}
/**
* @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
* }
* }
* }
*/
pagesList = {};
/**
* @var {number} current time, time of first edit (milliseconds since 1970-01-01)
*/
currTime = 0;
firstTime = 0;
// helper and format functions
/**
* pad a number with a leading 0 if neccessary
* @param n {number} number to pad
* @return {string}
*/
function pad ( n ) {
return n < 10 ? '0' + String( n ) : String( n );
}
/**
* 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();
}
/**
* validates an input to ensure it contains an integer
* @param $input {jQuery} input to read from
* @param def {number} default value to use instead
* @return {number} validated number, this number is also put in the input
*/
function validateNumber ( $input, def ) {
var val = $input.val() || '';
val = val.trim();
if ( /^\d+$/.test( val ) ) {
val = Number( val );
} else {
val = 0;
}
if ( val === 0 ) {
val = def;
}
$input.val( String( val ) );
return val;
}
/**
* formats a size change (see ChangesList.php for the original)
* @param diff {number} difference between old and new size
* @param newsize {number} new size
* @return {string} formatted HTML
*/
function showCharacterDifference ( diff, newsize ) {
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', title: mw.msg( 'rc-change-size-new', mw.language.convertNumber( newsize ) ) },
sign + mw.msg( 'rc-change-size', mw.language.convertNumber( diff ) ) );
}
/**
* determins whether the edits are increasing or decreasing
* @param data {object} object with entries edits and time
* @return {string} arrow symbol in a <span> 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: mw.msg( tooltipMsg ) }, arrow );
}
/**
* formats a period of time
* @param hours {number} hours
* @return {string} formatted period
*/
function formatPeriod ( hours ) {
var dayString = '', hourString = '', minuteString = '',
days, wholeHours, minutes;
if ( hours >= 24 ) {
days = Math.floor( hours / 24 );
hours -= days * 24;
hours = Math.round( hours );
if ( hours === 24 ) {
days += 1;
hours = 0;
}
dayString = mw.msg( 'days', days );
if ( hours > 0 ) {
hourString = ' ' + mw.msg( 'hours', hours );
}
return dayString + hourString;
} else {
wholeHours = Math.floor( hours );
minutes = Math.round( 60 * ( hours - wholeHours ) );
if ( minutes === 60 ) {
wholeHours += 1;
minutes = 0;
}
if ( wholeHours > 0 ) {
hourString = mw.msg( 'hours', wholeHours );
}
if ( wholeHours === 0 || minutes > 0 ) {
minuteString = mw.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 cont {object} continue parameter and value, leave empty for first call
*/
function getAPIRecentChanges ( end, namespaces, maxcalls, callback, cont ) {
var data = {
action: 'query',
list: 'recentchanges',
rcend: end,
rclimit: 'max',
rcprop: 'user|comment|title|sizes|flags|timestamp',
rctype: 'edit|new',
format: 'json',
formatversion: 2
};
if ( cont ) {
$.extend( data, cont );
} else {
data[ 'continue' ] = '';
data.curtimestamp = true;
}
if ( namespaces ) {
data.rcnamespace = namespaces;
}
$.getJSON( mw.util.wikiScript( 'api' ), data ).then( function ( json ) {
var rc, i, edit, time, section;
if ( json && json.curtimestamp ) {
currTime = getTime( json.curtimestamp );
}
if ( json && json.query && json.query.recentchanges ) {
rc = json.query.recentchanges;
for ( i = 0; i < rc.length; i++ ) {
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: {}
};
}
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
};
}
}
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++;
}
}
// update oldlen for every edit, only the earliest (= last) is interesting
pagesList[ edit.title ].oldsize = edit.oldlen;
if ( pagesList[ edit.title ].users.indexOf( edit.user ) === -1 ) { // store if new user
pagesList[ edit.title ].users.push( edit.user );
if ( edit.anon ) {
pagesList[ edit.title ].anons++;
}
}
if ( section ) {
if ( pagesList[ edit.title ].sections[ section ].users.indexOf( edit.user ) === -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[ 'continue' ] ) {
if ( maxcalls > 1 ) {
getAPIRecentChanges( end, namespaces, maxcalls - 1, callback, json[ 'continue' ] );
} 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
* @param sortBy {string} criterion to sort pages by
* @return {array} list of the pages/sections with the most edits (decreasing order)
*/
function getMostEdited ( data, count, edits, sortBy ) {
var items = [], item, output, sortFunction;
for ( item in data ) {
if ( data.hasOwnProperty( item ) ) {
items.push( item );
}
}
//jscs:disable requireFunctionDeclarations
switch ( sortBy ) {
case 'edits':
sortFunction = function ( a, b ) {
return data[ b ].edits - data[ a ].edits;
};
break;
case 'users':
sortFunction = function ( a, b ) {
return data[ b ].users.length - data[ a ].users.length;
};
break;
case 'size':
sortFunction = function ( a, b ) {
return Math.abs( data[ b ].oldsize - data[ b ].newsize ) - Math.abs( data[ a ].oldsize - data[ a ].newsize );
};
}
//jscs:enable requireFunctionDeclarations
items.sort( sortFunction );
output = items.slice( 0, count );
while ( sortBy === 'edits' && output.length > 0 && data[ output[ output.length - 1 ] ].edits < edits ) {
output.pop();
}
return output;
}
// functions generating HTML
//for output
/**
* generate list with information about edits for a page/section
* @param edits {number} number of edits
* @param minor {number} number of minor edits
* @param users {array} users that edited the page/section
* @param anons {number} number of anonymous users
* @param oldsize {number} old size (or undefined for sections)
* @param newsize {number} new size (or undefined for sections)
* @param href {string} for pages: href of action=history
* @return {string} HTML <ul>
*/
function generateListHTML ( edits, minor, users, anons, oldsize, newsize, href ) {
var html = '', editsHTML = mw.msg( 'mostedited-edits', edits, minor );
if ( href ) {
editsHTML = mw.html.element( 'a', { href: href }, new mw.html.Raw( editsHTML ) );
}
html += mw.html.element( 'li', {}, new mw.html.Raw( editsHTML ) );
html += mw.html.element( 'li', { title: users.join( ', ' ) },
new mw.html.Raw( mw.msg( 'mostedited-users', users.length, anons ) ) );
if ( oldsize !== undefined && newsize !== undefined ) {
html += mw.html.element( 'li', {}, new mw.html.Raw(
mw.msg( 'mostedited-size', showCharacterDifference( newsize - oldsize, newsize ) ) ) );
}
return mw.html.element( 'ul', {}, new mw.html.Raw( 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, sections, i, section, sectionData, history;
history = mw.util.getUrl( page, { action: 'history' } );
html = mw.html.element( 'h2', {}, new mw.html.Raw(
mw.html.element( 'a', { href: mw.util.getUrl( page ), title: page }, page ) +
getTrend( data ) ) );
html += generateListHTML( data.edits, data.minor, data.users, data.anons, data.oldsize, data.newsize, history );
sections = getMostEdited( data.sections, count, edits, 'edits' );
for ( i = 0; i < sections.length; i++ ) {
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 + '#' + section ) },
section ) +
getTrend( sectionData ) ) );
html += generateListHTML( sectionData.edits, sectionData.minor, sectionData.users, 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
* @param sortBy {string} criterion to sort pages by
* @return {string} HTML
*/
function generateHTML ( countPages, countSections, editPages, editSections, sortBy ) {
var pages = getMostEdited( pagesList, countPages, editPages, sortBy ),
html = '',
i;
if ( pages.length === 0 ) {
html += mw.msg( 'mostedited-no-pages', editPages );
} else {
for ( i = 0; i < pages.length; i++ ) {
html += generatePageHTML( pages[ i ], countSections, editSections );
}
}
return html;
}
//for input
/**
* get HTML for the time select
* @return {string} HTML
*/
function generateTimeSelect () {
var i, hours, times, optionsTime = [], labelTime, selectTime;
labelTime = mw.html.element( 'label', { 'for': 'time' }, mw.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 ( times.indexOf( hours ) === -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( '' ) ) );
return '<tr><td class="mw-label">' +
labelTime +
'</td><td class="mw-input">' +
selectTime +
' <span id="mostedited-real-time"></span>' +
'</td></tr>';
}
/**
* get HTML for the namespace select
* @return {string} HTML
*/
function generateNamespaceSelect () {
var i, formattedNamespaces, namespace, optionsNamespaces = [], labelNamespaces, selectNamespaces, invert, associated, cls;
formattedNamespaces = mw.config.get( 'wgFormattedNamespaces' );
labelNamespaces = mw.html.element( 'label', { 'for': 'namespace' }, mw.msg( 'namespace' ) );
optionsNamespaces.push( mw.html.element( 'option', { value: '' }, mw.msg( 'namespacesall' ) ) );
for ( i in formattedNamespaces ) {
if ( formattedNamespaces.hasOwnProperty( i ) && i >= 0 ) {
namespace = formattedNamespaces[ i ];
if ( namespace === '' ) {
namespace = mw.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: mw.msg( 'tooltip-invert' ),
checked: mw.util.getParamValue( 'invert' ) === '1' } ) +
' ' +
mw.html.element( 'label', { 'for': 'nsinvert', title: mw.msg( 'tooltip-invert' ) }, mw.msg( 'invert' ) );
associated = mw.html.element( 'input',
{ name: 'associated', value: 1, id: 'nsassociated', type: 'checkbox',
title: mw.msg( 'tooltip-namespace_association' ),
checked: mw.util.getParamValue( 'associated' ) === '1' } ) +
' ' +
mw.html.element( 'label',
{ 'for': 'nsassociated', title: mw.msg( 'tooltip-namespace_association' ) },
mw.msg( 'namespace_association' ) );
cls = [ 'mw-input-with-label' ];
if ( !mw.util.getParamValue( 'namespace' ) ) {
cls.push( 'mw-input-hidden' );
}
cls = cls.join( ' ' );
invert = mw.html.element( 'span', { 'class': cls }, new mw.html.Raw( invert ) );
associated = mw.html.element( 'span', { 'class': cls }, new mw.html.Raw( associated ) );
return '<tr><td class="mw-label">' +
labelNamespaces +
'</td><td class="mw-input">' +
selectNamespaces +
' ' +
invert +
' ' +
associated +
'</td></tr>';
}
/**
* get HTML for the sort select
* @param html {string} HTML to insert after the select
* @return {string} HTML
*/
function generateSortSelect ( html ) {
var sortParam, labelSort, selectSort;
labelSort = mw.html.element( 'label', { 'for': 'mostedited-sort' }, mw.msg( 'mostedited-option-sort' ) );
sortParam = mw.util.getParamValue( 'sort' ) || 'edits';
selectSort = mw.html.element( 'select', { id: 'mostedited-sort' }, new mw.html.Raw(
mw.html.element( 'option', { value: 'edits', selected: sortParam === 'edits' },
mw.msg( 'mostedited-option-sort-edits' ) ) +
mw.html.element( 'option', { value: 'users', selected: sortParam === 'users' },
mw.msg( 'mostedited-option-sort-users' ) ) +
mw.html.element( 'option', { value: 'size', selected: sortParam === 'size' },
mw.msg( 'mostedited-option-sort-size' ) )
) );
return '<tr><td class="mw-label">' +
labelSort +
'</td><td class="mw-input">' +
selectSort +
html +
'</td></tr>';
}
/**
* generate HTML for an input (used for advanced options)
* @param id {string} ID of input
* @param labelMsg {string} message for label
* @param URLParam {string} name of URL parameter
* @param def {number} default value
* @return {string} HTML
*/
function generateInput ( id, labelMsg, URLParam, def ) {
return '<tr><td class="mw-label">' +
mw.html.element( 'label', { 'for': id }, mw.msg( labelMsg ) ) +
'</td><td class="mw-input">' +
mw.html.element( 'input', { type: 'text', id: id, 'class': 'noime', size: 3,
value: mw.util.getParamValue( URLParam ) || def } ) +
'</td></tr>';
}
/**
* get HTML for header
* @return {string} HTML
*/
function generateHeaderHTML () {
$( '#firstHeading' ).text( mw.msg( 'mostedited' ) );
var html, legend, submit;
legend = mw.html.element( 'legend', {}, mw.msg( 'mostedited-legend' ) );
submit = mw.html.element( 'input', { type: 'button', id: 'submitButton', value: mw.msg( 'allpagessubmit' ) } );
html = '<fieldset class="rcoptions">' + // structure copied from HTML of Special:RecentChanges
legend +
'<table class="mw-recentchanges-table"><tbody>' +
generateTimeSelect() +
generateNamespaceSelect() +
generateSortSelect( ' ' + submit ) +
'</tbody></table>' +
mw.html.element( 'div', { id: 'advanced-options', 'class': 'mw-collapsed',
'data-collapsetext': mw.msg( 'mostedited-hide' ), 'data-expandtext': mw.msg( 'mostedited-show' ) },
new mw.html.Raw(
'<table><tbody>' +
generateInput( 'mostedited-input-max-calls', 'mostedited-option-max-calls', 'max-calls', 5 ) +
generateInput( 'mostedited-input-limit', 'mostedited-option-limit', 'limit', 10 ) +
generateInput( 'mostedited-input-section-limit', 'mostedited-option-section-limit', 'section-limit', 3 ) +
generateInput( 'mostedited-input-edits', 'mostedited-option-edits', 'edits', 2 ) +
generateInput( 'mostedited-input-section-edits', 'mostedited-option-section-edits', 'section-edits', 2 ) +
'</tbody></table>' +
mw.html.element( 'input', { type: 'button', id: 'reloadButton',
value: mw.msg( 'mostedited-reload' ), title: mw.msg( 'mostedited-tooltip-reload' ) } )
)
) +
'</fieldset>';
html += mw.html.element( 'div', { id: 'mostEditedContainer' }, '' );
return html;
}
// functions to interact with user
/**
* reads user input from form elements, usable for URL
* @return {object} object with all parameters
*/
function readRawUserInput () {
return {
namespace: $( '#namespace option:selected' ).val(),
invert: $( '#nsinvert' ).prop( 'checked' ),
associated: $( '#nsassociated' ).prop( 'checked' ),
hours: $( '#time option:selected' ).val(),
sort: $( '#mostedited-sort option:selected' ).val(),
maxCalls: validateNumber( $( '#mostedited-input-max-calls' ), 5 ),
limit: validateNumber( $( '#mostedited-input-limit' ), 10 ),
sectionLimit: validateNumber( $( '#mostedited-input-section-limit' ), 3 ),
edits: validateNumber( $( '#mostedited-input-edits' ), 2 ),
sectionEdits: validateNumber( $( '#mostedited-input-section-edits' ), 2 )
};
}
/**
* reads user input from form elements, usable for API
* @return {object} object with all parameters
*/
function readUserInput () {
var rawInput = readRawUserInput(),
namespace = rawInput.namespace,
invert = rawInput.invert,
associated = rawInput.associated,
sort = rawInput.sort,
namespaces,
hours = rawInput.hours,
ago = new Date( Date.now() - hours * 3600000 ),
end = String( ago.getUTCFullYear() ) + '-' +
pad( ago.getUTCMonth() + 1 ) + '-' +
pad( ago.getUTCDate() ) + 'T' +
pad( ago.getUTCHours() ) + ':' +
pad( ago.getUTCMinutes() ) + ':' +
pad( ago.getUTCSeconds() ) + 'Z',
list, formattedNamespaces, i;
if ( namespace === '' ) {
namespaces = ''; // all
} else {
namespace = Number( namespace );
list = [ namespace ];
if ( associated ) {
list.push(
( namespace % 2 === 0 ) ?
namespace + 1 :
namespace - 1 );
}
if ( invert ) {
namespaces = [];
formattedNamespaces = mw.config.get( 'wgFormattedNamespaces' );
for ( i in formattedNamespaces ) {
if ( i >= 0 && list.indexOf( Number( i ) ) === -1 ) {
namespaces.push( i );
}
}
namespaces = namespaces.join( '|' );
} else {
namespaces = list.join( '|' );
}
}
return {
namespaces: namespaces,
sort: sort,
end: end,
maxCalls: rawInput.maxCalls,
limit: rawInput.limit,
sectionLimit: rawInput.sectionLimit,
edits: rawInput.edits,
sectionEdits: rawInput.sectionEdits
};
}
/**
* called when user clicks reload button
*/
function reloadPage () {
var params = readRawUserInput();
document.location.href = mw.util.getUrl( 'Special:BlankPage', {
action: 'mostedited',
namespace: params.namespace,
invert: params.invert ? '1' : '0',
associated: params.associated ? '1' : '0',
hours: params.hours,
sort: params.sort,
'max-calls': params.maxCalls,
limit: params.limit,
'section-limit': params.sectionLimit,
edits: params.edits,
'section-edits': params.sectionEdits
} );
}
/**
* called when user clicks submit button
*/
function submitQuery () {
var params = readUserInput();
$( '#submitButton' ).prop( 'disabled', true );
$( '#mostEditedContainer' ).empty().injectSpinner( 'mostedited' );
pagesList = {}; // empty
getAPIRecentChanges( params.end, params.namespaces, params.maxCalls, function ( done ) {
var $realTime = $( '#mostedited-real-time' ), $div = $( '<div>' );
if ( done ) {
firstTime = getTime( params.end );
$realTime.text( '' );
} else {
$realTime.html( mw.html.element( 'span',
{ title: mw.msg( 'mostedited-changed-period-tooltip' ) },
mw.msg( 'mostedited-changed-period', formatPeriod( ( currTime - firstTime ) / 3600000 ) ) ) );
}
$div.html( generateHTML( params.limit, params.sectionLimit, params.edits, params.sectionEdits, params.sort ) );
mw.hook( 'wikipage.content' ).fire( $div );
$( '#mostEditedContainer' ).html( $div );
$.removeSpinner( 'mostedited' );
$( '#submitButton' ).prop( 'disabled', false );
} );
}
// initialise
/**
* initialises the interface on Special:Blankpage
*/
function initBlankpage () {
document.title = mw.msg( 'pagetitle', mw.msg( 'mostedited' ) );
var $content = $( '#mw-content-text' ), // don't clear away subtitle, newtalk and jumpto,
$advancedOptions;
if ( $content.length !== 1 ) {
$content = mw.util.$content; // fallback
}
$content.html( generateHeaderHTML() );
// CSS for colors; enables/disables checkboxes
mw.loader.load( [ 'mediawiki.special.changeslist', 'mediawiki.special.recentchanges' ] );
$advancedOptions = $content.find( '#advanced-options' );
$advancedOptions.makeCollapsible();
$advancedOptions.find( '.mw-collapsible-toggle' ).appendTo( $advancedOptions );
$( '#reloadButton' ).on( 'click', reloadPage );
$( '#submitButton' ).on( 'click', submitQuery ).trigger( 'click' );
}
/**
* initialises the interface on Special:RecentChanges
*/
function initRecentchanges () {
var $button;
if ( $( '.rcfilters-head' ).length ) {
return;
}
$button = $( mw.html.element( 'input', { type: 'button', value: mw.msg( 'mostedited-submit' ) } ) )
.on( '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', {
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' } ),
mw.msg( 'mostedited' ),
'n-mostedited',
mw.msg( 'tooltip-n-mostedited' ),
null, // access key
'#n-recentchanges' );
}
mw.loader.using( [ 'mediawiki.language', 'mediawiki.util' ] ).then( function () {
initL10N( messages, [ 'minutes', 'hours', 'days', 'allpagessubmit', 'namespace',
'invert', 'tooltip-invert', 'namespace_association', 'tooltip-namespace_association',
'blanknamespace', 'namespacesall', 'rc-change-size', 'rc-change-size-new', 'pagetitle' ] );
$( initSidebar );
if ( mw.config.get( 'wgCanonicalSpecialPageName' ) === 'Recentchanges' ) {
$( initRecentchanges );
}
if (
mw.config.get( 'wgCanonicalSpecialPageName' ) === 'Blankpage' &&
mw.util.getParamValue( 'action' ) === 'mostedited'
) {
$.when(
mw.loader.using( [ 'mediawiki.jqueryMsg', 'jquery.spinner', 'jquery.makeCollapsible' ] ),
$.ready
).then( initBlankpage );
}
} );
//virtual outdent
} )( jQuery, mediaWiki );
// </nowiki>