ശ്രദ്ധിക്കുക: സേവ് ചെയ്തശേഷം മാറ്റങ്ങൾ കാണാനായി താങ്കൾക്ക് ബ്രൗസറിന്റെ കാഷെ ഒഴിവാക്കേണ്ടി വന്നേക്കാം.

  • ഫയർഫോക്സ് / സഫാരി: Reload ബട്ടൺ അമർത്തുമ്പോൾ Shift കീ അമർത്തി പിടിക്കുകയോ, Ctrl-F5 അല്ലെങ്കിൽ Ctrl-R (മാക്കിന്റോഷിൽ ⌘-R ) എന്ന് ഒരുമിച്ച് അമർത്തുകയോ ചെയ്യുക
  • ഗൂഗിൾ ക്രോം: Ctrl-Shift-R (മാക്കിന്റോഷിൽ ⌘-Shift-R ) അമർത്തുക
  • ഇന്റർനെറ്റ് എക്സ്പ്ലോറർ: Refresh ബട്ടൺ അമർത്തുമ്പോൾ Ctrl കീ അമർത്തിപിടിക്കുക. അല്ലെങ്കിൽ Ctrl-F5 അമർത്തുക
  • ഓപ്പറ: Menu → Settings എടുക്കുക (മാക്കിൽ Opera → Preferences) എന്നിട്ട് Privacy & security → Clear browsing data → Cached images and files ചെയ്യുക.
//CONTAINS CODE OF Gadget-twinklewarn.js, Gadget-twinklespeedy.js, Gadget-friendlyshared.js, Gadget-twinklediff.js, Gadget-twinkleunlink.js, Gadget-friendlytag.js
//Gadget-twinklewarn.js
// <nowiki>


