MediaWiki:Gadget-AjaxQuickDelete.js
Jump to navigation
Jump to search
Note: After saving, you have to bypass your browser's cache to see the changes. Internet Explorer: press Ctrl-F5, Mozilla: hold down Shift while clicking Reload (or press Ctrl-Shift-R), Opera/Konqueror: press F5, Safari: hold down Shift + Alt while clicking Reload, Chrome: hold down Shift while clicking Reload.
This user script seems to have a documentation page at MediaWiki:Gadget-AjaxQuickDelete and an accompanying .css page at MediaWiki:Gadget-AjaxQuickDelete.css. |
- Report page listing warnings and errors.
/**
* AjaxQuickDelete from <https://commons.wikimedia.org/wiki/MediaWiki:Gadget-AjaxQuickDelete.js>
*
* Changelog:
* - 2010: Created in 2010 by [[User:Ilmari Karonen]] as Ajax-based replacement for [[MediaWiki:Quick-delete-code.js]].
* - Rewritten by [[User:DieBuche]].
* - Botdetection and encoding fixer by [[User:Lupo]].
* - 2011-2012: Validation and further development [[User:Rillke]], 2011-2012.
* - 2018: dropped IE8 support.
*
* TODO:
* - Fix problems with moves of videos.
* - Support move of timed text for videos.
*
* This pages has automated validation on save. Interested? See [[:commons:Commons:User scripts/reports]].
*/
// <nowiki>
/* eslint indent:[error,tab,{outerIIFEBody:0}] */
/* eslint-disable one-var, vars-on-top, camelcase, no-underscore-dangle, valid-jsdoc */
/* global $:false, mw:false, importScript:false */
( function () {
'use strict';
// Guard against multiple inclusions
if ( window.AjaxQuickDelete ) {
return;
}
var AQD,
conf = mw.config.get( [
'wgArticleId',
'wgCanonicalNamespace',
'wgCanonicalSpecialPageName',
'wgCategories',
'wgFormattedNamespaces',
'wgNamespaceNumber',
'wgPageName',
'wgRestrictionEdit',
'wgUserGroups',
'wgUserLanguage',
'wgUserName',
'wgIsRedirect'
] ),
nsNr = conf.wgNamespaceNumber,
pageName = conf.wgPageName;
// A bunch of helper functions
function _firstItem( o ) {
for ( var i in o ) {
if ( Object.prototype.hasOwnProperty.call( o, i ) ) {
return o[ i ];
}
}
}
$.ucFirst = function ( s ) {
return s[ 0 ].toUpperCase() + s.slice( 1 );
};
// Create the AjaxQuickDelete-singleton (object literal)
AQD = window.AjaxQuickDelete = {
// When maintaining this script always bump this number!
version: '1.1.3',
/**
* Runs before document ready and before translation is available
* (important event-binders should be attached as fast as possible)
*/
preinstall: function () {
// Promote our gadget when user opened old move page
if ( conf.wgCanonicalSpecialPageName === 'Movepage' && Number( $( 'select[name="wpNewTitleNs"]' ).val() ) === 6 ) {
$( '#mw-movepage-table' ).before(
'<div class="mw-message-box mw-message-box-warning">Consider using <i>Move & Replace</i> from the menu on file pages (open with a single click) when moving files to care for global usage and redirects.</div>'
);
}
this.doNothing = ( !conf.wgArticleId || nsNr < 0 || /^Commons:Deletion/.test( pageName ) || /^Commons:Categories/.test( pageName ) );
if ( this.doNothing ) {
return;
}
// Check user group
if ( conf.wgUserGroups.indexOf( 'sysop' ) !== -1 ) {
this.userRights = 'sysop';
} else if ( conf.wgUserGroups.indexOf( 'filemover' ) !== -1 ) {
this.userRights = 'filemover';
} else {
this.userRights = [ 'autopatrolled', 'patroller', 'image-reviewer' ].filter( function ( g ) {
return conf.wgUserGroups.indexOf( g ) !== -1;
} )[ 0 ];
}
if ( [ 'filemover', 'sysop' ].indexOf( this.userRights ) !== -1 && nsNr === 6 ) {
// Change "Move" to "Move & Replace"
var $moveLink = $( '#ca-move' ),
$moveLanchor = $moveLink.find( 'a' );
this.$moveLink = $moveLink = $moveLanchor.length ? $moveLanchor : $moveLink;
$moveLink.text( $moveLink.text() + ' & Replace' )
.attr( 'title', 'Click in order to ' + $moveLink.attr( 'title' ) + ' and replace usage. Default form though new tab.' )
.on( 'click', function ( e ) {
e.preventDefault();
AQD.moveFile();
} );
}
},
/**
* Set up the AjaxQuickDelete object and add the toolbox link. Called via document.ready during page loading.
*/
install: function () {
// Disallow performing operations on empty or special pages
if ( this.doNothing ) {
return;
}
// Check edit restrictions and do not install anything if protected
if ( conf.wgRestrictionEdit && conf.wgRestrictionEdit.length &&
conf.wgUserGroups.indexOf( conf.wgRestrictionEdit[ 0 ] ) === -1 ) {
return;
}
// Trigger a jQuery event for other scripts that like to know
// when this script has loaded all translations and is ready to install
$( document ).triggerHandler( 'scriptLoaded', [ 'AjaxQuickDelete' ] );
var link;
// Set up toolbox link
if ( nsNr === 14 ) {
// In categories the discuss-category link
link = mw.util.addPortletLink( 'p-tb', '#', this.i18n.toolboxLinkDiscuss, 't-ajaxquickdiscusscat' );
if ( link ) {
link.addEventListener( 'click', function ( e ) {
e.preventDefault();
mw.loader.using( 'jquery.ui' ).then( function () {
AQD.discussCategory();
} );
} );
}
} else {
// On other pages, the nominate-for-deletion link
link = mw.util.addPortletLink( 'p-tb', '#', this.i18n.toolboxLinkDelete, 't-ajaxquickdelete' );
if ( link ) {
link.addEventListener( 'click', function ( e ) {
e.preventDefault();
AQD.nominateForDeletion();
} );
}
}
// Install AjaxMoveButton for filemovers and administrators
if ( this.$moveLink ) {
// Change Move & Replace link to fully localized text
this.$moveLink.html( $( '<span>' ).text( this.i18n.dropdownMove ) );
// Add quicklinks to template
$( '#AjaxRenameLink' ).append( '<a href="javascript:AjaxQuickDelete.moveFile();">' + this.i18n.moveAndReplace + '</a>' )
.append( '<a href="javascript:AjaxQuickDelete.loadAndDeclineRequest(\'move\');" class="ajaxDeleteDeclineMove"><sup> ' + this.i18n.anyDecline + '</sup></a>' );
// Install x-To-DR. See [[Template:X-To-DR]]
$( '#mw-imagepage-content .convert-to-dr' )
.find( '.ctdr-btn-convert' ).on( 'click', this._convertToDR ).show().end()
// Currently filemover rights required
.find( '.ctdr-btn-remove' ).on( 'click', this._removeAnyTag ).show();
} else if ( this.userRights ) {
$( '#mw-imagepage-content .convert-to-dr .ctdr-btn-convert' ).on( 'click', this._convertToDR ).show();
}
// Install "Process Duplicates"-Link (either in template
// or if no template was detected and MediaWiki found dupes, behind the link in the dupe-section)
if ( this.userRights === 'sysop' && nsNr === 6 ) {
var dupeSection = $( '#AjaxDupeProcess' );
if ( dupeSection.length ) {
dupeSection.append( $( '<a>', {
href: '#',
text: this.i18n.processDupes,
style: 'font-weight:bold',
click: function ( e ) {
e.preventDefault();
AQD.processDupes();
}
} ) ).show();
} else {
dupeSection = $( '#mw-imagepage-section-duplicates .mw-imagepage-duplicates' );
if ( dupeSection.length ) {
dupeSection.find( 'li:first' )
.append( $( '<span>', {
style: 'display:none',
id: 'AjaxDupeDestination',
text: dupeSection.find( 'a' ).attr( 'title' )
} ) )
.append( ' ', $( '<sup>' ).append( $( '<a>', {
href: '#',
text: '[' + this.i18n.processDupes + ']',
style: 'background:#CEB',
click: function ( e ) {
e.preventDefault();
AQD.processDupes();
}
} ) ) );
}
}
}
// Extra buttons
// Wait until the user's js was loaded and executed
mw.loader.using( 'user', function () {
if ( mw.user.options.get( 'gadget-QuickDelete' ) ) {
mw.loader.using( 'ext.gadget.QuickDelete' ).always( function () {
AQD.doInsertTagButtons();
} );
}
} );
},
/**
* Ensure that all variables are in a good state
* You must call this method before doing anything!
* TODO: Never override pageName, always clean task queue
*/
initialize: function () {
pageName = conf.wgPageName;
this.tasks = [];
this.destination = undefined;
this.details = undefined;
this.declineReason = undefined;
this.notifyUser = true;
this.watchlist = 'preferences';
},
/**
* If a file exists, exchange the messages (very hackish)
* so the user is prompted to choose another destination
* TODO: Develop an improved solution
*/
fileExists: function () {
this.i18n.moveDestination = this.i18n.moveOtherDestination;
this.moveFile();
},
/**
* For moving files
*/
moveFile: function () {
var o = this;
o.initialize();
mw.loader.using( [ 'jquery.ui' ] ).then( function () {
o.showProgress();
if ( $( '#AjaxRenameLink' ).length ) {
o.possibleDestination = $( '#AjaxRenameDestination' ).text();
o.possibleReason = o.cleanReason( $( '#AjaxRenameReason' ).text() );
}
// Let's be sure we have a fresh token and the latest MIME-Info
o.addTask( 'getMoveToken' );
var linkstoimage = $( '#mw-imagepage-section-linkstoimage' );
if ( $( '#globalusage' ).length || (
linkstoimage.length &&
linkstoimage.find( 'a' ).not( '.mw-redirect' ).length -
linkstoimage.find( '.mw-imagepage-linkstoimage-ns2 a[href^="/wiki/User:OgreBot/Uploads"]' ).length
) ) {
o.inUse = true;
o.addTask( 'chkPreMoveDecline' );
}
o.addTask( 'promptForMoveTarget' );
o.addTask( 'doesFileExist' );
o.fileNameExistsCB = 'fileExists';
o.addTask( 'movePage' );
o.addTask( 'removeTemplate' );
o.addTask( 'queryRedirects' );
o.addTask( 'replaceUsage' );
// finally reload the page to show changed page
o.addTask( 'reloadPage' );
o.nextTask();
} );
},
promptForMoveTargetCB: function ( AQD ) {
if ( AQD.inUse ) {
$( '#AjaxQuestion2' ).prop( 'disabled', true );
}
},
promptForMoveTarget: function () {
var toAppend;
this.showProgress();
if ( mw.user.options.get( 'gadget-RenameLink' ) ) {
toAppend = [ $( '<a>' )
.text( this.i18n.moreInformation )
.on( 'click', function () {
if ( !AQD.rGetPolicy ) {
importScript( 'MediaWiki:RenameRequest.js' );
mw.hook( 'aqd.renamerequest.i18n' ).fire();
}
mw.hook( 'aqd.renamerequest.run' ).fire( { exec: this } );
} ).button( {
icons: { primary: 'ui-icon-script' },
showLabel: true,
text: false
} ).css( {
fontSize: '.6em',
margin: '0',
width: '2.5em',
'float': 'right'
} ), '<br>' ];
}
mw.hook( 'aqd.prompt' ).remove( this.promptForMoveTargetCB ).add( this.promptForMoveTargetCB );
this.prompt( [ {
message: this.i18n.moveDestination,
prefill: this.cleanFileName( this.possibleDestination || pageName ),
returnvalue: 'destination',
appendNode: toAppend,
cleanUp: true,
noEmpty: true
}, {
message: this.i18n.reasonForMove,
prefill: ( ( this.reason || this.possibleReason || '' ).trim().replace( /'{2,}/g, '' ).replace( /\s{2,}/g, ' ' ) ),
returnvalue: 'reason',
cleanUp: true,
noEmpty: false
}, {
message: this.i18n.leaveRedirect,
prefill: true,
returnvalue: 'wpLeaveRedirect',
// cleanUp: false,
noEmpty: false,
type: 'checkbox'
}, {
message: this.i18n.useCORSForReplace,
prefill: !window.aqdCORSOptOut,
returnvalue: 'replaceUsingCORS',
// cleanUp: false,
noEmpty: false,
type: 'checkbox'
}
], this.i18n.movingFile );
},
/* Warn other filemovers */
chkPreMoveDecline: function () {
$( '#mw-imagepage-section-linkstoimage' ).find( 'a' ).each( function () {
if ( $( this ).text() === 'Commons:File renaming/Recently declined rename requests' ) {
// eslint-disable-next-line no-alert
alert( AQD.i18n.warnRename );
return false;
}
} );
this.nextTask();
},
/**
* For loading jquery UI to decline a request
*/
loadAndDeclineRequest: function ( reason ) {
var that = this;
mw.loader.using('jquery.ui').then(function () {
that.declineRequest.call( that, reason );
} );
},
/**
* For declining a request
*/
declineRequest: function ( reason ) {
reason = reason || this.declineReason;
// No valid reason stated, see the rename guidelines or not an exact duplicate
this.initialize();
this.addTask( 'getMoveToken' );
this.addTask( 'removeTemplate' );
// finally reload the page to show the template was removed
this.addTask( 'reloadPage' );
// TODO extend the reason (for summary)
switch ( reason ) {
case 'move':
reason = 'rename request declined: does not comply with [[COM:FR|renaming guidelines]]';
if ( window.AjaxDeclineMoveWatchFile ) {
this.watchlist = 'watch';
}
break;
}
this.prompt( [ {
message: '',
prefill: reason || '',
returnvalue: 'declineReason',
cleanUp: false,
noEmpty: true,
byteLimit: 250
} ], this.i18n.declineRequest );
},
// This method is generlaly used directly by user scripts without going
// through other methods or setup functions. Needs to be standalone,
// and ensures loading of its own dependencies.
insertTagOnPage: function ( tag, img_summary, talk_tag, talk_summary, prompt_text, page, optin_notify ) {
var o = this;
this.initialize();
mw.loader.using( [ 'jquery.ui' ], function () {
o.pageName = ( page || pageName ).replace( /_/g, ' ' );
o.tag = tag.replace( '%USER%', conf.wgUserName ) + '\n';
o.img_summary = img_summary;
// first schedule some API queries to fetch the info we need…
// get token
o.addTask( 'findCreator' );
o.addTask( 'prependTemplate' );
if ( o.isMobile() && /(?:copyvio|nsd|npd|nld)/.test( tag ) ) {
o.addTask( 'listMobileUploadSpeedy' );
}
var prompt = [];
// Cave: insertTagOnPage is inserted as javascript link and therefore talk_tag can be "undefined"/string
if ( talk_tag && talk_tag !== 'undefined' ) {
o.talk_tag = talk_tag.replace( '%FILE%', o.pageName );
o.talk_summary = talk_summary.replace( '%FILE%', '[[:' + o.pageName + ']]' );
o.usersNeeded = true;
prompt.push( {
message: o.i18n.notifyUser,
prefill: true,
returnvalue: 'notifyUser',
type: 'checkbox'
} );
o.addTask( 'notifyUploaders' );
}
o.addTask( 'reloadPage' );
if ( tag.indexOf( '%PARAMETER%' ) !== -1 ) {
prompt.push( {
message: '',
prefill: '',
returnvalue: 'reason',
cleanUp: true,
noEmpty: true,
minLength: 1
} );
o.prompt( prompt, prompt_text || o.i18n.reasonForDeletion );
} else if ( optin_notify && prompt.length && o.talk_summary ) {
o.prompt( prompt, o.talk_summary );
} else {
o.nextTask();
}
} );
},
discussCategory: function () {
// reset task list in case an earlier error left it non-empty
this.initialize();
this.pageName = pageName.replace( /_/g, ' ' );
this.startDate = new Date();
// eslint-disable-next-line no-useless-escape
this.tag = '{{subst:cfd}}\n';
this.img_summary = 'This category needs discussion';
// eslint-disable-next-line no-useless-escape
this.talk_tag = '{{subst:cdw|1=' + pageName + '}}';
this.talk_summary = '[[:' + pageName + ']] needs discussion';
this.subpage_summary = 'Starting category discussion';
// set up some page names we'll need later
this.requestPage = 'Commons:Categories for discussion/' + this.formatDate( 'YYYY/MM/' ) + pageName;
this.dailyLogPage = 'Commons:Categories for discussion/' + this.formatDate( 'YYYY/MM' );
// first schedule some API queries to fetch the info we need…
this.addTask( 'findCreator' );
// …then schedule the actual edits
this.addTask( 'prependTemplate' );
this.addTask( 'createRequestSubpage' );
this.addTask( 'listRequestSubpage' );
this.addTask( 'notifyUploaders' );
// finally reload the page to show the deletion tag
this.addTask( 'reloadPage' );
var lazyLoadNode = this.createLazyLoadNode( this.i18n.moreInformation, 'MediaWiki:Gadget-AjaxQuickDelete.js/DiscussCategoryInfo', '#AjaxQuickDeleteCatInfo' );
this.prompt( [ {
message: '',
prefill: '',
returnvalue: 'reason',
cleanUp: true,
appendNode: lazyLoadNode,
noEmpty: true,
parseReason: true
}
], this.i18n.reasonForDiscussion );
},
nominateForDeletion: function ( page ) {
var o = this;
// reset task list in case an earlier error left it non-empty
this.initialize();
mw.loader.using( [ 'mediawiki.String', 'jquery.ui' ], function () {
o.pageName = ( page || pageName ).replace( /_/g, ' ' );
o.startDate = new Date();
// set up some page names we'll need later
var requestPage = o.pageName,
mwString = require( 'mediawiki.String' );
// MediaWiki has an ugly limit of 255 bytes per title, excluding the namespace
while ( mwString.byteLength( requestPage ) + mwString.byteLength( o.requestPagePrefix.replace( /^.+?:/, '' ) ) >= 255 ) {
requestPage = requestPage.slice( 0, requestPage.length - 1 ).trim();
}
o.requestPage = o.requestPagePrefix + requestPage;
o.dailyLogPage = o.requestPagePrefix + o.formatDate( 'YYYY/MM/DD' );
o.tag = '{{delete|reason=%PARAMETER%|subpage=' + requestPage + o.formatDate( '|year=YYYY|month=MON|day=DAY}}\n' );
switch ( nsNr ) {
// On MediaWiki pages, wrap inside comments (for css and js)
case 8:
o.tag = '/*' + o.tag + '*/';
break;
// On templates and creator/institution-templates: Wrap inside <noinclude>s.
case 10:
case 100:
case 106:
o.tag = '<noinclude>' + o.tag + '</noinclude>';
break;
case 828: // Lua comments
o.tag = '\n--[=[ ' + o.tag + ' ]=]\n';
}
if ( o.templateReplace ) {
o.declineReason = o.img_summary;
}
o.img_summary = 'Nominating for deletion';
// eslint-disable-next-line no-useless-escape
o.talk_tag = '{{subst:idw|1=' + requestPage + '}}';
o.talk_summary = '[[:' + o.pageName + ']] has been nominated for deletion';
o.subpage_summary = 'Starting deletion request';
// without \n it breaks the redirect syntax
if ( conf.wgIsRedirect ) {
o.tag += '\n';
}
// first schedule some API queries to fetch the info we need…
o.addTask( 'findCreator' );
// …then schedule the actual edits
o.addTask( o.templateReplace ? 'replaceTemplate' : 'prependTemplate' );
o.addTask( 'createRequestSubpage' );
o.addTask( 'listRequestSubpage' );
o.addTask( 'purge' );
o.addTask( 'notifyUploaders' );
if ( o.isMobile() ) {
o.addTask( 'listMobileUpload' );
}
// finally reload the page to show the deletion tag
o.addTask( 'reloadPage' );
var lazyLoadNode = o.createLazyLoadNode( o.i18n.moreInformation, 'MediaWiki:Gadget-AjaxQuickDelete.js/DeleteInfo', '#AjaxQuickDeleteDeleteInfo' );
o.prevDRNode = $( '<ul>' ).attr( 'id', 'AjaxDeletePrevRequests' );
o.secureCall( 'checkForFormerDR' );
var toAppend = $( '<div>' ).append( $( '<div>' ).attr( 'class', 'ajaxDeleteLazyLoad' ).css( {
'max-height': Math.max( Math.round( $( window ).height() / 2 ) - 250, 100 ),
'min-height': 0,
overflow: 'auto'
} ).append( o.prevDRNode ), '<br>', lazyLoadNode );
o.prompt( [ {
message: '',
prefill: o.reason || '',
returnvalue: 'reason',
cleanUp: true,
noEmpty: true,
appendNode: toAppend,
parseReason: true
}
], o.i18n.reasonForDeletion );
} );
},
// Check whether there was a deletion request for the same title in the past
checkForFormerDR: function () {
// Don't search for "kept" when nominating talk pages
if ( nsNr % 2 === 0 ) {
this.talkPage = conf.wgFormattedNamespaces[ nsNr + 1 ] + ':' + this.pageName.replace( conf.wgCanonicalNamespace + ':', '' );
this.queryAPI( {
prop: 'templates',
titles: this.talkPage,
tltemplates: 'Template:Kept',
tllimit: 1
}, 'formerDRTalk' );
}
this.queryAPI( {
list: 'backlinks',
bltitle: this.pageName,
blnamespace: 4,
blfilterredir: 'nonredirects',
bllimit: 500
}, 'formerDRRequestpage' );
},
formerDRTalk: function ( r ) {
var pgs = r.query.pages;
$.each( pgs, function ( id, pg ) {
if ( Array.isArray( pg.templates ) ) {
$( '<li>' ).append( $( '<a>', {
text: AQD.i18n.keptAfterDR,
href: mw.util.getUrl( AQD.talkPage )
} ) ).prependTo( AQD.prevDRNode );
} else if ( pg.missing === undefined ) {
$( '<li>' ).append( $( '<a>', {
text: AQD.i18n.hasTalkpage,
href: mw.util.getUrl( AQD.talkPage )
} ) ).appendTo( AQD.prevDRNode );
}
} );
},
formerDRRequestpage: function ( r ) {
var bls = r.query.backlinks,
_addItem = function ( t, m, bl ) {
$( '<li>' ).append( $( '<a>', {
text: t.replace( '%PAGE%', bl.title ),
href: mw.util.getUrl( bl.title )
} ) )[ m ]( AQD.prevDRNode );
};
$.each( bls, function ( i, bl ) {
if ( this.requestPage === bl.title ) {
_addItem( AQD.i18n.mentionedInDR, 'prependTo', bl );
} else if ( /^Commons:Deletion requests\/\D/.test( bl.title ) ) {
_addItem( AQD.i18n.mentionedInDR, 'appendTo', bl );
} else if ( /^Commons:Village pump\//.test( bl.title ) ) {
_addItem( AQD.i18n.mentionedInForum, 'appendTo', bl );
}
} );
},
renderNode: function ( $node, remotecontent, selector ) {
if ( selector ) {
selector = ' ' + selector;
}
$node.load( mw.util.wikiScript() + '?' + $.param( {
action: 'render',
title: remotecontent,
uselang: conf.wgUserLanguage
} ) + ( selector || '' ), function () {
$node.find( 'a' ).attr( 'href', function ( i, v ) {
return v.replace( 'MediaWiki:Anoneditwarning', conf.wgPageName );
} );
} );
return $node;
},
createLazyLoadNode: function ( label, page, selector ) {
return $( '<div>', { style: 'min-height:40px;' } ).append( $( '<a>', {
href: '#',
text: label
} ).on( 'click', function ( e ) {
e.preventDefault();
var $content = $( this ).parent().find( '.ajaxDeleteLazyLoad' ),
$contentInner = $content.find( '.ajax-quick-delete-loading' );
if ( $contentInner.length ) {
// first time invoked, do the XHR to load the content
AQD.renderNode( $content, $contentInner.data( 'aqdPage' ), selector );
}
$content.toggle( 'fast' );
} ), $( '<div>', {
// eslint-disable-next-line quote-props
'class': 'ajaxDeleteLazyLoad',
style: 'display:none;'
} ).append( $( '<span>', {
// eslint-disable-next-line quote-props
'class': 'ajax-quick-delete-loading',
text: this.i18n.loading
} ).data( 'aqdPage', page ) ) );
},
extractFromHTML: function ( $el ) {
$el = $( $el ).parent();
// …extract the regular expression from html
this.templateRegExp = $el.find( '.ctdr-regex' ).text();
var m = this.templateRegExp.match( /^\/(.+)\/(i)?$/ );
if ( !m || !m[ 1 ] ) {
m = new Error( this.i18n.templateRegExp );
this.fail( m );
throw m;
}
this.templateRegExp = new RegExp( m[ 1 ], m[ 2 ] );
// …and the decline reason
this.declineReason = $el.find( '.ctdr-template-decline-reason' ).text();
// …and the template name itself
m = $el.find( '.ctdr-template-name' ).text();
this.reason = 'This file was initially tagged by %USER%' + ( m ? ( ' as \'\'\'' + m + '\'\'\'' ) : '' );
},
removeProgress: function () {
this.showProgress();
return this.nextTask();
},
/**
* Remove any tag
* @context DOM-Element
* This function must be called with the DOM-Element as this-arg!
*/
_removeAnyTag: function ( e ) {
AQD.extractFromHTML( e.currentTarget || this );
AQD.removeAnyTag();
return false;
},
removeAnyTag: function () {
// this.initialize();
this.addTask( 'declineRequest' );
this.nextTask();
},
/**
* Convert any tag to a deletion request
* @context DOM-Element
* This function must be called with the DOM-Element as this-arg!
*/
_convertToDR: function ( e ) {
e = e.currentTarget || this;
AQD.extractFromHTML( e );
AQD.convertToDR( e );
return false;
},
convertToDR: function ( el ) {
// reset task list in case an earlier error left it non-empty
this.initialize();
this.declineReason = 'This file does not qualify for [[COM:SPEEDY|speedy-deletion]] and a regular deletion request will be started.';
this.templateReplace = true;
// first schedule a API query to fetch the info we need…
this.addTask( 'findTemplateAdder' );
this.addTask( 'getMoveToken' );
// prompt before conversion for user decision
this.addTask( 'removeTemplate' );
this.addTask( 'removeProgress' );
this.addTask( 'nominateForDeletion' );
// Hide the buttons to prevent attempts of duplicate removal
$( el ).closest( '.convert-to-dr' ).hide();
// … and go!
this.nextTask();
},
findTemplateAdder: function () {
var query = {
prop: 'revisions',
rvprop: 'content|user',
titles: pageName.replace( /_/g, ' ' ),
rvlimit: 50
};
this.queryAPI( query, 'findTemplateAdderCB' );
},
findTemplateAdderCB: function ( result ) {
var reason,
user,
pgRevs, // for debug
template;
$.each( result.query.pages, function ( id, pg ) {
pgRevs = pg.revisions;
var lastM = null;
for (var i = pgRevs.length - 1; i >= 0; i--) {
var m = pgRevs[ i ][ '*' ].match( AQD.templateRegExp );
if ( m ) {
if ( !lastM ) { // require tag to be an addition not there before
user = pgRevs[ i ].user;
if ( m.length > 1 && !template ) {
template = m[ 1 ];
}
if ( m.length > 2 && !reason ) {
reason = m[ 2 ];
}
}
lastM = m;
} else {
lastM = null;
}
}
} );
if ( !user ) {
mw.log.warn( pgRevs );
throw new Error( this.i18n.findTemplateAdderErr );
}
this.reason = this.reason.replace( '%USER%', '[[User:' + user + '|' + user + ']]' );
if ( template ) {
this.reason += ' (' + template + ')';
}
if ( reason ) {
this.reason += ' and the most recent rationale was: <span style="font-family: monospace">' + reason + '</span>';
}
this.nextTask();
},
processDupes: function () {
// reset task list in case an earlier error left it non-empty
this.initialize();
if ( $( '#globalusage' ).length || !$( '#mw-imagepage-nolinkstoimage' ).length ) {
this.inUse = true;
}
this.addTask( 'getDupeDetails' );
this.addTask( 'compareDetails' );
this.addTask( 'mergeDescriptions' );
this.addTask( 'saveDescription' );
this.addTask( 'replaceUsage' );
this.addTask( 'queryRedirects' );
this.addTask( 'deletePage' );
this.addTask( 'redirectPage' );
this.addTask( 'reloadPage' );
this.destination = $( '#AjaxDupeDestination' ).text();
this.nextTask();
},
getDupeDetails: function () {
this.queryAPI( {
curtimestamp: 1,
meta: 'tokens',
prop: 'imageinfo|revisions|info',
rvprop: 'content|timestamp',
inprop: 'watched',
iiprop: 'sha1|size|url',
iiurlwidth: 365,
redirects: 1,
titles: pageName.replace( /_/g, ' ' ) + '|' + this.destination
}, 'getDupeDetailsCB' );
this.showProgress( 'Fetching details' );
},
getDupeDetailsCB: function ( result ) {
this.details = [];
if ( result ) {
var q = result.query,
id,
pg,
ii,
n,
pages = q.pages;
for ( id in pages ) {
if ( Object.prototype.hasOwnProperty.call( pages, id ) ) {
pg = pages[ id ];
if ( !pg.imageinfo ) {
// Nothing we can change so prevent users reporting
this.disableReport = true;
throw new Error( ( ( pg.title.trim() === '{{{1}}}' ) ?
this.i18n.dupeParaErr :
this.i18n.dupeExistErr.replace( '%TITLE%', pg.title ) ) + ' (pg.imageinfo is undefined)' );
}
ii = pg.imageinfo[ 0 ];
n = {
title: pg.title,
size: ii.size,
width: ii.width,
height: ii.height,
thumburl: ii.thumburl,
thumbwidth: ii.thumbwidth,
thumbheight: ii.thumbheight,
descriptionurl: ii.descriptionurl,
sha1: ii.sha1,
content: pg.revisions[ 0 ][ '*' ],
starttimestamp: result.curtimestamp
};
this.details.push( n );
this.csrftoken = q.tokens.csrftoken;
if ( pg.watched !== undefined ) {
this.pageWasWatched = true;
}
}
}
}
if ( this.details.length < 2 ) {
this.disableReport = true;
throw new Error( this.i18n.noPageFound );
}
// If order (old=0, new=1) is incorrect: Reverse
if ( this.details[ 0 ].title !== pageName.replace( /_/g, ' ' ) ) {
this.details.reverse();
}
this.nextTask();
},
/**
* Edit the current page to add the specified tag. Assumes that the page hasn't
* been tagged yet; if it is, a duplicate tag will be added.
*/
prependTemplate: function () {
var page = {
title: this.pageName,
text: this.tag,
editType: 'prependtext',
watchlist: window.AjaxDeleteWatchFile ? 'watch' : this.watchlist,
minor: false
};
this.showProgress( this.i18n.addingAnyTemplate );
this.savePage( page, this.img_summary, 'nextTask' );
},
/**
* Edit the current page to add the specified tag and the changed content.
*/
replaceTemplate: function ( text ) {
var page = {
title: this.destination || pageName,
text: text || this.tag + this.pageContent,
editType: 'text',
starttimestamp: this.starttimestamp,
timestamp: this.timestamp,
watchlist: window.AjaxDeleteWatchFile ? 'watch' : this.watchlist,
minor: false
};
this.templateReplace = false;
this.showProgress( this.i18n.addingAnyTemplate );
this.savePage( page, ( this.declineReason || this.img_summary ), 'nextTask' );
},
/**
* Create the DR subpage (or append a new request to an existing subpage).
* The request page will always be watchlisted.
*/
createRequestSubpage: function () {
this.templateAdded = true; // we've got this far; if something fails, user can follow instructions on template to finish
var page = {
title: this.requestPage,
// eslint-disable-next-line no-useless-escape
text: '\n=== [[:' + this.pageName + ']] ===\n' + this.reason + ' ~~~~\n',
watchlist: 'watch',
editType: 'appendtext'
};
if ( this.isMobile() ) {
page.text += '\n<noinclude>[[Category:MobileUpload-related deletion requests]]</noinclude>';
}
this.showProgress( this.i18n.creatingNomination );
this.savePage( page, this.subpage_summary, 'nextTask' );
},
/**
* Transclude the nomination page onto today's DR log page, creating it if necessary.
* The log page will never be watchlisted (unless the user is already watching it).
*/
listRequestSubpage: function () {
var page = {};
page.title = this.dailyLogPage;
// Impossible when using appendtext. Shouldn't not be severe though, since DRBot creates those pages before they are needed.
// if (!page.text) page.text = "{{"+"subst:" + this.requestPagePrefix + "newday}}"; // add header to new log pages
page.text = '\n{{' + this.requestPage + '}}\n';
page.watchlist = 'preferences';
page.editType = 'appendtext';
this.showProgress( this.i18n.listingNomination );
this.savePage( page, 'Listing [[' + this.requestPage + ']]', 'nextTask' );
},
isMobile: function () {
var isMobile = false,
cats = conf.wgCategories;
// On mobile categories are not defined. Exit early to avoid .length error.
if (!cats) {
return true;
}
for ( var i = 0, len = cats.length; i < len; i++ ) {
isMobile = isMobile || /^Uploaded with Mobile/.test( cats[ i ] );
}
return isMobile;
},
listMobileUpload: function () {
var page = {
title: 'Commons:Deletion requests/mobile tracking',
text: '\n{{' + this.requestPage + '}}\n',
watchlist: 'preferences',
editType: 'appendtext'
};
this.showProgress( this.i18n.listingMobile );
this.savePage( page, 'Listing [[' + this.requestPage + ']]', 'nextTask' );
},
listMobileUploadSpeedy: function () {
var page = {
title: 'Commons:Mobile app/deletion request tracking',
text: '\n# [[:' + this.pageName + ']]',
watchlist: 'preferences',
editType: 'appendtext'
};
this.showProgress( this.i18n.listingMobile );
this.savePage( page, 'Listing [[' + this.pageName + ']]', 'nextTask' );
},
/**
* Check the users talkpage is not blocked indefinite
*/
verifyUserNotify: function ( user ) {
this.queryAPI( {
prop: 'info',
titles: this.userTalkPrefix + user,
redirects: 1,
inprop: 'protection'
}, 'verifyUserNotifyCB' );
},
verifyUserNotifyCB: function ( result ) {
var page;
this.notifyUser = true; // reset
if ( !result || !result.query || !result.query.pages ) {
mw.log.warn( 'Verify user: result.query.pages is undefined. ', result );
return this.uploaderNotified();
}
result = result.query.pages;
for ( var pg in result ) {
pg = result[ pg ];
page = pg.title;
pg = pg.protection;
if ( pg ) {
for ( var p = 0; p < pg.length; p++ ) {
var pt = pg[ p ];
if ( pt && pt.type === 'edit' && pt.level === 'sysop' ) {
// Disable report for protected userpages
this.disableReport = true;
// Disable notify for indefinite protected
if ( pt.expiry === 'infinity' ) {
this.notifyUser = false;
}
}
}
}
}
if ( this.notifyUser && page ) {
page = {
title: page,
// eslint-disable-next-line no-useless-escape
text: '\n' + this.talk_tag + ' ~~~~\n',
editType: 'appendtext',
redirect: true,
minor: false
};
if ( window.AjaxDeleteWatchUserTalk ) {
page.watchlist = 'watch';
}
this.savePage( page, this.talk_summary, 'uploaderNotified' );
} else {
this.uploaderNotified();
}
},
/**
* Notify any uploaders/creators of this page using {{idw}}.
*/
notifyUploaders: function () {
this.uploadersToNotify = 0;
if ( this.notifyUser ) {
for ( var user in this.uploaders ) {
if ( Object.prototype.hasOwnProperty.call( this.uploaders, user ) ) {
if ( user === conf.wgUserName ) {
// notifying yourself is pointless
continue;
}
this.verifyUserNotify( user );
this.showProgress( this.i18n.notifyingUploader.replace( '%USER%', user ) );
this.uploadersToNotify++;
}
}
}
if ( !this.uploadersToNotify ) {
this.nextTask();
}
},
uploaderNotified: function () {
this.uploadersToNotify--;
if ( !this.uploadersToNotify ) {
this.nextTask();
}
},
/**
* Compile a list of uploaders to notify. Users who have only reverted the file to an
* earlier version will not be notified.
* DONE: notify creator of non-file pages
* DONE: notify fileimporter (2019-09-09)
*/
findCreator: function () {
var q = {
curtimestamp: 1,
meta: 'tokens',
prop: 'revisions',
titles: this.pageName,
rvprop: 'timestamp|user',
rvdir: 'newer',
rvslots: 'main',
rvlimit: 1
};
if ( nsNr === 6 ) {
$.extend( q, {
prop: q.prop + '|imageinfo',
rvprop: q.rvprop + '|content',
iiprop: 'user|comment|sha1',
iilimit: '50',
list: 'logevents',
letype: 'import',
leprop: 'user',
letitle: this.pageName
} );
}
this.showProgress( this.i18n.preparingToEdit );
this.queryAPI( q, 'findCreatorCB' );
},
findCreatorCB: function ( r ) {
this.uploaders = {};
if ( !r || !r.query || !r.query.pages ) {
this.disableReport = true;
throw new Error( this.i18n.noPageFound );
}
var q = r.query,
pg = _firstItem( q.pages ),
rv;
if ( !pg || !pg.revisions ) {
throw new Error( this.i18n.noCreatorFound );
}
// The csrftoken only changes between sessions
this.csrftoken = q.tokens.csrftoken;
rv = pg.revisions[ 0 ];
// First handle non-file pages
if ( nsNr !== 6 || !pg.imageinfo ) {
this.starttimestamp = r.curtimestamp;
this.timestamp = rv.timestamp;
if ( ( this.pageCreator = rv.user ) ) {
this.uploaders[ this.pageCreator ] = true;
}
} else {
var info = pg.imageinfo,
i = info.length,
content = rv.slots.main[ '*' ],
seenHashes = {};
// Iterate in reverse order (sha1 check)
for ( --i; i >= 0; i-- ) {
var iii = info[ i ],
rev = seenHashes[ iii.sha1 ];
// Skip reverts and remove users
if ( iii.sha1 && rev ) {
for ( rev; rev > i; rev-- ) {
var u = info[ rev - 1 ].user;
if ( this.uploaders[ u ] < rev ) {
delete this.uploaders[ u ];
}
}
continue;
}
seenHashes[ iii.sha1 ] = i + 1;
// No need to check again the whole shebang
if ( this.uploaders[ iii.user ] ) {
continue;
}
// Now exclude bots which only reupload a new version:
if ( mw.libs.commons.isSmallChangesBot( iii.user ) ) {
continue;
}
// Outsourced to MediaWiki:Gadget-libCommons.js
iii = mw.libs.commons.getUploadBotUser( iii.user, content, iii.comment, rv.user );
if ( iii ) {
this.uploaders[ iii ] = i + 1;
}
}
// Get fileimporter
if ( ( info = q.logevents ) && ( i = info.length ) ) {
for ( --i; i >= 0; i-- ) {
if ( info[ i ].user ) {
this.uploaders[ info[ i ].user ] = true;
}
}
}
}
this.nextTask();
},
getMoveToken: function () {
this.showProgress( this.i18n.preparingToEdit );
var query = {
curtimestamp: 1,
prop: 'info|revisions',
meta: 'tokens',
rvprop: 'content|timestamp',
inprop: 'watched',
titles: this.pageName || pageName.replace( /_/g, ' ' )
};
if ( !this.declineReason ) {
query.prop += '|imageinfo';
query.iiprop = 'mediatype|mime|timestamp';
}
this.queryAPI( query, 'getMoveTokenCB' );
},
/**
* @brief [callback] Prepare page for saving before
* @param [in] result of query
* @return csrftoken, pageContent, starttimestamp, timestamp, imagetimestamp, mimeFileExtension, pageWasWatched
*/
getMoveTokenCB: function ( result ) {
var q = result.query || {},
pg = _firstItem( q.pages );
if ( !pg || !pg.revisions ) {
this.disableReport = true;
throw new Error( this.i18n.noPageFound );
}
// The csrftoken only changes between sessions
$.extend( this, {
csrftoken: q.tokens.csrftoken,
pageContent: pg.revisions[ 0 ][ '*' ],
starttimestamp: result.curtimestamp,
timestamp: pg.revisions[ 0 ].timestamp
} );
if ( pg.watched !== undefined ) {
this.pageWasWatched = true;
}
var ii = pg.imageinfo;
if ( ii && ii.length && ii[ 0 ].mime ) {
ii = ii[ 0 ];
this.imagetimestamp = ii.timestamp;
this.mimeFileExtension = ii.mime
.toLowerCase()
.replace( 'image/jpeg', 'jpg' )
.replace( /image\/(?:x-|vnd\.)?(png|gif|xcf|djvu|svg|tiff)(?:\+xml)?/, '$1' )
.replace( /application\/(ogg|pdf)/, '$1' )
.replace( /video\/(webm)/, '$1' )
.replace( 'audio/midi', 'mid' )
.replace( /audio\/(?:x-|vnd\.)?wave?/, 'wav' )
.replace( /audio\/(?:x-)?flac/, 'flac' );
if ( this.mimeFileExtension.length > 5 ) {
this.mimeFileExtension = '';
} else if ( this.mimeFileExtension === 'ogg' ) {
switch ( ii.mediatype ) {
case 'AUDIO':
this.mimeFileExtension = 'oga';
break;
case 'VIDEO':
this.mimeFileExtension = 'ogv';
break;
}
}
}
this.nextTask();
},
doesFileExist: function () {
if ( !this.destination ) {
// eslint-disable-next-line no-alert
return alert( this.i18n.moveDestination );
}
this.destination = this.cleanFileName( this.destination );
var query = {
prop: 'info|revisions',
titles: this.destination,
rvprop: 'content',
rvlimit: 2
};
// usually you would use 'redirects': 1, to detect the redirect target but
// in this case you would get the revisions for the target and not the redirect
this.showProgress( this.i18n.checkFileExists );
this.queryAPI( query, 'doesFileExistCB' );
},
/**
* Return nextTask if the page does not exist
* or it is a redirect with one revision to the source
*/
doesFileExistCB: function ( result ) {
if ( !result || !result.query || !result.query.pages ) {
throw new Error( 'Checking filename: result.query.pages is undefined. ' + this.destination );
}
var exists = true,
pg = _firstItem( result.query.pages ),
getRedirRegExp = function ( title ) {
title = title.replace( /^(File|Image):/, '' ).replace( /_/g, ' ' );
return new RegExp( '^\\s*#REDIRECT\\s*\\[\\[File\\:[' +
mw.util.escapeRegExp( title[ 0 ].toUpperCase() ) + mw.util.escapeRegExp( title[ 0 ].toLowerCase() ) + ']' +
mw.util.escapeRegExp( title.slice( 1 ) ).replace( / /g, '[ _]' ) +
'\\s*\\]\\]',
'' );
};
if ( pg.missing !== undefined ) {
exists = false;
} else if ( !pg.revisions || pg.revisions.length === 1 && getRedirRegExp( pageName )
.test( pg.revisions[ 0 ][ '*' ].replace( 'Image:', 'File:' ) ) ) {
// There seems to be no way to find out whether a title is a redirect
// and whether the redirect only consists of one revision
exists = false;
}
if ( exists ) {
if ( this.fileNameExistsCB ) {
this[ this.fileNameExistsCB ]( pg.title.replace( /^File:/, '' ) );
}
return;
}
this.nextTask();
},
removeTemplate: function () {
this.replaceWith = ( this.replaceWith || ( this.templateRegExp ? '' : '$1$2' ) );
// Remove the template from the text. In case there is an empty line before, remove this also.
var newText = this.pageContent
.replace(
( this.templateRegExp || /(?:([^=])\n)?\{\{(?:rename|rename media|rename image|move)\s*\|.*?\}\}(?:\n([^=]))?/i ),
this.replaceWith
);
if ( newText === this.pageContent ) {
return this.nextTask();
}
this.showProgress( this.i18n.removingTemplate );
newText = this.pageContent = newText.trim();
// We have another save request
if ( this.templateReplace ) {
this.img_summary = this.declineReason;
return this.nextTask();
}
if ( !this.declineReason ) {
this.img_summary = 'Removing template; rename done';
}
// If nothing remains, add the no-license-template (this also to prevent abuse filter blocking this edit because of page blanking)
this.replaceTemplate( newText || '{{subst:nld}}' );
},
replaceUsage: function () {
if ( !this.inUse ) {
return this.nextTask();
}
this.showProgress( this.i18n.replacingUsage );
var reasonShort = '[[COM:Duplicate|Duplicate]]:';
if ( !this.details ) {
AQD.reason = AQD.reason.replace( /\[\[Commons:File[_ ]renaming[^[\]]*\]\]:? ?/i, '' );
reasonShort = '[[COM:FR|File renamed]]:';
}
mw.loader.using( 'ext.gadget.libGlobalReplace', function () {
if ( AQD.replaceUsingCORS ) {
mw.libs.globalReplace( pageName, AQD.destination, reasonShort, AQD.reason )
.fail( function ( err ) {
throw new Error( err );
} )
.done( function () {
AQD.nextTask();
} )
.progress( function ( r ) {
AQD.showProgress( r );
mw.log( r );
} );
} else {
mw.libs.globalReplaceDelinker( pageName, AQD.destination, reasonShort + ' ' + AQD.reason, function () {
AQD.nextTask();
}, function ( err ) {
throw new Error( err );
} );
}
} );
},
redirectPage: function () {
var page = {
title: pageName,
text: '#REDIRECT [[' + this.destination + ']]',
editType: 'text',
watchlist: AQD.pageWasWatched ? 'watch' : 'preferences'
};
this.showProgress( this.i18n.redirectingFile );
this.savePage( page, 'Redirecting to duplicate file', 'nextTask' );
},
saveDescription: function () {
var page = {
title: this.destination,
text: this.newPageText,
editType: 'text',
watchlist: AQD.pageWasWatched ? 'watch' : 'preferences'
};
this.showProgress( this.i18n.savingDescription );
this.savePage( page, 'Merging details from duplicate ([[' + pageName + ']])', 'nextTask' );
},
/**
* Updates the redirects to the current page
* when moving or processing dupes immediately
* to prevent double redirects
*/
queryRedirects: function () {
mw.loader.using( 'mediawiki.api' ).then( function () {
return new mw.Api().loadMessagesIfMissing( [
'Whatlinkshere'
] );
} ).then( function () {
AQD.showProgress( mw.msg( 'Whatlinkshere' ) );
// TODO: Replace also the redirects inclusions!?
AQD.queryAPI( {
generator: 'backlinks',
gblfilterredir: 'redirects',
prop: 'revisions',
rvprop: 'content',
gbltitle: AQD.pageName || pageName.replace( /_/g, ' ' )
}, AQD.queryRedirectsCB ?
AQD.queryRedirectsCB :
'updateRedirects' );
} );
},
updateRedirects: function ( result ) {
AQD.redirectsToUpdate = 0;
if ( result.query && result.query.pages ) {
this.showProgress( this.i18n.updRedir );
$.each( result.query.pages, function ( id, pg ) {
var rv = pg.revisions[ 0 ];
if ( !rv || !rv[ '*' ] ) {
return;
}
// Update only redirects with same mimetype
if ( AQD.checkFileExt( pg.title, AQD.destination, true ) ) {
return mw.log( 'Redirect skipped, not same mimetype.', pg.title );
}
var page = {
title: pg.title,
text: rv[ '*' ].replace( /#\s*REDIRECT\s*\[\[.+/, '#REDIRECT [[' + AQD.destination + ']]' ),
editType: 'text',
watchlist: 'preferences'
};
AQD.savePage( page, 'Updating redirect while processing [[' + pageName.replace( /_/g, ' ' ) + ']]', 'updateRedirectsCB' );
AQD.redirectsToUpdate++;
} );
}
if ( !AQD.redirectsToUpdate ) {
AQD.nextTask();
}
},
updateRedirectsCB: function () {
AQD.redirectsToUpdate--;
if ( !AQD.redirectsToUpdate ) {
AQD.nextTask();
}
},
/**
* Pseudo-Modal JS windows.
*/
prompt: function ( questions, title, width ) {
var o = this,
dlgButtons = {};
dlgButtons[ this.i18n.submitButtonLabel ] = function () {
questions.forEach( function ( v, i ) {
var response = document.getElementById( 'AjaxQuestion' + i );
response = ( v.type === 'checkbox' ) ? response.checked : response.value;
if ( v.cleanUp ) {
if ( v.returnvalue === 'reason' ) {
response = AQD.cleanReason( response );
}
if ( v.returnvalue === 'destination' ) {
response = AQD.cleanFileName( response );
}
}
AQD[ v.returnvalue ] = response;
if ( v.returnvalue === 'reason' && AQD.tag ) {
AQD.tag = AQD.tag.replace( '%PARAMETER%', response );
if ( AQD.talk_tag ) {
AQD.talk_tag = AQD.talk_tag.replace( '%PARAMETER%', response );
}
AQD.img_summary = AQD.img_summary.replace( '%PARAMETER%', response )
.replace( '%PARAMETER-LINKED%', '[[:' + response + ']]' );
}
} );
$( this ).dialog( 'close' );
AQD.nextTask();
};
dlgButtons[ this.i18n.cancelButtonLabel ] = function () {
$( this ).dialog( 'close' );
};
var $submitButton,
$AjaxDeleteContainer = $( '<div>', { id: 'AjaxDeleteContainer' } ),
_parseReason = function () {
var $el = $( this ),
$parserResultNode = $el.data( 'parserResultNode' );
if ( !$parserResultNode ) {
return;
}
$parserResultNode.css( 'color', '#877' );
var _gotParsedText = function ( r ) {
try {
$parserResultNode.html( r );
$parserResultNode.css( 'color', '#000' );
} catch ( ex ) {}
};
mw.loader.using( [ 'ext.gadget.libAPI' ], function () {
mw.libs.commons.api.parse( $el.val(), conf.wgUserLanguage, pageName, _gotParsedText );
} );
},
_validateInput = function ( event ) {
var $el = $( this ),
v = $el.data( 'v' );
if ( v.noEmpty ) {
$submitButton.button( 'option', 'disabled', $el.val().trim().length < ( v.minLength || 8 ) );
}
if (
( $el.prop( 'nodeName' ) !== 'TEXTAREA' ) &&
( event.which === 13 ) &&
( v.enterToSubmit !== false ) &&
!$submitButton.button( 'option', 'disabled' )
) {
$submitButton.trigger( 'click' );
}
},
_convertToTextarea = function () {
var $el = $( this ),
$input = $el.data( 'toConvert' ),
$tarea = $( '<textarea>', {
id: $input.attr( 'id' ),
style: 'height:10em; width:98%; display:none;'
} );
$el.off().fadeOut();
$input.parent().prepend(
$tarea
.data( 'v', $input.data( 'v' ) ).data( 'parserResultNode', $input.data( 'parserResultNode' ) )
.val( $input.val() ).on( 'keyup', _parseReason ).on( 'keyup input', _validateInput ) );
$tarea.slideDown();
$input.remove();
};
questions.forEach( function ( v, i ) {
v.type = ( v.type || 'text' );
if ( v.type === 'textarea' ) {
$AjaxDeleteContainer.append( '<label for="AjaxQuestion' + i + '">' + v.message + '</label>' )
.append( '<textarea rows=20 id="AjaxQuestion' + i + '">' );
} else {
$AjaxDeleteContainer.append( '<label for="AjaxQuestion' + i + '">' + v.message + '</label>' )
.append( '<input type="' + v.type + '" id="AjaxQuestion' + i + '" style="width:97%;">' );
}
var curQuestion = $AjaxDeleteContainer.find( '#AjaxQuestion' + i );
if ( v.parseReason ) {
var $parserResultNode = $( '<div>', {
id: 'AjaxQuestionParse' + i,
html: ' '
} );
$AjaxDeleteContainer.append( '<br><label for="AjaxQuestionParse' + i + '">' +
o.i18n.previewLabel + '</label>' ).append( $parserResultNode );
curQuestion.data( 'parserResultNode', $parserResultNode ).keyup( _parseReason );
}
if ( v.type !== 'textarea' ) {
$AjaxDeleteContainer.append( '<br>' );
}
$AjaxDeleteContainer.append( v.appendNode ? v.appendNode : '<br>' );
if ( typeof v.byteLimit === 'number' ) {
mw.loader.using( 'jquery.lengthLimit', function () {
curQuestion.byteLimit( v.byteLimit );
} );
}
curQuestion.data( 'v', v );
curQuestion.on( 'keyup input', _validateInput );
// SECURITY: prefill could contain evil jsCode. Never use it unescaped!
// Use .val() or { value: prefill } or '<input value="' + mw.html.escape() + '" …>
curQuestion.val( v.prefill );
if ( v.type === 'checkbox' ) {
curQuestion.prop( 'checked', v.prefill ).attr( 'style', 'margin-left: 5px' );
}
} );
if ( mw.user.isAnon() ) {
AQD.renderNode( $( '<div>', { id: 'ajaxDeleteAnonwarning' } ), 'MediaWiki:Anoneditwarning' ).appendTo( $AjaxDeleteContainer );
}
$( '<div>' ).append( $AjaxDeleteContainer ).dialog( {
width: ( width || 600 ),
modal: true,
title: title,
dialogClass: 'wikiEditor-toolbar-dialog',
close: function () {
$( this ).dialog( 'destroy' ).remove();
if ( AQD.currentTask === 'formerDRRequestpage' ) {
$('.convert-to-dr' ).show();
}
},
buttons: dlgButtons,
open: function () {
// Look out for http://bugs.jqueryui.com/ticket/6830 / jQuery UI 1.9
var $buttons = $( this ).parent().find( '.ui-dialog-buttonpane button' );
$submitButton = $buttons.eq( 0 ).specialButton( 'proceed' );
$buttons.eq( 1 ).specialButton( 'cancel' );
}
} );
questions.forEach( function ( v, i ) {
var curQuestion = $AjaxDeleteContainer.find( '#AjaxQuestion' + i );
curQuestion.trigger( 'keyup' );
if ( v.type === 'text' ) {
var $q = curQuestion.wrap( '<div style="position:relative;">' ).parent(),
$i = $.createIcon( 'ui-icon-arrow-4-diag' ).attr( 'title', AQD.i18n.expandToTextarea );
$( '<span>', {
// eslint-disable-next-line quote-props
'class': 'ajaxTextareaConverter' } ).append( $i ).appendTo( $q ).data( 'toConvert', curQuestion ).on( 'click', _convertToTextarea );
}
} );
$( '#AjaxQuestion0' ).trigger( 'focus' ).trigger( 'select' );
mw.hook( 'aqd.prompt' ).fire( o );
},
/**
* Open a jQuery dialog with preview-images and some options
* and information to compare the two files
*/
compareDetails: function () {
var d = this.details[ 0 ],
f = this.details[ 1 ],
$swapButton,
$overlayButton;
if ( d.sha1 === f.sha1 ) {
this.exactDupes = true;
this.nextTask();
return;
}
var $imgD = $( '<div>' ).append( $( '<img>', {
src: d.thumburl,
height: d.thumbheight,
width: d.thumbwidth
} ), $( '<div>', {
id: 'AjaxDeleteImgDel',
html: Math.round( d.size / 1000 ) + ' KiB <br>' + d.width + '×' + d.height + '<br>'
} ).append(
$( '<a>', {
href: d.descriptionurl,
text: d.title,
target: '_blank'
} ) ) ),
$imgF = $( '<div>' ).append( $( '<img>', {
src: f.thumburl,
height: f.thumbheight,
width: f.thumbwidth
} ), $( '<div>', {
id: 'AjaxDeleteImgKeep',
html: Math.round( f.size / 1000 ) + ' KiB <br>' + f.width + '×' + f.height + '<br>'
} ).append(
$( '<a>', {
href: f.descriptionurl,
text: f.title,
target: '_blank'
} ) ) ),
dlgButtons = {};
dlgButtons[ this.i18n.submitButtonLabel ] = function () {
$( this ).dialog( 'close' );
AQD.nextTask();
};
dlgButtons[ this.i18n.inverseButtonLabel ] = function () {
$( this ).dialog( 'close' );
AQD.destination = pageName.replace( /_/g, ' ' );
pageName = f.title;
AQD.details.reverse();
AQD.inUse = true;
setTimeout( function () {
AQD.compareDetails();
}, 20 );
};
dlgButtons[ this.i18n.swapImagesButtonLabel ] = function () {
if ( $imgD[ 0 ].nextSibling === $imgF[ 0 ] ) {
$imgD.before( $imgF );
} else {
$imgF.before( $imgD );
}
};
var $fClone;
dlgButtons[ this.i18n.overlayButtonLabel ] = function () {
if ( $fClone ) {
$fClone.remove();
$fClone = 0;
} else {
$fClone = $imgF.clone().appendTo( $imgF.parent() );
$fClone.css( 'position', 'absolute' );
var pos = $imgD.position();
$fClone.css( {
top: pos.top - 1, left: pos.left - 1
} )
.fadeTo( 0, 0.65 );
// These modules should be already loaded for the dialog but let's be sure
mw.loader.using( [ 'jquery.ui'], function () {
// Set width to auto because AjaxQuickDelete.css sets it to a fixed size
$fClone.css( {
background: 'rgba(200, 200, 200, 0.5)',
width: 'auto',
border: '1px solid #0c9'
} ).draggable();
$fClone.find( 'img' ).resizable();
// In IE, opacity is not fully inerhited
$fClone.children( 'div' ).fadeTo( 0, 0.7 );
} );
}
};
this.showProgress();
var $AjaxDupeContainer = $( '<div>', { id: 'AjaxDupeContainer' } ).append( $imgD, $imgF );
$( '<div>' ).append( $AjaxDupeContainer ).dialog( {
width: 800,
modal: true,
title: this.i18n.compareDetails,
draggable: false,
dialogClass: 'wikiEditor-toolbar-dialog',
close: function () {
$( this ).dialog( 'destroy' ).remove();
},
buttons: dlgButtons,
open: function () {
var $buttons = $( this ).parent().find( '.ui-dialog-buttonpane button' );
$buttons.eq( 0 ).specialButton( 'proceed' );
$buttons.eq( 1 ).button( { icons: { primary: 'ui-icon-refresh' } } );
$swapButton = $buttons.eq( 2 ).button( { icons: { primary: 'ui-icon-transfer-e-w' } } );
$overlayButton = $buttons.eq( 3 ).button( { icons: { primary: 'ui-icon-newwin' } } );
$swapButton.css( 'float', ( ( $swapButton.css( 'float' ) === 'left' ) ? 'right' : 'left' ) );
$overlayButton.css( 'float', ( ( $overlayButton.css( 'float' ) === 'left' ) ? 'right' : 'left' ) );
}
} );
mw.loader.load( [ 'ext.gadget.libGlobalReplace', 'ext.gadget.libWikiDOM' ] );
},
mergeDescriptions: function () {
var newPageText = this.details[ 1 ].content;
mw.loader.using( [ 'ext.gadget.libGlobalReplace', 'ext.gadget.libWikiDOM' ], function () {
newPageText = mw.libs.wikiDOM.nowikiEscaper( newPageText ).doCleanUp();
AQD.showProgress();
AQD.prompt( [ {
message: '',
prefill: AQD.details[ 0 ].content,
returnvalue: 'discard',
cleanUp: false,
noEmpty: false,
type: 'textarea',
enterToSubmit: false
}, {
message: '',
prefill: newPageText,
returnvalue: 'newPageText',
cleanUp: false,
noEmpty: false,
type: 'textarea',
enterToSubmit: false
}, {
message: AQD.i18n.useCORSForReplace,
prefill: !window.aqdCORSOptOut,
returnvalue: 'replaceUsingCORS',
// cleanUp: false,
noEmpty: false,
type: 'checkbox'
}
], AQD.i18n.mergeDescription, 800 );
AQD.destination = AQD.details[ 1 ].title;
AQD.reason = 'Exact or scaled-down duplicate: [[:' + AQD.destination + ']]';
} );
},
/**
* Correct the MIME-Type; Accepts only valid filenames (with extension)
* Either a filename is passed or the destination property is used
*/
correctMIME: function ( fn ) {
// If the current mime-type is available to the script, check it;
// MediaWiki sometimes allows uploading mismatching mimetypes but not moving
var f = fn || this.destination;
if ( this.mimeFileExtension ) {
f = f.replace( /\.\w{2,5}$/, '.' + this.mimeFileExtension );
}
if ( !fn ) {
this.destination = f;
return this.nextTask();
} else {
return f;
}
},
cleanFileName: function ( fn, ignoreMIME ) {
// Remove Namespace
fn = fn.replace( /^(?:Image|File):/i, '' )
// Convert extension to lower case
.replace( /(\.\w{2,5})+$/, function ( $e ) {
return $e.toLowerCase();
} )
// jpeg -> jpg
.replace( /\.jpe*g$/, '.jpg' )
// First cleanUp from Flinfo (FlinfoOut.php) by Flominator and Lupo
.replace( /~{3,}/g, '' ) // "signature"
.replace( /[\u00A0\u1680\u180E\u2000-\u200B\u2028\u2029\u202F\u205F\u3000]/, ' ' ) // remove NBSP and other unusual spaces
.replace( /\s+|_/g, ' ' ) // (multiple) whitespace
// eslint-disable-next-line no-control-regex
.replace( /[\x00-\x1f\x7f]/g, '' )
.replace( /%([0-9A-Fa-f]{2})/g, '% $1' ) // URL encoding stuff
.replace( /&(([A-Za-z0-9\x80-\xff]+|#[0-9]+|#x[0-9A-Fa-f]+);)/g, '& $1' ) // URL-params?
.replace( /''/g, '"' )
.replace( /[:/|#]/g, '-' )
.replace( /[\]}>]/g, ')' )
.replace( /[[{<]/g, '(' );
fn = this.checkFileExt( pageName, fn, ignoreMIME ) || fn;
// Capitalize the first letter and prefix the namespace
return 'File:' + $.ucFirst( fn ); // own prototype
},
/**
* @brief Compare mimetype
* @param [in] of old filename
* @param [in] fn new filename
* @param [in] boolean
* @return false if same, otherwise new file with old extension
*/
checkFileExt: function ( of, fn, ignoreMIME ) {
var currentExt = ( !ignoreMIME && this.mimeFileExtension ) ?
this.mimeFileExtension :
of.replace( /.*?\.(\w{2,5})$/, '$1' ).toLowerCase().replace( 'jpeg', 'jpg' ),
reCurrentExt = new RegExp( '\\.' + mw.util.escapeRegExp( currentExt ) + '$', 'i' ),
reDestExt = new RegExp( '\\.' + mw.util.escapeRegExp( fn.replace( /.*?\.(\w{2,5})$/, '$1' ) ) + '$', 'i' );
// If new filename is without (same) extension, add the one from the old name
if ( !reCurrentExt.test( fn ) ) {
// First, try to replace the old extension
fn = fn.replace( reDestExt, '.' + currentExt );
if ( !reCurrentExt.test( fn ) ) {
// If this did not work, then simply append the old extension
fn += '.' + currentExt;
}
} else {
fn = false;
} // is equal
return fn;
},
cleanReason: function ( uncleanReason ) {
return uncleanReason
.trim() // whitespace
.replace( /(?:--|–|—)? ?~{3,5} ?/, '' ) // remove signature
.replace( /\|\s/g, '| ' ); // pipes
},
/**
* For display of progress messages.
*/
showProgress: function ( message ) {
if ( !message ) {
if ( this.progressDialog ) {
this.progressDialog.remove();
}
this.progressDialog = 0;
document.body.style.cursor = 'default';
return;
}
if ( $( '#feedbackContainer' ).length ) {
$( '#feedbackContainer' ).html( message );
} else {
document.body.style.cursor = 'wait';
this.progressDialog = $( '<div>' ).html( '<div id="feedbackContainer">' + ( message || this.i18n.preparingToEdit ) + '</div>' ).dialog( {
width: 450,
height: 'auto',
minHeight: 90,
modal: true,
resizable: false,
draggable: false,
closeOnEscape: false,
dialogClass: 'ajaxDeleteFeedback',
open: function () {
$( this ).parent().find( '.ui-dialog-titlebar' ).hide();
},
close: function () {
$( this ).dialog( 'destroy' ).remove();
}
} );
}
},
/**
* Submit an edited page.
*/
savePage: function ( page, summary, callback ) {
if ( AQD.csrftoken ) {
mw.user.tokens.set( 'csrfToken', AQD.csrftoken );
}
$.extend( true, page, {
cb: function ( r ) {
AQD.secureCall( callback, r );
},
// text, result, query
errCb: function ( t, r ) {
if ( AQD.uploadersToNotify ) {
// If user notify fails don't break next task (e.q. redirect to protected page, very rare)
AQD.secureCall( callback, r );
}
AQD.fail( t, r );
},
summary: summary
} );
mw.loader.using( [ 'ext.gadget.libAPI' ], function () {
mw.libs.commons.api.editPage( page );
} );
},
movePage: function () {
mw.user.tokens.set( 'csrfToken', AQD.csrftoken );
// Some users don't get it: They want to move pages to themselves.
if ( pageName.replace( /_/g, ' ' ) === AQD.destination ) {
return AQD.nextTask();
}
mw.loader.using( [ 'ext.gadget.libAPI' ], function () {
var moveArgs = {
cb: function () {
AQD.nextTask();
},
// text, r-result, query
errCb: function ( t, r ) {
if ( r && r.error && /articleexists/.test( r.error.code ) ) {
AQD.disableReport = true;
}
AQD.fail( t, r );
},
from: pageName,
to: AQD.destination,
reason: AQD.reason,
movetalk: true,
watchlist: AQD.pageWasWatched ? 'watch' : 'preferences'
};
// Option to not leave a redirect behind, MediaWiki default does leave one behind
// Just like movetalk, an empty parameter sets it to true
if ( AQD.wpLeaveRedirect === false ) {
moveArgs.noredirect = true;
}
AQD.showProgress( AQD.i18n.movingFile );
mw.libs.commons.api.movePage( moveArgs );
} );
},
deletePage: function () {
mw.user.tokens.set( 'csrfToken', AQD.csrftoken );
mw.loader.using( [ 'ext.gadget.libAPI' ], function () {
AQD.showProgress( AQD.i18n.deletingFile );
mw.libs.commons.api.deletePage( {
cb: function () {
AQD.nextTask();
},
// text, result, query
errCb: function ( t, r ) {
AQD.fail( t, r );
},
title: pageName,
reason: AQD.reason
} );
} );
},
purge: function () {
// No need for checking success, showing progress, nor for waiting for task to complete
this.nextTask();
$.post( this.apiURL, {
format: 'json',
action: 'purge',
forcelinkupdate: 1,
titles: pageName
} );
},
/**
* Does a MediaWiki API request and passes the result to the supplied callback (method name).
*/
queryAPI: function ( params, callback ) {
mw.loader.using( [ 'ext.gadget.libAPI' ], function () {
params.action = params.action || 'query';
mw.libs.commons.api.query( params, {
method: 'GET',
cache: false,
cb: function ( r ) {
AQD.secureCall( callback, r );
},
// text, result, query
errCb: function ( t, r ) {
AQD.fail( t, r );
}
} );
} );
},
/**
* Method to catch errors and report where they occurred
*/
secureCall: function ( fn, r ) {
var o = AQD;
try {
o.currentTask = arguments[ 0 ];
if ( typeof fn === 'function' ) {
// arguments is not of type array so we can't take .slice
return fn.apply( o, Array.prototype.slice.call( arguments, 1 ) );
} else if ( typeof fn === 'string' ) {
return o[ fn ].apply( o, Array.prototype.slice.call( arguments, 1 ) );
} else {
mw.log.warn( fn, this.tasks );
o.fail( 'This is not a function!' );
}
} catch ( ex ) {
o.fail( ex, r );
}
},
/**
* Simple task queue. addTask() adds a new task to the queue, nextTask() executes
* the next scheduled task. Tasks are specified as method names to call.
*/
tasks: [],
// list of pending tasks
currentTask: '',
// current task, for error reporting
addTask: function ( task ) {
this.tasks.push( task );
},
nextTask: function () {
this.secureCall( this.tasks.shift() );
},
retryTask: function () {
this.secureCall( this.currentTask );
},
/**
* Once we're all done, reload the page.
*/
reloadPage: function () {
this.showProgress();
if ( this.pageName && this.pageName.replace( / /g, '_' ) !== pageName ) {
return;
}
location.href = mw.util.getUrl( this.destination || pageName );
},
/**
* Error handler. Throws an alert at the user and give him
* the possibility to retry or autoreport the error-message.
*/
fail: function ( err, r ) {
var o = this,
msg,
dlgButtons = {};
if ( typeof err === 'object' ) {
msg = err.message + ' \n\n ' + err.name;
if ( err.lineNumber ) {
msg += ' @line' + err.lineNumber;
}
err = msg;
}
// Mostly the same as err
if ( typeof r === 'object' ) {
if ( r.error && /tpt-target-page|readonly/.test( r.error.code ) ) {
this.disableReport = true;
}
}
// err += '\n' + JSON.stringify( r );
msg = this.i18n.taskFailure[ this.currentTask ] || this.i18n.genericFailure;
// TODO: Needs cleanup
if ( this.img_summary === 'Nominating for deletion' ) {
msg += ' ' + ( this.templateAdded ? this.i18n.completeRequestByHand : this.i18n.addTemplateByHand );
}
dlgButtons[ this.i18n.retryButtonLabel ] = function () {
$( this ).remove();
o.retryTask();
};
if ( [ 'movePage', 'deletePage', 'notifyUploaders' ].indexOf( o.currentTask ) !== -1 &&
( /code 50\d|missingtitle/.test( err ) ) ) {
dlgButtons[ this.i18n.ignoreButtonLabel ] = function () {
$( this ).remove();
o.nextTask();
};
}
if ( !this.disableReport ) {
dlgButtons[ this.i18n.reportButtonLabel ] = function () {
var randomId = Math.round( Math.random() * 1099511627776 ),
toSend = '\n== Autoreport by AjaxQuickDelete ' + randomId + ' ==\n' + err +
'\nAQD version: ' + o.version +
'\n++++\n:Task: ' + o.currentTask + '\n:NextTask: ' + o.tasks[ 0 ] + '\n:LastTask: ' + o.tasks[ o.tasks.length - 1 ] +
'\n:Page: {{Page|1=' + ( o.pageName || pageName ) + '}}\n:Skin: ' + mw.user.options.get( 'skin' ) +
'\n:[{{fullurl:Special:Contributions|target={{subst:urlencode:{{subst:REVISIONUSER}}}}&offset={{subst:REVISIONTIMESTAMP}}}} Contribs] ' +
'[{{fullurl:Special:Log|user={{subst:urlencode:{{subst:REVISIONUSER}}}}&offset={{subst:REVISIONTIMESTAMP}}}} Log] ' +
'before error [[User:{{subst:REVISIONUSER}}|]] ~~~~~\n\n';
$( '#feedbackContainer' ).contents().remove().end()
.append( $( '<img>', { src: '//upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif' } ) ).css( 'text-align', 'center' );
$.post( o.apiURL, {
action: 'edit',
format: 'json',
title: 'MediaWiki talk:Gadget-AjaxQuickDelete.js/auto-errors',
summary: '/*Autoreport by AjaxQuickDelete ' + randomId + '*/ error with random id',
appendtext: toSend,
token: ( o.csrftoken || mw.user.tokens.get( 'csrfToken' ) )
}, function () {
o.reloadPage();
} );
};
}
dlgButtons[ this.i18n.abortButtonLabel ] = function () {
$( this ).remove();
};
this.disableReport = false;
this.showProgress();
this.progressDialog = $( '<div>' ).append( $( '<div>', {
id: 'feedbackContainer',
html: ( msg + '<br>' + this.i18n.errorDetails + '<br>' + mw.html.escape( err ) + '<br>' +
( this.tag ? ( this.i18n.tagWas + this.tag ) : '' ) + '<br><a href="' + mw.util.getUrl( 'MediaWiki talk:AjaxQuickDelete.js' ) +
'" >' + this.i18n.errorReport.replace( /%BUTTON%/, '<tt>' + this.i18n.reportButtonLabel + '</tt>' ) + '</a>' )
} ) ).dialog( {
width: 550,
modal: true,
closeOnEscape: false,
title: this.i18n.errorDlgTitle,
dialogClass: 'ajaxDeleteError',
buttons: dlgButtons,
close: function () {
$( this ).dialog( 'destroy' ).remove();
}
} );
if ( mw.log.warn ) {
mw.log.warn( err );
}
},
/**
* Very simple date formatter. Replaces the substrings "YYYY", "MM" and "DD" in a
* given string with the UTC year, month and day numbers respectively.
* Also replaces "MON" with the English full month name and "DAY" with the unpadded day.
*/
formatDate: function ( fmt, date ) {
return mw.libs.commons.formatDate( fmt, date,
( mw.libs.commons.api && mw.libs.commons.api.getCurrentDate() || new Date() )
);
},
// Constants
// DR subpage prefix
requestPagePrefix: 'Commons:Deletion requests/',
// user talk page prefix
userTalkPrefix: conf.wgFormattedNamespaces[ 3 ] + ':',
// MediaWiki API script URL
apiURL: mw.util.wikiScript( 'api' ),
// Max number of errors that are allowed for silent retry
apiErrorThreshold: 10,
// Translatable strings
i18n: {
toolboxLinkDelete: 'Nominate for deletion',
toolboxLinkDiscuss: 'Nominate category for discussion',
// GUI reason prompt form
reasonForDeletion: 'Why should this file be deleted?',
reasonForDiscussion: 'Why does this category need discussion?',
moreInformation: 'More information',
loading: 'Loading…',
keptAfterDR: 'This page was kept after a deletion request. Please contact the user who kept it before re-nominating.',
hasTalkpage: 'There is a talk page. Consider reading it or adding your remarks.',
mentionedInDR: 'Consider reading the deletion debate –%PAGE%– that links to this page.',
mentionedInForum: 'On %PAGE%, this page is part of a discussion.',
// Labels
previewLabel: 'Preview:',
submitButtonLabel: 'Proceed',
cancelButtonLabel: 'Cancel',
abortButtonLabel: 'Abort',
reportButtonLabel: 'Report automatically',
retryButtonLabel: 'Retry',
ignoreButtonLabel: 'Ignore and continue',
inverseButtonLabel: 'Inverse. Keep this delete other',
swapImagesButtonLabel: 'Swap to compare',
overlayButtonLabel: 'Overlay to compare',
expandToTextarea: 'Expand to textarea',
notifyUser: 'Notify users',
// GUI progress messages
preparingToEdit: 'Preparing to edit pages… ',
creatingNomination: 'Creating nomination page… ',
listingNomination: 'Adding nomination page to daily list… ',
addingAnyTemplate: 'Adding template to ' + conf.wgCanonicalNamespace.toLowerCase() + ' page… ',
notifyingUploader: 'Notifying %USER%… ',
listingMobile: 'Listing mobile upload',
updRedir: 'Updating redirects',
// Extended version
toolboxLinkSource: 'No source',
toolboxLinkLicense: 'No license',
toolboxLinkPermission: 'No permission',
toolboxLinkCopyvio: 'Report copyright violation',
reasonForCopyvio: 'Why is this file a copyright violation?',
// For moving files
notAllowed: 'You do not have the neccessary rights to move files',
reasonForMove: 'Why do you want to move this file?',
moveDestination: 'What should be the new filename?',
moveOtherDestination: 'The name you have specified exists. Choose a new name, please.',
checkFileExists: 'Checking whether file exists',
movingFile: 'Moving file',
replacingUsage: 'Ordering CommonsDelinker to replace all usage',
dropdownMove: 'Move & Replace',
leaveRedirect: 'Leave a redirect behind:',
moveAndReplace: 'Move file and replace all usage',
warnRename: 'File renaming was recently declined, be prudent!',
// For declining any request
removingTemplate: 'Removing template',
declineRequest: 'Why do you want to decline the request?',
anyDecline: 'Decline request',
// For Duplicates
useCORSForReplace: 'Try to replace usage immediately using your user account:',
deletingFile: 'Deleting file',
compareDetails: 'Please compare the images before merging the descriptions. The image with the bold text will be deleted.',
mergeDescription: 'Please now merge the file descriptions',
redirectingFile: 'Redirecting file',
savingDescription: 'Saving new details',
processDupes: 'Process Duplicates',
// Errors
errorDlgTitle: 'Error',
genericFailure: 'An error occurred while trying to do the requested action. ',
taskFailure: {
listUploaders: 'An error occurred while determining the ' + ( nsNr === 6 ? ' uploader(s) of this file' : 'creator of this page' ) + '.',
loadPages: 'An error occurred while preparing to nominate this ' + conf.wgCanonicalNamespace.toLowerCase() + ' for deletion.',
prependDeletionTemplate: 'An error occurred while adding the {{delete}} template to this ' + conf.wgCanonicalNamespace.toLowerCase() + '.',
createRequestSubpage: 'An error occurred while creating the request subpage.',
listRequestSubpage: 'An error occurred while adding the deletion request to today’s log.',
notifyUploaders: 'An error occurred while notifying the ' + ( nsNr === 6 ? ' uploader(s) of this file' : 'creator of this page' ) + '.',
movePage: 'Error while moving the page.',
deletePage: 'Error deleting the page.'
},
addTemplateByHand: 'To nominate this ' + conf.wgCanonicalNamespace.toLowerCase() + ' for deletion, please edit the page to add the {{delete}} template and follow the instructions shown on it.',
completeRequestByHand: 'Please follow the instructions on the deletion notice to complete the request.',
errorDetails: 'A detailed description of the error is shown below:',
errorReport: 'Manually report the error here or click on %BUTTON% to send an automatic error-report.',
tagWas: 'The tag to be inserted into this page was ',
// Minor errors/warnings
templateRegExp: 'The template does not expose a valid regular expression for {{X-To-DR}}. Go the the template and fix it there.',
findTemplateAdderErr: 'Unable to find the person who added the template. This can occur if the template was already removed, the page is deleted or a redirect to the template is used. In this case you must add the redirect to the RegExp of the target template.',
dupeParaErr: 'Error in the duplicate-template, check your language version!',
dupeExistErr: 'Retrieving information about %TITLE% failed. It is possible that it is deleted, the last revision is corrupt or the file is a redirect.',
noCreatorFound: 'The page you are attempting to add a tag to was deleted or moved. Unable to retrieve the content.',
noPageFound: 'The page you are attempting to modify or move is corrupted, was deleted or moved: Unable to retrieve history and contents.'
}
};
AQD.preinstall();
if ( conf.wgUserLanguage !== 'en' ) {
$.when( mw.loader.getScript( mw.util.wikiScript() +
'?title=MediaWiki:Gadget-AjaxQuickDelete.js/' +
conf.wgUserLanguage + '.js&action=raw&ctype=text/javascript'
), $.ready ).always( function () { AQD.install(); } );
} else {
$( function () {
AQD.install();
} );
}
}() );
// </nowiki>