Difference between revisions 213087 and 383129 on guwiki// Original code written by [[User:Ilmari Karonen]] // Rewritten & extended by [[User:DieBuche]]. Botdetection and encoding fixer by [[User:Lupo]] // Validation and further development [[User:Rillke]], 2011-2012 // // Ajax-based replacement for [[MediaWiki:Quick-delete-code.js]] // // TODO: Fix problems with moves of videos // TODO: Delete talk //<nowiki> /*global jQuery:false, mediaWiki:false */ /*jshint curly:false, laxbreak:true, scripturl:true, onecase:true, */ (function($, mw) { 'use strict'; var namespaceNumber = mw.config.get('wgNamespaceNumber'); var pageName = mw.config.get('wgPageName'); var canonicalNS = mw.config.get('wgCanonicalNamespace'); var AjaxQuickDelete, AQD; if (typeof AjaxQuickDelete !== 'undefined' || namespaceNumber < 0) return; // utility method: Should be moved out into some global site code since used everywhere $.createIcon = function (iconClass) { return $('<span>', { 'class': 'ui-icon ' + iconClass + ' ajaxInlineIcon', text: ' ' }); }; AjaxQuickDelete = AQD = window.AjaxQuickDelete = { /** ** 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 (0 === mw.config.get('wgArticleId')) return; // Check edit restrictions and do not install anything if protected if (mw.config.get('wgRestrictionEdit') && mw.config.get('wgRestrictionEdit').length) { if ($.inArray(mw.config.get('wgRestrictionEdit')[0], mw.config.get('wgUserGroups')) === -1) { return; } } // wait for document.readyState $(function() { $(document).triggerHandler('scriptLoaded', ['AjaxQuickDelete']); // Set up toolbox link if (namespaceNumber !== 14) { mw.util.addPortletLink('p-tb', 'javascript:AjaxQuickDelete.nominateForDeletion();', AQD.i18n.toolboxLinkDelete, 't-ajaxquickdelete', null); } else { mw.util.addPortletLink('p-tb', 'javascript:AjaxQuickDelete.discussCategory();', AQD.i18n.toolboxLinkDiscuss, 't-ajaxquickdiscusscat', null); } // Check user group. if ($.inArray('sysop', mw.config.get('wgUserGroups')) !== -1) { AQD.userRights = 'sysop'; } else if ($.inArray('filemover', mw.config.get('wgUserGroups')) !== -1) { AQD.userRights = 'filemover'; } // Install AjaxMoveButton if ((AQD.userRights === 'filemover' || AQD.userRights === 'sysop') && namespaceNumber === 6) { // Also add a "Move & Replace" button to dropdown menu mw.util.addPortletLink('p-cactions', 'javascript:AjaxQuickDelete.moveFile("", "");', AQD.i18n.dropdownMove, 'ca-quickmove', 'ca-move'); //Add quicklinks to template if ($('#AjaxRenameLink').length) { $('#AjaxRenameLink').append('<a href="javascript:AjaxQuickDelete.moveFile();">' + AQD.i18n.moveAndReplace + '</a>').append('<a href="javascript:AjaxQuickDelete.declineRequest(\'move\');" class="ajaxDeleteDeclineMove"><sup> ' + AQD.i18n.anyDecline + '</sup></a>'); } // Install x-To-DR $('.ctdr-btn-convert').click(AQD._convertToDR); $('.ctdr-btn-remove').click(AQD._removeAnyTag); $('.convert-to-dr').show(); } if (AQD.userRights === 'sysop' && namespaceNumber === 6) { if ($('#AjaxDupeProcess').length) { $('#AjaxDupeProcess').append('<a href="javascript:AjaxQuickDelete.processDupes();">Process Duplicates</a>').show(); } } // Extra buttons if ("1" === mw.user.options.get('gadget-QuickDelete')) { // Wait until the user's js was loaded and executed mw.loader.using(['ext.gadget.QuickDelete', 'user'], function() { AQD.doInsertTagButtons(); }); } }); }, /** ** Ensure that all variables are in a good state ** You must call this method before doing anything! **/ initialize: function(undefined) { pageName = mw.config.get('wgPageName'); this.tasks = []; this.destination = undefined; this.details = undefined; }, fileExists: function() { this.i18n.moveDestination = this.i18n.moveOtherDestination; this.moveFile(); }, /** ** For moving files **/ moveFile: function() { this.initialize(); this.showProgress(); if ($('#AjaxRenameLink').length) { this.possibleDestination = this.cleanFileName($('#AjaxRenameDestination').text()); this.possibleReason = this.cleanReason($('#AjaxRenameReason').text()); } if ($('#globalusage').length || !$('#mw-imagepage-nolinkstoimage').length) this.inUse = true; this.addTask('doesFileExist'); this.fileNameExistsCB = 'fileExists'; this.addTask('getMoveToken'); this.addTask('movePage'); this.addTask('removeTemplate'); if (this.inUse) this.addTask('replaceUsage'); // finally reload the page to show changed page this.addTask('reloadPage'); this.prompt([{ message: this.i18n.moveDestination, 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); }, /** ** For declining a request **/ declineRequest: function(reason) { // No valid reason stated, see the rename guidelines or not an exact duplicate this.initialize(); this.addTask('getMoveToken'); this.addTask('removeTemplate'); // finally reload the page to show the template was removed this.addTask('reloadPage'); // extend the reason switch (reason) { case 'move': reason = 'No valid reason stated, see the [[COM:MOVE|rename guidelines]]'; break; } this.prompt([{ message: '', 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) { this.initialize(); this.pageName = (page === undefined) ? pageName.replace(/_/g, ' ') : page.replace(/_/g, ' '); this.tag = tag + '\n'; this.img_summary = img_summary; // first schedule some API queries to fetch the info we need... // get token this.addTask('findCreator'); this.addTask('prependTemplate'); // Cave: insertTagOnPage is inserted as javascript link and therefore talk_tag can be "undefined"/string if (talk_tag && talk_tag !== "undefined") { this.talk_tag = talk_tag.replace('%FILE%', this.pageName); this.talk_summary = talk_summary.replace('%FILE%', '[[:' + this.pageName + ']]'); this.usersNeeded = true; this.addTask('notifyUploaders'); } this.addTask('reloadPage'); if (tag.indexOf("%PARAMETER%") !== -1) { this.prompt([{ message: '', prefill: '', returnvalue: 'reason', cleanUp: true, noEmpty: true, minLength: 1 }], prompt_text || this.i18n.reasonForDeletion); } else { this.nextTask(); } }, discussCategory: function() { // reset task list in case an earlier error left it non-empty this.initialize(); this.pageName = pageName.replace(/_/g, ' '); this.startDate = new Date(); this.tag = '{' + '{subst:cfd}}'; this.img_summary = 'This category needs discussion'; this.talk_tag = '{' + '{subst:cdw|' + pageName + '}}'; this.talk_summary = "[[:" + pageName + "]] needs discussion"; this.subpage_summary = 'Starting category discussion'; // set up some page names we'll need later this.requestPage = 'Commons:Categories for discussion/' + this.formatDate("YYYY/MM/") + pageName; this.dailyLogPage = 'Commons:Categories for discussion/' + this.formatDate("YYYY/MM"); // first schedule some API queries to fetch the info we need... this.addTask('findCreator'); // ...then schedule the actual edits this.addTask('notifyUploaders'); this.addTask('prependTemplate'); this.addTask('createRequestSubpage'); this.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.prompt([{ message: '', prefill: '', returnvalue: 'reason', cleanUp: true, appendNode: lazyLoadNode, noEmpty: true, parseReason: true }], this.i18n.reasonForDiscussion); }, nominateForDeletion: function(page) { var o = this; // reset task list in case an earlier error left it non-empty this.initialize(); mw.loader.using(['jquery.byteLength', 'jquery.ui.dialog'], function() { o.pageName = (page === undefined) ? pageName.replace(/_/g, ' ') : page.replace(/_/g, ' '); o.startDate = new Date(); // set up some page names we'll need later var requestPage = o.pageName; // MediaWiki has an ugly limit of 255 bytes per title, excluding the namespace while ($.byteLength(requestPage) + $.byteLength(o.requestPagePrefix.replace(/^.+?\:/, '')) >= 255) { requestPage = $.trim(requestPage.slice(0, requestPage.length-1)); } o.requestPage = o.requestPagePrefix + requestPage; o.dailyLogPage = o.requestPagePrefix + o.formatDate("YYYY/MM/DD"); o.tag = "{{delete|કારણ=%PARAMETER%|subpage=" + requestPage + o.formatDate("|year=YYYY|month=MON|day=DAY}}\n"); switch (namespaceNumber) { // 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; } o.img_summary = 'Nominating for deletion'; o.talk_tag = '{' + '{subst:idw|' + requestPage + '}}'; o.talk_summary = "[[:" + o.pageName + "]] has been nominated for deletion"; o.subpage_summary = 'Starting deletion request'; // first schedule some API queries to fetch the info we need... o.addTask('findCreator'); // ...then schedule the actual edits o.addTask('prependTemplate'); o.addTask('createRequestSubpage'); o.addTask('listRequestSubpage'); o.addTask('notifyUploaders'); // finally reload the page to show the deletion tag o.addTask('reloadPage'); var lazyLoadNode = o.createLazyLoadNode(o.i18n.moreInformation, 'MediaWiki:Gadget-AjaxQuickDelete.js/DeleteInfo', '#AjaxQuickDeleteDeleteInfo'); o.prompt([{ message: '', prefill: o.reason || '', 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) { return $('<div>', { style: 'min-height:40px;' }).append($('<a>', { 'href': '#', 'text': label }).click(function(e) { e.preventDefault(); var $content = $(this).parent().find('.ajaxDeleteLazyLoad'); 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'); }), $('<div>', { 'class': 'ajaxDeleteLazyLoad', 'style': 'display:none;' }).append($('<span>', { '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 = "This file was initially tagged by %USER%" + (template ? (" as '''" + template + "'''") : ""); // ...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(); // first schedule a API query to fetch the info we need... this.addTask('findTemplateAdder'); this.addTask('getMoveToken'); // ...then schedule the actual edits this.addTask('removeTemplate'); this.addTask('removeProgress'); this.addTask('nominateForDeletion'); this.declineReason = "This file does not qualify for [[COM:SPEEDY|speedy-deletion]] and a regular deletion request will be started."; // Hide the buttons to prevent attempts of duplicate removal $('.convert-to-dr').hide(); // ... and go! this.nextTask(); }, findTemplateAdder: function() { var query = { 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 > 1 && !template) template = m[1]; if (m.length > 2 && !reason) reason = m[2]; } else { return false; } }); }); if (!user) throw new Error("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."); this.reason = this.reason.replace('%USER%', "[[User:" + user + "|" + user + "]]"); if (template) this.reason += " (" + template + ")"; if (reason) this.reason += " and the most recent rationale was: <tt>" + reason + "</tt>"; this.nextTask(); }, processDupes: function() { // reset task list in case an earlier error left it non-empty this.initialize(); if ($('#globalusage').length || !$('#mw-imagepage-nolinkstoimage').length) this.inUse = true; this.addTask('getDupeDetails'); this.addTask('compareDetails'); this.addTask('mergeDescriptions'); this.addTask('saveDescription'); if (this.inUse) this.addTask('replaceUsage'); this.addTask('deletePage'); this.addTask('redirectPage'); this.addTask('reloadPage'); this.destination = $('#AjaxDupeDestination').text(); this.nextTask(); }, getDupeDetails: function() { var query = { 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; this.details = []; for (id in pages) { if (pages.hasOwnProperty(id)) { 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("Error in the duplicate-template, check your language version! (v.imageinfo is undefined)"); } else { throw new Error("Retrieving information about " + v.title + " failed. It is possible that it is deleted, the last revision is corrupt or the file is a redirect. (v.imageinfo is undefined)"); } } 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(); }, /** ** 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.img_summary, 'nextTask'); }, /** ** Create the DR subpage (or append a new request to an existing subpage). ** The request page will always be watchlisted. **/ createRequestSubpage: function() { this.templateAdded = true; // we've got this far; if something fails, user can follow instructions on template to finish var page = {}; page.title = this.requestPage; page.text = "\n=== [[:" + this.pageName + "]] ===\n" + this.reason + " ~~" + "~~\n"; page.watchlist = 'watch'; page.editType = 'appendtext'; this.showProgress(this.i18n.creatingNomination); this.savePage(page, this.subpage_summary, 'nextTask'); }, /** ** Transclude the nomination page onto today's DR log page, creating it if necessary. ** The log page will never be watchlisted (unless the user is already watching it). **/ listRequestSubpage: function() { var page = {}; page.title = this.dailyLogPage; // Impossible when using appendtext. Shouldn't not be severe though, since DRBot creates those pages before they are needed. // if (!page.text) page.text = "{{"+"subst:" + this.requestPagePrefix + "newday}}"; // add header to new log pages page.text = "\n{{" + this.requestPage + "}}\n"; page.watchlist = '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 (this.uploaders.hasOwnProperty(user)) { if (user === mw.config.get('wgUserName')) continue; // notifying yourself is pointless var page = {}; page.title = this.userTalkPrefix + user; page.text = "\n" + this.talk_tag + " ~~" + "~~\n"; 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)); 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 (namespaceNumber === 6) { query = { action: 'query', prop: 'imageinfo|revisions|info', rvprop: 'content|timestamp', intoken: 'edit', iiprop: 'user|sha1|comment', iilimit: 50, titles: this.pageName }; } else { query = { action: 'query', prop: 'info|revisions', rvprop: 'user|timestamp', rvlimit: 1, rvdir: 'newer', 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 if (namespaceNumber !== 6 || !pages[id].imageinfo) { this.pageCreator = pages[id].revisions[0].user; this.starttimestamp = pages[id].starttimestamp; this.timestamp = pages[id].revisions[0].timestamp; if (typeof this.pageCreator !== 'undefined') { 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]) 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 (-1 !== $.inArray(info[i].user, this.excludedBots)) continue; // outsourced to [[MediaWiki:Gadget-libCommons.js]] var match = mw.libs.commons.getUploadBotUser(info[i].user, content, info[i].comment); if (match) { this.uploaders[match] = true; } } } } } this.nextTask(); }, getMoveToken: function() { var query = { action: 'query', prop: 'info|revisions|imageinfo', rvprop: 'content|timestamp', iiprop: 'mime', intoken: 'edit|move', titles: pageName }; this.showProgress(this.i18n.preparingToEdit); this.doAPICall(query, 'getMoveTokenCB'); }, getMoveTokenCB: function(result) { 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)) { 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 && pg.imageinfo.length && 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 > 5) this.fileMime = ''; } } } this.nextTask(); }, doesFileExist: function() { var toCheck = this.cleanFileName(this.destination).replace(/^File:/, ''); var query = { 'action': 'query', 'list': 'allpages', 'apfrom': toCheck, 'apto': toCheck, 'apnamespace': 6 }; 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(); }, removeTemplate: function() { var page = {}; this.replaceWith = (this.replaceWith || (this.templateRegExp ? '' : '$1$2' )); page.title = (this.destination || pageName); page.text = $.trim(this.pageContent.replace((this.templateRegExp || /(?:([^\=])\n)?\{\{(?:rename|rename media|move)\|.*?\}\}(?:\n([^\=]))?/i), this.replaceWith)); page.editType = 'text'; page.starttimestamp = this.starttimestamp; page.timestamp = this.timestamp; this.showProgress(this.i18n.removingTemplate); this.savePage(page, (this.declineReason || "Removing template; rename done"), 'nextTask'); }, replaceUsage: function() { var page = {}; page.title = 'User:CommonsDelinker/commands'; if (this.userRights === 'filemover') { page.title = 'User:CommonsDelinker/commands/filemovers'; this.reason = this.reason.replace(/\{/g, '{').replace(/\}/g, '}').replace(/\=/g, '='); } if (!this.details) this.reason = '[[COM:FR|File renamed]]: ' + this.reason.replace(/\[\[Commons:File[_ ]renaming[^\[\]]*\]\]:? ?/i, ''); page.text = '\n{{universal replace|' + pageName.replace('File:', '') + '|' + this.destination.replace('File:', '') + '|reason=' + this.reason + '}}'; page.editType = 'appendtext'; page.watchlist = 'nochange'; this.showProgress(this.i18n.replacingUsage); this.savePage(page, 'universal replace: [[:' + pageName + ']] → [[:' + this.destination + ']]', 'nextTask'); }, 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() { 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 ([[' + pageName + ']])', 'nextTask'); }, /** ** Pseudo-Modal JS windows. **/ 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' && 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 = $('<div>', { id: 'AjaxDeleteContainer' }); var _convertToTextarea = function(e) { var $el = $(this), $input = $el.data('toConvert'), $tarea = $('<textarea>', { id: $input.attr('id'), style: 'height:10em; width:98%; display:none;' }); $el.unbind(); $el.fadeOut(); $input.parent().prepend( $tarea .data('v', $input.data('v')).data('parserResultNode', $input.data('parserResultNode')) .val($input.val()).keyup(_parseReason).bind('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; $parserResultNode.css('color', '#877'); parsertimeout = parsertimeout || 0; if (parserjqXHR) parserjqXHR.abort(); var gotJSON = function(d) { try { $parserResultNode.html(d.parse.text['*']); $parserResultNode.css('color', '#000'); } catch (ex) {} }; var parseIt = function() { var toParse = $el.val(); if (!toParse || !/(?:<|\/\/|\[|\'\{|~~)/.test(toParse)) { gotJSON({ parse: { text: { '*': toParse || '' } } }); 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 < (v.minLength || 10)) { $submitButton.button('option', 'disabled', true); } else { $submitButton.button('option', 'disabled', false); } } if (('TEXTAREA' !== $el.prop('nodeName')) && ((event.keyCode - 0) === 13) && (v.enterToSubmit !== false) && !$submitButton.button('option', 'disabled') ) $submitButton.click(); }; $.each(questions, function(i, v) { v.type = (v.type || 'text'); if (v.type === 'textarea') { $AjaxDeleteContainer.append('<label for="AjaxQuestion' + i + '">' + v.message + '</label>').append('<textarea rows=20 id="AjaxQuestion' + i + '">'); } else { $AjaxDeleteContainer.append('<label for="AjaxQuestion' + i + '">' + v.message + '</label>').append('<input type="' + v.type + '" id="AjaxQuestion' + i + '" style="width:97%;">'); } var curQuestion = $AjaxDeleteContainer.find('#AjaxQuestion' + i); if (v.parseReason) { var $parserResultNode = $('<div>', { id: 'AjaxQuestionParse' + i, html: ' ' }); $AjaxDeleteContainer.append('<br><label for="AjaxQuestionParse' + i + '">' + o.i18n.previewLabel + '</label>').append($parserResultNode); curQuestion.data('parserResultNode', $parserResultNode).keyup(_parseReason); } if (v.type !== 'textarea') $AjaxDeleteContainer.append('<br><br>'); if (v.appendNode) { $AjaxDeleteContainer.append(v.appendNode); } if ('number' === typeof v.byteLimit) { mw.loader.using('jquery.byteLimit', function() { curQuestion.byteLimit(v.byteLimit); }); } curQuestion.data('v', v); curQuestion.bind('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.attr('checked', v.prefill).attr('style', 'margin-left: 5px'); }); if (mw.user.anonymousisAnon()) { AQD.renderNode($('<div>', { id: 'ajaxDeleteAnonwarning' }), 'MediaWiki:Anoneditwarning').appendTo($AjaxDeleteContainer); } var $dialog = $('<div></div>').append($AjaxDeleteContainer).dialog({ width: (width || 600), modal: true, title: title, dialogClass: "wikiEditor-toolbar-dialog", close: function() { $(this).dialog("destroy"); $(this).remove(); }, 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).button({ icons: { primary: 'ui-icon-circle-check' } }).addClass('ui-button-green'); $cancelButton = $buttons.eq(1).button({ icons: { primary: 'ui-icon-circle-close' } }).addClass('ui-button-red'); } }); $.each(questions, function(i, v) { var curQuestion = $AjaxDeleteContainer.find('#AjaxQuestion' + i); curQuestion.keyup(); if (v.type === 'text') { var $q = curQuestion.wrap('<div style="position:relative;">').parent(); var $i = $.createIcon('ui-icon-arrow-4-diag').attr('title', 'Expand to textarea'); $('<span>', { 'class': 'ajaxTextareaConverter' }).append($i).appendTo($q).data('toConvert', curQuestion).click(_convertToTextarea); } }); $('#AjaxQuestion0').focus().select(); }, /** ** Pseudo-Modal JS windows. **/ compareDetails: function() { var d = this.details[0], f = this.details[1], $submitButton, $inverseButton, $cancelButton, $swapButton, $overlayButton; this.showProgress(); 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) + ' KB <br>' + d.width + 'x' + d.height + '<br>' }).append( $('<a>', { href: d.descriptionurl, text: d.title, target: '_blank' }))); var $imgF = $('<div>').append($('<img>', { src: f.thumburl, height: f.thumbheight, width: f.thumbwidth }), $('<div>', { id: 'AjaxDeleteImgKeep', html: Math.round(f.size / 1000) + ' KB <br>' + f.width + 'x' + f.height + '<br>' }).append( $('<a>', { href: f.descriptionurl, text: f.title, target: '_blank' }))); var 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(); setTimeout(function() { AQD.compareDetails(); }, 10); }; dlgButtons[this.i18n.cancelButtonLabel] = function() { $(this).dialog("close"); }; 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); $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 mw.loader.using(['jquery.ui.draggable', 'jquery.ui.resizable'], function() { // 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(); $fClone.find('img').resizable(); // In IE, opacity is not fully inerhited $fClone.children('div').fadeTo(0, 0.7); }); } }; var $AjaxDupeContainer = $('<div>', { id: 'AjaxDupeContainer' }).append($imgD, $imgF); var $dialog = $('<div></div>').append($AjaxDupeContainer).dialog({ width: 800, modal: true, title: this.i18n.compareDetails, draggable: false, dialogClass: "wikiEditor-toolbar-dialog", close: function() { $(this).dialog("destroy"); $(this).remove(); }, buttons: dlgButtons, open: function() { var $buttons = $(this).parent().find('.ui-dialog-buttonpane button'); $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() { this.prompt([{ message: '', prefill: this.details[0].content, returnvalue: 'discard', cleanUp: false, noEmpty: false, type: 'textarea', enterToSubmit: false }, { message: '', prefill: this.details[1].content, returnvalue: 'newPageText', cleanUp: false, noEmpty: false, 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 uncleanName = uncleanName.replace(/^(?:Image|File):/i, ''); // Convert extension to lower case uncleanName = uncleanName.replace(/\.\w{3,4}$/, function($e) { return $e.toLowerCase(); }); // jpeg -> jpg uncleanName = uncleanName.replace(/\.jpe*g$/, '.jpg'); // 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(/&(([A-Za-z0-9\x80-\xff]+|#[0-9]+|#x[0-9A-Fa-f]+);)/g, '& $1') .replace(/[:\/|#]/g, '-') .replace(/[\]\}>]/g, ')') .replace(/[\[\{<]/g, '('); var currentExt = pageName.toLowerCase().replace(/.*?\.(\w{3,4})$/, '$1').replace('jpeg', 'jpg'); // If the current mime-type is available to the script, check it; // MediaWiki sometimes allows uploading mismatching mimetypes but not moving if (this.fileMime) { currentExt = ('ogg' === this.fileMime && ('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 if (!reCurrentExt.test(uncleanName.toLowerCase())) uncleanName += '.' + currentExt; // Capitalize the first letter and prefix the namespace return 'File:' + uncleanName.replace(/^\w/, function($0) { return $0.toUpperCase(); }); }, 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. **/ 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></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', open: function() { $(this).parent().find('.ui-dialog-titlebar').hide(); }, close: function() { $(this).dialog("destroy"); $(this).remove(); } }); } }, /** ** 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); }, movePage: function() { // Some users don't get it: They want to move pages to itself. if (AQD.cleanFileName(pageName) === AQD.destination) return AQD.nextTask(); mw.loader.using(['ext.gadget.libAPI'], function() { mw.user.tokens.set('moveToken', AQD.movetoken); var moveArgs = { cb: function() { AQD.nextTask(); }, // r-result, query, text errCb: function(r, q, t) { AQD.fail(t); }, from: pageName, to: AQD.destination, 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 = { action: 'delete', reason: this.reason, title: pageName, token: this.deletetoken, recreate: '' }; this.showProgress(this.i18n.deletingFile); this.doAPICall(edit, 'nextTask'); }, setCurrentDate: function(x) { 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], mw.config.get('wgMonthNamesShort')) - 1, 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 (<0, >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(); } }, /** ** 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, newParams = { format: 'json' }; // At least let's try to send the format first and the token last // If the POST-request is cut off, we get "invalid token" or other errors $.extend(newParams, params); if ('edit' === newParams.action) newParams.token = this.edittoken; var retry = function(timeout, errText) { o.apiErrorThreshold--; if (0 === o.apiErrorThreshold) { return o.fail(errText); } else { return setTimeout(function () { o.doAPICall(params, callback); }, timeout); } }; $.ajax({ url: this.apiURL, cache: false, dataType: 'json', data: newParams, type: 'POST', success: function(result, status, x) { if (!o.currentDate && x && x.getResponseHeader) o.setCurrentDate(x); if (!result && 'query' === newParams.action) return retry(1500, "Received empty API response:\n" + x.responseText); if (!result) return o.fail("Received 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 retry(500, "mysterious 231 unknown error"); if (result.error && 'editconflict' === result.error.code && (params.prependtext || params.appendtext)) return retry(750, "edit conflict"); 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("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) { if ('query' === newParams.action) return retry(1500, "API request returned code " + x.status + " " + status + ". Error code is " + 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); } }, retryTask: function() { try { this[this.currentTask](); } catch (e) { this.fail(e); } }, /** ** Once we're all done, reload the page. **/ reloadPage: function() { this.showProgress(); if (this.pageName && this.pageName.replace(/ /g, '_') !== pageName) return; var encTitle = (this.destination || pageName); encTitle = encodeURIComponent(encTitle.replace(/ /g, '_')).replace(/%2F/ig, '/').replace(/%3A/ig, ':'); location.href = mw.config.get('wgServer') + mw.config.get('wgArticlePath').replace("$1", encTitle); }, /** ** Error handler. Throws an alert at the user and give him ** the possibility to retry or autoreport the error-message. **/ fail: function(err) { var o = this; if (typeof err === 'object') { 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; //TODO: Needs cleanup var fix = ''; if (this.img_summary === 'Nominating for deletion') { fix = (this.templateAdded ? this.i18n.completeRequestByHand : this.i18n.addTemplateByHand); } var dlgButtons = {}; dlgButtons[this.i18n.retryButtonLabel] = function() { $(this).remove(); o.retryTask(); }; if (-1 !== $.inArray(o.currentTask, ['movePage', 'deletePage', 'notifyUploaders']) && (/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($('<img>', { src: '//bits.wikimedia.org/skins-1.5/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={{subst:urlencode:{{subst:REVISIONUSER}}}}&offset={{subst:REVISIONTIMESTAMP}}}} 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.tokens.get('editToken')) }, function() { o.reloadPage(); }); }; } dlgButtons[this.i18n.abortButtonLabel] = function() { $(this).remove(); }; this.disableReport = false; this.showProgress(); this.progressDialog = $('<div>').append($('<div>', { id: 'feedbackContainer', html: (msg + ' ' + fix + '<br>' + this.i18n.errorDetails + '<br>' + mw.html.escape(err) + '<br>' + (this.tag ? (this.i18n.tagWas + this.tag) : '') + '<br><a href="' + mw.config.get('wgServer') + '/wiki/MediaWiki_talk:AjaxQuickDelete.js" >' + this.i18n.errorReport + '</a>') })).dialog({ width: 550, modal: true, closeOnEscape: false, title: this.i18n.errorDlgTitle, dialogClass: "ajaxDeleteError", buttons: dlgButtons, close: function() { $(this).dialog("destroy"); $(this).remove(); } }); }, /** ** 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.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 // DR subpage prefix requestPagePrefix: "વિકિપીડિયા:દૂર કરવા વિનંતી/", // user talk page prefix userTalkPrefix: mw.config.get('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: "પાનું દૂર કરવા વિનંતી", toolboxLinkDiscuss: "Nominate category for discussion", // GUI reason prompt form reasonForDeletion: "શા માટે આ પાનું દૂર કરવું?", reasonForDiscussion: "Why does this category need discussion?", moreInformation: "More information", loading: "Loading...", // 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", // GUI progress messages preparingToEdit: "Preparing to edit pages... ", creatingNomination: "Creating nomination page... ", listingNomination: "Adding nomination page to daily list... ", addingAnyTemplate: "Adding template to " + canonicalNS.toLowerCase() + " page... ", notifyingUploader: "Notifying %USER%... ", // 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 file name?", 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", // For declining any request removingTemplate: "Removing template", declineRequest: "Why do you want to decline the request?", anyDecline: "Decline request", //For Duplicates 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", // Errors errorDlgTitle: "Error", genericFailure: "An error occurred while trying to do the requested action. ", taskFailure: { listUploaders: "An error occurred while determining the " + (namespaceNumber === 6 ? " uploader(s) of this file" : "creator of this page") + ".", loadPages: "An error occurred while preparing to nominate this " + canonicalNS.toLowerCase() + " for deletion.", prependDeletionTemplate: "An error occurred while adding the {{delete}} template to this " + canonicalNS.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 " + (namespaceNumber === 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 " + canonicalNS.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 <tt>Report automatically</tt> to send an automatic error-report.", tagWas: "The tag to be inserted into this page was " } }; if (mw.config.get('wgUserLanguage') !== 'en') { $.ajax({ url: mw.util.wikiScript(), dataType: 'script', data: { title: 'MediaWiki:Gadget-AjaxQuickDelete.js/' + mw.config.get('wgUserLanguage') + '.js', action: 'raw', ctype: 'text/javascript', // Allow caching for 28 days maxage: 2419200, smaxage: 2419200 }, cache: true, success: AQD.install, error: AQD.install }); } else { AQD.install(); } }(jQuery, mediaWiki)); // </nowiki> All content in the above text box is licensed under the Creative Commons Attribution-ShareAlike license Version 4 and was originally sourced from https://gu.wikipedia.org/w/index.php?diff=prev&oldid=383129.
![]() ![]() This site is not affiliated with or endorsed in any way by the Wikimedia Foundation or any of its affiliates. In fact, we fucking despise them.
|