(function($) {


/*
 ****************************************
 *** twinklewarn.js: Warn module
 ****************************************
 * Mode of invocation:     Tab ("Warn")
 * Active on:              Any page with relevant user name (userspace, contribs,
 *                         etc.), as well as the rollback success page
 */

Twinkle.warn = function twinklewarn() {

	if (mw.config.get('wgRelevantUserName')) {
		Twinkle.addPortletLink(Twinkle.warn.callback, 'Warn', 'tw-warn', 'Warn/notify user');
		if (Twinkle.getPref('autoMenuAfterRollback') &&
			mw.config.get('wgNamespaceNumber') === 3 &&
			mw.util.getParamValue('vanarticle') &&
			!mw.util.getParamValue('friendlywelcome') &&
			!mw.util.getParamValue('noautowarn')) {
			Twinkle.warn.callback();
		}
	}

	// Modify URL of talk page on rollback success pages, makes use of a
	// custom message box in [[MediaWiki:Rollback-success]]
	if (mw.config.get('wgAction') === 'rollback') {
		var $vandalTalkLink = $('#mw-rollback-success').find('.mw-usertoollinks a').first();
		if ($vandalTalkLink.length) {
			$vandalTalkLink.css('font-weight', 'bold');
			$vandalTalkLink.wrapInner($('<span/>').attr('title', 'ഉചിതമെങ്കിൽ ഈ താളിലെ തിരുത്തലുകൾ കാരണം ഉപയോക്താവിന് അറിയിപ്പ് നൽകുവാൻ ട്വിങ്കിൾ ഉപയോഗിക്കാവുന്നാതാണ്.'));

			// Can't provide vanarticlerevid as only wgCurRevisionId is provided
			var extraParam = 'vanarticle=' + mw.util.rawurlencode(Morebits.pageNameNorm);
			var href = $vandalTalkLink.attr('href');
			if (href.indexOf('?') === -1) {
				$vandalTalkLink.attr('href', href + '?' + extraParam);
			} else {
				$vandalTalkLink.attr('href', href + '&' + extraParam);
			}
		}
	}
};

// Used to close window when switching to ARV in autolevel
Twinkle.warn.dialog = null;

Twinkle.warn.callback = function twinklewarnCallback() {
	if (mw.config.get('wgRelevantUserName') === mw.config.get('wgUserName') &&
		!confirm('താങ്കൾ താങ്കളെ തന്നെ താക്കീത് ചെയ്യാൻ പോകുന്നു! തീരുമാനവുമായി മുന്നോട്ട് പോവുകയല്ല??')) {
		return;
	}

	var dialog;
	Twinkle.warn.dialog = new Morebits.simpleWindow(600, 440);
	dialog = Twinkle.warn.dialog;
	dialog.setTitle('Warn/notify user');
	dialog.setScriptName('Twinkle');
	dialog.addFooterLink('Choosing a warning level', 'WP:UWUL#Levels');
	dialog.addFooterLink('Twinkle help', 'WP:TW/DOC#warn');

	var form = new Morebits.quickForm(Twinkle.warn.callback.evaluate);
	var main_select = form.append({
		type: 'field',
		label: 'Choose type of warning/notice to issue',
		tooltip: 'ആദ്യം ഒരു പ്രധാന മുന്നറിയിപ്പ് വിഭാഗം തിരഞ്ഞെടുക്കുക, പിന്നീട് നിർദിഷ്ട മുന്നറിയിപ്പ് തിരഞ്ഞെടുക്കുക.'
	});

	var main_group = main_select.append({
		type: 'select',
		name: 'main_group',
		event: Twinkle.warn.callback.change_category
	});

	var defaultGroup = parseInt(Twinkle.getPref('defaultWarningGroup'), 10);
	main_group.append({ type: 'option', label: '1: പൊതുവായ കുറിപ്പ്', value: 'level1', selected: defaultGroup === 1 });
	main_group.append({ type: 'option', label: '2: ജാഗ്രത', value: 'level2', selected: defaultGroup === 2 });
	main_group.append({ type: 'option', label: '3: മുന്നറിയിപ്പ്', value: 'level3', selected: defaultGroup === 3 });
	main_group.append({ type: 'option', label: '4: അവസാന മുന്നറിയിപ്പ്', value: 'level4', selected: defaultGroup === 4 });
	main_group.append({ type: 'option', label: '4im: മുന്നറിയിപ്പ് മാത്രം', value: 'level4im', selected: defaultGroup === 5 });
	if (Twinkle.getPref('combinedSingletMenus')) {
		main_group.append({ type: 'option', label: 'ഒരൊറ്റ വിഷയ സന്ദേശങ്ങൾ', value: 'singlecombined', selected: defaultGroup === 6 || defaultGroup === 7 });
	} else {
		main_group.append({ type: 'option', label: 'ഒരൊറ്റ വിഷയ നോട്ടീസുകൾ', value: 'singlenotice', selected: defaultGroup === 6 });
		main_group.append({ type: 'option', label: 'ഒരൊറ്റ വിഷയ മുന്നറിയിപ്പുകൾ', value: 'singlewarn', selected: defaultGroup === 7 });
	}
	if (Twinkle.getPref('customWarningList').length) {
		main_group.append({ type: 'option', label: 'ഇഷ്ടാനുസൃത മുന്നറിയിപ്പുകൾ', value: 'custom', selected: defaultGroup === 9 });
	}
	main_group.append({ type: 'option', label: 'എല്ലാ മുന്നറിയിപ്പ് ഫലകങ്ങളും', value: 'kitchensink', selected: defaultGroup === 10 });
	main_group.append({ type: 'option', label: 'Auto-select level (1-4)', value: 'autolevel', selected: defaultGroup === 11 });

	main_select.append({ type: 'select', name: 'sub_group', event: Twinkle.warn.callback.change_subcategory }); // Will be empty to begin with.

	form.append({
		type: 'input',
		name: 'article',
		label: 'ബന്ധപ്പെട്ട താൾ',
		value: mw.util.getParamValue('vanarticle') || '',
		tooltip: 'A page can be linked within the notice, perhaps because it was a revert to said page that dispatched this notice. Leave empty for no page to be linked.'
	});

	form.append({
		type: 'div',
		label: '',
		style: 'color: red',
		id: 'twinkle-warn-warning-messages'
	});

	var vanrevid = mw.util.getParamValue('vanarticlerevid');
	if (vanrevid) {
		var message = '';
		var query = {};

		// If you tried reverting, check if *you* actually reverted
		if (!mw.util.getParamValue('noautowarn') && mw.util.getParamValue('vanarticle')) { // Via fluff link
			query = {
				action: 'query',
				titles: mw.util.getParamValue('vanarticle'),
				prop: 'revisions',
				rvstartid: vanrevid,
				rvlimit: 2,
				rvdir: 'newer',
				rvprop: 'user'
			};

			new Morebits.wiki.api('Checking if you successfully reverted the page', query, function(apiobj) {
				var revertUser = $(apiobj.getResponse()).find('revisions rev')[1].getAttribute('user');
				if (revertUser && revertUser !== mw.config.get('wgUserName')) {
					message += ' Someone else reverted the page and may have already warned the user.';
					$('#twinkle-warn-warning-messages').text('Note:' + message);
				}
			}).post();
		}

		// Confirm edit wasn't too old for a warning
		query = {
			action: 'query',
			prop: 'revisions',
			rvprop: 'timestamp',
			revids: vanrevid
		};
		new Morebits.wiki.api('Grabbing the revision timestamps', query, function(apiobj) {
			var vantimestamp = $(apiobj.getResponse()).find('revisions rev').attr('timestamp');
			var revDate = new Morebits.date(vantimestamp);
			if (vantimestamp && revDate.isValid()) {
				if (revDate.add(24, 'hours').isBefore(new Date())) {
					message += 'ഈ തിരുത്തൽ 24 മണിക്കൂർ മുൻപ് നടത്തിയതാണ്. ആയതിനാൽ മുന്നറിയിപ്പ് പഴകിയതാണ്.';
					$('#twinkle-warn-warning-messages').text('Note:' + message);
				}
			}
		}).post();
	}

	var more = form.append({ type: 'field', name: 'reasonGroup', label: 'Warning information' });
	more.append({ type: 'textarea', label: 'Optional message:', name: 'reason', tooltip: 'Perhaps a reason, or that a more detailed notice must be appended' });

	var previewlink = document.createElement('a');
	$(previewlink).click(function() {
		Twinkle.warn.callbacks.preview(result);  // |result| is defined below
	});
	previewlink.style.cursor = 'pointer';
	previewlink.textContent = 'Preview';
	more.append({ type: 'div', id: 'warningpreview', label: [ previewlink ] });
	more.append({ type: 'div', id: 'twinklewarn-previewbox', style: 'display: none' });

	more.append({ type: 'submit', label: 'Submit' });

	var result = form.render();
	dialog.setContent(result);
	dialog.display();
	result.main_group.root = result;
	result.previewer = new Morebits.wiki.preview($(result).find('div#twinklewarn-previewbox').last()[0]);

	// We must init the first choice (General Note);
	var evt = document.createEvent('Event');
	evt.initEvent('change', true, true);
	result.main_group.dispatchEvent(evt);
};

// This is all the messages that might be dispatched by the code
// Each of the individual templates require the following information:
//   label (required): A short description displayed in the dialog
//   summary (required): The edit summary used. If an article name is entered, the summary is postfixed with "on [[article]]", and it is always postfixed with ". $summaryAd"
//   suppressArticleInSummary (optional): Set to true to suppress showing the article name in the edit summary. Useful if the warning relates to attack pages, or some such.
Twinkle.warn.messages = {
	levels: {
		'Common warnings': {
			'uw-vandalism': {
				level1: {
					label: 'നശീകരണം',
					summary: 'General note: Unconstructive editing'
				},
				level2: {
					label: 'നശീകരണം',
					summary: 'Caution: Unconstructive editing'
				},
				level3: {
					label: 'നശീകരണം',
					summary: 'Warning: Vandalism'
				},
				level4: {
					label: 'നശീകരണം',
					summary: 'Final warning: Vandalism'
				},
				level4im: {
					label: 'നശീകരണം',
					summary: 'Only warning: Vandalism'
				}
			},
			'uw-disruptive': {
				level1: {
					label: 'Disruptive editing',
					summary: 'General note: Unconstructive editing'
				},
				level2: {
					label: 'Disruptive editing',
					summary: 'Caution: Unconstructive editing'
				},
				level3: {
					label: 'Disruptive editing',
					summary: 'Warning: Disruptive editing'
				}
			},
			'uw-test': {
				level1: {
					label: 'Editing tests',
					summary: 'General note: Editing tests'
				},
				level2: {
					label: 'Editing tests',
					summary: 'Caution: Editing tests'
				},
				level3: {
					label: 'Editing tests',
					summary: 'Warning: Editing tests'
				}
			},
			'uw-delete': {
				level1: {
					label: 'Removal of content, blanking',
					summary: 'General note: Removal of content, blanking'
				},
				level2: {
					label: 'Removal of content, blanking',
					summary: 'Caution: Removal of content, blanking'
				},
				level3: {
					label: 'Removal of content, blanking',
					summary: 'Warning: Removal of content, blanking'
				},
				level4: {
					label: 'Removal of content, blanking',
					summary: 'Final warning: Removal of content, blanking'
				},
				level4im: {
					label: 'Removal of content, blanking',
					summary: 'Only warning: Removal of content, blanking'
				}
			},
			'uw-generic': {
				level4: {
					label: 'Generic warning (for template series missing level 4)',
					summary: 'Final warning notice'
				}
			}
		},
		'Behavior in articles': {
			'uw-biog': {
				level1: {
					label: 'Adding unreferenced controversial information about living persons',
					summary: 'General note: Adding unreferenced controversial information about living persons'
				},
				level2: {
					label: 'Adding unreferenced controversial information about living persons',
					summary: 'Caution: Adding unreferenced controversial information about living persons'
				},
				level3: {
					label: 'Adding unreferenced controversial/defamatory information about living persons',
					summary: 'Warning: Adding unreferenced controversial information about living persons'
				},
				level4: {
					label: 'Adding unreferenced defamatory information about living persons',
					summary: 'Final warning: Adding unreferenced controversial information about living persons'
				},
				level4im: {
					label: 'Adding unreferenced defamatory information about living persons',
					summary: 'Only warning: Adding unreferenced controversial information about living persons'
				}
			},
			'uw-defamatory': {
				level1: {
					label: 'Addition of defamatory content',
					summary: 'General note: Addition of defamatory content'
				},
				level2: {
					label: 'Addition of defamatory content',
					summary: 'Caution: Addition of defamatory content'
				},
				level3: {
					label: 'Addition of defamatory content',
					summary: 'Warning: Addition of defamatory content'
				},
				level4: {
					label: 'Addition of defamatory content',
					summary: 'Final warning: Addition of defamatory content'
				},
				level4im: {
					label: 'Addition of defamatory content',
					summary: 'Only warning: Addition of defamatory content'
				}
			},
			'uw-error': {
				level1: {
					label: 'Introducing deliberate factual errors',
					summary: 'General note: Introducing factual errors'
				},
				level2: {
					label: 'Introducing deliberate factual errors',
					summary: 'Caution: Introducing factual errors'
				},
				level3: {
					label: 'Introducing deliberate factual errors',
					summary: 'Warning: Introducing deliberate factual errors'
				},
				level4: {
					label: 'Introducing deliberate factual errors',
					summary: 'Final warning: Introducing deliberate factual errors'
				}
			},
			'uw-genre': {
				level1: {
					label: 'Frequent or mass changes to genres without consensus or references',
					summary: 'General note: Frequent or mass changes to genres without consensus or references'
				},
				level2: {
					label: 'Frequent or mass changes to genres without consensus or references',
					summary: 'Caution: Frequent or mass changes to genres without consensus or references'
				},
				level3: {
					label: 'Frequent or mass changes to genres without consensus or reference',
					summary: 'Warning: Frequent or mass changes to genres without consensus or reference'
				},
				level4: {
					label: 'Frequent or mass changes to genres without consensus or reference',
					summary: 'Final warning: Frequent or mass changes to genres without consensus or reference'
				}
			},
			'uw-image': {
				level1: {
					label: 'Image-related vandalism in articles',
					summary: 'General note: Image-related vandalism in articles'
				},
				level2: {
					label: 'Image-related vandalism in articles',
					summary: 'Caution: Image-related vandalism in articles'
				},
				level3: {
					label: 'Image-related vandalism in articles',
					summary: 'Warning: Image-related vandalism in articles'
				},
				level4: {
					label: 'Image-related vandalism in articles',
					summary: 'Final warning: Image-related vandalism in articles'
				},
				level4im: {
					label: 'Image-related vandalism',
					summary: 'Only warning: Image-related vandalism'
				}
			},
			'uw-joke': {
				level1: {
					label: 'Using improper humor in articles',
					summary: 'General note: Using improper humor in articles'
				},
				level2: {
					label: 'Using improper humor in articles',
					summary: 'Caution: Using improper humor in articles'
				},
				level3: {
					label: 'Using improper humor in articles',
					summary: 'Warning: Using improper humor in articles'
				},
				level4: {
					label: 'Using improper humor in articles',
					summary: 'Final warning: Using improper humor in articles'
				},
				level4im: {
					label: 'Using improper humor',
					summary: 'Only warning: Using improper humor'
				}
			},
			'uw-nor': {
				level1: {
					label: 'Adding original research, including unpublished syntheses of sources',
					summary: 'General note: Adding original research, including unpublished syntheses of sources'
				},
				level2: {
					label: 'Adding original research, including unpublished syntheses of sources',
					summary: 'Caution: Adding original research, including unpublished syntheses of sources'
				},
				level3: {
					label: 'Adding original research, including unpublished syntheses of sources',
					summary: 'Warning: Adding original research, including unpublished syntheses of sources'
				},
				level4: {
					label: 'Adding original research, including unpublished syntheses of sources',
					summary: 'Final warning: Adding original research, including unpublished syntheses of sources'
				}
			},
			'uw-notcensored': {
				level1: {
					label: 'Censorship of material',
					summary: 'General note: Censorship of material'
				},
				level2: {
					label: 'Censorship of material',
					summary: 'Caution: Censorship of material'
				},
				level3: {
					label: 'Censorship of material',
					summary: 'Warning: Censorship of material'
				}
			},
			'uw-own': {
				level1: {
					label: 'Ownership of articles',
					summary: 'General note: Ownership of articles'
				},
				level2: {
					label: 'Ownership of articles',
					summary: 'Caution: Ownership of articles'
				},
				level3: {
					label: 'Ownership of articles',
					summary: 'Warning: Ownership of articles'
				},
				level4: {
					label: 'Ownership of articles',
					summary: 'Final warning: Ownership of articles'
				},
				level4im: {
					label: 'Ownership of articles',
					summary: 'Only warning: Ownership of articles'
				}
			},
			'uw-tdel': {
				level1: {
					label: 'Removal of maintenance templates',
					summary: 'General note: Removal of maintenance templates'
				},
				level2: {
					label: 'Removal of maintenance templates',
					summary: 'Caution: Removal of maintenance templates'
				},
				level3: {
					label: 'Removal of maintenance templates',
					summary: 'Warning: Removal of maintenance templates'
				},
				level4: {
					label: 'Removal of maintenance templates',
					summary: 'Final warning: Removal of maintenance templates'
				}
			},
			'uw-unsourced': {
				level1: {
					label: 'Addition of unsourced or improperly cited material',
					summary: 'General note: Addition of unsourced or improperly cited material'
				},
				level2: {
					label: 'Addition of unsourced or improperly cited material',
					summary: 'Caution: Addition of unsourced or improperly cited material'
				},
				level3: {
					label: 'Addition of unsourced or improperly cited material',
					summary: 'Warning: Addition of unsourced or improperly cited material'
				},
				level4: {
					label: 'Addition of unsourced or improperly cited material',
					summary: 'Final warning: Addition of unsourced or improperly cited material'
				}
			}
		},
		'Promotions and spam': {
			'uw-advert': {
				level1: {
					label: 'Using Wikipedia for advertising or promotion',
					summary: 'General note: Using Wikipedia for advertising or promotion'
				},
				level2: {
					label: 'Using Wikipedia for advertising or promotion',
					summary: 'Caution: Using Wikipedia for advertising or promotion'
				},
				level3: {
					label: 'Using Wikipedia for advertising or promotion',
					summary: 'Warning: Using Wikipedia for advertising or promotion'
				},
				level4: {
					label: 'Using Wikipedia for advertising or promotion',
					summary: 'Final warning: Using Wikipedia for advertising or promotion'
				},
				level4im: {
					label: 'Using Wikipedia for advertising or promotion',
					summary: 'Only warning: Using Wikipedia for advertising or promotion'
				}
			},
			'uw-npov': {
				level1: {
					label: 'Not adhering to neutral point of view',
					summary: 'General note: Not adhering to neutral point of view'
				},
				level2: {
					label: 'Not adhering to neutral point of view',
					summary: 'Caution: Not adhering to neutral point of view'
				},
				level3: {
					label: 'Not adhering to neutral point of view',
					summary: 'Warning: Not adhering to neutral point of view'
				},
				level4: {
					label: 'Not adhering to neutral point of view',
					summary: 'Final warning: Not adhering to neutral point of view'
				}
			},
			'uw-paid': {
				level1: {
					label: 'Paid editing without disclosure under the Wikimedia Terms of Use',
					summary: 'General note: Paid editing without disclosure under the Wikimedia Terms of Use'
				},
				level2: {
					label: 'Paid editing without disclosure under the Wikimedia Terms of Use',
					summary: 'Caution: Paid editing without disclosure under the Wikimedia Terms of Use'
				},
				level3: {
					label: 'Paid editing without disclosure under the Wikimedia Terms of Use',
					summary: 'Warning: Paid editing without disclosure under the Wikimedia Terms of Use'
				},
				level4: {
					label: 'Paid editing without disclosure under the Wikimedia Terms of Use',
					summary: 'Final warning: Paid editing without disclosure under the Wikimedia Terms of Use'
				}
			},
			'uw-spam': {
				level1: {
					label: 'Adding inappropriate external links',
					summary: 'General note: Adding inappropriate external links'
				},
				level2: {
					label: 'Adding spam links',
					summary: 'Caution: Adding spam links'
				},
				level3: {
					label: 'Adding spam links',
					summary: 'Warning: Adding spam links'
				},
				level4: {
					label: 'Adding spam links',
					summary: 'Final warning: Adding spam links'
				},
				level4im: {
					label: 'Adding spam links',
					summary: 'Only warning: Adding spam links'
				}
			}
		},
		'Behavior towards other editors': {
			'uw-agf': {
				level1: {
					label: 'Not assuming good faith',
					summary: 'General note: Not assuming good faith'
				},
				level2: {
					label: 'Not assuming good faith',
					summary: 'Caution: Not assuming good faith'
				},
				level3: {
					label: 'Not assuming good faith',
					summary: 'Warning: Not assuming good faith'
				}
			},
			'uw-harass': {
				level1: {
					label: 'Harassment of other users',
					summary: 'General note: Harassment of other users'
				},
				level2: {
					label: 'Harassment of other users',
					summary: 'Caution: Harassment of other users'
				},
				level3: {
					label: 'Harassment of other users',
					summary: 'Warning: Harassment of other users'
				},
				level4: {
					label: 'Harassment of other users',
					summary: 'Final warning: Harassment of other users'
				},
				level4im: {
					label: 'Harassment of other users',
					summary: 'Only warning: Harassment of other users'
				}
			},
			'uw-npa': {
				level1: {
					label: 'Personal attack directed at a specific editor',
					summary: 'General note: Personal attack directed at a specific editor'
				},
				level2: {
					label: 'Personal attack directed at a specific editor',
					summary: 'Caution: Personal attack directed at a specific editor'
				},
				level3: {
					label: 'Personal attack directed at a specific editor',
					summary: 'Warning: Personal attack directed at a specific editor'
				},
				level4: {
					label: 'Personal attack directed at a specific editor',
					summary: 'Final warning: Personal attack directed at a specific editor'
				},
				level4im: {
					label: 'Personal attack directed at a specific editor',
					summary: 'Only warning: Personal attack directed at a specific editor'
				}
			},
			'uw-tempabuse': {
				level1: {
					label: 'Improper use of warning or blocking template',
					summary: 'General note: Improper use of warning or blocking template'
				},
				level2: {
					label: 'Improper use of warning or blocking template',
					summary: 'Caution: Improper use of warning or blocking template'
				}
			}
		},
		'Removal of deletion tags': {
			'uw-afd': {
				level1: {
					label: 'Removing {{afd}} templates',
					summary: 'General note: Removing {{afd}} templates'
				},
				level2: {
					label: 'Removing {{afd}} templates',
					summary: 'Caution: Removing {{afd}} templates'
				},
				level3: {
					label: 'Removing {{afd}} templates',
					summary: 'Warning: Removing {{afd}} templates'
				},
				level4: {
					label: 'Removing {{afd}} templates',
					summary: 'Final warning: Removing {{afd}} templates'
				}
			},
			'uw-blpprod': {
				level1: {
					label: 'Removing {{blp prod}} templates',
					summary: 'General note: Removing {{blp prod}} templates'
				},
				level2: {
					label: 'Removing {{blp prod}} templates',
					summary: 'Caution: Removing {{blp prod}} templates'
				},
				level3: {
					label: 'Removing {{blp prod}} templates',
					summary: 'Warning: Removing {{blp prod}} templates'
				},
				level4: {
					label: 'Removing {{blp prod}} templates',
					summary: 'Final warning: Removing {{blp prod}} templates'
				}
			},
			'uw-idt': {
				level1: {
					label: 'Removing file deletion tags',
					summary: 'General note: Removing file deletion tags'
				},
				level2: {
					label: 'Removing file deletion tags',
					summary: 'Caution: Removing file deletion tags'
				},
				level3: {
					label: 'Removing file deletion tags',
					summary: 'Warning: Removing file deletion tags'
				},
				level4: {
					label: 'Removing file deletion tags',
					summary: 'Final warning: Removing file deletion tags'
				}
			},
			'uw-speedy': {
				level1: {
					label: 'Removing speedy deletion tags',
					summary: 'General note: Removing speedy deletion tags'
				},
				level2: {
					label: 'Removing speedy deletion tags',
					summary: 'Caution: Removing speedy deletion tags'
				},
				level3: {
					label: 'Removing speedy deletion tags',
					summary: 'Warning: Removing speedy deletion tags'
				},
				level4: {
					label: 'Removing speedy deletion tags',
					summary: 'Final warning: Removing speedy deletion tags'
				}
			}
		},
		'Other': {
			'uw-attempt': {
				level1: {
					label: 'Triggering the edit filter',
					summary: 'General note: Triggering the edit filter'
				},
				level2: {
					label: 'Triggering the edit filter',
					summary: 'Caution: Triggering the edit filter'
				},
				level3: {
					label: 'Triggering the edit filter',
					summary: 'Warning: Triggering the edit filter'
				},
				level4: {
					label: 'Triggering the edit filter',
					summary: 'Final warning: Triggering the edit filter'
				}
			},
			'uw-chat': {
				level1: {
					label: 'Using talk page as forum',
					summary: 'General note: Using talk page as forum'
				},
				level2: {
					label: 'Using talk page as forum',
					summary: 'Caution: Using talk page as forum'
				},
				level3: {
					label: 'Using talk page as forum',
					summary: 'Warning: Using talk page as forum'
				},
				level4: {
					label: 'Using talk page as forum',
					summary: 'Final warning: Using talk page as forum'
				}
			},
			'uw-create': {
				level1: {
					label: 'Creating inappropriate pages',
					summary: 'General note: Creating inappropriate pages'
				},
				level2: {
					label: 'Creating inappropriate pages',
					summary: 'Caution: Creating inappropriate pages'
				},
				level3: {
					label: 'Creating inappropriate pages',
					summary: 'Warning: Creating inappropriate pages'
				},
				level4: {
					label: 'Creating inappropriate pages',
					summary: 'Final warning: Creating inappropriate pages'
				},
				level4im: {
					label: 'Creating inappropriate pages',
					summary: 'Only warning: Creating inappropriate pages'
				}
			},
			'uw-mos': {
				level1: {
					label: 'Manual of style',
					summary: 'General note: Formatting, date, language, etc (Manual of style)'
				},
				level2: {
					label: 'Manual of style',
					summary: 'Caution: Formatting, date, language, etc (Manual of style)'
				},
				level3: {
					label: 'Manual of style',
					summary: 'Warning: Formatting, date, language, etc (Manual of style)'
				},
				level4: {
					label: 'Manual of style',
					summary: 'Final warning: Formatting, date, language, etc (Manual of style)'
				}
			},
			'uw-move': {
				level1: {
					label: 'Page moves against naming conventions or consensus',
					summary: 'General note: Page moves against naming conventions or consensus'
				},
				level2: {
					label: 'Page moves against naming conventions or consensus',
					summary: 'Caution: Page moves against naming conventions or consensus'
				},
				level3: {
					label: 'Page moves against naming conventions or consensus',
					summary: 'Warning: Page moves against naming conventions or consensus'
				},
				level4: {
					label: 'Page moves against naming conventions or consensus',
					summary: 'Final warning: Page moves against naming conventions or consensus'
				},
				level4im: {
					label: 'Page moves against naming conventions or consensus',
					summary: 'Only warning: Page moves against naming conventions or consensus'
				}
			},
			'uw-tpv': {
				level1: {
					label: "Refactoring others' talk page comments",
					summary: "General note: Refactoring others' talk page comments"
				},
				level2: {
					label: "Refactoring others' talk page comments",
					summary: "Caution: Refactoring others' talk page comments"
				},
				level3: {
					label: "Refactoring others' talk page comments",
					summary: "Warning: Refactoring others' talk page comments"
				},
				level4: {
					label: "Refactoring others' talk page comments",
					summary: "Final warning: Refactoring others' talk page comments"
				},
				level4im: {
					label: "Refactoring others' talk page comments",
					summary: "Only warning: Refactoring others' talk page comments"
				}
			},
			'uw-upload': {
				level1: {
					label: 'Uploading unencyclopedic images',
					summary: 'General note: Uploading unencyclopedic images'
				},
				level2: {
					label: 'Uploading unencyclopedic images',
					summary: 'Caution: Uploading unencyclopedic images'
				},
				level3: {
					label: 'Uploading unencyclopedic images',
					summary: 'Warning: Uploading unencyclopedic images'
				},
				level4: {
					label: 'Uploading unencyclopedic images',
					summary: 'Final warning: Uploading unencyclopedic images'
				},
				level4im: {
					label: 'Uploading unencyclopedic images',
					summary: 'Only warning: Uploading unencyclopedic images'
				}
			}
		}
	},

	singlenotice: {
		'uw-aiv': {
			label: 'Bad AIV report',
			summary: 'Notice: Bad AIV report'
		},
		'uw-autobiography': {
			label: 'Creating autobiographies',
			summary: 'Notice: Creating autobiographies'
		},
		'uw-badcat': {
			label: 'Adding incorrect categories',
			summary: 'Notice: Adding incorrect categories'
		},
		'uw-badlistentry': {
			label: 'Adding inappropriate entries to lists',
			summary: 'Notice: Adding inappropriate entries to lists'
		},
		'uw-bite': {
			label: '"Biting" newcomers',
			summary: 'Notice: "Biting" newcomers',
			suppressArticleInSummary: true  // non-standard (user name, not article), and not necessary
		},
		'uw-coi': {
			label: 'Conflict of interest',
			summary: 'Notice: Conflict of interest',
			heading: 'Managing a conflict of interest'
		},
		'uw-controversial': {
			label: 'Introducing controversial material',
			summary: 'Notice: Introducing controversial material'
		},
		'uw-copying': {
			label: 'Copying text to another page',
			summary: 'Notice: Copying text to another page'
		},
		'uw-crystal': {
			label: 'Adding speculative or unconfirmed information',
			summary: 'Notice: Adding speculative or unconfirmed information'
		},
		'uw-c&pmove': {
			label: 'Cut and paste moves',
			summary: 'Notice: Cut and paste moves'
		},
		'uw-dab': {
			label: 'Incorrect edit to a disambiguation page',
			summary: 'Notice: Incorrect edit to a disambiguation page'
		},
		'uw-date': {
			label: 'Unnecessarily changing date formats',
			summary: 'Notice: Unnecessarily changing date formats'
		},
		'uw-deadlink': {
			label: 'Removing proper sources containing dead links',
			summary: 'Notice: Removing proper sources containing dead links'
		},
		'uw-draftfirst': {
			label: 'User should draft in userspace without the risk of speedy deletion',
			summary: 'Notice: Consider drafting your article in [[Help:Userspace draft|userspace]]'
		},
		'uw-editsummary': {
			label: 'Not using edit summary',
			summary: 'Notice: Not using edit summary'
		},
		'uw-elinbody': {
			label: 'Adding external links to the body of an article',
			summary: 'Notice: Keep external links to External links sections at the bottom of an article'
		},
		'uw-english': {
			label: 'Not communicating in English',
			summary: 'Notice: Not communicating in English'
		},
		'uw-hasty': {
			label: 'Hasty addition of speedy deletion tags',
			summary: 'Notice: Allow creators time to improve their articles before tagging them for deletion'
		},
		'uw-italicize': {
			label: 'Italicize books, films, albums, magazines, TV series, etc within articles',
			summary: 'Notice: Italicize books, films, albums, magazines, TV series, etc within articles'
		},
		'uw-lang': {
			label: 'Unnecessarily changing between British and American English',
			summary: 'Notice: Unnecessarily changing between British and American English',
			heading: 'National varieties of English'
		},
		'uw-linking': {
			label: 'Excessive addition of redlinks or repeated blue links',
			summary: 'Notice: Excessive addition of redlinks or repeated blue links'
		},
		'uw-minor': {
			label: 'Incorrect use of minor edits check box',
			summary: 'Notice: Incorrect use of minor edits check box'
		},
		'uw-notenglish': {
			label: 'Creating non-English articles',
			summary: 'Notice: Creating non-English articles'
		},
		'uw-notvote': {
			label: 'We use consensus, not voting',
			summary: 'Notice: We use consensus, not voting'
		},
		'uw-plagiarism': {
			label: 'Copying from public domain sources without attribution',
			summary: 'Notice: Copying from public domain sources without attribution'
		},
		'uw-preview': {
			label: 'Use preview button to avoid mistakes',
			summary: 'Notice: Use preview button to avoid mistakes'
		},
		'uw-redlink': {
			label: 'Indiscriminate removal of redlinks',
			summary: 'Notice: Be careful when removing redlinks'
		},
		'uw-selfrevert': {
			label: 'Reverting self tests',
			summary: 'Notice: Reverting self tests'
		},
		'uw-socialnetwork': {
			label: 'Wikipedia is not a social network',
			summary: 'Notice: Wikipedia is not a social network'
		},
		'uw-sofixit': {
			label: 'Be bold and fix things yourself',
			summary: 'Notice: You can be bold and fix things yourself'
		},
		'uw-spoiler': {
			label: 'Adding spoiler alerts or removing spoilers from appropriate sections',
			summary: "Notice: Don't delete or flag potential 'spoilers' in Wikipedia articles"
		},
		'uw-talkinarticle': {
			label: 'Talk in article',
			summary: 'Notice: Talk in article'
		},
		'uw-tilde': {
			label: 'Not signing posts',
			summary: 'Notice: Not signing posts'
		},
		'uw-toppost': {
			label: 'Posting at the top of talk pages',
			summary: 'Notice: Posting at the top of talk pages'
		},
		'uw-userspace draft finish': {
			label: 'Stale userspace draft',
			summary: 'Notice: Stale userspace draft'
		},
		'uw-vgscope': {
			label: 'Adding video game walkthroughs, cheats or instructions',
			summary: 'Notice: Adding video game walkthroughs, cheats or instructions'
		},
		'uw-warn': {
			label: 'Place user warning templates when reverting vandalism',
			summary: 'Notice: You can use user warning templates when reverting vandalism'
		},
		'uw-wrongsummary': {
			label: 'Using inaccurate or inappropriate edit summaries',
			summary: 'Warning: Using inaccurate or inappropriate edit summaries'
		}
	},

	singlewarn: {
		'uw-3rr': {
			label: 'Potential three-revert rule violation; see also uw-ew',
			summary: 'Warning: Three-revert rule'
		},
		'uw-affiliate': {
			label: 'Affiliate marketing',
			summary: 'Warning: Affiliate marketing'
		},
		'uw-agf-sock': {
			label: 'Use of multiple accounts (assuming good faith)',
			summary: 'Warning: Using multiple accounts'
		},
		'uw-attack': {
			label: 'Creating attack pages',
			summary: 'Warning: Creating attack pages',
			suppressArticleInSummary: true
		},
		'uw-botun': {
			label: 'Bot username',
			summary: 'Warning: Bot username'
		},
		'uw-canvass': {
			label: 'Canvassing',
			summary: 'Warning: Canvassing'
		},
		'uw-copyright': {
			label: 'Copyright violation',
			summary: 'Warning: Copyright violation'
		},
		'uw-copyright-link': {
			label: 'Linking to copyrighted works violation',
			summary: 'Warning: Linking to copyrighted works violation'
		},
		'uw-copyright-new': {
			label: 'Copyright violation (with explanation for new users)',
			summary: 'Notice: Avoiding copyright problems',
			heading: 'Wikipedia and copyright'
		},
		'uw-copyright-remove': {
			label: 'Removing {{copyvio}} template from articles',
			summary: 'Warning: Removing {{copyvio}} templates'
		},
		'uw-efsummary': {
			label: 'Edit summary triggering the edit filter',
			summary: 'Warning: Edit summary triggering the edit filter'
		},
		'uw-ew': {
			label: 'Edit warring (stronger wording)',
			summary: 'Warning: Edit warring'
		},
		'uw-ewsoft': {
			label: 'Edit warring (softer wording for newcomers)',
			summary: 'Warning: Edit warring'
		},
		'uw-hijacking': {
			label: 'Hijacking articles',
			summary: 'Warning: Hijacking articles'
		},
		'uw-hoax': {
			label: 'Creating hoaxes',
			summary: 'Warning: Creating hoaxes'
		},
		'uw-legal': {
			label: 'Making legal threats',
			summary: 'Warning: Making legal threats'
		},
		'uw-login': {
			label: 'Editing while logged out',
			summary: 'Warning: Editing while logged out'
		},
		'uw-multipleIPs': {
			label: 'Usage of multiple IPs',
			summary: 'Warning: Vandalism using multiple IPs'
		},
		'uw-pinfo': {
			label: 'Personal info',
			summary: 'Warning: Personal info'
		},
		'uw-salt': {
			label: 'Recreating salted articles under a different title',
			summary: 'Notice: Recreating creation-protected articles under a different title'
		},
		'uw-socksuspect': {
			label: 'Sockpuppetry',
			summary: 'Warning: You are a suspected [[WP:SOCK|sockpuppet]]'  // of User:...
		},
		'uw-upv': {
			label: 'Userpage vandalism',
			summary: 'Warning: Userpage vandalism'
		},
		'uw-username': {
			label: 'Username is against policy',
			summary: 'Warning: Your username might be against policy',
			suppressArticleInSummary: true  // not relevant for this template
		},
		'uw-coi-username': {
			label: 'Username is against policy, and conflict of interest',
			summary: 'Warning: Username and conflict of interest policy',
			heading: 'Your username'
		},
		'uw-userpage': {
			label: 'Userpage or subpage is against policy',
			summary: 'Warning: Userpage or subpage is against policy'
		}
	}
};

// Used repeatedly below across menu rebuilds
Twinkle.warn.prev_article = null;
Twinkle.warn.prev_reason = null;
Twinkle.warn.talkpageObj = null;

Twinkle.warn.callback.change_category = function twinklewarnCallbackChangeCategory(e) {
	var value = e.target.value;
	var sub_group = e.target.root.sub_group;
	sub_group.main_group = value;
	var old_subvalue = sub_group.value;
	var old_subvalue_re;
	if (old_subvalue) {
		if (value === 'kitchensink') { // Exact match possible in kitchensink menu
			old_subvalue_re = new RegExp(mw.util.escapeRegExp(old_subvalue));
		} else {
			old_subvalue = old_subvalue.replace(/\d*(im)?$/, '');
			old_subvalue_re = new RegExp(mw.util.escapeRegExp(old_subvalue) + '(\\d*(?:im)?)$');
		}
	}

	while (sub_group.hasChildNodes()) {
		sub_group.removeChild(sub_group.firstChild);
	}

	var selected = false;
	// worker function to create the combo box entries
	var createEntries = function(contents, container, wrapInOptgroup, val) {
		val = typeof val !== 'undefined' ? val : value; // IE doesn't support default parameters
		// level2->2, singlewarn->''; also used to distinguish the
		// scaled levels from singlenotice, singlewarn, and custom
		var level = val.replace(/^\D+/g, '');
		// due to an apparent iOS bug, we have to add an option-group to prevent truncation of text
		// (search WT:TW archives for "Problem selecting warnings on an iPhone")
		if (wrapInOptgroup && $.client.profile().platform === 'iphone') {
			var wrapperOptgroup = new Morebits.quickForm.element({
				type: 'optgroup',
				label: 'Available templates'
			});
			wrapperOptgroup = wrapperOptgroup.render();
			container.appendChild(wrapperOptgroup);
			container = wrapperOptgroup;
		}

		$.each(contents, function(itemKey, itemProperties) {
			// Skip if the current template doesn't have a version for the current level
			if (!!level && !itemProperties[val]) {
				return;
			}
			var key = typeof itemKey === 'string' ? itemKey : itemProperties.value;
			var template = key + level;

			var elem = new Morebits.quickForm.element({
				type: 'option',
				label: '{{' + template + '}}: ' + (level ? itemProperties[val].label : itemProperties.label),
				value: template
			});

			// Select item best corresponding to previous selection
			if (!selected && old_subvalue && old_subvalue_re.test(template)) {
				elem.data.selected = selected = true;
			}
			var elemRendered = container.appendChild(elem.render());
			$(elemRendered).data('messageData', itemProperties);
		});
	};

	switch (value) {
		case 'singlenotice':
		case 'singlewarn':
			createEntries(Twinkle.warn.messages[value], sub_group, true);
			break;
		case 'singlecombined':
			var unSortedSinglets = $.extend({}, Twinkle.warn.messages.singlenotice, Twinkle.warn.messages.singlewarn);
			var sortedSingletMessages = {};
			Object.keys(unSortedSinglets).sort().forEach(function(key) {
				sortedSingletMessages[key] = unSortedSinglets[key];
			});
			createEntries(sortedSingletMessages, sub_group, true);
			break;
		case 'custom':
			createEntries(Twinkle.getPref('customWarningList'), sub_group, true);
			break;
		case 'kitchensink':
			['level1', 'level2', 'level3', 'level4', 'level4im'].forEach(function(lvl) {
				$.each(Twinkle.warn.messages.levels, function(_, levelGroup) {
					createEntries(levelGroup, sub_group, true, lvl);
				});
			});
			createEntries(Twinkle.warn.messages.singlenotice, sub_group, true);
			createEntries(Twinkle.warn.messages.singlewarn, sub_group, true);
			createEntries(Twinkle.getPref('customWarningList'), sub_group, true);
			break;
		case 'level1':
		case 'level2':
		case 'level3':
		case 'level4':
		case 'level4im':
			// Creates subgroup regardless of whether there is anything to place in it;
			// leaves "Removal of deletion tags" empty for 4im
			$.each(Twinkle.warn.messages.levels, function(groupLabel, groupContents) {
				var optgroup = new Morebits.quickForm.element({
					type: 'optgroup',
					label: groupLabel
				});
				optgroup = optgroup.render();
				sub_group.appendChild(optgroup);
				// create the options
				createEntries(groupContents, optgroup, false);
			});
			break;
		case 'autolevel':
			// Check user page to determine appropriate level
			var autolevelProc = function() {
				var wikitext = Twinkle.warn.talkpageObj.getPageText();
				// history not needed for autolevel
				var latest = Twinkle.warn.callbacks.dateProcessing(wikitext)[0];
				// Pseudo-params with only what's needed to parse the level i.e. no messageData
				var params = {
					sub_group: old_subvalue,
					article: e.target.root.article.value
				};
				var lvl = 'level' + Twinkle.warn.callbacks.autolevelParseWikitext(wikitext, params, latest)[1];

				// Identical to level1, etc. above but explicitly provides the level
				$.each(Twinkle.warn.messages.levels, function(groupLabel, groupContents) {
					var optgroup = new Morebits.quickForm.element({
						type: 'optgroup',
						label: groupLabel
					});
					optgroup = optgroup.render();
					sub_group.appendChild(optgroup);
					// create the options
					createEntries(groupContents, optgroup, false, lvl);
				});

				// Trigger subcategory change, add select menu, etc.
				Twinkle.warn.callback.postCategoryCleanup(e);
			};


			if (Twinkle.warn.talkpageObj) {
				autolevelProc();
			} else {
				var usertalk_page = new Morebits.wiki.page('User_talk:' + mw.config.get('wgRelevantUserName'), 'Loading previous warnings');
				usertalk_page.setFollowRedirect(true);
				usertalk_page.load(function(pageobj) {
					Twinkle.warn.talkpageObj = pageobj; // Update talkpageObj
					autolevelProc();
				});
			}
			break;
		default:
			alert('Unknown warning group in twinklewarn');
			break;
	}

	// Trigger subcategory change, add select menu, etc.
	// Here because of the async load for autolevel
	if (value !== 'autolevel') {
		// reset any autolevel-specific messages while we're here
		$('#twinkle-warn-autolevel-message').remove();

		Twinkle.warn.callback.postCategoryCleanup(e);
	}
};

Twinkle.warn.callback.postCategoryCleanup = function twinklewarnCallbackPostCategoryCleanup(e) {
	// clear overridden label on article textbox
	Morebits.quickForm.setElementTooltipVisibility(e.target.root.article, true);
	Morebits.quickForm.resetElementLabel(e.target.root.article);
	// Trigger custom label/change on main category change
	Twinkle.warn.callback.change_subcategory(e);

	// Use select2 to make the select menu searchable
	if (!Twinkle.getPref('oldSelect')) {
		$('select[name=sub_group]')
			.select2({
				width: '100%',
				matcher: Morebits.select2.matchers.optgroupFull,
				templateResult: Morebits.select2.highlightSearchMatches,
				language: {
					searching: Morebits.select2.queryInterceptor
				}
			})
			.change(Twinkle.warn.callback.change_subcategory);

		$('.select2-selection').keydown(Morebits.select2.autoStart);

		mw.util.addCSS(
			// prevent dropdown from appearing behind the dialog, just in case
			'.select2-container { z-index: 10000; }' +

			// Increase height
			'.select2-container .select2-dropdown .select2-results > .select2-results__options { max-height: 350px; }' +

			// Reduce padding
			'.select2-results .select2-results__option { padding-top: 1px; padding-bottom: 1px; }' +
			'.select2-results .select2-results__group { padding-top: 1px; padding-bottom: 1px; } ' +

			// Adjust font size
			'.select2-container .select2-dropdown .select2-results { font-size: 13px; }' +
			'.select2-container .selection .select2-selection__rendered { font-size: 13px; }'
		);
	}
};

Twinkle.warn.callback.change_subcategory = function twinklewarnCallbackChangeSubcategory(e) {
	var main_group = e.target.form.main_group.value;
	var value = e.target.form.sub_group.value;

	// Tags that don't take a linked article, but something else (often a username).
	// The value of each tag is the label next to the input field
	var notLinkedArticle = {
		'uw-agf-sock': 'Optional username of other account (without User:) ',
		'uw-bite': "Username of 'bitten' user (without User:) ",
		'uw-socksuspect': 'Username of sock master, if known (without User:) ',
		'uw-username': 'Username violates policy because... ',
		'uw-aiv': 'Optional username that was reported (without User:) '
	};

	if (['singlenotice', 'singlewarn', 'singlecombined', 'kitchensink'].indexOf(main_group) !== -1) {
		if (notLinkedArticle[value]) {
			if (Twinkle.warn.prev_article === null) {
				Twinkle.warn.prev_article = e.target.form.article.value;
			}
			e.target.form.article.notArticle = true;
			e.target.form.article.value = '';

			// change form labels according to the warning selected
			Morebits.quickForm.setElementTooltipVisibility(e.target.form.article, false);
			Morebits.quickForm.overrideElementLabel(e.target.form.article, notLinkedArticle[value]);
		} else if (e.target.form.article.notArticle) {
			if (Twinkle.warn.prev_article !== null) {
				e.target.form.article.value = Twinkle.warn.prev_article;
				Twinkle.warn.prev_article = null;
			}
			e.target.form.article.notArticle = false;
			Morebits.quickForm.setElementTooltipVisibility(e.target.form.article, true);
			Morebits.quickForm.resetElementLabel(e.target.form.article);
		}
	}

	// add big red notice, warning users about how to use {{uw-[coi-]username}} appropriately
	$('#tw-warn-red-notice').remove();
	var $redWarning;
	if (value === 'uw-username') {
		$redWarning = $("<div style='color: red;' id='tw-warn-red-notice'>{{uw-username}} should <b>not</b> be used for <b>blatant</b> username policy violations. " +
			"Blatant violations should be reported directly to UAA (via Twinkle's ARV tab). " +
			'{{uw-username}} should only be used in edge cases in order to engage in discussion with the user.</div>');
		$redWarning.insertAfter(Morebits.quickForm.getElementLabelObject(e.target.form.reasonGroup));
	} else if (value === 'uw-coi-username') {
		$redWarning = $("<div style='color: red;' id='tw-warn-red-notice'>{{uw-coi-username}} should <b>not</b> be used for <b>blatant</b> username policy violations. " +
			"Blatant violations should be reported directly to UAA (via Twinkle's ARV tab). " +
			'{{uw-coi-username}} should only be used in edge cases in order to engage in discussion with the user.</div>');
		$redWarning.insertAfter(Morebits.quickForm.getElementLabelObject(e.target.form.reasonGroup));
	}
};

Twinkle.warn.callbacks = {
	getWarningWikitext: function(templateName, article, reason, isCustom) {
		var text = '{{subst:' + templateName;

		// add linked article for user warnings
		if (article) {
			// c&pmove has the source as the first parameter
			if (templateName === 'uw-c&pmove') {
				text += '|to=' + article;
			} else {
				text += '|1=' + article;
			}
		}
		if (reason && !isCustom) {
			// add extra message
			if (templateName === 'uw-csd' || templateName === 'uw-probation' ||
				templateName === 'uw-userspacenoindex' || templateName === 'uw-userpage') {
				text += "|3=''" + reason + "''";
			} else {
				text += "|2=''" + reason + "''";
			}
		}
		text += '}}';

		if (reason && isCustom) {
			// we assume that custom warnings lack a {{{2}}} parameter
			text += " ''" + reason + "''";
		}

		return text + ' ~~~~';
	},
	showPreview: function(form, templatename) {
		// Provided on autolevel, not otherwise
		templatename = templatename || form.sub_group.value;
		var linkedarticle = form.article.value;
		var templatetext;

		templatetext = Twinkle.warn.callbacks.getWarningWikitext(templatename, linkedarticle,
			form.reason.value, form.main_group.value === 'custom');

		form.previewer.beginRender(templatetext, 'User_talk:' + mw.config.get('wgRelevantUserName')); // Force wikitext/correct username
	},
	// Just a pass-through unless the autolevel option was selected
	preview: function(form) {
		if (form.main_group.value === 'autolevel') {
			// Always get a new, updated talkpage for autolevel processing
			var usertalk_page = new Morebits.wiki.page('User_talk:' + mw.config.get('wgRelevantUserName'), 'Loading previous warnings');
			usertalk_page.setFollowRedirect(true);
			usertalk_page.load(function(pageobj) {
				Twinkle.warn.talkpageObj = pageobj; // Update talkpageObj

				var wikitext = pageobj.getPageText();
				// history not needed for autolevel
				var latest = Twinkle.warn.callbacks.dateProcessing(wikitext)[0];
				var params = {
					sub_group: form.sub_group.value,
					article: form.article.value,
					messageData: $(form.sub_group).find('option[value="' + $(form.sub_group).val() + '"]').data('messageData')
				};
				var template = Twinkle.warn.callbacks.autolevelParseWikitext(wikitext, params, latest)[0];
				Twinkle.warn.callbacks.showPreview(form, template);

				// If the templates have diverged, fake a change event
				// to reload the menu with the updated pageobj
				if (form.sub_group.value !== template) {
					var evt = document.createEvent('Event');
					evt.initEvent('change', true, true);
					form.main_group.dispatchEvent(evt);
				}
			});
		} else {
			Twinkle.warn.callbacks.showPreview(form);
		}
	},
	/**
	* Used in the main and autolevel loops to determine when to warn
	* about excessively recent, stale, or identical warnings.
	* @param {string} wikitext  The text of a user's talk page, from getPageText()
	* @returns {Object[]} - Array of objects: latest contains most recent
	* warning and date; history lists all prior warnings
	*/
	dateProcessing: function(wikitext) {
		var history_re = /<!--\s?Template:([uU]w-.*?)\s?-->.*?(\d{1,2}:\d{1,2}, \d{1,2} \w+ \d{4} \(UTC\))/g;
		var history = {};
		var latest = { date: new Morebits.date(0), type: '' };
		var current;

		while ((current = history_re.exec(wikitext)) !== null) {
			var template = current[1], current_date = new Morebits.date(current[2]);
			if (!(template in history) || history[template].isBefore(current_date)) {
				history[template] = current_date;
			}
			if (!latest.date.isAfter(current_date)) {
				latest.date = current_date;
				latest.type = template;
			}
		}
		return [latest, history];
	},
	/**
	* Main loop for deciding what the level should increment to. Most of
	* this is really just error catching and updating the subsequent data.
	* May produce up to two notices in a twinkle-warn-autolevel-messages div
	*
	* @param {string} wikitext  The text of a user's talk page, from getPageText() (required)
	* @param {Object} params  Params object: sub_group is the template (required);
	* article is the user-provided article (form.article) used to link ARV on recent level4 warnings;
	* messageData is only necessary if getting the full template, as it's
	* used to ensure a valid template of that level exists
	* @param {Object} latest  First element of the array returned from
	* dateProcessing. Provided here rather than processed within to avoid
	* repeated call to dateProcessing
	* @param {(Date|Morebits.date)} date  Date from which staleness is determined
	* @param {Morebits.status} statelem  Status element, only used for handling error in final execution
	*
	* @returns {Array} - Array that contains the full template and just the warning level
	*/
	autolevelParseWikitext: function(wikitext, params, latest, date, statelem) {
		var template = params.sub_group.replace(/(.*)\d$/, '$1');

		var level; // undefined rather than '' means the isNaN below will return true
		if (/\d(?:im)?$/.test(latest.type)) { // level1-4im
			level = parseInt(latest.type.replace(/.*(\d)(?:im)?$/, '$1'), 10);
		} else if (latest.type) { // Non-numbered warning
			// Try to leverage existing categorization of
			// warnings, all but one are universally lowercased
			var loweredType = /uw-multipleIPs/i.test(template) ? 'uw-multipleIPs' : template.toLowerCase();
			// It would be nice to account for blocks, but in most
			// cases the hidden message is terminal, not the sig
			if (Twinkle.warn.messages.singlewarn[loweredType]) {
				level = 3;
			} else {
				level = 1; // singlenotice or not found
			}
		}

		var $autolevelMessage = $('<div/>', {'id': 'twinkle-warn-autolevel-message'});

		if (isNaN(level)) { // No prior warnings found, this is the first
			level = 1;
		} else if (level > 4 || level < 1) { // Shouldn't happen
			var message = 'Unable to parse previous warning level, please manually select a warning level.';
			if (statelem) {
				statelem.error(message);
			} else {
				alert(message);
			}
			return;
		} else {
			date = date || new Date();
			var autoTimeout = new Morebits.date(latest.date.getTime()).add(parseInt(Twinkle.getPref('autolevelStaleDays'), 10), 'days');
			if (autoTimeout.isAfter(date)) {
				if (level === 4) {
					level = 4;
					// Basically indicates whether we're in the final Main evaluation or not,
					// and thus whether we can continue or need to display the warning and link
					if (!statelem) {
						var $link = $('<a/>', {
							'href': '#',
							'text': 'click here to open the ARV tool.',
							'css': { 'fontWeight': 'bold' },
							'click': function() {
								Morebits.wiki.actionCompleted.redirect = null;
								Twinkle.warn.dialog.close();
								Twinkle.arv.callback(mw.config.get('wgRelevantUserName'));
								$('input[name=page]').val(params.article); // Target page
								$('input[value=final]').prop('checked', true); // Vandalism after final
							}
						});
						var statusNode = $('<div/>', {
							'text': mw.config.get('wgRelevantUserName') + ' recently received a level 4 warning (' + latest.type + ') so it might be better to report them instead; ',
							'css': {'color': 'red' }
						});
						statusNode.append($link[0]);
						$autolevelMessage.append(statusNode);
					}
				} else { // Automatically increase severity
					level += 1;
				}
			} else { // Reset warning level if most-recent warning is too old
				level = 1;
			}
		}

		// Validate warning level, falling back to the uw-generic series.
		// Only a few items are missing a level, and in all but a handful
		// of cases, the uw-generic series is explicitly used elsewhere per WP:UTM.
		if (params.messageData && !params.messageData['level' + level]) {
			template = 'uw-generic';
		}
		template += level;

		$autolevelMessage.prepend($('<div>Will issue a <span style="font-weight: bold;">level ' + level + '</span> template.</div>'));
		// Place after the stale and other-user-reverted (text-only) messages
		$('#twinkle-warn-autolevel-message').remove(); // clean slate
		$autolevelMessage.insertAfter($('#twinkle-warn-warning-messages'));

		return [template, level];
	},
	main: function(pageobj) {
		var text = pageobj.getPageText();
		var statelem = pageobj.getStatusElement();
		var params = pageobj.getCallbackParameters();
		var messageData = params.messageData;

		// JS somehow didn't get destructured assignment until ES6 so of course IE doesn't support it
		var warningHistory = Twinkle.warn.callbacks.dateProcessing(text);
		var latest = warningHistory[0];
		var history = warningHistory[1];

		var now = new Morebits.date(pageobj.getLoadTime());

		Twinkle.warn.talkpageObj = pageobj; // Update talkpageObj, just in case
		if (params.main_group === 'autolevel') {
			// [template, level]
			var templateAndLevel = Twinkle.warn.callbacks.autolevelParseWikitext(text, params, latest, now, statelem);

			// Only if there's a change from the prior display/load
			if (params.sub_group !== templateAndLevel[0] && !confirm('Will issue a {{' + templateAndLevel[0] + '}} template to the user, okay?')) {
				statelem.error('aborted per user request');
				return;
			}
			// Update params now that we've selected a warning
			params.sub_group = templateAndLevel[0];
			messageData = params.messageData['level' + templateAndLevel[1]];
		} else if (params.sub_group in history) {
			if (new Morebits.date(history[params.sub_group]).add(1, 'day').isAfter(now)) {
				if (!confirm('An identical ' + params.sub_group + ' has been issued in the last 24 hours.  \nWould you still like to add this warning/notice?')) {
					statelem.error('aborted per user request');
					return;
				}
			}
		}

		latest.date.add(1, 'minute'); // after long debate, one minute is max

		if (latest.date.isAfter(now)) {
			if (!confirm('A ' + latest.type + ' has been issued in the last minute.  \nWould you still like to add this warning/notice?')) {
				statelem.error('aborted per user request');
				return;
			}
		}

		var dateHeaderRegex = now.monthHeaderRegex(), dateHeaderRegexLast, dateHeaderRegexResult;
		while ((dateHeaderRegexLast = dateHeaderRegex.exec(text)) !== null) {
			dateHeaderRegexResult = dateHeaderRegexLast;
		}
		// If dateHeaderRegexResult is null then lastHeaderIndex is never checked. If it is not null but
		// \n== is not found, then the date header must be at the very start of the page. lastIndexOf
		// returns -1 in this case, so lastHeaderIndex gets set to 0 as desired.
		var lastHeaderIndex = text.lastIndexOf('\n==') + 1;

		if (text.length > 0) {
			text += '\n\n';
		}

		if (messageData.heading) {
			text += '== ' + messageData.heading + ' ==\n';
		} else if (!dateHeaderRegexResult || dateHeaderRegexResult.index !== lastHeaderIndex) {
			Morebits.status.info('Info', 'Will create a new level 2 heading for the date, as none was found for this month');
			text += now.monthHeader() + '\n';
		}
		text += Twinkle.warn.callbacks.getWarningWikitext(params.sub_group, params.article,
			params.reason, params.main_group === 'custom');

		if (Twinkle.getPref('showSharedIPNotice') && mw.util.isIPAddress(mw.config.get('wgTitle'))) {
			Morebits.status.info('Info', 'Adding a shared IP notice');
			text += '\n{{subst:Shared IP advice}}';
		}

		// build the edit summary
		var summary;
		if (params.main_group === 'custom') {
			switch (params.sub_group.substr(-1)) {
				case '1':
					summary = 'General note';
					break;
				case '2':
					summary = 'Caution';
					break;
				case '3':
					summary = 'Warning';
					break;
				case '4':
					summary = 'Final warning';
					break;
				case 'm':
					if (params.sub_group.substr(-3) === '4im') {
						summary = 'Only warning';
						break;
					}
					summary = 'Notice';
					break;
				default:
					summary = 'Notice';
					break;
			}
			summary += ': ' + Morebits.string.toUpperCaseFirstChar(messageData.label);
		} else {
			// Normalize kitchensink to the 1-4im style
			if (params.main_group === 'kitchensink' && !/^D+$/.test(params.sub_group)) {
				var sub = params.sub_group.substr(-1);
				if (sub === 'm') {
					sub = params.sub_group.substr(-3);
				}
				// Don't overwrite uw-3rr, technically unnecessary
				if (/\d/.test(sub)) {
					params.main_group = 'level' + sub;
				}
			}
			summary = /^\D+$/.test(params.main_group) ? messageData.summary : messageData[params.main_group].summary;
			if (messageData.suppressArticleInSummary !== true && params.article) {
				if (params.sub_group === 'uw-agf-sock' ||
						params.sub_group === 'uw-socksuspect' ||
						params.sub_group === 'uw-aiv') {  // these templates require a username
					summary += ' of [[:User:' + params.article + ']]';
				} else {
					summary += ' on [[:' + params.article + ']]';
				}
			}
		}
		summary += '.' + Twinkle.getPref('summaryAd');

		pageobj.setPageText(text);
		pageobj.setEditSummary(summary);
		pageobj.setWatchlist(Twinkle.getPref('watchWarnings'));
		pageobj.save();
	}
};

Twinkle.warn.callback.evaluate = function twinklewarnCallbackEvaluate(e) {
	var userTalkPage = 'User_talk:' + mw.config.get('wgRelevantUserName');

	// First, check to make sure a reason was filled in if uw-username was selected

	if (e.target.sub_group.value === 'uw-username' && e.target.article.value.trim() === '') {
		alert('You must supply a reason for the {{uw-username}} template.');
		return;
	}

	// Find the selected <option> element so we can fetch the data structure
	var selectedEl = $(e.target.sub_group).find('option[value="' + $(e.target.sub_group).val() + '"]');

	// Then, grab all the values provided by the form
	var params = {
		reason: e.target.reason.value,
		main_group: e.target.main_group.value,
		sub_group: e.target.sub_group.value,
		article: e.target.article.value,  // .replace( /^(Image|Category):/i, ':$1:' ),  -- apparently no longer needed...
		messageData: selectedEl.data('messageData')
	};

	Morebits.simpleWindow.setButtonsEnabled(false);
	Morebits.status.init(e.target);

	Morebits.wiki.actionCompleted.redirect = userTalkPage;
	Morebits.wiki.actionCompleted.notice = 'Warning complete, reloading talk page in a few seconds';

	var wikipedia_page = new Morebits.wiki.page(userTalkPage, 'User talk page modification');
	wikipedia_page.setCallbackParameters(params);
	wikipedia_page.setFollowRedirect(true);
	wikipedia_page.load(Twinkle.warn.callbacks.main);
};
})(jQuery);


