મીડિયાવિકિ:Gadget-AjaxQuickDelete.js: આવૃત્તિઓ વચ્ચેનો તફાવત

Content deleted Content added
maintenance: more info discussCategory also triggers a .dialog call
trial
ટેગ: Reverted
લીટી ૧૫:
* This pages has automated validation on save. Interested? See [[:commons:Commons:User scripts/reports]].
*/
// <&lt;nowiki>&gt;
 
/*global jQuery:false, mediaWiki:false */
/* eslint indent:[error,tab,{outerIIFEBody:0}] */
/*jshint curly:false,laxbreak:true,scripturl:true,onecase:true,*/
/* eslint-disable one-var, vars-on-top, camelcase, no-underscore-dangle, valid-jsdoc */
 
/* global $:false, mw:false, importScript:false */
(function($, mw) {
 
( function () {
'use strict';
 
var namespaceNumber = mw.config.get('wgNamespaceNumber');
// Guard against multiple inclusions
var pageName = mw.config.get('wgPageName');
if ( window.AjaxQuickDelete ) {
var canonicalNS = mw.config.get('wgCanonicalNamespace');
return;
}
 
var AjaxQuickDelete, AQD;
var AQD,
conf = mw.config.get( [
'wgArticleId',
'wgCanonicalNamespace',
'wgCanonicalSpecialPageName',
'wgCategories',
'wgFormattedNamespaces',
'wgNamespaceNumber',
'wgPageName',
'wgRestrictionEdit',
'wgUserGroups',
'wgUserLanguage',
'wgUserName',
'wgIsRedirect'
] ),
nsNr = conf.wgNamespaceNumber,
pageName = conf.wgPageName;
 
if (typeof AjaxQuickDelete !== 'undefined' || namespaceNumber &lt; 0) return;
// A bunch of helper functions
function _firstItem( o ) {
for ( var i in o ) {
if ( Object.prototype.hasOwnProperty.call( o, i ) ) {
return o[ i ];
}
}
}
 
// utility method: Should be moved out into some global site code since used everywhere
$.ucFirst = function ( s ) {
$.createIcon = function (iconClass) {
return s[ 0 ].toUpperCase() + s.slice( 1 );
return $('&lt;span&gt;', { 'class': 'ui-icon ' + iconClass + ' ajaxInlineIcon', text: ' ' });
};
 
//AjaxQuickDelete = CreateAQD the= window.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="warningbox">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 ) );
** Set up the AjaxQuickDelete object and add the toolbox link. Called via $(document).ready() during page loading.
**/
install: function() {
 
// Disallow performing operations on empty pages
if ( this.doNothing ) {
if (0 === mw.config.get('wgArticleId')) return;
return;
}
 
// Check edit restrictions and do not install anything if protected
// Check user group
if (mw.config.get('wgRestrictionEdit') &amp;&amp; mw.config.get('wgRestrictionEdit').length) {
if ( conf.wgUserGroups.indexOf( 'sysop' ) !== -1 ) {
if ($.inArray(mw.config.get('wgRestrictionEdit')[0], mw.config.get('wgUserGroups')) === -1) {
this.userRights = 'sysop';
return;
} 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 ];
}
 
// wait for document.readyState
if ( [ 'filemover', 'sysop' ].indexOf( this.userRights ) !== -1 && nsNr === 6 ) {
$(function() {
// Change "Move" to "Move & Replace"
$(document).triggerHandler('scriptLoaded', ['AjaxQuickDelete']);
var $moveLink = $( '#ca-move' ),
$moveLanchor = $moveLink.find( 'a' );
this.$moveLink = $moveLink = $moveLanchor.length ? $moveLanchor : $moveLink;
 
// Set up toolbox link
$moveLink.text( $moveLink.text() + ' & Replace' )
if (namespaceNumber !== 14) {
.attr( 'title', 'Click in order to ' + $moveLink.attr( 'title' ) + ' and replace usage. Default form though new tab.' )
mw.util.addPortletLink('p-tb', 'javascript:AjaxQuickDelete.nominateForDeletion();', AQD.i18n.toolboxLinkDelete, 't-ajaxquickdelete', null);
.on( 'click', function ( e ) {
} else {
e.preventDefault();
mw.util.addPortletLink('p-tb', 'javascript:AjaxQuickDelete.discussCategory();', AQD.i18n.toolboxLinkDiscuss, 't-ajaxquickdiscusscat', null);
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 user group.
// Check edit restrictions and do not install anything if protected
if ($.inArray('sysop', mw.config.get('wgUserGroups')) !== -1) {
if ( conf.wgRestrictionEdit && conf.wgRestrictionEdit.length &&
AQD.userRights = 'sysop';
conf.wgUserGroups.indexOf( conf.wgRestrictionEdit[ 0 ] ) === -1 ) {
} else if ($.inArray('filemover', mw.config.get('wgUserGroups')) !== -1) {
return;
AQD.userRights = 'filemover';
}
}
 
// Install AjaxMoveButton
// Trigger a jQuery event for other scripts that like to know
if ((AQD.userRights === 'filemover' || AQD.userRights === 'sysop') &amp;&amp; namespaceNumber === 6) {
// when this script has loaded all translations and is ready to install
$( document ).triggerHandler( 'scriptLoaded', [ 'AjaxQuickDelete' ] );
 
// Also add a &quot;Move &amp; Replace&quot; button to dropdown menu
var link;
mw.util.addPortletLink('p-cactions', 'javascript:AjaxQuickDelete.moveFile(&quot;&quot;, &quot;&quot;);', AQD.i18n.dropdownMove, 'ca-quickmove', 'ca-move');
// 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();
} );
}
}
 
//Add quicklinks to template
// Install AjaxMoveButton for filemovers and administrators
if ($('#AjaxRenameLink').length) {
if ( this.$moveLink ) {
$('#AjaxRenameLink').append('&lt;a href=&quot;javascript:AjaxQuickDelete.moveFile();&quot;&gt;' + AQD.i18n.moveAndReplace + '&lt;/a&gt;').append('&lt;a href=&quot;javascript:AjaxQuickDelete.declineRequest(\'move\');&quot; class=&quot;ajaxDeleteDeclineMove&quot;&gt;&lt;sup&gt; ' + AQD.i18n.anyDecline + '&lt;/sup&gt;&lt;/a&gt;');
// Change Move & Replace link to fully localized text
}
this.$moveLink.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 x-To-DR
// Install "Process Duplicates"-Link (either in template
$('.ctdr-btn-convert').click(AQD._convertToDR);
// or if no template was detected and MediaWiki found dupes, behind the link in the dupe-section)
$('.ctdr-btn-remove').click(AQD._removeAnyTag);
if ( this.userRights === 'sysop' && nsNr === 6 ) {
$('.convert-to-dr').show();
var dupeSection = $( '#AjaxDupeProcess' );
}
if ( dupeSection.length ) {
if (AQD.userRights === 'sysop' &amp;&amp; namespaceNumber === 6) {
dupeSection.append( $( '<a>', {
if ($('#AjaxDupeProcess').length) {
href: '#',
$('#AjaxDupeProcess').append('&lt;a href=&quot;javascript:AjaxQuickDelete.processDupes();&quot;&gt;Process Duplicates&lt;/a&gt;').show();
text: this.i18n.processDupes,
}
style: 'font-weight:bold',
}
click: function ( e ) {
// Extra buttons
e.preventDefault();
if (&quot;1&quot; === mw.user.options.get('gadget-QuickDelete')) {
AQD.processDupes();
// Wait until the user's js was loaded and executed
}
mw.loader.using(['ext.gadget.QuickDelete', 'user'], function() {
} ) ).show();
AQD.doInsertTagButtons();
} else {
});
dupeSection = $( '#mw-imagepage-section-duplicates .mw-imagepage-duplicates' );
}
if ( dupeSection.length ) {
});
dupeSection.find( 'li:first' )
},
.append( $( '<span>', {
style: 'display:none',
/**
id: 'AjaxDupeDestination',
** Ensure that all variables are in a good state
text: dupeSection.find( 'a' ).attr( 'title' )
** You must call this method before doing anything!
} ) )
**/
.append( ' ', $( '<sup>' ).append( $( '<a>', {
initialize: function(undefined) {
href: '#',
pageName = mw.config.get('wgPageName');
text: '[' + this.i18n.processDupes + ']',
this.tasks = [];
style: 'background:#CEB',
this.destination = undefined;
click: function ( e ) {
this.details = undefined;
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();
} );
}
} );
},
 
fileExists: function() {
/**
this.i18n.moveDestination = this.i18n.moveOtherDestination;
* Ensure that all variables are in a good state
this.moveFile();
* 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';
},
 
/**
** For moving files
* If a file exists, exchange the messages (very hackish)
**/
* so the user is prompted to choose another destination
moveFile: function() {
* TODO: Develop an improved solution
this.initialize();
*/
this.showProgress();
fileExists: function () {
this.i18n.moveDestination = this.i18n.moveOtherDestination;
this.moveFile();
},
 
if ($('#AjaxRenameLink').length) {
/**
this.possibleDestination = this.cleanFileName($('#AjaxRenameDestination').text());
* For moving files
this.possibleReason = this.cleanReason($('#AjaxRenameReason').text());
*/
}
moveFile: function () {
var o = this;
 
if ($('#globalusage').length || !$('#mw-imagepage-nolinkstoimage').length) this.inUse = true;
o.initialize();
 
this.addTask('doesFileExist');
mw.loader.using( [ 'jquery.ui' ] ).then( function () {
this.fileNameExistsCB = 'fileExists';
o.showProgress();
this.addTask('getMoveToken');
this.addTask('movePage');
this.addTask('removeTemplate');
if (this.inUse) this.addTask('replaceUsage');
 
// finally reload the page to show changed page
if ( $( '#AjaxRenameLink' ).length ) {
this.addTask('reloadPage');
o.possibleDestination = $( '#AjaxRenameDestination' ).text();
o.possibleReason = o.cleanReason( $( '#AjaxRenameReason' ).text() );
}
 
this.prompt([{
// Let's be sure we have a fresh token and the latest MIME-Info
message: this.i18n.moveDestination,
o.addTask( 'getMoveToken' );
prefill: (this.possibleDestination || this.cleanFileName(pageName)),
returnvalue: 'destination',
cleanUp: true,
noEmpty: true
}, {
message: this.i18n.reasonForMove,
prefill: (this.reason || this.possibleReason || ''),
returnvalue: 'reason',
cleanUp: true,
noEmpty: false
}, {
message: this.i18n.leaveRedirect,
prefill: true,
returnvalue: 'wpLeaveRedirect',
cleanUp: false,
noEmpty: false,
type: 'checkbox'
}], this.i18n.movingFile);
if (this.inUse || this.userRights === 'filemover') $('#AjaxQuestion2').attr('disabled', true);
},
 
/**
var linkstoimage = $( '#mw-imagepage-section-linkstoimage' );
** For declining a request
if ( $( '#globalusage' ).length || (
**/
linkstoimage.length &&
declineRequest: function(reason) {
linkstoimage.find( 'a' ).not( '.mw-redirect' ).length -
// No valid reason stated, see the rename guidelines or not an exact duplicate
linkstoimage.find( '.mw-imagepage-linkstoimage-ns2 a[href^="/wiki/User:OgreBot/Uploads"]' ).length
this.initialize();
) ) {
o.inUse = true;
o.addTask( 'chkPreMoveDecline' );
}
 
o this.addTask( 'promptForMoveTargetgetMoveToken' );
this.addTask('removeTemplate');
 
// finally reload the page to show the template was removed
o.addTask( 'doesFileExist' );
this.addTask('reloadPage');
o.fileNameExistsCB = 'fileExists';
o.addTask( 'movePage' );
// extend the reason
o.addTask( 'removeTemplate' );
switch (reason) {
o.addTask( 'queryRedirects' );
case 'move':
o.addTask( 'replaceUsage' );
reason = 'No valid reason stated, see the [[COM:MOVE|rename guidelines]]';
break;
}
 
this.prompt([{
// finally reload the page to show changed page
message: '',
o.addTask( 'reloadPage' );
prefill: reason || this.declineReason || '',
returnvalue: 'declineReason',
cleanUp: false,
noEmpty: true,
byteLimit: 250
}], this.i18n.declineRequest);
},
 
insertTagOnPage: function(tag, img_summary, talk_tag, talk_summary, prompt_text, page) {
o.nextTask();
this.initialize();
} );
},
 
this.pageName = (page === undefined) ? pageName.replace(/_/g, ' ') : page.replace(/_/g, ' ');
promptForMoveTargetCB: function ( AQD ) {
this.tag = tag + '\n';
if ( AQD.inUse ) {
this.img_summary = img_summary;
$( '#AjaxQuestion2' ).prop( 'disabled', true );
}
},
 
// first schedule some API queries to fetch the info we need...
promptForMoveTarget: function () {
var toAppend;
this.showProgress();
 
// get token
if ( mw.user.options.get( 'gadget-RenameLink' ) ) {
this.addTask('findCreator');
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.addTask('prependTemplate');
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 );
},
 
// Cave: insertTagOnPage is inserted as javascript link and therefore talk_tag can be &quot;undefined&quot;/string
/* Warn other filemovers */
if (talk_tag &amp;&amp; talk_tag !== &quot;undefined&quot;) {
chkPreMoveDecline: function () {
this.talk_tag = talk_tag.replace('%FILE%', this.pageName);
$( '#mw-imagepage-section-linkstoimage' ).find( 'a' ).each( function () {
this.talk_summary = talk_summary.replace('%FILE%', '[[:' + this.pageName + ']]');
if ( $( this ).text() === 'Commons:File renaming/Recently declined rename requests' ) {
// eslint-disable-next-line no-alert
alert( AQD.i18n.warnRename );
return false;
}
} );
this.nextTask();
},
 
this.usersNeeded = true;
/**
this.addTask('notifyUploaders');
* For loading jquery UI to decline a request
}
*/
this.addTask('reloadPage');
loadAndDeclineRequest: function ( reason ) {
var that = this;
mw.loader.using('jquery.ui').then(function () {
that.declineRequest.call( that, reason );
} );
},
 
if (tag.indexOf(&quot;%PARAMETER%&quot;) !== -1) {
/**
this.prompt([{
* For declining a request
message: '',
*/
prefill: '',
declineRequest: function ( reason ) {
returnvalue: 'reason',
reason = reason || this.declineReason;
cleanUp: true,
// No valid reason stated, see the rename guidelines or not an exact duplicate
noEmpty: true,
this.initialize();
minLength: 1
}], prompt_text || this.i18n.reasonForDeletion);
} else {
this.nextTask();
}
},
 
discussCategory: function() {
this.addTask( 'getMoveToken' );
// reset task list in case an earlier error left it non-empty
this.addTask( 'removeTemplate' );
this.initialize();
 
this.pageName = pageName.replace(/_/g, ' ');
// finally reload the page to show the template was removed
this.startDate = new Date();
this.addTask( 'reloadPage' );
this.tag = '{' + '{subst:cfd}}';
this.img_summary = 'This category needs discussion';
this.talk_tag = '{' + '{subst:cdw|' + pageName + '}}';
this.talk_summary = &quot;[[:&quot; + pageName + &quot;]] needs discussion&quot;;
this.subpage_summary = 'Starting category discussion';
 
// set up some page names we'll need later
// TODO extend the reason (for summary)
this.requestPage = 'Commons:Categories for discussion/' + this.formatDate(&quot;YYYY/MM/&quot;) + pageName;
switch ( reason ) {
this.dailyLogPage = 'Commons:Categories for discussion/' + this.formatDate(&quot;YYYY/MM&quot;);
case 'move':
reason = 'rename request declined: does not comply with [[COM:FR|renaming guidelines]]';
if ( window.AjaxDeclineMoveWatchFile ) {
this.watchlist = 'watch';
}
break;
}
 
// first schedule some API queries to fetch the info we need...
this.prompt( [ {
this.addTask('findCreator');
message: '',
prefill: reason || '',
// ...then schedule the actual edits
returnvalue: 'declineReason',
this.addTask('notifyUploaders');
cleanUp: false,
this.addTask('prependTemplate');
noEmpty: true,
this.addTask('createRequestSubpage');
byteLimit: 250
} ], this.i18n.declineRequest addTask('listRequestSubpage');
 
// 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 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();
 
this.prompt([{
mw.loader.using( [ 'jquery.ui' ], function () {
message: '',
o.pageName = ( page || pageName ).replace( /_/g, ' ' );
prefill: '',
o.tag = tag.replace( '%USER%', conf.wgUserName ) + '\n';
returnvalue: 'reason',
o.img_summary = img_summary;
cleanUp: true,
appendNode: lazyLoadNode,
noEmpty: true,
parseReason: true
}], this.i18n.reasonForDiscussion);
 
},
// first schedule some API queries to fetch the info we need…
nominateForDeletion: function(page) {
// get token
var o = this;
o.addTask( 'findCreator' );
o.addTask( 'prependTemplate' );
// reset task list in case an earlier error left it non-empty
this.initialize();
 
mw.loader.using(['mediawiki.String', 'jquery.ui'], function (require) {
if ( o.isMobile() && /(?:copyvio|nsd|npd|nld)/.test( tag ) ) {
var mwStr = require('mediawiki.String');
o.addTask( 'listMobileUploadSpeedy' );
o.pageName = (page === undefined) ? pageName.replace(/_/g, ' ') : page.replace(/_/g, ' ');
}
o.startDate = new Date();
 
// set up some page names we'll need later
var prompt = [];
var requestPage = o.pageName;
// MediaWiki has an ugly limit of 255 bytes per title, excluding the namespace
while (mwStr.byteLength(requestPage) + mwStr.byteLength(o.requestPagePrefix.replace(/^.+?\:/, '')) &gt;= 255) {
requestPage = $.trim(requestPage.slice(0, requestPage.length-1));
}
o.requestPage = o.requestPagePrefix + requestPage;
o.dailyLogPage = o.requestPagePrefix + o.formatDate(&quot;YYYY/MM/DD&quot;);
 
o.tag = &quot;{{delete|કારણ=%PARAMETER%|subpage=&quot; + requestPage + o.formatDate(&quot;|year=YYYY|month=MON|day=DAY}}n&quot;);
// Cave: insertTagOnPage is inserted as javascript link and therefore talk_tag can be "undefined"/string
if ( talk_tag && talk_tag !== 'undefined' ) {
switch (namespaceNumber) {
o.talk_tag = talk_tag.replace( '%FILE%', o.pageName );
// On MediaWiki pages, wrap inside comments (for css and js)
o.talk_summary = talk_summary.replace( '%FILE%', '[[:' + o.pageName + ']]' );
case 8:
o.tag = '/*' + o.tag + '*/';
break;
// On templates and creator/institution-templates: Wrap inside &lt;noinclude&gt;s.
case 10:
case 100:
case 106:
o.tag = '&lt;noinclude&gt;' + o.tag + '&lt;/noinclude&gt;';
break;
}
o.img_summary = 'Nominating for deletion';
o.talk_tag = '{' + '{subst:idw|' + requestPage + '}}';
o.talk_summary = &quot;[[:&quot; + o.pageName + &quot;]] has been nominated for deletion&quot;;
o.subpage_summary = 'Starting deletion request';
 
o.usersNeeded = true;
// first schedule some API queries to fetch the info we need...
o.addTask('findCreator');
 
// ...then schedule the actual edits
prompt.push( {
o.addTask('prependTemplate');
message: o.i18n.notifyUser,
o.addTask('createRequestSubpage');
prefill: true,
o.addTask('listRequestSubpage');
returnvalue: 'notifyUser',
o.addTask('notifyUploaders');
type: 'checkbox'
} );
o.addTask( 'notifyUploaders' );
}
o.addTask( 'reloadPage' );
 
// finally reload the page to show the deletion tag
if ( tag.indexOf( '%PARAMETER%' ) !== -1 ) {
o.addTask('reloadPage');
prompt.push( {
message: '',
prefill: '',
returnvalue: 'reason',
cleanUp: true,
noEmpty: true,
minLength: 1
} );
 
var lazyLoadNode = o.createLazyLoadNode(o.i18n.moreInformation, 'MediaWiki:Gadget-AjaxQuickDelete.js/DeleteInfo', '#AjaxQuickDeleteDeleteInfo');
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();
}
} );
},
 
o.prompt([{
discussCategory: function () {
message: '',
// reset task list in case an earlier error left it non-empty
prefill: o.reason || '',
this.initialize();
returnvalue: 'reason',
cleanUp: true,
noEmpty: true,
appendNode: lazyLoadNode,
parseReason: true
}], o.i18n.reasonForDeletion);
});
},
renderNode: function($node, remotecontent, selector) {
if (selector) selector = ' ' + selector;
$node.load(mw.config.get('wgScript') + '?' + $.param({
'action': 'render',
'title': remotecontent,
'uselang': mw.config.get('wgUserLanguage')
}) + (selector || ''), function() {
$node.find('a').each(function(i, el) {
var $el = $(el);
$el.attr('href', $el.attr('href').replace('MediaWiki:Anoneditwarning', mw.config.get('wgPageName')));
});
});
return $node;
},
 
createLazyLoadNode: function(label, page, selector) {
this.pageName = pageName.replace( /_/g, ' ' );
return $('&lt;div&gt;', {
this.startDate = new Date();
style: 'min-height:40px;'
// eslint-disable-next-line no-useless-escape
}).append($('&lt;a&gt;', {
this.tag = '{{subst:cfd}}';
'href': '#',
this.img_summary = 'This category needs discussion';
'text': label
// eslint-disable-next-line no-useless-escape
}).click(function(e) {
this.talk_tag = '{{subst:cdw|1=' + pageName + '}}';
e.preventDefault();
this.talk_summary = '[[:' + pageName + ']] needs discussion';
var $content = $(this).parent().find('.ajaxDeleteLazyLoad');
this.subpage_summary = 'Starting category discussion';
var $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');
}), $('&lt;div&gt;', {
'class': 'ajaxDeleteLazyLoad',
'style': 'display:none;'
}).append($('&lt;span&gt;', {
'class': 'ajax-quick-delete-loading',
'text': this.i18n.loading
}).data('aqdPage', page)));
},
extractFromHTML: function(DOMElement) {
var $el = $(DOMElement);
// ...extract the regular expression from html
this.templateRegExp = $el.parent().find('.ctdr-regex').text();
var m = this.templateRegExp.match(/^\/(.+)\/(i)?$/);
if (!m || !m[1]) {
var err = new Error('The template does not expose a valid regular expression for {{X-To-DR}}. Go the the template and fix it there.');
this.fail(err);
throw err;
}
this.templateRegExp = new RegExp(m[1], m[2]);
// ...and the template name itself
var template = $el.parent().find('.ctdr-template-name').text();
this.reason = &quot;This file was initially tagged by %USER%&quot; + (template ? (&quot; as '''&quot; + template + &quot;'''&quot;) : &quot;&quot;);
// ...and the decline reason
this.declineReason = $el.parent().find('.ctdr-template-decline-reason').text();
},
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(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) {
AQD.extractFromHTML(this);
AQD.convertToDR();
return false;
},
convertToDR: function() {
// reset task list in case an earlier error left it non-empty
this.initialize();
 
// setfirst upschedule somea pageAPI namesquery to fetch the info we'll need later...
this.addTask('findTemplateAdder');
this.requestPage = 'Commons:Categories for discussion/' + this.formatDate( 'YYYY/MM/' ) + pageName;
this.addTask('getMoveToken');
this.dailyLogPage = 'Commons:Categories for discussion/' + this.formatDate( 'YYYY/MM' );
 
// ...then schedule the actual edits
// first schedule some API queries to fetch the info we need…
this.addTask( 'findCreatorremoveTemplate' );
this.addTask('removeProgress');
this.addTask('nominateForDeletion');
this.declineReason = &quot;This file does not qualify for [[COM:SPEEDY|speedy-deletion]] and a regular deletion request will be started.&quot;;
// Hide the buttons to prevent attempts of duplicate removal
$('.convert-to-dr').hide();
 
// ... and go!
// …then schedule the actual edits
this.nextTask();
this.addTask( 'prependTemplate' );
},
this.addTask( 'createRequestSubpage' );
findTemplateAdder: function() {
this.addTask( 'listRequestSubpage' );
var query = {
this.addTask( 'notifyUploaders' );
action: 'query',
prop: 'revisions',
rvprop: 'user|content',
titles: pageName.replace(/_/g, ' '),
rvlimit: 50
};
this.doAPICall(query, 'findTemplateAdderCB');
},
findTemplateAdderCB: function(result) {
var m, reason, user, template;
$.each(result.query.pages, function(id, pg) {
$.each(pg.revisions, function(iRv, rv) {
m = rv['*'].match(AQD.templateRegExp);
if (m) {
user = rv.user;
if (m.length &gt; 1 &amp;&amp; !template) template = m[1];
if (m.length &gt; 2 &amp;&amp; !reason) reason = m[2];
} else {
return false;
}
});
});
if (!user) throw new Error(&quot;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.&quot;);
this.reason = this.reason.replace('%USER%', &quot;[[User:&quot; + user + &quot;|&quot; + user + &quot;]]&quot;);
if (template) this.reason += &quot; (&quot; + template + &quot;)&quot;;
if (reason) this.reason += &quot; and the most recent rationale was: &lt;tt&gt;&quot; + reason + &quot;&lt;/tt&gt;&quot;;
this.nextTask();
},
 
processDupes: function() {
// finally reload the page to show the deletion tag
// reset task list in case an earlier error left it non-empty
this.addTask( 'reloadPage' );
this.initialize();
 
if ($('#globalusage').length || !$('#mw-imagepage-nolinkstoimage').length) this.inUse = true;
var lazyLoadNode = this.createLazyLoadNode( this.i18n.moreInformation, 'MediaWiki:Gadget-AjaxQuickDelete.js/DiscussCategoryInfo', '#AjaxQuickDeleteCatInfo' );
 
this.addTask('getDupeDetails');
this.prompt( [ {
message: '',
prefill: '',
returnvalue: 'reason',
cleanUp: true,
appendNode: lazyLoadNode,
noEmpty: true,
parseReason: true
}
], this.i18n.reasonForDiscussion );
 
this.addTask('compareDetails');
},
this.addTask('mergeDescriptions');
nominateForDeletion: function ( page ) {
this.addTask('saveDescription');
var o = this;
if (this.inUse) this.addTask('replaceUsage');
this.addTask('deletePage');
this.addTask('redirectPage');
 
this.addTask('reloadPage');
// reset task list in case an earlier error left it non-empty
this.initialize();
 
this.destination = $('#AjaxDupeDestination').text();
mw.loader.using( [ 'mediawiki.String', 'jquery.ui' ], function () {
this.nextTask();
o.pageName = ( page || pageName ).replace( /_/g, ' ' );
o.startDate = new Date();
 
},
// set up some page names we'll need later
getDupeDetails: function() {
var requestPage = o.pageName,
var query = {
mwString = require( 'mediawiki.String' );
action: 'query',
prop: 'imageinfo|revisions|info',
rvprop: 'content|timestamp',
intoken: 'edit|delete',
iiprop: 'size|sha1|url',
iiurlwidth: 365,
titles: pageName.replace(/_/g, ' ') + '|' + this.destination
};
this.doAPICall(query, 'getDupeDetailsCB');
this.showProgress('Fetching details');
},
getDupeDetailsCB: function(result) {
var pages, id, v, ii, n;
 
pages = result.query.pages;
// MediaWiki has an ugly limit of 255 bytes per title, excluding the namespace
this.details = [];
while ( mwString.byteLength( requestPage ) + mwString.byteLength( o.requestPagePrefix.replace( /^.+?:/, '' ) ) >= 255 ) {
requestPage = requestPage.slice( 0, requestPage.length - 1 ).trim();
}
 
for (id in pages) {
o.requestPage = o.requestPagePrefix + requestPage;
if (pages.hasOwnProperty(id)) {
o.dailyLogPage = o.requestPagePrefix + o.formatDate( 'YYYY/MM/DD' );
v = pages[id];
if (!v.imageinfo) {
// Nothing we can change so prevent users reporting
this.disableReport = true;
if ($.trim(v.title) === '{{{1}}}') {
throw new Error(&quot;Error in the duplicate-template, check your language version! (v.imageinfo is undefined)&quot;);
} else {
throw new Error(&quot;Retrieving information about &quot; + v.title + &quot; failed. It is possible that it is deleted, the last revision is corrupt or the file is a redirect. (v.imageinfo is undefined)&quot;);
}
}
ii = v.imageinfo[0];
n = {};
this.details.push(n);
n.title = v.title;
n.size = ii.size;
n.width = ii.width;
n.height = ii.height;
n.thumburl = ii.thumburl;
n.thumbwidth = ii.thumbwidth;
n.thumbheight = ii.thumbheight;
n.descriptionurl = ii.descriptionurl;
n.sha1 = ii.sha1;
n.content = v.revisions[0]['*'];
n.starttimestamp = v.starttimestamp;
this.edittoken = v.edittoken;
this.deletetoken = v.deletetoken;
}
}
//If ordner (old=0, new=1) not correct: Reverse the order
if (this.details[0].title !== pageName.replace(/_/g, ' ')) this.details.reverse();
this.nextTask();
},
 
/**
o.tag = '{{delete|reason=%PARAMETER%|subpage=' + requestPage + o.formatDate( '|year=YYYY|month=MON|day=DAY}}\n' );
** 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 = {};
page.title = this.pageName;
page.text = this.tag;
page.editType = 'prependtext';
if (window.AjaxDeleteWatchFile) page.watchlist = 'watch';
 
this.showProgress(this.i18n.addingAnyTemplate);
switch ( nsNr ) {
this.savePage(page, this.img_summary, 'nextTask');
// 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 ) {
** Create the DR subpage (or append a new request to an existing subpage).
o.declineReason = o.img_summary;
** The request page will always be watchlisted.
}
**/
o.img_summary = 'Nominating for deletion';
createRequestSubpage: function() {
// eslint-disable-next-line no-useless-escape
this.templateAdded = true; // we've got this far; if something fails, user can follow instructions on template to finish
o.talk_tag = '{{subst:idw|1=' + requestPage + '}}';
var page = {};
o.talk_summary = '[[:' + o.pageName + ']] has been nominated for deletion';
page.title = this.requestPage;
o.subpage_summary = 'Starting deletion request';
page.text = &quot;\n=== [[:&quot; + this.pageName + &quot;]] ===\n&quot; + this.reason + &quot; ~~&quot; + &quot;~~\n&quot;;
// without \n it breaks the redirect syntax
page.watchlist = 'watch';
if ( conf.wgIsRedirect ) {
page.editType = 'appendtext';
o.tag += '\n';
}
 
this.showProgress(this.i18n.creatingNomination);
// first schedule some API queries to fetch the info we need…
o.addTask( 'findCreator' );
 
this.savePage(page, this.subpage_summary, 'nextTask');
// …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
** Transclude the nomination page onto today's DR log page, creating it if necessary.
o.addTask( 'reloadPage' );
** 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.
var lazyLoadNode = o.createLazyLoadNode( o.i18n.moreInformation, 'MediaWiki:Gadget-AjaxQuickDelete.js/DeleteInfo', '#AjaxQuickDeleteDeleteInfo' );
// if (!page.text) page.text = &quot;{{&quot;+&quot;subst:&quot; + this.requestPagePrefix + &quot;newday}}&quot;; // add header to new log pages
o.prevDRNode = $( '<ul>' ).attr( 'id', 'AjaxDeletePrevRequests' );
page.text = &quot;\n{{&quot; + this.requestPage + &quot;}}\n&quot;;
o.secureCall( 'checkForFormerDR' );
page.watchlist = 'nochange';
var toAppend = $( '<div>' ).append( $( '<div>' ).attr( 'class', 'ajaxDeleteLazyLoad' ).css( {
page.editType = 'appendtext';
'max-height': Math.max( Math.round( $( window ).height() / 2 ) - 250, 100 ),
'min-height': 0,
overflow: 'auto'
} ).append( o.prevDRNode ), '<br>', lazyLoadNode );
 
this.showProgress(this.i18n.listingNomination);
o.prompt( [ {
message: '',
prefill: o.reason || '',
returnvalue: 'reason',
cleanUp: true,
noEmpty: true,
appendNode: toAppend,
parseReason: true
}
], o.i18n.reasonForDeletion );
} );
},
 
this.savePage(page, &quot;Listing [[&quot; + this.requestPage + &quot;]]&quot;, 'nextTask');
// 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 );
}
 
/**
} );
** Notify any uploaders/creators of this page using {{idw}}.
},
**/
notifyUploaders: function() {
this.uploadersToNotify = 0;
for (var user in this.uploaders) {
if (this.uploaders.hasOwnProperty(user)) {
if (user === mw.config.get('wgUserName')) continue; // notifying yourself is pointless
var page = {};
page.title = this.userTalkPrefix + user;
page.text = &quot;\n&quot; + this.talk_tag + &quot; ~~&quot; + &quot;~~\n&quot;;
page.editType = 'appendtext';
page.redirect = true;
if (window.AjaxDeleteWatchUserTalk) page.watchlist = 'watch';
this.savePage(page, this.talk_summary, 'uploaderNotified');
 
this.showProgress(this.i18n.notifyingUploader.replace('%USER%', user));
renderNode: function ( $node, remotecontent, selector ) {
if ( selector ) {
selector = ' ' + selector;
}
 
this.uploadersToNotify++;
$node.load( mw.util.wikiScript() + '?' + $.param( {
}
action: 'render',
}
title: remotecontent,
if (this.uploadersToNotify === 0) this.nextTask();
uselang: conf.wgUserLanguage
},
} ) + ( selector || '' ), function () {
$node.find( 'a' ).attr( 'href', function ( i, v ) {
return v.replace( 'MediaWiki:Anoneditwarning', conf.wgPageName );
} );
} );
return $node;
},
 
createLazyLoadNode uploaderNotified: function ( label, page, selector ) {
this.uploadersToNotify--;
return $( '<div>', { style: 'min-height:40px;' } ).append( $( '<a>', {
if (this.uploadersToNotify === 0) this.nextTask();
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
** Compile a list of uploaders to notify. Users who have only reverted the file to an
this.templateRegExp = $el.find( '.ctdr-regex' ).text();
** earlier version will not be notified.
** DONE: notify creator of non-file pages
**/
findCreator: function() {
var query;
if (namespaceNumber === 6) {
query = {
action: 'query',
prop: 'imageinfo|revisions|info',
rvprop: 'content|timestamp',
intoken: 'edit',
iiprop: 'user|sha1|comment',
iilimit: 50,
titles: this.pageName
};
 
} else {
var m = this.templateRegExp.match( /^\/(.+)\/(i)?$/ );
if ( !m || !m[ 1 ] ) query = {
action: 'query',
m = new Error( this.i18n.templateRegExp );
prop: 'info|revisions',
this.fail( m );
rvprop: 'user|timestamp',
throw m;
rvlimit: 1,
}
rvdir: 'newer',
this.templateRegExp = new RegExp( m[ 1 ], m[ 2 ] );
intoken: 'edit',
titles: this.pageName
};
}
this.showProgress(this.i18n.preparingToEdit);
this.doAPICall(query, 'findCreatorCB');
},
findCreatorCB: function(result) {
this.uploaders = {};
var pages = result.query.pages;
for (var id in pages) { // there should be only one, but we don't know its ID
if (pages.hasOwnProperty(id)) {
// The edittoken only changes between sessions
this.edittoken = pages[id].edittoken;
if (!pages[id].revisions) {
this.disableReport = true;
throw new Error('The page you are attempting to add a tag to was deleted or moved. Unable to retrieve the content.');
}
 
//First handle non-file pages
// …and the decline reason
if (namespaceNumber !== 6 || !pages[id].imageinfo) {
this.declineReason = $el.find( '.ctdr-template-decline-reason' ).text();
 
this.pageCreator = pages[id].revisions[0].user;
// …and the template name itself
this.starttimestamp = pages[id].starttimestamp;
m = $el.find( '.ctdr-template-name' ).text();
this.timestamp = pages[id].revisions[0].timestamp;
 
if (typeof this.pageCreator !== 'undefined') {
this.reason = 'This file was initially tagged by %USER%' + ( m ? ( ' as \'\'\'' + m + '\'\'\'' ) : '' );
this.uploaders[this.pageCreator] = true;
}
 
} else {
},
var info = pages[id].imageinfo;
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;
pgRevs.forEach( function ( rv ) {
var m = rv[ '*' ].match( AQD.templateRegExp );
if ( m ) {
user = rv.user;
if ( m.length > 1 && !template ) {
template = m[ 1 ];
}
 
var content = pages[id].revisions[0]['*'];
if ( m.length > 2 && !reason ) {
reason = m[ 2 ];
}
 
var seenHashes = {};
} else {
for (var i = info.length - 1; i &gt;= 0; i--) { // iterate in reverse order
return false;
if (info[i].sha1 &amp;&amp; seenHashes[info[i].sha1]) continue; // skip reverts
}
seenHashes[info[i].sha1] = true;
} );
// Now exclude bots which only reupload a new version:
} );
this.excludedBots = ['FlickreviewR', 'Rotatebot', 'Cropbot', 'Picasa Review Bot', 'Reedy RotateBot'];
if ( !user ) {
if (-1 !== $.inArray(info[i].user, this.excludedBots)) continue;
mw.log.warn( pgRevs );
throw new Error( this.i18n.findTemplateAdderErr );
}
this.reason = this.reason.replace( '%USER%', '[[User:' + user + '|' + user + ']]' );
if ( template ) {
this.reason += ' (' + template + ')';
}
 
// outsourced to [[MediaWiki:Gadget-libCommons.js]]
if ( reason ) {
var match = mw.libs.commons.getUploadBotUser(info[i].user, content, info[i].comment);
this.reason += ' and the most recent rationale was: <tt>' + reason + '</tt>';
if (match) {
}
this.uploaders[match] = true;
}
}
}
}
}
this.nextTask();
},
 
getMoveToken: function() {
this.nextTask();
var query = {
},
action: 'query',
prop: 'info|revisions|imageinfo',
rvprop: 'content|timestamp',
iiprop: 'mime',
intoken: 'edit|move',
titles: pageName
};
 
this.showProgress(this.i18n.preparingToEdit);
processDupes: function () {
this.doAPICall(query, 'getMoveTokenCB');
// reset task list in case an earlier error left it non-empty
},
this.initialize();
 
getMoveTokenCB: function(result) {
if ( $( '#globalusage' ).length || !$( '#mw-imagepage-nolinkstoimage' ).length ) {
var pages = result.query.pages;
this.inUse = true;
for (var id in pages) { // there should be only one, but we don't know its ID
}
if (pages.hasOwnProperty(id)) {
var pg = pages[id];
if (!pg.revisions) {
this.disableReport = true;
throw new Error('The page you are attempting to modify or move was deleted or moved. Unable to history and contents.');
}
// The edittoken only changes between sessions
this.edittoken = pg.edittoken;
this.movetoken = pg.movetoken;
this.pageContent = pg.revisions[0]['*'];
this.starttimestamp = pg.starttimestamp;
this.timestamp = pg.revisions[0].timestamp;
if (pg.imageinfo &amp;&amp; pg.imageinfo.length &amp;&amp; pg.imageinfo[0].mime) {
this.fileMime = pg.imageinfo[0].mime
.replace('image/jpeg', 'jpg')
.replace(/image\/(?:(png)|(gif)|x-(xcf)|vnd\.(djvu)|(svg)\+xml|(tif)f)/, '$1')
.replace(/application\/(ogg|pdf)/, '$1')
.replace('audio\/midi', 'mid');
if (this.fileMime.length &gt; 5) this.fileMime = '';
}
}
}
 
this.nextTask();
this.addTask( 'getDupeDetails' );
},
this.addTask( 'compareDetails' );
this.addTask( 'mergeDescriptions' );
doesFileExist: function() {
this.addTask( 'saveDescription' );
var toCheck = this.cleanFileName(this.destination).replace(/^File:/, '');
this.addTask( 'replaceUsage' );
var query = {
this.addTask( 'queryRedirects' );
'action': 'query',
this.addTask( 'deletePage' );
'list': 'allpages',
this.addTask( 'redirectPage' );
'apfrom': toCheck,
this.addTask( 'reloadPage' );
'apto': toCheck,
this.destination = $( '#AjaxDupeDestination' ).text();
'apnamespace': 6
this.nextTask();
};
},
this.showProgress(this.i18n.checkFileExists);
this.doAPICall(query, 'doesFileExistCB');
},
doesFileExistCB: function(result) {
if (!result || !result.query || !result.query.allpages) throw new Error('Checking file name: result.query.allpages is undefined.');
if (result.query.allpages[0]) {
if (this.fileNameExistsCB) this[this.fileNameExistsCB](result.query.allpages[0].title.replace(/^File:/, ''));
return;
}
this.nextTask();
},
 
getDupeDetails removeTemplate: function () {
var page = {};
this.queryAPI( {
this.replaceWith = (this.replaceWith || (this.templateRegExp ? '' : '$1$2' ));
curtimestamp: 1,
page.title = (this.destination || pageName);
meta: 'tokens',
page.text = $.trim(this.pageContent.replace((this.templateRegExp || /(?:([^\=])\n)?\{\{(?:rename|rename media|move)\|.*?\}\}(?:\n([^\=]))?/i), this.replaceWith));
prop: 'imageinfo|revisions|info',
page.editType = 'text';
rvprop: 'content|timestamp',
page.starttimestamp = this.starttimestamp;
inprop: 'watched',
page.timestamp = this.timestamp;
iiprop: 'sha1|size|url',
iiurlwidth: 365,
redirects: 1,
titles: pageName.replace( /_/g, ' ' ) + '|' + this.destination
}, 'getDupeDetailsCB' );
this.showProgress( 'Fetching details' );
},
 
this.showProgress(this.i18n.removingTemplate);
getDupeDetailsCB: function ( result ) {
this.savePage(page, (this.declineReason || &quot;Removing template; rename done&quot;), 'nextTask');
this.details = [];
},
if ( result ) {
var q = result.query,
id,
pg,
ii,
n,
pages = q.pages;
 
replaceUsage: function() {
for ( id in pages ) {
var page = {};
if ( Object.prototype.hasOwnProperty.call( pages, id ) ) {
page.title = 'User:CommonsDelinker/commands';
pg = pages[ id ];
if ( !pgthis.imageinfouserRights === 'filemover') {
page.title = 'User:CommonsDelinker/commands/filemovers';
// Nothing we can change so prevent users reporting
this.reason = this.reason.replace(/\{/g, '&amp;#123;').replace(/\}/g, '&amp;#125;').replace(/\=/g, '&amp;#61;');
this.disableReport = true;
}
throw new Error( ( ( pg.title.trim() === '{{{1}}}' ) ?
if (!this.details) this.reason = '[[COM:FR|File renamed]]: ' + this.reason.replace(/\[\[Commons:File[_ ]renaming[^\[\]]*\]\]:? ?/i, '');
this.i18n.dupeParaErr :
page.text = '\n{{universal replace|' + pageName.replace('File:', '') + '|' + this.destination.replace('File:', '') + '|reason=' + this.reason + '}}';
this.i18n.dupeExistErr.replace( '%TITLE%', pg.title ) ) + ' (pg.imageinfo is undefined)' );
page.editType = 'appendtext';
}
page.watchlist = 'nochange';
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;
 
this.showProgress(this.i18n.replacingUsage);
if ( pg.watched !== undefined ) {
this.savePage(page, 'universal replace: [[:' + pageName + ']] → [[:' + this.destination + ']]', 'nextTask');
this.pageWasWatched = true;
},
}
redirectPage: function() {
var page = {};
page.title = pageName;
page.text = '#REDIRECT [[' + this.destination + ']]';
page.editType = 'text';
 
this.showProgress(this.i18n.redirectingFile);
}
this.savePage(page, 'Redirecting to duplicate file', 'nextTask');
}
},
}
saveDescription: function() {
if ( this.details.length < 2 ) {
var page = {};
this.disableReport = true;
page.title = this.destination;
throw new Error( this.i18n.noPageFound );
page.text = this.newPageText;
}
page.editType = 'text';
// If order (old=0, new=1) is incorrect: Reverse
if ( this.details[ 0 ].title !== pageName.replace( /_/g, ' ' ) ) {
this.details.reverse();
}
 
this.showProgress(this.i18n.savingDescription);
this.nextTask();
this.savePage(page, 'Merging details from duplicate ([[' + pageName + ']])', '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 );
** Pseudo-Modal JS windows.
this.savePage( page, this.img_summary, 'nextTask' );
**/
},
prompt: function(questions, title, width) {
var o = this;
var dlgButtons = {};
dlgButtons[this.i18n.submitButtonLabel] = function() {
$.each(questions, function(i, v) {
var response = $('#AjaxQuestion' + i).val();
if (v.type === 'checkbox') response = $('#AjaxQuestion' + i).attr('checked');
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' &amp;&amp; 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);
AQD.img_summary = AQD.img_summary.replace('%PARAMETER-LINKED%', '[[:' + response + ']]');
}
});
$(this).dialog('close');
AQD.nextTask();
};
dlgButtons[this.i18n.cancelButtonLabel] = function() {
$(this).dialog('close');
};
 
var $submitButton, $cancelButton;
/**
var $AjaxDeleteContainer = $('&lt;div&gt;', {
* Edit the current page to add the specified tag and the changed content.
id: 'AjaxDeleteContainer'
*/
});
replaceTemplate: function ( text ) {
var page = {
var _convertToTextarea = function(e) {
title: this.destination || pageName,
var $el = $(this),
text: text || this.tag + this.pageContent,
$input = $el.data('toConvert'),
editType: 'text',
$tarea = $('&lt;textarea&gt;', { id: $input.attr('id'), style: 'height:10em; width:98%; display:none;' });
starttimestamp: this.starttimestamp,
timestamp: this.timestamp,
$el.off();
watchlist: window.AjaxDeleteWatchFile ? 'watch' : this.watchlist,
$el.fadeOut();
minor: false
$input.parent().prepend(
};
$tarea
.data('v', $input.data('v')).data('parserResultNode', $input.data('parserResultNode'))
.val($input.val()).keyup(_parseReason).on('keyup input', _validateInput));
$tarea.slideDown();
$input.remove();
};
var _parseReason = function(event) {
var $el = $(this),
parsertimeout = $el.data('parsertimeout'),
parserjqXHR = $el.data('parserjqXHR'),
$parserResultNode = $el.data('parserResultNode'),
delay = 1000;
 
if (!$parserResultNode) return;
this.templateReplace = false;
this.showProgress( this.i18n.addingAnyTemplate );
$parserResultNode.css('color', '#877');
this.savePage( page, ( this.declineReason || this.img_summary ), 'nextTask' );
},
 
parsertimeout = parsertimeout || 0;
/**
if (parserjqXHR) parserjqXHR.abort();
* 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>';
}
 
var gotJSON = function(d) {
this.showProgress( this.i18n.creatingNomination );
try {
this.savePage( page, this.subpage_summary, 'nextTask' );
$parserResultNode.html(d.parse.text['*']);
},
$parserResultNode.css('color', '#000');
} catch (ex) {}
};
 
var parseIt = function() {
/**
var toParse = $el.val();
* Transclude the nomination page onto today's DR log page, creating it if necessary.
if (!toParse || !/(?:&lt;|\/\/|\[|\'\{|~~)/.test(toParse)) {
* The log page will never be watchlisted (unless the user is already watching it).
gotJSON({
*/
parse: {
listRequestSubpage: function () {
text: {
var page = {};
'*': toParse || ''
page.title = this.dailyLogPage;
}
}
});
return;
}
var query = {
format: 'json',
action: 'parse',
uselang: mw.config.get('wgUserLanguage'),
redirects: true,
prop: 'text',
pst: true,
text: toParse
};
$el.data('parserjqXHR',
$.getJSON(mw.util.wikiScript('api'), query, function(text) {
gotJSON(text);
delay += 65;
})
);
};
clearTimeout(parsertimeout);
$el.data('parsertimeout',
setTimeout(parseIt, Math.min(3500, delay))
);
};
var _validateInput = function(event) {
var $el = $(this),
v = $el.data('v');
if (v.noEmpty) {
if ($.trim($el.val()).length &lt; (v.minLength || 10)) {
$submitButton.button('option', 'disabled', true);
} else {
$submitButton.button('option', 'disabled', false);
}
}
if (('TEXTAREA' !== $el.prop('nodeName')) &amp;&amp;
((event.keyCode - 0) === 13) &amp;&amp;
(v.enterToSubmit !== false) &amp;&amp;
!$submitButton.button('option', 'disabled')
) $submitButton.click();
};
 
$.each(questions, function(i, v) {
// Impossible when using appendtext. Shouldn't not be severe though, since DRBot creates those pages before they are needed.
v.type = (v.type || 'text');
// if (!page.text) page.text = "{{"+"subst:" + this.requestPagePrefix + "newday}}"; // add header to new log pages
if (v.type === 'textarea') {
page.text = '\n{{' + this.requestPage + '}}\n';
$AjaxDeleteContainer.append('&lt;label for=&quot;AjaxQuestion' + i + '&quot;&gt;' + v.message + '&lt;/label&gt;').append('&lt;textarea rows=20 id=&quot;AjaxQuestion' + i + '&quot;&gt;');
page.watchlist = 'preferences';
} else {
page.editType = 'appendtext';
$AjaxDeleteContainer.append('&lt;label for=&quot;AjaxQuestion' + i + '&quot;&gt;' + v.message + '&lt;/label&gt;').append('&lt;input type=&quot;' + v.type + '&quot; id=&quot;AjaxQuestion' + i + '&quot; style=&quot;width:97%;&quot;&gt;');
}
 
var curQuestion = $AjaxDeleteContainer.find('#AjaxQuestion' + i);
this.showProgress( this.i18n.listingNomination );
 
if (v.parseReason) {
this.savePage( page, 'Listing [[' + this.requestPage + ']]', 'nextTask' );
var $parserResultNode = $('&lt;div&gt;', {
},
id: 'AjaxQuestionParse' + i,
html: '&amp;nbsp;'
});
$AjaxDeleteContainer.append('&lt;br&gt;&lt;label for=&quot;AjaxQuestionParse' + i + '&quot;&gt;' + o.i18n.previewLabel + '&lt;/label&gt;').append($parserResultNode);
 
curQuestion.data('parserResultNode', $parserResultNode).keyup(_parseReason);
isMobile: function () {
}
var isMobile = false,
if (v.type !== 'textarea') $AjaxDeleteContainer.append('&lt;br&gt;&lt;br&gt;');
cats = conf.wgCategories;
if (v.appendNode) {
$AjaxDeleteContainer.append(v.appendNode);
}
if ('number' === typeof v.byteLimit) {
mw.loader.using('jquery.lengthLimit', function() {
curQuestion.byteLimit(v.byteLimit);
});
}
 
curQuestion.data('v', v);
for ( var i = 0, len = cats.length; i < len; i++ ) {
curQuestion.on('keyup input', _validateInput);
isMobile = isMobile || /^Uploaded with Mobile/.test( cats[ i ] );
}
// SECURITY: prefill could contain evil jsCode. Never use it unescaped!
// Use .val() or { value: prefill } or '&lt;input value=&quot;' + mw.html.escape() + '&quot; ...&gt;
curQuestion.val(v.prefill);
if (v.type === 'checkbox') curQuestion.attr('checked', v.prefill).attr('style', 'margin-left: 5px');
});
if (mw.user.isAnon()) {
AQD.renderNode($('&lt;div&gt;', { id: 'ajaxDeleteAnonwarning' }), 'MediaWiki:Anoneditwarning').appendTo($AjaxDeleteContainer);
}
 
var $dialog = $('&lt;div&gt;&lt;/div&gt;').append($AjaxDeleteContainer).dialog({
return isMobile;
width: (width || 600),
},
modal: true,
 
title: title,
listMobileUpload: function () {
dialogClass: &quot;wikiEditor-toolbar-dialog&quot;,
var page = {
close: function() {
title: 'Commons:Deletion requests/mobile tracking',
$(this).dialog(&quot;destroy&quot;);
text: '\n{{' + this.requestPage + '}}\n',
$(this).remove();
watchlist: 'preferences',
},
editType: 'appendtext'
buttons: dlgButtons,
};
open: function() {
 
// Look out for http://bugs.jqueryui.com/ticket/6830 / jQuery UI 1.9
this.showProgress( this.i18n.listingMobile );
var $buttons = $(this).parent().find('.ui-dialog-buttonpane button');
 
$submitButton = $buttons.eq(0).button({ icons: { primary: 'ui-icon-circle-check' } }).addClass('ui-button-green');
this.savePage( page, 'Listing [[' + this.requestPage + ']]', 'nextTask' );
$cancelButton = $buttons.eq(1).button({ icons: { primary: 'ui-icon-circle-close' } }).addClass('ui-button-red');
},
 
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';
}
});
 
$.each(questions, function(i, v) {
this.savePage( page, this.talk_summary, 'uploaderNotified' );
var curQuestion = $AjaxDeleteContainer.find('#AjaxQuestion' + i);
} else {
curQuestion.keyup();
this.uploaderNotified();
if (v.type === 'text') {
}
var $q = curQuestion.wrap('&lt;div style=&quot;position:relative;&quot;&gt;').parent();
},
var $i = $.createIcon('ui-icon-arrow-4-diag').attr('title', 'Expand to textarea');
$('&lt;span&gt;', { 'class': 'ajaxTextareaConverter' }).append($i).appendTo($q).data('toConvert', curQuestion).click(_convertToTextarea);
}
});
 
$('#AjaxQuestion0').focus().select();
/**
},
* 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();
}
 
/**
},
** Pseudo-Modal JS windows.
**/
compareDetails: function() {
var d = this.details[0],
f = this.details[1],
$submitButton, $inverseButton, $cancelButton, $swapButton, $overlayButton;
 
this.showProgress();
uploaderNotified: function () {
if (d.sha1 === f.sha1) {
this.uploadersToNotify--;
if ( ! this.uploadersToNotifyexactDupes )= {true;
this.nextTask();
return;
}
}
 
var $imgD = $('&lt;div&gt;').append($('&lt;img&gt;', {
},
src: d.thumburl,
height: d.thumbheight,
width: d.thumbwidth
}), $('&lt;div&gt;', {
id: 'AjaxDeleteImgDel',
html: Math.round(d.size / 1000) + ' KB &lt;br&gt;' + d.width + 'x' + d.height + '&lt;br&gt;'
}).append(
$('&lt;a&gt;', {
href: d.descriptionurl,
text: d.title,
target: '_blank'
})));
var $imgF = $('&lt;div&gt;').append($('&lt;img&gt;', {
src: f.thumburl,
height: f.thumbheight,
width: f.thumbwidth
}), $('&lt;div&gt;', {
id: 'AjaxDeleteImgKeep',
html: Math.round(f.size / 1000) + ' KB &lt;br&gt;' + f.width + 'x' + f.height + '&lt;br&gt;'
}).append(
$('&lt;a&gt;', {
href: f.descriptionurl,
text: f.title,
target: '_blank'
})));
var dlgButtons = {};
 
dlgButtons[this.i18n.submitButtonLabel] = function() {
/**
$(this).dialog(&quot;close&quot;);
* Compile a list of uploaders to notify. Users who have only reverted the file to an
AQD.nextTask();
* earlier version will not be notified.
};
* DONE: notify creator of non-file pages
dlgButtons[this.i18n.inverseButtonLabel] = function() {
* DONE: notify fileimporter (2019-09-09)
$(this).dialog(&quot;close&quot;);
*/
AQD.destination = pageName.replace(/_/g, ' ');
findCreator: function () {
pageName = f.title;
var q = {
AQD.details.reverse();
curtimestamp: 1,
setTimeout(function() {
meta: 'tokens',
AQD.compareDetails();
prop: 'revisions',
}, 10);
titles: this.pageName,
};
rvprop: 'timestamp|user',
dlgButtons[this.i18n.cancelButtonLabel] = function() {
rvdir: 'newer',
$(this).dialog(&quot;close&quot;);
rvslots: 'main',
};
rvlimit: 1
dlgButtons[this.i18n.swapImagesButtonLabel] = function() {
};
if ($imgD[0].nextSibling === $imgF[0]) {
 
$imgD.before($imgF);
if ( nsNr === 6 ) {
} else {
$.extend( q, {
$imgF.before($imgD);
prop: q.prop + '|imageinfo',
}
rvprop: q.rvprop + '|content',
};
iiprop: 'user|comment|sha1',
var $fClone;
iilimit: '50',
dlgButtons[this.i18n.overlayButtonLabel] = function() {
list: 'logevents',
if ($fClone) {
letype: 'import',
$fClone.remove();
leprop: 'user',
$fClone = 0;
letitle: this.pageName
} else {
} );
$fClone = $imgF.clone().appendTo($imgF.parent());
}
$fClone.css('position', 'absolute');
 
var pos = $imgD.position();
this.showProgress( this.i18n.preparingToEdit );
$fClone.css('top', pos.top - 1);
this.queryAPI( q, 'findCreatorCB' );
$fClone.css('left', pos.left - 1);
},
$fClone.fadeTo(0, 0.65);
 
// These modules should be already loaded for the dialog but let's be sure
findCreatorCB: function ( r ) {
mw.loader.using(['jquery.ui'], function() {
this.uploaders = {};
// Set width to auto because AjaxQuickDelete.css sets it to a fixed size
 
$fClone.css('background', 'rgba(200, 200, 200, 0.5)').css('width', 'auto').css('border', '1px solid #0c9').draggable();
if ( !r || !r.query || !r.query.pages ) {
$fClone.find('img').resizable();
this.disableReport = true;
// In IE, opacity is not fully inerhited
throw new Error( this.i18n.noPageFound );
$fClone.children('div').fadeTo(0, 0.7);
}
});
var q = r.query,
}
pg = _firstItem( q.pages ),
};
rv;
var $AjaxDupeContainer = $('&lt;div&gt;', {
 
id: 'AjaxDupeContainer'
if ( !pg || !pg.revisions ) {
}).append($imgD, $imgF);
throw new Error( this.i18n.noCreatorFound );
var $dialog = $('&lt;div&gt;&lt;/div&gt;').append($AjaxDupeContainer).dialog({
}
width: 800,
 
modal: true,
// The csrftoken only changes between sessions
title: this.i18n.compareDetails,
this.csrftoken = q.tokens.csrftoken;
draggable: false,
rv = pg.revisions[ 0 ];
dialogClass: &quot;wikiEditor-toolbar-dialog&quot;,
 
close: function() {
// First handle non-file pages
$(this).dialog(&quot;destroy&quot;);
if ( nsNr !== 6 || !pg.imageinfo ) {
$(this).remove();
this.starttimestamp = r.curtimestamp;
},
this.timestamp = rv.timestamp;
buttons: dlgButtons,
 
open: function() {
if ( ( this.pageCreator = rv.user ) ) {
var $buttons = $(this).parent().find('.ui-dialog-buttonpane button');
this.uploaders[ this.pageCreator ] = true;
$submitButton = $buttons.eq(0).button({ icons: { primary: 'ui-icon-circle-check' } }).addClass('ui-button-green');
$inverseButton = $buttons.eq(1).button({ icons: { primary: 'ui-icon-refresh' } });
$cancelButton = $buttons.eq(2).button({ icons: { primary: 'ui-icon-circle-close' } }).addClass('ui-button-red');
$swapButton = $buttons.eq(3).button({ icons: { primary: 'ui-icon-transfer-e-w' } });
$overlayButton = $buttons.eq(4).button({ icons: { primary: 'ui-icon-newwin' } });
$swapButton.css('float', (('left' === $swapButton.css('float')) ? 'right' : 'left'));
$overlayButton.css('float', (('left' === $overlayButton.css('float')) ? 'right' : 'left'));
}
});
},
 
mergeDescriptions: function() {
} else {
this.prompt([{
var info = pg.imageinfo,
message: '',
i = info.length,
prefill: this.details[0].content,
content = rv.slots.main[ '*' ],
returnvalue: 'discard',
seenHashes = {};
cleanUp: false,
// Iterate in reverse order (sha1 check)
noEmpty: false,
for ( --i; i >= 0; i-- ) {
type: 'textarea',
var iii = info[ i ],
enterToSubmit: false
rev = seenHashes[ iii.sha1 ];
}, {
// Skip reverts and remove users
message: '',
if ( iii.sha1 && rev ) {
prefill: this.details[1].content,
for ( rev; rev > i; rev-- ) {
returnvalue: 'newPageText',
var u = info[ rev - 1 ].user;
cleanUp: false,
if ( this.uploaders[ u ] < rev ) {
noEmpty: false,
delete this.uploaders[ u ];
type: 'textarea',
}
enterToSubmit: false
}], this.i18n.mergeDescription, 800);
this.destination = this.details[1].title;
this.reason = 'Exact or scaled-down duplicate: [[:' + this.destination + ']]';
},
 
cleanFileName: function(uncleanName) {
}
// Remove Namespace
continue;
uncleanName = uncleanName.replace(/^(?:Image|File):/i, '');
}
// Convert extension to lower case
seenHashes[ iii.sha1 ] = i + 1;
uncleanName = uncleanName.replace(/\.\w{3,4}$/, function($e) { return $e.toLowerCase(); });
// No need to check again the whole shebang
// jpeg -&gt; jpg
if ( this.uploaders[ iii.user ] ) {
uncleanName = uncleanName.replace(/\.jpe*g$/, '.jpg');
continue;
}
// First cleanUp from Flinfo (FlinfoOut.php) by Flominator and Lupo
uncleanName = uncleanName.replace(/~{3,}/g, '')
.replace(/\s+|_/g, ' ')
.replace(/[\x00-\x1f\x7f]/g, '')
.replace(/%([0-9A-Fa-f]{2})/g, '% $1')
.replace(/&amp;(([A-Za-z0-9\x80-\xff]+|#[0-9]+|#x[0-9A-Fa-f]+);)/g, '&amp; $1')
.replace(/[:\/|#]/g, '-')
.replace(/[\]\}&gt;]/g, ')')
.replace(/[\[\{&lt;]/g, '(');
 
var currentExt = pageName.toLowerCase().replace(/.*?\.(\w{3,4})$/, '$1').replace('jpeg', 'jpg');
// Now exclude bots which only reupload a new version:
// If the current mime-type is available to the script, check it;
if ( mw.libs.commons.isSmallChangesBot( iii.user ) ) {
// MediaWiki sometimes allows uploading mismatching mimetypes but not moving
continue;
if (this.fileMime) {
}
currentExt = ('ogg' === this.fileMime &amp;&amp; ('oga' === currentExt || 'ogv' === currentExt)) ? currentExt : this.fileMime;
}
var reCurrentExt = new RegExp('\\.' + currentExt + '$', 'i');
 
// If new file name is without extension, add the one from the old name
// Outsourced to MediaWiki:Gadget-libCommons.js
if (!reCurrentExt.test(uncleanName.toLowerCase())) uncleanName += '.' + currentExt;
iii = mw.libs.commons.getUploadBotUser( iii.user, content, iii.comment, rv.user );
// Capitalize the first letter and prefix the namespace
if ( iii ) {
return 'File:' + uncleanName.replace(/^\w/, function($0) { return $0.toUpperCase(); });
this.uploaders[ iii ] = i + 1;
},
}
cleanReason: function(uncleanReason) {
// trim whitespace
uncleanReason = uncleanReason.replace(/^\s*(.+)\s*$/, '$1');
// remove signature
uncleanReason = uncleanReason.replace(/(?:\-\-|–|—)? ?~{3,5}$/, '').replace(/^~{3,5} ?/, '');
return uncleanReason;
},
 
/**
}
** For display of progress messages.
// Get fileimporter
**/
if ( ( info = q.logevents ) && ( i = info.length ) ) {
showProgress: function(message) {
for ( --i; i >= 0; i-- ) {
if (!message) {
if ( info[ i ].user ) {
if (this.progressDialog) this.progressDialog.remove();
this.uploaders[ info[ i ].user ] = true;
this.progressDialog = 0;
}
document.body.style.cursor = 'default';
}
return;
}
if ($('#feedbackContainer').length) {
$('#feedbackContainer').html(message);
} else {
document.body.style.cursor = 'wait';
 
this.progressDialog = $('&lt;div&gt;&lt;/div&gt;').html('&lt;div id=&quot;feedbackContainer&quot;&gt;' + (message || this.i18n.preparingToEdit) + '&lt;/div&gt;').dialog({
}
width: 450,
height: 90,
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(&quot;destroy&quot;);
$(this).remove();
}
});
}
 
},
}
/**
this.nextTask();
** Submit an edited page.
},
**/
savePage: function(page, summary, callback) {
var edit = {
action: 'edit',
summary: summary,
watchlist: (page.watchlist || 'preferences'),
title: page.title
};
if (page.redirect) edit.redirect = '';
edit[page.editType] = page.text;
this.doAPICall(edit, callback);
},
 
getMoveToken movePage: function () {
// Some users don't get it: They want to move pages to itself.
this.showProgress( this.i18n.preparingToEdit );
if (AQD.cleanFileName(pageName) === AQD.destination) return AQD.nextTask();
var query = {
mw.loader.using(['ext.gadget.libAPI'], function() {
curtimestamp: 1,
mw.user.tokens.set('moveToken', AQD.movetoken);
prop: 'info|revisions',
var moveArgs = {
meta: 'tokens',
cb: function() {
rvprop: 'content|timestamp',
AQD.nextTask();
inprop: 'watched',
},
titles: this.pageName || pageName.replace( /_/g, ' ' )
// r-result, query, text
};
errCb: function(r, q, t) {
if ( !this.declineReason ) {
AQD.fail(t);
query.prop += '|imageinfo';
},
query.iiprop = 'mediatype|mime|timestamp';
from: pageName,
}
to: AQD.destination,
this.queryAPI( query, 'getMoveTokenCB' );
reason: AQD.reason,
},
movetalk: true
};
// Option to not leave a redirect behind, MediaWiki default does leave one behind
// Just like movetalk, an empty parameter sets it to true (true to not leave a redirect behind)
if (AQD.wpLeaveRedirect === false) {
moveArgs.noredirect = true;
}
AQD.showProgress(AQD.i18n.movingFile);
mw.libs.commons.api.movePage(moveArgs);
});
},
 
deletePage: function() {
/**
var edit = {
* @brief [callback] Prepare page for saving before
action: 'delete',
* @param [in] result of query
reason: this.reason,
* @return csrftoken, pageContent, starttimestamp, timestamp, imagetimestamp, mimeFileExtension, pageWasWatched
title: pageName,
*/
token: this.deletetoken,
getMoveTokenCB: function ( result ) {
recreate: ''
var q = result.query || {},
};
pg = _firstItem( q.pages );
this.showProgress(this.i18n.deletingFile);
if ( !pg || !pg.revisions ) {
this.doAPICall(edit, 'nextTask');
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
} );
 
setCurrentDate: function(x) {
if ( pg.watched !== undefined ) {
var shortNames = [&quot;Jan&quot;,&quot;Feb&quot;,&quot;Mar&quot;,&quot;Apr&quot;,&quot;May&quot;,&quot;Jun&quot;,&quot;Jul&quot;,&quot;Aug&quot;,&quot;Sep&quot;,&quot;Oct&quot;,&quot;Nov&quot;,&quot;Dec&quot;];
this.pageWasWatched = true;
try {
}
var dat = x.getResponseHeader('date').match(/\D+(\d\d) (\D{3}) (\d{4}) (\d\d):(\d\d):(\d\d)/);
this.currentDate = new Date(dat[3], $.inArray(dat[2], shortNames), dat[1], dat[4], dat[5], dat[6]);
// The date is initialized/ constructed in local time but the server returned GMT-Time, so remove the offset
// According to w3c under- and overflow (&lt;0, &gt;60) are handled by the date-object itself
this.currentDate.setMinutes(this.currentDate.getMinutes() - this.currentDate.getTimezoneOffset());
} catch (ex) {
this.currentDate = this.startDate || new Date();
}
},
 
/**
var ii = pg.imageinfo;
** Does a MediaWiki API request and passes the result to the supplied callback (method name).
if ( ii && ii.length && ii[ 0 ].mime ) {
** Uses POST requests for everything for simplicity.
ii = ii[ 0 ];
**/
this.imagetimestamp = ii.timestamp;
doAPICall: function(params, callback) {
this.mimeFileExtension = ii.mime
var o = this,
.toLowerCase()
newParams = { format: 'json' };
.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();
},
 
// At least let's try to send the format first and the token last
doesFileExist: function () {
// If the POST-request is cut off, we get &quot;invalid token&quot; or other errors
if ( !this.destination ) {
$.extend(newParams, params);
// eslint-disable-next-line no-alert
if ('edit' === newParams.action) newParams.token = this.edittoken;
return alert( this.i18n.moveDestination );
}
var retry = function(timeout, errText) {
this.destination = this.cleanFileName( this.destination );
o.apiErrorThreshold--;
var query = {
if (0 === o.apiErrorThreshold) {
prop: 'info|revisions',
return o.fail(errText);
titles: this.destination,
} else {
rvprop: 'content',
return setTimeout(function () {
rvlimit: 2
o.doAPICall(params, callback);
};
}, timeout);
// 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
};
$.ajax({
url: this.apiURL,
cache: false,
dataType: 'json',
data: newParams,
type: 'POST',
success: function(result, status, x) {
if (!o.currentDate &amp;&amp; x &amp;&amp; x.getResponseHeader) o.setCurrentDate(x);
if (!result &amp;&amp; 'query' === newParams.action) return retry(1500, &quot;Received empty API response:\n&quot; + x.responseText);
if (!result) return o.fail(&quot;Received empty API response:\n&quot; + x.responseText);
 
// In case we get the mysterious 231 unknown error, just try again
this.showProgress( this.i18n.checkFileExists );
if (result.error &amp;&amp; result.error.info.indexOf('231') !== -1) return retry(500, &quot;mysterious 231 unknown error&quot;);
this.queryAPI( query, 'doesFileExistCB' );
},
if (result.error &amp;&amp; 'editconflict' === result.error.code &amp;&amp; (params.prependtext || params.appendtext)) return retry(750, &quot;edit conflict&quot;);
if (result.error) {
// In some cases, we just don't want to know. If users have protected their talk-page it's their problem.
if (-1 !== $.inArray(result.error.code, ['protectedpage', 'missingtitle'])) this.disableReport = true;
return o.fail(&quot;API request failed (&quot; + result.error.code + &quot;): &quot; + result.error.info);
}
if (result.edit &amp;&amp; result.edit.spamblacklist) {
return o.fail(&quot;The edit failed because &quot; + result.edit.spamblacklist + &quot; is on the Spam Blacklist&quot;);
}
try {
o[callback](result);
} catch (e) {
return o.fail(e);
}
},
error: function(x, status, error) {
if ('query' === newParams.action) return retry(1500, &quot;API request returned code &quot; + x.status + &quot; &quot; + status + &quot;. Error code is &quot; + error);
return o.fail(&quot;API request returned code &quot; + x.status + &quot; &quot; + status + &quot;. Error code is &quot; + error);
}
});
},
 
/**
** Simple task queue. addTask() adds a new task to the queue, nextTask() executes
* Return nextTask if the page does not exist
** the next scheduled task. Tasks are specified as method names to call.
* or it is a redirect with one revision to the source
**/
tasks: [],
doesFileExistCB: function ( result ) {
// list of pending tasks
if ( !result || !result.query || !result.query.pages ) {
currentTask: '',
throw new Error( 'Checking filename: result.query.pages is undefined. ' + this.destination );
// current task, for error reporting
}
addTask: function(task) {
this.tasks.push(task);
},
nextTask: function() {
var task = this.currentTask = this.tasks.shift();
try {
this[task]();
} catch (e) {
this.fail(e);
}
},
retryTask: function() {
try {
this[this.currentTask]();
} catch (e) {
this.fail(e);
}
},
 
/**
var exists = true,
** Once we're all done, reload the page.
pg = _firstItem( result.query.pages ),
**/
getRedirRegExp = function ( title ) {
reloadPage: function() {
title = title.replace( /^(File|Image):/, '' ).replace( /_/g, ' ' );
this.showProgress();
return new RegExp( '^\\s*#REDIRECT\\s*\\[\\[File\\:[' +
if (this.pageName &amp;&amp; this.pageName.replace(/ /g, '_') !== pageName) return;
mw.util.escapeRegExp( title[ 0 ].toUpperCase() ) + mw.util.escapeRegExp( title[ 0 ].toLowerCase() ) + ']' +
var encTitle = (this.destination || pageName);
mw.util.escapeRegExp( title.slice( 1 ) ).replace( / /g, '[ _]' ) +
encTitle = encodeURIComponent(encTitle.replace(/ /g, '_')).replace(/%2F/ig, '/').replace(/%3A/ig, ':');
'\\s*\\]\\]',
location.href = mw.config.get('wgServer') + mw.config.get('wgArticlePath').replace(&quot;$1&quot;, encTitle);
'' );
},
};
 
/**
if ( pg.missing !== undefined ) {
** Error handler. Throws an alert at the user and give him
exists = false;
** the possibility to retry or autoreport the error-message.
} else if ( !pg.revisions || pg.revisions.length === 1 && getRedirRegExp( pageName )
**/
.test( pg.revisions[ 0 ][ '*' ].replace( 'Image:', 'File:' ) ) ) {
fail: function(err) {
// There seems to be no way to find out whether a title is a redirect
var o = this;
// and whether the redirect only consists of one revision
if (typeof err === 'object') {
exists = false;
var stErr = err.message + ' \n\n ' + err.name;
}
if (err.lineNumber) stErr += ' @line' + err.lineNumber;
err = stErr;
}
 
var msg = this.i18n.taskFailure[this.currentTask] || this.i18n.genericFailure;
if ( exists ) {
if ( this.fileNameExistsCB ) {
this[ this.fileNameExistsCB ]( pg.title.replace( /^File:/, '' ) );
}
return;
}
this.nextTask();
},
 
//TODO: Needs cleanup
removeTemplate: function () {
var fix = '';
this.replaceWith = ( this.replaceWith || ( this.templateRegExp ? '' : '$1$2' ) );
if (this.img_summary === 'Nominating for deletion') {
// Remove the template from the text. In case there is an empty line before, remove this also.
fix = (this.templateAdded ? this.i18n.completeRequestByHand : this.i18n.addTemplateByHand);
var newText = this.pageContent
}
.replace(
( this.templateRegExp || /(?:([^=])\n)?\{\{(?:rename|rename media|move)\s*\|.*?\}\}(?:\n([^=]))?/i ),
this.replaceWith
);
if ( newText === this.pageContent ) {
return this.nextTask();
}
 
var dlgButtons = {};
this.showProgress( this.i18n.removingTemplate );
dlgButtons[this.i18n.retryButtonLabel] = function() {
newText = this.pageContent = newText.trim();
$(this).remove();
o.retryTask();
};
if (-1 !== $.inArray(o.currentTask, ['movePage', 'deletePage', 'notifyUploaders']) &amp;&amp; (/code 50\d/.test(err) || /missingtitle/.test(err))) {
dlgButtons[this.i18n.ignoreButtonLabel] = function() {
$(this).remove();
o.nextTask();
};
}
if (!this.disableReport) {
dlgButtons[this.i18n.reportButtonLabel] = function() {
$('#feedbackContainer').contents().remove();
$('#feedbackContainer').append($('&lt;img&gt;', {
src: '/w/skins/common/images/ajax-loader.gif'
})).css('text-align', 'center');
var randomId = Math.round(Math.random()*1099511627776);
var toSend = '\n== Autoreport by AjaxQuickDelete ' + randomId + ' ==\n' + err + '\n++++\n:Task: ' + o.currentTask + '\n:NextTask: ' + o.tasks[0] + '\n:LastTask: ' + o.tasks[o.tasks.length - 1] +
'\n:Page: ' + (o.pageName || pageName) + '\n:Skin: ' + mw.user.options.get('skin') + '\n:[{{fullurl:Special:Contributions|target=Dsvyas&amp;offset=20210806052332}} Contribs before error]';
$.post(o.apiURL, {
'action': 'edit',
'format': 'json',
'title': 'MediaWiki talk:Gadget-AjaxQuickDelete.js/auto-errors',
'summary': '[[#Autoreport by AjaxQuickDelete ' + randomId + '|Reporting an AjaxQuickDelete error.]] Random ID=' + randomId,
'appendtext': toSend,
'token': (o.edittoken || mw.user.options.get('csrfToken'))
}, function() {
o.reloadPage();
});
};
}
dlgButtons[this.i18n.abortButtonLabel] = function() {
$(this).remove();
};
this.disableReport = false;
this.showProgress();
this.progressDialog = $('&lt;div&gt;').append($('&lt;div&gt;', {
id: 'feedbackContainer',
html: (msg + ' ' + fix + '&lt;br&gt;' + this.i18n.errorDetails + '&lt;br&gt;' + mw.html.escape(err) + '&lt;br&gt;' + (this.tag ? (this.i18n.tagWas + this.tag) : '') + '&lt;br&gt;&lt;a href=&quot;' + mw.config.get('wgServer') + '/wiki/MediaWiki_talk:AjaxQuickDelete.js&quot; &gt;' + this.i18n.errorReport + '&lt;/a&gt;')
})).dialog({
width: 550,
modal: true,
closeOnEscape: false,
title: this.i18n.errorDlgTitle,
dialogClass: &quot;ajaxDeleteError&quot;,
buttons: dlgButtons,
close: function() {
$(this).dialog(&quot;destroy&quot;);
$(this).remove();
}
});
},
 
/**
// We have another save request
** Very simple date formatter. Replaces the substrings &quot;YYYY&quot;, &quot;MM&quot; and &quot;DD&quot; in a
if ( this.templateReplace ) {
** given string with the UTC year, month and day numbers respectively.
this.img_summary = this.declineReason;
** Also replaces &quot;MON&quot; with the English full month name and &quot;DAY&quot; with the unpadded day.
return this.nextTask();
**/
}
formatDate: function(fmt, date) {
if ( !this.declineReason ) {
var pad0 = function(s) {
this.img_summary = 'Removing template; rename done';
s = &quot;&quot; + s;
}
return (s.length &gt; 1 ? s : &quot;0&quot; + s);
}; // zero-pad to two digits
if (!date) date = this.currentDate || this.startDate;
fmt = fmt.replace(/YYYY/g, date.getUTCFullYear());
fmt = fmt.replace(/MM/g, pad0(date.getUTCMonth() + 1));
fmt = fmt.replace(/DD/g, pad0(date.getUTCDate()));
fmt = fmt.replace(/MON/g, mw.config.get('wgMonthNames')[date.getUTCMonth() + 1]);
fmt = fmt.replace(/DAY/g, date.getUTCDate());
return fmt;
},
 
// Constants
// If nothing remains, add the no-license-template (this also to prevent abuse filter blocking this edit because of page blanking)
// DR subpage prefix
this.replaceTemplate( newText || '{{subst:nld}}' );
requestPagePrefix: &quot;વિકિપીડિયા:દૂર કરવા વિનંતી/&quot;,
},
// user talk page prefix
userTalkPrefix: mw.config.get('wgFormattedNamespaces')[3] + &quot;:&quot;,
// MediaWiki API script URL
apiURL: mw.util.wikiScript('api'),
// Max number of errors that are allowed for silent retry
apiErrorThreshold: 10,
 
replaceUsage: function () {
if ( !this.inUse ) {
return this.nextTask();
}
 
// Translatable strings
this.showProgress( this.i18n.replacingUsage );
i18n: {
var reasonShort = '[[COM:Duplicate|Duplicate]]:';
toolboxLinkDelete: &quot;પાનું દૂર કરવા વિનંતી&quot;,
toolboxLinkDiscuss: &quot;Nominate category for discussion&quot;,
 
// GUI reason prompt form
if ( !this.details ) {
reasonForDeletion: &quot;શા માટે આ પાનું દૂર કરવું?&quot;,
AQD.reason = AQD.reason.replace( /\[\[Commons:File[_ ]renaming[^[\]]*\]\]:? ?/i, '' );
reasonForDiscussion: &quot;Why does this category need discussion?&quot;,
reasonShort = '[[COM:FR|File renamed]]:';
moreInformation: &quot;More information&quot;,
}
loading: &quot;Loading...&quot;,
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 );
 
// Labels
} );
previewLabel: &quot;Preview:&quot;,
} else {
submitButtonLabel: &quot;Proceed&quot;,
mw.libs.globalReplaceDelinker( pageName, AQD.destination, reasonShort + ' ' + AQD.reason, function () {
cancelButtonLabel: &quot;Cancel&quot;,
AQD.nextTask();
abortButtonLabel: &quot;Abort&quot;,
}, function ( err ) {
reportButtonLabel: &quot;Report automatically&quot;,
throw new Error( err );
retryButtonLabel: &quot;Retry&quot;,
} );
ignoreButtonLabel: &quot;Ignore and continue&quot;,
}
inverseButtonLabel: &quot;Inverse. Keep this delete other&quot;,
} );
swapImagesButtonLabel: &quot;Swap to compare&quot;,
},
overlayButtonLabel: &quot;Overlay to compare&quot;,
redirectPage: function () {
var page = {
title: pageName,
text: '#REDIRECT [[' + this.destination + ']]',
editType: 'text',
watchlist: AQD.pageWasWatched ? 'watch' : 'preferences'
};
 
// GUI progress messages
this.showProgress( this.i18n.redirectingFile );
preparingToEdit: &quot;Preparing to edit pages... &quot;,
this.savePage( page, 'Redirecting to duplicate file', 'nextTask' );
creatingNomination: &quot;Creating nomination page... &quot;,
},
listingNomination: &quot;Adding nomination page to daily list... &quot;,
saveDescription: function () {
addingAnyTemplate: &quot;Adding template to &quot; + canonicalNS.toLowerCase() + &quot; page... &quot;,
var page = {
notifyingUploader: &quot;Notifying %USER%... &quot;,
title: this.destination,
text: this.newPageText,
editType: 'text',
watchlist: AQD.pageWasWatched ? 'watch' : 'preferences'
};
 
// Extended version
this.showProgress( this.i18n.savingDescription );
toolboxLinkSource: &quot;No source&quot;,
this.savePage( page, 'Merging details from duplicate ([[' + pageName + ']])', 'nextTask' );
toolboxLinkLicense: &quot;No license&quot;,
},
toolboxLinkPermission: &quot;No permission&quot;,
toolboxLinkCopyvio: &quot;Report copyright violation&quot;,
reasonForCopyvio: &quot;Why is this file a copyright violation?&quot;,
 
