User:Xiplus/TwinkleGlobal/twinklespeedy.js

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
// <nowiki>


(function($) {


/*
 ****************************************
 *** twinklespeedy.js: CSD module
 ****************************************
 * Mode of invocation:     Tab ("CSD")
 * Active on:              Non-special, existing pages
 *
 * NOTE FOR DEVELOPERS:
 *   If adding a new criterion, add it to the appropriate places at the top of
 *   twinkleconfig.js.  Also check out the default values of the CSD preferences
 *   in twinkle.js, and add your new criterion to those if you think it would be
 *   good.
 */

TwinkleGlobal.speedy = function twinklespeedy() {
	// Disable on:
	// * special pages
	// * non-existent pages
	if (mw.config.get('wgNamespaceNumber') < 0 || !mw.config.get('wgArticleId')) {
		return;
	}

	var disabledWikis = $.map(TwinkleGlobal.getPref('speedyDisabledWikis'), function(el) {
		return el.value.trim();
	});

	if (disabledWikis.indexOf(mw.config.get('wgDBname')) !== -1) {
		return;
	}

	TwinkleGlobal.addPortletLink(TwinkleGlobal.speedy.callback, 'CSD', 'twg-csd', 'Request speedy deletion');
};

TwinkleGlobal.speedy.speedyTemplate = null;
TwinkleGlobal.speedy.speedyTemplateRegex = null;
TwinkleGlobal.speedy.speedyTemplateDefault = {
	jawiki: 'Delete',
	ptwiki: 'ER/TwinkleGlobal'
};
TwinkleGlobal.speedy.speedyTemplateSubst = [
	'kowiki',
	'hrwiki',
	'ptwiki'
];

// This function is run when the CSD tab/header link is clicked
TwinkleGlobal.speedy.callback = function twinklespeedyCallback() {
	var cacheKey = 'Twinkle_global_xiplus_speedy_template';
	var cache = mw.storage.getObject(cacheKey);
	if (cache) {
		var cacheTime = new Date(cache.timestamp);
		if (cache.template && cache.regex && !isNaN(cacheTime.getTime()) && new Date() - cacheTime < 7 * 86400 * 1000) {
			TwinkleGlobal.speedy.speedyTemplate = cache.template;
			TwinkleGlobal.speedy.speedyTemplateRegex = cache.regex;
			TwinkleGlobal.speedy.initDialog(TwinkleGlobal.speedy.callback.evaluateUser);
			return;
		}
	}

	var getTemplate = function() {
		var def = $.Deferred();
		var DBname = mw.config.get('wgDBname');
		if (DBname in TwinkleGlobal.speedy.speedyTemplateDefault) {
			TwinkleGlobal.speedy.speedyTemplate = TwinkleGlobal.speedy.speedyTemplateDefault[DBname];
			return def.resolve();
		}

		var dataapi = TwinkleGlobal.getPref('dataApi');
		dataapi.get({
			action: 'wbgetentities',
			format: 'json',
			ids: TwinkleGlobal.getPref('speedyTemplateItem'),
			props: 'sitelinks',
			sitefilter: mw.config.get('wgDBname')
		}).done(function (data) {
			var site = data.entities[TwinkleGlobal.getPref('speedyTemplateItem')].sitelinks[mw.config.get('wgDBname')];
			if (site !== undefined) {
				TwinkleGlobal.speedy.speedyTemplate = new mw.Title(site.title).title;
			} else {
				TwinkleGlobal.speedy.speedyTemplate = 'Delete';
			}
			return def.resolve();
		}).fail(function() {
			mw.notify('Failed to get speedy template title', { type: 'error' });
		});
		return def;
	};

	getTemplate().then(function() {
		var api = new mw.Api();
		api.get({
			action: 'query',
			format: 'json',
			prop: 'redirects',
			titles: 'Template:' + TwinkleGlobal.speedy.speedyTemplate,
			rdlimit: 'max',
			formatversion: '2',
		}).then(function (data) {
			var redirects = [MorebitsGlobal.pageNameRegex(TwinkleGlobal.speedy.speedyTemplate)];
			(data.query.pages[0].redirects || []).forEach(function(redirect) {
				redirects.push(MorebitsGlobal.pageNameRegex(new mw.Title(redirect.title).title));
			});
			TwinkleGlobal.speedy.speedyTemplateRegex = redirects.join('|');

			mw.storage.setObject(cacheKey, {
				template: TwinkleGlobal.speedy.speedyTemplate,
				regex: TwinkleGlobal.speedy.speedyTemplateRegex,
				timestamp: new Date().toISOString(),
			});

			TwinkleGlobal.speedy.initDialog(TwinkleGlobal.speedy.callback.evaluateUser, true);
		});
	});
};

// Used by unlink feature
TwinkleGlobal.speedy.dialog = null;

// The speedy criteria list can be in one of several modes
TwinkleGlobal.speedy.mode = {
	sysopSingleSubmit: 1,  // radio buttons, no subgroups, submit when "Submit" button is clicked
	sysopRadioClick: 2,  // radio buttons, no subgroups, submit when a radio button is clicked
	sysopMultipleSubmit: 3, // check boxes, subgroups, "Submit" button already present
	sysopMultipleRadioClick: 4, // check boxes, subgroups, need to add a "Submit" button
	userMultipleSubmit: 5,  // check boxes, subgroups, "Submit" button already pressent
	userMultipleRadioClick: 6,  // check boxes, subgroups, need to add a "Submit" button
	userSingleSubmit: 7,  // radio buttons, subgroups, submit when "Submit" button is clicked
	userSingleRadioClick: 8,  // radio buttons, subgroups, submit when a radio button is clicked

	// are we in "delete page" mode?
	// (sysops can access both "delete page" [sysop] and "tag page only" [user] modes)
	isSysop: function twinklespeedyModeIsSysop(mode) {
		return mode === TwinkleGlobal.speedy.mode.sysopSingleSubmit ||
			mode === TwinkleGlobal.speedy.mode.sysopMultipleSubmit ||
			mode === TwinkleGlobal.speedy.mode.sysopRadioClick ||
			mode === TwinkleGlobal.speedy.mode.sysopMultipleRadioClick;
	},
	// do we have a "Submit" button once the form is created?
	hasSubmitButton: function twinklespeedyModeHasSubmitButton(mode) {
		return mode === TwinkleGlobal.speedy.mode.sysopSingleSubmit ||
			mode === TwinkleGlobal.speedy.mode.sysopMultipleSubmit ||
			mode === TwinkleGlobal.speedy.mode.sysopMultipleRadioClick ||
			mode === TwinkleGlobal.speedy.mode.userMultipleSubmit ||
			mode === TwinkleGlobal.speedy.mode.userMultipleRadioClick ||
			mode === TwinkleGlobal.speedy.mode.userSingleSubmit;
	},
	// is db-multiple the outcome here?
	isMultiple: function twinklespeedyModeIsMultiple(mode) {
		return mode === TwinkleGlobal.speedy.mode.userMultipleSubmit ||
			mode === TwinkleGlobal.speedy.mode.sysopMultipleSubmit ||
			mode === TwinkleGlobal.speedy.mode.userMultipleRadioClick ||
			mode === TwinkleGlobal.speedy.mode.sysopMultipleRadioClick;
	}
};

// Prepares the speedy deletion dialog and displays it
TwinkleGlobal.speedy.initDialog = function twinklespeedyInitDialog(callbackfunc) {
	var dialog;
	TwinkleGlobal.speedy.dialog = new MorebitsGlobal.simpleWindow(TwinkleGlobal.getPref('speedyWindowWidth'), TwinkleGlobal.getPref('speedyWindowHeight'));
	dialog = TwinkleGlobal.speedy.dialog;
	dialog.setTitle('Choose criteria for speedy deletion');
	dialog.setScriptName('Twinkle');
	dialog.addFooterLink('Add custom reason', TwinkleGlobal.getPref('configPage'));
	dialog.addFooterLink('Suggest useful reasons', TwinkleGlobal.getPref('bugReportLink'));

	var form = new MorebitsGlobal.quickForm(callbackfunc, TwinkleGlobal.getPref('speedySelectionStyle') === 'radioClick' ? 'change' : null);

	var tagOptions = form.append({
		type: 'div',
		name: 'tag_options'
	});

	tagOptions.append({
		type: 'checkbox',
		list: [
			{
				label: 'Tag with multiple criteria',
				value: 'multiple',
				name: 'multiple',
				tooltip: 'When selected, you can select several criteria that apply to the page.',
				event: function(event) {
					TwinkleGlobal.speedy.callback.modeChanged(event.target.form);
					event.stopPropagation();
				}
			}
		]
	});

	tagOptions.append({
		type: 'checkbox',
		list: [
			{
				label: 'Blank the page',
				value: 'blank',
				name: 'blank',
				tooltip: 'When selected, blank the page before tagging.'
			}
		]
	});

	tagOptions.append({
		type: 'checkbox',
		list: [
			{
				label: 'Wrap deletion tag with <noinclude>',
				value: 'noinclude',
				name: 'noinclude',
				checked: mw.config.get('wgNamespaceNumber') === 10,
				tooltip: 'Will wrap the deletion tag in <noinclude> tags, so that it won\'t transclude. This option is not normally required.'
			}
		]
	});

	if (MorebitsGlobal.isGSWiki()) {
		tagOptions.append({
			type: 'checkbox',
			list: [
				{
					label: 'Request at m:Global sysops/Requests (BETA feature: Remember to review your edits after clicking "Submit Query")',
					value: 'gsr',
					name: 'gsr'
				}
			]
		});
	}

	form.append({
		type: 'div',
		name: 'work_area',
		label: 'Failed to initialize the CSD module. Please try again, or tell the Twinkle developers about the issue.'
	});

	if (TwinkleGlobal.getPref('speedySelectionStyle') !== 'radioClick') {
		form.append({ type: 'submit' });
	}

	var result = form.render();
	dialog.setContent(result);
	dialog.display();

	TwinkleGlobal.speedy.callback.modeChanged(result);
};

TwinkleGlobal.speedy.callback.getMode = function twinklespeedyCallbackGetMode(form) {
	var mode = TwinkleGlobal.speedy.mode.userSingleSubmit;

	if (form.multiple.checked) {
		mode = TwinkleGlobal.speedy.mode.userMultipleSubmit;
	} else {
		mode = TwinkleGlobal.speedy.mode.userSingleSubmit;
	}
	if (TwinkleGlobal.getPref('speedySelectionStyle') === 'radioClick') {
		mode++;
	}

	return mode;
};

TwinkleGlobal.speedy.callback.modeChanged = function twinklespeedyCallbackModeChanged(form) {
	// first figure out what mode we're in
	var mode = TwinkleGlobal.speedy.callback.getMode(form);

	var work_area = new MorebitsGlobal.quickForm.element({
		type: 'div',
		name: 'work_area'
	});

	if (mode === TwinkleGlobal.speedy.mode.userMultipleRadioClick || mode === TwinkleGlobal.speedy.mode.sysopMultipleRadioClick) {
		var evaluateType = 'evaluateUser';

		work_area.append({
			type: 'div',
			label: 'When finished choosing criteria, click:'
		});
		work_area.append({
			type: 'button',
			name: 'submit-multiple',
			label: 'Submit Query',
			event: function(event) {
				TwinkleGlobal.speedy.callback[evaluateType](event);
				event.stopPropagation();
			}
		});
	}

	var radioOrCheckbox = TwinkleGlobal.speedy.mode.isMultiple(mode) ? 'checkbox' : 'radio';

	var generalCriteria = TwinkleGlobal.speedy.generalList.concat(TwinkleGlobal.getPref('customCSDReasonList'));

	$.each(TwinkleGlobal.getPref('customCSDReasonList'), function (_, item) {
		TwinkleGlobal.speedy.normalizeHash[item.value] = item.value;
	});

	// custom rationale lives under general criteria when tagging
	generalCriteria = TwinkleGlobal.speedy.customRationale.concat(generalCriteria);
	work_area.append({ type: 'header', label: 'General criteria' });
	work_area.append({ type: radioOrCheckbox, name: 'csd', list: TwinkleGlobal.speedy.generateCsdList(generalCriteria, mode) });

	if (TwinkleGlobal.speedy.speedyTemplate) {
		work_area.append({
			type: 'div',
			label: $.parseHTML('<span>Note: {{<a href="' + mw.util.getUrl('Template:' + TwinkleGlobal.speedy.speedyTemplate) + '" target="_blank">' + TwinkleGlobal.speedy.speedyTemplate + '</a>}} will be used as speedy deletion template on this wiki. Wrong? Fix it <a href="' + mw.util.getUrl('d:' + TwinkleGlobal.getPref('speedyTemplateItem')) + '" target="_blank">on Wikidata</a> or report bug <a href="' + TwinkleGlobal.getPref('bugReportLink') + '" target="_blank">here</a>.</span>')
		});
	} else {
		work_area.append({
			type: 'div',
			label: $.parseHTML('<span>Note: Fail to retrieve speedy deletion template name. {{<a href="' + mw.util.getUrl('Template:Delete') + '" target="_blank">Delete</a>}} will be used as speedy deletion template. Add template name <a href="' + mw.util.getUrl('d:' + TwinkleGlobal.getPref('speedyTemplateItem')) + '" target="_blank">on Wikidata</a> or report bug <a href="' + TwinkleGlobal.getPref('bugReportLink') + '" target="_blank">here</a></span>')
		});
	}

	var old_area = MorebitsGlobal.quickForm.getElements(form, 'work_area')[0];
	form.replaceChild(work_area.render(), old_area);
};

TwinkleGlobal.speedy.generateCsdList = function twinklespeedyGenerateCsdList(list, mode) {
	// mode switches
	var multiple = TwinkleGlobal.speedy.mode.isMultiple(mode);
	var hasSubmitButton = TwinkleGlobal.speedy.mode.hasSubmitButton(mode);
	var pageNamespace = mw.config.get('wgNamespaceNumber');

	var openSubgroupHandler = function(e) {
		$(e.target.form).find('input').prop('disabled', true);
		$(e.target.form).children().css('color', 'gray');
		$(e.target).parent().css('color', 'black').find('input').prop('disabled', false);
		$(e.target).parent().find('input:text')[0].focus();
		e.stopPropagation();
	};
	var submitSubgroupHandler = function(e) {
		var evaluateType = 'evaluateUser';
		TwinkleGlobal.speedy.callback[evaluateType](e);
		e.stopPropagation();
	};

	return $.map(list, function(critElement) {
		var criterion = $.extend({}, critElement);

		if (multiple) {
			if (criterion.hideWhenMultiple) {
				return null;
			}
			if (criterion.hideSubgroupWhenMultiple) {
				criterion.subgroup = null;
			}
		} else {
			if (criterion.hideWhenSingle) {
				return null;
			}
			if (criterion.hideSubgroupWhenSingle) {
				criterion.subgroup = null;
			}
		}

		if (mw.config.get('wgIsRedirect') && criterion.hideWhenRedirect) {
			return null;
		}

		if (criterion.showInNamespaces && criterion.showInNamespaces.indexOf(pageNamespace) < 0) {
			return null;
		}
		if (criterion.hideInNamespaces && criterion.hideInNamespaces.indexOf(pageNamespace) > -1) {
			return null;
		}

		if (criterion.subgroup && !hasSubmitButton) {
			if (Array.isArray(criterion.subgroup)) {
				criterion.subgroup = criterion.subgroup.concat({
					type: 'button',
					name: 'submit',
					label: 'Submit Query',
					event: submitSubgroupHandler
				});
			} else {
				criterion.subgroup = [
					criterion.subgroup,
					{
						type: 'button',
						name: 'submit',  // ends up being called "csd.submit" so this is OK
						label: 'Submit Query',
						event: submitSubgroupHandler
					}
				];
			}
			// FIXME: does this do anything?
			criterion.event = openSubgroupHandler;
		}

		return criterion;
	});
};

TwinkleGlobal.speedy.customRationale = [
	{
		label: 'Custom rationale',
		value: 'reason',
		subgroup: {
			name: 'reason_1',
			type: 'input',
			label: 'Rationale: ',
			size: 60
		}
	}
];

TwinkleGlobal.speedy.generalList = [
	{
		label: 'Spam',
		value: 'Spam'
	},
	{
		label: 'Vandalism',
		value: 'Vandalism'
	},
	{
		label: 'Out of project scope',
		value: 'Out of project scope'
	},
	{
		label: 'Nonsense',
		value: 'Nonsense'
	},
	{
		label: 'Test page',
		value: 'Test page'
	},
	{
		label: 'Empty page',
		value: 'Empty page'
	},
	{
		label: 'Author\'s request',
		value: 'Author\'s request'
	}
];

TwinkleGlobal.speedy.normalizeHash = {
	'reason': 'db',
	'Spam': 'spam',
	'Vandalism': 'vandalism',
	'Out of project scope': 'scope',
	'Nonsense': 'nonsense',
	'Test page': 'test',
	'Empty page': 'empty'
};

TwinkleGlobal.speedy.callbacks = {
	getTemplateCodeAndParams: function(params) {
		var reason = '', norm, parameters;

		$.each(params.values, function(index, value) {
			norm = TwinkleGlobal.speedy.normalizeHash[value];
			if (norm !== 'db') {
				reason += value + ', ';
			}
			parameters = params.templateParams[index] || [];
			for (var i in parameters) {
				if (typeof parameters[i] === 'string') {
					reason += parameters[i] + ', ';
				}
			}
		});
		reason = reason.substr(0, reason.length - 2); // remove trailing comma
		var code = '{{' +
			(TwinkleGlobal.speedy.speedyTemplateSubst.indexOf(mw.config.get('wgDBname')) !== -1 ? 'subst:' : '') +
			(TwinkleGlobal.speedy.speedyTemplate || 'Delete') + '|1=' + reason + '}}';

		return [code, reason];
	},

	parseWikitext: function(wikitext, callback) {
		var query = {
			action: 'parse',
			prop: 'text',
			pst: 'true',
			text: wikitext,
			contentmodel: 'wikitext',
			title: mw.config.get('wgPageName')
		};

		var statusIndicator = new MorebitsGlobal.status('Building deletion summary');
		var api = new MorebitsGlobal.wiki.api('Parsing deletion template', query, function(apiObj) {
			var reason = decodeURIComponent($(apiObj.getXML().querySelector('text').childNodes[0].nodeValue).find('#delete-reason').text()).replace(/\+/g, ' ');
			if (!reason) {
				statusIndicator.warn('Unable to generate summary from deletion template');
			} else {
				statusIndicator.info('complete');
			}
			callback(reason);
		}, statusIndicator);
		api.post();
	},

	user: {
		main: function(pageobj) {
			var statelem = pageobj.getStatusElement();

			// defaults to /doc for lua modules, which may not exist
			if (!pageobj.exists() && mw.config.get('wgPageContentModel') !== 'Scribunto') {
				statelem.error("It seems that the page doesn't exist; perhaps it has already been deleted");
				return;
			}

			var text = pageobj.getPageText();
			var params = pageobj.getCallbackParameters();

			// given the params, builds the template and also adds the user talk page parameters to the params that were passed in
			// returns => [<string> wikitext, <string> reason]
			var buildData = TwinkleGlobal.speedy.callbacks.getTemplateCodeAndParams(params),
				code = buildData[0],
				reason = buildData[1];

			var thispage = new MorebitsGlobal.wiki.page(mw.config.get('wgPageName'));
			// patrol the page, if reached from Special:NewPages
			if (TwinkleGlobal.getPref('markSpeedyPagesAsPatrolled')) {
				thispage.patrol();
			}

			if (params.gsr) {
				var statusIndicator = new MorebitsGlobal.status('Adding request at Global sysops/Requests', 'Fetching page...');

				var metaapi = TwinkleGlobal.getPref('metaApi');
				metaapi.edit('Global sysops/Requests', function(revision) {
					var text = revision.content.trim();

					text += '\n* Please delete ';
					if (MorebitsGlobal.interwikiPrefix !== null) {
						text += '[[:' + MorebitsGlobal.interwikiPrefix + ':' + MorebitsGlobal.pageNameNorm + ']]';
					} else {
						text += 'https:' + mw.config.get('wgServer') + mw.util.getUrl();
					}
					text += ': ' + reason + ' ~~~~';

					return {
						text: text,
						summary: 'Requesting deletion' + TwinkleGlobal.getPref('summaryAd'),
						assert: 'user'
					};
				}).then(function() {
					statusIndicator.info('Done');
				}, function(e) {
					statusIndicator.error(e);
				});
			}

			statelem.status('Checking for tags on the page...');

			// check for existing deletion tags
			var delete_re = new RegExp('(<noinclude>)?{{\\s*(' + TwinkleGlobal.speedy.speedyTemplateRegex + ')\\s*(\\|(?:{{[^{}]*}}|[^{}])*)?}}(</noinclude>)?\\s*', 'i');
			var textNoSd = text.replace(delete_re, '');
			if (text !== textNoSd && !confirm('The page already has the CSD-related template on it. Do you want to remove this one and add yours template?')) {
				statelem.error('Aborted marking by user.');
				return;
			}
			text = textNoSd;

			// Wrap SD template in noinclude tags if user want to.
			if (params.noinclude) {
				code = '<noinclude>' + code + '</noinclude>';
			}

			// Generate edit summary for edit
			var editsummary, norm, parameters;
			if (params.values.length > 1) {
				editsummary = 'Requesting speedy deletion (';
				$.each(params.values, function(index, value) {
					norm = TwinkleGlobal.speedy.normalizeHash[value];
					if (norm !== 'db') {
						editsummary += value + ', ';
					}
					parameters = params.templateParams[index] || [];
					for (var i in parameters) {
						if (typeof parameters[i] === 'string') {
							editsummary += parameters[i] + ', ';
						}
					}
				});
				editsummary = editsummary.substr(0, editsummary.length - 2); // remove trailing comma
				editsummary += ').';
			} else if (params.normalizeds[0] === 'db') {
				editsummary = 'Requesting speedy deletion with rationale "' + params.templateParams[0]['1'] + '".';
			} else {
				editsummary = 'Requesting speedy deletion (' + params.values[0] + ').';
			}

			pageobj.setPageText(code + (params.blank ? '' : '\n' + text));
			pageobj.setEditSummary(editsummary + TwinkleGlobal.getPref('summaryAd'));
			pageobj.setWatchlist(params.watch);
			if (params.scribunto) {
				pageobj.setCreateOption('recreate'); // Module /doc might not exist
				if (params.watch) {
					// Watch module in addition to /doc subpage
					var watch_query = {
						action: 'watch',
						titles: mw.config.get('wgPageName'),
						token: mw.user.tokens.get('watchToken')
					};
					new MorebitsGlobal.wiki.api('Adding Module to watchlist', watch_query).post();
				}
			}
			pageobj.save();
		}
	}
};

// validate subgroups in the form passed into the speedy deletion tag
TwinkleGlobal.speedy.getParameters = function twinklespeedyGetParameters(form, values) {
	var parameters = [];

	$.each(values, function(index, value) {
		var currentParams = [];
		switch (value) {
			case 'reason':
				if (form['csd.reason_1']) {
					var dbrationale = form['csd.reason_1'].value;
					if (!dbrationale || !dbrationale.trim()) {
						alert('Custom rationale:  Please specify a rationale.');
						parameters = null;
						return false;
					}
					currentParams['1'] = dbrationale;
				}
				break;
			default:
				break;
		}
		parameters.push(currentParams);
	});
	return parameters;
};


TwinkleGlobal.speedy.resolveCsdValues = function twinklespeedyResolveCsdValues(e) {
	var values = (e.target.form ? e.target.form : e.target).getChecked('csd');
	if (values.length === 0) {
		alert('Please select a criterion!');
		return null;
	}
	return values;
};

TwinkleGlobal.speedy.callback.evaluateUser = function twinklespeedyCallbackEvaluateUser(e) {
	var form = e.target.form ? e.target.form : e.target;

	if (e.target.type === 'checkbox' || e.target.type === 'text' ||
			e.target.type === 'select') {
		return;
	}

	var values = TwinkleGlobal.speedy.resolveCsdValues(e);
	if (!values) {
		return;
	}
	// var multiple = form.multiple.checked;
	var normalizeds = [];
	$.each(values, function(index, value) {
		var norm = TwinkleGlobal.speedy.normalizeHash[value];

		normalizeds.push(norm);
	});

	var blank = form.blank.checked;
	var gsr = form.gsr ? form.gsr.checked : false;
	var noinclude = form.noinclude.checked;

	var params = {
		values: values,
		normalizeds: normalizeds,
		templateParams: TwinkleGlobal.speedy.getParameters(form, values),
		blank: blank,
		gsr: gsr,
		noinclude: noinclude
	};
	if (!params.templateParams) {
		return;
	}

	MorebitsGlobal.simpleWindow.setButtonsEnabled(false);
	MorebitsGlobal.status.init(form);

	MorebitsGlobal.wiki.actionCompleted.redirect = mw.config.get('wgPageName');
	MorebitsGlobal.wiki.actionCompleted.notice = 'Tagging complete';

	// Modules can't be tagged, follow standard at TfD and place on /doc subpage
	params.scribunto = mw.config.get('wgPageContentModel') === 'Scribunto';
	var wikipedia_page = params.scribunto ? new MorebitsGlobal.wiki.page(mw.config.get('wgPageName') + '/doc', 'Tagging module documentation page') : new MorebitsGlobal.wiki.page(mw.config.get('wgPageName'), 'Tagging page');
	wikipedia_page.setCallbackParameters(params);
	wikipedia_page.load(TwinkleGlobal.speedy.callbacks.user.main);
};
})(jQuery);


// </nowiki>