// </nowiki>

//MediaWiki:Gadget-twinklespeedy.js
// <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.
 */

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

	Twinkle.addPortletLink(Twinkle.speedy.callback, 'CSD', 'tw-csd', Morebits.userIsSysop ? 'Delete page according to WP:CSD' : 'Request speedy deletion according to WP:CSD');
};

// This function is run when the CSD tab/header link is clicked
Twinkle.speedy.callback = function twinklespeedyCallback() {
	Twinkle.speedy.initDialog(Morebits.userIsSysop ? Twinkle.speedy.callback.evaluateSysop : Twinkle.speedy.callback.evaluateUser, true);
};

// Used by unlink feature
Twinkle.speedy.dialog = null;
// Used throughout
Twinkle.speedy.hasCSD = !!$('#delete-reason').length;

// The speedy criteria list can be in one of several modes
Twinkle.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 === Twinkle.speedy.mode.sysopSingleSubmit ||
			mode === Twinkle.speedy.mode.sysopMultipleSubmit ||
			mode === Twinkle.speedy.mode.sysopRadioClick ||
			mode === Twinkle.speedy.mode.sysopMultipleRadioClick;
	},
	// do we have a "Submit" button once the form is created?
	hasSubmitButton: function twinklespeedyModeHasSubmitButton(mode) {
		return mode === Twinkle.speedy.mode.sysopSingleSubmit ||
			mode === Twinkle.speedy.mode.sysopMultipleSubmit ||
			mode === Twinkle.speedy.mode.sysopMultipleRadioClick ||
			mode === Twinkle.speedy.mode.userMultipleSubmit ||
			mode === Twinkle.speedy.mode.userMultipleRadioClick ||
			mode === Twinkle.speedy.mode.userSingleSubmit;
	},
	// is db-multiple the outcome here?
	isMultiple: function twinklespeedyModeIsMultiple(mode) {
		return mode === Twinkle.speedy.mode.userMultipleSubmit ||
			mode === Twinkle.speedy.mode.sysopMultipleSubmit ||
			mode === Twinkle.speedy.mode.userMultipleRadioClick ||
			mode === Twinkle.speedy.mode.sysopMultipleRadioClick;
	}
};

// Prepares the speedy deletion dialog and displays it
Twinkle.speedy.initDialog = function twinklespeedyInitDialog(callbackfunc) {
	var dialog;
	Twinkle.speedy.dialog = new Morebits.simpleWindow(Twinkle.getPref('speedyWindowWidth'), Twinkle.getPref('speedyWindowHeight'));
	dialog = Twinkle.speedy.dialog;
	dialog.setTitle('Choose criteria for speedy deletion');
	dialog.setScriptName('Twinkle');
	dialog.addFooterLink('Speedy deletion policy', 'WP:CSD');
	dialog.addFooterLink('Twinkle help', 'WP:TW/DOC#speedy');

	var form = new Morebits.quickForm(callbackfunc, Twinkle.getPref('speedySelectionStyle') === 'radioClick' ? 'change' : null);
	if (Morebits.userIsSysop) {
		form.append({
			type: 'checkbox',
			list: [
				{
					label: 'Tag page only, don\'t delete',
					value: 'tag_only',
					name: 'tag_only',
					tooltip: 'If you just want to tag the page, instead of deleting it now',
					checked: !(Twinkle.speedy.hasCSD || Twinkle.getPref('deleteSysopDefaultToDelete')),
					event: function(event) {
						var cForm = event.target.form;
						var cChecked = event.target.checked;
						// enable talk page checkbox
						if (cForm.talkpage) {
							cForm.talkpage.checked = !cChecked && Twinkle.getPref('deleteTalkPageOnDelete');
						}
						// enable redirects checkbox
						cForm.redirects.checked = !cChecked;
						// enable delete multiple
						cForm.delmultiple.checked = false;
						// enable notify checkbox
						cForm.notify.checked = cChecked;
						// enable deletion notification checkbox
						cForm.warnusertalk.checked = !cChecked && !Twinkle.speedy.hasCSD;
						// enable multiple
						cForm.multiple.checked = false;
						// enable requesting creation protection
						cForm.salting.checked = false;

						Twinkle.speedy.callback.modeChanged(cForm);

						event.stopPropagation();
					}
				}
			]
		});

		var deleteOptions = form.append({
			type: 'div',
			name: 'delete_options'
		});
		deleteOptions.append({
			type: 'header',
			label: 'Delete-related options'
		});
		if (mw.config.get('wgNamespaceNumber') % 2 === 0 && (mw.config.get('wgNamespaceNumber') !== 2 || (/\//).test(mw.config.get('wgTitle')))) {  // hide option for user pages, to avoid accidentally deleting user talk page
			deleteOptions.append({
				type: 'checkbox',
				list: [
					{
						label: 'Also delete talk page',
						value: 'talkpage',
						name: 'talkpage',
						tooltip: "This option deletes the page's talk page in addition. If you choose the F8 (moved to Commons) criterion, this option is ignored and the talk page is *not* deleted.",
						checked: Twinkle.getPref('deleteTalkPageOnDelete'),
						event: function(event) {
							event.stopPropagation();
						}
					}
				]
			});
		}
		deleteOptions.append({
			type: 'checkbox',
			list: [
				{
					label: 'Also delete all redirects',
					value: 'redirects',
					name: 'redirects',
					tooltip: 'This option deletes all incoming redirects in addition. Avoid this option for procedural (e.g. move/merge) deletions.',
					checked: Twinkle.getPref('deleteRedirectsOnDelete'),
					event: function(event) {
						event.stopPropagation();
					}
				}
			]
		});
		deleteOptions.append({
			type: 'checkbox',
			list: [
				{
					label: 'Delete under multiple criteria',
					value: 'delmultiple',
					name: 'delmultiple',
					tooltip: 'When selected, you can select several criteria that apply to the page. For example, G11 and A7 are a common combination for articles.',
					event: function(event) {
						Twinkle.speedy.callback.modeChanged(event.target.form);
						event.stopPropagation();
					}
				}
			]
		});
		deleteOptions.append({
			type: 'checkbox',
			list: [
				{
					label: 'Notify page creator of page deletion',
					value: 'warnusertalk',
					name: 'warnusertalk',
					tooltip: 'A notification template will be placed on the talk page of the creator, IF you have a notification enabled in your Twinkle preferences ' +
						'for the criterion you choose AND this box is checked. The creator may be welcomed as well.',
					checked: !Twinkle.speedy.hasCSD,
					event: function(event) {
						event.stopPropagation();
					}
				}
			]
		});
	}

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

	if (Morebits.userIsSysop) {
		tagOptions.append({
			type: 'header',
			label: 'Tag-related options'
		});
	}

	tagOptions.append({
		type: 'checkbox',
		list: [
			{
				label: 'Notify page creator if possible',
				value: 'notify',
				name: 'notify',
				tooltip: 'A notification template will be placed on the talk page of the creator, IF you have a notification enabled in your Twinkle preferences ' +
						'for the criterion you choose AND this box is checked. The creator may be welcomed as well.',
				checked: !Morebits.userIsSysop || !(Twinkle.speedy.hasCSD || Twinkle.getPref('deleteSysopDefaultToDelete')),
				event: function(event) {
					event.stopPropagation();
				}
			}
		]
	});
	tagOptions.append({
		type: 'checkbox',
		list: [
			{
				label: 'Tag for creation protection (salting) as well',
				value: 'salting',
				name: 'salting',
				tooltip: 'When selected, the speedy deletion tag will be accompanied by a {{salt}} tag requesting that the deleting administrator apply creation protection. Only select if this page has been repeatedly recreated.',
				event: function(event) {
					event.stopPropagation();
				}
			}
		]
	});
	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. For example, G11 and A7 are a common combination for articles.',
				event: function(event) {
					Twinkle.speedy.callback.modeChanged(event.target.form);
					event.stopPropagation();
				}
			}
		]
	});

	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 (Twinkle.getPref('speedySelectionStyle') !== 'radioClick') {
		form.append({ type: 'submit', className: 'tw-speedy-submit' }); // Renamed in modeChanged
	}

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

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

Twinkle.speedy.callback.getMode = function twinklespeedyCallbackGetMode(form) {
	var mode = Twinkle.speedy.mode.userSingleSubmit;
	if (form.tag_only && !form.tag_only.checked) {
		if (form.delmultiple.checked) {
			mode = Twinkle.speedy.mode.sysopMultipleSubmit;
		} else {
			mode = Twinkle.speedy.mode.sysopSingleSubmit;
		}
	} else {
		if (form.multiple.checked) {
			mode = Twinkle.speedy.mode.userMultipleSubmit;
		} else {
			mode = Twinkle.speedy.mode.userSingleSubmit;
		}
	}
	if (Twinkle.getPref('speedySelectionStyle') === 'radioClick') {
		mode++;
	}

	return mode;
};

Twinkle.speedy.callback.modeChanged = function twinklespeedyCallbackModeChanged(form) {
	var namespace = mw.config.get('wgNamespaceNumber');

	// first figure out what mode we're in
	var mode = Twinkle.speedy.callback.getMode(form);
	var isSysopMode = Twinkle.speedy.mode.isSysop(mode);

	if (isSysopMode) {
		$('[name=delete_options]').show();
		$('[name=tag_options]').hide();
		$('button.tw-speedy-submit').text('Delete page');
	} else {
		$('[name=delete_options]').hide();
		$('[name=tag_options]').show();
		$('button.tw-speedy-submit').text('Tag page');
	}

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

	if (mode === Twinkle.speedy.mode.userMultipleRadioClick || mode === Twinkle.speedy.mode.sysopMultipleRadioClick) {
		var evaluateType = isSysopMode ? 'evaluateSysop' : 'evaluateUser';

		work_area.append({
			type: 'div',
			label: 'When finished choosing criteria, click:'
		});
		work_area.append({
			type: 'button',
			name: 'submit-multiple',
			label: isSysopMode ? 'Delete page' : 'Tag page',
			event: function(event) {
				Twinkle.speedy.callback[evaluateType](event);
				event.stopPropagation();
			}
		});
	}

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

	if (isSysopMode && !Twinkle.speedy.mode.isMultiple(mode)) {
		work_area.append({ type: 'header', label: 'Custom rationale' });
		work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.customRationale, mode) });
	}

	if (namespace % 2 === 1 && namespace !== 3) {
		// show db-talk on talk pages, but not user talk pages
		work_area.append({ type: 'header', label: 'Talk pages' });
		work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.talkList, mode) });
	}

	if (!mw.config.get('wgIsRedirect')) {
		switch (namespace) {
			case 0:  // article
			case 1:  // talk
				work_area.append({ type: 'header', label: 'Articles' });
				work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.articleList, mode) });
				break;

			case 2:  // user
			case 3:  // user talk
				work_area.append({ type: 'header', label: 'User pages' });
				work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.userList, mode) });
				break;

			case 6:  // file
			case 7:  // file talk
				work_area.append({ type: 'header', label: 'Files' });
				work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.fileList, mode) });
				if (!isSysopMode) {
					work_area.append({ type: 'div', label: 'Tagging for CSD F4 (no license), F5 (orphaned fair use), F6 (no fair use rationale), and F11 (no permission) can be done using Twinkle\'s "DI" tab.' });
				}
				break;

			case 10:  // template
			case 11:  // template talk
				work_area.append({ type: 'header', label: 'Templates' });
				work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.templateList, mode) });
				break;

			case 14:  // category
			case 15:  // category talk
				work_area.append({ type: 'header', label: 'Categories' });
				work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.categoryList, mode) });
				break;

			case 100:  // portal
			case 101:  // portal talk
				work_area.append({ type: 'header', label: 'Portals' });
				work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.portalList, mode) });
				break;

			default:
				break;
		}
	} else {
		if (namespace === 2 || namespace === 3) {
			work_area.append({ type: 'header', label: 'User pages' });
			work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.userList, mode) });
		}
		work_area.append({ type: 'header', label: 'Redirects' });
		work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.redirectList, mode) });
	}

	var generalCriteria = Twinkle.speedy.generalList;

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

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

	// if sysop, check if CSD is already on the page and fill in custom rationale
	if (isSysopMode && Twinkle.speedy.hasCSD) {
		var customOption = $('input[name=csd][value=reason]')[0];
		if (customOption) {
			if (Twinkle.getPref('speedySelectionStyle') !== 'radioClick') {
				// force listeners to re-init
				customOption.click();
				customOption.parentNode.appendChild(customOption.subgroup);
			}
			customOption.subgroup.querySelector('input').value = decodeURIComponent($('#delete-reason').text()).replace(/\+/g, ' ');
		}
	}
};

Twinkle.speedy.generateCsdList = function twinklespeedyGenerateCsdList(list, mode) {
	// mode switches
	var isSysopMode = Twinkle.speedy.mode.isSysop(mode);
	var multiple = Twinkle.speedy.mode.isMultiple(mode);
	var hasSubmitButton = Twinkle.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 = Twinkle.speedy.mode.isSysop(mode) ? 'evaluateSysop' : 'evaluateUser';
		Twinkle.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 (isSysopMode) {
			if (criterion.hideWhenSysop) {
				return null;
			}
			if (criterion.hideSubgroupWhenSysop) {
				criterion.subgroup = null;
			}
		} else {
			if (criterion.hideWhenUser) {
				return null;
			}
			if (criterion.hideSubgroupWhenUser) {
				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: isSysopMode ? 'Delete page' : 'Tag page',
					event: submitSubgroupHandler
				});
			} else {
				criterion.subgroup = [
					criterion.subgroup,
					{
						type: 'button',
						name: 'submit',  // ends up being called "csd.submit" so this is OK
						label: isSysopMode ? 'Delete page' : 'Tag page',
						event: submitSubgroupHandler
					}
				];
			}
			// FIXME: does this do anything?
			criterion.event = openSubgroupHandler;
		}

		return criterion;
	});
};

Twinkle.speedy.customRationale = [
	{
		label: 'Custom rationale' + (Morebits.userIsSysop ? ' (custom deletion reason)' : ' using {{db}} template'),
		value: 'reason',
		tooltip: '{{db}} is short for "delete because". At least one of the other deletion criteria must still apply to the page, and you must make mention of this in your rationale. This is not a "catch-all" for when you can\'t find any criteria that fit.',
		subgroup: {
			name: 'reason_1',
			type: 'input',
			label: 'Rationale: ',
			size: 60
		},
		hideWhenMultiple: true
	}
];

Twinkle.speedy.talkList = [
	{
		label: 'G8: Talk pages with no corresponding subject page',
		value: 'talk',
		tooltip: 'This excludes any page that is useful to the project - in particular, user talk pages, talk page archives, and talk pages for files that exist on Wikimedia Commons.'
	}
];

Twinkle.speedy.fileList = [
	{
		label: 'F1: Redundant file',
		value: 'redundantimage',
		tooltip: 'Any file that is a redundant copy, in the same file format and same or lower resolution, of something else on Wikipedia. Likewise, other media that is a redundant copy, in the same format and of the same or lower quality. This does not apply to files duplicated on Wikimedia Commons, because of licence issues; these should be tagged with {{subst:ncd|Image:newname.ext}} or {{subst:ncd}} instead',
		subgroup: {
			name: 'redundantimage_filename',
			type: 'input',
			label: 'File this is redundant to: ',
			tooltip: 'The "File:" prefix can be left off.'
		}
	},
	{
		label: 'F2: Corrupt, mising, or empty file',
		value: 'noimage',
		tooltip: 'Before deleting this type of file, verify that the MediaWiki engine cannot read it by previewing a resized thumbnail of it. This also includes empty (i.e., no content) file description pages for Commons files'
	},
	{
		label: 'F2: Unneeded file description page for a file on Commons',
		value: 'fpcfail',
		tooltip: 'An image, hosted on Commons, but with tags or information on its English Wikipedia description page that are no longer needed. (For example, a failed featured picture candidate.)',
		hideWhenMultiple: true
	},
	{
		label: 'F3: Improper license',
		value: 'noncom',
		tooltip: 'Files licensed as "for non-commercial use only", "non-derivative use" or "used with permission" that were uploaded on or after 2005-05-19, except where they have been shown to comply with the limited standards for the use of non-free content. This includes files licensed under a "Non-commercial Creative Commons License". Such files uploaded before 2005-05-19 may also be speedily deleted if they are not used in any articles'
	},
	{
		label: 'F4: Lack of licensing information',
		value: 'unksource',
		tooltip: 'Files in category "Files with unknown source", "Files with unknown copyright status", or "Files with no copyright tag" that have been tagged with a template that places them in the category for more than seven days, regardless of when uploaded. Note, users sometimes specify their source in the upload summary, so be sure to check the circumstances of the file.',
		hideWhenUser: true
	},
	{
		label: 'F5: Unused non-free copyrighted file',
		value: 'f5',
		tooltip: 'Files that are not under a free license or in the public domain that are not used in any article, whose only use is in a deleted article, and that are very unlikely to be used on any other article. Reasonable exceptions may be made for files uploaded for an upcoming article. For other unused non-free files, use the "Orphaned fair use" option in Twinkle\'s DI tab.',
		hideWhenUser: true
	},
	{
		label: 'F6: Missing fair-use rationale',
		value: 'norat',
		tooltip: 'Any file without a fair use rationale may be deleted seven days after it is uploaded.  Boilerplate fair use templates do not constitute a fair use rationale.  Files uploaded before 2006-05-04 should not be deleted immediately; instead, the uploader should be notified that a fair-use rationale is needed.  Files uploaded after 2006-05-04 can be tagged using the "No fair use rationale" option in Twinkle\'s DI module. Such files can be found in the dated subcategories of Category:Files with no fair use rationale.',
		hideWhenUser: true
	},
	{
		label: 'F7: Clearly invalid fair-use tag',
		value: 'badfairuse',  // same as below
		tooltip: 'This is only for files with a clearly invalid fair-use tag, such as a {{Non-free logo}} tag on a photograph of a mascot. For cases that require a waiting period (replaceable images or otherwise disputed rationales), use the options on Twinkle\'s DI tab.',
		subgroup: {
			name: 'badfairuse_rationale',
			type: 'input',
			label: 'Optional explanation: ',
			size: 60
		}
	},
	{
		label: 'F7: Fair-use media from a commercial image agency which is not the subject of sourced commentary',
		value: 'badfairuse',  // same as above
		tooltip: 'Non-free images or media from a commercial source (e.g., Associated Press, Getty), where the file itself is not the subject of sourced commentary, are considered an invalid claim of fair use and fail the strict requirements of WP:NFCC.',
		subgroup: {
			name: 'badfairuse_rationale',
			type: 'input',
			label: 'Optional explanation: ',
			size: 60
		},
		hideWhenMultiple: true
	},
	{
		label: 'F8: File available as an identical or higher-resolution copy on Wikimedia Commons',
		value: 'commons',
		tooltip: 'Provided the following conditions are met: 1: The file format of both images is the same. 2: The file\'s license and source status is beyond reasonable doubt, and the license is undoubtedly accepted at Commons. 3: All information on the file description page is present on the Commons file description page. That includes the complete upload history with links to the uploader\'s local user pages. 4: The file is not protected, and the file description page does not contain a request not to move it to Commons. 5: If the file is available on Commons under a different name than locally, all local references to the file must be updated to point to the title used at Commons. 6: For {{c-uploaded}} files: They may be speedily deleted as soon as they are off the Main Page',
		subgroup: {
			name: 'commons_filename',
			type: 'input',
			label: 'Filename on Commons: ',
			value: Morebits.pageNameNorm,
			tooltip: 'This can be left blank if the file has the same name on Commons as here. The "File:" prefix is optional.'
		},
		hideWhenMultiple: true
	},
	{
		label: 'F9: Unambiguous copyright infringement',
		value: 'imgcopyvio',
		tooltip: 'The file was copied from a website or other source that does not have a license compatible with Wikipedia, and the uploader neither claims fair use nor makes a credible assertion of permission of free use. Sources that do not have a license compatible with Wikipedia include stock photo libraries such as Getty Images or Corbis. Non-blatant copyright infringements should be discussed at Wikipedia:Files for deletion',
		subgroup: [
			{
				name: 'imgcopyvio_url',
				type: 'input',
				label: 'URL of the copyvio, including the "http://".  If the copyvio is of a non-internet source and you cannot provide a URL, you must use the deletion rationale box. ',
				size: 60
			},
			{
				name: 'imgcopyvio_rationale',
				type: 'input',
				label: 'Deletion rationale for non-internet copyvios: ',
				size: 60
			}
		]
	},
	{
		label: 'F10: Useless non-media file',
		value: 'badfiletype',
		tooltip: 'Files uploaded that are neither image, sound, nor video files (e.g. .doc, .pdf, or .xls files) which are not used in any article and have no foreseeable encyclopedic use'
	},
	{
		label: 'F11: No evidence of permission',
		value: 'nopermission',
		tooltip: 'If an uploader has specified a license and has named a third party as the source/copyright holder without providing evidence that this third party has in fact agreed, the item may be deleted seven days after notification of the uploader',
		hideWhenUser: true
	},
	{
		label: 'G8: File description page with no corresponding file',
		value: 'imagepage',
		tooltip: 'This is only for use when the file doesn\'t exist at all. Corrupt files, and local description pages for files on Commons, should use F2; implausible redirects should use R3; and broken Commons redirects should use R4.'
	}
];

Twinkle.speedy.articleList = [
	{
		label: 'A1: No context. Articles lacking sufficient context to identify the subject of the article.',
		value: 'nocontext',
		tooltip: 'Example: "He is a funny man with a red car. He makes people laugh." This applies only to very short articles. Context is different from content, treated in A3, below.'
	},
	{
		label: 'A2: Foreign language articles that exist on another Wikimedia project',
		value: 'foreign',
		tooltip: 'If the article in question does not exist on another project, the template {{notenglish}} should be used instead. All articles in a non-English language that do not meet this criteria (and do not meet any other criteria for speedy deletion) should be listed at Pages Needing Translation (PNT) for review and possible translation',
		subgroup: {
			name: 'foreign_source',
			type: 'input',
			label: 'Interwiki link to the article on the foreign-language wiki: ',
			tooltip: 'For example, fr:Bonjour'
		}
	},
	{
		label: 'A3: No content whatsoever',
		value: 'nocontent',
		tooltip: 'Any article consisting only of links elsewhere (including hyperlinks, category tags and "see also" sections), a rephrasing of the title, and/or attempts to correspond with the person or group named by its title. This does not include disambiguation pages'
	},
	{
		label: 'A5: Transwikied articles',
		value: 'transwiki',
		tooltip: 'Any article that has been discussed at Articles for Deletion (et al), where the outcome was to transwiki, and where the transwikification has been properly performed and the author information recorded. Alternately, any article that consists of only a dictionary definition, where the transwikification has been properly performed and the author information recorded',
		subgroup: {
			name: 'transwiki_location',
			type: 'input',
			label: 'Link to where the page has been transwikied: ',
			tooltip: 'For example, https://en.wiktionary.org/wiki/twinkle or [[wikt:twinkle]]'
		}
	},
	{
		label: 'A7: No indication of importance (people, groups, companies, web content, individual animals, or organized events)',
		value: 'a7',
		tooltip: 'An article about a real person, group of people, band, club, company, web content, individual animal, tour, or party that does not assert the importance or significance of its subject. If controversial, or if a previous AfD has resulted in the article being kept, the article should be nominated for AfD instead',
		hideWhenSingle: true
	},
	{
		label: 'A7: No indication of importance (person)',
		value: 'person',
		tooltip: 'An article about a real person that does not assert the importance or significance of its subject. If controversial, or if there has been a previous AfD that resulted in the article being kept, the article should be nominated for AfD instead',
		hideWhenMultiple: true
	},
	{
		label: 'A7: No indication of importance (musician(s) or band)',
		value: 'band',
		tooltip: 'Article about a band, singer, musician, or musical ensemble that does not assert the importance or significance of the subject',
		hideWhenMultiple: true
	},
	{
		label: 'A7: No indication of importance (club, society or group)',
		value: 'club',
		tooltip: 'Article about a club, society or group that does not assert the importance or significance of the subject',
		hideWhenMultiple: true
	},
	{
		label: 'A7: No indication of importance (company or organization)',
		value: 'corp',
		tooltip: 'Article about a company or organization that does not assert the importance or significance of the subject',
		hideWhenMultiple: true
	},
	{
		label: 'A7: No indication of importance (website or web content)',
		value: 'web',
		tooltip: 'Article about a web site, blog, online forum, webcomic, podcast, or similar web content that does not assert the importance or significance of its subject',
		hideWhenMultiple: true
	},
	{
		label: 'A7: No indication of importance (individual animal)',
		value: 'animal',
		tooltip: 'Article about an individual animal (e.g. pet) that does not assert the importance or significance of its subject',
		hideWhenMultiple: true
	},
	{
		label: 'A7: No indication of importance (organized event)',
		value: 'event',
		tooltip: 'Article about an organized event (tour, function, meeting, party, etc.) that does not assert the importance or significance of its subject',
		hideWhenMultiple: true
	},
	{
		label: 'A9: Unremarkable musical recording where artist\'s article doesn\'t exist',
		value: 'a9',
		tooltip: 'An article about a musical recording which does not indicate why its subject is important or significant, and where the artist\'s article has never existed or has been deleted'
	},
	{
		label: 'A10: Recently created article that duplicates an existing topic',
		value: 'a10',
		tooltip: 'A recently created article with no relevant page history that does not aim to expand upon, detail or improve information within any existing article(s) on the subject, and where the title is not a plausible redirect. This does not include content forks, split pages or any article that aims at expanding or detailing an existing one.',
		subgroup: {
			name: 'a10_article',
			type: 'input',
			label: 'Article that is duplicated: '
		}
	},
	{
		label: 'A11: Obviously made up by creator, and no claim of significance',
		value: 'madeup',
		tooltip: 'An article which plainly indicates that the subject was invented/coined/discovered by the article\'s creator or someone they know personally, and does not credibly indicate why its subject is important or significant'
	}
];