// For moving files
/**
notAllowed: &quot;You do not have the neccessary rights to move files&quot;,
* Updates the redirects to the current page
reasonForMove: &quot;Why do you want to move this file?&quot;,
* when moving or processing dupes immediately
moveDestination: &quot;What should be the new file name?&quot;,
* to prevent double redirects
moveOtherDestination: &quot;The name you have specified exists. Choose a new name, please.&quot;,
*/
checkFileExists: &quot;Checking whether file exists&quot;,
queryRedirects: function () {
movingFile: &quot;Moving file&quot;,
mw.loader.using( 'mediawiki.api' ).then( function () {
replacingUsage: &quot;Ordering CommonsDelinker to replace all usage&quot;,
return new mw.Api().loadMessagesIfMissing( [
dropdownMove: &quot;Move &amp; Replace&quot;,
'Whatlinkshere'
leaveRedirect: &quot;Leave a redirect behind:&quot;,
] );
moveAndReplace: &quot;Move file and replace all usage&quot;,
} ).then( function () {
AQD.showProgress( mw.msg( 'Whatlinkshere' ) );
// For declining any request
// TODO: Replace also the redirects inclusions!?
removingTemplate: &quot;Removing template&quot;,
AQD.queryAPI( {
declineRequest: &quot;Why do you want to decline the request?&quot;,
generator: 'backlinks',
anyDecline: &quot;Decline request&quot;,
gblfilterredir: 'redirects',
prop: 'revisions',
rvprop: 'content',
gbltitle: AQD.pageName || pageName.replace( /_/g, ' ' )
}, AQD.queryRedirectsCB ?
AQD.queryRedirectsCB :
'updateRedirects' );
} );
},
 
