MediaWiki:Gadget-dashboard.UndeletionRequester.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.
/**
**  Undeletion request helper for Wikimedia Commons
**  @description
**     Easily post undeletion requests
**
**  @autor Rillke, 2012
**  @license GPL v.3
**
**  Adds an UI to [[Template:Dashboard/Widgets/Post undeletion request]].
**  Also a dashboard widget.
**
**/
// Invoke automated jsHint-validation on save: A feature on WikimediaCommons
// Interested? See [[c:MediaWiki:JSValidator.js]].
// <nowiki>
/* jshint curly:false, laxbreak:true */
/* global jQuery:false, mediaWiki:false*/
(function ($, mw) {
'use strict';

$.when(mw.loader.using([
	'jquery.ui',
	'ext.gadget.tipsyDeprecated',
	'ext.gadget.AjaxQuickDelete',
	'mediawiki.api',
	'mediawiki.util'
]), $.ready).then(function () {
// 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': ' ' });
	};

	mw.util.addCSS(
		'.undelete-item { margin:0.5em; padding:4px; }\n\
		.undelete-item-heading { padding:4px; }\n\
		.undelete-option-details { padding-left:4px; }\n\
		.undelete-recycle { background:url(//upload.wikimedia.org/wikipedia/commons/4/47/Crystal_Clear_mimetype_recycled.png) no-repeat center }\n\
		#AjaxUndeleteItems { border:1px dotted #55a; max-height:350px; min-height:200px; overflow:auto; background-color:#fff; }\n\
		#AjaxUndeleteOptions { min-height:125px; border:1px dotted #55a; margin-top:0.5em; }\n'
	);

	var AQD,
		udh,
		__onRemoveClick = function () {
			var $el = $(this).parents('.undelete-item');
			AQD.secureCall('uRmPageItem', $el);
			$el.remove();
		},
		// Just some HTML
		$uC = $('<div>').attr({ id: 'AjaxUndeleteContainer', title: 'Request undeletion of deleted pages and files – Recycling protects the environment' }),
		$uO_desc = $('<p>', { text: 'Add items to the list of pages to be restored by' }).appendTo($uC),
		$uO = $('<div>').attr({ id: 'AjaxUndeleteOptions' }).appendTo($uC),
		$uI_desc = $('<p>', { text: 'Pages that should be restored:' }).appendTo($uC),
		$uI = $('<div>').attr({ 'id': 'AjaxUndeleteItems', 'class': 'undelete-recycle', 'title': 'Add pages or files using the controls above.' }).appendTo($uC),
		// Options
		$uOI = $('<div>').attr({ id: 'AjaxUndeleteOptionInput' }).appendTo($uO),
		$uOU = $('<div>').attr({ id: 'AjaxUndeleteOptionUploader' }).appendTo($uO),
		$uOD = $('<div>').attr({ id: 'AjaxUndeleteOptionDeleter' }).appendTo($uO),
		// Construct Option UI
		// direct input
		$uOI_r = $('<input name="undelAddOption" id="optionInputOption" type="radio" checked="checked" value="page" />').appendTo($uOI),
		$uOI_rl = $('<label>').attr({ 'for': 'optionInputOption' }).text('File name or URL').appendTo($uOI),
		$uOI_d = $('<div>').attr({ 'class': 'undelete-option-details' }).appendTo($uOI),
		$uOI_t = $('<input>').attr({ type: 'text', size: 70, maxlength: 500, placeholder: 'Full page name or URL of the page or file to restore' }).appendTo($uOI_d),
		$uOI_b = $('<button>').text('Add').button({ icons: { primary: 'ui-icon-circle-plus' } }).appendTo($uOI_d),
		// uploader
		$uOU_r = $('<input name="undelAddOption" id="optionUploaderOption" type="radio" value="uploader"/>').appendTo($uOU),
		$uOU_rl = $('<label>').attr({ 'for': 'optionUploaderOption' }).text('Uploader').appendTo($uOU),
		$uOU_d = $('<div>').attr({ 'class': 'undelete-option-details' }).css('display', 'none').appendTo($uOU),
		$uOU_t = $('<input>').attr({ type: 'text', size: 20, maxlength: 100, placeholder: 'Uploader (user name)' }).appendTo($uOU_d),
		$uOU_df = $('<input>').attr({ type: 'text', size: 20, maxlength: 20, placeholder: 'From YYYY-MM-DD' }).appendTo($uOU_d),
		$uOU_dt = $('<input>').attr({ type: 'text', size: 20, maxlength: 20, placeholder: 'To YYYY-MM-DD' }).appendTo($uOU_d),
		$uOU_b = $('<button>').text('Add').button({ icons: { primary: 'ui-icon-circle-plus' } }).appendTo($uOU_d),
		// deleter
		$uOD_r = $('<input name="undelAddOption" id="optionDeleterOption" type="radio" value="deleter"/>').appendTo($uOD),
		$uOD_rl = $('<label>').attr({ 'for': 'optionDeleterOption' }).text('Deleting Administrator').appendTo($uOD),
		$uOD_d = $('<div>').attr({ 'class': 'undelete-option-details' }).css('display', 'none').appendTo($uOD),
		$uOD_t = $('<input>').attr({ type: 'text', size: 20, maxlength: 100, placeholder: 'Deleting administrator' }).appendTo($uOD_d),
		$uOD_df = $('<input>').attr({ type: 'text', size: 20, maxlength: 20, placeholder: 'From YYYY-MM-DD' }).appendTo($uOD_d),
		$uOD_dt = $('<input>').attr({ type: 'text', size: 20, maxlength: 20, placeholder: 'To YYYY-MM-DD' }).appendTo($uOD_d),
		$uOD_b = $('<button>').text('Add').button({ icons: { primary: 'ui-icon-circle-plus' } }).appendTo($uOD_d),
		// reason
		$uR = $('<textarea>', { text: 'Reason: ', rows: 4, css: { width: '99%' }, title: 'Fill in the reason: Why should these pages be restored?' }).appendTo($uC),
		$uR_ParseL = $('<span>', { text: 'Preview:' }).appendTo($uC),
		$uR_Parse = $('<div>').attr({ id: 'AjaxQuestionParse' }).appendTo($uC),

		// Templates for page items
		$uIT = $('<div>').attr({ 'class': 'undelete-item ui-widget ui-widget-content ui-helper-clearfix ui-corner-all' }),
		$uITH = $('<div>').attr({ 'class': 'undelete-item-heading ui-widget-header ui-corner-all' }).appendTo($uIT),
		$uITC = $('<div>').attr({ 'class': 'undelete-item-content' }).appendTo($uIT),
		$rmBtn = $('<button>').text('Remove').css('float', 'right'); // We can't append it right now because when removing parent and attempting to add a new item button style is gone.

	udh = window.undeleteHelper = {
		uVersion: '0.0.4.3',
		uPagesToRestore: {},
		uRmPageItem: function ($item) {
			delete this.uPagesToRestore[$item.data('udh_name')];
		},
		uAddPageItem: function (pagename, content, uploader, deleter) {
			pagename = pagename.replace(/_/g, ' ');
			if (pagename in this.uPagesToRestore) {
				var $item = this.uPagesToRestore[pagename].ui;
				$uI.stop().clearQueue().animate({ scrollTop: $item.position().top - $uI.position().top }, 600);
				udh.hIfEmpty(this.uPagesToRestore[pagename].ui, '', 1);
				return;
			}
			var pg = this.uPagesToRestore[pagename] = {
				content: content,
				uploader: uploader,
				deleter: deleter,
				ui: $uIT.clone().find('.undelete-item-heading').text(pagename).append($rmBtn.clone().button({ icons: { primary: 'ui-icon-circle-minus' } }).click(__onRemoveClick)).parent().find('.undelete-item-content').text(content).parent().attr('title', pagename).data('udh_name', pagename)
			};
			$uI.append(pg.ui);

			var __onContentParsed = function (r) {
				if (pg && pg.ui) pg.ui.find('.undelete-item-content').text('').append(r);
			};
			mw.libs.commons.api.parse(content, mw.config.get('wgUserLanguage'), mw.config.get('wgPageName'), __onContentParsed);
		},
		uFormatTimestamp: function (t) {
			return '{{ISOdate|' + t.replace(/T/, ' ').replace(/Z/, '') + '}}';
		},
		uBindEvents: function () {
		// Autocomplete
			var nsUser = mw.config.get('wgFormattedNamespaces')[2],
				rxUser = new RegExp('^' + nsUser + ':', 'i'),
				lastXHR = 0,
				pLastCB = 0,
				delayXHR = 500,
				didXHR = function (result, pCallback) {
					var searchTerms = [];
					if (!result) {
						if (typeof pCallback === 'function') pCallback(searchTerms);
						return;
					}
					result = result.query.allusers;
					$.each(result, function (id, it) {
						searchTerms.push({ value: (it.name || it['*']) });
					});
					if (typeof pCallback === 'function') pCallback(searchTerms);
					pLastCB = 0;
				},
				doXHR = function (request, pCallback) {
					var queryU = {
						action: 'query',
						format: 'json',
						list: 'allusers',
						auprefix: request.term.replace(rxUser, '')
					};
					lastXHR = $.getJSON(mw.util.wikiScript('api'), queryU, function (r) { didXHR(r, pCallback); });
				},
				__autocomplete = {
					minLength: 1,
					delay: delayXHR,
					select: function (/* event, ui*/) {
						var _this = this;
						setTimeout(function () {
							$(_this).triggerHandler('change');
						}, 10);
					},
					source: function (request, callback) {
						if (pLastCB) pLastCB([]);
						pLastCB = callback;
						if (lastXHR) lastXHR.abort();
						delayXHR += 100;
						doXHR(request, callback);
					}
				};
			$uOU_t.autocomplete(__autocomplete);
			$uOD_t.autocomplete(__autocomplete);

			// Option Accordion
			var __optionChange = function () {
				if (this !== $uOI_r[0]) $uOI_d.slideUp();
				if (this !== $uOU_r[0]) $uOU_d.slideUp();
				if (this !== $uOD_r[0]) $uOD_d.slideUp();
				$(this).parent().find('.undelete-option-details').slideDown();
			};
			$uOI_r.change(__optionChange);
			$uOU_r.change(__optionChange);
			$uOD_r.change(__optionChange);

			// Date pickers an tipsy
			$.datepicker.setDefaults($.datepicker.regional[mw.config.get('wgUserLanguage')]);
			var __datepicker = {
					changeYear: true,
					changeMonth: true,
					dateFormat: 'yy-mm-dd 12:00:00',
					showWeek: true,
					firstDay: 1
				},
				__tipsy = {
					trigger: 'focus',
					gravity: 's',
					html: true,
					title: function () {
						return 'Format:  YYYY-MM-DD<br> or, optionally<br>YYYY-MM-DD&nbsp;hh:mm:ss<br>or use the picker';
					}
				};
			$uOU_df.datepicker(__datepicker).tipsy(__tipsy);
			$uOU_dt.datepicker(__datepicker).tipsy(__tipsy);
			$uOD_df.datepicker(__datepicker).tipsy(__tipsy);
			$uOD_dt.datepicker(__datepicker).tipsy(__tipsy);

			// The "Add Buttons"
			$uOI_b.click(function () { udh.secureCall('uAddDirect'); });
			$uOU_b.click(function () { udh.secureCall('uAddFromUploadLog'); });
			$uOD_b.click(function () { udh.secureCall('uAddFromDeletionLog'); });

			var handleKey = function (e, $toClick, self) {
				if (e.which === 13) $toClick.click();

				var oldVal = self.value,
					newVal = udh.uCleanTitle(oldVal);
				if (oldVal !== newVal) self.value = newVal;
			};
			// Input fields
			$uOI_t.keyup(function (e) { handleKey(e, $uOI_b, this); });
			$uOU_t.keyup(function (e) { handleKey(e, $uOU_b, this); });
			$uOD_t.keyup(function (e) { handleKey(e, $uOD_b, this); });

			// Reason parsing
			$uR_ParseL.text(this.i18n.previewLabel);
			var oldVal = '';
			$uR.on('input keyup', function () {
				var $el = $(this),
					val = $el.val();
				if (val === oldVal) return;
				oldVal = val;

				$uR_Parse.css('color', '#877');

				var __gotParsedText = function (r) {
					$uR_Parse.html(r);
					$uR_Parse.css('color', '#000');
				};
				mw.libs.commons.api.parse(val, mw.config.get('wgUserLanguage'), mw.config.get('wgPageName'), __gotParsedText);
			});
		},
		uCleanTitle: function (txt) {
			return $.trim($.ucFirst(txt));
		},
		uAddDirect: function () {
			var pg = $uOI_t.val();
			if ((!pg || !$.trim(pg).length) && !udh.hIfEmpty($uOI_t, '', 1)) return;

			if (pg.indexOf('//') !== -1) {
				var t = mw.util.getParamValue('page', pg);
				if (!t) t = mw.util.getParamValue('title', pg);
				if (t) {
					pg = t;
				} else {
					var RE_Page = new RegExp(mw.RegExp.escape(mw.config.get('wgServer') + mw.config.get('wgArticlePath')).replace('\\$1', '(.+)')),
						m = pg.match(RE_Page);
					if (m && m[1]) pg = m[1];
					pg = pg.replace(/#.+$/, '');
				}
				try {
					pg = decodeURIComponent(pg);
				} catch (ex) { }
			}
			pg = $.trim(pg.replace(/[|_]/g, ' ').replace(/[\]}>]/g, ')').replace(/[[{<]/g, '('));
			$uOI_t.val(pg);

			$uOI_d.block({ message: $.createSpinner() });
			udh.queryAPI({
				action: 'query',
				list: 'logevents',
				letitle: pg,
				lelimit: 500
			}, 'uAddDirectCB');
		},
		uAddDirectCB: function (r) {
			var deleter,
				uploader,
				title,
				comment,
				_onFunctionExit = function () {
					if (udh.cbAfterItemAdded) udh.secureCall(udh.cbAfterItemAdded);
					delete udh.cbAfterItemAdded;
				};
			$.each(r.query.logevents, function (i, le) {
				if (le.title) title = le.title;
				if (!deleter && le.action === 'delete') {
					deleter = le.user;
					comment = 'was deleted by [[User:' + le.user + '|]] on ' + udh.uFormatTimestamp(le.timestamp) + " because ''" + le.comment + "''";
				}
				if (le.action === 'upload') uploader = le.user;
			});
			$uOI_d.unblock();
			if (!deleter) {
				var __hideTipsy = function () {
						$uOI_t.tipsy('hide');
					},
					t = $uOI_t.val(),
					m = t.match(/\.(\S{2,5})$/),
					extAllowed = !!(m && m[1] && ($.inArray(m[1].toLowerCase(), mw.config.get('wgFileExtensions')) !== -1));

				if (extAllowed && !/^(?:[Ff]ile|[Ii]mage):/.test(t)) {
					$uOI_t.val('File:' + t);
					return this.uAddDirect();
				} else {
					var text = (title || t || 'This page') + ' <b>was never deleted</b>. Namespace and file extension (e.g. <b>File:</b>Example<b>.jpg</b>) must be included.';
					if (m && m[1] && !extAllowed) text = text + ' <i>' + m[1] + '</i> is not an accepted file format on Commons.';
					$uOI_t.attr('title', text).tipsy({
						gravity: 'sw',
						html: true,
						trigger: 'manual',
						fade: true
					}).blur(__hideTipsy);
					$uOI_t.tipsy('show');
					setTimeout(__hideTipsy, 20000);
					udh.hIfEmpty($uOI_t, '', 1);
					return _onFunctionExit();
				}
			}
			if (uploader) comment = comment + ' and was uploaded by [[User:' + uploader + '|]]';

			this.uAddPageItem(title, comment, uploader, deleter);
			_onFunctionExit();
		},
		getMwDate: function (dateX) {
			var mwDateRx = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/;
			if (typeof dateX !== 'string') return dateX;
			if (mwDateRx.test(dateX)) return dateX;
			var m1 = dateX.match(/(\d{4}).(\d{2}).(\d{2})\D*(\d{2}:\d{2}:\d{2})?/);
			if (m1) return (m1[1] + '-' + m1[2] + '-' + m1[3] + 'T' + (m1[4] ? m1[4] : '12:00:00') + 'Z');
			m1 = dateX.match(/(\d{2}).(\d{2}).(\d{4})\D*(\d{2}:\d{2}:\d{2})?/);
			if (m1) return (m1[3] + '-' + m1[2] + '-' + m1[1] + 'T' + (m1[4] ? m1[4] : '12:00:00') + 'Z');
			else return '';
		},
		uAddFromUploadLog: function () {
			this.udhDoesUserExist($uOU_t, 'uQueryUploadLog');
		},
		uQueryUploadLog: function (uploader) {
			var query = {
					action: 'query',
					list: 'logevents',
					letype: 'upload',
					leuser: uploader,
					ledir: 'newer',
					lelimit: 100
				},
				fromDate = this.getMwDate($uOU_df.val()),
				toDate = this.getMwDate($uOU_dt.val());
			if (fromDate) query.lestart = fromDate;
			if (toDate) query.leend = toDate;
			$uOU_d.block({ message: $.createSpinner() });
			this.queryAPI(query, 'uQueryUploadLogCB');
		},
		uQueryUploadLogCB: function (r) {
			$.each(r.query.logevents, function (i, le) {
				if (!le.pageid) udh.uAddPageItem(le.title, 'uploaded on ' + udh.uFormatTimestamp(le.timestamp) + ' by ' + le.user, le.user);
			});
			$uOU_d.unblock();
		},
		uAddFromDeletionLog: function () {
			this.udhDoesUserExist($uOD_t, 'uQueryDeletionLog');
		},
		uQueryDeletionLog: function (deleter) {
			var query = {
					action: 'query',
					list: 'logevents',
					letype: 'delete',
					leuser: deleter,
					ledir: 'newer',
					lelimit: 100
				},
				fromDate = this.getMwDate($uOD_df.val()),
				toDate = this.getMwDate($uOD_dt.val());
			if (fromDate) query.lestart = fromDate;
			if (toDate) query.leend = toDate;
			$uOD_d.block({ message: $.createSpinner() });
			this.queryAPI(query, 'uQueryDeletionLogCB');
		},
		uQueryDeletionLogCB: function (r) {
			$.each(r.query.logevents, function (i, le) {
				if (!le.pageid) udh.uAddPageItem(le.title, 'deleted on ' + udh.uFormatTimestamp(le.timestamp) + ' by ' + le.user + ' because ' + le.comment, undefined, le.user);
			});
			$uOD_d.unblock();
		},
		udhDoesUserExist: function ($el, cb) {
			var u = $.trim($el.val());
			if (!this.hIfEmpty($el, u, 1)) return;
			u = $.ucFirst(u);
			$el.val(u);

			udh.queryAPI({
				action: 'query',
				list: 'allusers',
				aufrom: u,
				auto: u,
				lelimit: 1
			}, function (r) {
				if (!udh.hIfEmpty($el, r.query.allusers, 1)) return;
				udh.secureCall(cb, u);
			});
		},
		udhCheckRequest: function () {
			if ($uOI_r[0].checked && $uOI_t.val().length) {
				udh.cbAfterItemAdded = 'udhPrepareRequest';
				$uOI_b.click();
				return;
			}
			udh.secureCall('udhPrepareRequest');
		},
		udhPrepareRequest: function () {
			if (!udh.hIfEmpty($uR, $uR.val(), 10)) return;
			if (!udh.hIfEmpty($uO, $uI.find('.undelete-item'), 1)) return;

			// 1) Guess a good heading
			var maxCount = 0,
				heading,
				uploaders = {},
				deleter = {};

			$.each(this.uPagesToRestore, function (pgName, details) {
				maxCount++;
				heading = pgName;
				if (details.uploader && (typeof uploaders[details.uploader] !== 'number')) uploaders[details.uploader] = 0;
				else uploaders[details.uploader]++;

				if (details.deleter && (typeof deleter[details.deleter] !== 'number')) deleter[details.deleter] = 0;
				else deleter[details.deleter]++;

			});
			if (maxCount < 4) return this.secureCall('udhSendRequest', heading);
			maxCount = 0;
			$.each(uploaders, function (uName, count) {
				if (count > maxCount) {
					maxCount = count;
					heading = 'Files uploaded by ' + uName;
				}
			});
			$.each(deleter, function (dName, count) {
				if (count > maxCount) {
					maxCount = count;
					heading = 'Files deleted by ' + dName;
				}
			});
			this.secureCall('udhSendRequest', heading);
		},
		udhSendRequest: function (heading) {
		// mw.libs.commons.api.getCurrentDate()
			var plainHeading = heading,
				clickableHeading = /^(File:|Category:)/.test(heading) ? ('[[:' + heading + ']]') : heading,
				text = '\n== ' + clickableHeading + ' ==\n<!-- x-origin: ' + mw.config.get('wgPageName') + ' -->Please restore the following pages: ';

			$.each(this.uPagesToRestore, function (pgName/* , details*/) {
				if (/^File:/i.test(pgName)) text += '\n* {{Il|1=' + pgName.replace(/^File:/i, '') + '}}';
				else text += '\n* [[' + pgName.replace(/^Category:/i, ':Category:') + ']]';
			});

			text += '\nReason: ' + this.cleanReason($uR.val()).replace(/^Reason:? ?/i, '') + ' ~~~~';

			var page = {
				title: this.udhPage,
				text: text,
				editType: 'appendtext',
				watchlist: 'watch'
			};
			this.uPlainHeading = plainHeading;
			this.showProgress('Posting undeletion request');
			this.savePage(page, '([[MediaWiki:Gadget-dashboard.UndeletionRequester.js|Script]]): Posting undeletion request [[#' + plainHeading + ']]', 'udhRequestComplete');
		},
		udhRequestComplete: function () {
			this.showProgress();
			var dlgButtons = {};
			dlgButtons['Ok – Show my request!'] = function () {
				window.location.href = mw.config.get('wgServer') +
			mw.config.get('wgArticlePath').replace('$1', udh.udhPage.replace(/ /g, '_')) + '?x=' + Math.random() +
			'#footer';
			};
			$uC.text('Your request for restoration was successfully posted. Please check back regularly for answering questions.').dialog({ buttons: dlgButtons, title: 'Request posted successfully – Please watch your request' });
		},
		hIfEmpty: function ($el, l, minLen) {
			if (l.length < minLen) {
				$el.addClass('ui-state-error');
				setTimeout(function () {
					$el.removeClass('ui-state-error');
				}, 2000);
				return false;
			}
			return true;
		},
		udhShowDlg: function () {
			$uC.dialog('open');
		},
		udhInstall: function () {
			var dlgButtons = {};
			dlgButtons['Request undeletion'] = udh.udhCheckRequest;

			if (mw.user.isAnon()) {
				var $logInNode = $('#pt-login');
				if (!$logInNode.length) return;
				dlgButtons[$logInNode.text() || 'LogIn'] = function () {
					var logInLink = ($logInNode.find('a').length ? $logInNode.find('a').attr('href') : $logInNode.attr('href')) || mw.util.wikiScript() + '?' + $.param({ title: 'Special:UserLogin', returnto: mw.config.get('wgPageName') });
					window.location = logInLink;
				};
			}

			var dlgWidth = Math.min(600, $(window).width() - 250);
			$uC.dialog({
				modal: true,
				autoOpen: false,
				width: dlgWidth,
				buttons: dlgButtons,
				create: 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'),
						$logInButton = $buttons.eq(1).button({ icons: { primary: 'ui-icon-key' } }).addClass('ui-button-orange'),
						$feedback = $('<a>', { href: mw.util.getUrl('Commons talk:Undeletion requests') + '#Tool_feedback', target: '_blank', text: 'Constructive comments/feedback appreciated' });
					$(this).parent().find('.ui-dialog-buttonpane').append($feedback);
					$buttons = document.forms.commentbox;
					if ($buttons && ($buttons = $buttons.elements.preloadtitle)) {
						$buttons = $buttons.defaultValue !== $buttons.value ? $buttons.value.replace(/\[\[:?([^\n]+)\]\]/, '$1') : '';
						$uOI_t.val($buttons);
					}
				}
			});
			mw.loader.using([
				'ext.gadget.libAPI',
				'ext.gadget.jquery.blockUI',
				'jquery.ui',
				'jquery.ui',
				'jquery.spinner',
				'ext.gadget.tipsyDeprecated',
				'mediawiki.util'
			], function () {
				udh.secureCall('uBindEvents');
				$(document).triggerHandler('scriptLoaded', ['undeleteHelper']);
			});
		},
		udhPage: 'Commons:Undeletion requests/Current requests',

		/**
		*  Implement Commons Dashboard Widget interface
		*  @dashboardwidgetinterface
		**/
		$getUI: function () {
			var $btn = document.forms.commentbox;
			$btn = $btn ? $btn.elements.create.value : 'Request undeletion';
			$btn = $('<button>', { 'class': 'center ui-button-large ui-button-blue', 'title': $btn })
				.text(mw.messages.get('Gadgetusage-gadget') || 'Gadget').button();
			$btn.find('.ui-button-text').prepend($('<div>', { 'css': { height: '128px', width: '128px' }, 'class': 'undelete-recycle' }));
			$btn.click(udh.udhShowDlg);
			return $btn;
		},
		createUI: function () {
			$('#udhContainer').text('').append(udh.$getUI());
		}
	};

	AQD = window.AjaxQuickDelete;
	$.extend(AQD, window.undeleteHelper);
	window.undeleteHelper = AQD;
	udh = AQD;
	new mw.Api().loadMessagesIfMissing( ['Gadgetusage-gadget'] ).always(function () {
		udh.secureCall('createUI');
		udh.secureCall('udhInstall');
	});
});
}(jQuery, mediaWiki));
// </nowiki>