Twinkle.speedy.categoryList = [
	{
		label: 'C1: Empty categories',
		value: 'catempty',
		tooltip: 'Categories that have been unpopulated for at least seven days. This does not apply to categories being discussed at WP:CFD, disambiguation categories, and certain other exceptions. If the category isn\'t relatively new, it possibly contained articles earlier, and deeper investigation is needed'
	},
	{
		label: 'G8: Categories populated by a deleted or retargeted template',
		value: 'templatecat',
		tooltip: 'This is for situations where a category is effectively empty, because the template(s) that formerly placed pages in that category are now deleted. This excludes categories that are still in use.'
	},
	{
		label: 'G8: Redirects to non-existent targets',
		value: 'redirnone',
		tooltip: 'This excludes any page that is useful to the project, and in particular: deletion discussions that are not logged elsewhere, user and user talk pages, talk page archives, plausible redirects that can be changed to valid targets, and file pages or talk pages for files that exist on Wikimedia Commons.',
		hideWhenMultiple: true
	}
];

Twinkle.speedy.userList = [
	{
		label: 'U1: User request',
		value: 'userreq',
		tooltip: 'Personal subpages, upon request by their user. In some rare cases there may be administrative need to retain the page. Also, sometimes, main user pages may be deleted as well. See Wikipedia:User page for full instructions and guidelines',
		subgroup: mw.config.get('wgNamespaceNumber') === 3 && mw.config.get('wgTitle').indexOf('/') === -1 ? {
			name: 'userreq_rationale',
			type: 'input',
			label: 'A mandatory rationale to explain why this user talk page should be deleted: ',
			tooltip: 'User talk pages are deleted only in highly exceptional circumstances. See WP:DELTALK.',
			size: 60
		} : null,
		hideSubgroupWhenMultiple: true
	},
	{
		label: 'U2: Nonexistent user',
		value: 'nouser',
		tooltip: 'User pages of users that do not exist (Check Special:Listusers)'
	},
	{
		label: 'U3: Non-free galleries',
		value: 'gallery',
		tooltip: 'Galleries in the userspace which consist mostly of "fair use" or non-free files. Wikipedia\'s non-free content policy forbids users from displaying non-free files, even ones they have uploaded themselves, in userspace. It is acceptable to have free files, GFDL-files, Creative Commons and similar licenses along with public domain material, but not "fair use" files',
		hideWhenRedirect: true
	},
	{
		label: 'U5: Blatant WP:NOTWEBHOST violations',
		value: 'notwebhost',
		tooltip: 'Pages in userspace consisting of writings, information, discussions, and/or activities not closely related to Wikipedia\'s goals, where the owner has made few or no edits outside of userspace, with the exception of plausible drafts and pages adhering to WP:UPYES.',
		hideWhenRedirect: true
	},
	{
		label: 'G11: Promotional user page under a promotional user name',
		value: 'spamuser',
		tooltip: 'A promotional user page, with a username that promotes or implies affiliation with the thing being promoted. Note that simply having a page on a company or product in one\'s userspace does not qualify it for deletion. If a user page is spammy but the username is not, then consider tagging with regular G11 instead.',
		hideWhenMultiple: true,
		hideWhenRedirect: true
	},
	{
		label: 'G13: AfC draft submission or a blank draft, stale by over 6 months',
		value: 'afc',
		tooltip: 'Any rejected or unsubmitted AfC draft submission or a blank draft, that has not been edited in over 6 months (excluding bot edits).',
		hideWhenMultiple: true,
		hideWhenRedirect: true
	}
];

Twinkle.speedy.templateList = [
	{
		label: 'T3: Duplicate templates or hardcoded instances',
		value: 'duplicatetemplate',
		tooltip: 'Templates that are either substantial duplications of another template or hardcoded instances of another template where the same functionality could be provided by that other template',
		subgroup: {
			name: 'duplicatetemplate_2',
			type: 'input',
			label: 'Template this is redundant to: ',
			tooltip: 'The "Template:" prefix is not needed.'
		},
		hideWhenMultiple: true
	}
];

Twinkle.speedy.portalList = [
	{
		label: 'P1: Portal that would be subject to speedy deletion if it were an article',
		value: 'p1',
		tooltip: 'You must specify a single article criterion that applies in this case (A1, A3, A7, or A10).',
		subgroup: {
			name: 'p1_criterion',
			type: 'input',
			label: 'Article criterion that would apply: '
		}
	},
	{
		label: 'P2: Underpopulated portal (fewer than three non-stub articles)',
		value: 'emptyportal',
		tooltip: 'Any Portal based on a topic for which there is not a non-stub header article, and at least three non-stub articles detailing subject matter that would be appropriate to discuss under the title of that Portal'
	}
];

Twinkle.speedy.generalList = [
	{
		label: 'G1: Patent nonsense. Pages consisting purely of incoherent text or gibberish with no meaningful content or history.',
		value: 'nonsense',
		tooltip: 'This does not include poor writing, partisan screeds, obscene remarks, vandalism, fictional material, material not in English, poorly translated material, implausible theories, or hoaxes. In short, if you can understand it, G1 does not apply.',
		hideInNamespaces: [ 2 ] // Not applicable in userspace
	},
	{
		label: 'G2: Test page',
		value: 'test',
		tooltip: 'A page created to test editing or other Wikipedia functions. Pages in the User namespace are not included, nor are valid but unused or duplicate templates (although criterion T3 may apply).',
		hideInNamespaces: [ 2 ] // Not applicable in userspace
	},
	{
		label: 'G3: Pure vandalism',
		value: 'vandalism',
		tooltip: 'Plain pure vandalism (including redirects left behind from pagemove vandalism)'
	},
	{
		label: 'G3: Blatant hoax',
		value: 'hoax',
		tooltip: 'Blatant and obvious hoax, to the point of vandalism',
		hideWhenMultiple: true
	},
	{
		label: 'G4: Recreation of material deleted via a deletion discussion',
		value: 'repost',
		tooltip: 'A copy, by any title, of a page that was deleted via an XfD process or Deletion review, provided that the copy is substantially identical to the deleted version. This clause does not apply to content that has been "userfied", to content undeleted as a result of Deletion review, or if the prior deletions were proposed or speedy deletions, although in this last case, other speedy deletion criteria may still apply',
		subgroup: {
			name: 'repost_xfd',
			type: 'input',
			label: 'Page where the deletion discussion took place: ',
			tooltip: 'Must start with "Wikipedia:"',
			size: 60
		}
	},
	{
		label: 'G5: Created by a banned or blocked user',
		value: 'banned',
		tooltip: 'Pages created by banned or blocked users in violation of their ban or block, and which have no substantial edits by others',
		subgroup: {
			name: 'banned_user',
			type: 'input',
			label: 'Username of banned user (if available): ',
			tooltip: 'Should not start with "User:"'
		}
	},
	{
		label: 'G6: Move',
		value: 'move',
		tooltip: 'Making way for an uncontroversial move like reversing a redirect',
		subgroup: [
			{
				name: 'move_page',
				type: 'input',
				label: 'Page to be moved here: '
			},
			{
				name: 'move_reason',
				type: 'input',
				label: 'Reason: ',
				size: 60
			}
		],
		hideWhenMultiple: true
	},
	{
		label: 'G6: XfD',
		value: 'xfd',
		tooltip: 'A deletion discussion (at AfD, FfD, RfD, TfD, CfD, or MfD) was closed as "delete", but the page wasn\'t actually deleted.',
		subgroup: {
			name: 'xfd_fullvotepage',
			type: 'input',
			label: 'Page where the deletion discussion was held: ',
			tooltip: 'Must start with "Wikipedia:"',
			size: 40
		},
		hideWhenMultiple: true
	},
	{
		label: 'G6: Copy-and-paste page move',
		value: 'copypaste',
		tooltip: 'This only applies for a copy-and-paste page move of another page that needs to be temporarily deleted to make room for a clean page move.',
		subgroup: {
			name: 'copypaste_sourcepage',
			type: 'input',
			label: 'Original page that was copy-pasted here: '
		},
		hideWhenMultiple: true
	},
	{
		label: 'G6: Housekeeping and non-controversial cleanup',
		value: 'g6',
		tooltip: 'Other routine maintenance tasks',
		subgroup: {
			name: 'g6_rationale',
			type: 'input',
			label: 'Rationale: ',
			size: 60
		}
	},
	{
		label: 'G7: Author requests deletion, or author blanked',
		value: 'author',
		tooltip: 'Any page for which deletion is requested by the original author in good faith, provided the page\'s only substantial content was added by its author. If the author blanks the page, this can also be taken as a deletion request.',
		subgroup: {
			name: 'author_rationale',
			type: 'input',
			label: 'Optional explanation: ',
			tooltip: 'Perhaps linking to where the author requested this deletion.',
			size: 60
		},
		hideSubgroupWhenSysop: true
	},
	{
		label: 'G8: Pages dependent on a non-existent or deleted page',
		value: 'g8',
		tooltip: 'such as talk pages with no corresponding subject page; subpages with no parent page; file pages without a corresponding file; redirects to non-existent targets; or categories populated by deleted or retargeted templates. This excludes any page that is useful to the project, and in particular: deletion discussions that are not logged elsewhere, user and user talk pages, talk page archives, plausible redirects that can be changed to valid targets, and file pages or talk pages for files that exist on Wikimedia Commons.',
		subgroup: {
			name: 'g8_rationale',
			type: 'input',
			label: 'Optional explanation: ',
			size: 60
		},
		hideSubgroupWhenSysop: true
	},
	{
		label: 'G8: Subpages with no parent page',
		value: 'subpage',
		tooltip: 'This excludes any page that is useful to the project, and in particular: deletion discussions that are not logged elsewhere, user and user talk pages, talk page archives, plausible redirects that can be changed to valid targets, and file pages or talk pages for files that exist on Wikimedia Commons.',
		hideWhenMultiple: true,
		hideInNamespaces: [ 0, 6, 8 ]  // hide in main, file, and mediawiki-spaces
	},
	{
		label: 'G10: Attack page',
		value: 'attack',
		tooltip: 'Pages that serve no purpose but to disparage or threaten their subject or some other entity (e.g., "John Q. Doe is an imbecile"). This includes a biography of a living person that is negative in tone and unsourced, where there is no NPOV version in the history to revert to. Administrators deleting such pages should not quote the content of the page in the deletion summary!'
	},
	{
		label: 'G10: Wholly negative, unsourced BLP',
		value: 'negublp',
		tooltip: 'A biography of a living person that is entirely negative in tone and unsourced, where there is no neutral version in the history to revert to.',
		hideWhenMultiple: true
	},
	{
		label: 'G11: Unambiguous advertising or promotion',
		value: 'spam',
		tooltip: 'Pages which exclusively promote a company, product, group, service, or person and which would need to be fundamentally rewritten in order to become encyclopedic. Note that an article about a company or a product which describes its subject from a neutral point of view does not qualify for this criterion; an article that is blatant advertising should have inappropriate content as well'
	},
	{
		label: 'G12: Unambiguous copyright infringement',
		value: 'copyvio',
		tooltip: 'Either: (1) Material was copied from another website that does not have a license compatible with Wikipedia, or is photography from a stock photo seller (such as Getty Images or Corbis) or other commercial content provider; (2) There is no non-infringing content in the page history worth saving; or (3) The infringement was introduced at once by a single person rather than created organically on wiki and then copied by another website such as one of the many Wikipedia mirrors',
		subgroup: [
			{
				name: 'copyvio_url',
				type: 'input',
				label: 'URL (if available): ',
				tooltip: 'If the material was copied from an online source, put the URL here, including the "http://" or "https://" protocol.',
				size: 60
			},
			{
				name: 'copyvio_url2',
				type: 'input',
				label: 'Additional URL: ',
				tooltip: 'Optional. Should begin with "http://" or "https://"',
				size: 60
			},
			{
				name: 'copyvio_url3',
				type: 'input',
				label: 'Additional URL: ',
				tooltip: 'Optional. Should begin with "http://" or "https://"',
				size: 60
			}
		]
	},
	{
		label: 'G13: Page in draft namespace or userspace AfC submission, stale by over 6 months',
		value: 'afc',
		tooltip: 'Any rejected or unsubmitted AfC submission in userspace or any non-redirect page in draft namespace, that has not been edited for more than 6 months. Blank drafts in either namespace are also included.',
		hideWhenRedirect: true,
		showInNamespaces: [2, 118]  // user, draft namespaces only
	},
	{
		label: 'G14: Unnecessary disambiguation page',
		value: 'disambig',
		tooltip: 'This only applies for orphaned disambiguation pages which either: (1) disambiguate only one existing Wikipedia page and whose title ends in "(disambiguation)" (i.e., there is a primary topic); or (2) disambiguate no (zero) existing Wikipedia pages, regardless of its title.  It also applies to orphan "Foo (disambiguation)" redirects that target pages that are not disambiguation or similar disambiguation-like pages (such as set index articles or lists)'
	}
];

Twinkle.speedy.redirectList = [
	{
		label: 'R2: Redirect from mainspace to any other namespace except the Category:, Template:, Wikipedia:, Help: and Portal: namespaces',
		value: 'rediruser',
		tooltip: 'This does not include the pseudo-namespace shortcuts. If this was the result of a page move, consider waiting a day or two before deleting the redirect',
		showInNamespaces: [ 0 ]
	},
	{
		label: 'R3: Recently created redirect from an implausible typo or misnomer',
		value: 'redirtypo',
		tooltip: 'However, redirects from common misspellings or misnomers are generally useful, as are redirects in other languages'
	},
	{
		label: 'R4: File namespace redirect with a name that matches a Commons page',
		value: 'redircom',
		tooltip: 'The redirect should have no incoming links (unless the links are cleary intended for the file or redirect at Commons).',
		showInNamespaces: [ 6 ]
	},
	{
		label: 'G6: Redirect to malplaced disambiguation page',
		value: 'movedab',
		tooltip: 'This only applies for redirects to disambiguation pages ending in (disambiguation) where a primary topic does not exist.',
		hideWhenMultiple: true
	},
	{
		label: 'G8: Redirects to non-existent targets',
		value: 'redirnone',
		tooltip: 'This excludes any page that is useful to the project, and in particular: deletion discussions that are not logged elsewhere, user and user talk pages, talk page archives, plausible redirects that can be changed to valid targets, and file pages or talk pages for files that exist on Wikimedia Commons.',
		hideWhenMultiple: true
	}
];

Twinkle.speedy.normalizeHash = {
	'reason': 'db',
	'nonsense': 'g1',
	'test': 'g2',
	'vandalism': 'g3',
	'hoax': 'g3',
	'repost': 'g4',
	'banned': 'g5',
	'move': 'g6',
	'xfd': 'g6',
	'movedab': 'g6',
	'copypaste': 'g6',
	'g6': 'g6',
	'author': 'g7',
	'g8': 'g8',
	'talk': 'g8',
	'subpage': 'g8',
	'redirnone': 'g8',
	'templatecat': 'g8',
	'imagepage': 'g8',
	'attack': 'g10',
	'negublp': 'g10',
	'spam': 'g11',
	'spamuser': 'g11',
	'copyvio': 'g12',
	'afc': 'g13',
	'disambig': 'g14',
	'nocontext': 'a1',
	'foreign': 'a2',
	'nocontent': 'a3',
	'transwiki': 'a5',
	'a7': 'a7',
	'person': 'a7',
	'corp': 'a7',
	'web': 'a7',
	'band': 'a7',
	'club': 'a7',
	'animal': 'a7',
	'event': 'a7',
	'a9': 'a9',
	'a10': 'a10',
	'madeup': 'a11',
	'rediruser': 'r2',
	'redirtypo': 'r3',
	'redircom': 'r4',
	'redundantimage': 'f1',
	'noimage': 'f2',
	'fpcfail': 'f2',
	'noncom': 'f3',
	'unksource': 'f4',
	'unfree': 'f5',
	'f5': 'f5',
	'norat': 'f6',
	'badfairuse': 'f7',
	'commons': 'f8',
	'imgcopyvio': 'f9',
	'badfiletype': 'f10',
	'nopermission': 'f11',
	'catempty': 'c1',
	'userreq': 'u1',
	'nouser': 'u2',
	'gallery': 'u3',
	'notwebhost': 'u5',
	'duplicatetemplate': 't3',
	'p1': 'p1',
	'emptyportal': 'p2'
};

Twinkle.speedy.callbacks = {
	getTemplateCodeAndParams: function(params) {
		var code, parameters, i;
		if (params.normalizeds.length > 1) {
			code = '{{db-multiple';
			params.utparams = {};
			$.each(params.normalizeds, function(index, norm) {
				code += '|' + norm.toUpperCase();
				parameters = params.templateParams[index] || [];
				for (var i in parameters) {
					if (typeof parameters[i] === 'string' && !parseInt(i, 10)) {  // skip numeric parameters - {{db-multiple}} doesn't understand them
						code += '|' + i + '=' + parameters[i];
					}
				}
				$.extend(params.utparams, Twinkle.speedy.getUserTalkParameters(norm, parameters));
			});
			code += '}}';
		} else {
			parameters = params.templateParams[0] || [];
			code = '{{db-' + params.values[0];
			for (i in parameters) {
				if (typeof parameters[i] === 'string') {
					code += '|' + i + '=' + parameters[i];
				}
			}
			if (params.usertalk) {
				code += '|help=off';
			}
			code += '}}';
			params.utparams = Twinkle.speedy.getUserTalkParameters(params.normalizeds[0], parameters);
		}

		return [code, params.utparams];
	},

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

		var statusIndicator = new Morebits.status('Building deletion summary');
		var api = new Morebits.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();
	},

	noteToCreator: function(pageobj) {
		var params = pageobj.getCallbackParameters();
		var initialContrib = pageobj.getCreator();

		// disallow notifying yourself
		if (initialContrib === mw.config.get('wgUserName')) {
			Morebits.status.warn('You (' + initialContrib + ') created this page; skipping user notification');
			initialContrib = null;

		// don't notify users when their user talk page is nominated/deleted
		} else if (initialContrib === mw.config.get('wgTitle') && mw.config.get('wgNamespaceNumber') === 3) {
			Morebits.status.warn('Notifying initial contributor: this user created their own user talk page; skipping notification');
			initialContrib = null;

		// quick hack to prevent excessive unwanted notifications, per request. Should actually be configurable on recipient page...
		} else if ((initialContrib === 'Cyberbot I' || initialContrib === 'SoxBot') && params.normalizeds[0] === 'f2') {
			Morebits.status.warn('Notifying initial contributor: page created procedurally by bot; skipping notification');
			initialContrib = null;

		// Check for already existing tags
		} else if (Twinkle.speedy.hasCSD && params.warnUser && !confirm('The page is has a deletion-related tag, and thus the creator has likely been notified.  Do you want to notify them for this deletion as well?')) {
			Morebits.status.info('Notifying initial contributor', 'canceled by user; skipping notification.');
			initialContrib = null;
		}

		if (initialContrib) {
			var usertalkpage = new Morebits.wiki.page('User talk:' + initialContrib, 'Notifying initial contributor (' + initialContrib + ')'),
				notifytext, i, editsummary;

			// special cases: "db" and "db-multiple"
			if (params.normalizeds.length > 1) {
				notifytext = '\n{{subst:db-' + (params.warnUser ? 'deleted' : 'notice') + '-multiple|1=' + Morebits.pageNameNorm;
				var count = 2;
				$.each(params.normalizeds, function(index, norm) {
					notifytext += '|' + count++ + '=' + norm.toUpperCase();
				});
			} else if (params.normalizeds[0] === 'db') {
				notifytext = '\n{{subst:db-reason-' + (params.warnUser ? 'deleted' : 'notice') + '|1=' + Morebits.pageNameNorm;
			} else {
				notifytext = '\n{{subst:db-csd-' + (params.warnUser ? 'deleted' : 'notice') + '-custom|1=';
				if (params.values[0] === 'copypaste') {
					notifytext += params.templateParams[0].sourcepage;
				} else {
					notifytext += Morebits.pageNameNorm;
				}
				notifytext += '|2=' + params.values[0];
			}

			for (i in params.utparams) {
				if (typeof params.utparams[i] === 'string') {
					notifytext += '|' + i + '=' + params.utparams[i];
				}
			}
			notifytext += (params.welcomeuser ? '' : '|nowelcome=yes') + '}} ~~~~';

			editsummary = 'Notification: speedy deletion' + (params.warnUser ? '' : ' nomination');
			if (params.normalizeds.indexOf('g10') === -1) {  // no article name in summary for G10 taggings
				editsummary += ' of [[:' + Morebits.pageNameNorm + ']].';
			} else {
				editsummary += ' of an attack page.';
			}

			usertalkpage.setAppendText(notifytext);
			usertalkpage.setEditSummary(editsummary + Twinkle.getPref('summaryAd'));
			usertalkpage.setCreateOption('recreate');
			usertalkpage.setFollowRedirect(true);
			usertalkpage.append(function onNotifySuccess() {
				// add this nomination to the user's userspace log, if the user has enabled it
				if (params.lognomination) {
					Twinkle.speedy.callbacks.user.addToLog(params, initialContrib);
				}
			}, function onNotifyError() {
				// if user could not be notified, log nomination without mentioning that notification was sent
				if (params.lognomination) {
					Twinkle.speedy.callbacks.user.addToLog(params, null);
				}
			});
		} else if (params.lognomination) {
			// log nomination even if the user notification wasn't sent
			Twinkle.speedy.callbacks.user.addToLog(params, null);
		}
	},

	sysop: {
		main: function(params) {
			var reason;
			if (!params.normalizeds.length && params.normalizeds[0] === 'db') {
				reason = prompt('Enter the deletion summary to use, which will be entered into the deletion log:', '');
				Twinkle.speedy.callbacks.sysop.deletePage(reason, params);
			} else {
				var code = Twinkle.speedy.callbacks.getTemplateCodeAndParams(params)[0];
				Twinkle.speedy.callbacks.parseWikitext(code, function(reason) {
					if (params.promptForSummary) {
						reason = prompt('Enter the deletion summary to use, or press OK to accept the automatically generated one.', reason);
					}
					Twinkle.speedy.callbacks.sysop.deletePage(reason, params);
				});
			}
		},
		deletePage: function(reason, params) {
			var thispage = new Morebits.wiki.page(mw.config.get('wgPageName'), 'Deleting page');

			if (reason === null) {
				return Morebits.status.error('Asking for reason', 'User cancelled');
			} else if (!reason || !reason.replace(/^\s*/, '').replace(/\s*$/, '')) {
				return Morebits.status.error('Asking for reason', "you didn't give one.  I don't know... what with admins and their apathetic antics... I give up...");
			}

			var deleteMain = function(callback) {
				thispage.setEditSummary(reason + Twinkle.getPref('deletionSummaryAd'));
				thispage.deletePage(function() {
					thispage.getStatusElement().info('done');
					typeof callback === 'function' && callback();
					Twinkle.speedy.callbacks.sysop.deleteTalk(params);
				});
			};

			// look up initial contributor. If prompting user for deletion reason, just display a link.
			// Otherwise open the talk page directly
			if (params.warnUser) {
				thispage.setCallbackParameters(params);
				thispage.lookupCreation(function(pageobj) {
					deleteMain(function() {
						Twinkle.speedy.callbacks.noteToCreator(pageobj);
					});
				});
			} else {
				deleteMain();
			}
		},
		deleteTalk: function(params) {
			// delete talk page
			if (params.deleteTalkPage &&
					params.normalized !== 'f8' &&
					document.getElementById('ca-talk').className !== 'new') {
				var talkpage = new Morebits.wiki.page(mw.config.get('wgFormattedNamespaces')[mw.config.get('wgNamespaceNumber') + 1] + ':' + mw.config.get('wgTitle'), 'Deleting talk page');
				talkpage.setEditSummary('[[WP:CSD#G8|G8]]: Talk page of deleted page "' + Morebits.pageNameNorm + '"' + Twinkle.getPref('deletionSummaryAd'));
				talkpage.deletePage();
				// this is ugly, but because of the architecture of wiki.api, it is needed
				// (otherwise success/failure messages for the previous action would be suppressed)
				window.setTimeout(function() {
					Twinkle.speedy.callbacks.sysop.deleteRedirects(params);
				}, 1800);
			} else {
				Twinkle.speedy.callbacks.sysop.deleteRedirects(params);
			}
		},
		deleteRedirects: function(params) {
			// delete redirects
			if (params.deleteRedirects) {
				var query = {
					'action': 'query',
					'titles': mw.config.get('wgPageName'),
					'prop': 'redirects',
					'rdlimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
				};
				var wikipedia_api = new Morebits.wiki.api('getting list of redirects...', query, Twinkle.speedy.callbacks.sysop.deleteRedirectsMain,
					new Morebits.status('Deleting redirects'));
				wikipedia_api.params = params;
				wikipedia_api.post();
			}

			// promote Unlink tool
			var $link, $bigtext;
			if (mw.config.get('wgNamespaceNumber') === 6 && params.normalized !== 'f8') {
				$link = $('<a/>', {
					'href': '#',
					'text': 'click here to go to the Unlink tool',
					'css': { 'fontSize': '130%', 'fontWeight': 'bold' },
					'click': function() {
						Morebits.wiki.actionCompleted.redirect = null;
						Twinkle.speedy.dialog.close();
						Twinkle.unlink.callback('Removing usages of and/or links to deleted file ' + Morebits.pageNameNorm);
					}
				});
				$bigtext = $('<span/>', {
					'text': 'To orphan backlinks and remove instances of file usage',
					'css': { 'fontSize': '130%', 'fontWeight': 'bold' }
				});
				Morebits.status.info($bigtext[0], $link[0]);
			} else if (params.normalized !== 'f8') {
				$link = $('<a/>', {
					'href': '#',
					'text': 'click here to go to the Unlink tool',
					'css': { 'fontSize': '130%', 'fontWeight': 'bold' },
					'click': function() {
						Morebits.wiki.actionCompleted.redirect = null;
						Twinkle.speedy.dialog.close();
						Twinkle.unlink.callback('Removing links to deleted page ' + Morebits.pageNameNorm);
					}
				});
				$bigtext = $('<span/>', {
					'text': 'To orphan backlinks',
					'css': { 'fontSize': '130%', 'fontWeight': 'bold' }
				});
				Morebits.status.info($bigtext[0], $link[0]);
			}
		},
		deleteRedirectsMain: function(apiobj) {
			var xmlDoc = apiobj.getXML();
			var $snapshot = $(xmlDoc).find('redirects rd');
			var total = $snapshot.length;
			var statusIndicator = apiobj.statelem;

			if (!total) {
				statusIndicator.status('no redirects found');
				return;
			}

			statusIndicator.status('0%');

			var current = 0;
			var onsuccess = function(apiobjInner) {
				var now = parseInt(100 * ++current / total, 10) + '%';
				statusIndicator.update(now);
				apiobjInner.statelem.unlink();
				if (current >= total) {
					statusIndicator.info(now + ' (completed)');
					Morebits.wiki.removeCheckpoint();
				}
			};

			Morebits.wiki.addCheckpoint();

			$snapshot.each(function(key, value) {
				var title = $(value).attr('title');
				var page = new Morebits.wiki.page(title, 'Deleting redirect "' + title + '"');
				page.setEditSummary('[[WP:CSD#G8|G8]]: Redirect to deleted page "' + Morebits.pageNameNorm + '"' + Twinkle.getPref('deletionSummaryAd'));
				page.deletePage(onsuccess);
			});
		}
	},

	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();

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

			// check for existing deletion tags
			var tag = /(?:\{\{\s*(db|delete|db-.*?|speedy deletion-.*?)(?:\s*\||\s*\}\}))/.exec(text);
			// This won't make use of the db-multiple template but it probably should
			if (tag && !confirm('The page already has the CSD-related template {{' + tag[1] + '}} on it.  Do you want to add another CSD template?')) {
				return;
			}

			var xfd = /\{\{((?:article for deletion|proposed deletion|prod blp|template for discussion)\/dated|[cfm]fd\b)/i.exec(text) || /#invoke:(RfD)/.exec(text);
			if (xfd && !confirm('The deletion-related template {{' + xfd[1] + '}} was found on the page. Do you still want to add a CSD template?')) {
				return;
			}

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

			// curate/patrol the page
			if (Twinkle.getPref('markSpeedyPagesAsPatrolled')) {
				pageobj.triage();
			}

			// Wrap SD template in noinclude tags if we are in template space.
			// Won't work with userboxes in userspace, or any other transcluded page outside template space
			if (mw.config.get('wgNamespaceNumber') === 10) {  // Template:
				code = '<noinclude>' + code + '</noinclude>';
			}

			// Remove tags that become superfluous with this action
			text = text.replace(/\{\{\s*([Uu]serspace draft)\s*(\|(?:\{\{[^{}]*\}\}|[^{}])*)?\}\}\s*/g, '');
			if (mw.config.get('wgNamespaceNumber') === 6) {
				// remove "move to Commons" tag - deletion-tagged files cannot be moved to Commons
				text = text.replace(/\{\{(mtc|(copy |move )?to ?commons|move to wikimedia commons|copy to wikimedia commons)[^}]*\}\}/gi, '');
			}

			if (params.requestsalt) {
				if (params.normalizeds.indexOf('g10') === -1) {
					code = code + '\n{{salt}}';
				} else {
					code = '{{salt}}\n' + code;
				}
			}

			// Generate edit summary for edit
			var editsummary;
			if (params.normalizeds.length > 1) {
				editsummary = 'Requesting speedy deletion (';
				$.each(params.normalizeds, function(index, norm) {
					editsummary += '[[WP:CSD#' + norm.toUpperCase() + '|CSD ' + norm.toUpperCase() + ']], ';
				});
				editsummary = editsummary.substr(0, editsummary.length - 2); // remove trailing comma
				editsummary += ').';
			} else if (params.normalizeds[0] === 'db') {
				editsummary = 'Requesting [[WP:CSD|speedy deletion]] with rationale "' + params.templateParams[0]['1'] + '".';
			} else {
				editsummary = 'Requesting speedy deletion ([[WP:CSD#' + params.normalizeds[0].toUpperCase() + '|CSD ' + params.normalizeds[0].toUpperCase() + ']]).';
			}

			// Set the correct value for |ts= parameter in {{db-g13}}
			if (params.normalizeds.indexOf('g13') !== -1) {
				code = code.replace('$TIMESTAMP', pageobj.getLastEditTime());
			}

			pageobj.setPageText(code + (params.normalizeds.indexOf('g10') !== -1 ? '' : '\n' + text)); // cause attack pages to be blanked
			pageobj.setEditSummary(editsummary + Twinkle.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 Morebits.wiki.api('Adding Module to watchlist', watch_query).post();
				}
			}
			pageobj.save(Twinkle.speedy.callbacks.user.tagComplete);
		},

		tagComplete: function(pageobj) {
			var params = pageobj.getCallbackParameters();

			// Notification to first contributor, will also log nomination to the user's userspace log
			if (params.usertalk) {
				var thispage = new Morebits.wiki.page(Morebits.pageNameNorm);
				thispage.setCallbackParameters(params);
				thispage.lookupCreation(Twinkle.speedy.callbacks.noteToCreator);
			// or, if not notifying, add this nomination to the user's userspace log without the initial contributor's name
			} else if (params.lognomination) {
				Twinkle.speedy.callbacks.user.addToLog(params, null);
			}
		},

		// note: this code is also invoked from twinkleimage
		// the params used are:
		//   for CSD: params.values, params.normalizeds  (note: normalizeds is an array)
		//   for DI: params.fromDI = true, params.templatename, params.normalized  (note: normalized is a string)
		addToLog: function(params, initialContrib) {
			var usl = new Morebits.userspaceLogger(Twinkle.getPref('speedyLogPageName'));
			usl.initialText =
				"This is a log of all [[WP:CSD|speedy deletion]] nominations made by this user using [[WP:TW|Twinkle]]'s CSD module.\n\n" +
				'If you no longer wish to keep this log, you can turn it off using the [[Wikipedia:Twinkle/Preferences|preferences panel]], and ' +
				'nominate this page for speedy deletion under [[WP:CSD#U1|CSD U1]].' +
				(Morebits.userIsSysop ? '\n\nThis log does not track outright speedy deletions made using Twinkle.' : '');

			var formatParamLog = function(normalize, csdparam, input) {
				if ((normalize === 'G4' && csdparam === 'xfd') ||
					(normalize === 'G6' && csdparam === 'page') ||
					(normalize === 'G6' && csdparam === 'fullvotepage') ||
					(normalize === 'G6' && csdparam === 'sourcepage') ||
					(normalize === 'A2' && csdparam === 'source') ||
					(normalize === 'A10' && csdparam === 'article') ||
					(normalize === 'F1' && csdparam === 'filename') ||
					(normalize === 'F5' && csdparam === 'replacement')) {
					input = '[[:' + input + ']]';
				} else if (normalize === 'G5' && csdparam === 'user') {
					input = '[[:User:' + input + ']]';
				} else if (normalize === 'G12' && csdparam.lastIndexOf('url', 0) === 0 && input.lastIndexOf('http', 0) === 0) {
					input = '[' + input + ' ' + input + ']';
				} else if (normalize === 'T3' && csdparam === 'template') {
					input = '[[:Template:' + input + ']]';
				} else if (normalize === 'F8' && csdparam === 'filename') {
					input = '[[commons:' + input + ']]';
				} else if (normalize === 'P1' && csdparam === 'criterion') {
					input = '[[WP:CSD#' + input + ']]';
				}
				return ' {' + normalize + ' ' + csdparam + ': ' + input + '}';
			};

			var extraInfo = '';

			// If a logged file is deleted but exists on commons, the wikilink will be blue, so provide a link to the log
			var fileLogLink = mw.config.get('wgNamespaceNumber') === 6 ? ' ([{{fullurl:Special:Log|page=' + mw.util.wikiUrlencode(mw.config.get('wgPageName')) + '}} log])' : '';

			var editsummary = 'Logging speedy deletion nomination';
			var appendText = '# [[:' + Morebits.pageNameNorm;

			if (params.fromDI) {
				appendText += ']]' + fileLogLink + ': DI [[WP:CSD#' + params.normalized.toUpperCase() + '|CSD ' + params.normalized.toUpperCase() + ']] ({{tl|di-' + params.templatename + '}})';
				// The params data structure when coming from DI is quite different,
				// so this hardcodes the only interesting items worth logging
				['reason', 'replacement', 'source'].forEach(function(item) {
					if (params[item]) {
						extraInfo += formatParamLog(params.normalized.toUpperCase(), item, params[item]);
						return false;
					}
				});
				editsummary += ' of [[:' + Morebits.pageNameNorm + ']].';
			} else {
				if (params.normalizeds.indexOf('g10') === -1) {  // no article name in log for G10 taggings
					appendText += ']]' + fileLogLink + ': ';
					editsummary += ' of [[:' + Morebits.pageNameNorm + ']].';
				} else {
					appendText += '|This]] attack page' + fileLogLink + ': ';
					editsummary += ' of an attack page.';
				}
				if (params.normalizeds.length > 1) {
					appendText += 'multiple criteria (';
					$.each(params.normalizeds, function(index, norm) {
						appendText += '[[WP:CSD#' + norm.toUpperCase() + '|' + norm.toUpperCase() + ']], ';
					});
					appendText = appendText.substr(0, appendText.length - 2);  // remove trailing comma
					appendText += ')';
				} else if (params.normalizeds[0] === 'db') {
					appendText += '{{tl|db-reason}}';
				} else {
					appendText += '[[WP:CSD#' + params.normalizeds[0].toUpperCase() + '|CSD ' + params.normalizeds[0].toUpperCase() + ']] ({{tl|db-' + params.values[0] + '}})';
				}

				// If params is "empty" it will still be full of empty arrays, but ask anyway
				if (params.templateParams) {
					// Treat custom rationale individually
					if (params.normalizeds[0] && params.normalizeds[0] === 'db') {
						extraInfo += formatParamLog('Custom', 'rationale', params.templateParams[0]['1']);
					} else {
						params.templateParams.forEach(function(item, index) {
							var keys = Object.keys(item);
							if (keys[0] !== undefined && keys[0].length > 0) {
								// Second loop required since some items (G12, F9) may have multiple keys
								keys.forEach(function(key, keyIndex) {
									if (keys[keyIndex] === 'blanked' || keys[keyIndex] === 'ts') {
										return true; // Not worth logging
									}
									extraInfo += formatParamLog(params.normalizeds[index].toUpperCase(), keys[keyIndex], item[key]);
								});
							}
						});
					}
				}
			}

			if (params.requestsalt) {
				appendText += '; requested creation protection ([[WP:SALT|salting]])';
			}
			if (extraInfo) {
				appendText += '; additional information:' + extraInfo;
			}
			if (initialContrib) {
				appendText += '; notified {{user|1=' + initialContrib + '}}';
			}
			appendText += ' ~~~~~\n';

			usl.log(appendText, editsummary + Twinkle.getPref('summaryAd'));

		}
	}
};

