User:BMacZero/AjaxCfdClose.js

From Wikimedia Commons, the free media repository
Jump to navigation Jump to search
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.
// Ajax CFD Close adapted by [[User:BMacZero]] from [[MediaWiki:AjaxQuickDelete.js]].
//
// AjaxQuickDelete:
// Original code written by [[User:Ilmari Karonen]]
// Rewritten & extended by [[User:DieBuche]]. Botdetection and encoding fixer by [[User:Lupo]]
// <nowiki>

if (typeof AjaxCfdClose == 'undefined' && mw.config.get('wgNamespaceNumber') >= 0) {

	window.AjaxCfdClose = {

		/**
		** Set up the AjaxCfdClose object and add the toolbox link.  Called via $(document).ready() during page loading.
		**/
		install: function () {
			var skin = mw.config.get('skin');
			// Abort if the user is using an antique skin & load the legacy version
			if ((skin != 'vector' && skin != 'monobook')) {
				console.error("AjaxCfdClose - skin compatibility problem?");
			}
			// Import stylesheet
			importStylesheet('MediaWiki:AjaxQuickDelete.css');

			//jQuery UI is not loaded on all pages:
			if (jQuery.ui == undefined) {
				// FIXME: Use mw.loader.using() to depend on jquery.ui something
			}
			// Set up toolbox link
			if (mw.config.get('wgPageName').match(/Commons:Categories_for_discussion\/[0-9][0-9][0-9][0-9]\/[0-9][0-9]\//g)) {
				mw.util.addPortletLink('p-tb', 'javascript:AjaxCfdClose.closeCfd();', this.i18n.toolboxLinkCloseCfd, 't-ajaxcfdclose', null);
			}
		},

		closeCfd: function () {
			this.tasks = [];  // reset task list in case an earlier error left it non-empty

			//HACK:?
			this.edittoken = mw.user.tokens.values.csrfToken;

			this.addTask('formatClosedDiscussion');
			//TODO: Remove CFD notice from any linked pages
			//TODO: Add appropriate notices to affected talk pages

			// finally reload the page to show it in its new closed state
			this.addTask('reloadPage');

			this.prompt([{
				message: '',
				prefill: '',
				returnvalue: 'reason',
				cleanUp: true,
				noEmpty: true
			}], this.i18n.queryCloseRationale);
		},

		/**
		** Edit the current page to add the closing comment.
		**/
		formatClosedDiscussion: function () {
			this.showProgress(this.i18n.closingCategoryDiscussion);

			var edit = {
				action: 'edit',
				summary: "Closing category discussion with [[User:BMacZero/AjaxCfdClose.js|AjaxCfdClose]]",
				watchlist: 'watch',
				title: mw.config.get('wgPageName'),
				token: this.edittoken
			};

			edit['prependtext'] = "{{cfdh}}\n";
			edit['appendtext'] = "\n----\n" + AjaxCfdClose.reason + " ~~" + "~~\n{{cfdf}}";
			this.doAPICall(edit, 'nextTask');
		},

		/**
		** Pseudo-Modal JS windows.
		**/
		prompt: function (questions, title, width) {
			var dlgButtons = {};
			dlgButtons[this.i18n.submitButtonLabel] = function () {
				$.each(questions, function (i, v) {
					response = $('#AjaxQuestion' + i).val();
					if (v.type == 'checkbox') response = $('#AjaxQuestion' + i).attr('checked');
					if (v.cleanup) {
						if (v.returnvalue == 'reason') response = AjaxCfdClose.cleanReason(response);
						if (v.returnvalue == 'destination') response = AjaxCfdClose.cleanFileName(response);
					}
					AjaxCfdClose[v.returnvalue] = response;
					if (v.returnvalue == 'reason' && AjaxCfdClose.tag) {
						AjaxCfdClose.tag = AjaxCfdClose.tag.replace('%PARAMETER%', response);
						AjaxCfdClose.img_summary = AjaxCfdClose.img_summary.replace('%PARAMETER%', response);
						AjaxCfdClose.img_summary = AjaxCfdClose.img_summary.replace('%PARAMETER-LINKED%', '[[:' + response + ']]');
					}
				});
				$(this).dialog("close");
				AjaxCfdClose.nextTask();
			};
			dlgButtons[this.i18n.cancelButtonLabel] = function () {
				$(this).dialog("close");
			};

			var $dialog = $('<div></div>')
				.html('<div id="AjaxDeleteContainer"></div>')
				.dialog({
					width: (width || 600),
					modal: true,
					title: title,
					draggable: false,
					dialogClass: "wikiEditor-toolbar-dialog",
					close: function () {
						$(this).dialog("destroy");
						$(this).remove();
					},
					buttons: dlgButtons
				});
			var submitButton = $('.ui-dialog-buttonpane button:first');

			$.each(questions, function (i, v) {
				if (v.type == 'textarea') {
					$('#AjaxDeleteContainer').append(v.message)
						.append('<textarea rows=20 id="AjaxQuestion' + i + '"><br><br>');
				} else {
					$('#AjaxDeleteContainer').append(v.message)
						.append('<input type="' + (v.type || 'text') + '" id="AjaxQuestion' + i + '" style="width: 98%;"><br><br>');
				}
				$('#AjaxQuestion' + i).keyup(function (event) {
					if (v.noEmpty) {
						if ($(this).val().length < 4) {
							submitButton.addClass('ui-state-disabled');
						} else {
							submitButton.removeClass('ui-state-disabled');
						}
					}
					if (event.keyCode == '13' && v.enterToSubmit != false) submitButton.click();
				});
				$('#AjaxQuestion' + i).val(v.prefill);
				if (v.type == 'checkbox') $('#AjaxQuestion' + i).attr('checked', v.prefill).attr('style', 'margin-left: 5px');
				$('#AjaxQuestion' + i).keyup();
			});
			$('#AjaxQuestion0').focus();
		},

		cleanFileName: function (uncleanName) {
			uncleanName = uncleanName.replace(/Image:/i, 'File:');
			uncleanName = uncleanName.replace(/.jpe*g/i, '.jpg');
			uncleanName = uncleanName.replace(/.png/i, '.png');
			uncleanName = uncleanName.replace(/.gif/i, '.gif');

			// If new file name is without extension, add the one from the old name
			if (uncleanName.toLowerCase().indexOf(mw.config.get('wgPageName').toLowerCase().replace(/.*./, '').replace(/jpe*g/i, 'jpg')) == -1) {
				uncleanName += '.' + mw.config.get('wgPageName').toLowerCase().replace(/.*./, '').replace(/jpe*g/i, 'jpg');
			}
			return uncleanName;
		},

		cleanReason: function (uncleanReason) {
			// trim whitespace
			uncleanReason = uncleanReason.replace(/^\s*(.+)\s*$/, '$1');
			// remove signature
			uncleanReason = uncleanReason.replace(/(.+)(--)?~{3,5}$/, '$1');
			return uncleanReason;
		},

		/**
		** For display of progress messages.
		**/
		showProgress: function (message) {
			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: 'auto',
						minHeight: 90,
						modal: true,
						resizable: false,
						draggable: false,
						closeOnEscape: false,
						dialogClass: "ajaxDeleteFeedback"
					});
				$('.ui-dialog-titlebar').hide();
			}
		},

		/**
		** Submit an edited page.
		**/
		savePage: function (page, summary, callback) {
			var edit = {
				action: 'edit',
				summary: summary,
				watchlist: (page.watchlist || 'preferences'),
				title: page.title,
				token: this.edittoken
			};

			edit[page.editType] = page.text;
			this.doAPICall(edit, callback);
		},

		movePage: function () {
			var edit = {
				action: 'move',
				reason: this.reason,
				from: mw.config.get('wgPageName'),
				to: this.destination,
				movetalk: '',
				token: this.movetoken
			};
			// 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 (this.wpLeaveRedirect === false) {
				edit.noredirect = '';
			}

			this.showProgress(this.i18n.movingFile);
			this.doAPICall(edit, 'nextTask');
		},

		/**
		** 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;

			params.format = 'json';
			$.ajax({
				url: this.apiURL,
				cache: false,
				dataType: 'json',
				data: params,
				type: 'POST',
				success: function (result, status, x) {
					if (!result) return o.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 () { o.doAPICall(params, callback); }, 500);
					if (result.error) 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) {
					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); }
		},

		/**
		** Once we're all done, reload the page.
		**/
		reloadPage: function () {
			location.href = mw.config.get('wgServer') + mw.config.get('wgArticlePath').replace("$1", encodeURIComponent(this.destination || mw.config.get('wgPageName')));
		},

		/**
		** Crude error handler. Just throws an alert at the user and
		** (if we managed to add the {{delete}} tag) reloads the page.
		**/
		fail: function (err) {
			document.body.style.cursor = 'default';
			var msg = this.i18n.taskFailure[this.currentTask] || this.i18n.genericFailure;
			var fix = (this.templateAdded ? this.i18n.completeRequestByHand : this.i18n.addTemplateByHand);

			$('#feedbackContainer').html(msg + " " + fix + "<br>" + this.i18n.errorDetails + "<br>" + err + "<br><a href=" + mw.config.get('wgServer') + "/wiki/MediaWiki_talk:AjaxQuickDelete.js>" + this.i18n.errorReport + "</a>");
			$('.ui-dialog-content').height('auto');
			$('.ui-dialog').addClass('ajaxDeleteError');
			// Allow some time to read the message
			if (this.templateAdded) setTimeout(this.reloadPage(), 5000);
		},

		/**
		** 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.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, this.months[date.getUTCMonth()]);
			fmt = fmt.replace(/DAY/g, date.getUTCDate());
			return fmt;
		},
		months: "January February March April May June July August September October November December".split(" "),

		// Constants
		// MediaWiki API script URL
		apiURL: mw.config.get('wgServer') + mw.config.get('wgScriptPath') + "/api.php",

		// Translatable strings
		i18n: {

			// GUI reason prompt form
			toolboxLinkCloseCfd   : "Close category discussion",
			submitButtonLabel     : "Proceed",
			cancelButtonLabel     : "Cancel",

			// Closing category discussion
			queryCloseRationale: "What is your conclusion for this discussion?",
			closingCategoryDiscussion: "Closing category discussion...",

			// Errors
			errorDetails: "A detailed description of the error is shown below:",
			errorReport: "Report the error here"
		}
	};
	var wgUserLanguage = mw.config.get('wgUserLanguage');
	if (wgUserLanguage != 'en') importScript('User:BMacZero/AjaxCfdClose.js/' + wgUserLanguage);
	$(document).ready(function () { AjaxCfdClose.install(); });

} // end if (guard)

// </nowiki>