MediaWiki:Gadget-ajaxQuickDelete.js
Перейти к навигации
Перейти к поиску
JS-код ниже относится к гаджету «Добавить ссылки для пометки проблемных файлов (бета)» (править описание). Связанный CSS-файл: MediaWiki:Gadget-ajaxQuickDelete.css. Его использует около 2000 учётных записей.
После сохранения или недавних изменений очистите кэш браузера.
// Original code written by [[User:Ilmari Karonen]]
// Rewritten & extended by [[User:DieBuche]]. Botdetection and encoding fixer by [[User:Lupo]]
//
// Ajax-based replacement for [[MediaWiki:Quick-delete-code.js]]
//<nowiki>
if ( typeof( AjaxQuickDelete ) === 'undefined' ) {
window.AjaxQuickDelete = {
/**
** Set up the AjaxQuickDelete object and add the toolbox link. Called via $( document ).ready() during page loading.
**/
install: function ( ) {
this.insertTagButtons = [{
label: this.i18n.toolboxLinkAuthor,
tag: '{\{subst:nad}}',
talkTag: '{\{subst:Запрос о статусе файла|1=%FILE%}}',
imgSummary: 'не указан автор файла',
talkSummary: '[[ВП:ППФ|автоматическое]] уведомление об отсутствии автора для %FILE%'
}, {
label: this.i18n.toolboxLinkSource,
tag: '{\{subst:nsd}}',
talkTag: '{\{subst:Запрос о статусе файла|1=%FILE%}}',
imgSummary: 'не указан источник файла',
talkSummary: '[[ВП:ППФ|автоматическое]] уведомление об отсутствии источника для %FILE%'
}, {
label: this.i18n.toolboxLinkLicense,
tag: '{\{subst:nld}}',
talkTag: '{\{subst:Запрос о статусе файла|1=%FILE%}}',
imgSummary: 'не указана лицензия файла',
talkSummary: '[[ВП:ППФ|автоматическое]] уведомление об отсутствии лицензии для %FILE%'
}, {
label: this.i18n.toolboxLinkPermission,
tag: '{\{subst:npd}}',
talkTag: '{\{subst:Запрос о статусе файла|1=%FILE%}}',
imgSummary: 'отсутствует [[ВП:ДОБРО|разрешение]] на использование файла',
talkSummary: '[[ВП:ППФ|автоматическое]] уведомление об отсутствии [[ВП:ДОБРО|разрешения]] на использование %FILE%'
}, {
label: this.i18n.toolboxLinkDisputed,
tag: '{\{subst:dd}}',
talkTag: '{\{subst:Запрос о статусе файла|1=%FILE%}}',
imgSummary: 'сомнительный статус файла',
talkSummary: '[[ВП:ППФ|автоматическое]] уведомление о сомнительном статусе %FILE%'
}, {
label: this.i18n.toolboxLinkDisputedFairuse,
tag: '{\{subst:dfud%FUC1%%FUC2%%FUC3%%FUC4%%FUC9%%FUC10%}}',
talkTag: '{\{subst:Запрос о соответствии КДИ|1=%FILE%}}',
imgSummary: 'файл не соответствует [[ВП:КДИ]]',
talkSummary: '[[ВП:ППФ|автоматическое]] уведомление о несоответствии [[ВП:КДИ]] %FILE%',
promptText: {
title: this.i18n.reasonForDisputedFairuse,
questions: [{
message: '1 — может быть заменено свободным эквивалентом;',
type: 'checkbox',
prefill: '|1',
returnvalue: 'FUC1'
}, {
message: '2 — разрешение/качество наносит имущественный ущерб правообладателю;',
type: 'checkbox',
prefill: '|2',
returnvalue: 'FUC2'
}, {
message: '3 — не отвечает критерию минимального использования или не имеет значимости;',
type: 'checkbox',
prefill: '|3',
returnvalue: 'FUC3'
}, {
message: '4 — не публиковалось ранее или не имеет проверяемого источника;',
type: 'checkbox',
prefill: '|4',
returnvalue: 'FUC4'
}, {
message: '9 — используется вне пространства статей или не используется в статьях;',
type: 'checkbox',
prefill: '|9',
returnvalue: 'FUC9'
}, {
message: '10 — не имеет описания и/или обоснования добросовестности использования.',
type: 'checkbox',
prefill: '|10',
returnvalue: 'FUC10'
}]
}
}, {
label: this.i18n.toolboxLinkOrphanedFairuse,
tag: '{\{subst:ofud}}',
imgSummary: 'неиспользуемый несвободный файл'
}, {
label: this.i18n.toolboxLinkNotHost,
tag: '{\{subst:nothost}}',
imgSummary: 'неиспользуемый файл, не имеющий энциклопедической ценности'
}];
if ( typeof window.AjaxDeleteExtraButtons !== 'undefined' ) {
this.insertTagButtons = this.insertTagButtons.concat( window.AjaxDeleteExtraButtons );
}
// Define optional buttons
if ( mw.config.get( 'wgNamespaceNumber' ) === 6 && $( '#shared-image-desc' ).length === 0 ) {
$.each( this.insertTagButtons, function ( k, v ) {
mw.util.addPortletLink( 'p-tb', 'javascript:AjaxQuickDelete.insertTagOnPage("' + v.tag + '","'
+ v.imgSummary + '","' + v.talkTag + '","' + v.talkSummary + '","'
+ ( typeof v.promptText !== 'undefined' ? JSON.stringify( v.promptText ).replace( /"/g, '\\"' ) : v.promptText )
+ '");', v.label );
} );
}
},
insertTagOnPage: function ( tag, imgSummary, talkTag, talkSummary, promptText, page ) {
this.pageName = ( page === undefined ) ? mw.config.get( 'wgPageName' ).replace( /_/g, ' ' ) : page.replace( /_/g, ' ' );
this.tag = tag + '\n';
this.imgSummary = imgSummary;
// first schedule some API queries to fetch the info we need...
this.tasks = [];
// get token
this.addTask( 'findCreator' );
this.addTask( 'prependTemplate' );
if ( talkTag !== 'undefined' ) {
this.talkTag = talkTag.replace( '%FILE%', this.pageName );
this.talkSummary = talkSummary.replace( '%FILE%', '[[:' + this.pageName + ']]' );
this.usersNeeded = true;
this.addTask( 'notifyUploaders' );
}
this.addTask( 'reloadPage' );
if ( promptText !== 'undefined' ) {
promptText = JSON.parse( promptText );
if ( typeof promptText === 'string' ) {
this.prompt( [{
message: '',
prefill: '',
returnvalue: 'reason',
cleanUp: true,
noEmpty: true
}], promptText || this.i18n.reasonForDeletion );
} else {
this.prompt( promptText.questions, promptText.title );
}
} else {
this.nextTask();
}
},
nominateForDeletion: function ( page ) {
this.pageName = ( page === undefined ) ? mw.config.get( 'wgPageName' ) : page;
this.startDate = new Date();
// set up some page names we'll need later
this.requestPage = this.requestPagePrefix + this.pageName;
this.dailyLogPage = this.requestPagePrefix + this.formatDate( 'YYYY/MM/DD' );
this.tag = '{{delete|reason=%PARAMETER%|subpage=' + this.pageName + this.formatDate( '|year=YYYY|month=MON|day=DAY}}\n' );
// On templates: Wrap inside <noinclude>s. Thanks Rillke
if ( mw.config.get( 'wgNamespaceNumber' ) === 10 ) {
this.tag = '<noinclude>' + this.tag + '</noinclude>';
}
this.imgSummary = 'Nominating for deletion';
this.talkTag = '{\{subst:idw|' + this.pageName + '}}';
this.talkSummary = '[[:' + this.pageName + ']] has been nominated for deletion';
this.subpageSummary = 'Starting deletion request';
// first schedule some API queries to fetch the info we need...
this.tasks = []; // reset task list in case an earlier error left it non-empty
this.addTask( 'findCreator' );
// ...then schedule the actual edits
this.addTask( 'prependTemplate' );
this.addTask( 'createRequestSubpage' );
this.addTask( 'listRequestSubpage' );
this.addTask( 'notifyUploaders' );
// finally reload the page to show the deletion tag
this.addTask( 'reloadPage' );
this.prompt( [{
message: '',
prefill: '',
returnvalue: 'reason',
cleanUp: true,
noEmpty: true
}], this.i18n.reasonForDeletion );
},
/**
** 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 );
this.savePage( page, this.imgSummary, 'nextTask' );
},
/**
** Create the DR subpage (or append a new request to an existing subpage).
** The request page will always be watchlisted.
**/
createRequestSubpage: function () {
this.templateAdded = true; // we've got this far; if something fails, user can follow instructions on template to finish
var page = [];
page.title = this.requestPage;
page.text = '\n\n=== [[:' + this.pageName + ']] ===\n' + this.reason + ' ~~' + '~~\n';
page.watchlist = 'watch';
page.editType = 'appendtext';
this.showProgress( this.i18n.creatingNomination );
this.savePage( page, this.subpageSummary, 'nextTask' );
},
/**
** Transclude the nomination page onto today's DR log page, creating it if necessary.
** The log page will never be watchlisted (unless the user is already watching it).
**/
listRequestSubpage: function () {
var page = [];
page.title = this.dailyLogPage;
// Impossible when using appendtext. Shouldn't not be severe though, since DRBot creates those pages before they are needed.
// if (!page.text) page.text = '{{'+'subst:' + this.requestPagePrefix + 'newday}}'; // add header to new log pages
page.text = '\n{{' + this.requestPage + '}}\n';
page.watchlist = 'nochange';
page.editType = 'appendtext';
this.showProgress( this.i18n.listingNomination );
this.savePage( page, 'Listing [[' + this.requestPage + ']]', 'nextTask' );
},
/**
** Notify any uploaders/creators of this page using {{idw}}.
**/
notifyUploaders: function () {
this.uploadersToNotify = 0;
for ( var user in this.uploaders ) {
if ( user === mw.config.get( 'wgUserName' ) ) {
// notifying yourself is pointless
continue;
}
var page = [];
page.title = this.userTalkPrefix + user;
page.text = '\n' + this.talkTag + ' ~~' + '~~\n';
page.editType = 'appendtext';
page.redirect = true;
if ( window.AjaxDeleteWatchUserTalk ) {
page.watchlist = 'watch';
}
this.savePage( page, this.talkSummary, 'uploaderNotified' );
this.showProgress( this.i18n.notifyingUploader.replace( '%USER%', user ) );
this.uploadersToNotify++;
}
if ( this.uploadersToNotify === 0 ) {
this.nextTask();
}
},
uploaderNotified: function () {
this.uploadersToNotify--;
if ( this.uploadersToNotify === 0 ) {
this.nextTask();
}
},
/**
** Compile a list of uploaders to notify. Users who have only reverted the file to an
** earlier version will not be notified.
** DONE: notify creator of non-file pages
**/
findCreator: function () {
var query;
if ( mw.config.get( 'wgNamespaceNumber' ) === 6 ) {
query = {
action: 'query',
prop: 'imageinfo|revisions|info',
rvprop: 'content|timestamp',
meta: 'tokens',
type: 'csrf',
iiprop: 'user|sha1|comment',
iilimit: 50,
titles: this.pageName
};
} else {
query = {
action: 'query',
prop: 'info|revisions',
rvprop: 'user|timestamp',
rvlimit: 1,
rvdir: 'newer',
meta: 'tokens',
type: 'csrf',
titles: this.pageName
};
}
this.showProgress();
this.doAPICall( query, 'findCreatorCB' );
},
findCreatorCB: function ( result ) {
this.uploaders = {};
this.edittoken = result.query.tokens.csrftoken;
var pages = result.query.pages;
for ( var id in pages ) { // there should be only one, but we don't know its ID
//First handle non-file pages
if ( mw.config.get( 'wgNamespaceNumber' ) !== 6 ) {
this.pageCreator = pages[id].revisions[0].user;
this.starttimestamp = pages[id].starttimestamp;
this.timestamp = pages[id].revisions[0].timestamp;
this.uploaders[this.pageCreator] = true;
} else {
var info = pages[id].imageinfo;
var content = pages[id].revisions[0]['*'];
var seenHashes = {};
for ( var i = info.length - 1; i >= 0; i-- ) { // iterate in reverse order
if ( info[i].sha1 && seenHashes[info[i].sha1] ) {
// skip reverts
continue;
}
seenHashes[info[i].sha1] = true;
// Now exclude bots which only reupload a new version:
this.excludedBots = 'FlickreviewR, Rotatebot, Cropbot, Picasa Review Bot';
if ( this.excludedBots.indexOf( info[i].user ) !== -1 ) {
continue;
}
// Handle some special cases, most of the code by [[User:Lupo]]
var match;
if ( info[i].user === 'File Upload Bot (Magnus Manske)' ) {
// CommonsHelper
match = /transferred to Commons by \[\[User:([^\]\|]*)(\|([^\]]*))?\]\] using/.exec( info[i].comment );
// geograph_org2commons, regex accounts for typo ("transferd") and it's possible future correction
if ( !match ) {
match = /geograph.org.uk\]; transferr?e?d by \[\[User:([^\]\|]*)(\|([^\]]*))?\]\] using/.exec( info[i].comment );
}
// flickr2commons
if ( !match ) {
match = /\* Uploaded by \[\[User:([^\]\|]*)(\|([^\]]*))?\]\]/.exec( content );
}
if ( match ) {
match = match[1];
}
// Really necessary?
match = this.fixDoubleEncoding( match );
} else if ( info[i].user === 'FlickrLickr' ) {
match = /\n\|reviewer=\s*(.*)\n/.exec( content );
if ( match ) {
match = match[1];
}
} else if ( info[i].user === 'Flickr upload bot' ) {
// Check for the bot's upload template
match = /\{\{User:Flickr upload bot\/upload(\|[^\|\}]*)?\|reviewer=([^\}]*)\}\}/.exec( content );
if ( match ) {
match = match[2];
}
} else {
// No special case applies, just continue;
this.uploaders[info[i].user] = true;
continue;
}
if ( match ) {
// Make sure the username is in canonical form
match = match
.replace( /^[\s_]+/, '' )
.replace( /[\s_]+$/, '' )
.replace( /[\s_]+/g, ' ' );
match = match.substr( 0, 1 ).toUpperCase() + match.substr( 1 );
this.uploaders[match] = true;
}
}
}
}
this.nextTask();
},
removeTemplate: function () {
var page = [];
page.title = ( this.destination || mw.config.get( 'wgPageName' ) );
page.text = this.pageContent.replace( /\{\{(rename|rename media|move)\|.*?\}\}/i, '' );
page.editType = 'text';
page.starttimestamp = this.starttimestamp;
page.timestamp = this.timestamp;
this.showProgress( this.i18n.removingTemplate );
this.savePage( page, ( this.declineReason || this.i18n.renameDone ), 'nextTask' );
},
redirectPage: function () {
var page = [];
page.title = mw.config.get( 'wgPageName' );
page.text = '#REDIRECT [[' + this.destination + ']]';
page.editType = 'text';
this.showProgress( this.i18n.redirectingFile );
this.savePage( page, 'Redirecting to duplicate file', 'nextTask' );
},
saveDescription: function () {
var page = [];
page.title = this.destination;
page.text = this.newPageText;
page.editType = 'text';
this.showProgress( this.i18n.savingDescription );
this.savePage( page, 'Merging details from duplicate', 'nextTask' );
},
/**
** Pseudo-Modal JS windows.
**/
prompt: function ( questions, title, width ) {
var dlgButtons = {};
dlgButtons[this.i18n.submitButtonLabel] = function () {
$.each( questions, function ( i, v ) {
var response = $( '#AjaxQuestion' + i ).val();
if ( v.type === 'checkbox' ) {
if ( response === 'on' ) {
// no value
response = $( '#AjaxQuestion' + i ).prop( 'checked' );
}
else if ( !$( '#AjaxQuestion' + i ).prop( 'checked' ) ) {
// unchecked
response = '';
}
}
if ( v.cleanUp ) {
if ( v.returnvalue === 'reason' ) {
response = AjaxQuickDelete.cleanReason( response );
}
if ( v.returnvalue === 'destination' ) {
response = AjaxQuickDelete.cleanFileName( response );
}
}
AjaxQuickDelete[v.returnvalue] = response;
if ( v.returnvalue === 'reason' && AjaxQuickDelete.tag ) {
AjaxQuickDelete.tag = AjaxQuickDelete.tag.replace( '%PARAMETER%', response );
AjaxQuickDelete.imgSummary = AjaxQuickDelete.imgSummary.replace( '%PARAMETER%', response );
AjaxQuickDelete.imgSummary = AjaxQuickDelete.imgSummary.replace( '%PARAMETER-LINKED%', '[[:' + response + ']]' );
}
var param = '%' + v.returnvalue.toUpperCase() + '%';
AjaxQuickDelete.tag = AjaxQuickDelete.tag.replace( param, response );
AjaxQuickDelete.imgSummary = AjaxQuickDelete.imgSummary.replace( param, response );
} );
$( this ).dialog( 'close' );
AjaxQuickDelete.nextTask();
};
dlgButtons[this.i18n.cancelButtonLabel] = function () {
$( this ).dialog( 'close' );
};
$( '<div>' )
.html( $('<div>').attr('id', 'AjaxDeleteContainer') )
.dialog( {
width: ( width || 600 ),
modal: true,
title: title,
draggable: false,
dialogClass: 'wikiEditor-toolbar-dialog',
close: function () {
$( this ).dialog( 'destroy' );
$( this ).remove();
},
buttons: dlgButtons
} );
var submitButton = $( '.ui-dialog-buttonpane button:first' );
$.each( questions, function ( i, v ) {
if ( v.type === 'textarea' ) {
$( '#AjaxDeleteContainer' )
.append( '<label for="AjaxQuestion' + i + '">' + v.message + '</label>' )
.append( '<textarea rows=20 id="AjaxQuestion' + i + '"><br/><br/>' );
} else {
$( '#AjaxDeleteContainer' )
.append( '<label for="AjaxQuestion' + i + '">' + v.message + '</label>' )
.append( '<input type="' + ( v.type || 'text' ) + '" id="AjaxQuestion' + i + '" style="width: 98%;"><br/><br/>' );
}
var curQuestion = $( '#AjaxQuestion' + i );
curQuestion.keyup( function ( event ) {
if ( v.noEmpty ) {
if ( $( this ).val().length < ( v.minLength || 4 ) ) {
submitButton.addClass( 'ui-state-disabled' );
} else {
submitButton.removeClass( 'ui-state-disabled' );
}
}
if ( event.keyCode === '13' && v.enterToSubmit !== false ) {
submitButton.click();
}
} );
curQuestion.val( v.prefill );
if ( v.type === 'checkbox' ) {
$( '#AjaxQuestion' + i )
.prop( 'checked', ( typeof v.checked !== 'undefined' ? v.checked : false ) )
.attr( 'style', 'float:left; margin-right:5px' );
}
curQuestion.keyup();
} );
$( '#AjaxQuestion0' ).focus();
},
/**
** Pseudo-Modal JS windows.
**/
compareDetails: function () {
var d = this.details[0];
var f = this.details[1];
document.body.style.cursor = 'default';
this.progressDialog.remove();
if ( d.sha1 === f.sha1 ) {
this.exactDupes = true;
this.nextTask();
return;
}
var dlgButtons = {};
dlgButtons[this.i18n.submitButtonLabel] = function () {
$( this ).dialog( 'close' );
AjaxQuickDelete.nextTask();
};
dlgButtons[this.i18n.cancelButtonLabel] = function () {
$( this ).dialog( 'close' );
};
$( '<div>' )
.html( $('<div>').attr('id', 'AjaxDeleteContainer') )
.dialog( {
width: 800,
modal: true,
title: 'title',
draggable: false,
dialogClass: 'wikiEditor-toolbar-dialog',
close: function () {
$( this ).dialog( 'destroy' );
$( this ).remove();
},
buttons: dlgButtons
} );
$( '#AjaxDupeContainer' )
.append( '<div><img src="' + d.thumburl + '"></div>' )
.append( '<div><img src="' + f.thumburl + '"></div>' )
.append( '<div>' + Math.round( d.size / 1000 ) + ' KB <br/>' + d.width + 'x' + d.height + '</div>' )
.append( '<div>' + Math.round( f.size / 1000 ) + ' KB <br/>' + f.width + 'x' + f.height + '</div>' );
},
/**
** Double encoding fixer by Lupo. This is necessary for some older uploads of Magnus' bot.
**/
fixDoubleEncoding: function ( match ) {
if ( !match ) {
return match;
}
var utf8 = /[u00C2-u00F4][u0080-u00BF][u0080-u00BF]?[u0080-u00BF]?/g;
if ( !utf8.test( match ) ) {
return match;
}
// Looks like we have a double encoding. At least it contains character
// sequences that might be legal UTF-8 encodings. Translate them into %-
// syntax and try to decode again.
var temp = '',
curr = 0,
m,
hexDigit = '0123456789ABCDEF';
var str = match.replace( /%/g, '%25' );
utf8.lastIndex = 0;
// Reset regexp to beginning of string
try {
while ( ( m = utf8.exec( str ) ) !== null ) {
temp += str.substring( curr, m.index );
m = m[0];
for ( var i = 0; i < m.length; i++ ) {
temp += '%' + hexDigit.charAt( m.charCodeAt( i ) / 16 ) + hexDigit.charAt( m.charCodeAt( i ) % 16 );
}
curr = utf8.lastIndex;
}
if ( curr < str.length ) {
temp += str.substring( curr );
}
temp = decodeURIComponent( temp );
return temp;
} catch ( e ) {}
return match;
},
cleanFileName: function ( uncleanName ) {
uncleanName = uncleanName
.replace( /^Image:/i, 'File:' )
.replace( /\.jpe*g$/i, '.jpg' )
.replace( /\.png$/i, '.png' )
.replace( /\.gif$/i, '.gif' );
var currentExtension = mw.config.get( 'wgPageName' ).toLowerCase().replace( /.*?\.(\w{3,4})$/, '$1' ).replace( 'jpeg', 'jpg' );
// If new file name is without extension, add the one from the old name
if ( uncleanName.toLowerCase().indexOf( currentExtension ) === -1 ) {
uncleanName += '.' + currentExtension;
}
if ( uncleanName.indexOf( 'File:' ) === -1 ) {
uncleanName = 'File:' + uncleanName;
}
return uncleanName;
},
cleanReason: function ( uncleanReason ) {
// trim whitespace
uncleanReason = uncleanReason.replace( /^\s*(.+)\s*$/, '$1' );
// remove signature
uncleanReason = uncleanReason.replace( /(.+)(--)?~{3,5}$/, '$1' );
return uncleanReason;
},
/**
** For display of progress messages.
**/
showProgress: function ( message ) {
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: 90,
minHeight: 90,
modal: true,
resizable: false,
draggable: false,
closeOnEscape: false,
dialogClass: 'ajaxDeleteFeedback'
} );
$( '.ui-dialog-titlebar' ).hide();
}
},
/**
** Submit an edited page.
**/
savePage: function ( page, summary, callback ) {
var edit = {
action: 'edit',
summary: summary,
watchlist: ( page.watchlist || 'preferences' ),
title: page.title,
token: this.edittoken
};
if ( page.redirect ) {
edit.redirect = '';
}
edit[page.editType] = page.text;
this.doAPICall( edit, callback );
},
/**
** Does a MediaWiki API request and passes the result to the supplied callback (method name).
** Uses POST requests for everything for simplicity.
**/
doAPICall: function ( params, callback ) {
var o = this;
params.format = 'json';
$.ajax( {
url: this.apiURL,
cache: false,
dataType: 'json',
data: params,
type: 'POST',
success: function ( result, status, x ) {
if ( !result ) {
return o.fail( 'Receive empty API response:\n' + x.responseText );
}
// In case we get the mysterious 231 unknown error, just try again
if ( result.error && result.error.info.indexOf( '231' ) !== -1 ) {
return setTimeout( function () {
o.doAPICall( params, callback );
}, 500 );
}
if ( result.error ) {
return o.fail( 'API request failed (' + result.error.code + '): ' + result.error.info );
}
if ( result.edit && result.edit.spamblacklist ) {
return o.fail( 'The edit failed because ' + result.edit.spamblacklist + ' is on the Spam Blacklist' );
}
try {
o[callback]( result );
} catch ( e ) {
return o.fail( e );
}
},
error: function ( x, status, error ) {
return o.fail( 'API request returned code ' + x.status + ' ' + status + 'Error code is ' + error );
}
} );
},
/**
** 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 () {
var task = this.currentTask = this.tasks.shift();
try {
this[task]();
} catch ( e ) {
this.fail( e );
}
},
/**
** Once we're all done, reload the page.
**/
reloadPage: function () {
this.progressDialog.remove();
if ( this.pageName && this.pageName.replace( / /g, '_' ) !== mw.config.get( 'wgPageName' ) ) {
return;
}
var encTitle = ( this.destination || mw.config.get( 'wgPageName' ) );
encTitle = encodeURIComponent( encTitle.replace( / /g, '_' ) ).replace( /%2F/ig, '/' ).replace( /%3A/ig, ':' );
location.href = mw.config.get( 'wgServer' ) + mw.config.get( 'wgArticlePath' ).replace( '$1', encTitle );
},
/**
** Crude error handler. Just throws an alert at the user and
** (if we managed to add the { { delete } } tag) reloads the page.
**/
fail: function ( err ) {
if ( 'object' === typeof err ) {
var stErr = err.message + '<br/>' + err.name;
if ( err.lineNumber ) {
stErr += ' @line' + err.lineNumber;
}
err = stErr;
}
document.body.style.cursor = 'default';
var msg = this.i18n.taskFailure[this.currentTask] || this.i18n.genericFailure;
//TODO: Needs cleanup
var fix = '';
if ( this.imgSummary === 'Nominating for deletion' ) {
fix = ( this.templateAdded ? this.i18n.completeRequestByHand : this.i18n.addTemplateByHand );
}
$( '#feedbackContainer' ).html( msg + ' ' + fix + '<br/>' + this.i18n.errorDetails + '<br/>' + err
+ '<br/><a href=' + mw.config.get( 'wgServer' ) + '/wiki/MediaWiki_talk:AjaxQuickDelete.js>' + this.i18n.errorReport + '</a>' );
$( '.ui-dialog-content' ).height( 'auto' );
$( '.ui-dialog' ).addClass( 'ajaxDeleteError' );
// Allow some time to read the message
if ( this.templateAdded ) {
setTimeout( this.reloadPage(), 5000 );
}
},
/**
** 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 ) {
var pad0 = function ( s ) {
s = '' + s;
return ( s.length > 1 ? s : '0' + s );
}; // zero-pad to two digits
if ( !date ) {
date = 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, this.months[date.getUTCMonth()] );
fmt = fmt.replace( /DAY/g, date.getUTCDate() );
return fmt;
},
months: 'January February March April May June July August September October November December'.split( ' ' ),
// Constants
// DR subpage prefix
requestPagePrefix: 'Commons:Deletion requests/',
// user talk page prefix
userTalkPrefix: mw.config.get( 'wgFormattedNamespaces' )[3] + ':',
// MediaWiki API script URL
apiURL: ( /^\/\//.test( mw.config.get( 'wgServer' ) ) ? document.location.protocol : '' ) + mw.config.get( 'wgServer' ) + mw.util.wikiScript( 'api' ),
// 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?',
reasonForDisputedFairuse: 'Каким критериям не соответствует файл?',
submitButtonLabel: 'Подтвердить',
cancelButtonLabel: 'Отменить',
// GUI progress messages
preparingToEdit: 'Подготовка к редактированию страниц… ',
creatingNomination: 'Creating nomination page... ',
listingNomination: 'Adding nomination page to daily list... ',
addingAnyTemplate: 'Добавление шаблона на страницу' + ( mw.config.get( 'wgNamespaceNumber' ) === 6 ? ' файла' : '' ) + '… ',
notifyingUploader: 'Уведомление участника %USER%… ',
// Extended version
toolboxLinkAuthor: 'Нет автора',
toolboxLinkLicense: 'Нет лицензии',
toolboxLinkSource: 'Нет источника',
toolboxLinkPermission: 'Нет разрешения',
toolboxLinkDisputed: 'Сомнительный',
toolboxLinkDisputedFairuse: 'Не соответствует КДИ',
toolboxLinkOrphanedFairuse: 'Неисп. несвободный',
toolboxLinkNotHost: 'Не хостинг',
toolboxLinkCopyvio: 'Report copyright violation',
reasonForCopyvio: 'Why is this file a copyright violation?',
// For moving files
renameDone: 'Removing template; rename done',
removingTemplate: 'Removing rename template',
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 file name?',
movingFile: 'Moving file',
replacingUsage: 'Ordering CommonsDelinker to replace all usage',
declineMove: 'Why do you want to decline the request?',
leaveRedirect: 'Leave a redirect behind:',
//For Duplicates
deletingFile: 'Deleting file',
mergeDescription: 'Please now merge the file descriptions',
redirectingFile: 'Redirecting file',
savingDescription: 'Saving new details',
// Errors
genericFailure: 'An error occurred while trying to do the requested action. ',
taskFailure: {
listUploaders: 'An error occurred while determining the '
+ ( 6 === mw.config.get( 'wgNamespaceNumber' ) ? ' uploader(s) of this file' : 'creator of this page' ) + '.',
loadPages: 'An error occurred while preparing to nominate this '
+ mw.config.get( 'wgCanonicalNamespace' ).toLowerCase() + ' for deletion.',
prependDeletionTemplate: 'An error occurred while adding the {{delete}} template to this '
+ mw.config.get( '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 '
+ ( 6 === mw.config.get( 'wgNamespaceNumber' ) ? ' uploader(s) of this file' : 'creator of this page' ) + '.'
},
addTemplateByHand: 'To nominate this ' + mw.config.get( '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: 'Report the error here'
}
};
mediaWiki.loader.using( 'jquery.ui', function () {
$( document ).ready( function () {
AjaxQuickDelete.install();
} );
} );
} // end if (guard)
// </nowiki>