// validate subgroups in the form passed into the speedy deletion tag
Twinkle.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;

			case 'userreq':  // U1
				if (form['csd.userreq_rationale']) {
					var u1rationale = form['csd.userreq_rationale'].value;
					if (mw.config.get('wgNamespaceNumber') === 3 && !(/\//).test(mw.config.get('wgTitle')) &&
							(!u1rationale || !u1rationale.trim())) {
						alert('CSD U1:  Please specify a rationale when nominating user talk pages.');
						parameters = null;
						return false;
					}
					currentParams.rationale = u1rationale;
				}
				break;

			case 'repost':  // G4
				if (form['csd.repost_xfd']) {
					var deldisc = form['csd.repost_xfd'].value;
					if (deldisc) {
						if (!/^(?:wp|wikipedia):/i.test(deldisc)) {
							alert('CSD G4:  The deletion discussion page name, if provided, must start with "Wikipedia:".');
							parameters = null;
							return false;
						}
						currentParams.xfd = deldisc;
					}
				}
				break;

			case 'banned':  // G5
				if (form['csd.banned_user'] && form['csd.banned_user'].value) {
					currentParams.user = form['csd.banned_user'].value.replace(/^\s*User:/i, '');
				}
				break;

			case 'move':  // G6
				if (form['csd.move_page'] && form['csd.move_reason']) {
					var movepage = form['csd.move_page'].value,
						movereason = form['csd.move_reason'].value;
					if (!movepage || !movepage.trim()) {
						alert('CSD G6 (move):  Please specify the page to be moved here.');
						parameters = null;
						return false;
					}
					if (!movereason || !movereason.trim()) {
						alert('CSD G6 (move):  Please specify the reason for the move.');
						parameters = null;
						return false;
					}
					currentParams.page = movepage;
					currentParams.reason = movereason;
				}
				break;

			case 'xfd':  // G6
				if (form['csd.xfd_fullvotepage']) {
					var xfd = form['csd.xfd_fullvotepage'].value;
					if (xfd) {
						if (!/^(?:wp|wikipedia):/i.test(xfd)) {
							alert('CSD G6 (XFD):  The deletion discussion page name, if provided, must start with "Wikipedia:".');
							parameters = null;
							return false;
						}
						currentParams.fullvotepage = xfd;
					}
				}
				break;

			case 'copypaste':  // G6
				if (form['csd.copypaste_sourcepage']) {
					var copypaste = form['csd.copypaste_sourcepage'].value;
					if (!copypaste || !copypaste.trim()) {
						alert('CSD G6 (copypaste):  Please specify the source page name.');
						parameters = null;
						return false;
					}
					currentParams.sourcepage = copypaste;
				}
				break;

			case 'g6':  // G6
				if (form['csd.g6_rationale'] && form['csd.g6_rationale'].value) {
					currentParams.rationale = form['csd.g6_rationale'].value;
				}
				break;

			case 'author':  // G7
				if (form['csd.author_rationale'] && form['csd.author_rationale'].value) {
					currentParams.rationale = form['csd.author_rationale'].value;
				}
				break;

			case 'g8':  // G8
				if (form['csd.g8_rationale'] && form['csd.g8_rationale'].value) {
					currentParams.rationale = form['csd.g8_rationale'].value;
				}
				break;

			case 'attack':  // G10
				currentParams.blanked = 'yes';
				// it is actually blanked elsewhere in code, but setting the flag here
				break;

			case 'copyvio':  // G12
				if (form['csd.copyvio_url'] && form['csd.copyvio_url'].value) {
					currentParams.url = form['csd.copyvio_url'].value;
				}
				if (form['csd.copyvio_url2'] && form['csd.copyvio_url2'].value) {
					currentParams.url2 = form['csd.copyvio_url2'].value;
				}
				if (form['csd.copyvio_url3'] && form['csd.copyvio_url3'].value) {
					currentParams.url3 = form['csd.copyvio_url3'].value;
				}
				break;

			case 'afc':  // G13
				currentParams.ts = '$TIMESTAMP'; // to be replaced by the last revision timestamp when page is saved
				break;

			case 'redundantimage':  // F1
				if (form['csd.redundantimage_filename']) {
					var redimage = form['csd.redundantimage_filename'].value;
					if (!redimage || !redimage.trim()) {
						alert('CSD F1:  Please specify the filename of the other file.');
						parameters = null;
						return false;
					}
					currentParams.filename = /^\s*(Image|File):/i.test(redimage) ? redimage : 'File:' + redimage;
				}
				break;

			case 'badfairuse':  // F7
				if (form['csd.badfairuse_rationale'] && form['csd.badfairuse_rationale'].value) {
					currentParams.rationale = form['csd.badfairuse_rationale'].value;
				}
				break;

			case 'commons':  // F8
				if (form['csd.commons_filename']) {
					var filename = form['csd.commons_filename'].value;
					if (filename && filename.trim() && filename !== Morebits.pageNameNorm) {
						currentParams.filename = /^\s*(Image|File):/i.test(filename) ? filename : 'File:' + filename;
					}
				}
				break;

			case 'imgcopyvio':  // F9
				if (form['csd.imgcopyvio_url'] && form['csd.imgcopyvio_rationale']) {
					var f9url = form['csd.imgcopyvio_url'].value;
					var f9rationale = form['csd.imgcopyvio_rationale'].value;
					if ((!f9url || !f9url.trim()) && (!f9rationale || !f9rationale.trim())) {
						alert('CSD F9: You must enter a url or reason (or both) when nominating a file under F9.');
						parameters = null;
						return false;
					}
					if (form['csd.imgcopyvio_url'].value) {
						currentParams.url = f9url;
					}
					if (form['csd.imgcopyvio_rationale'].value) {
						currentParams.rationale = f9rationale;
					}
				}
				break;

			case 'foreign':  // A2
				if (form['csd.foreign_source']) {
					var foreignlink = form['csd.foreign_source'].value;
					if (!foreignlink || !foreignlink.trim()) {
						alert('CSD A2:  Please specify an interwiki link to the article of which this is a copy.');
						parameters = null;
						return false;
					}
					currentParams.source = foreignlink;
				}
				break;

			case 'transwiki':  // A5
				if (form['csd.transwiki_location'] && form['csd.transwiki_location'].value) {
					currentParams.location = form['csd.transwiki_location'].value;
				}
				break;

			case 'a10':  // A10
				if (form['csd.a10_article']) {
					var duptitle = form['csd.a10_article'].value;
					if (!duptitle || !duptitle.trim()) {
						alert('CSD A10:  Please specify the name of the article which is duplicated.');
						parameters = null;
						return false;
					}
					currentParams.article = duptitle;
				}
				break;

			case 'duplicatetemplate':  // T3
				if (form['csd.duplicatetemplate_2']) {
					var t3template = form['csd.duplicatetemplate_2'].value;
					if (!t3template || !t3template.trim()) {
						alert('CSD T3:  Please specify the name of a template duplicated by this one.');
						parameters = null;
						return false;
					}
					currentParams.ts = '~~~~~';
					currentParams.template = t3template.replace(/^\s*Template:/i, '');
				}
				break;

			case 'p1':  // P1
				if (form['csd.p1_criterion']) {
					var criterion = form['csd.p1_criterion'].value;
					if (!criterion || !criterion.trim()) {
						alert('CSD P1:  Please specify a single criterion.');
						parameters = null;
						return false;
					}
					currentParams.criterion = criterion;
				}
				break;

			default:
				break;
		}
		parameters.push(currentParams);
	});
	return parameters;
};

// Function for processing talk page notification template parameters
// key1/value1: for {{db-criterion-[notice|deleted]}} (via {{db-csd-[notice|deleted]-custom}})
// utparams.param: for {{db-[notice|deleted]-multiple}}
Twinkle.speedy.getUserTalkParameters = function twinklespeedyGetUserTalkParameters(normalized, parameters) {
	var utparams = [];

	// Special cases
	if (normalized === 'db') {
		utparams['2'] = parameters['1'];
	} else if (normalized === 'g6') {
		utparams.key1 = 'to';
		utparams.value1 = Morebits.pageNameNorm;
	} else if (normalized === 'g12') {
		['url', 'url2', 'url3'].forEach(function(item, idx) {
			if (parameters[item]) {
				idx++;
				utparams['key' + idx] = item;
				utparams['value' + idx] = utparams[item] = parameters[item];
			}
		});
	} else {
		// Handle the rest
		var param;
		switch (normalized) {
			case 'g4':
				param = 'xfd';
				break;
			case 'a2':
				param = 'source';
				break;
			case 'a5':
				param = 'location';
				break;
			case 'a10':
				param = 'article';
				break;
			case 'f9':
				param = 'url';
				break;
			case 'p1':
				param = 'criterion';
				break;
			default:
				break;
		}
		// No harm in providing a usertalk template with the others' parameters
		if (param && parameters[param]) {
			utparams.key1 = param;
			utparams.value1 = utparams[param] = parameters[param];
		}
	}
	return utparams;
};


Twinkle.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;
};

Twinkle.speedy.callback.evaluateSysop = function twinklespeedyCallbackEvaluateSysop(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 tag_only = form.tag_only;
	if (tag_only && tag_only.checked) {
		Twinkle.speedy.callback.evaluateUser(e);
		return;
	}

	var values = Twinkle.speedy.resolveCsdValues(e);
	if (!values) {
		return;
	}
	var templateParams = Twinkle.speedy.getParameters(form, values);
	if (!templateParams) {
		return;
	}

	var normalizeds = values.map(function(value) {
		return Twinkle.speedy.normalizeHash[value];
	});

	// analyse each criterion to determine whether to watch the page, prompt for summary, or notify the creator
	var watchPage, promptForSummary;
	normalizeds.forEach(function(norm) {
		if (Twinkle.getPref('watchSpeedyPages').indexOf(norm) !== -1) {
			watchPage = true;
		}
		if (Twinkle.getPref('promptForSpeedyDeletionSummary').indexOf(norm) !== -1) {
			promptForSummary = true;
		}
	});

	var warnusertalk = false;
	if (form.warnusertalk.checked) {
		$.each(normalizeds, function(index, norm) {
			if (Twinkle.getPref('warnUserOnSpeedyDelete').indexOf(norm) !== -1) {
				if (norm === 'g6' && values[index] !== 'copypaste') {
					return true;
				}
				warnusertalk = true;
				return false;  // break
			}
		});
	}

	var welcomeuser = false;
	if (warnusertalk) {
		$.each(normalizeds, function(index, norm) {
			if (Twinkle.getPref('welcomeUserOnSpeedyDeletionNotification').indexOf(norm) !== -1) {
				welcomeuser = true;
				return false;  // break
			}
		});
	}

	var params = {
		values: values,
		normalizeds: normalizeds,
		watch: watchPage,
		deleteTalkPage: form.talkpage && form.talkpage.checked,
		deleteRedirects: form.redirects.checked,
		warnUser: warnusertalk,
		welcomeuser: welcomeuser,
		promptForSummary: promptForSummary,
		templateParams: templateParams
	};

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

	Twinkle.speedy.callbacks.sysop.main(params);
};

Twinkle.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 = Twinkle.speedy.resolveCsdValues(e);
	if (!values) {
		return;
	}
	var templateParams = Twinkle.speedy.getParameters(form, values);
	if (!templateParams) {
		return;
	}

	// var multiple = form.multiple.checked;
	var normalizeds = [];
	$.each(values, function(index, value) {
		var norm = Twinkle.speedy.normalizeHash[value];

		normalizeds.push(norm);
	});

	// analyse each criterion to determine whether to watch the page/notify the creator
	var watchPage = false;
	$.each(normalizeds, function(index, norm) {
		if (Twinkle.getPref('watchSpeedyPages').indexOf(norm) !== -1) {
			watchPage = true;
			return false;  // break
		}
	});

	var notifyuser = false;
	if (form.notify.checked) {
		$.each(normalizeds, function(index, norm) {
			if (Twinkle.getPref('notifyUserOnSpeedyDeletionNomination').indexOf(norm) !== -1) {
				if (norm === 'g6' && values[index] !== 'copypaste') {
					return true;
				}
				notifyuser = true;
				return false;  // break
			}
		});
	}

	var welcomeuser = false;
	if (notifyuser) {
		$.each(normalizeds, function(index, norm) {
			if (Twinkle.getPref('welcomeUserOnSpeedyDeletionNotification').indexOf(norm) !== -1) {
				welcomeuser = true;
				return false;  // break
			}
		});
	}

	var csdlog = false;
	if (Twinkle.getPref('logSpeedyNominations')) {
		$.each(normalizeds, function(index, norm) {
			if (Twinkle.getPref('noLogOnSpeedyNomination').indexOf(norm) === -1) {
				csdlog = true;
				return false;  // break
			}
		});
	}

	var params = {
		values: values,
		normalizeds: normalizeds,
		watch: watchPage,
		usertalk: notifyuser,
		welcomeuser: welcomeuser,
		lognomination: csdlog,
		requestsalt: form.salting.checked,
		templateParams: templateParams
	};

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

	Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName');
	Morebits.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 Morebits.wiki.page(mw.config.get('wgPageName') + '/doc', 'Tagging module documentation page') : new Morebits.wiki.page(mw.config.get('wgPageName'), 'Tagging page');
	wikipedia_page.setCallbackParameters(params);
	wikipedia_page.load(Twinkle.speedy.callbacks.user.main);
};
})(jQuery);


// </nowiki>

//MediaWiki:Gadget-friendlyshared.js

// <nowiki>


(function($) { // eslint-disable-line no-unused-vars


/*
 ****************************************
 *** friendlyshared.js: Shared IP tagging module
 ****************************************
 * Mode of invocation:     Tab ("Shared")
 * Active on:              Existing IP user talk pages
 */

Twinkle.shared = function friendlyshared() {
	if (mw.config.get('wgNamespaceNumber') === 3 && mw.util.isIPAddress(mw.config.get('wgTitle'))) {
		var username = mw.config.get('wgRelevantUserName');
		Twinkle.addPortletLink(function() {
			Twinkle.shared.callback(username);
		}, 'Shared IP', 'friendly-shared', 'Shared IP tagging');
	}
};

Twinkle.shared.callback = function friendlysharedCallback() {
	var Window = new Morebits.simpleWindow(600, 420);
	Window.setTitle('Shared IP address tagging');
	Window.setScriptName('Twinkle');
	Window.addFooterLink('Twinkle help', 'WP:TW/DOC#shared');

	var form = new Morebits.quickForm(Twinkle.shared.callback.evaluate);

	var div = form.append({
		type: 'div',
		id: 'sharedip-templatelist',
		className: 'morebits-scrollbox'
	}
	);
	div.append({ type: 'header', label: 'Shared IP address templates' });
	div.append({ type: 'radio', name: 'shared', list: Twinkle.shared.standardList,
		event: function(e) {
			Twinkle.shared.callback.change_shared(e);
			e.stopPropagation();
		}
	});

	var org = form.append({ type: 'field', label: 'Fill in other details (optional) and click "Submit"' });
	org.append({
		type: 'input',
		name: 'organization',
		label: 'IP address owner/operator',
		disabled: true,
		tooltip: 'You can optionally enter the name of the organization that owns/operates the IP address.  You can use wikimarkup if necessary.'
	}
	);
	org.append({
		type: 'input',
		name: 'host',
		label: 'Host name (optional)',
		disabled: true,
		tooltip: 'The host name (for example, proxy.example.com) can be optionally entered here and will be linked by the template.'
	}
	);
	org.append({
		type: 'input',
		name: 'contact',
		label: 'Contact information (only if requested)',
		disabled: true,
		tooltip: 'You can optionally enter some contact details for the organization.  Use this parameter only if the organization has specifically requested that it be added.  You can use wikimarkup if necessary.'
	}
	);

	form.append({ type: 'submit' });

	var result = form.render();
	Window.setContent(result);
	Window.display();
};

Twinkle.shared.standardList = [
	{
		label: '{{Shared IP}}: standard shared IP address template',
		value: 'Shared IP',
		tooltip: 'IP user talk page template that shows helpful information to IP users and those wishing to warn, block or ban them'
	},
	{
		label: '{{Shared IP edu}}: shared IP address template modified for educational institutions',
		value: 'Shared IP edu'
	},
	{
		label: '{{Shared IP corp}}: shared IP address template modified for businesses',
		value: 'Shared IP corp'
	},
	{
		label: '{{Shared IP public}}: shared IP address template modified for public terminals',
		value: 'Shared IP public'
	},
	{
		label: '{{Shared IP gov}}: shared IP address template modified for government agencies or facilities',
		value: 'Shared IP gov'
	},
	{
		label: '{{Dynamic IP}}: shared IP address template modified for organizations with dynamic addressing',
		value: 'Dynamic IP'
	},
	{
		label: '{{Static IP}}: shared IP address template modified for static IP addresses',
		value: 'Static IP'
	},
	{
		label: '{{ISP}}: shared IP address template modified for ISP organizations (specifically proxies)',
		value: 'ISP'
	},
	{
		label: '{{Mobile IP}}: shared IP address template modified for mobile phone companies and their customers',
		value: 'Mobile IP'
	},
	{
		label: '{{Whois}}: template for IP addresses in need of monitoring, but unknown whether static, dynamic or shared',
		value: 'Whois'
	}
];

Twinkle.shared.callback.change_shared = function friendlysharedCallbackChangeShared(e) {
	e.target.form.contact.disabled = e.target.value !== 'Shared IP edu';  // only supported by {{Shared IP edu}}
	e.target.form.organization.disabled = false;
	e.target.form.host.disabled = e.target.value === 'Whois';  // host= not supported by {{Whois}}
};

Twinkle.shared.callbacks = {
	main: function(pageobj) {
		var params = pageobj.getCallbackParameters();
		var pageText = pageobj.getPageText();
		var found = false;
		var text = '{{';

		for (var i = 0; i < Twinkle.shared.standardList.length; i++) {
			var tagRe = new RegExp('(\\{\\{' + Twinkle.shared.standardList[i].value + '(\\||\\}\\}))', 'im');
			if (tagRe.exec(pageText)) {
				Morebits.status.warn('Info', 'Found {{' + Twinkle.shared.standardList[i].value + '}} on the user\'s talk page already...aborting');
				found = true;
			}
		}

		if (found) {
			return;
		}

		Morebits.status.info('Info', 'Will add the shared IP address template to the top of the user\'s talk page.');
		text += params.value + '|' + params.organization;
		if (params.value === 'Shared IP edu' && params.contact !== '') {
			text += '|' + params.contact;
		}
		if (params.value !== 'Whois' && params.host !== '') {
			text += '|host=' + params.host;
		}
		text += '}}\n\n';

		var summaryText = 'Added {{[[Template:' + params.value + '|' + params.value + ']]}} template.';
		pageobj.setPageText(text + pageText);
		pageobj.setEditSummary(summaryText + Twinkle.getPref('summaryAd'));
		pageobj.setMinorEdit(Twinkle.getPref('markSharedIPAsMinor'));
		pageobj.setCreateOption('recreate');
		pageobj.save();
	}
};

Twinkle.shared.callback.evaluate = function friendlysharedCallbackEvaluate(e) {
	var shared = e.target.getChecked('shared');
	if (!shared || shared.length <= 0) {
		alert('You must select a shared IP address template to use!');
		return;
	}

	var value = shared[0];

	if (e.target.organization.value === '') {
		alert('You must input an organization for the {{' + value + '}} template!');
		return;
	}

	var params = {
		value: value,
		organization: e.target.organization.value,
		host: e.target.host.value,
		contact: e.target.contact.value
	};

	Morebits.simpleWindow.setButtonsEnabled(false);
	Morebits.status.init(e.target);

	Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName');
	Morebits.wiki.actionCompleted.notice = 'Tagging complete, reloading talk page in a few seconds';

	var wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName'), 'User talk page modification');
	wikipedia_page.setFollowRedirect(true);
	wikipedia_page.setCallbackParameters(params);
	wikipedia_page.load(Twinkle.shared.callbacks.main);
};
})(jQuery);


// </nowiki>

//MediaWiki:Gadget-twinklediff.js

// <nowiki>


(function($) {


/*
 ****************************************
 *** twinklediff.js: Diff module
 ****************************************
 * Mode of invocation:     Tab on non-diff pages ("Last"); tabs on diff pages ("Since", "Since mine", "Current")
 * Active on:              Existing non-special pages
 */

Twinkle.diff = function twinklediff() {
	if (mw.config.get('wgNamespaceNumber') < 0 || !mw.config.get('wgArticleId')) {
		return;
	}
	Twinkle.addPortletLink(mw.util.getUrl(mw.config.get('wgPageName'), {diff: 'cur', oldid: 'prev'}), 'Last', 'tw-lastdiff', 'Show most recent diff');

	// Show additional tabs only on diff pages
	if (mw.util.getParamValue('diff')) {
		Twinkle.addPortletLink(function() {
			Twinkle.diff.evaluate(false);
		}, 'Since', 'tw-since', 'Show difference between last diff and the revision made by previous user');
		Twinkle.addPortletLink(function() {
			Twinkle.diff.evaluate(true);
		}, 'Since mine', 'tw-sincemine', 'Show difference between last diff and my last revision');

		var oldid = /oldid=(.+)/.exec($('#mw-diff-ntitle1').find('strong a').first().attr('href'))[1];
		Twinkle.addPortletLink(mw.util.getUrl(mw.config.get('wgPageName'), {diff: 'cur', oldid: oldid}), 'Current', 'tw-curdiff', 'Show difference to current revision');
	}
};

Twinkle.diff.evaluate = function twinklediffEvaluate(me) {

	var user;
	if (me) {
		user = mw.config.get('wgUserName');
	} else {
		var node = document.getElementById('mw-diff-ntitle2');
		if (!node) {
			// nothing to do?
			return;
		}
		user = $(node).find('a').first().text();
	}
	var query = {
		'prop': 'revisions',
		'action': 'query',
		'titles': mw.config.get('wgPageName'),
		'rvlimit': 1,
		'rvprop': [ 'ids', 'user' ],
		'rvstartid': mw.config.get('wgCurRevisionId') - 1, // i.e. not the current one
		'rvuser': user
	};
	Morebits.status.init(document.getElementById('mw-content-text'));
	var wikipedia_api = new Morebits.wiki.api('Grabbing data of initial contributor', query, Twinkle.diff.callbacks.main);
	wikipedia_api.params = { user: user };
	wikipedia_api.post();
};

Twinkle.diff.callbacks = {
	main: function(self) {
		var xmlDoc = self.responseXML;
		var revid = $(xmlDoc).find('rev').attr('revid');

		if (!revid) {
			self.statelem.error('no suitable earlier revision found, or ' + self.params.user + ' is the only contributor. Aborting.');
			return;
		}
		window.location = mw.util.getUrl(mw.config.get('wgPageName'), {
			diff: mw.config.get('wgCurRevisionId'),
			oldid: revid
		});
	}
};
})(jQuery);


// </nowiki>

//Gadget-twinkleunlink.js

// <nowiki>


