صارف:Aafi/DelReqHandler.js
آپ کی توجہ درکار ہے۔ اس صفحہ میں جو اسکرپٹ شامل ہیں، وہ صفحہ لوڈ ہونے پر ہر دفعہ چلیں گی۔ لہذا خراب، غلط اور نامناسب کوڈ یا اسکرپٹ یہاں درج نہ کریں۔ اس سے آپ کے کھاتے کو بھی خطرہ لاحق ہو سکتا ہے۔ |
یاددہانی: محفوظ کرنے کے بعد تازہ ترین تبدیلیوں کو دیکھنے کے لیے آپ کو اپنے براؤزر کا کیش صاف کرنا ہوگا۔
- فائرفاکس/ سفاری: جب Reload پر کلک کریں تو Shift دبا کر رکھیں، یا Ctrl-F5 یا Ctrl-R دبائیں (Mac پر R- )
- گوگل کروم: Ctrl-Shift-R دبائیں (Mac پر Shift-R-⌘)
- انٹرنیٹ ایکسپلورر: جب Refresh پر کلک کریں تو Ctrl یا Ctrl-F5 دبائیں
- اوپیرا: Tools → Preferences میں جائیں اور کیش صاف کریں
اس اسکرپٹ کی دستاویزی تفصیلات صارف:Aafi/DelReqHandler پر درج کی جا سکتی ہیں۔ |
/**
مقامی فورک
@description: Support for quick deletions and closing of deletion requests at the Commons.
@author: [[User:Lupo]], October 2007 - January 2008
@author: [[User:DieBuche]], February 2011
@author: [[User:Rillke]], April 2012; jsHint-validation, outsourcing
@author: [[User:Perhelion]], 2016; performance tuning
@revision: 21:11, 11 August 2019 (UTC)
@license: Quadruple licensed GFDL, GPL, LGPL and Creative Commons Attribution 3.0 (CC-BY-3.0)
Choose whichever license of these you like best :-)
IE not supported
@required modules: user.options, mediawiki.util, jquery.blockUI, jquery.tipsy
* TODO: replacement for deprecated Tipsy
**/
// <nowiki>
/* global mediaWiki:false, jQuery:false, prompt:false, alert:false*/
/* jshint bitwise:true, curly:false, eqeqeq:true, forin:false, laxbreak:true */
/* eslint-env es5*/
(function ($, mw) {
'use strict';
// Guard against double inclusions // Enable the whole shebang only for sysops.
if (window.DelReqHandler || mw.config.get('wgUserGroups').indexOf('sysop') === -1) return;
// window.delReqGlobalUsage = 1;
var DRH = window.DelReqHandler = {
/* ------------------------------------------------------------------------------------------
Deletion request closing: add "[del]" and "[keep]" links to the left of the section edit
links of a deletion request. [del] and [keep] prompt for an (optional) reason, then
add "delh" and "delf" with "Deleted." or "Kept." plus the reason and signature (four tildes).
Links are added to every non-deleted image mentioned on a deletion request page. The "[del]" link
triggers deletion (auto-completed!) of the image, with a deletion summary linking to the
deletion request. If the image has a talk page, it is deleted as well. The "[keep]" link
automatically removes the "delete" template from the image page and adds the "kept" template
to the image talk page, both linking back to the deletion request.
Additional there is a quick delete link [qd] without any prompt.
------------------------------------------------------------------------------------------*/
running: [], // for race event?
titleFromHref: function (href) {
href = decodeURI(href.getAttribute('href')); // only Wikilinks
if (/^\/wiki\//.test(href)) // faster than indexOf
return RegExp.rightContext || href.substring(6);
return '';
},
spanFragC: $('<span class="navbar reqHandlerLinks2 mw-editsection-bracket"> [<a name="1" href="#">Close: Kept</a>] [<a href="#">بند کریں: حذف شد</a>]</span>')[0],
spanFragA: $('<span class="navbar reqHandlerLinks2 mw-editsection-bracket"> [<a name="1" href="#" title="Mass handle only here selected">اجتماعی حذف شدگی</a>]\
<a href="#" class="new" style="display:none"><s>تمام حذف کریں</s></a></span>')[0],
spanFragF: $('<span class="navbar reqHandlerLinks mw-editsection-bracket"> [<a name="1" href="#">رکھیں</a>] [<a href="#" class="new">حذف کریں</a>] \
[<a href="#" onclick="DelReqHandler.quickDeleteFile(event);" title="فوری حذف" class="new">qd</a>]</span>')[0],
quickDeleteFile: function (e) {
e.preventDefault();
e = e.target;
// take the function from the adjacent del link
$(e).prev().attr('title', e.title).trigger('click');
return false;
},
nextUntilH3: function (cur) {
var matched = [cur];
cur = cur.nextElementSibling;
// https://www.mediawiki.org/wiki/Heading_HTML_changes
while (cur && !(cur.nodeName === 'H3' || (cur.nodeName === 'DIV' && cur.className === 'delh') || cur.classList.contains('mw-heading3'))) {
matched.push(cur);
cur = cur.nextElementSibling;
}
return matched;
},
parse: function () {
var $content = $('#mw-content-text');
if (!$content.length) return;
if (window.delReqGlobalUsage && $.fn.badge) {
this.spanFragF.appendChild(
$('<a>', {
'title': 'GlobalUsage',
'onclick': 'DelReqHandler._onBadge(event)',
'class': 'guGU'
}).badge('?', 'inline', true).get(0));
} else if (window.delReqGlobalUsage) {
// module not ready yet, try once again
return setTimeout(function () {
DRH.parse();
setTimeout(function () {
window.delReqGlobalUsage = 0;
}, 300);
}, 200);
}
// var parent = $content.parent();
// $content.detach(); // speedup DOM manipulation?
var h3 = $content[0].getElementsByTagName('H3'),
h = h3.length,
linkReg = /نامزدگی_برائے_حذف\/[^\n]*?§ion=(T-)?\d$/;
/*
* Main DOM loop: use as less as possibly operations, especially omit jQuery,
* as we could scan over 10.000 links.
*/
while (h--) {
var th = h3[h],
discussion = [],
headLine, requestPage;
// https://www.mediawiki.org/wiki/Heading_HTML_changes
headLine = th.querySelector('span.mw-headline') || th;
th = th.closest('.mw-heading3') || th;
requestPage = th.querySelector('span.mw-editsection a');
// For some reason, not all h3 have a link, e.q.: [[Commons:Deletion_requests/Files_in_Category:Liquor_bottles]]
if (requestPage) requestPage = requestPage.getAttribute('href');
// It’s really an editlink to a deletion request subpage, and not a section
// edit for a daily subpage or something else
if (!requestPage || !linkReg.test(requestPage)) continue;
discussion = this.nextUntilH3(th); // .printfooter?
if (th.parentNode.className !== 'delh')
this.addLinks(requestPage, headLine, /* title*/ '', true, discussion);
var links = [],
d = 0,
i = discussion.length;
while (i--) {
var al = discussion[i].getElementsByTagName('A'),
l = al.length;
while (l--) {
var a = al[l];
if (a.className !== 'new') {
links[d] = a;
d++;
}
}
}
i = links.length;
// Probably last link is topic
if (i > 16 && !/^File:/.test(this.titleFromHref(links[i - 1]))) { // We have a non image link
this.addLinks(requestPage, links.pop(), '', false, discussion); // Add mass links
i--;
}
while (i--) {
var link = links[i],
title = this.titleFromHref(link);
if (/^File:/.test(title) && !/\//.test(title) && link.className !== 'internal') { // We have an image link
this.addLinks(requestPage, link, title, false, discussion);
}
}
}
mw.util.addCSS(
'.reqHandlerLinks a,.reqHandlerLinks2 a, input.reqHandlerBox {margin:0 .25em}\n\
input.reqHandlerBox {vertical-align:middle}');
// parent.append( $content );
},
/**
* Adds links to each headline.
*
* @param {string} requestPage The href property containing the URL.
* @param {HTMLElement} element The HTMLAnchorElement
* @param {string} imagePage If image href
* @param {boolean} closeRequest Keep/Del
* @param {NodeList} discussion The whole DR discussion section
*/
addLinks: function (requestPage, element, imagePage, closeRequest, discussion) {
// jQuery is too slow here! // with vars tiny faster
var frag = document.createDocumentFragment(),
span = (closeRequest ? this.spanFragC : (imagePage ? this.spanFragF : this.spanFragA)).cloneNode(1),
click = function (e) {
e.preventDefault();
// Use link.name for keep boolean // link.title for quick boolean
e = new DRH.Process(e.target, closeRequest, requestPage, imagePage, element, span, discussion);
DRH.running.push(e); // for race event?
},
lks = span.children;
lks[0].onclick = click;
lks[1].onclick = click;
frag.appendChild(span);
element.parentNode.insertBefore(frag, element.nextSibling);
},
Process: function (e, closeRequestBool, requestPage, imagePage, element, span, discussion) {
// Merge the page processing functions into our new process
$.extend(this, DRH.processHelpers);
this.keep = e.name;
var reason = this.keep ?
['keep', window.keepReqReason || 'no valid reason for deletion'] :
['delete', window.delReqReason || 'per nomination'],
why = 'Why did you decide to %1 this file?';
this.tasks = [];
this.requestPage = this.titleFromTitle(requestPage);
this.closeRequestBool = closeRequestBool;
this.imagePage = decodeURIComponent(imagePage);
this.summary = 'per [[' + this.requestPage + ']]';
this.domElements = [$(element), $(span), $(discussion)];
this.pageIDs = [];
// getToken
this.addTask('getPages');
if (closeRequestBool) {
this.reason = prompt(why.replace(/%1/, reason[0]), reason[1]);
// User canceled
if (!this.reason)
return;
this.pagesToGet = [this.requestPage];
this.sectionCount = this.getSectionCount(requestPage);
this.addTask('closeRequest');
} else if (this.imagePage) {
this.pagesToGet = [this.imagePage];
this.redirect = this.domElements[0].hasClass('mw-redirect');
if (this.keep) {
this.addTask('markAsKept');
this.addTask('getDate'); // runs addKeepToTalk
this.summary = 'Kept ' + this.summary;
} else {
this.addTask('deleteFile');
// this.addTask('nothing'); // ?
}
this.summary = (e.title === 'QuickDelete') ? this.summary : prompt('Summary:', this.summary);
// User canceled
if (!this.summary)
return;
} else {
this.tasks.pop(); // remove normal getPages
// Merge more functions into our new process
$.extend(this, {
setMassCheckBoxes: DRH.setMassCheckBoxes,
processAll: DRH.processAll,
processAllChunks: DRH.processAllChunks
});
return this.setMassCheckBoxes();
}
this.showProgress();
this.addTask('fakeReload');
this.nextTask();
},
setMassCheckBoxes: function () {
var checkFrag = $('<input class="reqHandlerBox" type="checkbox" checked>')[0],
$lks = this.domElements[1].children(),
$lk2 = $lks.eq(1);
// e.preventDefault();
if ($lk2.is(':hidden')) {
$lk2.after('] ');
$lk2.before(' [');
$lk2.show();
$lks.eq(0).text('Keep all');
this.domElements[1].css('background-color', '#FB9');
// Get all page links from relevant discussion section
$(this.domElements[2]).find('.reqHandlerLinks').each(function (a) {
var li = this.parentNode;
if (li.tagName === 'LI') {
a = li.firstChild;
if (a.tagName === 'A' && a.className !== 'new')
li.insertBefore(checkFrag.cloneNode(), a);
}
});
delete DRH.running[0];
} else { this.processAll(); }
// return false;
},
processAll: function () {
var allPages = [],
cSize = 50; // Max chunk size for API, bots 500
this.chunkPagesToGet = []; // list of arrays
if (this.keep)
this.processTasks = ['markAsKept']; // 'getDate' add msg on talk on mass?
else
this.processTasks = ['deleteFile'];
this.summary = prompt('Summary:', this.summary);
if (!this.summary) {
if (this.domElements[3]) this.domElements[3].unblock();
return;
}
// :checkbox
$(this.domElements[2]).find('input.reqHandlerBox:checked').each(function (a) {
a = DRH.titleFromHref(this.nextSibling);
if (a) allPages.push(a);
this.parentNode.removeChild(this);
});
// this.redirect = 1;
// Make chunks due the API limit
for (var p = 0; p < allPages.length; p += cSize)
this.chunkPagesToGet.push(allPages.slice(p, p + cSize));
this.showProgress();
this.addTask('processAllChunks');
this.nextTask();
},
processAllChunks: function () {
this.pagesToGet = this.chunkPagesToGet.pop();
if (this.pagesToGet) {
this.addTask('getPages');
this.addTask(this.processTasks[0]); // currently only one
// this.tasks.concat(this.processTasks);
this.addTask('processAllChunks');
} else { this.addTask('fakeReload'); }
this.nextTask();
},
_onBadge: function (e) {
var query = {},
$gu = $(e.target).closest('a.guGU'),
t = $gu.closest('span.reqHandlerLinks').prev('a');
t = window.DelReqHandler.titleFromHref(t[0]);
$gu[0].onclick = null;
if (!t) return;
t = decodeURIComponent(t).replace(/_/g, ' ');
query[t] = $gu;
$gu = mw.libs.GlobalUsage(5, 5);
$gu.tipsyGravity = $('body').is('.rtl') ? 'sw' : 'se';
$gu.query(query);
},
setup: function () {
var title = mw.config.get('wgTitle');
if (mw.config.get('wgNamespaceNumber') === 4 &&
/^Deletion requests\/|\/Deletion requests$/.test(title) &&
mw.config.get('wgAction') === 'view' &&
document.URL.search(/[?&]oldid=/) === -1) {
// We’re on COM:DEL or one of its daily subpages
// Don’t do anything if we're not viewing the current version of the page
var ext = ['user.options', 'mediawiki.util'];
if (window.delReqGlobalUsage)
ext.push('ext.gadget.jquery.badge');
$.when(mw.loader.using(ext), $.ready).done(function () {
DRH.parse();
setTimeout(function () { // not needed at startup
ext = ['ext.gadget.jquery.blockUI'];
if (window.delReqGlobalUsage)
ext = ext.concat(['ext.gadget.GlobalUsage', 'ext.gadget.tipsyDeprecated']);
mw.loader.load(ext);
}, 500);
});
}
}
};
DRH.processHelpers = {
titleFromTitle: function (title) {
if (title) {
title = mw.util.getParamValue('title', title);
if (title)
return title.replace(/_/g, ' ');
}
return '';
},
getSectionCount: function (title) {
if (title) {
title = mw.util.getParamValue('section', title);
if (title) {
title = parseInt(title.replace(/T-/g, ''));
if (!isNaN(title))
return title;
}
}
return '';
},
getPages: function () {
var query = {
action: 'query',
prop: 'revisions|info',
rvprop: 'content|timestamp',
// inprop: 'talkid', not needed if we only handle files
titles: this.pagesToGet.join('|'),
redirects: this.redirect,
meta: 'tokens'
};
this.doAPICall(query, 'getPagesCallback');
},
getPagesCallback: function (result) {
var pages = result.query.pages,
task = this.tasks.shift();
this.unknownResult = {};
this.imagePageResult = {};
this.requestPageResult = {};
// The edittoken only changes between logins
this.edittoken = result.query.tokens.csrftoken;
for (var id in pages) { // there should be only one, but we don't know it's ID
if (pages.hasOwnProperty(id)) {
var page = pages[id];
// FIXME better fail handling
if (!page.revisions) continue;
this.pageIDs.push(id); // For mulitple pages
var type = 'unknown';
switch (page.ns) {
case 6:
type = 'imagePage';
// if (this.redirect) this.imagePage = page.title;
break;
case 4:
type = 'requestPage';
break;
}
this.tasks.unshift(task); // Add much as pages
this[type + 'Result'][id] = {
title: page.title,
pageContent: page.revisions[0]['*'],
starttimestamp: page.starttimestamp,
timestamp: page.revisions[0].timestamp
};
}
}
this.nextTask();
},
closeRequest: function () {
// (we always load the whole page)
var text = this.requestPageResult[this.pageIDs.pop()].pageContent,
watchFor = '<noinclude>[[Category:MobileUpload-related deletion requests',
c = 0,
hRegex = /^=+.+=+.*$/gm,
sec = ']]</noinclude>';
this.decision = this.keep ? 'Kept' : 'Deleted';
text = text.replace(watchFor + sec, watchFor + '/' + this.decision.toLowerCase() + sec);
// Multiple nominations
if ((sec = this.sectionCount)) {
while ((watchFor = hRegex.exec(text)) !== null) {
c++;
if (c === sec) {
sec = watchFor.index;
break;
}
}
c = 0;
if (watchFor[0]) {
c = text.indexOf('{{delh}}\n', hRegex.lastIndex);
if (c === -1) c = text.indexOf(watchFor[0], hRegex.lastIndex);
if (c !== -1) {
// closed section at end
if (!(watchFor = text.slice(c))) c = 0;
} else { c = 0; } // last section so skip to default
}
}
if (!c) c = undefined;
if (!sec && !c) // Check anyway for a second previous nomination
sec = text.lastIndexOf('{{delf}}\n') + 9; // Additional more accurately: text.substr(sec).search(/^==+/m) but not really needed
text = (sec > 51 || c) ? // minimum text-size
text.slice(0, sec) + '{{delh}}\n' + text.slice(sec, c).trim() :
'{{delh}}\n' + text.trim(); // the whole page
text += '\n----\n';
// Add dashes on 'lesser' individual signatures
sec = (mw.user.options.get('fancysig') && mw.user.options.get('nickname').search(/^[ ']*\[\[/) !== 0) ?
'' : '--';
if (this.reason) {
this.decision += ':';
this.reason = this.reason.replace(/[.\s-]*$/, '. ');
} else { this.decision += '.'; }
text += '\'\'\'' + this.decision + '\'\'\' ' + this.reason + sec + '~~~~\n{{delf}}\n';
if (c) text += watchFor;
this.savePage({
title: this.requestPage,
text: text,
summary: this.decision + ' ' + this.reason,
editType: 'text'
}, 'nextTask');
},
markAsKept: function () {
var text = this.pageIDs.pop(); // id
this.imagePage = this.imagePageResult[text].title;
this.imageTalkPage = this.imagePage.replace(/^File:/, 'File_talk:');
text = this.removeTemplate(this.imagePageResult[text].pageContent);
if (text) {
this.savePage({
text: text,
title: this.imagePage, // pageid: id,
summary: this.summary,
editType: 'text'
}, 'nextTask');
} else { this.nextTask(); }
},
removeTemplate: function (text) {
var start = text.search(/\{\{[dD]elete/),
level = 0,
curr = start + 2,
end = 0,
opening = -1,
closing = -1;
if (start >= 0) {
while (curr < text.length && !end) {
opening = text.indexOf('{{', curr);
closing = text.indexOf('}}', curr);
if (opening >= 0 && opening < closing) {
level++;
curr = opening + 2;
} else {
if (closing < 0) {
// No closing braces found
curr = text.length;
} else {
if (level > 0)
level--;
else
end = closing + 2;
curr = closing + 2;
}
}
}
if (end) {
// Also strip whitespace after the "delete" template
end = text.substring(end).replace(/^\s+/, '');
return start ? text.substring(0, start) + end : end;
}
}
end = 'Couldn’t remove the {{delete}} template, please check the ' + this.imagePage + ' manually.';
mw.log.warn(end);
if (!this.processAllChunks) alert(end);
},
// Get start date of the DR
getDate: function (c) {
var query = {
action: 'query',
prop: 'revisions',
titles: this.requestPage,
// rvprop: 'comment|timestamp',
rvlimit: 50
};
if (c)
query.rvcontinue = c;
this.doAPICall(query, 'addKeepToTalk');
},
addKeepToTalk: function (result) {
var cont = result['continue']; // parse error on this line if not as bracket selector
if (!result.hasOwnProperty('batchcomplete') && cont && cont.rvcontinue)
cont = cont.rvcontinue;
var date = '',
pages = result.query.pages,
rev = {},
revLen;
for (var id in pages) {
// There should be only one, but we don't know it's ID
if (pages.hasOwnProperty(id) && pages[id].revisions) {
rev = pages[id].revisions;
revLen = rev.length;
for (var i = 0; i < revLen; i++) {
if (rev[i].comment === 'Starting deletion request') {
date = rev[i].timestamp;
if (date)
break;
}
}
}
}
if (!date && cont) { this.getDate(cont); } else {
if (!date) { // Fallback first edit if no appropriate comment?
date = rev[revLen - 1].timestamp;
}
// Extract year, month, and day from the timestamp.
date = date.substr(0, 4) + '-' + date.substr(5, 2) + '-' + date.substr(8, 2);
this.savePage({
title: this.imageTalkPage,
text: '{{kept|' + date + '|' + this.requestPage + '}}\n',
summary: 'Adding {{kept}}',
editType: 'prependtext'
}, 'nextTask');
}
},
reload: function () {
window.location.reload();
},
fakeReload: function () {
var dE = this.domElements;
if (dE[3]) dE[3].unblock(); // showProgress
// Remove links with keep width for following links position
dE[1].css('opacity', '0').find('a').removeAttr('href onclick title').css('cursor', 'default');
if (this.closeRequestBool) {
dE[3].toggleClass('delh delreqworking');
dE[2].eq(0).before('<i>This deletion debate is now closed. Please do not make any edits to this archive.</i>');
dE[2].eq(-1).after('<br><span class="success">Saved successfully.\
<br>This is just an approximate rendering. Reload to see the actual request.</span>');
dE[2].eq(-1).after('<b>' + this.decision + '</b> ' + this.reason + ' --' + mw.config.get('wgUserName'));
dE[2].eq(-1).after('<hr>');
} else {
if (!this.keep)
dE[0].addClass('new'); // Color link red
}
},
/**
* 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.
**/
// 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);
}
},
deleteFile: function () {
var imagePage = this.imagePageResult[this.pageIDs.pop()].title;
var edit = {
action: 'delete',
reason: this.summary,
title: imagePage,
recreate: ''
};
this.doAPICall(edit, 'nothing');
edit = {
action: 'delete',
reason: 'Talk page of deleted image',
title: imagePage.replace(/^File:/, 'File talk:'),
recreate: ''
};
this.doAPICall(edit, 'nextTask', true);
},
savePage: function (page, callback) {
var edit = {
action: 'edit',
summary: page.summary,
notminor: 1,
watchlist: window.AjaxDeleteWatchFile ? 'watch' : 'nochange',
title: page.title
};
edit[page.editType] = page.text;
this.doAPICall(edit, callback);
},
fail: function (e) {
mw.notify(e, { title: 'DelReqHandler', type: 'error' });
},
doAPICall: function (params, callback, ignoreErrors) {
var k = this;
params.format = 'json';
params.token = this.edittoken;
$.ajax({
url: mw.util.wikiScript('api'),
cache: false,
dataType: 'json',
data: params,
type: 'POST',
success: function (result, status, x) {
if (ignoreErrors) {
k[callback](result);
return;
}
if (!result)
return k.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 () {
k.doAPICall(params, callback);
}, 500);
}
if (result.error)
return k.fail('API request failed (' + result.error.code + '): ' + result.error.info);
k[callback](result);
},
error: function (x, status, error) {
return k.fail('API request returned code ' + x.status + ' ' + status + 'Error code is ' + error);
}
});
},
showProgress: function () {
var dE = this.domElements;
if (this.closeRequestBool) {
dE[2].wrapAll('<div class="delreqworking">');
dE[3] = dE[2].parent('.delreqworking');
dE[3].block({
message: '<img src="https://upload.wikimedia.org/wikipedia/commons/3/39/Spinning_wheel_throbber_blue.gif"/> Closing request…',
css: {
border: '3px solid #9C3',
fontSize: '135%'
}
});
} else {
dE[3] = dE[0].parent();
dE[3].block({
message: '<img src="https://upload.wikimedia.org/wikipedia/commons/f/f8/Ajax-loader%282%29.gif"/> Working…',
css: {
color: '#9C3',
fontWeight: 'bold',
background: 'none',
border: 'none'
}
});
}
},
nothing: function () {}
};
DRH.setup();
}(jQuery, mediaWiki));
// </nowiki> EOF