User:~riley/script.js
(Redirected from User:Riley Huntley/script.js)
Note: After saving, you have to bypass your browser's cache to see the changes. Internet Explorer: press Ctrl-F5, Mozilla: hold down Shift while clicking Reload (or press Ctrl-Shift-R), Opera/Konqueror: press F5, Safari: hold down Shift + Alt while clicking Reload, Chrome: hold down Shift while clicking Reload.
Documentation for this user script can be added at User:~riley/script. |
/**
Support for quick deletions and closing of deletion requests at the Commons.
Authors: [[User:Lupo]], October 2007 - January 2008,
[[User:DieBuche]], February 2011
[[User:Perhelion]], January 2016
Revision: 23:02, 25 January 2016 (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
**/
//<nowiki>
/*global mediaWiki:false, jQuery:false, prompt:false, alert:false*/
/*jshint bitwise:true, curly:false, eqeqeq:true, forin:false, laxbreak:true,
trailing:true, undef:true, unused:true, white:false, smarttabs:true */
(function($, mw) {
'use strict';
// Guard against double inclusions // Enable the whole shebang only for sysops.
if ('object' === typeof DelReqHandler || -1 === $.inArray('rollbacker', mw.config.get('wgUserGroups'))) return;
var DelReqHandler = 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.
------------------------------------------------------------------------------------------*/
running: [],
parse: function () {
var $content = $('#bodyContent, #mw_contentholder');
if (!$content.length) return;
//mw.util.addCSS('.reqHandlerLinks {font-size: 85%;}'); replaced by class navbar
var linkRegex = /Commons:Deletion_requests\/.*?§ion=(T-)?[1-4]$/;
$content.find('h3').each(function () {
var $t = $(this);
var headLine = $t.find('span.mw-headline').eq(0);
var requestHref = $t.find('span.mw-editsection a').not('.mw-editsection-visualeditor').eq(0).attr('href');
// It's really an edit lk to a deletion request subpage, and not a section
// edit for a daily subpage or something else
if (!linkRegex.test(requestHref)) return true;
var headLink = headLine.find('a').not('.new').eq(0);
var title = (headLink.length) ? DelReqHandler.titleFromHref(headLink.attr('href')) : "";
var requestPage = DelReqHandler.titleFromHref(requestHref);
var discussion = $t.nextUntil('h3, .printfooter, .delh');
var wholeDiscussion = discussion.add($t);
if (!$t.parents('.delh').length)
DelReqHandler.addLinks(requestPage, headLine, title, true, wholeDiscussion);
discussion.find('a').not('.new').add(headLink).each(function () {
var title = DelReqHandler.titleFromHref(this.href);
if (title.indexOf('File:') === 0 && title.indexOf('/') < 0)
//We have an image link
DelReqHandler.addLinks(requestPage, $(this), title, false, wholeDiscussion);
});
});
},
titleFromHref: function (href) {
if (href) {
var title = mw.util.getParamValue('title', href);
if (title) return title.replace(/_/g, ' ');
var prefix = mw.config.get('wgArticlePath').replace('$1', "");
// Fully expanded URL?
if (href.indexOf(prefix) !== 0) prefix = mw.config.get('wgServer') + prefix;
if (href.indexOf(prefix) !== 0 && prefix.indexOf('//') === 0) prefix = document.location.protocol + prefix; // protocol-relative wgServer?
if (href.indexOf(prefix) === 0) return decodeURIComponent(href.substring(prefix.length)).replace(/_/g, ' ');
}
return "";
},
addLinks: function (requestPage, location, imagePage, closeRequest, discussion) {
var span = $('<span/>', { 'class': 'navbar' });
function _click (e) {
// Use link.name for keep boolean
e.preventDefault();
e = new DelReqHandler.process(e.target.name, closeRequest, requestPage, imagePage, [location, span, discussion]);
DelReqHandler.running.push(e);
}
var linkK = $('<a/>', { href: '#', 'name': 1, 'click': _click }),
linkD = $('<a/>', { href: '#', 'click': _click });
if (closeRequest) {
linkK.text('Close: Kept');
linkD.text('Close: Deleted');
span.addClass('reqHandlerLinks2');
} else {
linkK.text('keep');
linkD.text('del');
span.addClass('reqHandlerLinks');
}
span.append(' [').append(linkK).append('] [').append(linkD).append(']');
location.after(span);
},
setup: function () {
if (mw.config.get('wgPageName').indexOf('Commons:Deletion_requests/') !== -1 && 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
this.parse();
}
},
process: function (keep, closeRequestBool, requestPage, imagePage, domElements) {
//Merge the page processing functions into our new process
$.extend(this, DelReqHandler.processHelpers);
var delReqReason = window.delReqReason || "per nomination";
var keepReqReason = window.keepReqReason || "no valid reason for deletion";
this.tasks = [];
this.requestPage = requestPage.replace(/_/g, ' ');
this.keep = keep;
this.closeRequestBool = closeRequestBool;
this.imagePage = imagePage;
this.imageTalkPage = imagePage.replace(/^File:/, 'File talk:');
this.summary = 'Per [[' + requestPage + ']]';
this.domElements = domElements;
//getToken
this.addTask('getPages');
if (closeRequestBool) {
if (keep) {
this.reason = prompt('Why did you decide to keep this file?', keepReqReason);
//User canceled
if (!this.reason) return;
this.pagesToGet = [requestPage, imagePage];
//this.addTask('markAsKept');
//this.addTask('getDate');
} else {
this.reason = prompt('Why did you decide to delete this file?', delReqReason);
//User canceled
if (!this.reason) return;
this.pagesToGet = [requestPage];
//if (imagePage != "") {
// this.addTask('deleteFile');
// this.addTask('deleteFileTalk');
//}
}
this.addTask('closeRequest');
} else {
this.pagesToGet = [imagePage];
if (keep) {
this.addTask('markAsKept');
this.addTask('getDate');
//first letter lowercase
this.summary = 'Kept p' + this.summary.slice(1);
} else {
this.addTask('deleteFile');
this.addTask('nothing');
}
this.summary = prompt("Summary:", this.summary);
//User canceled
if (!this.summary) return;
}
this.addTask('fakeReload');
this.nextTask();
this.showProgress();
}
};
DelReqHandler.processHelpers = {
getPages: function () {
var query = {
action: 'query',
prop: 'revisions|info',
rvprop: 'content|timestamp',
intoken: 'edit',
titles: this.pagesToGet.join('|')
};
this.doAPICall(query, 'getPagesCallback');
},
getPagesCallback: function (result) {
var pages = result.query.pages;
for (var id in pages) { // there should be only one, but we don't know it's ID
if (pages.hasOwnProperty(id)) {
// The edittoken only changes between logins
this.edittoken = pages[id].edittoken;
var type;
switch (pages[id].title) {
case this.imagePage:
type = 'imagePage';
break;
case this.requestPage:
type = 'requestPage';
break;
default:
type = 'unknown';
break;
}
this[type + 'Result'] = {
pageContent: pages[id].revisions[0]['*'],
starttimestamp: pages[id].starttimestamp,
timestamp: pages[id].revisions[0].timestamp
};
}
}
this.nextTask();
},
closeRequest: function () {
var text = this.requestPageResult.pageContent,
watchFor = '<noinclude>[[Category:MobileUpload-related deletion requests', replace = ']]</noinclude>';
this.decision = (this.keep) ? 'Kept' : 'Deleted';
text = text.replace(watchFor + replace, watchFor + '/' + this.decision.toLowerCase() + replace);
// Check for second nomination (we always load the full page)
var sec = text.lastIndexOf('{{delf}}\n') + 9; // Additional more accurately: text.substr(sec).search(/^==+/m) but not really needed
text = (sec > 51) ? // minimum text-size
text.slice(0, sec) + '{{delh}}\n' + $.trim(text.slice(sec)) : '{{delh}}\n' + $.trim(text);
text += '\n----\n';
// Add dashes on 'lesser' individual signatures
var uSig = (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 + uSig + '~~\~~\n{{delf}}';
var page = {
title: this.requestPage,
text: text,
summary: this.decision + ' ' + this.reason,
editType: 'text'
};
this.savePage(page, 'nextTask');
},
markAsKept: function () {
var text = this.imagePageResult.pageContent;
text = this.removeTemplate(text);
var page = {
title: this.imagePage,
text: text,
summary: this.summary,
editType: 'text'
};
this.savePage(page, 'nextTask');
},
removeTemplate: function (text) {
var start = text.search(/\{\{[dD]elete/);
if (start >= 0) {
var level = 0;
var curr = start + 2;
var end = 0;
while (curr < text.length && end === 0) {
var opening = text.indexOf('{{', curr);
var closing = text.indexOf('}}', curr);
if (opening >= 0 && opening < closing) {
level = level + 1;
curr = opening + 2;
} else {
if (closing < 0) {
// No closing braces found
curr = text.length;
} else {
if (level > 0) level = level - 1;
else end = closing + 2;
curr = closing + 2;
}
}
}
if (end > start) {
// Also strip whitespace after the "delete" template
if (start > 0)
text = text.substring(0, start) + text.substring(end).replace(/^\s*/, '');
else
text = text.substring(end).replace(/^\s*/, '');
return text;
}
}
alert('Couldn’t remove the {{delete}} template, please check the file ' + this.imagePage + ' manually.');
return text;
},
getDate: function () {
var query = {
action: 'query',
prop: 'revisions',
rvlimit: 1,
rvprop: 'timestamp',
rvdir: 'newer',
titles: this.requestPage
};
this.doAPICall(query, 'addKeepToTalk');
},
addKeepToTalk: function (result) {
var pages = result.query.pages;
var date = "";
for (var id in pages) {
if (pages.hasOwnProperty(id)) {
// there should be only one, but we don't know it's ID
var ts = pages[id].revisions[0].timestamp;
if (ts) {
// Extract year, month, and day from the timestamp.
// We don't care about the exact time.
var year = ts.substr(0, 4);
var month = ts.substr(5, 2);
var day = ts.substr(8, 2);
date = year + '-' + month + '-' + day;
}
}
}
var page = {
title: this.imageTalkPage,
text: '{{kept|' + date + '|' + this.requestPage + '}}\n',
summary: 'Adding {{kept}}',
editType: 'prependtext'
};
this.savePage(page, 'nextTask');
},
reload: function () {
window.location.reload();
},
fakeReload: function () {
var dE = this.domElements;
dE[3].unblock();
//Remove links
dE[1].remove();
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 style="color:green">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 {
//Color link red
if (!this.keep) dE[0].addClass('new');
}
},
apiURL: mw.util.wikiScript('api'),
/**
** 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 edit = {
action: 'delete',
reason: this.summary,
title: this.imagePage,
token: this.edittoken,
recreate: ''
};
this.doAPICall(edit, 'nextTask');
edit = {
action: 'delete',
reason: "Talk page of deleted image",
title: this.imageTalkPage,
token: this.edittoken,
recreate: ''
};
this.doAPICall(edit, 'nextTask', true);
},
savePage: function (page, callback) {
var edit = {
action: 'edit',
summary: page.summary,
title: page.title,
token: this.edittoken
};
edit[page.editType] = page.text;
this.doAPICall(edit, callback);
},
fail: function (e) {
alert(e);
},
doAPICall: function (params, callback, ignoreErrors) {
var k = this;
params.format = 'json';
$.ajax({
url: this.apiURL,
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 () {
if (this.closeRequestBool){
this.domElements[2].wrapAll('<div class="delreqworking">');
this.domElements[3] = this.domElements[2].parent('.delreqworking');
this.domElements[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 {
this.domElements[3] = this.domElements[0].parent();
this.domElements[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 () {
//nothing
}
};
mw.loader.using('user.options', function () { $(DelReqHandler.setup()); });
})(jQuery, mediaWiki);
//</nowiki>