(function($) {


/*
 ****************************************
 *** twinkleunlink.js: Unlink module
 ****************************************
 * Mode of invocation:     Tab ("Unlink")
 * Active on:              Non-special pages, except Wikipedia:Sandbox
 */

Twinkle.unlink = function twinkleunlink() {
	if (mw.config.get('wgNamespaceNumber') < 0 || mw.config.get('wgPageName') === 'Wikipedia:Sandbox' ||
		// Restrict to extended confirmed users (see #428)
		(!Morebits.userIsInGroup('extendedconfirmed') && !Morebits.userIsSysop)) {
		return;
	}
	Twinkle.addPortletLink(Twinkle.unlink.callback, 'Unlink', 'tw-unlink', 'Unlink backlinks');
};

Twinkle.unlink.getChecked2 = function twinkleunlinkGetChecked2(nodelist) {
	if (!(nodelist instanceof NodeList) && !(nodelist instanceof HTMLCollection)) {
		return nodelist.checked ? [ nodelist.values ] : [];
	}
	var result = [];
	for (var i = 0; i < nodelist.length; ++i) {
		if (nodelist[i].checked) {
			result.push(nodelist[i].values);
		}
	}
	return result;
};

// the parameter is used when invoking unlink from admin speedy
Twinkle.unlink.callback = function(presetReason) {
	var Window = new Morebits.simpleWindow(600, 440);
	Window.setTitle('Unlink backlinks' + (mw.config.get('wgNamespaceNumber') === 6 ? ' and file usages' : ''));
	Window.setScriptName('Twinkle');
	Window.addFooterLink('Twinkle help', 'WP:TW/DOC#unlink');

	var form = new Morebits.quickForm(Twinkle.unlink.callback.evaluate);

	// prepend some basic documentation
	var node1 = Morebits.htmlNode('code', '[[' + Morebits.pageNameNorm + '|link text]]');
	var node2 = Morebits.htmlNode('code', 'link text');
	node1.style.fontFamily = node2.style.fontFamily = 'monospace';
	node1.style.fontStyle = node2.style.fontStyle = 'normal';
	form.append({
		type: 'div',
		style: 'margin-bottom: 0.5em',
		label: [
			'This tool allows you to unlink all incoming links ("backlinks") that point to this page' +
				(mw.config.get('wgNamespaceNumber') === 6 ? ', and/or hide all inclusions of this file by wrapping them in <!-- --> comment markup' : '') +
				'. For instance, ',
			node1,
			' would become ',
			node2,
			'. Use it with caution.'
		]
	});

	form.append({
		type: 'input',
		name: 'reason',
		label: 'Reason: ',
		value: presetReason ? presetReason : '',
		size: 60
	});

	var query;
	if (mw.config.get('wgNamespaceNumber') === 6) {  // File:
		query = {
			'action': 'query',
			'list': [ 'backlinks', 'imageusage' ],
			'bltitle': mw.config.get('wgPageName'),
			'iutitle': mw.config.get('wgPageName'),
			'bllimit': 'max', // 500 is max for normal users, 5000 for bots and sysops
			'iulimit': 'max', // 500 is max for normal users, 5000 for bots and sysops
			'blnamespace': Twinkle.getPref('unlinkNamespaces'),
			'iunamespace': Twinkle.getPref('unlinkNamespaces'),
			'rawcontinue': true
		};
	} else {
		query = {
			'action': 'query',
			'list': 'backlinks',
			'bltitle': mw.config.get('wgPageName'),
			'blfilterredir': 'nonredirects',
			'bllimit': 'max', // 500 is max for normal users, 5000 for bots and sysops
			'blnamespace': Twinkle.getPref('unlinkNamespaces'),
			'rawcontinue': true
		};
	}
	var wikipedia_api = new Morebits.wiki.api('Grabbing backlinks', query, Twinkle.unlink.callbacks.display.backlinks);
	wikipedia_api.params = { form: form, Window: Window, image: mw.config.get('wgNamespaceNumber') === 6 };
	wikipedia_api.post();

	var root = document.createElement('div');
	root.style.padding = '15px';  // just so it doesn't look broken
	Morebits.status.init(root);
	wikipedia_api.statelem.status('loading...');
	Window.setContent(root);
	Window.display();
};

Twinkle.unlink.callback.evaluate = function twinkleunlinkCallbackEvaluate(event) {
	var reason = event.target.reason.value;
	if (!reason) {
		alert('You must specify a reason for unlinking.');
		return;
	}

	var backlinks = [], imageusage = [];
	if (event.target.backlinks) {
		backlinks = Twinkle.unlink.getChecked2(event.target.backlinks);
	}
	if (event.target.imageusage) {
		imageusage = Twinkle.unlink.getChecked2(event.target.imageusage);
	}

	Morebits.simpleWindow.setButtonsEnabled(false);
	Morebits.status.init(event.target);

	var pages = Morebits.array.uniq(backlinks.concat(imageusage));

	var unlinker = new Morebits.batchOperation('Unlinking backlinks' + (imageusage ? ' and instances of file usage' : ''));
	unlinker.setOption('preserveIndividualStatusLines', true);
	unlinker.setPageList(pages);
	var params = { reason: reason, unlinker: unlinker };
	unlinker.run(function(pageName) {
		var wikipedia_page = new Morebits.wiki.page(pageName, 'Unlinking in article "' + pageName + '"');
		wikipedia_page.setBotEdit(true);  // unlink considered a floody operation
		var innerParams = $.extend({}, params);
		innerParams.doBacklinks = backlinks && backlinks.indexOf(pageName) !== -1;
		innerParams.doImageusage = imageusage && imageusage.indexOf(pageName) !== -1;
		wikipedia_page.setCallbackParameters(innerParams);
		wikipedia_page.load(Twinkle.unlink.callbacks.unlinkBacklinks);
	});
};

Twinkle.unlink.callbacks = {
	display: {
		backlinks: function twinkleunlinkCallbackDisplayBacklinks(apiobj) {
			var xmlDoc = apiobj.responseXML;
			var havecontent = false;
			var list, namespaces, i;

			if (apiobj.params.image) {
				var imageusage = $(xmlDoc).find('query imageusage iu');
				list = [];
				for (i = 0; i < imageusage.length; ++i) {
					var usagetitle = imageusage[i].getAttribute('title');
					list.push({ label: usagetitle, value: usagetitle, checked: true });
				}
				if (!list.length) {
					apiobj.params.form.append({ type: 'div', label: 'No instances of file usage found.' });
				} else {
					apiobj.params.form.append({ type: 'header', label: 'File usage' });
					namespaces = [];
					$.each(Twinkle.getPref('unlinkNamespaces'), function(k, v) {
						namespaces.push(v === '0' ? '(Article)' : mw.config.get('wgFormattedNamespaces')[v]);
					});
					apiobj.params.form.append({
						type: 'div',
						label: 'Selected namespaces: ' + namespaces.join(', '),
						tooltip: 'You can change this with your Twinkle preferences, at [[WP:TWPREFS]]'
					});
					if ($(xmlDoc).find('query-continue').length) {
						apiobj.params.form.append({
							type: 'div',
							label: 'First ' + list.length.toString() + ' file usages shown.'
						});
					}
					apiobj.params.form.append({
						type: 'button',
						label: 'Select All',
						event: function(e) {
							$(Morebits.quickForm.getElements(e.target.form, 'imageusage')).prop('checked', true);
						}
					});
					apiobj.params.form.append({
						type: 'button',
						label: 'Deselect All',
						event: function(e) {
							$(Morebits.quickForm.getElements(e.target.form, 'imageusage')).prop('checked', false);
						}
					});
					apiobj.params.form.append({
						type: 'checkbox',
						name: 'imageusage',
						list: list
					});
					havecontent = true;
				}
			}

			var backlinks = $(xmlDoc).find('query backlinks bl');
			if (backlinks.length > 0) {
				list = [];
				for (i = 0; i < backlinks.length; ++i) {
					var title = backlinks[i].getAttribute('title');
					list.push({ label: title, value: title, checked: true });
				}
				apiobj.params.form.append({ type: 'header', label: 'Backlinks' });
				namespaces = [];
				$.each(Twinkle.getPref('unlinkNamespaces'), function(k, v) {
					namespaces.push(v === '0' ? '(Article)' : mw.config.get('wgFormattedNamespaces')[v]);
				});
				apiobj.params.form.append({
					type: 'div',
					label: 'Selected namespaces: ' + namespaces.join(', '),
					tooltip: 'You can change this with your Twinkle preferences, at [[WP:TWPREFS]]'
				});
				if ($(xmlDoc).find('query-continue').length) {
					apiobj.params.form.append({
						type: 'div',
						label: 'First ' + list.length.toString() + ' backlinks shown.'
					});
				}
				apiobj.params.form.append({
					type: 'button',
					label: 'Select All',
					event: function(e) {
						$(Morebits.quickForm.getElements(e.target.form, 'backlinks')).prop('checked', true);
					}
				});
				apiobj.params.form.append({
					type: 'button',
					label: 'Deselect All',
					event: function(e) {
						$(Morebits.quickForm.getElements(e.target.form, 'backlinks')).prop('checked', false);
					}
				});
				apiobj.params.form.append({
					type: 'checkbox',
					name: 'backlinks',
					list: list
				});
				havecontent = true;
			} else {
				apiobj.params.form.append({ type: 'div', label: 'No backlinks found.' });
			}

			if (havecontent) {
				apiobj.params.form.append({ type: 'submit' });
			}

			var result = apiobj.params.form.render();
			apiobj.params.Window.setContent(result);

			Morebits.checkboxShiftClickSupport($("input[name='imageusage']", result));
			Morebits.checkboxShiftClickSupport($("input[name='backlinks']", result));

		}
	},
	unlinkBacklinks: function twinkleunlinkCallbackUnlinkBacklinks(pageobj) {
		var oldtext = pageobj.getPageText();
		var params = pageobj.getCallbackParameters();
		var wikiPage = new Morebits.wikitext.page(oldtext);

		var summaryText = '', warningString = false;
		var text;

		// remove image usages
		if (params.doImageusage) {
			wikiPage.commentOutImage(mw.config.get('wgTitle'), 'Commented out');
			text = wikiPage.getText();
			// did we actually make any changes?
			if (text === oldtext) {
				warningString = 'file usages';
			} else {
				summaryText = 'Commenting out use(s) of file';
				oldtext = text;
			}
		}

		// remove backlinks
		if (params.doBacklinks) {
			wikiPage.removeLink(Morebits.pageNameNorm);
			text = wikiPage.getText();
			// did we actually make any changes?
			if (text === oldtext) {
				warningString = warningString ? 'backlinks or file usages' : 'backlinks';
			} else {
				summaryText = (summaryText ? summaryText + ' / ' : '') + 'Removing link(s) to';
				oldtext = text;
			}
		}

		if (warningString) {
			// nothing to do!
			pageobj.getStatusElement().error("Didn't find any " + warningString + ' on the page.');
			params.unlinker.workerFailure(pageobj);
			return;
		}

		pageobj.setPageText(text);
		pageobj.setEditSummary(summaryText + ' "' + Morebits.pageNameNorm + '": ' + params.reason + '.' + Twinkle.getPref('summaryAd'));
		pageobj.setCreateOption('nocreate');
		pageobj.save(params.unlinker.workerSuccess, params.unlinker.workerFailure);
	}
};
})(jQuery);


// </nowiki>

//Gadget-friendlytag.js

// <nowiki>

