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.
/**
  Quick voting on deletion requests.

  IE not supported, probably
 * 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
if ('object' === typeof DelReqHandler) return;

var DelReqHandler = window.DelReqHandler = {
	/*------------------------------------------------------------------------------------------
	Deletion request: add "[del]" links to the left of the section edit
	links of a deletion request. [del] prompt for an (optional) reason, then 
	add the reason and signature (four tildes).
	------------------------------------------------------------------------------------------*/
	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\/.*?&section=(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('Files uploaded') === 0) && title.indexOf('/') < 0)
					//We have an image link or group of files in DR
					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, closeRequest, voteD, 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, voteD, requestPage, [location, span, discussion]);
			DelReqHandler.running.push(e);
		}
		
		var linkK = $('<a/>', { href: '#', 'name': 1, 'click': _click }),
			linkD = $('<a/>', { href: '#', 'click': _click });
			linkK.text('Del');
			linkD.text('Close: Deleted');
		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, del, closeRequestBool, voteDBool, requestPage) {
		//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.del = del;
		this.closeRequestBool = closeRequestBool;
		this.voteDBool = voteDBool;
		this.summary = 'Per [[' + requestPage + ']]';
		//getToken
		this.addTask('getPages');
		if (closeRequestBool) {
			if (keep) {
				this.reason = prompt('Why do you want to delete this file?', delReqReason);
				//User canceled
				if (!this.reason) return;
				this.pagesToGet = [requestPage];
			}
			this.addTask('closeRequest');
		} else if (voteDBool) {
		    if (del) {
				this.reason = prompt('Result?', delReqReason);
				if (!this.reason) return;
				this.pagesToGet = [requestPage];
			}
			this.addTask('voteD');
		} else {
			this.pagesToGet = [requestPage];
			this.summary = prompt("Summary:", this.summary);
			//User canceled
			if (!this.summary) return;

		}
		this.nextTask();
	}
};
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.requestPage:
					type = 'requestPage';
					break;
				default:
					type = 'unknown';
					break;
				}
				this[type + 'Result'] = {
					pageContent: pages[id].revisions[0]['*']
				};
			}
		}
		this.nextTask();
	},
	closeRequest: function () {
		var text = this.requestPageResult.pageContent,
			watchFor = '<noinclude>[[Category:MobileUpload-related deletion requests', replace = ']]</noinclude>';
		this.decision = (this.del) ? 'Deleted' : 'Kept';
		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 + '<nowiki>~~~~</nowiki>\n{{delf}}';

		var page = {
			title: this.requestPage,
			text: text,
			summary: this.decision + ' ' + this.reason,
			editType: 'text'
		};
		this.savePage(page, 'nextTask');
	},
	voteD: function () {
		var textvoted = this.requestPageResult.pageContent;
		this.decision = (this.keep) ? 'Delete' : 'Keep';
		// Add dashes on 'lesser' individual signatures
		var uSig2 = (mw.user.options.get('fancysig') && mw.user.options.get('nickname').search(/^[ ']*\[\[/) !== 0)?
			'' : '--';
		if (this.reason) {
			this.reason = this.reason.replace(/[.\s-]*$/, '. ');
		        textvoted += "\n*{{vd}}: " + this.reason + uSig2 + '<nowiki>~~~~</nowiki>';
		}
		else textvoted += "\n*{{vd}}. " + uSig2 + '<nowiki>~~~~</nowiki>';

		var page1 = {
			title: this.requestPage,
			text: textvoted,
			summary: this.decision + ' (using [[User:XXN/QuickVoteDR.js|script]])',
			editType: 'text'
		};
		this.savePage(page1, 'nextTask');
	},

	apiURL: mw.util.wikiScript('api'),
	currentTask: '',
	addTask: function (task) {
		this.tasks.push(task);
	},
	nextTask: function () {
		var task = this.currentTask = this.tasks.shift();
		try {
			this[task]();
		} catch (e) {
			this.fail(e);
		}
	},
	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);
			}
		});
	},
	nothing: function () {
		//nothing
	}
};

mw.loader.using('user.options', function () { $(DelReqHandler.setup()); });

})(jQuery, mediaWiki);
//</nowiki>