//For Duplicates
updateRedirects: function ( result ) {
deletingFile: &quot;Deleting file&quot;,
AQD.redirectsToUpdate = 0;
compareDetails: &quot;Please compare the images before merging the descriptions. The image with the bold text will be deleted.&quot;,
if ( result.query && result.query.pages ) {
mergeDescription: &quot;Please now merge the file descriptions&quot;,
this.showProgress( this.i18n.updRedir );
redirectingFile: &quot;Redirecting file&quot;,
$.each( result.query.pages, function ( id, pg ) {
savingDescription: &quot;Saving new details&quot;,
var rv = pg.revisions[ 0 ];
if ( !rv || !rv[ '*' ] ) {
return;
}
 
// Errors
// Update only redirects with same mimetype
errorDlgTitle: &quot;Error&quot;,
if ( AQD.checkFileExt( pg.title, AQD.destination, true ) ) {
genericFailure: &quot;An error occurred while trying to do the requested action. &quot;,
return mw.log( 'Redirect skipped, not same mimetype.', pg.title );
taskFailure: {
}
listUploaders: &quot;An error occurred while determining the &quot; + (namespaceNumber === 6 ? &quot; uploader(s) of this file&quot; : &quot;creator of this page&quot;) + &quot;.&quot;,
 
loadPages: &quot;An error occurred while preparing to nominate this &quot; + canonicalNS.toLowerCase() + &quot; for deletion.&quot;,
var page = {
prependDeletionTemplate: &quot;An error occurred while adding the {{delete}} template to this &quot; + canonicalNS.toLowerCase() + &quot;.&quot;,
title: pg.title,
createRequestSubpage: &quot;An error occurred while creating the request subpage.&quot;,
text: rv[ '*' ].replace( /#\s*REDIRECT\s*\[\[.+/, '#REDIRECT [[' + AQD.destination + ']]' ),
listRequestSubpage: &quot;An error occurred while adding the deletion request to today's log.&quot;,
editType: 'text',
notifyUploaders: &quot;An error occurred while notifying the &quot; + (namespaceNumber === 6 ? &quot; uploader(s) of this file&quot; : &quot;creator of this page&quot;) + &quot;.&quot;,
watchlist: 'preferences'
movePage: &quot;Error while moving the page.&quot;,
};
deletePage: &quot;Error deleting the page.&quot;
 
},
AQD.savePage( page, 'Updating redirect while processing [[' + pageName.replace( /_/g, ' ' ) + ']]', 'updateRedirectsCB' );
addTemplateByHand: &quot;To nominate this &quot; + canonicalNS.toLowerCase() + &quot; for deletion, please edit the page to add the {{delete}} template and follow the instructions shown on it.&quot;,
AQD.redirectsToUpdate++;
completeRequestByHand: &quot;Please follow the instructions on the deletion notice to complete the request.&quot;,
} );
errorDetails: &quot;A detailed description of the error is shown below:&quot;,
}
errorReport: &quot;Manually report the error here or click on &lt;tt&gt;Report automatically&lt;/tt&gt; to send an automatic error-report.&quot;,
if ( !AQD.redirectsToUpdate ) {
tagWas: &quot;The tag to be inserted into this page was &quot;
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: '&nbsp;'
} );
$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' ) {
mw.util.$content.find( '.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, '&#124; ' ); // 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 administrator 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.'
}
};
 
if (mw.config.get('wgUserLanguage') !== 'en') {
AQD.preinstall();
$.ajax({
 
url: mw.util.wikiScript(),
if ( conf.wgUserLanguage !== 'en' ) {
dataType: 'script',
$.when( mw.loader.getScript( mw.util.wikiScript() +
data: {
'?title=MediaWiki:Gadget-AjaxQuickDelete.js/' +
title: 'MediaWiki:Gadget-AjaxQuickDelete.js/' + mw.config.get('wgUserLanguage') + '.js',
conf.wgUserLanguage + '.js&action=raw&ctype=text/javascript'
action: 'raw',
), $.ready ).always( function () { AQD.install(); } );
ctype: 'text/javascript',
// Allow caching for 28 days
maxage: 2419200,
smaxage: 2419200
},
cache: true,
success: AQD.install,
error: AQD.install
});
} else {
AQD.install();
$( function () {
AQD.install();
} );
}
 
 
}() );
}(jQuery, mediaWiki));
// </nowiki>
// &lt;/nowiki&gt;</text>