(function($) {


/*
 ****************************************
 *** friendlytag.js: Tag module
 ****************************************
 * Mode of invocation:     Tab ("Tag")
 * Active on:              Existing articles and drafts; file pages with a corresponding file
 *                         which is local (not on Commons); all redirects
 */

Twinkle.tag = function friendlytag() {
	// redirect tagging
	if (Morebits.wiki.isPageRedirect()) {
		Twinkle.tag.mode = 'redirect';
		Twinkle.addPortletLink(Twinkle.tag.callback, 'Tag', 'friendly-tag', 'Tag redirect');
	// file tagging
	} else if (mw.config.get('wgNamespaceNumber') === 6 && !document.getElementById('mw-sharedupload') && document.getElementById('mw-imagepage-section-filehistory')) {
		Twinkle.tag.mode = 'file';
		Twinkle.addPortletLink(Twinkle.tag.callback, 'Tag', 'friendly-tag', 'Add maintenance tags to file');
	// article/draft article tagging
	} else if ([0, 118].indexOf(mw.config.get('wgNamespaceNumber')) !== -1 && mw.config.get('wgCurRevisionId')) {
		Twinkle.tag.mode = 'article';
		// Can't remove tags when not viewing current version
		Twinkle.tag.canRemove = (mw.config.get('wgCurRevisionId') === mw.config.get('wgRevisionId')) &&
			// Disabled on latest diff because the diff slider could be used to slide
			// away from the latest diff without causing the script to reload
			!mw.config.get('wgDiffNewId');
		Twinkle.addPortletLink(Twinkle.tag.callback, 'Tag', 'friendly-tag', 'Add or remove article maintenance tags');
	}
};

Twinkle.tag.checkedTags = [];

Twinkle.tag.callback = function friendlytagCallback() {
	var Window = new Morebits.simpleWindow(630, Twinkle.tag.mode === 'article' ? 500 : 400);
	Window.setScriptName('Twinkle');
	// anyone got a good policy/guideline/info page/instructional page link??
	Window.addFooterLink('Twinkle help', 'WP:TW/DOC#tag');

	var form = new Morebits.quickForm(Twinkle.tag.callback.evaluate);

	form.append({
		type: 'input',
		label: 'Quick filter: ',
		name: 'quickfilter',
		size: '30px',
		event: function twinkletagquickfilter() {
			// flush the DOM of all existing underline spans
			$allCheckboxDivs.find('.search-hit').each(function(i, e) {
				var label_element = e.parentElement;
				// This would convert <label>Hello <span class=search-hit>wo</span>rld</label>
				// to <label>Hello world</label>
				label_element.innerHTML = label_element.textContent;
			});

			if (this.value) {
				$allCheckboxDivs.hide();
				$allHeaders.hide();
				var searchString = this.value;
				var searchRegex = new RegExp(mw.util.escapeRegExp(searchString), 'i');

				$allCheckboxDivs.find('label').each(function () {
					var label_text = this.textContent;
					var searchHit = searchRegex.exec(label_text);
					if (searchHit) {
						var range = document.createRange();
						var textnode = this.childNodes[0];
						range.selectNodeContents(textnode);
						range.setStart(textnode, searchHit.index);
						range.setEnd(textnode, searchHit.index + searchString.length);
						var underline_span = $('<span>').addClass('search-hit').css('text-decoration', 'underline')[0];
						range.surroundContents(underline_span);
						this.parentElement.style.display = 'block'; // show
					}
				});
			} else {
				$allCheckboxDivs.show();
				$allHeaders.show();
			}
		}
	});

	switch (Twinkle.tag.mode) {
		case 'article':
			Window.setTitle('Article maintenance tagging');

			form.append({
				type: 'select',
				name: 'sortorder',
				label: 'View this list:',
				tooltip: 'You can change the default view order in your Twinkle preferences (WP:TWPREFS).',
				event: Twinkle.tag.updateSortOrder,
				list: [
					{ type: 'option', value: 'cat', label: 'By categories', selected: Twinkle.getPref('tagArticleSortOrder') === 'cat' },
					{ type: 'option', value: 'alpha', label: 'In alphabetical order', selected: Twinkle.getPref('tagArticleSortOrder') === 'alpha' }
				]
			});


			if (!Twinkle.tag.canRemove) {
				var divElement = document.createElement('div');
				divElement.innerHTML = 'For removal of existing tags, please open Tag menu from the current version of article';
				form.append({
					type: 'div',
					name: 'untagnotice',
					label: divElement
				});
			}

			form.append({
				type: 'div',
				id: 'tagWorkArea',
				className: 'morebits-scrollbox',
				style: 'max-height: 28em'
			});

			form.append({
				type: 'checkbox',
				list: [
					{
						label: 'Group inside {{multiple issues}} if possible',
						value: 'group',
						name: 'group',
						tooltip: 'If applying two or more templates supported by {{multiple issues}} and this box is checked, all supported templates will be grouped inside a {{multiple issues}} template.',
						checked: Twinkle.getPref('groupByDefault')
					}
				]
			});

			form.append({
				type: 'input',
				label: 'Reason',
				name: 'reason',
				tooltip: 'Optional reason to be appended in edit summary. Recommended when removing tags.',
				size: '60px'
			});

			break;

		case 'file':
			Window.setTitle('File maintenance tagging');

			form.append({ type: 'header', label: 'License and sourcing problem tags' });
			form.append({ type: 'checkbox', name: 'fileTags', list: Twinkle.tag.file.licenseList });

			form.append({ type: 'header', label: 'Wikimedia Commons-related tags' });
			form.append({ type: 'checkbox', name: 'fileTags', list: Twinkle.tag.file.commonsList });

			form.append({ type: 'header', label: 'Cleanup tags' });
			form.append({ type: 'checkbox', name: 'fileTags', list: Twinkle.tag.file.cleanupList });

			form.append({ type: 'header', label: 'Image quality tags' });
			form.append({ type: 'checkbox', name: 'fileTags', list: Twinkle.tag.file.qualityList });

			form.append({ type: 'header', label: 'Replacement tags' });
			form.append({ type: 'checkbox', name: 'fileTags', list: Twinkle.tag.file.replacementList });

			if (Twinkle.getPref('customFileTagList').length) {
				form.append({ type: 'header', label: 'Custom tags' });
				form.append({ type: 'checkbox', name: 'fileTags', list: Twinkle.getPref('customFileTagList') });
			}
			break;

		case 'redirect':
			Window.setTitle('Redirect tagging');

			form.append({ type: 'header', label: 'Spelling, misspelling, tense and capitalization templates' });
			form.append({ type: 'checkbox', name: 'redirectTags', list: Twinkle.tag.spellingList });

			form.append({ type: 'header', label: 'Alternative name templates' });
			form.append({ type: 'checkbox', name: 'redirectTags', list: Twinkle.tag.alternativeList });

			form.append({ type: 'header', label: 'Miscellaneous and administrative redirect templates' });
			form.append({ type: 'checkbox', name: 'redirectTags', list: Twinkle.tag.administrativeList });

			if (Twinkle.getPref('customRedirectTagList').length) {
				form.append({ type: 'header', label: 'Custom tags' });
				form.append({ type: 'checkbox', name: 'redirectTags', list: Twinkle.getPref('customRedirectTagList') });
			}
			break;

		default:
			alert('Twinkle.tag: unknown mode ' + Twinkle.tag.mode);
			break;
	}

	if (document.getElementsByClassName('patrollink').length) {
		form.append({
			type: 'checkbox',
			list: [
				{
					label: 'Mark the page as patrolled/reviewed',
					value: 'patrolPage',
					name: 'patrolPage',
					checked: Twinkle.getPref('markTaggedPagesAsPatrolled')
				}
			]
		});
	}
	form.append({ type: 'submit', className: 'tw-tag-submit' });

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

	// for quick filter:
	$allCheckboxDivs = $(result).find('[name$=Tags]').parent();
	$allHeaders = $(result).find('h5');
	result.quickfilter.focus();  // place cursor in the quick filter field as soon as window is opened
	result.quickfilter.autocomplete = 'off'; // disable browser suggestions
	result.quickfilter.addEventListener('keypress', function(e) {
		if (e.keyCode === 13) { // prevent enter key from accidentally submitting the form
			e.preventDefault();
			return false;
		}
	});

	if (Twinkle.tag.mode === 'article') {

		Twinkle.tag.alreadyPresentTags = [];

		if (Twinkle.tag.canRemove) {
			// Look for existing maintenance tags in the lead section and put them in array

			// All tags are HTML table elements that are direct children of .mw-parser-output,
			// except when they are within {{multiple issues}}
			$('.mw-parser-output').children().each(function parsehtml(i, e) {

				// break out on encountering the first heading, which means we are no
				// longer in the lead section
				if (e.tagName === 'H2') {
					return false;
				}

				// The ability to remove tags depends on the template's {{ambox}} |name=
				// parameter bearing the template's correct name (preferably) or a name that at
				// least redirects to the actual name

				// All tags have their first class name as "box-" + template name
				if (e.className.indexOf('box-') === 0) {
					if (e.classList[0] === 'box-Multiple_issues') {
						$(e).find('.ambox').each(function(idx, e) {
							var tag = e.classList[0].slice(4).replace(/_/g, ' ');
							Twinkle.tag.alreadyPresentTags.push(tag);
						});
						return true; // continue
					}

					var tag = e.classList[0].slice(4).replace(/_/g, ' ');
					Twinkle.tag.alreadyPresentTags.push(tag);
				}
			});

			// {{Uncategorized}} and {{Improve categories}} are usually placed at the end
			if ($('.box-Uncategorized').length) {
				Twinkle.tag.alreadyPresentTags.push('Uncategorized');
			}
			if ($('.box-Improve_categories').length) {
				Twinkle.tag.alreadyPresentTags.push('Improve categories');
			}

		}

		// Add status text node after Submit button
		var statusNode = document.createElement('small');
		statusNode.id = 'tw-tag-status';
		Twinkle.tag.status = {
			// initial state; defined like this because these need to be available for reference
			// in the click event handler
			numAdded: 0,
			numRemoved: 0
		};
		$('button.tw-tag-submit').after(statusNode);

		// fake a change event on the sort dropdown, to initialize the tag list
		var evt = document.createEvent('Event');
		evt.initEvent('change', true, true);
		result.sortorder.dispatchEvent(evt);

	} else {
		// Redirects and files: Add a link to each template's description page
		Morebits.quickForm.getElements(result, Twinkle.tag.mode + 'Tags').forEach(generateLinks);
	}
};


// $allCheckboxDivs and $allHeaders are defined globally, rather than in the
// quickfilter event function, to avoid having to recompute them on every keydown
var $allCheckboxDivs, $allHeaders;

Twinkle.tag.updateSortOrder = function(e) {
	var form = e.target.form;
	var sortorder = e.target.value;
	Twinkle.tag.checkedTags = form.getChecked('articleTags') || [];

	var container = new Morebits.quickForm.element({ type: 'fragment' });

	// function to generate a checkbox, with appropriate subgroup if needed
	var makeCheckbox = function(tag, description) {
		var checkbox = { value: tag, label: '{{' + tag + '}}: ' + description };
		if (Twinkle.tag.checkedTags.indexOf(tag) !== -1) {
			checkbox.checked = true;
		}
		switch (tag) {
			case 'Cleanup':
				checkbox.subgroup = {
					name: 'cleanup',
					type: 'input',
					label: 'Specific reason why cleanup is needed: ',
					tooltip: 'Required.',
					size: 35
				};
				break;
			case 'Close paraphrasing':
				checkbox.subgroup = {
					name: 'closeParaphrasing',
					type: 'input',
					label: 'Source: ',
					tooltip: 'Source that has been closely paraphrased'
				};
				break;
			case 'Copy edit':
				checkbox.subgroup = {
					name: 'copyEdit',
					type: 'input',
					label: '"This article may require copy editing for..." ',
					tooltip: 'e.g. "consistent spelling". Optional.',
					size: 35
				};
				break;
			case 'Copypaste':
				checkbox.subgroup = {
					name: 'copypaste',
					type: 'input',
					label: 'Source URL: ',
					tooltip: 'If known.',
					size: 50
				};
				break;
			case 'Expand language':
				checkbox.subgroup = [ {
					name: 'expandLanguageLangCode',
					type: 'input',
					label: 'Language code: ',
					tooltip: 'Language code of the language from which article is to be expanded from'
				}, {
					name: 'expandLanguageArticle',
					type: 'input',
					label: 'Name of article: ',
					tooltip: 'Name of article to be expanded from, without the interwiki prefix'
				}
				];
				break;
			case 'Expert needed':
				checkbox.subgroup = [
					{
						name: 'expertNeeded',
						type: 'input',
						label: 'Name of relevant WikiProject: ',
						tooltip: 'Optionally, enter the name of a WikiProject which might be able to help recruit an expert. Don\'t include the "WikiProject" prefix.'
					},
					{
						name: 'expertNeededReason',
						type: 'input',
						label: 'Reason: ',
						tooltip: 'Short explanation describing the issue. Either Reason or Talk link is required.'
					},
					{
						name: 'expertNeededTalk',
						type: 'input',
						label: 'Talk discussion: ',
						tooltip: 'Name of the section of this article\'s talk page where the issue is being discussed. Do not give a link, just the name of the section. Either Reason or Talk link is required.'
					}
				];
				break;
			case 'Globalize':
				checkbox.subgroup = {
					name: 'globalizeRegion',
					type: 'input',
					label: 'Over-represented country or region'
				};
				break;
			case 'History merge':
				checkbox.subgroup = [
					{
						name: 'histmergeOriginalPage',
						type: 'input',
						label: 'Other article: ',
						tooltip: 'Name of the page that should be merged into this one (required).'
					},
					{
						name: 'histmergeReason',
						type: 'input',
						label: 'Reason: ',
						tooltip: 'Short explanation describing the reason a history merge is needed. Should probably begin with "because" and end with a period.'
					},
					{
						name: 'histmergeSysopDetails',
						type: 'input',
						label: 'Extra details: ',
						tooltip: 'For complex cases, provide extra instructions for the reviewing administrator.'
					}
				];
				break;
			case 'Merge':
			case 'Merge from':
			case 'Merge to':
				var otherTagName = 'Merge';
				switch (tag) {
					case 'Merge from':
						otherTagName = 'Merge to';
						break;
					case 'Merge to':
						otherTagName = 'Merge from';
						break;
					// no default
				}
				checkbox.subgroup = [
					{
						name: 'mergeTarget',
						type: 'input',
						label: 'Other article(s): ',
						tooltip: 'If specifying multiple articles, separate them with pipe characters: Article one|Article two'
					},
					{
						name: 'mergeTagOther',
						type: 'checkbox',
						list: [
							{
								label: 'Tag the other article with a {{' + otherTagName + '}} tag',
								checked: true,
								tooltip: 'Only available if a single article name is entered.'
							}
						]
					}
				];
				if (mw.config.get('wgNamespaceNumber') === 0) {
					checkbox.subgroup.push({
						name: 'mergeReason',
						type: 'textarea',
						label: 'Rationale for merge (will be posted on ' +
							(tag === 'Merge to' ? 'the other article\'s' : 'this article\'s') + ' talk page):',
						tooltip: 'Optional, but strongly recommended. Leave blank if not wanted. Only available if a single article name is entered.'
					});
				}
				break;
			case 'Not English':
			case 'Rough translation':
				checkbox.subgroup = [
					{
						name: 'translationLanguage',
						type: 'input',
						label: 'Language of article (if known): ',
						tooltip: 'Consider looking at [[WP:LRC]] for help. If listing the article at PNT, please try to avoid leaving this box blank, unless you are completely unsure.'
					}
				];
				if (tag === 'Not English') {
					checkbox.subgroup.push({
						name: 'translationNotify',
						type: 'checkbox',
						list: [
							{
								label: 'Notify article creator',
								checked: true,
								tooltip: "Places {{uw-notenglish}} on the creator's talk page."
							}
						]
					});
				}
				if (mw.config.get('wgNamespaceNumber') === 0) {
					checkbox.subgroup.push({
						name: 'translationPostAtPNT',
						type: 'checkbox',
						list: [
							{
								label: 'List this article at Wikipedia:Pages needing translation into English (PNT)',
								checked: true
							}
						]
					});
					checkbox.subgroup.push({
						name: 'translationComments',
						type: 'textarea',
						label: 'Additional comments to post at PNT',
						tooltip: 'Optional, and only relevant if "List this article ..." above is checked.'
					});
				}
				break;
			case 'Notability':
				checkbox.subgroup = {
					name: 'notability',
					type: 'select',
					list: [
						{ label: "{{notability}}: article's subject may not meet the general notability guideline", value: 'none' },
						{ label: '{{notability|Academics}}: notability guideline for academics', value: 'Academics' },
						{ label: '{{notability|Astro}}: notability guideline for astronomical objects', value: 'Astro' },
						{ label: '{{notability|Biographies}}: notability guideline for biographies', value: 'Biographies' },
						{ label: '{{notability|Books}}: notability guideline for books', value: 'Books' },
						{ label: '{{notability|Companies}}: notability guidelines for companies and organizations', value: 'Companies' },
						{ label: '{{notability|Events}}: notability guideline for events', value: 'Events' },
						{ label: '{{notability|Films}}: notability guideline for films', value: 'Films' },
						{ label: '{{notability|Geographic}}: notability guideline for geographic features', value: 'Geographic' },
						{ label: '{{notability|Lists}}: notability guideline for stand-alone lists', value: 'Lists' },
						{ label: '{{notability|Music}}: notability guideline for music', value: 'Music' },
						{ label: '{{notability|Neologisms}}: notability guideline for neologisms', value: 'Neologisms' },
						{ label: '{{notability|Numbers}}: notability guideline for numbers', value: 'Numbers' },
						{ label: '{{notability|Products}}: notability guideline for products and services', value: 'Products' },
						{ label: '{{notability|Sports}}: notability guideline for sports and athletics', value: 'Sports' },
						{ label: '{{notability|Television}}: notability guideline for television shows', value: 'Television' },
						{ label: '{{notability|Web}}: notability guideline for web content', value: 'Web' }
					]
				};
				break;
			default:
				break;
		}
		return checkbox;
	};

	var makeCheckboxesForAlreadyPresentTags = function() {
		container.append({ type: 'header', id: 'tagHeader0', label: 'Tags already present' });
		var subdiv = container.append({ type: 'div', id: 'tagSubdiv0' });
		var checkboxes = [];
		var unCheckedTags = e.target.form.getUnchecked('alreadyPresentArticleTags') || [];
		Twinkle.tag.alreadyPresentTags.forEach(function(tag) {
			var description = Twinkle.tag.article.tags[tag];
			var checkbox =
				{
					value: tag,
					label: '{{' + tag + '}}' + (description ? ': ' + description : ''),
					checked: unCheckedTags.indexOf(tag) === -1,
					style: 'font-style: italic'
				};

			checkboxes.push(checkbox);
		});
		subdiv.append({
			type: 'checkbox',
			name: 'alreadyPresentArticleTags',
			list: checkboxes
		});
	};

	if (sortorder === 'cat') { // categorical sort order
		// function to iterate through the tags and create a checkbox for each one
		var doCategoryCheckboxes = function(subdiv, array) {
			var checkboxes = [];
			$.each(array, function(k, tag) {
				var description = Twinkle.tag.article.tags[tag];
				if (Twinkle.tag.alreadyPresentTags.indexOf(tag) === -1) {
					checkboxes.push(makeCheckbox(tag, description));
				}
			});
			subdiv.append({
				type: 'checkbox',
				name: 'articleTags',
				list: checkboxes
			});
		};

		if (Twinkle.tag.alreadyPresentTags.length > 0) {
			makeCheckboxesForAlreadyPresentTags();
		}
		var i = 1;
		// go through each category and sub-category and append lists of checkboxes
		$.each(Twinkle.tag.article.tagCategories, function(title, content) {
			container.append({ type: 'header', id: 'tagHeader' + i, label: title });
			var subdiv = container.append({ type: 'div', id: 'tagSubdiv' + i++ });
			if (Array.isArray(content)) {
				doCategoryCheckboxes(subdiv, content);
			} else {
				$.each(content, function(subtitle, subcontent) {
					subdiv.append({ type: 'div', label: [ Morebits.htmlNode('b', subtitle) ] });
					doCategoryCheckboxes(subdiv, subcontent);
				});
			}
		});
	} else { // alphabetical sort order
		if (Twinkle.tag.alreadyPresentTags.length > 0) {
			makeCheckboxesForAlreadyPresentTags();
			container.append({ type: 'header', id: 'tagHeader1', label: 'Available tags' });
		}
		var checkboxes = [];
		$.each(Twinkle.tag.article.tags, function(tag, description) {
			if (Twinkle.tag.alreadyPresentTags.indexOf(tag) === -1) {
				checkboxes.push(makeCheckbox(tag, description));
			}
		});
		container.append({
			type: 'checkbox',
			name: 'articleTags',
			list: checkboxes
		});
	}

	// append any custom tags
	if (Twinkle.getPref('customTagList').length) {
		container.append({ type: 'header', label: 'Custom tags' });
		container.append({ type: 'checkbox', name: 'articleTags',
			list: Twinkle.getPref('customTagList').map(function(el) {
				el.checked = Twinkle.tag.checkedTags.indexOf(el.value) !== -1;
				return el;
			})
		});
	}

	var $workarea = $(form).find('#tagWorkArea');
	var rendered = container.render();
	$workarea.empty().append(rendered);

	// for quick filter:
	$allCheckboxDivs = $workarea.find('[name$=Tags]').parent();
	$allHeaders = $workarea.find('h5, .quickformDescription');
	form.quickfilter.value = ''; // clear search, because the search results are not preserved over mode change
	form.quickfilter.focus();

	// style adjustments
	$workarea.find('h5').css({ 'font-size': '110%' });
	$workarea.find('h5:not(:first-child)').css({ 'margin-top': '1em' });
	$workarea.find('div').filter(':has(span.quickformDescription)').css({ 'margin-top': '0.4em' });

	var alreadyPresentTags = Morebits.quickForm.getElements(form, 'alreadyPresentArticleTags');
	if (alreadyPresentTags) {
		alreadyPresentTags.forEach(generateLinks);
	}
	// in the unlikely case that *every* tag is already on the page
	var notPresentTags = Morebits.quickForm.getElements(form, 'articleTags');
	if (notPresentTags) {
		notPresentTags.forEach(generateLinks);
	}

	// tally tags added/removed, update statusNode text
	var statusNode = document.getElementById('tw-tag-status');
	$('[name=articleTags], [name=alreadyPresentArticleTags]').click(function() {
		if (this.name === 'articleTags') {
			Twinkle.tag.status.numAdded += this.checked ? 1 : -1;
		} else if (this.name === 'alreadyPresentArticleTags') {
			Twinkle.tag.status.numRemoved += this.checked ? -1 : 1;
		}

		var firstPart = 'Adding ' + Twinkle.tag.status.numAdded + ' tag' + (Twinkle.tag.status.numAdded > 1 ? 's' : '');
		var secondPart = 'Removing ' + Twinkle.tag.status.numRemoved + ' tag' + (Twinkle.tag.status.numRemoved > 1 ? 's' : '');
		statusNode.textContent =
			(Twinkle.tag.status.numAdded ? '  ' + firstPart : '') +
			(Twinkle.tag.status.numRemoved ? (Twinkle.tag.status.numAdded ? '; ' : '  ') + secondPart : '');
	});
};

/**
 * Adds a link to each template's description page
 * @param {Morebits.quickForm.element} checkbox  associated with the template
 */
var generateLinks = function(checkbox) {
	var link = Morebits.htmlNode('a', '>');
	link.setAttribute('class', 'tag-template-link');
	var tagname = checkbox.values;
	link.setAttribute('href', mw.util.getUrl(
		(tagname.indexOf(':') === -1 ? 'Template:' : '') +
		(tagname.indexOf('|') === -1 ? tagname : tagname.slice(0, tagname.indexOf('|')))
	));
	link.setAttribute('target', '_blank');
	$(checkbox).parent().append(['\u00A0', link]);
};


// Tags for ARTICLES start here

Twinkle.tag.article = {};

// A list of all article tags, in alphabetical order
// To ensure tags appear in the default "categorized" view, add them to the tagCategories hash below.

Twinkle.tag.article.tags = {
	'Advert': 'written like an advertisement',
	'All plot': 'almost entirely a plot summary',
	'Autobiography': 'autobiography and may not be written neutrally',
	'BLP sources': 'BLP that needs additional sources for verification',
	'BLP unsourced': 'BLP that has no sources at all (use BLP PROD instead for new articles)',
	'Citation style': 'unclear or inconsistent citation style',
	'Cleanup': 'requires cleanup',
	'Cleanup bare URLs': 'uses bare URLs for references, which are prone to link rot',
	'Cleanup-PR': 'reads like a press release or news article',
	'Cleanup reorganize': "needs reorganization to comply with Wikipedia's layout guidelines",
	'Cleanup rewrite': "needs to be rewritten entirely to comply with Wikipedia's quality standards",
	'Cleanup tense': 'does not follow guidelines on use of different tenses.',
	'Close paraphrasing': 'contains close paraphrasing of a non-free copyrighted source',
	'COI': 'creator or major contributor may have a conflict of interest',
	'Condense': 'too many section headers dividing up content',
	'Confusing': 'confusing or unclear',
	'Context': 'insufficient context for those unfamiliar with the subject',
	'Copy edit': 'requires copy editing for grammar, style, cohesion, tone, or spelling',
	'Copypaste': 'appears to have been copied and pasted from another location',
	'Current': 'documents a current event',
	'Dead end': 'article has no links to other articles',
	'Disputed': 'questionable factual accuracy',
	'Essay-like': 'written like a personal reflection, personal essay, or argumentative essay',
	'Expand language': 'should be expanded with text translated from a foreign-language article',
	'Expert needed': 'needs attention from an expert on the subject',
	'External links': 'external links may not follow content policies or guidelines',
	'Fanpov': "written from a fan's point of view",
	'Fiction': 'fails to distinguish between fact and fiction',
	'Globalize': 'may not represent a worldwide view of the subject',
	'GOCEinuse': 'currently undergoing a major copy edit by the Guild of Copy Editors',
	'History merge': 'another page should be history merged into this one',
	'Hoax': 'may partially or completely be a hoax',
	'Improve categories': 'needs additional or more specific categories',
	'Incomprehensible': 'very hard to understand or incomprehensible',
	'In-universe': 'subject is fictional and needs rewriting to provide a non-fictional perspective',
	'In use': 'undergoing a major edit for a short while',
	'Lead missing': 'no lead section',
	'Lead rewrite': 'lead section needs to be rewritten to comply with guidelines',
	'Lead too long': 'lead section is too long for the length of the article',
	'Lead too short': 'lead section is too short and should be expanded to summarize key points',
	'Like resume': 'written like a resume',
	'Long plot': 'plot summary is too long or excessively detailed',
	'Manual': 'written like a manual or guidebook',
	'Merge': 'should be merged with another given article',
	'Merge from': 'another given article should be merged into this one',
	'Merge to': 'should be merged into another given article',
	'More citations needed': 'needs additional references or sources for verification',
	'More footnotes': 'has some references, but insufficient inline citations',
	'No footnotes': 'has references, but lacks inline citations',
	'No plot': 'needs a plot summary',
	'Non-free': 'may contain excessive or improper use of copyrighted materials',
	'Notability': 'subject may not meet the general notability guideline',
	'Not English': 'written in a language other than English and needs translation',
	'One source': 'relies largely or entirely on a single source',
	'Original research': 'contains original research',
	'Orphan': 'linked to from no other articles',
	'Over-coverage': 'extensive bias or disproportional coverage towards one or more specific regions',
	'Overlinked': 'too many duplicate and/or irrelevant links to other articles',
	'Overly detailed': 'excessive amount of intricate detail',
	'Over-quotation': 'too many or too-lengthy quotations for an encyclopedic entry',
	'Peacock': 'contains wording that promotes the subject in a subjective manner without adding information',
	'POV': 'does not maintain a neutral point of view',
	'Primary sources': 'relies too much on references to primary sources, and needs secondary sources',
	'Prose': 'written in a list format but may read better as prose',
	'Recentism': 'slanted towards recent events',
	'Rough translation': 'poor translation from another language',
	'Sections': 'needs to be divided into sections by topic',
	'Self-published': 'contains excessive or inappropriate references to self-published sources',
	'Sources exist': 'notable topic, sources are available that could be added to article',
	'Technical': 'too technical for most readers to understand',
	'Third-party': 'relies too heavily on sources too closely associated with the subject',
	'Tone': 'tone or style may not reflect the encyclopedic tone used on Wikipedia',
	'Too few opinions': 'may not include all significant viewpoints',
	'Uncategorized': 'not added to any categories',
	'Under construction': 'in the process of an expansion or major restructuring',
	'Underlinked': 'needs more wikilinks to other articles',
	'Undue weight': 'lends undue weight to certain ideas, incidents, or controversies',
	'Unfocused': 'lacks focus or is about more than one topic',
	'Unreferenced': 'does not cite any sources at all',
	'Unreliable sources': 'some references may not be reliable',
	'Undisclosed paid': 'may have been created or edited in return for undisclosed payments',
	'Update': 'needs additional up-to-date information added',
	'Very long': 'too long to read and navigate comfortably',
	'Weasel': 'neutrality or verifiability is compromised by the use of weasel words'
};

// A list of tags in order of category
// Tags should be in alphabetical order within the categories
// Add new categories with discretion - the list is long enough as is!

Twinkle.tag.article.tagCategories = {
	'Cleanup and maintenance tags': {
		'General cleanup': [
			'Cleanup',  // has a subgroup with text input
			'Cleanup rewrite',
			'Copy edit'  // has a subgroup with text input
		],
		'Potentially unwanted content': [
			'Close paraphrasing',
			'Copypaste',  // has a subgroup with text input
			'External links',
			'Non-free'
		],
		'Structure, formatting, and lead section': [
			'Cleanup reorganize',
			'Condense',
			'Lead missing',
			'Lead rewrite',
			'Lead too long',
			'Lead too short',
			'Sections',
			'Very long'
		],
		'Fiction-related cleanup': [
			'All plot',
			'Fiction',
			'In-universe',
			'Long plot',
			'No plot'
		]
	},
	'General content issues': {
		'Importance and notability': [
			'Notability'  // has a subgroup with subcategories
		],
		'Style of writing': [
			'Advert',
			'Cleanup tense',
			'Essay-like',
			'Fanpov',
			'Like resume',
			'Manual',
			'Cleanup-PR',
			'Over-quotation',
			'Prose',
			'Technical',
			'Tone'
		],
		'Sense (or lack thereof)': [
			'Confusing',
			'Incomprehensible',
			'Unfocused'
		],
		'Information and detail': [
			'Context',
			'Expert needed',
			'Overly detailed',
			'Undue weight'
		],
		'Timeliness': [
			'Current',
			'Update'
		],
		'Neutrality, bias, and factual accuracy': [
			'Autobiography',
			'COI',
			'Disputed',
			'Hoax',
			'Globalize',
			'Over-coverage',
			'Peacock',
			'POV',
			'Recentism',
			'Too few opinions',
			'Undisclosed paid',
			'Weasel'
		],
		'Verifiability and sources': [
			'BLP sources',
			'BLP unsourced',
			'More citations needed',
			'One source',
			'Original research',
			'Primary sources',
			'Self-published',
			'Sources exist',
			'Third-party',
			'Unreferenced',
			'Unreliable sources'
		]
	},
	'Specific content issues': {
		'Language': [
			'Not English',  // has a subgroup with several options
			'Rough translation',  // has a subgroup with several options
			'Expand language'
		],
		'Links': [
			'Dead end',
			'Orphan',
			'Overlinked',
			'Underlinked'
		],
		'Referencing technique': [
			'Citation style',
			'Cleanup bare URLs',
			'More footnotes',
			'No footnotes'
		],
		'Categories': [
			'Improve categories',
			'Uncategorized'
		]
	},
	'Merging': [
		'History merge',
		'Merge',   // these three have a subgroup with several options
		'Merge from',
		'Merge to'
	],
	'Informational': [
		'GOCEinuse',
		'In use',
		'Under construction'
	]
};

// Contains those article tags that *do not* work inside {{multiple issues}}.
Twinkle.tag.multipleIssuesExceptions = [
	'Copypaste',
	'Current', // Works but not intended for use in MI
	'Expand language',
	'GOCEinuse',
	'History merge',
	'Improve categories',
	'In use',
	'Merge',
	'Merge from',
	'Merge to',
	'Not English',
	'Rough translation',
	'Uncategorized',
	'Under construction'
];

// Tags for REDIRECTS start here

Twinkle.tag.spellingList = [
	{
		label: '{{R from acronym}}: redirect from an acronym (e.g. POTUS) to its expanded form',
		value: 'R from acronym'
	},
	{
		label: '{{R from alternative spelling}}: redirect from a title with a different spelling',
		value: 'R from alternative spelling'
	},
	{
		label: '{{R from initialism}}: redirect from an initialism (e.g. AGF) to its expanded form',
		value: 'R from initialism'
	},
	{
		label: '{{R from ASCII-only}}: redirect from a title in only basic ASCII to the formal article title, with differences that are not diacritical marks (accents, umlauts, etc.) or ligatures',
		value: 'R from ASCII-only'
	},
	{
		label: '{{R from member}}: redirect from a member of a group to a related topic such as the group, organization, or team of membership',
		value: 'R from member'
	},
	{
		label: '{{R from misspelling}}: redirect from a misspelling or typographical error',
		value: 'R from misspelling'
	},
	{
		label: '{{R from modification}}: redirect from a modification of the target\'s title, such as with words rearranged',
		value: 'R from modification'
	},
	{
		label: '{{R from other capitalisation}}: redirect from a title with another method of capitalisation',
		value: 'R from other capitalisation'
	},
	{
		label: '{{R from plural}}: redirect from a plural word to the singular equivalent',
		value: 'R from plural'
	},
	{
		label: '{{R from related word}}: redirect from a related word',
		value: 'R from related word'
	},
	{
		label: '{{R to list entry}}: redirect to a "list of minor entities"-type article which contains brief descriptions of subjects not notable enough to have separate articles',
		value: 'R to list entry'
	},
	{
		label: '{{R to section}}: similar to {{R to list entry}}, but when list is organized in sections, such as list of characters in a fictional universe.',
		value: 'R to section'
	},
	{
		label: '{{R with possibilities}}: redirect from a more specific title to a more general, less detailed article, hence something which can and should be expanded',
		value: 'R with possibilities'
	}
];

Twinkle.tag.alternativeList = [
	{
		label: '{{R from alternative language}}: redirect from an English name to a name in another language, or vice-versa',
		value: 'R from alternative language',
		subgroup: [
			{
				name: 'altLangFrom',
				type: 'input',
				label: 'From language (two-letter code): ',
				tooltip: 'Enter the two-letter code of the language the redirect name is in; such as en for English, de for German'
			},
			{
				name: 'altLangTo',
				type: 'input',
				label: 'To language (two-letter code): ',
				tooltip: 'Enter the two-letter code of the language the target name is in; such as en for English, de for German'
			},
			{
				name: 'altLangInfo',
				type: 'div',
				label: $.parseHTML('<p>For a list of language codes, see <a href="/wiki/Wp:Template_messages/Redirect_language_codes">Wikipedia:Template messages/Redirect language codes</a></p>')
			}
		]
	},
	{
		label: '{{R from alternative name}}: redirect from a title that is another name, a pseudonym, a nickname, or a synonym',
		value: 'R from alternative name'
	},
	{
		label: '{{R from former name}}: redirect from a former name or working title',
		value: 'R from former name'
	},
	{
		label: '{{R from historic name}}: redirect from another name with a significant historic past as a region, state, city or such, but which is no longer known by that title or name',
		value: 'R from historic name'
	},
	{
		label: '{{R from incorrect name}}: redirect from an erroneus name that is unsuitable as a title',
		value: 'R from incorrect name'
	},
	{
		label: '{{R from long name}}: redirect from a title that is a complete or more complete name',
		value: 'R from long name'
	},
	{
		label: '{{R from molecular formula}}: redirect from a molecular/chemical formula to its technical or trivial name',
		value: 'R from molecular formula'
	},
	{
		label: '{{R from name and country}}: redirect from the specific name to the briefer name',
		value: 'R from name and country'
	},
	{
		label: '{{R from phrase}}: redirect from a phrase to a more general relevant article covering the topic',
		value: 'R from phrase'
	},
	{
		label: '{{R from scientific name}}: redirect from the scientific name to the common name',
		value: 'R from scientific name'
	},
	{
		label: '{{R from short name}}: redirect from a title that is a shortened form of a person\'s full name, a book title, or other more complete title',
		value: 'R from short name'
	},
	{
		label: '{{R from subtopic}}: redirect from a title that is a subtopic of the target article',
		value: 'R from subtopic'
	},
	{
		label: '{{R from surname}}: redirect from a title that is a surname',
		value: 'R from surname'
	},
	{
		label: '{{R to diacritic}}: redirect to the article title with diacritical marks (accents, umlauts, etc.)',
		value: 'R to diacritic'
	},
	{
		label: '{{R to related topic}}: redirect to an article about a similar topic',
		value: 'R to related topic'
	},
	{
		label: '{{R to scientific name}}: redirect from the common name to the scientific name',
		value: 'R to scientific name'
	}
];

Twinkle.tag.administrativeList = [
	{
		label: '{{R from ambiguous term}}: redirect from an ambiguous page name to a page that disambiguates it. This template should never appear on a page that has "(disambiguation)" in its title, use R to disambiguation page instead',
		value: 'R from ambiguous term'
	},
	{
		label: '{{R from CamelCase}}: redirect from a CamelCase title',
		value: 'R from CamelCase'
	},
	{
		label: '{{R to decade}}: redirect from a year to the decade article',
		value: 'R to decade'
	},
	{
		label: '{{R to disambiguation page}}: redirect to a disambiguation page',
		value: 'R to disambiguation page'
	},
	{
		label: '{{R from duplicated article}}: redirect to a similar article in order to preserve its edit history',
		value: 'R from duplicated article'
	},
	{
		label: '{{R from file metadata link}}: redirect of a wikilink created from EXIF, XMP, or other information (i.e. the "metadata" section on some image description pages)',
		value: 'R from file metadata link'
	},
	{
		label: '{{R with history}}: redirect from a page containing substantive page history, kept to preserve content and attributions',
		value: 'R with history'
	},
	{
		label: '{{R from incomplete disambiguation}}: redirect from a page name that is too ambiguous to be the title of an article and should redirect to an appropriate disambiguation page',
		value: 'R from incomplete disambiguation'
	},
	{
		label: '{{R from merge}}: redirect from a merged page in order to preserve its edit history',
		value: 'R from merge'
	},
	{
		label: '{{R from other disambiguation}}: redirect from a page name with an alternative disambiguation qualifier',
		value: 'R from other disambiguation'
	},
	{
		label: '{{R printworthy}}: redirect from a title that would be helpful in a printed or CD/DVD version of Wikipedia',
		value: 'R printworthy'
	},
	{
		label: '{{R from school}}: redirect from a school article that had very little information',
		value: 'R from school'
	},
	{
		label: '{{R from shortcut}}: redirect from a Wikipedia shortcut',
		value: 'R from shortcut'
	},
	{
		label: '{{R from sort name}}: redirect from the target\'s sort name, such as beginning with their surname rather than given name',
		value: 'R from sort name'
	},
	{
		label: '{{R unprintworthy}}: redirect from a title that would NOT be helpful in a printed or CD/DVD version of Wikipedia',
		value: 'R unprintworthy'
	}
];

// maintenance tags for FILES start here

Twinkle.tag.file = {};

Twinkle.tag.file.licenseList = [
	{ label: '{{Bsr}}: source info consists of bare image URL/generic base URL only', value: 'Bsr' },
	{ label: '{{Non-free reduce}}: non-low-resolution fair use image (or too-long audio clip, etc)', value: 'Non-free reduce' },
	{ label: '{{Orphaned non-free revisions}}: fair use media with old revisions that need to be deleted', value: 'subst:orfurrev' }
];

Twinkle.tag.file.commonsList = [
	{ label: '{{Copy to Commons}}: free media that should be copied to Commons', value: 'Copy to Commons' },
	{ label: '{{Do not move to Commons}} (PD issue): file is PD in the US but not in country of origin', value: 'Do not move to Commons' },
	{
		label: '{{Do not move to Commons}} (other reason)',
		value: 'Do not move to Commons_reason',
		subgroup: {
			type: 'input',
			name: 'DoNotMoveToCommons',
			label: 'Reason: ',
			tooltip: 'Enter the reason why this image should not be moved to Commons (required)'
		}
	},
	{
		label: '{{Keep local}}: request to keep local copy of a Commons file',
		value: 'Keep local',
		subgroup: {
			type: 'input',
			name: 'keeplocalName',
			label: 'Commons image name if different: ',
			tooltip: 'Name of the image on Commons (if different from local name), excluding the File: prefix:'
		}
	},
	{
		label: '{{Now Commons}}: file has been copied to Commons',
		value: 'subst:ncd',
		subgroup: {
			type: 'input',
			name: 'ncdName',
			label: 'Commons image name if different: ',
			tooltip: 'Name of the image on Commons (if different from local name), excluding the File: prefix:'
		}
	}
];

Twinkle.tag.file.cleanupList = [
	{ label: '{{Artifacts}}: PNG contains residual compression artifacts', value: 'Artifacts' },
	{ label: '{{Bad font}}: SVG uses fonts not available on the thumbnail server', value: 'Bad font' },
	{ label: '{{Bad format}}: PDF/DOC/... file should be converted to a more useful format', value: 'Bad format' },
	{ label: '{{Bad GIF}}: GIF that should be PNG, JPEG, or SVG', value: 'Bad GIF' },
	{ label: '{{Bad JPEG}}: JPEG that should be PNG or SVG', value: 'Bad JPEG' },
	{ label: '{{Bad SVG}}: SVG containing raster grahpics', value: 'Bad SVG' },
	{ label: '{{Bad trace}}: auto-traced SVG requiring cleanup', value: 'Bad trace' },
	{
		label: '{{Cleanup image}}: general cleanup', value: 'Cleanup image',
		subgroup: {
			type: 'input',
			name: 'cleanupimageReason',
			label: 'Reason: ',
			tooltip: 'Enter the reason for cleanup (required)'
		}
	},
	{ label: '{{ClearType}}: image (not screenshot) with ClearType anti-aliasing', value: 'ClearType' },
	{ label: '{{Imagewatermark}}: image contains visible or invisible watermarking', value: 'Imagewatermark' },
	{ label: '{{NoCoins}}: image using coins to indicate scale', value: 'NoCoins' },
	{ label: '{{Overcompressed JPEG}}: JPEG with high levels of artifacts', value: 'Overcompressed JPEG' },
	{ label: '{{Opaque}}: opaque background should be transparent', value: 'Opaque' },
	{ label: '{{Remove border}}: unneeded border, white space, etc.', value: 'Remove border' },
	{
		label: '{{Rename media}}: file should be renamed according to the criteria at [[WP:FMV]]',
		value: 'Rename media',
		subgroup: [
			{
				type: 'input',
				name: 'renamemediaNewname',
				label: 'New name: ',
				tooltip: 'Enter the new name for the image (optional)'
			},
			{
				type: 'input',
				name: 'renamemediaReason',
				label: 'Reason: ',
				tooltip: 'Enter the reason for the rename (optional)'
			}
		]
	},
	{ label: '{{Should be PNG}}: GIF or JPEG should be lossless', value: 'Should be PNG' },
	{
		label: '{{Should be SVG}}: PNG, GIF or JPEG should be vector graphics', value: 'Should be SVG',
		subgroup: {
			name: 'svgCategory',
			type: 'select',
			list: [
				{ label: '{{Should be SVG|other}}', value: 'other' },
				{ label: '{{Should be SVG|alphabet}}: character images, font examples, etc.', value: 'alphabet' },
				{ label: '{{Should be SVG|chemical}}: chemical diagrams, etc.', value: 'chemical' },
				{ label: '{{Should be SVG|circuit}}: electronic circuit diagrams, etc.', value: 'circuit' },
				{ label: '{{Should be SVG|coat of arms}}: coats of arms', value: 'coat of arms' },
				{ label: '{{Should be SVG|diagram}}: diagrams that do not fit any other subcategory', value: 'diagram' },
				{ label: '{{Should be SVG|emblem}}: emblems, free/libre logos, insignias, etc.', value: 'emblem' },
				{ label: '{{Should be SVG|fair use}}: fair-use images, fair-use logos', value: 'fair use' },
				{ label: '{{Should be SVG|flag}}: flags', value: 'flag' },
				{ label: '{{Should be SVG|graph}}: visual plots of data', value: 'graph' },
				{ label: '{{Should be SVG|logo}}: logos', value: 'logo' },
				{ label: '{{Should be SVG|map}}: maps', value: 'map' },
				{ label: '{{Should be SVG|music}}: musical scales, notes, etc.', value: 'music' },
				{ label: '{{Should be SVG|physical}}: "realistic" images of physical objects, people, etc.', value: 'physical' },
				{ label: '{{Should be SVG|symbol}}: miscellaneous symbols, icons, etc.', value: 'symbol' }
			]
		}
	},
	{ label: '{{Should be text}}: image should be represented as text, tables, or math markup', value: 'Should be text' }
];

Twinkle.tag.file.qualityList = [
	{ label: '{{Image-blownout}}', value: 'Image-blownout' },
	{ label: '{{Image-out-of-focus}}', value: 'Image-out-of-focus' },
	{
		label: '{{Image-Poor-Quality}}', value: 'Image-Poor-Quality',
		subgroup: {
			type: 'input',
			name: 'ImagePoorQualityReason',
			label: 'Reason: ',
			tooltip: 'Enter the reason why this image is so bad (required)'
		}
	},
	{ label: '{{Image-underexposure}}', value: 'Image-underexposure' },
	{
		label: '{{Low quality chem}}: disputed chemical structures', value: 'Low quality chem',
		subgroup: {
			type: 'input',
			name: 'lowQualityChemReason',
			label: 'Reason: ',
			tooltip: 'Enter the reason why the diagram is disputed (required)'
		}
	}
];

Twinkle.tag.file.replacementList = [
	{ label: '{{Obsolete}}: improved version available', value: 'Obsolete' },
	{ label: '{{PNG version available}}', value: 'PNG version available' },
	{ label: '{{Vector version available}}', value: 'Vector version available' }
];
Twinkle.tag.file.replacementList.forEach(function(el) {
	el.subgroup = {
		type: 'input',
		label: 'Replacement file: ',
		tooltip: 'Enter the name of the file which replaces this one (required)',
		name: el.value.replace(/ /g, '_') + 'File'
	};
});


Twinkle.tag.callbacks = {
	article: function articleCallback(pageobj) {

		// Remove tags that become superfluous with this action
		var pageText = pageobj.getPageText().replace(/\{\{\s*([Uu]serspace draft)\s*(\|(?:\{\{[^{}]*\}\}|[^{}])*)?\}\}\s*/g, '');
		var params = pageobj.getCallbackParameters();

		/**
		 * Saves the page following the removal of tags if any. The last step.
		 * Called from removeTags()
		 */
		var postRemoval = function() {
			if (params.tagsToRemove.length) {
				// Remove empty {{multiple issues}} if found
				pageText = pageText.replace(/\{\{(multiple ?issues|article ?issues|mi)\s*\|\s*\}\}\n?/im, '');
				// Remove single-element {{multiple issues}} if found
				pageText = pageText.replace(/\{\{(?:multiple ?issues|article ?issues|mi)\s*\|\s*(\{\{[^}]+\}\})\s*\}\}/im, '$1');
			}

			// Build edit summary
			var makeSentence = function(array) {
				if (array.length < 3) {
					return array.join(' and ');
				}
				var last = array.pop();
				return array.join(', ') + ', and ' + last;
			};
			var makeTemplateLink = function(tag) {
				var text = '{{[[';
				// if it is a custom tag with a parameter
				if (tag.indexOf('|') !== -1) {
					tag = tag.slice(0, tag.indexOf('|'));
				}
				text += tag.indexOf(':') !== -1 ? tag : 'Template:' + tag + '|' + tag;
				return text + ']]}}';
			};

			var summaryText;
			var addedTags = params.tags.map(makeTemplateLink);
			var removedTags = params.tagsToRemove.map(makeTemplateLink);
			if (addedTags.length) {
				summaryText = 'Added ' + makeSentence(addedTags);
				summaryText += removedTags.length ? '; and removed ' + makeSentence(removedTags) : '';
			} else {
				summaryText = 'Removed ' + makeSentence(removedTags);
			}
			summaryText += ' tag' + (addedTags.length + removedTags.length > 1 ? 's' : '');
			if (params.reason) {
				summaryText += ': ' + params.reason;
			}

			// avoid truncated summaries
			if (summaryText.length > (499 - Twinkle.getPref('summaryAd').length)) {
				summaryText = summaryText.replace(/\[\[[^|]+\|([^\]]+)\]\]/g, '$1');
			}

			pageobj.setPageText(pageText);
			pageobj.setEditSummary(summaryText + Twinkle.getPref('summaryAd'));
			pageobj.setWatchlist(Twinkle.getPref('watchTaggedPages'));
			pageobj.setMinorEdit(Twinkle.getPref('markTaggedPagesAsMinor'));
			pageobj.setCreateOption('nocreate');
			pageobj.save(function() {
				// special functions for merge tags
				if (params.mergeReason) {
					// post the rationale on the talk page (only operates in main namespace)
					var talkpageText = '\n\n== ' + params.talkDiscussionTitleLinked + ' ==\n\n';
					talkpageText += params.mergeReason.trim() + ' ~~~~';
					var talkpage = new Morebits.wiki.page('Talk:' + params.discussArticle, 'Posting rationale on talk page');
					talkpage.setAppendText(talkpageText);
					talkpage.setEditSummary('/* ' + params.talkDiscussionTitle + ' */ new section' + Twinkle.getPref('summaryAd'));
					talkpage.setWatchlist(Twinkle.getPref('watchMergeDiscussions'));
					talkpage.setCreateOption('recreate');
					talkpage.append();
				}
				if (params.mergeTagOther) {
					// tag the target page if requested
					var otherTagName = 'Merge';
					if (params.mergeTag === 'Merge from') {
						otherTagName = 'Merge to';
					} else if (params.mergeTag === 'Merge to') {
						otherTagName = 'Merge from';
					}
					var newParams = {
						tags: [otherTagName],
						tagsToRemove: [],
						tagsToRemain: [],
						mergeTarget: Morebits.pageNameNorm,
						discussArticle: params.discussArticle,
						talkDiscussionTitle: params.talkDiscussionTitle,
						talkDiscussionTitleLinked: params.talkDiscussionTitleLinked
					};
					var otherpage = new Morebits.wiki.page(params.mergeTarget, 'Tagging other page (' +
						params.mergeTarget + ')');
					otherpage.setCallbackParameters(newParams);
					otherpage.load(Twinkle.tag.callbacks.article);
				}

				// post at WP:PNT for {{not English}} and {{rough translation}} tag
				if (params.translationPostAtPNT) {
					var pntPage = new Morebits.wiki.page('Wikipedia:Pages needing translation into English',
						'Listing article at Wikipedia:Pages needing translation into English');
					pntPage.setFollowRedirect(true);
					pntPage.load(function friendlytagCallbacksTranslationListPage(pageobj) {
						var old_text = pageobj.getPageText();

						var template = params.tags.indexOf('Rough translation') !== -1 ? 'duflu' : 'needtrans';
						var lang = params.translationLanguage;
						var reason = params.translationComments;

						var templateText = '{{subst:' + template + '|pg=' + Morebits.pageNameNorm + '|Language=' +
							(lang || 'uncertain') + '|Comments=' + reason.trim() + '}} ~~~~';

						var text, summary;
						if (template === 'duflu') {
							text = old_text + '\n\n' + templateText;
							summary = 'Translation cleanup requested on ';
						} else {
							text = old_text.replace(/\n+(==\s?Translated pages that could still use some cleanup\s?==)/,
								'\n\n' + templateText + '\n\n$1');
							summary = 'Translation' + (lang ? ' from ' + lang : '') + ' requested on ';
						}

						if (text === old_text) {
							pageobj.getStatusElement().error('failed to find target spot for the discussion');
							return;
						}
						pageobj.setPageText(text);
						pageobj.setEditSummary(summary + ' [[:' + Morebits.pageNameNorm + ']]' + Twinkle.getPref('summaryAd'));
						pageobj.setCreateOption('recreate');
						pageobj.save();
					});
				}
				if (params.translationNotify) {
					pageobj.lookupCreation(function(innerPageobj) {
						var initialContrib = innerPageobj.getCreator();

						// Disallow warning yourself
						if (initialContrib === mw.config.get('wgUserName')) {
							innerPageobj.getStatusElement().warn('You (' + initialContrib + ') created this page; skipping user notification');
							return;
						}

						var userTalkPage = new Morebits.wiki.page('User talk:' + initialContrib,
							'Notifying initial contributor (' + initialContrib + ')');
						var notifytext = '\n\n== Your article [[' + Morebits.pageNameNorm + ']]==\n' +
							'{{subst:uw-notenglish|1=' + Morebits.pageNameNorm +
							(params.translationPostAtPNT ? '' : '|nopnt=yes') + '}} ~~~~';
						userTalkPage.setAppendText(notifytext);
						userTalkPage.setEditSummary('Notice: Please use English when contributing to the English Wikipedia.' +
							Twinkle.getPref('summaryAd'));
						userTalkPage.setCreateOption('recreate');
						userTalkPage.setFollowRedirect(true);
						userTalkPage.append();
					});
				}
			});

			if (params.patrol) {
				pageobj.triage();
			}
		};

		/**
		 * Removes the existing tags that were deselected (if any)
		 * Calls postRemoval() when done
		 */
		var removeTags = function removeTags() {

			if (params.tagsToRemove.length === 0) {
				postRemoval();
				return;
			}

			Morebits.status.info('Info', 'Removing deselected tags that were already present');

			var getRedirectsFor = [];

			// Remove the tags from the page text, if found in its proper name,
			// otherwise moves it to `getRedirectsFor` array earmarking it for
			// later removal
			params.tagsToRemove.forEach(function removeTag(tag) {
				var tag_re = new RegExp('\\{\\{' + Morebits.pageNameRegex(tag) + '\\s*(\\|[^}]+)?\\}\\}\\n?');

				if (tag_re.test(pageText)) {
					pageText = pageText.replace(tag_re, '');
				} else {
					getRedirectsFor.push('Template:' + tag);
				}
			});

			if (!getRedirectsFor.length) {
				postRemoval();
				return;
			}

			// Remove tags which appear in page text as redirects
			var api = new Morebits.wiki.api('Getting template redirects', {
				'action': 'query',
				'prop': 'linkshere',
				'titles': getRedirectsFor.join('|'),
				'redirects': 1,  // follow redirect if the class name turns out to be a redirect page
				'lhnamespace': '10',  // template namespace only
				'lhshow': 'redirect',
				'lhlimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
			}, function removeRedirectTag(apiobj) {

				$(apiobj.responseXML).find('page').each(function(idx, page) {
					var removed = false;
					$(page).find('lh').each(function(idx, el) {
						var tag = $(el).attr('title').slice(9);
						var tag_re = new RegExp('\\{\\{' + Morebits.pageNameRegex(tag) + '\\s*(\\|[^}]*)?\\}\\}\\n?');
						if (tag_re.test(pageText)) {
							pageText = pageText.replace(tag_re, '');
							removed = true;
							return false;   // break out of $.each
						}
					});
					if (!removed) {
						Morebits.status.warn('Info', 'Failed to find {{' +
						$(page).attr('title').slice(9) + '}} on the page... excluding');
					}

				});

				postRemoval();

			});
			api.post();

		};

		if (!params.tags.length) {
			removeTags();
			return;
		}

		var tagRe, tagText = '', tags = [], groupableTags = [], groupableExistingTags = [];
		// Executes first: addition of selected tags

		/**
		 * Updates `tagText` with the syntax of `tagName` template with its parameters
		 * @param {number} tagIndex
		 * @param {string} tagName
		 */
		var addTag = function articleAddTag(tagIndex, tagName) {
			var currentTag = '';
			if (tagName === 'Uncategorized' || tagName === 'Improve categories') {
				pageText += '\n\n{{' + tagName + '|date={{subst:CURRENTMONTHNAME}} {{subst:CURRENTYEAR}}}}';
			} else {
				currentTag += '{{' + tagName;
				// fill in other parameters, based on the tag
				switch (tagName) {
					case 'Cleanup':
						currentTag += '|reason=' + params.cleanup;
						break;
					case 'Close paraphrasing':
						currentTag += '|source=' + params.closeParaphrasing;
						break;
					case 'Copy edit':
						if (params.copyEdit) {
							currentTag += '|for=' + params.copyEdit;
						}
						break;
					case 'Copypaste':
						if (params.copypaste) {
							currentTag += '|url=' + params.copypaste;
						}
						break;
					case 'Expand language':
						currentTag += '|topic=';
						currentTag += '|langcode=' + params.expandLanguageLangCode;
						if (params.expandLanguageArticle !== null) {
							currentTag += '|otherarticle=' + params.expandLanguageArticle;
						}
						break;
					case 'Expert needed':
						if (params.expertNeeded) {
							currentTag += '|1=' + params.expertNeeded;
						}
						if (params.expertNeededTalk) {
							currentTag += '|talk=' + params.expertNeededTalk;
						}
						if (params.expertNeededReason) {
							currentTag += '|reason=' + params.expertNeededReason;
						}
						break;
					case 'Globalize':
						currentTag += '|1=article';
						if (params.globalizeRegion) {
							currentTag += '|2=' + params.globalizeRegion;
						}
						break;
					case 'News release':
						currentTag += '|1=article';
						break;
					case 'Notability':
						if (params.notability !== 'none') {
							currentTag += '|' + params.notability;
						}
						break;
					case 'Not English':
					case 'Rough translation':
						if (params.translationLanguage) {
							currentTag += '|1=' + params.translationLanguage;
						}
						if (params.translationPostAtPNT) {
							currentTag += '|listed=yes';
						}
						break;
					case 'History merge':
						currentTag += '|originalpage=' + params.histmergeOriginalPage;
						if (params.histmergeReason) {
							currentTag += '|reason=' + params.histmergeReason;
						}
						if (params.histmergeSysopDetails) {
							currentTag += '|details=' + params.histmergeSysopDetails;
						}
						break;
					case 'Merge':
					case 'Merge to':
					case 'Merge from':
						params.mergeTag = tagName;
						// normalize the merge target for now and later
						params.mergeTarget = Morebits.string.toUpperCaseFirstChar(params.mergeTarget.replace(/_/g, ' '));

						currentTag += '|' + params.mergeTarget;

						// link to the correct section on the talk page, for article space only
						if (mw.config.get('wgNamespaceNumber') === 0 && (params.mergeReason || params.discussArticle)) {
							if (!params.discussArticle) {
								// discussArticle is the article whose talk page will contain the discussion
								params.discussArticle = tagName === 'Merge to' ? params.mergeTarget : mw.config.get('wgTitle');
								// nonDiscussArticle is the article which won't have the discussion
								params.nonDiscussArticle = tagName === 'Merge to' ? mw.config.get('wgTitle') : params.mergeTarget;
								var direction = '[[' + params.nonDiscussArticle + ']]' + (params.mergeTag === 'Merge' ? ' with ' : ' into ') + '[[' + params.discussArticle + ']]';
								params.talkDiscussionTitleLinked = 'Proposed merge of ' + direction;
								params.talkDiscussionTitle = params.talkDiscussionTitleLinked.replace(/\[\[(.*?)\]\]/g, '$1');
							}
							currentTag += '|discuss=Talk:' + params.discussArticle + '#' + params.talkDiscussionTitle;
						}
						break;
					default:
						break;
				}

				currentTag += '|date={{subst:CURRENTMONTHNAME}} {{subst:CURRENTYEAR}}}}\n';
				tagText += currentTag;
			}
		};

		/**
		 * Adds the tags which go outside {{multiple issues}}, either because
		 * these tags aren't supported in {{multiple issues}} or because
		 * {{multiple issues}} is not being added to the page at all
		 */
		var addUngroupedTags = function() {
			$.each(tags, addTag);

			// Smartly insert the new tags after any hatnotes or
			// afd, csd, or prod templates or hatnotes. Regex is
			// extra complicated to allow for templates with
			// parameters and to handle whitespace properly.
			pageText = pageText.replace(
				new RegExp(
					// leading whitespace
					'^\\s*' +
					// capture template(s)
					'(?:((?:\\s*' +
					// AfD is special, as the tag includes html comments before and after the actual template
					'(?:<!--.*AfD.*\\n\\{\\{(?:Article for deletion\\/dated|AfDM).*\\}\\}\\n<!--.*(?:\\n<!--.*)?AfD.*(?:\\s*\\n))?|' + // trailing whitespace/newline needed since this subst's a newline
					// begin template format
					'\\{\\{\\s*(?:' +
					// CSD
					'db|delete|db-.*?|speedy deletion-.*?|' +
					// PROD
					'(?:proposed deletion|prod blp)\\/dated(?:\\s*\\|(?:concern|user|timestamp|help).*)+|' +
					// various hatnote templates
					'about|correct title|dablink|distinguish|for|other\\s?(?:hurricaneuses|people|persons|places|uses(?:of)?)|redirect(?:-acronym)?|see\\s?(?:also|wiktionary)|selfref|short description|the' +
					// not a hatnote, but sometimes under a CSD or AfD
					'|salt|proposed deletion endorsed' +
					// end main template name, optionally with a number (such as redirect2)
					')\\d*\\s*' +
					// template parameters
					'(\\|(?:\\{\\{[^{}]*\\}\\}|[^{}])*)?' +
					// end template format
					'\\}\\})+' +
					// end capture
					'(?:\\s*\\n)?)' +
					// trailing whitespace
					'\\s*)?',
					'i'), '$1' + tagText
			);

			removeTags();
		};

		// Separate tags into groupable ones (`groupableTags`) and non-groupable ones (`tags`)
		params.tags.forEach(function(tag) {
			tagRe = new RegExp('\\{\\{' + tag + '(\\||\\}\\})', 'im');
			// regex check for preexistence of tag can be skipped if in canRemove mode
			if (Twinkle.tag.canRemove || !tagRe.exec(pageText)) {
				// condition Twinkle.tag.article.tags[tag] to ensure that its not a custom tag
				// Custom tags are assumed non-groupable, since we don't know whether MI template supports them
				if (Twinkle.tag.article.tags[tag] && Twinkle.tag.multipleIssuesExceptions.indexOf(tag) === -1) {
					groupableTags.push(tag);
				} else {
					tags.push(tag);
				}
			} else {
				if (tag === 'Merge from' || tag === 'History merge') {
					tags.push(tag);
				} else {
					Morebits.status.warn('Info', 'Found {{' + tag +
						'}} on the article already...excluding');
					// don't do anything else with merge tags
					if (['Merge', 'Merge to'].indexOf(tag) !== -1) {
						params.mergeTarget = params.mergeReason = params.mergeTagOther = null;
					}
				}
			}
		});

		// To-be-retained existing tags that are groupable
		params.tagsToRemain.forEach(function(tag) {
			if (Twinkle.tag.multipleIssuesExceptions.indexOf(tag) === -1) {
				groupableExistingTags.push(tag);
			}
		});

		var miTest = /\{\{(multiple ?issues|article ?issues|mi)(?!\s*\|\s*section\s*=)[^}]+\{/im.exec(pageText);

		if (miTest && groupableTags.length > 0) {
			Morebits.status.info('Info', 'Adding supported tags inside existing {{multiple issues}} tag');

			tagText = '';
			$.each(groupableTags, addTag);

			var miRegex = new RegExp('(\\{\\{\\s*' + miTest[1] + '\\s*(?:\\|(?:\\{\\{[^{}]*\\}\\}|[^{}])*)?)\\}\\}\\s*', 'im');
			pageText = pageText.replace(miRegex, '$1' + tagText + '}}\n');
			tagText = '';

			addUngroupedTags();

		} else if (params.group && !miTest && (groupableExistingTags.length + groupableTags.length) >= 2) {
			Morebits.status.info('Info', 'Grouping supported tags inside {{multiple issues}}');

			tagText += '{{Multiple issues|\n';

			/**
			 * Adds newly added tags to MI
			 */
			var addNewTagsToMI = function() {
				$.each(groupableTags, addTag);
				tagText += '}}\n';

				addUngroupedTags();
			};


			var getRedirectsFor = [];

			// Reposition the tags on the page into {{multiple issues}}, if found with its
			// proper name, else moves it to `getRedirectsFor` array to be handled later
			groupableExistingTags.forEach(function repositionTagIntoMI(tag) {
				var tag_re = new RegExp('(\\{\\{' + Morebits.pageNameRegex(tag) + '\\s*(\\|[^}]+)?\\}\\}\\n?)');
				if (tag_re.test(pageText)) {
					tagText += tag_re.exec(pageText)[1];
					pageText = pageText.replace(tag_re, '');
				} else {
					getRedirectsFor.push('Template:' + tag);
				}
			});

			if (!getRedirectsFor.length) {
				addNewTagsToMI();
				return;
			}

			var api = new Morebits.wiki.api('Getting template redirects', {
				'action': 'query',
				'prop': 'linkshere',
				'titles': getRedirectsFor.join('|'),
				'redirects': 1,
				'lhnamespace': '10', // template namespace only
				'lhshow': 'redirect',
				'lhlimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
			}, function replaceRedirectTag(apiobj) {
				$(apiobj.responseXML).find('page').each(function(idx, page) {
					var found = false;
					$(page).find('lh').each(function(idx, el) {
						var tag = $(el).attr('title').slice(9);
						var tag_re = new RegExp('(\\{\\{' + Morebits.pageNameRegex(tag) + '\\s*(\\|[^}]*)?\\}\\}\\n?)');
						if (tag_re.test(pageText)) {
							tagText += tag_re.exec(pageText)[1];
							pageText = pageText.replace(tag_re, '');
							found = true;
							return false;   // break out of $.each
						}
					});
					if (!found) {
						Morebits.status.warn('Info', 'Failed to find the existing {{' +
						$(page).attr('title').slice(9) + '}} on the page... skip repositioning');
					}
				});
				addNewTagsToMI();
			});
			api.post();

		} else {
			tags = tags.concat(groupableTags);
			addUngroupedTags();
		}
	},

	redirect: function redirect(pageobj) {
		var params = pageobj.getCallbackParameters(),
			pageText = pageobj.getPageText(),
			tagRe, tagText = '', summaryText = 'Added',
			tags = [], i;

		for (i = 0; i < params.tags.length; i++) {
			tagRe = new RegExp('(\\{\\{' + params.tags[i] + '(\\||\\}\\}))', 'im');
			if (!tagRe.exec(pageText)) {
				tags.push(params.tags[i]);
			} else {
				Morebits.status.warn('Info', 'Found {{' + params.tags[i] +
					'}} on the redirect already...excluding');
			}
		}

		var addTag = function redirectAddTag(tagIndex, tagName) {
			tagText += '\n{{' + tagName;
			if (tagName === 'R from alternative language') {
				if (params.altLangFrom) {
					tagText += '|from=' + params.altLangFrom;
				}
				if (params.altLangTo) {
					tagText += '|to=' + params.altLangTo;
				}
			}
			tagText += '}}';

			if (tagIndex > 0) {
				if (tagIndex === (tags.length - 1)) {
					summaryText += ' and';
				} else if (tagIndex < (tags.length - 1)) {
					summaryText += ',';
				}
			}

			summaryText += ' {{[[:' + (tagName.indexOf(':') !== -1 ? tagName : 'Template:' + tagName + '|' + tagName) + ']]}}';
		};

		tags.sort();
		$.each(tags, addTag);

		// Check for all Rcat shell redirects (from #433)
		if (pageText.match(/{{(?:redr|this is a redirect|r(?:edirect)?(?:.?cat.*)?[ _]?sh)/i)) {
			// Regex inspired by [[User:Kephir/gadgets/sagittarius.js]] ([[Special:PermaLink/831402893]])
			var oldTags = pageText.match(/(\s*{{[A-Za-z ]+\|)((?:[^|{}]*|{{[^}]*}})+)(}})\s*/i);
			pageText = pageText.replace(oldTags[0], oldTags[1] + tagText + oldTags[2] + oldTags[3]);
		} else {
			// Fold any pre-existing Rcats into taglist and under Rcatshell
			var pageTags = pageText.match(/\n{{R(?:edirect)? .*?}}/img);
			var oldPageTags = '';
			if (pageTags) {
				pageTags.forEach(function(pageTag) {
					var pageRe = new RegExp(pageTag, 'img');
					pageText = pageText.replace(pageRe, '');
					oldPageTags += pageTag;
				});
			}
			pageText += '\n{{Redirect category shell|' + tagText + oldPageTags + '\n}}';
		}

		summaryText += (tags.length > 0 ? ' tag' + (tags.length > 1 ? 's' : '') : '') + ' to redirect';

		// avoid truncated summaries
		if (summaryText.length > (499 - Twinkle.getPref('summaryAd').length)) {
			summaryText = summaryText.replace(/\[\[[^|]+\|([^\]]+)\]\]/g, '$1');
		}

		pageobj.setPageText(pageText);
		pageobj.setEditSummary(summaryText + Twinkle.getPref('summaryAd'));
		pageobj.setWatchlist(Twinkle.getPref('watchTaggedPages'));
		pageobj.setMinorEdit(Twinkle.getPref('markTaggedPagesAsMinor'));
		pageobj.setCreateOption('nocreate');
		pageobj.save();

		if (params.patrol) {
			pageobj.triage();
		}

	},

	file: function friendlytagCallbacksFile(pageobj) {
		var text = pageobj.getPageText();
		var params = pageobj.getCallbackParameters();
		var summary = 'Adding ';

		// Add maintenance tags
		if (params.tags.length) {

			var tagtext = '', currentTag;
			$.each(params.tags, function(k, tag) {
				// when other commons-related tags are placed, remove "move to Commons" tag
				if (['Keep local', 'subst:ncd', 'Do not move to Commons_reason', 'Do not move to Commons',
					'Now Commons'].indexOf(tag) !== -1) {
					text = text.replace(/\{\{(mtc|(copy |move )?to ?commons|move to wikimedia commons|copy to wikimedia commons)[^}]*\}\}/gi, '');
				}

				currentTag = '{{' + (tag === 'Do not move to Commons_reason' ? 'Do not move to Commons' : tag);

				switch (tag) {
					case 'subst:ncd':
						if (params.ncdName !== '') {
							currentTag += '|1=' + params.ncdName;
						}
						break;
					case 'Keep local':
						if (params.keeplocalName !== '') {
							currentTag += '|1=' + params.keeplocalName;
						}
						break;
					case 'Rename media':
						if (params.renamemediaNewname !== '') {
							currentTag += '|1=' + params.renamemediaNewname;
						}
						if (params.renamemediaReason !== '') {
							currentTag += '|2=' + params.renamemediaReason;
						}
						break;
					case 'Cleanup image':
						currentTag += '|1=' + params.cleanupimageReason;
						break;
					case 'Image-Poor-Quality':
						currentTag += '|1=' + params.ImagePoorQualityReason;
						break;
					case 'Low quality chem':
						currentTag += '|1=' + params.lowQualityChemReason;
						break;
					case 'Vector version available':
						text = text.replace(/\{\{((convert to |convertto|should be |shouldbe|to)?svg|badpng|vectorize)[^}]*\}\}/gi, '');
						/* falls through */
					case 'PNG version available':
						/* falls through */
					case 'Obsolete':
						currentTag += '|1=' + params[tag.replace(/ /g, '_') + 'File'];
						break;
					case 'Do not move to Commons_reason':
						currentTag += '|reason=' + params.DoNotMoveToCommons;
						break;
					case 'subst:orfurrev':
						// remove {{non-free reduce}} and redirects
						text = text.replace(/\{\{\s*(Template\s*:\s*)?(Non-free reduce|FairUseReduce|Fairusereduce|Fair Use Reduce|Fair use reduce|Reduce size|Reduce|Fair-use reduce|Image-toobig|Comic-ovrsize-img|Non-free-reduce|Nfr|Smaller image|Nonfree reduce)\s*(\|(?:\{\{[^{}]*\}\}|[^{}])*)?\}\}\s*/ig, '');
						currentTag += '|date={{subst:date}}';
						break;
					case 'Copy to Commons':
						currentTag += '|human=' + mw.config.get('wgUserName');
						break;
					case 'Should be SVG':
						currentTag += '|' + params.svgCategory;
						break;
					default:
						break;  // don't care
				}

				currentTag += '}}\n';

				tagtext += currentTag;
				summary += '{{' + tag + '}}, ';
			});

			if (!tagtext) {
				pageobj.getStatusElement().warn('User canceled operation; nothing to do');
				return;
			}

			text = tagtext + text;
		}

		pageobj.setPageText(text);
		pageobj.setEditSummary(summary.substring(0, summary.length - 2) + Twinkle.getPref('summaryAd'));
		pageobj.setWatchlist(Twinkle.getPref('watchTaggedPages'));
		pageobj.setMinorEdit(Twinkle.getPref('markTaggedPagesAsMinor'));
		pageobj.setCreateOption('nocreate');
		pageobj.save();

		if (params.patrol) {
			pageobj.triage();
		}
	}
};

Twinkle.tag.callback.evaluate = function friendlytagCallbackEvaluate(e) {
	var form = e.target;
	var params = {};
	if (form.patrolPage) {
		params.patrol = form.patrolPage.checked;
	}

	// Don't return null if there aren't any available tags
	params.tags = form.getChecked(Twinkle.tag.mode + 'Tags') || [];

	// Save values of input fields into params object. This works as quickform input
	// fields within subgroups of elements with name 'articleTags' (say) have their
	// name attribute as 'articleTags.' + name of the subgroup element

	var name_prefix = Twinkle.tag.mode + 'Tags.';
	$(form).find("[name^='" + name_prefix + "']:not(div)").each(function(idx, el) {
		// el are the HTMLInputElements, el.name gives the name attribute
		params[el.name.slice(name_prefix.length)] =
			el.type === 'checkbox' ? form[el.name].checked : form[el.name].value;
	});

	switch (Twinkle.tag.mode) {
		case 'article':
			params.tagsToRemove = form.getUnchecked('alreadyPresentArticleTags') || [];
			params.tagsToRemain = form.getChecked('alreadyPresentArticleTags') || [];
			params.reason = form.reason.value.trim();

			params.group = form.group.checked;

			// Validation
			if ((params.tags.indexOf('Merge') !== -1) || (params.tags.indexOf('Merge from') !== -1) ||
				(params.tags.indexOf('Merge to') !== -1)) {
				if (((params.tags.indexOf('Merge') !== -1) + (params.tags.indexOf('Merge from') !== -1) +
					(params.tags.indexOf('Merge to') !== -1)) > 1) {
					alert('Please select only one of {{merge}}, {{merge from}}, and {{merge to}}. If several merges are required, use {{merge}} and separate the article names with pipes (although in this case Twinkle cannot tag the other articles automatically).');
					return;
				}
				if (!params.mergeTarget) {
					alert('Please specify the title of the other article for use in the merge template.');
					return;
				}
				if ((params.mergeTagOther || params.mergeReason) && params.mergeTarget.indexOf('|') !== -1) {
					alert('Tagging multiple articles in a merge, and starting a discussion for multiple articles, is not supported at the moment. Please turn off "tag other article", and/or clear out the "reason" box, and try again.');
					return;
				}
			}
			if ((params.tags.indexOf('Not English') !== -1) && (params.tags.indexOf('Rough translation') !== -1)) {
				alert('Please select only one of {{not English}} and {{rough translation}}.');
				return;
			}
			if (params.tags.indexOf('History merge') !== -1 && params.histmergeOriginalPage.trim() === '') {
				alert('You must specify a page to be merged for the {{history merge}} tag.');
				return;
			}
			if (params.tags.indexOf('Cleanup') !== -1 && params.cleanup.trim() === '') {
				alert('You must specify a reason for the {{cleanup}} tag.');
				return;
			}
			if (params.tags.indexOf('Expand language') !== -1 && params.expandLanguageLangCode.trim() === '') {
				alert('You must specify language code for the {{expand language}} tag.');
				return;
			}
			break;

		case 'file':

			if (params.tags.indexOf('Cleanup image') !== -1 && params.cleanupimageReason === '') {
				alert('You must specify a reason for the cleanup tag.');
				return;
			}
			if (params.tags.indexOf('Image-Poor-Quality') !== -1 && params.ImagePoorQualityReason === '') {
				alert('You must specify a reason for the {{Image-Poor-Quality}} tag');
				return;
			}
			if (params.tags.indexOf('Low Quality Chem') !== -1 && params.lowQualityChemReason === '') {
				alert('You must specify a reason for the {{Low Quality Chem}} tag');
				return;
			}
			if ((params.tags.indexOf('Obsolete') !== -1 && params.ObsoleteFile === '') ||
				(params.tags.indexOf('PNG version available') !== -1 && params.PNG_version_availableFile === '') ||
				(params.tags.indexOf('Vector version available') !== -1 && params.Vector_version_availableFile === '')
			) {
				alert('You must specify the replacement file name for a tag in the Replacement tags list');
				return;
			}
			if (params.tags.indexOf('Do not move to Commons_reason') !== -1 && params.DoNotMoveToCommons === '') {
				alert('You must specify a reason for the {{Do not move to Commons}} tag');
				return;
			}
			break;

		case 'redirect':
			break;

		default:
			alert('Twinkle.tag: unknown mode ' + Twinkle.tag.mode);
			break;
	}

	// File/redirect: return if no tags selected
	// Article: return if no tag is selected and no already present tag is deselected
	if (params.tags.length === 0 && (Twinkle.tag.mode !== 'article' || params.tagsToRemove.length === 0)) {
		alert('You must select at least one tag!');
		return;
	}

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

	Morebits.wiki.actionCompleted.redirect = Morebits.pageNameNorm;
	Morebits.wiki.actionCompleted.notice = 'Tagging complete, reloading article in a few seconds';
	if (Twinkle.tag.mode === 'redirect') {
		Morebits.wiki.actionCompleted.followRedirect = false;
	}

	var wikipedia_page = new Morebits.wiki.page(Morebits.pageNameNorm, 'Tagging ' + Twinkle.tag.mode);
	wikipedia_page.setCallbackParameters(params);
	wikipedia_page.load(Twinkle.tag.callbacks[Twinkle.tag.mode]);

};

})(jQuery);
// </nowiki>
"https://ml.wikipedia.org/w/index.php?title=ഉപയോക്താവ്:Adithyak1997/Twinkle1.js&oldid=3385821" എന്ന താളിൽനിന്ന് ശേഖരിച്ചത്