صارف:Yethrosh/XFDcloser.js

آزاد دائرۃ المعارف، ویکیپیڈیا سے

تفصیل کے لیے کھولیں کے بٹن پر کلک کریں یاددہانی: محفوظ کرنے کے بعد تازہ ترین تبدیلیوں کو دیکھنے کے لیے آپ کو اپنے براؤزر کا کیش صاف کرنا ہوگا۔

  • فائرفاکس/ سفاری: جب Reload پر کلک کریں تو Shift دبا کر رکھیں، یا Ctrl-F5 یا Ctrl-R دبائیں (Mac پر R- )
  • گوگل کروم: Ctrl-Shift-R دبائیں (Mac پر Shift-R-⌘)
  • انٹرنیٹ ایکسپلورر: جب Refresh پر کلک کریں تو Ctrl یا Ctrl-F5 دبائیں
  • اوپیرا: Tools → Preferences میں جائیں اور کیش صاف کریں

/*******************************************************************************
 XFDcloser --- by Evad37
 > A script that assists in closing AFD, CFD, FFD, MFD, RFD, and TFD discussions
--------------------------------------------------------------------------------
* See [[User:Evad37/XFDcloser]] for installation details and documentation
--------------------------------------------------------------------------------
* Version 3.10.4
* See page history for change log
--------------------------------------------------------------------------------
* IMPORTANT NOTE:
  You are responsible for any edits/actions performed using XFDcloser! Please
  make sure you understand relevant Wikipedia policies and procedures, and use 
  this tool accordingly.
  (Some pertinent pages are: [[WP:CON]], [[WP:CLOSE]], [[WP:NAC]])
--------------------------------------------------------------------------------
* Licencing note:
  Originally published on the English Wikipedia at
  < https://en.wikipedia.org/wiki/User:Evad37/XFDcloser.js > ( and/or 
  < https://en.wikipedia.org/wiki/User:Evad37/XFDcloser/sandbox.js > and/or
  < https://en.wikipedia.org/wiki/User:Evad37/XFDcloser/v3.js > )
  and available under Creative Commons Attribution-ShareAlike 3.0 Unported
  License (CC BY-SA 3.0) < https://creativecommons.org/licenses/by-sa/3.0/ > and
  GNU Free Documentation License (GFDL) < http://www.gnu.org/copyleft/fdl.html >
* Attribution note:
  This script incorporates code derived from, and is a direct
  replacement for, these other scripts also written by Evad37:
  - https://en.wikipedia.org/wiki/User:Evad37/CFDcloser.js
  - https://en.wikipedia.org/wiki/User:Evad37/FFDcloser.js
  - https://en.wikipedia.org/wiki/User:Evad37/TFDcloser.js
  Futhermore, it incorporates code copied/derived from:
  - https://en.wikipedia.org/wiki/User:Mr.Z-man/closeAFD2.js
  - https://en.wikipedia.org/wiki/MediaWiki:Gadget-twinkleunlink.js
  - https://en.wikipedia.org/wiki/MediaWiki:Gadget-morebits.js
  Further notes:
   - See these pages' "History" pages for their authors.
   - All of the these pages are also published on Wikipedia, and thus also
	 available under the terms of the CC BY-SA 3.0 license and GFDL
--------------------------------------------------------------------------------
	Have fun, and happy editing! - Evad37
*******************************************************************************/
/* <nowiki> */
// Record script start time ASAP
var XFDC_START_TIME = new Date();

/* ========== Dependencies ====================================================================== */
// Resource loader modules
mw.loader.using(
	['mediawiki.util', 'mediawiki.api', 'mediawiki.Title', 'mediawiki.RegExp',
	 'oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui-windows', 'jquery.ui'],
	function () {
// Load Morebits gadget if not already available
if ( window.Morebits == null ) {
	importScript('MediaWiki:Gadget-morebits.js');
	importStylesheet( 'MediaWiki:Gadget-morebits.css' );
}
// Load extra.js if not already available
if ( window.extraJs == null ) {
	importScript('User:Yethrosh/extra.js');
}
// Wait for page load
$( function($) {

/* ========== Config ============================================================================ */
// A global object that stores all the page and user configuration and settings
var config = {};
// Script info
config.script = {
	// Advert to append to edit summaries
	advert:  ' ([[وپ:نبح بند|نبح بند]])',
	version: '3.10.4'
};
// MediaWiki configuration values
config.mw = mw.config.get( [
	'wgPageName',
	'wgUserGroups',
	'wgUserName',
	'wgFormattedNamespaces',
	'wgMonthNames'
] );

/* --------- Quick checks that script should be running ----------------------------------------- */
if ( /(?:\?|&)(?:action|diff|oldid)=/.test(window.location.href) ) {
	console.log('[نبح بند] صفحہ کا خانہ ترمیم، تاریخچہ یا فرق کھلا ہے');
	return;
}

if (
	!config.mw.wgUserGroups.includes('extendedconfirmed') &&
	!config.mw.wgUserGroups.includes('sysop')
) {
	console.log('[نبح بند] صارف توسیعی تصدیق شدہ یا منتظم نہیں ہے');
	return;
}

var xfdpage_regex = /(مضامین_برائے_حذف\/|متفرقات_برائے_حذف|User:Cyberbot_I\/AfD's_requiring_attention|Wikipedia:WikiProject_Deletion_sorting\/(?!(Flat|Compact)$)|(زمرہ جات|فائل|سانچے|رجوع_مکررات)_برائے_حذف(?!\/(Working|Holding_cell)))(?!\/?Administrator_instructions$)/;
if ( !xfdpage_regex.test(config.mw.wgPageName) ) {
	console.log('[نبح بند] پیش نظر صفحہ نبح کا صفحہ نہیں ہے');
	return;
}

/* --------- CSS rules -------------------------------------------------------------------------- */
mw.util.addCSS(
	[	// Inline links
		'.xfdc-status { font-size:85%; margin-left:13px; font-weight:normal; }',
		'.xfdc-action { margin-left:13px; }',
		'.xfdc-action a { cursor:pointer; }',
		'.xfdc-qc-cancel { cursor: pointer; border: 1px solid #777; border-radius: 10px;'+
			'font-weight: bold; font-size: 90%; color: #777; padding: 0px; margin: 0px 1px; }',

		// Headings
		'.xfdc-old .xfdc-action { background-color:#c6ffc6 }',
		'.xfdc-notOld .xfdc-action { background-color:#ffc6c6 }',
		'.xfdc-unknownAge .xfdc-action { background-color:#ffffc6 }',
		'.xfdc-notOld.xfdc-relisted .xfdc-action { background-color:#ffe9c6 }',
			
		// Dialog
		'#xfdc-dialog { background-color:#f0f0f0; padding:0.5em 1em; font-size:110%; }',
		'.xfdc-dialog-option { white-space:nowrap; min-width:8em; margin-right:0.3em; '+
			'display:block; float:left; }',
		'.xfdc-dialog-bracketedOption { white-space:nowrap; margin-left:2em; }',
		'.xfdc-dialog-bracketedOption::before, .xfdc-bracketed::before { content: "(\\200A "; }',
		'.xfdc-dialog-bracketedOption::after,  .xfdc-bracketed::after  { content: "\\200A )"; }',
		'.xfdc-dialog-container { float:left; max-width:34%; margin-left:0.5em; }',
		'.xfdc-dialog-disabled { color:#777; }',
		'.xfdc-dialog-actionInfo { border:2px solid #fff; padding:0.2em; margin:0.1em; }',
		'.xfdc-dialog-actionInfo > strong { display:block; }',
		'.xfdc-dialog-actionInfo > span { margin-left:1.5em; }',
		'#closeXFD-interface-buttons > button { margin:1em; }',
		'#closeXFD-preview-output { background-color:#fff; border:1px solid #777; '+
			'margin-top: 0px; padding:0px 10px; }',
		'.closeXFD-errorNote { border:1px solid #ff0000 }',
		
		// Task notices
		'.xfdc-notices { width:80%; font-size:95%; padding-left:2.5em; }',
		'.xfdc-notices > p { margin:0; line-height:1.1em; }',
		'.xfdc-notice-error { color:#D00000; font-size:92% }',
		'.xfdc-notice-warning { color:#9900A2; font-size:92% }',
		'.xfdc-notice-error::before, .xfdc-notice-warning::before { content: " ["; }',
		'.xfdc-notice-error::after,  .xfdc-notice-warning::after  { content: "]"; }',
		'.xfdc-task-waiting { color:#595959; }',
		'.xfdc-task-started { color:#0000D0; }',
		'.xfdc-task-done { color:#006800; }',
		'.xfdc-task-skipped { color:#697000; }',
		'.xfdc-task-aborted { color:#C00049; }',
		'.xfdc-task-failed { color:#D00000; }',
		
		// Show/hide box
		'#XFDcloser-showhide { bottom:0; display:block; position:fixed; right:0; z-index:100; '+
			'padding:5px; box-shadow:0 2px 4px rgba(0,0,0,0.5); background-color:#FEF9E6; '+
			'border:1px solid #aaa; border-radius:5px; font-size:85% }'
	]
	.join('\n')
);

/* --------- Additional configuration ----------------------------------------------------------- */
// Set custom version of namespaces with description for namespace 0
config.mw.namespaces = $.extend({}, config.mw.wgFormattedNamespaces, {0: 'article'});
// Set custom version of monthNames with 0-indexed month names (remove "" from start of array)
config.mw.monthNames = config.mw.wgMonthNames.slice(1);
// Set sysop status
if ( config.mw.wgUserGroups.includes('sysop') ) {
	config.user = {
		'isSysop': true,
		'sig': '~~~~'
	};
} else {
	config.user = {
		'isSysop': false,
		'sig': '<small>(غیر انتظامی اختتام)</small>~~~~'
	};
}
// Start time, for detecting edit conflicts
config.startTime = XFDC_START_TIME;
// Variables for tracking across multiple discussions
config.track = {
	// Track Afd logpage edits using deferred objects, to know when it is safe to read the wikitext
	'afdLogEdit': [$.Deferred().resolve()],
	// Track how many closes/relists have been started and completed
	'started':  0,
	'finished': 0,
	'discussions': []
};

//Warn if unloading before closes/relists are completed
$(window).on('beforeunload', function(e) {
	if ( config.track.started > config.track.finished ) {
		e.returnValue = '';
		return '';		
	}
});

// ========== Convinenance functions ============================================================ */
/**
 * Un-escapes some HTML tags (<br>, <p>, <ul>, <li>, <hr>, and <pre>); turns wikilinks
 * into real links. Ignores anyting within <pre>...</pre> tags.
 * Input will first be escaped using mw.html.escape() unless specified 
 * @param {String} text
 * @param {Object} config Configuration options
 * @config {Boolean} noEscape - do not escape the input first
 * @returns {String} unescaped text
 */
var safeUnescape = function(text, config) {
	var path = 'https:' + mw.config.get('wgServer') + '/wiki/';

	return ( config && config.noEscape && text || mw.html.escape(text) )
	// Step 1: unescape <pre> tags
	.replace(  
		/&lt;(\/?pre\s?\/?)&gt;/g,
		'<$1>'
	)
	// Step 2: replace piped wikilinks with real links (unless inside <pre> tags)
	.replace( 
		/\[\[([^\|\]]*?)\|([^\|\]]*?)\]\](?![^<]*?<\/pre>)/g,
		'<a href="' + path + mw.util.wikiUrlencode('$1') + '" target="_blank">$2</a>'
	)
	// Step 3: replace other wikilinks with real links (unless inside <pre> tags)
	.replace( 
		/\[\[([^\|\]]+?)]\](?![^<]*?<\/pre>)/g,
		'<a href="' + path + mw.util.wikiUrlencode('$1') + '" target="_blank">$1</a>'
	)
	// Step 4: unescape other tags: <br>, <p>, <ul>, <li>, <hr> (unless inside <pre> tags)
	.replace(
		/&lt;(\/?(?:br|p|ul|li|hr)\s?\/?)&gt;(?![^<]*?<\/pre>)/g,
		'<$1>'
	);
};

var makeErrorMsg = function(code, jqxhr) {
	var details = '';
	if ( code === 'http' && jqxhr.textStatus === 'error' ) {
		details = 'ایچ ٹی ٹی پی نقص ' + jqxhr.xhr.status;
	} else if ( code === 'http' ) {
		details = 'ایچ ٹی ٹی پی نقص: ' + jqxhr.textStatus;
	} else if ( code === 'ok-but-empty' ) {
		details = 'Error: Got an empty response from the server';
	} else {
		details = 'اے پی آئی نقص: ' + code;
	}
	return details;
};

var formatErrorMessage = function(details) {
	var type = details && details.type || 'Script';
	var error = details && details.error || 'نامعلوم نقص';
	var message =  ( details.message )
		? ' - ' + safeUnescape(details.message)
		: '';

	return type + ' error: ' + error + message;
}

// TODO: how to pass in a message.... or maybe this should just return {type:..., error:...} objects?
var formatApiError = function(code, jqxhr, message) {
	if ( code === 'http') {
		var httpError = ( jqxhr.textStatus === 'error')
			? jqxhr.xhr.status
			: jqxhr.textStatus;
		return formatErrorMessage({
			type: 'HTTP',
			error: httpError,
			message: message
		});
	}

	if ( code === 'okay-but-empty' ) {
		return formatErrorMessage({
			type: 'Server',
			error: 'Got an empty response from the server'
		});
	}

	return  formatErrorMessage({
		type: 'API',
		error: code,
		message: message
	});
}

/* ========== XfdVenue class ====================================================================
   Each instance represents an XfD venue, with properties/function specific to that venue
   ---------------------------------------------------------------------------------------------- */
// Constructor
var XfdVenue = function(type, settings) {
	this.type = type;
	for ( var key in settings ) {
		this[key] = settings[key];
	}
};
// ---------- XfdVenue prototype  --------------------------------------------------------------- */
XfdVenue.prototype.hasNomTemplate = function(wikitext) {
	var pattern = new RegExp(this.regex.nomTemplate);
	return pattern.test(wikitext);
};
XfdVenue.prototype.removeNomTemplate = function(wikitext) {
	var pattern = new RegExp(this.regex.nomTemplate);
	return wikitext.replace(pattern, '');
};
// ---------- Venue-specific classes  ----------------------------------------------------------- */
// MFD
var MfdVenue = function() {
	XfdVenue.call(this,	'mfd', {
		path:		 'ویکیپیڈیا:متفرقات برائے حذف',
		subpagePath: 'ویکیپیڈیا:متفرقات برائے حذف/',
		ns_number:	 null,
		html: {
			head:			'h4',
			list:			'dl',
			listitem:		'dd'
		},
		wikitext: {
			closeTop:		"{{subst:Mfd top|'''__RESULT__'''}}__TO_TARGET____RATIONALE__ __SIG__",
			closeBottom:	"{{subst:Mfd bottom}}",
			oldXfd:			"{{Old MfD |date=__DATE__ |result='''__RESULT__''' |page=__SUBPAGE__}}"+
				"\n",
			mergeFrom:		"{{mfd-mergefrom|__NOMINATED__|__DEBATE__|__DATE__}}\n",
			mergeTo:		"{{mfd-mergeto|__TARGET__|__DEBATE__|__DATE__|__TARGETTALK__}}\n",
			alreadyClosed:	"{{#ifeq:{{FULLPAGENAME}}|ویکیپیڈیا:متفرقات برائے حذف|"+
				"{{collapse bottom}}|}}"
		},
		regex: {
			nomTemplate:	/(?:<noinclude>\s*)?(?:{{mfd[^}}]*}}|<span id="mfd".*?<\/span>&nbsp;\[\[زمرہ:متفرق صفحات برائے حذف شدگی\|?.*\]\]\s*)(?:\s*<\/noinclude>)?/gi
		}
	});
};
MfdVenue.prototype = Object.create(XfdVenue.prototype);

// CFD
var CfdVenue = function() {
	XfdVenue.call(this,	'cfd', {
		path:		 'Wikipedia:Categories for discussion/Log/',
		ns_number:	 [14],
		html: {
			head:			'h4',
			list:			'ul',
			listitem:		'li',
			nthSpan:		'2'
		},
		wikitext: {
			closeTop:		"{{subst:cfd top}} '''__RESULT__'''__TO_TARGET____RATIONALE__ __SIG__",
			closeBottom:	"{{subst:cfd bottom}}",
			oldXfd:			"{{Old CfD |__SECTION__ |date=__DATE__ |action=__ACTION__ "+
				"|result=__RESULT__}}\n",
			alreadyClosed:	"<!-- Template:Cfd top -->"		
		}
	});
};
CfdVenue.prototype = Object.create(XfdVenue.prototype);

// FFD
var FfdVenue = function() {
	XfdVenue.call(this,	'ffd', {
		path:		 'Wikipedia:Files for discussion/',
		ns_number:	 [6],
		ns_unlink:   ['0', '10', '100', '118'], // main, Template, Portal, Draft
		html: {
			head:			'h4',
			list:			'dl',
			listitem:		'dd',
			nthSpan:		'1'
		},
		wikitext: {
			closeTop:		"{{subst:ffd top|'''__RESULT__'''}}__TO_TARGET____RATIONALE__ __SIG__",
			closeBottom:	"{{subst:ffd bottom}}",
			oldXfd:			"{{oldffdfull |date=__DATE__ |result='''__RESULT__''' "+
				"|page=__SECTION__}}\n",
			pagelinks:		"{{subst:ffd2|__PAGE__|multi=yes}}\n",
			relistReplace:	"{{ffd|log=__TODAY__",
			alreadyClosed:	"<!--Template:Ffd top-->"		
		},
		regex: {
			nomTemplate:	/{{ffd[^}}]*}}/gi,
			relistPattern:	/{{\s*ffd\s*\|\s*log\s*=\s*[^|}}]*/gi
		}
	});
};
FfdVenue.prototype = Object.create(XfdVenue.prototype);

// TFD
var TfdVenue = function() {
	XfdVenue.call(this,	'tfd', {
		path:		 'Wikipedia:Templates for discussion/Log/',
		subpagePath: 'Wikipedia:Templates for discussion/',
		ns_number:	 [10, 828],
		html: {
			head:			'h4',
			list:			'ul',
			listitem:		'li',
			nthSpan:		'1'
		},
		wikitext: {
			closeTop:		"{{subst:Tfd top|'''__RESULT__'''}}__TO_TARGET____RATIONALE__ __SIG__",
			closeBottom:	"{{subst:Tfd bottom}}",
			oldXfd:			"{{oldtfdfull|date= __DATE__ |result=__RESULT__ |disc=__SECTION__}}\n",
			pagelinks:		"* {{tfd links|__PAGE__}}\n",
			relistReplace:	"Wikipedia:Templates for discussion/Log/__TODAY__#",
			alreadyClosed:	"<!-- Tfd top -->"
		},
		regex: {
			nomTemplate:	/(<noinclude>[\n\s]*)?{{(?:Template for discussion|Tfm)\/dated[^{}]*(?:{{[^}}]*}}[^}}]*)*?}}([\n\s]*<\/noinclude>)?(\n)?/gi,
			relistPattern:	/Wikipedia:Templates(_|\s){1}for(_|\s){1}discussion\/Log\/\d{4}(_|\s){1}\w*(_|\s){1}\d{1,2}#(?=[^}]*}{2})/gi
		},
		holdingCellSectionNumber: {
			"review":			2,
			"convert":			11,
			"substitute":		12,
			"orphan":			13,
			"ready":			14,	// (ready for deletion)
			"merge-arts":		4,
			"merge-geopolgov":	5,	// (geography, politics and governance)
			"merge-religion":	6,
			"merge-sports":		7,
			"merge-trasnport":	8,
			"merge-other":		9,
			"merge-meta":		10
		}
	});
};
TfdVenue.prototype = Object.create(XfdVenue.prototype);
TfdVenue.prototype.removeNomTemplate = function(wikitext) {
	var pattern = new RegExp(config.xfd.regex.nomTemplate);
	var matches = pattern.exec(wikitext);
	if ( !matches ) {
		return wikitext;
	}
	var logical_xor = function(first, second) {
		return (first ? true : false) !== (second ? true : false);
	};
	var unbalancedNoincludeTags = logical_xor(matches[1], matches[2]); 
	var replacement = ( unbalancedNoincludeTags ) ? "$1$2" : "";					
	return wikitext.replace(pattern, replacement);
};

// RFD
var RfdVenue = function() {
	XfdVenue.call(this,	'rfd', {
		type:		 'rfd',
		path:		 'Wikipedia:Redirects for discussion/Log/',
		ns_number:	 null,
		html: {
			head:			'h4',
			list:			'ul',
			listitem:		'li'
		},
		wikitext: {
			closeTop:		"{{subst:Rfd top|'''__RESULT__'''}}__TO_TARGET____RATIONALE__ __SIG__",
			closeBottom:	"{{subst:Rfd bottom}}",
			oldXfd:			"{{Old RfD |date={{subst:date|__FIRSTDATE__}} |result='''__RESULT__'''"+
				" |page=__DATE__#__SECTION__}}\n",
			alreadyClosed:	"<!-- Template:Rfd top-->",
			relistReplace:	"#invoke:RfD||2=__SECTION_HEADER__|"
		},
		regex: {
			nomTemplate:		/(^\s*{{.*#invoke:RfD(?:.|\n)*?-->\|content=\n?|\n?<!-- Don't add anything after this line.*? -->\n}}|\[\[Category:Temporary maintenance holdings\]\]\n?)/g,
			fullNomTemplate:	/(^\s*{{.*#invoke:RfD(?:.|\n)*?<!-- Don't add anything after this line.*? -->\n}}|\[\[Category:Temporary maintenance holdings\]\]\n?)/g,
			relistPattern:  	/#invoke:RfD\|\|\|/gi
			
		},
	});
};
RfdVenue.prototype = Object.create(XfdVenue.prototype);

// AFD
var AfdVenue = function(transcludedOnly) {
	XfdVenue.call(this,	'نبح', {
		type:		 'نبح',
		path:		 'ویکیپیڈیا:مضامین برائے حذف/نوشتہ/',
		subpagePath: 'ویکیپیڈیا:مضامین برائے حذف/',
		ns_number:	 [0], // main
		ns_logpages: 4, // Wikipedia
		ns_unlink:   ['0', '10', '100', '118'], // main, Template, Portal, Draft
		html: {
			head:			'h3',
			list:			'dl',
			listitem:		'dd',
			nthSpan:		'2'
		},
		wikitext: {
			closeTop:		"{{جا:نبح بالا|'''__RESULT__'''}}__TO_TARGET____RATIONALE__ __SIG__",
			closeBottom:	"{{جا:نبح زیریں}}",
			mergeFrom:		"{{Afd-merge from|__NOMINATED__|__DEBATE__|__DATE__}}\n",
			mergeTo:		"{{Afd-merge to|__TARGET__|__DEBATE__|__DATE__}}\n",
			alreadyClosed:	"<!--سانچہ:نبح زیریں-->"		
		},
		regex: {
			nomTemplate:	/(?:{{Article for deletion\/dated|<!-- Please do not remove or change this AfD message)(?:.|\n)*?}}(?:(?:.|\n)+this\ point -->)?\s*/g
		},
		transcludedOnly:	transcludedOnly
	});
};
AfdVenue.prototype = Object.create(XfdVenue.prototype);

// Create xfd venue object
var isAfd = /(مضامین_برائے_حذف|User:Cyberbot_I|Wikipedia:WikiProject_Deletion_sorting)/.test(config.mw.wgPageName);
var afdTranscludedOnly = /(User:Cyberbot_I|Wikipedia:WikiProject_Deletion_sorting)/.test(config.mw.wgPageName);

if ( config.mw.wgPageName.includes('ویکیپیڈیا:متفرقات_برائے_حذف') ) {
	config.xfd = new MfdVenue();
} else if ( config.mw.wgPageName.includes('Categories_for_discussion/') ) {
	config.xfd = new CfdVenue();
} else if ( config.mw.wgPageName.includes('Files_for_discussion') ) {
	config.xfd = new FfdVenue();
} else if ( config.mw.wgPageName.includes('Templates_for_discussion') ) {
	config.xfd = new TfdVenue();
} else if ( config.mw.wgPageName.includes('Redirects_for_discussion') ) {
	config.xfd = new RfdVenue();
} else if ( isAfd ) {
	config.xfd = new AfdVenue(afdTranscludedOnly);
} else {
	return;
}

/* ========== API =============================================================================== */
var API = new mw.Api( {
	ajax: {
		headers: { 
			'Api-User-Agent': 'XFDCloser/' + config.script.version + 
				' ( https://ur.wikipedia.org/wiki/WP:XFDC )'
		}
	}
} );

// Helper functions for processing results
var arrayFromResponsePages = function(response) {
	return $.map(response.query.pages, function(page) { return page; });
};

var pageFromResponse = function(response) {
	return arrayFromResponsePages(response)[0];
};


var dmyDateString = function(date) {
	return date.getUTCDate().toString() + ' ' +
		config.mw.monthNames[date.getUTCMonth()] + ' ' +
		date.getUTCFullYear().toString();
};

/**
 * Generates a JS Date object from the text of a timestamp
 * @param {String} sigTimestamp in format "`hh`:`mm`, `d` `Month` `yyyy` (UTC)", e.g. "09:42, 11 January 2019 (UTC)"
 * @returns {Date|NaN} Date object, or NaN if sigTimestamp could not be parsed
 */
var dateFromSigTimestamp = function(sigTimestamp) {
	var pattern = /(\d\d\:\d\d), (\d{1,2}) (\w+) (\d\d\d\d) \(UTC\)/;
	if ( !pattern.test(sigTimestamp) ) {
		return NaN;
	}
	var parts = pattern.exec(sigTimestamp);
	var year = parts[4];
	var monthIndex = config.mw.wgMonthNames.indexOf(parts[3]);
	if ( monthIndex === -1 ) {
		return NaN;
	}
	var month = ( monthIndex < 10 )
		? '0' +  monthIndex
		: monthIndex;
	var day = ( parts[2].length === 1 )
		? '0' + parts[2]
		: parts[2];
	var time = 'T' + parts[1] + 'Z';
	var iso8601DateString = year + '-' + month + '-' + day + time;
	return Date.parse(iso8601DateString) && new Date(iso8601DateString);
};

/* ========== Additional functions for working with mw.Title objects ============================ */
var hasCorrectNamespace = function(mwTitleObject) {
	return (
		config.xfd.ns_number === null ||
		config.xfd.ns_number.includes(mwTitleObject.getNamespaceId())
	);
};
var setExistence = function(mwTitleObject, exists) {
	mw.Title.exist.set(mwTitleObject.toString(), exists);
};

/* ========== Multibutton confirm dialog ======================================================== */

/** multiButtonConfirm
 * @param {Object} config
 * @config {String} title  Title for the dialogue
 * @config {String} message  Message for the dialogue. HTML tags (except for <br>, <p>, <ul>,
 *  <li>, <hr>, and <pre> tags) are escaped; wikilinks are turned into real links.
 * @config {Array} actions  Optional. Array of configuration objects for OO.ui.ActionWidget
 *  <https://doc.wikimedia.org/oojs-ui/master/js/#!/api/OO.ui.ActionWidget>.
 *  If not specified, the default actions are 'accept' (with label 'OK') and 'reject' (with
 *  label 'Cancel').
 * @config {String} size  Symbolic name of the dialog size: small, medium, large, larger or full.
 * @return {Promise<String>} action taken by user
 */
var multiButtonConfirm = function(config) {
	var dialogClosed = $.Deferred();
	
	// Wrap message in a HtmlSnippet to prevent escaping
	var htmlSnippetMessage = new OO.ui.HtmlSnippet(
		safeUnescape(config.message)
	);

	var windowManager = new OO.ui.WindowManager();
	var messageDialog = new OO.ui.MessageDialog();
	$('body').append( windowManager.$element );
	windowManager.addWindows( [ messageDialog ] );
	windowManager.openWindow( messageDialog, {
		'title': config.title,
		'message': htmlSnippetMessage,
		'actions': config.actions,
		'size': config.size
	} );
	windowManager.on('closing', function(_win, promise) {
		promise.then(function(data) {
			dialogClosed.resolve(data && data.action);
			windowManager.destroy();
		})
	});

	return dialogClosed.promise();
}

/* ========== Discusssion class =================================================================
   Each instance represents an XfD discussion.                          
   ---------------------------------------------------------------------------------------------- */
/** Constructor
 * @param {Object} discussionConfig configuration object:
 *   @config {String} id - A unique ID for this discussion (used in various elements' IDs)
 *   @config {String} nomPage - Page where the XFD discussion is taking place; either a dated or
 *           name-based subpage
 *   @config {String} sectionHeader - text of the section heading for the XFD discussion
 *   @config {String} sectionNumber - edit section number for the XFD discussion
 *   @config {mw.Title[]} pages - pages nominated in the XFD discussion; omit if in basic mode
 *   @config {String} firstDate - first timestamp date, for RFD discussions
 *   @config {Boolean} isOld - `true` if discussion has been listed (or relisted) for more than 7 days
 *   @config {Boolean} isRelisted - `true` if discussion has been relisted   
 * 
 */
var Discussion = function(discussionConfig) {
	var defaultConfig = {
		pages: [],
		deferred: {} // For later tracking of jQuery Deferred objects
	}
	$.extend(this, defaultConfig, discussionConfig);
};
// Construct from headline span element
Discussion.newFromHeadlineSpan = function (headingIndex, context) {
	var $headlineSpan = $(context);
	var $heading = $headlineSpan.parent();
	
	// Fix for "Auto-number headings" preference
	$('.mw-headline-number', context).prependTo($heading);

	// Get section header
	var sectionHeader = $headlineSpan.text().trim();

	// Check if already closed. Closed AfDs and MfDs have the box above the heading.
	if ( /(afd|mfd)/.test(config.xfd.type) && $heading.parent().attr('class') && $heading.parent().attr('class').includes('xfd-closed') ) {
		// Skip 
		return;
	} else if ( !/(afd|mfd)/.test(config.xfd.type) && $heading.next().attr('class') ) {
		// Only for closed discussion will the next element after the heading have any class set
		// Skip, add class to enable hiding of closed discussions
		$heading.addClass('xfd-closed');
		return;
	}

	var sectionlink = $heading.find('.mw-editsection a')
		.not('.mw-editsection-visualeditor, .autoCloserButton').attr('href');
	var editsection = sectionlink.split('section=')[1].split('&')[0];
	var nompage = '';
	if ( /T/.test(editsection) ) {
		// Section is transcluded from another page
		nompage = mw.Title.newFromText(
			decodeURIComponent(sectionlink.split("title=")[1].split("&")[0])
		).getPrefixedText();
		if ( -1 !== $.inArray(nompage, [
				'Wikipedia:Redirects for discussion/Header',
				'Wikipedia:Redirect/Deletion reasons',
				'Wikipedia:Templates for discussion/Holding cell'
			])
		) {
			// ignore headings transcuded from these pages
			return;
		}
		// remove "T-" from section number
		editsection = editsection.substr(2);
	} else {
		// Section is on current page, not transcluded
		if ( config.xfd.transcludedOnly ) {
			return;
		}
		nompage = mw.Title.newFromText( config.mw.wgPageName ).getPrefixedText();		
	}

	var pages=[];
	var firstdate = ( config.xfd.type === 'rfd' ) ? '' : null;

	if ( config.xfd.type === 'cfd' ) {
		//CFDs have basic closure only
		pages = false;
	} else if ( config.xfd.type === 'rfd' || config.xfd.type === 'mfd' ) {
		// For MFD, closed discussion are within a collapsed table
		$('table.collapsible').has('div.xfd-closed').addClass('xfd-closed');
		// MFD & RFD have nominated page links prior to span with classes plainlinks, lx
		pages = $heading
		.nextUntil(config.xfd.html.head + ', div.xfd-closed, table.xfd-closed')
		.find(config.xfd.html.listitem)
		.has('span.plainlinks.lx')
		.children('span')
		.filter(':first-child')
		.children('a, span.plainlinks:not(.lx)')
		.filter(':first-child')
		.map(function() { return mw.Title.newFromText($(this).text()); })
		.get();
		if ( config.xfd.type === 'rfd' ) {
			var discussionNodes = $heading
			.nextUntil(config.xfd.html.head + ', div.xfd-closed, table.xfd-closed')
			.clone();
			
			// Fix for "Comments in Local Time" gadget
			discussionNodes.find('span.localcomments').each(function(){
				var utcTime = $(this).attr('title');
				$(this).text(utcTime);
			});
			
			var discussionText = discussionNodes.text();
			// Ignore relisted discussions, and non-boxed closed discussions
			if (
				discussionText.includes('Relisted, see Wikipedia:Redirects for discussion') ||
				discussionText.includes('Closed discussion, see full discussion')
			) {
				return;
			}
			// Find first timestamp date
			var firstdatePatt = /(?:\d\d:\d\d, )(\d{1,2} \w+ \d{4})(?: \(UTC\))/;
			var firstdateMatch = firstdatePatt.exec(discussionText);
			firstdate = firstdateMatch && firstdateMatch[1];
		}
	} else {
		// AFD, FFD, TFD: nominated page links inside span with classes plainlinks, nourlexpansion
		pages = $heading
		.nextUntil(config.xfd.html.head + ', div.xfd-closed')
		.find(config.xfd.html.listitem + ' > span.plainlinks.nourlexpansion')
		.filter(':nth-of-type(' + config.xfd.html.nthSpan + ')')
		.children('a')
		.filter(':first-child')
		.map(function() { return mw.Title.newFromText($(this).text()); })
		.get();
	}
	
	// Sanity check - check if any pages are null
	if ( !pages || pages.length === 0 || pages.some(function(p) { return !p; }) ) {
		//Still offer a "basic" close using the section header
		pages = false;
	}

	// Check discussion age (since last relist, if applicable)
	// TODO: reduce redundancy with finding RfDs' first date
	var $discussionNodes = $heading
		.nextUntil(config.xfd.html.head + ', div.xfd-closed, table.xfd-closed')
		.clone()
		.find('span.localcomments')
			.each(function(){
				var utcTime = $(this).attr('title');
				$(this).text(utcTime);
			})
		.end();
	var lastRelist = $('<div>').append($discussionNodes).find('.xfd_relist').last().text();
	if ( lastRelist ) {
		$heading.addClass('xfdc-relisted');
	}
	var notTranscludedCorrectlyPatt = /(?:Automated|Procedural) (?:comment|Note).*transcluded.*/i;
	var notTranscludedCorrectlyMatch = $discussionNodes.text().match(notTranscludedCorrectlyPatt);
	var notTranscludedCorrectlyComment = notTranscludedCorrectlyMatch && notTranscludedCorrectlyMatch[0];

	var timestampPatt = /\d\d:\d\d, \d{1,2} \w+ \d{4} \(UTC\)/;
	var listingTimestampMatch = lastRelist.match(timestampPatt) ||
		notTranscludedCorrectlyComment && notTranscludedCorrectlyComment.match(timestampPatt) ||
		$discussionNodes.text().match(timestampPatt);
	var listingTimestampDate = listingTimestampMatch && dateFromSigTimestamp(listingTimestampMatch[0]);
	if ( !listingTimestampDate ) {
		$heading.addClass('xfdc-unknownAge')
	} else {
		var millisecondsSinceListing = new Date() - listingTimestampDate;
		var discussionRuntimeDays = 7;
		var discussionRuntimeMilliseconds = discussionRuntimeDays * 24 * 60 * 60 * 1000;
		var isOld = millisecondsSinceListing > discussionRuntimeMilliseconds;
		$heading.addClass((isOld ? 'xfdc-old' : 'xfdc-notOld'))
	}

	// Create status span and notices div with unique id based on headingIndex
	var uniqueID = 'XFDC' + headingIndex;
	$headlineSpan.after(
		$('<span>')
		.attr({'id':uniqueID, 'class':'xfdc-status'})
		.text('[نبح بند لوڈ ہو رہا ہے۔۔۔]')
	);
	$heading.after(
		$('<div>')
		.attr({'id':uniqueID+'-notices', 'class':'xfdc-notices'})
	);
	
	// Create discussion object
	return new Discussion({
		'id': uniqueID,
		'nomPage': nompage,
		'sectionHeader': sectionHeader,
		'sectionNumber': editsection,
		'pages': pages || [],
		'firstdate': firstdate,
		'isOld': !!isOld,
		'isRelisted': !!lastRelist
	});
};
// ---------- Discusssion prototype ------------------------------------------------------------- */
// Get status element (jQuery object)
Discussion.prototype.get$status = function() {
	return $('#'+this.id);
};
// Set status
Discussion.prototype.setStatus = function($status) {
	this.get$status().empty().append($status);
};
// Open dialog
Discussion.prototype.openDialog = function(relisting) {
	this.dialog = new Dialog(this, !!relisting);
	this.dialog.setup();
};
// Mark as finished
Discussion.prototype.setFinished = function(aborted) {
	var self = this;
	var msg;
	
	if ( aborted != null ) {
		msg = [
			$('<strong>').text( ( self.dialog && self.dialog.relisting ) ? 'کی نامزدگی کی کارروائی روک دی گئی' : 'کو بند کرنے کی کارروائی روک دی گئی' ),
			( aborted === '' ) ? '' : ': ' + aborted
		];		
	} else if ( self.dialog && self.dialog.relisting ) {
		msg = [
			'Discussion ',
			$('<strong>').text('مکرر نامزد کر دیا گیا'),
			' (صفحہ کو دوبارہ لوڈ کریں)'
		];
	} else {
		msg = [
			'گفتگو کو ',
			$('<strong>').text(self.taskManager.inputData.getResult()),
			' کے نتیجہ کے ساتھ بند کر دیا گیا (صفحہ کو دوبارہ لوڈ کریں)'
		];
	}
	self.setStatus(msg);
	self.get$status().prev().css('text-decoration', 'line-through');
};
// Get notices element (jQuery object)
Discussion.prototype.get$notices = function() {
	return $('#'+this.id+'-notices');
};
// Set notices element
Discussion.prototype.setNotices = function($content) {
	this.get$notices().empty().append($content);
};
// Get an array of page titles
Discussion.prototype.getPageTitles = function(pagearray, options) {
	var titles = (pagearray || this.pages).map(function(p) { 
		return p.getPrefixedText();
	});
	if ( options && options.moduledocs ) {
		return titles.map(function(t) {
			var isModule = ( t.indexOf('Module:') === 0 );
			return ( isModule ) ? t + '/doc' : t;
		});
	}
	return titles;
};
// Get an array of page' talkpage titles (excluding pages which are themselves talkpages)
Discussion.prototype.getTalkTitles = function(pagearray) {
	return (pagearray || this.pages).map(function(p) { 
		return p.getTalkPage().getPrefixedText();
	}).filter(function(t) { return t !== ''; });
};
// Get link text for a wikiink to the discussion - including anchor, except for AfDs/MfDs 
Discussion.prototype.getNomPageLink = function() {
	if (config.xfd.type === 'نبح' || config.xfd.type === 'mfd') {
		return this.nomPage;
	} else {
		return this.nomPage + '#' + mw.util.wikiUrlencode(this.sectionHeader).replace(/_/g, ' ');
	}
};
// Get nomination subpage
Discussion.prototype.getNomSubpage = function() {
	return this.nomPage.replace(config.xfd.subpagePath, '');
};
// Get page object by matching the title
Discussion.prototype.getPageByTitle = function(title, options) {
	var convertModuleDoc = ( options && options.moduledocs && title.indexOf('Module:') === 0 );
	var titleToCheck = ( convertModuleDoc ) ? title.replace(/\/doc$/,'') : title;

	var search = mw.Title.newFromText(titleToCheck).getPrefixedText();
	for ( var i=0; i<this.pages.length; i++ ) {
		if ( search === this.pages[i].getPrefixedText() ) {
			return this.pages[i];
		}
	}
	return false;
};
// Get page object by matching the talkpage's title
Discussion.prototype.getPageByTalkTitle = function(t) {
	var search = mw.Title.newFromText(t).getPrefixedText();
	for ( var i=0; i<this.pages.length; i++ ) {
		if ( search === this.pages[i].getTalkPage().getPrefixedText() ) {
			return this.pages[i];
		}
	}
	return false;
};

// Show links for closing/relisting
Discussion.prototype.showLinks = function(additonal) {
	// Preserve reference to self object
	var self = this;
	
	// Close link
	var $close = $('<span>')
	.addClass('xfdc-action')
	.append(
		'[',
		$('<a>')
		.attr('title', 'گفتگو بند کریں۔۔۔')
		.text('بند'),
		']'
	)
	.click(function() {
		self.setStatus('بند ہو رہا ہے۔۔۔');
		self.openDialog();
	});
	
	// Relist link
	var $relist = $('<span>')
	.addClass('xfdc-action')
	.append(
		'[',
		$('<a>')
		.attr({title:'گفتگو پھر شروع کی جا رہی ہے۔۔۔', class:'XFDcloser-link-relist'})
		.text('مکرر نامزدگی'),
		']'
	)
	.click(function() {
		self.setStatus('مکرر نامزد کیا جا رہا ہے۔۔۔');
		self.openDialog(true);
	});
	
	// quickKeep
	var $qk = $('<a>')
	.attr('title', 'فوری رکھیں: "رکھیں" کے نتیجہ کے ساتھ بند کریں، نامزدگی کے سانچے ہٹائیں، '+
		'اور تبادلہ خیال صفحہ پر گفتگو بند کے سانچے چسپاں کریں')
	.text('فوری رکھیں')
	.click(function(){
		var inputData = new InputData(self);
		inputData.result = 'رکھیں';
		inputData.after = 'doKeepActions';
		
		self.setStatus('بند ہو رہا ہے۔۔۔');
		self.taskManager = new TaskManager(self, inputData);
		self.taskManager.start();
	});

	// quickDelete
	var $qd = ( !config.user.isSysop && config.xfd.type !== 'tfd' ) ? '' : $('<a>')
	.attr({
		'title': 'فوری حذف: "حذف" کے نتیجہ کے ساتھ بند کریں، نامزد شدہ صفحات اور ان کے تبادلۂ خیال صفحے حذف کریں'+
			(( config.xfd.type === 'rfd' ) ? '' :' اور رجوع مکررات')+
			(( config.xfd.type === 'نبح' || config.xfd.type === 'ffd' ) ? '، روابط کو '+
				'غیر مربوط کریں (اختیاری)' : ''),
		'class': 'xfdc-qd'
		})
	.text('فوری حذف');
	if ( !config.user.isSysop && config.xfd.type == 'tfd' ) {
		$qd.attr('title', 'فوری حذف: "حذف" کے نتیجہ کے ساتھ بند کریں اور نامزد شدہ سانچوں پر '+
			'{{حذف کیا جا رہا ہے}} کا ٹیگ چسپاں کریں')
		.click(function(){
			var inputData = new InputData(self);
			inputData.result = 'حذف';
			inputData.after = 'holdingCell';
			inputData.holdcell = 'orphan';
			
			self.setStatus('بند ہو رہا ہے۔۔۔');
			self.taskManager = new TaskManager(self, inputData);
			self.taskManager.start();
		});
	} else if ( config.user.isSysop ) {
		$qd.click(function(){
			var inputData = new InputData(self);
			inputData.result = 'حذف';
			inputData.after = 'doDeleteActions';
			inputData.deleteredir = ( config.xfd.type === 'rfd' ) ? null : true;
			inputData.unlinkbackl = ( config.xfd.type === 'نبح' ||
				config.xfd.type === 'ffd' ) ? true : null;

			self.setStatus('بند ہو رہا ہے۔۔۔');
			self.taskManager = new TaskManager(self, inputData);
			self.taskManager.start();
		});
	}
	
	// quickClose links
	var $quick = $('<span>')
	.addClass('xfdc-action')
	.css('font-size', '92%')
	.append(
		'[',
		$('<a>')
			.attr('title', 'گفتگو کو فوراً بند کریں')
			.text('فوری بند')
			.click(function(){
				$(this).hide().next().show();
			}),
		$('<span>')
			.hide()
			.append(
				'&nbsp;',
				$qk,
				" ",
				$('<strong>').html('&middot;'),
				" ",
				$qd,
				'&nbsp;',
				$('<span>')
					.attr({title: 'منسوخ', class: 'xfdc-qc-cancel'})
					.html("&nbsp;م&nbsp;")
					.click(function(){
						$(this).parent().hide().prev().show();
					})
			),
		']'
	);


	//Add links in place of status
	self.setStatus([
		$close,
		( self.isBasicMode() ) ? '' : $quick,
		( config.xfd.type==='cfd') ? '' : $relist,
		additonal || ''
	]);
};
	
// Retrieve extra information - pages' existance, nomination date(s)
Discussion.prototype.retrieveExtraInfo = function() {
	// Preserve reference to discussion object
	var self = this;
	
	var pagesExistencesPromise = ( self.getPageTitles().length > 50 )
		? $.Deferred().reject('Too many pages') 
		: API.get( {
			action: 'query',
			titles: self.getPageTitles().join('|'),
			prop: 'info',
			inprop: 'talkid'
		} )
		.then(arrayFromResponsePages)
		.then(function(pages) {
		   for (var page of pages) {
				var pageObject = self.getPageByTitle(page.title);
				if ( !pageObject ) {
					return $.Deferred().reject('Unexpacted title `'+page.title+'`');
				}
				var pageExists = page.pageid > 0;
				var talkExists = page.talkid > 0;
				setExistence(pageObject, pageExists);
				setExistence(pageObject.getTalkPage(), talkExists);
			}
			return true;
		});
		
	var nominationDatePromise = ( config.xfd.type !== "afd" && config.xfd.type !== "mfd" )
		? $.Deferred().resolve(self.nomPage.split(config.xfd.path)[1])
		: API.get({
			action: 'query',
			titles: self.nomPage,
			prop: 'revisions',
			rvprop: 'timestamp',
			rvdir: 'newer',
			rvlimit: '1'
		})
		.then(pageFromResponse)
		.then(function(page) {
			var revisionDate = new Date(page.revisions[0].timestamp);
			return dmyDateString(revisionDate);
		});
	
	nominationDatePromise.then(function(nomDate) {
		self.nomDate = nomDate;
		// For an RfD with no first comment date detected, use the nom page date in dmy format
		if ( config.xfd.type === "rfd" && !self.firstDate ) {
			self.firstDate = nomDate.replace(/(\d+) (\w*) (\d+)/g, "$3 $2 $1");
		}
	});
	
	return $.when(pagesExistencesPromise, nominationDatePromise).then(
		function(){ return ''; },
		function(failMessage, jqxhr) {
			return $('<span>').addClass('xfdc-notice-error').append(
				'Error retrieving page information (reload the page to try again) ',
				$('<span>').addClass('xfdc-notice-error').append(
					makeErrorMsg(failMessage, jqxhr)
				)
			);
		}
	);
};

// Check if discussion is in 'basic' mode - i.e. no pages
Discussion.prototype.isBasicMode = function() {
	return !this.pages || this.pages.length === 0;
};


/* ========== Dialog class ======================================================================
   The user interface for closing/relisting XfD discussions 
   ---------------------------------------------------------------------------------------------- */
// Constructor
var Dialog = function(discussion, relist) {
	this.discussion = discussion;
	this.relisting = !!relist;
	
	// Make an new, empty, not-displayed dialog/interface window	
	this.interfaceWindow = new Morebits.simpleWindow(
		Math.min(900, Math.floor(window.innerWidth*0.8)),
		Math.floor(window.innerHeight*0.9)
	);
	this.interfaceWindow.setTitle( 'نبح بند' );
	this.interfaceWindow.setScriptName('(نسخہ' + config.script.version + ')');
	this.interfaceWindow.addFooterLink('رہنمائے اسکرپٹ', 'وپ:نبح_بند');
	this.interfaceWindow.addFooterLink('تاثرات', 'en:WT:XFDC');
	this.interfaceWindow.setContent(
		$('<div>')
		.attr('id', 'xfdc-dialog')
		.append(
			$('<div>').attr('id', 'xfdc-dialog-header'),
			$('<div>').attr('id', 'xfdc-dialog-body'),
			$('<div>').attr('id', 'xfdc-dialog-footer')
		)
		.get(0)
	);
	$('a.ui-dialog-titlebar-close.ui-corner-all').remove();
	$('#xfdc-dialog').parent().css('background-color', '#f0f0f0');
};

// ---------- Rcat multiselect widget ----------------------------------------------------------- */

/**
 * Dialog.RcatMultiselect
 * 
 * Creates a modified OOUI MenuTagMultiselectWidget with Rcat templates as options,
 * grouped as per https://en.wikipedia.org/wiki/Template:R_template_index
 *
 * @requires {Modules} oojs-ui-core, oojs-ui-widgets
 * @param {function} onChangeCallback({String[]} datas)
 *  Callback function for when the selected rcats change, is passed an array of Strings
 *  each of which is a selected rcat template (including  braces `{{` and `}}`). When all
 *  prior selections are removed, an empty array is passed.
 * @param {object} context
 *  The context to be used as the `this` value when executing the callback function
 * @param {jQuery} overlay
 *  Overlay element. If ommited, element with id `ejs-rcats-overlay` will be emptied and used
 *  if it exists, or else a div with that id will be created and used.
 * @return {LookupMenuTagMultiselectWidget}
 */
Dialog.RcatMultiselect = function(onChangeCallback, context, overlay) {

	// Extend OO.ui.MenuSelectWidget to use a more intuitive lookup system, such that
	// e.g. typing "section" brings up `{{R to section}}` as a match
	LookupMenuSelectWidget = function(config) {
		OO.ui.MenuSelectWidget.call(this, config);
	};
	LookupMenuSelectWidget.prototype = Object.create(OO.ui.MenuSelectWidget.prototype);
	LookupMenuSelectWidget.prototype.getItemMatcher = function ( s, exact ) {
		var re;
		if ( s.normalize ) {
			s = s.normalize();
		}
		s = exact ? s.trim() : s.replace( /^\s+/, '' );
		re = s.replace( /([\\{}()|.?*+\-^$[\]])/g, '\\$1' ).replace( /\s+/g, '\\s+' );
		if ( exact ) {
			re = '^\\s*' + re + '\\s*$';
		}
		re = new RegExp( re, 'i' );
		return function ( item ) {
			var matchText = item.getMatchText();
			if ( matchText.normalize ) {
				matchText = matchText.normalize();
			}
			return re.test( matchText );
		};
	};
	
	// Extend OO.ui.MenuTagMultiselectWidget to use a LookupMenuSelectWidget instead of
	// the standard MenuSelectWidget
	var LookupMenuTagMultiselectWidget = function ( config ) {
		OO.ui.MenuTagMultiselectWidget.call(this, config);
		// Override menu widget
		this.menu = new LookupMenuSelectWidget( $.extend(
			{
				widget: this,
				input: this.hasInput ? this.input : null,
				$input: this.hasInput ? this.input.$input : null,
				filterFromInput: !!this.hasInput,
				$autoCloseIgnore: this.hasInput ?
					this.input.$element : $( [] ),
				$floatableContainer: this.hasInput && this.inputPosition === 'outline' ?
					this.input.$element : this.$element,
				$overlay: this.$overlay,
				disabled: this.isDisabled()
			},
			config.menu
		) );
		this.menu.connect( this, {
			choose: 'onMenuChoose',
			toggle: 'onMenuToggle'
		} );
		if ( this.hasInput ) {
			this.input.connect( this, { change: 'onInputChange' } );
		}
		if ( this.$input ) {
			this.$input.prop( 'disabled', this.isDisabled() );
			this.$input.attr( {
				role: 'combobox',
				'aria-owns': this.menu.getElementId(),
				'aria-autocomplete': 'list'
			} );
		}
		if ( !this.popup ) {
			this.$content.append( this.$input );
			this.$overlay.append( this.menu.$element );
		}
		//this.onMenuItemsChange();
	};
	LookupMenuTagMultiselectWidget.prototype = Object.create(OO.ui.MenuTagMultiselectWidget.prototype);
	
	// Define new overlay, or use/reuse already defined element
	if ( overlay == null ) {
		if ( $('#ejs-rcats-overlay').length ) {
			overlay = $('#ejs-rcats-overlay').first().empty();
		} else {		
			overlay = $('<div>')
			.attr('id', 'ejs-rcats-overlay')
			.css({'z-index':'2000', 'position':'fixed', 'top':'0px', 'font-size':'85%'})
			.prependTo('body');
		}
	}
	
	// Make menu option widgets based on template name
	var newRcatOption = function(val) {
		return new OO.ui.MenuOptionWidget( {data: '{{'+val+'}}', label: '{{'+val+'}}'} );
	};
	
	// Make the widget
	var rcatMultiselectWidget = new LookupMenuTagMultiselectWidget( {
		allowArbitrary: true,
		$overlay: overlay,
		popup: false,
		menu: {
			items: [
				// Common Rcats
				new OO.ui.MenuSectionOptionWidget({label:'عمومی'}),
					newRcatOption('R to related topic'),
					newRcatOption('R from subtopic'),
					newRcatOption('R to list entry'),
					newRcatOption('R to section'),
				new OO.ui.MenuSectionOptionWidget({label:'متعلقہ معلومات'}),
					newRcatOption('R from album'),
					newRcatOption('R to article without mention'),
					newRcatOption('R from book'),
					newRcatOption('R to decade'),
					newRcatOption('R from domain name'),
					newRcatOption('R from top-level domain'),
					newRcatOption('R from film'),
					newRcatOption('R from gender'),
					newRcatOption('R from list topic'),
					newRcatOption('R from member'),
					newRcatOption('R to related topic'),
					newRcatOption('R from related word'),
					newRcatOption('R from phrase'),
					newRcatOption('R from school'),
					newRcatOption('R from song'),
					newRcatOption('R from subtopic'),
					newRcatOption('R to subtopic'),
					newRcatOption('R from Unicode'),
				new OO.ui.MenuSectionOptionWidget({label:'فکشن'}),
					newRcatOption('R from fictional character'),
					newRcatOption('R from fictional element'),
					newRcatOption('R from fictional location'),
					newRcatOption('R to TV episode list entry'),
				// Grammar, punctuation, and spelling
				new OO.ui.MenuSectionOptionWidget({label:'اختصار'}),
					newRcatOption('R to acronym'),
					newRcatOption('R from acronym'),
					newRcatOption('R to initialism'),
					newRcatOption('R from initialism'),
				new OO.ui.MenuSectionOptionWidget({label:'بڑے چھوٹے حروف'}),
					newRcatOption('R from CamelCase'),
					newRcatOption('R from other capitalisation'),
					newRcatOption('R from miscapitalisation'),
				new OO.ui.MenuSectionOptionWidget({label:'قواعد'}),
					newRcatOption('R from modification'),
					newRcatOption('R from plural'),
					newRcatOption('R to plural'),
				new OO.ui.MenuSectionOptionWidget({label:'اجزائے کلام'}),
					newRcatOption('R from adjective'),
					newRcatOption('R from adverb'),
					newRcatOption('R from common noun'),
					newRcatOption('R from gerund'),
					newRcatOption('R from proper noun'),
					newRcatOption('R from verb'),
				new OO.ui.MenuSectionOptionWidget({label:'املا'}),
					newRcatOption('R from alternative spelling'),
					newRcatOption('R from ASCII-only'),
					newRcatOption('R to ASCII-only'),
					newRcatOption('R from diacritic'),
					newRcatOption('R to diacritic'),
					newRcatOption('R from misspelling'),
					newRcatOption('R from stylization'),
				// Alternative names
				new OO.ui.MenuSectionOptionWidget({label:'متبادل نام (عمومی)'}),
					newRcatOption('R from alternative language'),
					newRcatOption('R from alternative name'),
					newRcatOption('R from former name'),
					newRcatOption('R from historic name'),
					newRcatOption('R from incorrect name'),
					newRcatOption('R from long name'),
					newRcatOption('R from portmanteau'),
					newRcatOption('R from short name'),
					newRcatOption('R from sort name'),
					newRcatOption('R from less specific name}'),
					newRcatOption('R from more specific name'),
					newRcatOption('R from synonym'),
					newRcatOption('R from antonym'),
				new OO.ui.MenuSectionOptionWidget({label:'متبادل نام (افراد)'}),
					newRcatOption('R from birth name'),
					newRcatOption('R from given name'),
					newRcatOption('R to joint biography'),
					newRcatOption('R from married name'),
					newRcatOption('R from name with title'),
					newRcatOption('R from personal name'),
					newRcatOption('R from pseudonym'),
					newRcatOption('R from surname'),
				new OO.ui.MenuSectionOptionWidget({label:'متبادل نام (تکنیکی)'}),
					newRcatOption('R from Java package name'),
					newRcatOption('R from molecular formula'),
					newRcatOption('R from technical name'),
					newRcatOption('R to technical name'),
					newRcatOption('R from trade name'),
				new OO.ui.MenuSectionOptionWidget({label:'متبادل نام (عضویے)'}),
					newRcatOption('R from scientific name'),
					newRcatOption('R from alternative scientific name'),
					newRcatOption('R to scientific name'),
				new OO.ui.MenuSectionOptionWidget({label:'متبادل نام (جغرافیہ)'}),
					newRcatOption('R from name and country'),
					newRcatOption('R from more specific geographic name'),
					newRcatOption('R from postal code'),
				// Navigation aids
				new OO.ui.MenuSectionOptionWidget({label:'ویکی پیمائی'}),
					newRcatOption('R to anchor'),
					newRcatOption('R avoided double redirect'),
					newRcatOption('R from file metadata link'),
					newRcatOption('R to list entry'),
					newRcatOption('R mentioned in hatnote'),
					newRcatOption('R to section'),
					newRcatOption('R from shortcut'),
					newRcatOption('R from template shortcut'),
				new OO.ui.MenuSectionOptionWidget({label:'ضد ابہام'}),
					newRcatOption('R from ambiguous term'),
					newRcatOption('R to anthroponymy page'),
					newRcatOption('R to disambiguation page'),
					newRcatOption('R from incomplete disambiguation'),
					newRcatOption('R from incorrect disambiguation'),
					newRcatOption('R from other disambiguation'),
					newRcatOption('R from unnecessary disambiguation'),
				new OO.ui.MenuSectionOptionWidget({label:'ضم، مکرر اور منتقلی'}),
					newRcatOption('R from duplicated article'),
					newRcatOption('R with history'),
					newRcatOption('R from merge'),
					newRcatOption('R from move'),
					newRcatOption('R with old history'),
				new OO.ui.MenuSectionOptionWidget({label:'درج ذیل نام فضا میں'}),
					newRcatOption('R to category namespace'),
					newRcatOption('R to draft namespace'),
					newRcatOption('R to help namespace'),
					newRcatOption('R to main namespace'),
					newRcatOption('R to portal namespace'),
					newRcatOption('R to project namespace'),
					newRcatOption('R to talk page'),
					newRcatOption('R to template namespace'),
					newRcatOption('R to user namespace'),
				// Miscellaneous
				new OO.ui.MenuSectionOptionWidget({label:'آئیسو رموز'}),
					newRcatOption('R from ISO 4'),
					newRcatOption('R from ISO 639 code'),
					newRcatOption('R from ISO 3166 code'),
					newRcatOption('R from ISO 4217 code'),
					newRcatOption('R from ISO 15924 code'),
				new OO.ui.MenuSectionOptionWidget({label:'متفرقات'}),
					newRcatOption('R printworthy'),
					newRcatOption('R unprintworthy'),
					newRcatOption('Wikidata redirect')
			]
		}
	} );
	
	// Execute callback when selector data changes
	rcatMultiselectWidget.on('change', function(selectedOptions) {
		onChangeCallback.call(
			context,
			selectedOptions.map(function(optionWidget) {
				return optionWidget.data;
			})
		);
	});
	
	return rcatMultiselectWidget;
	
};

// ---------- Dialog prototype ------------------------------------------------------------------ */

// --- Basic manipulation: ---
// Append content to header
Dialog.prototype.addToHeader = function($content) {
	$('#xfdc-dialog-header').append($content);
};
// Append content to body
Dialog.prototype.addToBody = function($content) {
	$('#xfdc-dialog-body').append($content);
};
// Append content to footer
Dialog.prototype.addToFooter = function($content) {
	$('#xfdc-dialog-footer').append($content);
};
// Clear dialog
Dialog.prototype.emptyContent = function() {
	$('#xfdc-dialog-header, #xfdc-dialog-body, #xfdc-dialog-footer').empty();
};
// Display dialog
Dialog.prototype.display = function() {
	this.interfaceWindow.display();
};
// Reset height
Dialog.prototype.resetHeight = function() {
	this.interfaceWindow.setHeight(Math.floor(window.innerHeight*0.9));
};
// Close dialog
Dialog.prototype.close = function() {
	this.interfaceWindow.close();
	$('#ejs-rcats-overlay').remove();
};

// --- Make interface elements: ---
Dialog.prototype.makeHeader = function(multimode) {
	var self = this;
	
	// Title, pagecount
	var $header = $('<h4>')
	.attr('id', 'closeXFD-interface-header')
	.append(
		$('<span>')
		.attr('id', 'closeXFD-interface-header-action')
		.append(
			$('<em>').text('«' + self.discussion.sectionHeader + '»'),
			( self.relisting ) ? 'کو مکرر نامزد کیا جا رہا ہے ' : 'پر گفتگو بند کی جا رہی ہے ',
			' ',
			$('<span>')
			.attr({'id':'closeXFD-pagecount', 'class':'xfdc-bracketed'})
			.css('font-size', '80%')
			.append(
				$('<span>')
					.attr('id', 'closeXFD-pagecount-pages')
					.css({'background':'#ff8', 'padding':'1px'})
					.text(
						( self.discussion.isBasicMode() )
						  ? 'basic mode only'
						  : self.discussion.pages.length +
						( ( self.discussion.pages.length === 1 ) ? ' صفحہ' : ' صفحے' )
					),
				( multimode ) ? '' : ' ',
				( multimode ) ? '' : $('<a>').attr('id', 'closeXFD-pagecount-show').text('[دکھائیں]'),
				( multimode ) ? '' : $('<a>').attr('id', 'closeXFD-pagecount-hide').text('[چھپائیں]').hide()
			)
		)
	);
	$header.find('#closeXFD-pagecount-show, #closeXFD-pagecount-hide').click(function() {
		$('#closeXFD-nominatedpages, #closeXFD-pagecount-show, #closeXFD-pagecount-hide').toggle();
	});	
	
	// Multi/single-mode button
	if ( !self.relisting && !self.discussion.isBasicMode() && self.discussion.pages.length > 1 ) {
		$header.prepend(
			$('<button>')
			.css('float', 'right')
			.text(( multimode ) ? 'ایک نتیجہ۔۔۔' : 'متعدد نتائج۔۔۔')
			.click(function() {
				if ( multimode ) {
					self.setup();
				} else {
					self.setupForMultiClose();
				}
			})
		);
	}
	
	return $header;
};

Dialog.prototype.makePagesList = function(multimode) {
	var self = this;

	// Template for per-page actions for multimode
	var $action_dropdown = $('<select>')
	.attr('class', 'closeXFD-pagelist-actionDropdown')
	.css('margin-right', '0.5em')
	.append(
		$('<option>').attr('value', 'default').text(''),
		$('<option>').attr('value', 'keep').text('رکھیں'),
		( config.xfd.type === 'ffd' ) ? '' : $('<option>')
			.attr('value', 'redirect').text('رجوع مکرر'),
		( config.xfd.type === 'ffd' || config.xfd.type === 'rfd' ) ? '' : $('<option>')
			.attr('value', 'merge').text('ضم'),
		( config.xfd.type !== 'rfd' ) ? '' : $('<option>')
			.attr('value', 'disambig').text('ضد ابہام'),
		$('<option>').attr('value', 'del').text('حذف'),
		$('<option>').attr('value', 'na').text('(کوئی کارروائی نہیں)')
	);
	var $target = $('<span>')
	.addClass('closeXFD-pagelist-target')
	.append(
		' to:',
		$('<input>').attr('type', 'text')
	)
	.css('display', 'none');

	
	// List of pages (or info on basic mode)
	var $pagesList = $('<ul>')
	.attr('id', 'closeXFD-nominatedpages')
	.css('font-size', '95%');
	if ( !self.discussion.isBasicMode() ) {
		for ( var i=0; i<self.discussion.pages.length; i++ ) {
			var pageTitle = self.discussion.pages[i].getPrefixedText();
			$('<li>')
			.append(
				( !multimode ) ? '' : $action_dropdown
					.clone()
					.attr('id', 'closeXFD-pagelist-action-'+i)
					.data('pageTitle', pageTitle),
				$('<span>').addClass('closeXFD-pagelist-title').text(pageTitle),
				' ',
				( !multimode ) ? '' : $target
					.clone()
					.attr('id', 'closeXFD-pagelist-target-'+i)
					.data('pageTitle', pageTitle)
			)
			.appendTo($pagesList);
			if ( multimode ) {
				self.inputData.pageActions[pageTitle] = { 'id':'closeXFD-pagelist-action-'+i };
			}
		}
	} else {
		$('<li>')
		.append(
			'Nominated pages were not detected. You can still ',
			( self.relisting ) ? 'relist' : 'close',
			' this ',
			config.xfd.type.toUpperCase(),
			' discussion, but updating the nomination templates will need to be done manually '+
			'&ndash; see ',
			extraJs.makeLink('WP:'+config.xfd.type.toUpperCase()+'AI'),
			' for instructions.'
		)
		.appendTo($pagesList);
	}
	
	// On changing a multimode action, show/hide corresponding options as appropriate
	$pagesList.find('select').change(function(){
		if ( $(this).children().first().val() === 'default' ) {
			$(this).children().first().remove();
		}
		var $li = $(this).parent();
		var v = $(this).val();
		var t = $li.find('.closeXFD-pagelist-title').text();
		self.inputData.pageActions[t].action = v;
		if ( v === 'redirect' || v === 'merge' ) {
			$li.find('.closeXFD-pagelist-target').show();
		} else {
			$li.find('.closeXFD-pagelist-target').val('').change().hide();
		}
		self.updateMultimodeOptions();
	});
	$pagesList.find('input').change(function(){
		var t = $(this).parent().parent().find('.closeXFD-pagelist-title').text();
		self.inputData.pageActions[t].target = mw.Title.newFromText(
			$(this).val().trim()
		);
	});
	if ( !multimode ) {
		$pagesList.hide();
	}
	return $pagesList;
};

Dialog.prototype.makeNonAdminNote = function() {
	var $NACD_note = $('<div>')
	.css({'text-align':'center', 'clear':'both'})
	.append(
		$('<em>')
		.append(
			'See the ',
			extraJs.makeLink('WP:NACD'),
			' guideline for advice on appropriate and inappropriate closures'
		),
		$('<hr>')
	);
	return $NACD_note;
};

Dialog.prototype.makeCloseResult = function() {
	var self = this;
	
	$resultContainer = $('<div>')
	.attr('id', 'closeXFD-resultContainer')
	.css({'float':'right', 'width':'99%', 'padding-bottom':'1%'})
	.append(
		$('<div>').append(
			$('<strong>').text('نتیجہ'),
			// Speedy
			$('<span>').addClass('xfdc-dialog-bracketedOption').append(
				$('<input>')
				.attr({'type':'checkbox', 'name':'closeXFD-speedy', 'id':'closeXFD-speedy'})
				.change(function(){
					self.inputData.speedy = ( $(this).prop('checked') );
				}),
				$('<label>').attr('for', 'closeXFD-speedy').text('فوری')
			)
		),
		$('<div>').attr('id', 'closeXFD-resultOptions').css('overflow','hidden').append(
			// Keep
			$('<span>').attr('class', 'xfdc-dialog-option').append(
				$('<input>').attr({'type':'radio', 'name':'closeXFD-result',
					'class':'closeXFD-keepResult', 'id':'closeXFD-result-keep', 'value':'رکھیں'}),
				$('<label>').attr('for', 'closeXFD-result-keep').text('رکھیں')
			),
			// Redirect
			( config.xfd.type === 'ffd' ) ? '' : $('<span>')
			.attr('class', 'xfdc-dialog-option').append(
				$('<input>').attr({'type':'radio', 'name':'closeXFD-result',
					'class':'closeXFD-keepResult', 'id':'closeXFD-result-redir',
					'value':(( config.xfd.type === 'rfd' ) ? 'retarget' : 'redirect')}),
				$('<label>')
				.attr('for', 'closeXFD-result-redir')
				.text(( config.xfd.type === 'rfd' ) ? 'Retarget' : 'رجوع مکرر'),
				( !config.user.isSysop ) ? '' : $('<label>')
				.attr('for', 'closeXFD-result-redir')
				.text(( config.xfd.type === 'rfd' ) ? 'Delete and retarget' : 'حذف اور رجوع مکرر')
				.hide()
			),
			// Soft redirect
			( config.xfd.type !== 'rfd' ) ? '' : $('<span>')
			.attr('class', 'xfdc-dialog-option').append(
				$('<input>').attr({'type':'radio', 'name':'closeXFD-result',
					'class':'closeXFD-keepResult', 'id':'closeXFD-result-softredir',
					'value':'soft redirect'}),
				$('<label>').attr('for', 'closeXFD-result-softredir').text('ملائم رجوع مکرر'),
				( !config.user.isSysop ) ? '' : $('<label>')
				.attr('for', 'closeXFD-result-softredir').text('حذف اور ملائم رجوع مکرر').hide()
			),	
			// No consensus
			$('<span>').attr('class', 'xfdc-dialog-option').append(
				$('<input>').attr({'type':'radio', 'name':'closeXFD-result',
					'class':'closeXFD-keepResult', 'id':'closeXFD-result-nocon',
					'value':'no consensus'}),
				$('<label>').attr('for', 'closeXFD-result-nocon').text('عدم اتفاق')
			),
			// Merge
			( config.xfd.type === 'ffd' || config.xfd.type === 'rfd' ) ? '' : $('<span>')
			.attr('class', 'xfdc-dialog-option').append(
				$('<input>').attr({'type':'radio', 'name':'closeXFD-result',
					'id':'closeXFD-result-merge', 'value':'merge'}),
				$('<label>').attr('for', 'closeXFD-result-merge').text('ضم')
			),	
			// Disambiguate
			( config.xfd.type !== 'rfd' ) ? '' : $('<span>')
			.attr('class', 'xfdc-dialog-option').append(
				$('<input>').attr({'type':'radio', 'name':'closeXFD-result',
					'id':'closeXFD-result-disambig', 'value':'disambiguate'}),
				$('<label>').attr('for', 'closeXFD-result-disambig').text('ضد ابہام')
			),			
			// Delete
			( !config.user.isSysop && config.xfd.type !== 'tfd') ? '' : $('<span>')
			.attr('class', 'xfdc-dialog-option').append(
				$('<input>').attr({'type':'radio', 'name':'closeXFD-result',
				'class':'closeXFD-deleteResult', 'id':'closeXFD-result-delete', 'value':'حذف'}),
				$('<label>').attr('for', 'closeXFD-result-delete').text('حذف')
			),
			// Delete (disabled)
			( config.user.isSysop || config.xfd.type === 'tfd') ? '' : $('<span>')
			.attr('class', 'xfdc-dialog-option').append(
				$('<input>').attr({'type':'radio', 'name':'closeXFD-result',
					'class':'closeXFD-deleteResult', 'id':'closeXFD-result-delete-disabled',
					'disabled':'disabled'}),
				$('<label>').attr('for', 'closeXFD-result-delete-disabled').append(
					'Delete ',
					extraJs.makeTooltip('Non-admin closure is not appropriate when the result '+
						'will require action by an administrator (per [[WP:BADNAC]])')
				)
			),
			// Soft delete
			( !config.user.isSysop || config.xfd.type === 'rfd' ) ? '' : $('<span>')
			.attr({'class':'xfdc-dialog-option', 'id':'closeXFD-resultContainer-softdel'}).append(
				$('<input>').attr({'type':'radio', 'name':'closeXFD-result',
					'class':'closeXFD-deleteResult', 'id':'closeXFD-result-softdel',
					'value':'soft delete'}),
				$('<label>').attr('for', 'closeXFD-result-softdel').text('ملائم حذف')
			),
			// Custom
			$('<span>').attr('class', 'xfdc-dialog-option').append(
				$('<input>').attr({'type':'radio', 'name':'closeXFD-result',
					'id':'closeXFD-result-custom', 'value':'custom'}),
				$('<label>').attr('for', 'closeXFD-result-custom').text('شخصی')
			)
		),
		$('<div>')
		.attr({'id':'closeXFD-resultContainer-custom'})
		.css({'clear':'both', 'padding-bottom':'1%', 'display':'none'})
		.append(
			$('<label>').attr('for', 'closeXFD-result-custom-input').text('شخصی نتیجہ:'),
			$('<input>')
			.attr({'type':'text', 'name':'closeXFD-result-custom-input',
				'id':'closeXFD-result-custom-input'})
			.change(function(){
				self.inputData.customResult = $('#closeXFD-result-custom-input').val().trim();
			})
		),
		// Target page
		$('<div>')
		.attr({'id':'closeXFD-resultContainer-target'})
		.css({'clear':'both', 'padding-bottom':'1%', 'display':'none'})
		.append(
			$('<label>').attr({'id':'closeXFD-result-target-label', 'for':'closeXFD-result-target'})
			.text('Target:'),
			$('<input>')
			.attr({'type':'text', 'name':'closeXFD-result-target', 'id':'closeXFD-result-target'})
			.change(function(){
				self.inputData.target = mw.Title.newFromText(
					$('#closeXFD-result-target').val().trim()
				);
			})
		)
	);
	
	$resultContainer.find('span.xfdc-dialog-option').after('<wbr>');
	
	$resultContainer.find("input[type=radio][name='closeXFD-result']").change(function(){
		var v = $(this).val();
		self.inputData.result = v;
		var currentAfter = $('input[name="closeXFD-after"]:checked').val() || false;
		// Show/hide options
		switch(v) {
			default:
			case 'رکھیں':
			case 'no consensus':
				$('.closeXFD-keepOptions').show();
				$('.closeXFD-redirectOptions, .closeXFD-mergeOptions, .closeXFD-disambigOptions, .closeXFD-deleteOptions, '+
				'#closeXFD-customOptions, #closeXFD-resultContainer-target, '+
				'#closeXFD-resultContainer-custom').hide();
				// default options:
				if (
					!currentAfter ||
					( currentAfter !== 'doKeepActions' && currentAfter !== 'noAction' )
				) {
					$('#closeXFD-after-doKeepActions').prop('checked', true).change();
				}
				break;
			case 'retarget':
			case 'redirect':
			case 'soft redirect':
				$('.closeXFD-redirectOptions, #closeXFD-resultContainer-target').show();
				$('.closeXFD-keepOptions, .closeXFD-mergeOptions, .closeXFD-disambigOptions, .closeXFD-deleteOptions, '+
					'#closeXFD-customOptions, #closeXFD-resultContainer-custom').hide();
				$('#closeXFD-result-target-label').text('اس سے رجوع مکرر بنائیں:');
				// default options:
				if (
					!currentAfter ||
					( currentAfter !== 'doRedirectActions' && currentAfter !== 'noAction' )
				) {
					$('#closeXFD-after-doRedirectActions')
					.prop('checked', true).change();
				}
				break;
			case 'merge':
				$('.closeXFD-mergeOptions, #closeXFD-resultContainer-target').show();
				$('.closeXFD-keepOptions, .closeXFD-redirectOptions, .closeXFD-disambigOptions, .closeXFD-deleteOptions, '+
					'#closeXFD-customOptions, #closeXFD-resultContainer-custom').hide();
				$('#closeXFD-result-target-label').text('اس صفحہ میں ضم کریں:');
				// default options:
				if (
					!currentAfter ||
					( currentAfter !== 'doMergeActions' && currentAfter !== 'noAction' )
				) {
					$('#closeXFD-after-doMergeActions').prop('checked', true).change();
				}
				break;
			case 'disambiguate':
				$('.closeXFD-disambigOptions').show();
				$('.closeXFD-keepOptions, .closeXFD-redirectOptions, .closeXFD-mergeOptions, .closeXFD-deleteOptions, '+
				'#closeXFD-customOptions, #closeXFD-resultContainer-target, '+
				'#closeXFD-resultContainer-custom').hide();
				// default options:
				if (
					!currentAfter ||
					( currentAfter !== 'doDisambigActions' && currentAfter !== 'noAction' )
				) {
					$('#closeXFD-after-doDisambigActions').prop('checked', true).change();
				}
				break;
			case 'حذف':
			case 'soft delete':
				$('.closeXFD-deleteOptions').show();
				$('.closeXFD-keepOptions, .closeXFD-redirectOptions, .closeXFD-mergeOptions, .closeXFD-disambigOptions, '+
					'#closeXFD-customOptions, #closeXFD-resultContainer-target, '+
					'#closeXFD-resultContainer-custom').hide();
				// default options:
				if (
					!currentAfter ||
					( currentAfter !== 'doDeleteActions' &&
					currentAfter !== 'holdingCell' &&
					currentAfter !== 'noAction' )
				) {
					$('#closeXFD-after-' +
					(( config.user.isSysop ) ? 'doDeleteActions' : 'holdingCell') +
					', #closeXFD-del-deleteredir, #closeXFD-del-unlinkbackl')
					.prop('checked', true).change();
				}
				// When soft delete is selected, prepend rationale with REFUND message
				if ( v === 'soft delete' ) {
					var old_rationale = $('#closeXFD-rationale').val();
					if (!old_rationale.includes('[[WP:REFUND]]')) {
						$('#closeXFD-rationale').val('[[وپ:بحالی]] کا اطلاق ہوگا۔ ' + old_rationale)
						.change();
					}
				}
				break;
			case 'custom':
				$('#closeXFD-customOptions, #closeXFD-resultContainer-custom, '+
				'.closeXFD-keepOptions, .closeXFD-deleteOptions').show();
				$('.closeXFD-redirectOptions, .closeXFD-mergeOptions, .closeXFD-disambigOptions, '+
				'#closeXFD-resultContainer-target').hide();
				// default options:
				if ( !currentAfter || currentAfter !==  'noAction' ) {
					$('#closeXFD-after-noAction').prop('checked', true).change();
				}
		}
		self.resetHeight();
	});
	
	return $resultContainer;

};

Dialog.prototype.makeRationaleBox = function(multimode) {
	var self = this;
	
	var $rationale = $('<div>')
	.css('clear', 'both')
	.append(
		$('<strong>').attr('id', 'closeXFD-rationale-label')
		.text( ( self.relisting ) ? 'مکرر نامزدگی کا سبب' : (( multimode ) ? 'Rationale' : 'اضافی '+
			'وجہ')),
		( !multimode ) ? ' (اختیاری):' : $('<a>')
		.addClass('xfdc-dialog-bracketedOption')
		.text('copy from above')
		.click(function(){
			var results = '';
			for ( var p in self.inputData.pageActions ) {
				var a = self.inputData.pageActions[p].action;
				if ( a ) {
					a = a
					.replace('del', 'delete')
					.replace(/(default|na)/, ' ');
				} else {
					a = ' ';
				}
				var t = ( /(merge|redirect)/.test(a) ) ? ' to [[' +
					self.inputData.pageActions[p].target + ']]\n' : '\n';
				results += "*'''" +	extraJs.toSentenceCase(a) + "''' [[" + p + "]]" + t;
			}
			$('#closeXFD-rationale').val(results + self.inputData.rationale).change();
		}),
		$('<br>'),
		$('<textarea>')
		.attr({'id':'closeXFD-rationale', 'rows':(( self.relisting ) ? 2 : 4)})
		.css('width', '99%')
		.change(function(){
			self.inputData.rationale = $(this).val().trim();
		})
		.change(),
		( self.relisting ) ? '' : $('<div>')
		.css({'clear':'both', 'padding-bottom':'1%'})
		.append(
			$('<input>')
			.attr({'type':'checkbox', 'name':'closeXFD-rationale-sentence',
				'id':'closeXFD-rationale-sentence'})
			.change(function(){
				if ( $(this).prop('checked') ) {
					self.inputData.resultPunct = '';
				} else {
					self.inputData.resultPunct = '';
				}
			})
			.change(),
			$('<label>').attr('for', 'closeXFD-rationale-sentence')
			.text('پہلے سے درج شدہ وجہ استعمال کریں')
		)
	);
	
	if ( multimode ) {
		$('<div>')
		.css({'clear':'both', 'padding-bottom':'1%'})
		.append(
			$('<label>').attr('for', 'closeXFD-resultSummary-input').append(
				'Result summary',
				extraJs.makeTooltip('Appears in bold text before the rationale; also used in '+
					'edit summaries, and in Old ' + config.xfd.type + ' templates'),
				': '
			),
			$('<input>')
			.attr({'type':'text', 'name':'closeXFD-resultSummary-input',
				'id':'closeXFD-resultSummary-input'})
			.change(function(){
				self.inputData.result = $(this).val().trim();
			})
		)
		.prependTo($rationale);
	}
	
	return $rationale;
};

Dialog.prototype.makeAfterActions = function(multimode) {
	var self = this;

	var $after = $('<div>')
	.attr('id', 'closeXFD-afterContainer')
	.addClass('xfdc-dialog-container')
	.css('max-width', ( multimode ) ? '30%' : '40%')
	.append(
		$('<strong>').text('بند کرنے کے بعد:').css('display','block'),
		// KeepActions
		$('<div>')
		.addClass('closeXFD-keepOptions')
		.hide()
		.append(
			( multimode ) ? $('<strong>')
			.text('"رکھیں" pages: ') : $('<input>')
			.attr({
				'type':'radio',
				'name':'closeXFD-after',
				'id':'closeXFD-after-doKeepActions',
				'value':'doKeepActions'
			}),
			$(( multimode ) ? '<span>' : '<label>')
			.attr('for', 'closeXFD-after-doKeepActions')
			.append(
				'صفحات اور تبادلۂ خیال صفحات کی تجدید کریں ',
				extraJs.makeTooltip('نامزدگی کے سانچے ہٹائیں اور گفتگو بند کے سانچے چسپاں کریں '+
					'to talk pages')
			)
		),
		// RedirectActions
		$('<div>')
		.addClass('closeXFD-redirectOptions')
		.hide()
		.append(
			( multimode ) ? $('<strong>')
			.text('"Redirect" pages: ') : $('<input>').attr({
				'type':'radio',
				'name':'closeXFD-after',
				'id':'closeXFD-after-doRedirectActions',
				'value': 'doRedirectActions'
			}),
			$(( multimode ) ? '<span>' : '<label>')
			.attr('for', 'closeXFD-after-doRedirectActions')
			.append(
				'رجوع مکرر بنائیں اور تبادلۂ خیال صفحہ کی تجدید کریں ',
				extraJs.makeTooltip('متعین ہدف کی جانب صفحہ کو رجوع مکرر کریں '+
					'اور تبادلۂ خیال صفحہ میں گفتگو بند کے سانچے چسپاں کریں')
			)
		),
		// MergeActions
		$('<div>')
		.addClass('closeXFD-mergeOptions')
		.hide()
		.append(
			( multimode ) ? $('<strong>')
			.text('"Merge" pages: ') : $('<input>').attr({
				'type':'radio',
				'name':'closeXFD-after',
				'id':'closeXFD-after-doMergeActions',
				'value': 'doMergeActions'
			}),
			$(( multimode ) ? '<span>' : '<label>')
			.attr('for', 'closeXFD-after-doMergeActions')
			.append(
				'صفحات اور تبادلۂ خیال صفحات کی تجدید کریں ',
				extraJs.makeTooltip('نامزدگی کے سانچے کو ضم کے سانچے سے بدلیں '+
					'اور تبادلہ خیال صفحہ میں گفتگو بند کا سانچہ چسپاں کریں')
			)
		),
		// DisambigActions
		$('<div>')
		.addClass('closeXFD-disambigOptions')
		.hide()
		.append(
			( multimode ) ? $('<strong>')
			.text('"Disambiguate" pages: ') : $('<input>')
			.attr({
				'type':'radio',
				'name':'closeXFD-after',
				'id':'closeXFD-after-doDisambigActions',
				'value':'doDisambigActions'
			}),
			$(( multimode ) ? '<span>' : '<label>')
			.attr('for', 'closeXFD-after-doDisambigActions')
			.append(
				'صفحات اور تبادلۂ خیال صفحات کی تجدید کریں ',
				extraJs.makeTooltip('نامزدگی کے سانچے ہٹائیں اور گفتگو بند کے سانچے چسپاں کریں '+
					'to talk pages')
			)
		),
		// Delete actions
		( multimode || !config.user.isSysop ) ? '' : $('<div>')
		.addClass('closeXFD-deleteOptions')
		.hide()
		.append(
			$('<input>').attr({
				'type':'radio',
				'name':'closeXFD-after',
				'id':'closeXFD-after-doDeleteActions',
				'value': 'doDeleteActions'
			}),
			$('<label>').attr('for', 'closeXFD-after-doDeleteActions').append(
				'صفحات حذف کریں ',
				extraJs.makeTooltip('نامزد شدہ صفحات اور ان کے تبادلہ خیال صفحے '+
					'حذف کریں')
			)
		),
		// Holding cell
		( multimode || config.xfd.type !== 'tfd' ) ? '' : $('<div>')
		.addClass('closeXFD-deleteOptions')
		.hide()
		.append(
			$('<input>').attr({
				'type':'radio',
				'name':'closeXFD-after',
				'id':'closeXFD-after-holdingCell',
				'value':'holdingCell'
			}),
			$('<label>').attr('for', 'closeXFD-after-holdingCell').append(
				'List pages at holding cell ',
				extraJs.makeTooltip('Replace nomination template with {{Being deleted}}, '+
					'and list at the specified holding cell section')
			)
		),
		// Multimode delete/holding cell
		( !multimode ) ? '' :  $('<div>')
		.addClass('closeXFD-deleteOptions')
		.hide()
		.append(
			$('<strong>').text('"Delete" pages: '),
			$('<span>').append(
				( !config.user.isSysop ) ? '' : 'صفحات حذف کریں ',
				( !config.user.isSysop ) ? '' : extraJs.makeTooltip('نامزد شدہ صفحات اور ان کے تبادلہ خیال صفحے '+
					'حذف کریں'),
				( config.xfd.type !== 'tfd' ) ? '' : (( config.user.isSysop ) ? ' or l' : 'L') +
					'ist pages at holding cell ',
				( config.xfd.type !== 'tfd' ) ? '' : extraJs.makeTooltip('Replace nomination '+
					'template with {{Being deleted}}, and list at the specified '+
					'holding cell section')
			)
		),
		// No actions
		$('<div>')
		.attr('id', 'closeXFD-noAction')
		.toggle(!multimode)
		.append(
			( multimode ) ? $('<strong>')
			.text('"(no action)" pages: ') : $('<input>').attr({
				'type':'radio',
				'name':'closeXFD-after',
				'id':'closeXFD-after-noAction',
				'value':'noAction'
			}),
			$(( multimode ) ? '<span>' : '<label>')
			.attr('for', 'closeXFD-after-noAction').text('کوئی خودکار کارروائی نہیں')
		)
	)
	.find('input').change(function() {
		self.inputData.after = $(this).val();
		// Hide options if no action selected, or 'doKeepActions' is selected
		$('#closeXFD-optionsContainer').toggle(!/(noAction|doKeepActions)/.test(self.inputData.after));
		// Show or hide holding cell sections and 'delete redirects', if holding cell selected
		if ( config.xfd.type === 'tfd' ) {
			$('#closeXFD-del-holdcell').toggle(self.inputData.after === 'holdingCell');
			$('#closeXFD-del-deleteredir').parent()
			.toggle(self.inputData.after === 'doDeleteActions');
			// Disable 'don't delete talk' option for holding cell (except 'ready for deletion')
			if ( self.inputData.after === 'holdingCell' && self.inputData.holdcell !== 'ready' ) {
				$('#closeXFD-del-deletetalk').prop('disabled', true)
				.next().addClass('xfdc-dialog-disabled');
			} else {
				$('#closeXFD-del-deletetalk').prop('disabled', false)
				.next().removeClass('xfdc-dialog-disabled');
			}
		}
	} )
	.end();
	
	if ( multimode ) {
		$after.children('div').addClass('xfdc-dialog-actionInfo');
	}
	
	return $after;

};

Dialog.prototype.makeOptions = function(multimode) {
	var self = this;
	
	var $options = $('<div>')
	.attr('id', 'closeXFD-optionsContainer')
	.append(
		// Redirect options:
		( config.xfd.type === 'ffd' ) ? '' : $('<div>')
		.addClass('closeXFD-redirectOptions')
		.hide()
		.append(
			$('<strong>').text(( multimode ) ? 'رجوع مکرر کے اختیارات' : 'اختیارات')
			.css('display','block'),
			// Delete before redirecting
			( !config.user.isSysop ) ? '' : $('<div>').append(
				$('<input>')
				.attr({
					'type':'checkbox',
					'name':'closeXFD-redir-deleteFirst',
					'id':'closeXFD-redir-deleteFirst'
				})
				.change(function(){
					self.inputData.deleteFirst = $(this).prop('checked');
					if ( !multimode ) {
						// toggle result labels
						$('#closeXFD-result-redir').nextAll().toggle();
						$('#closeXFD-result-softredir').nextAll().toggle();
					}
				}),
				$('<label>').attr('for', 'closeXFD-redir-deleteFirst')
				.text('رجوع مکرر بنانے سے قبل حذف کریں')
			),
			// Rcats
			$('<div>').append(
				$('<input>').attr({
					'type':'checkbox',
					'name':'closeXFD-redir-addRcats',
					'id': 'closeXFD-redir-rcats',
					'disabled': true
				}).prop('checked', ( config.xfd.type === 'نبح' )),
				$('<label>').attr('for', 'closeXFD-redir-rcats').append(
					$('<a>')
					.attr({'href':'https://ur.wikipedia.org/wiki/Template:R_template_index',
						'target':'_blank'}).text('رجوع مکرر کی نوعیت'),
					": ",
					extraJs.makeTooltip('Full markup required, e.g. "{{R to section}}";. '+
						'Multiple rcats can be specified, e.g. "{{R from book}}'+
						'{{R to anchor}}". Leave blank if unsure which rcat to use.')
				),
				self.rcatSelector.$element.detach()
			)
		),
		
		// Merge options: 
		( config.xfd.type !== 'tfd' ) ? '' : $('<div>')
		.attr('id', 'closeXFD-merge-holdcell')
		.addClass('closeXFD-mergeOptions')
		.hide()
		.append(
			$('<strong>').text('Holding cell merge subsection:').css('display','block'),
			// Arts
			$('<div>').append(
				$('<input>').attr({'type':'radio', 'name':'closeXFD-merge-holdingcell',
					'id':'closeXFD-holdingcell-merge-arts', 'value':'merge-arts'}),
				$('<label>').attr('for', 'closeXFD-holdingcell-merge-arts').text('Merge (Arts)')
			),
			// Geography, politics and governance
			$('<div>').append(
				$('<input>').attr({'type':'radio', 'name':'closeXFD-merge-holdingcell',
					'id':'closeXFD-holdingcell-merge-geopolgov', 'value':'merge-geopolgov'}),
				$('<label>').attr('for', 'closeXFD-holdingcell-merge-geopolgov')
				.text('Merge (Geography, politics and governance)')
			),
			// Religion
			$('<div>').append(
				$('<input>').attr({'type':'radio', 'name':'closeXFD-merge-holdingcell',
					'id':'closeXFD-holdingcell-merge-religion', 'value':'merge-religion'}),
				$('<label>').attr('for', 'closeXFD-holdingcell-merge-religion')
				.text('Merge (Religion)')
			),
			// Sports
			$('<div>').append(
				$('<input>').attr({'type':'radio', 'name':'closeXFD-merge-holdingcell',
					'id':'closeXFD-holdingcell-merge-sports', 'value':'merge-sports'}),
				$('<label>').attr('for', 'closeXFD-holdingcell-merge-sports')
				.text('Merge (Sports)')
			),
			// Other
			$('<div>').append(
				$('<input>').attr({'type':'radio', 'name':'closeXFD-merge-holdingcell',
					'id':'closeXFD-holdingcell-merge-other', 'value':'merge-other'}),
				$('<label>').attr('for', 'closeXFD-holdingcell-merge-other')
				.text('Merge (Other)')
			),
			// Meta
			$('<div>').append(
				$('<input>').attr({'type':'radio', 'name':'closeXFD-merge-holdingcell',
					'id':'closeXFD-holdingcell-merge-meta', 'value':'merge-meta'}),
				$('<label>').attr('for', 'closeXFD-holdingcell-merge-meta')
				.text('Merge (Meta)')
			)
		)
		.find("input[type=radio][name='closeXFD-merge-holdingcell']").change(function(){
			self.inputData.mergeHoldcell = $(this).val();
		})
		.end(),
		
		// Deletion options
		( !config.user.isSysop && config.xfd.type !== 'tfd' ) ? '' : $('<div>')
		.addClass('closeXFD-deleteOptions')
		.hide()
		.append(
			$('<strong>')
			.text(( multimode ) ? 'حذف کے اختیارات' : 'اختیارات').css('display','block'),
			// Don't delete talk pages
			( !config.user.isSysop && config.xfd.type !== 'tfd' ) ? '' : $('<div>').append(
				$('<input>').attr({'type':'checkbox', 'name':'closeXFD-del-deletetalk',
					'id':'closeXFD-del-deletetalk', 'value':'dontdeletetalk'}),
				$('<label>').attr('for', 'closeXFD-del-deletetalk').append(
					( config.user.isSysop ) ? 'تبادلۂ خیال صفحوں کو حذف نہ کریں' : 'تبادلہ خیال صفحوں '+
						'پر فوری حذف شدگی کا ٹیگ چسپاں نہ کریں'
				)
			),
			// Delete redirects
			( !config.user.isSysop || config.xfd.type === 'rfd' ) ? '' : $('<div>').append(
				$('<input>').attr({'type':'checkbox', 'name':'closeXFD-del-deleteredir',
					'id':'closeXFD-del-deleteredir', 'value':'deleteredir',
					'checked':'checked'}),
				$('<label>').attr('for', 'closeXFD-del-deleteredir')
				.text('رجوع مکرر بھی حذف کریں')
			),
			// Unlink backlinks
			( config.xfd.type !== 'نبح' && config.xfd.type !== 'ffd' ) ? '' : $('<div>')
			.append(
				$('<input>').attr({'type':'checkbox', 'name':'closeXFD-del-unlinkbackl',
					'id':'closeXFD-del-unlinkbackl', 'value':'unlinkbackl',
					'checked':'checked'}),
				$('<label>').attr('for', 'closeXFD-del-unlinkbackl').text('روابط کو غیر مربوط کریں')
			),
			// Delete and redirect
			( multimode || !config.user.isSysop || config.xfd.type === 'ffd' ) ? '' : $('<div>')
			.append(
				// Hidden checkbox, so that text lines up
				$('<input>').attr('type', 'checkbox').css('visibility', 'hidden'),
				// Shortcut for switching to 'Redirect' and selects 'Delete before redirecting'
				$('<a>').attr('id', 'closeXFD-del-deleteAndRedir')
				.text('حذف اور رجوع مکرر۔۔۔')
				.click(function(){
					$('#closeXFD-result-redir').prop('checked', true).change();
					$('#closeXFD-redir-deleteFirst').prop('checked', true);
					// Switch result labels to 'Delete and ...' versions, if needed
					if ( $('#closeXFD-result-redir').next().is(':visible') ) {
						$('#closeXFD-redir-deleteFirst').change();
					}
				} )
			),
			// Holding cell
			( config.xfd.type !== 'tfd' ) ? '' : $('<div>')
			.attr('id', 'closeXFD-del-holdcell')
			.append(
				$('<strong>').css('display', 'block').append(
					( !multimode ) ? 'Holding cell section:' : $('<input>')
					.attr({'type':'checkbox', 'name':'closeXFD-del-useHoldingCell',
						'id':'closeXFD-del-useHoldingCell', 'value':'useHoldingCell'})
					.on('change', function(){
						self.inputData.useHoldingCell = $(this).prop('checked');
						if ( self.inputData.useHoldingCell ) {
							$('#closeXFD-del-deletetalk, #closeXFD-del-deleteredir')
							.prop('disabled', true)
							.next().addClass('xfdc-dialog-disabled');
							$('#closeXFD-del-holdcell').children('div').show();
						} else {
							$('#closeXFD-del-deletetalk, #closeXFD-del-deleteredir')
							.prop('disabled', false)
							.next().removeClass('xfdc-dialog-disabled');
							$('#closeXFD-del-holdcell').children('div').hide();
						}
					})
					.prop('checked', !config.user.isSysop).change()
					.prop('disabled', !config.user.isSysop)
					.toggle(config.user.isSysop),
					( !multimode ) ? '' : $('<label>')
					.attr('for', 'closeXFD-del-useHoldingCell')
					.text((config.user.isSysop) ? 'Add to holding cell '+
						'instead of deleting:' : 'Holding cell section:')
				),
				// Review
				$('<div>').append(
					$('<input>').attr({'type':'radio', 'name':'closeXFD-holdingcell',
						'id':'closeXFD-holdingcell-review', 'value':'review'}),
					$('<label>').attr('for', 'closeXFD-holdingcell-review').text('Review')
				),
				// Convert
				$('<div>').append(
					$('<input>').attr({'type':'radio', 'name':'closeXFD-holdingcell',
						'id':'closeXFD-holdingcell-convert', 'value':'convert'}),
					$('<label>').attr('for', 'closeXFD-holdingcell-convert').text('Convert')
				),
				// Substitute
				$('<div>').append(
					$('<input>').attr({'type':'radio', 'name':'closeXFD-holdingcell',
						'id':'closeXFD-holdingcell-substitute', 'value':'substitute'}),
					$('<label>').attr('for', 'closeXFD-holdingcell-substitute').text('Substitute')
				),			
				// Orphan
				$('<div>').append(
					$('<input>').attr({'type':'radio', 'name':'closeXFD-holdingcell',
						'id':'closeXFD-holdingcell-orphan', 'value':'orphan'}),
					$('<label>').attr('for', 'closeXFD-holdingcell-orphan').text('Orphan')
				),
				// Ready for deletion
				$('<div>').append(
					$('<input>').attr({'type':'radio', 'name':'closeXFD-holdingcell',
						'id':'closeXFD-holdingcell-ready', 'value':'ready'}),
					$('<label>').attr('for', 'closeXFD-holdingcell-ready')
					.text('Ready for deletion')
				)
			)
			.children('div').toggle(!multimode || !config.user.isSysop).end()
			.find("input[type=radio][name='closeXFD-holdingcell']").change(function() {
				self.inputData.holdcell = $(this).val();
				// Disable 'don't delete talk' option unless 'ready for deletion' selected
				if ( self.inputData.holdcell !== 'ready' ) {
					$('#closeXFD-del-deletetalk').prop('disabled', true)
					.next().addClass('xfdc-dialog-disabled');
				} else {
					$('#closeXFD-del-deletetalk').prop('disabled', false)
					.next().removeClass('xfdc-dialog-disabled');
				}
			} )
			.end()
		)
	)
	.find('input[type=checkbox]')
		.not('#closeXFD-redir-deleteFirst, #closeXFD-del-useHoldingCell')
		.change(function(){
			var v = $(this).val();
			self.inputData[v] = $(this).prop('checked');
		})
		.change()
		.end()
	.end();
	
	if ( multimode ) {
		$options.children().addClass('xfdc-dialog-container');
	} else {
		$options.css({'float':'right', 'width':'59%'});
	}

	return $options;
};

Dialog.prototype.makeButtons = function(multimode) {
	var self = this;
	
	var $buttons = $('<div>')
	.attr('id', 'closeXFD-interface-buttons')
	.css({'text-align':'center', 'clear':'both'})
	.append(
		$('<button>')
		.attr('id', 'closeXFD-interface-cancel')
		.text('منسوخ')
		.click(function() {
			self.close();
			self.discussion.showLinks();
		})
	);
	if ( !self.relisting ) {
		$buttons.prepend(
			$('<button>')
			.attr('id', 'closeXFD-interface-closedisc')
			.text('گفتگو بند کریں')
			.click(function() {
				if ( multimode ) {
					self.evaluateMultimodeClose();
				} else {
					self.evaluateClose();
				}
			}),
			$('<button>')
			.attr('id', 'closeXFD-interface-preview')
			.text('نمائش')
			.click(function() {
				if ( multimode ) {
					self.evaluateMultimodeClose(true);
				} else {
					self.evaluateClose(true);
				}
			})
		);
	} else {
		$buttons.prepend(
			$('<button>').attr('id', 'closeXFD-interface-relistdisc').text('مکرر نامزد کریں')
			.click(function() {
				self.close();
				self.discussion.taskManager = new TaskManager(self.discussion);
				self.discussion.taskManager.start();
			}),
			$('<button>').attr('id', 'closeXFD-interface-previewRelist').text('نمائش')
			.click(function() {
				self.showPreview(true);
			})
		);
	}

	return $buttons;
};

Dialog.prototype.makePreviewBox = function() {
	return $('<p>').attr('id', 'closeXFD-preview-output').hide();
};

Dialog.prototype.updateMultimodeOptions = function() {
	$('.closeXFD-keepOptions').toggle(this.inputData.inPageActions('keep'));
	$('.closeXFD-redirectOptions').toggle(this.inputData.inPageActions('redirect'));
	$('.closeXFD-mergeOptions').toggle(this.inputData.inPageActions('merge'));
	$('.closeXFD-disambigOptions').toggle(this.inputData.inPageActions('disambig'));
	$('.closeXFD-deleteOptions').toggle(this.inputData.inPageActions('del'));
	$('#closeXFD-noAction').toggle(this.inputData.inPageActions('na'));
	this.resetHeight();
};

Dialog.prototype.storeRcatData = function(rcatData) {
	this.inputData.rcats = rcatData.join('\n').trim();
	$('#closeXFD-redir-rcats').prop('checked', rcatData.length > 0);
};

// --- Set up  for close or relist: ---
Dialog.prototype.setup = function() {
	var self = this;
	
	this.emptyContent();
	
	if ( config.xfd.type !== 'ffd' && !self.discussion.isBasicMode() ) {
		this.rcatSelector = Dialog.RcatMultiselect(self.storeRcatData, self);
	}
	
	this.inputData = new InputData(this.discussion);
	
	this.addToHeader(this.makeHeader());
	
	this.addToBody(this.makePagesList());
	if ( this.relisting ) {
		this.addToBody(this.makeRationaleBox());
	} else {
		if ( !config.user.isSysop ) {
			this.addToBody(this.makeNonAdminNote());
		}
		this.addToBody(this.makeCloseResult());
		this.addToBody(this.makeRationaleBox());
		if ( this.discussion.isBasicMode() ) {
			this.inputData.after = 'noAction';
		} else {
			if ( config.xfd.type === 'نبح' ) {
				this.rcatSelector.setValue(['{{R to related topic}}']);
			}
			this.addToBody(this.makeAfterActions());
			this.addToBody(this.makeOptions());
		}
	}
	
	this.addToFooter(this.makeButtons());
	this.addToFooter(this.makePreviewBox());
	
	this.resetHeight();
	this.display();
};

// --- Set up for multimode close: ---
Dialog.prototype.setupForMultiClose = function() {
	var self = this;
	
	this.emptyContent();
	
	if ( config.xfd.type !== 'ffd' && !self.discussion.isBasicMode() ) {
		this.rcatSelector = Dialog.RcatMultiselect(self.storeRcatData, self);
	}
	
	this.inputData = new InputData(this.discussion);
	this.inputData.initialiseMultimode();
	
	this.addToHeader(this.makeHeader(true));
	
	if ( !config.user.isSysop ) {
		this.addToBody(this.makeNonAdminNote());
	}
	
	this.addToBody(this.makePagesList(true));
	this.addToBody(this.makeRationaleBox(true));
	this.addToBody(this.makeAfterActions(true));
	if ( config.xfd.type === 'نبح' ) {
		this.rcatSelector.setValue(['{{R to related topic}}']);
	}
	this.addToBody(this.makeOptions(true));
	
	this.addToFooter(this.makeButtons(true));
	this.addToFooter(this.makePreviewBox());
	
	this.resetHeight();
	this.display();	
};

// Preview
Dialog.prototype.showPreview = function(relist) {

	var preview_wikitext = '';

	if ( relist ) {
		preview_wikitext = '{{مکرر نامزدگی|1=' + this.inputData.getRelistComment() + '}}';
	} else {
		preview_wikitext = "گفتگو کا نتیجہ '''" +
		this.inputData.getResult()+
		"'''" +
		(( this.inputData.getTargetLink() ) ? ' to ' + this.inputData.getTargetLink() : '') +
		( (this.inputData.getRationale()) || 'ہے۔' ) +
		' ' +
		config.user.sig;
	}

	API.get({
		action: 'parse',
		contentmodel: 'wikitext',
		text: preview_wikitext,
	})
	.done(function(result) {
		preview_html = result.parse.text['*'];
		$('#closeXFD-preview-output').html(preview_html).show().find('a').attr('target', '_blank');
	})
	.fail(function(code, jqxhr) {
		$('#closeXFD-preview-output')
		.empty()
		.append(
			$('<strong>')
			.css('color', '#f00')
			.append(
				'نمائش نہیں دکھائی جا سکی۔ ',
				formatApiError(code, jqxhr)
			)
		)
		.show();
	});
};


// --- Evaluate if required form elements have been completed ---

// Show errors for anything required but not completed
Dialog.prototype.showErrors = function(element_ids) {
	
	// remove any old error messages
	//$('.closeXFD-errorNote').parent().css('border', '').end().remove(); 
	
	
	$(element_ids.join(', '))
	.filter(':visible')
	.addClass('closeXFD-errorNote')
	.fadeTo('fast', 0.33).fadeTo('fast', 0.8).fadeTo('fast', 0.4).fadeTo('fast', 1)
	.on('change.errorNote', function() {
		$(this)
		.removeClass('closeXFD-errorNote')
		.off('change.errorNote');
	});
};

// Evaluate standard close
Dialog.prototype.evaluateClose = function(preview) {
	self = this;
	var errors = [];
	
	// Result
	if ( self.inputData.result ) {
		switch ( self.inputData.result ) {
			case 'custom':
				if ( !self.inputData.customResult ) {
					// Custom result not specified
					errors.push('#closeXFD-resultContainer-custom');
				}
				break;
			case 'retarget':
			case 'redirect':
			case 'soft redirect':
			case 'merge':
				if ( $('#closeXFD-result-target').val().trim() === '' ) {
					// Target not specified
					errors.push('#closeXFD-resultContainer-target');
				} else {
					if ( !self.inputData.target ) {
						// Invalid target
						alert('Bad ' + self.inputData.result +
							' target: the title is invalid.');
						errors.push('#closeXFD-resultContainer-target');
					}
				}
				break;
			default:
				break;
		}
	} else {
		// Result not selected
		errors.push('#closeXFD-resultOptions');
	}
	
	// Rationale
	var r = self.inputData.rationale;
	if ( r ) {
		// Prepend newline if it starts with a * or #
		r = r.replace(/^(\*|#)/, '\n$1');
		// Append a newline if the last line starts with a * or #
		var n = r.lastIndexOf('\n');
		if ( n > 0 && /(\*|#)/.test(r.slice(n+1,n+2) ) ) {
			r += '\n:';
		}
		self.inputData.rationale = r;
	}
	
	// After actions & options
	if ( self.inputData.after ) {
		if ( self.inputData.after === 'holdingCell' && !self.inputData.holdcell ) {
			// Holding cell section not selected
			errors.push('#closeXFD-del-holdcell');
		} else if (
			config.xfd.type === 'tfd' &&
			self.inputData.after === 'doMergeActions' &&
			!self.inputData.mergeHoldcell
		) {
			// Holding cell merge subsection not selected
			errors.push('#closeXFD-merge-holdcell');
		}
	} else {
		// After action not selected
		errors.push('#closeXFD-afterContainer');
	}
	
	if ( errors.length > 0 ) {
		// Show errors
		self.showErrors(errors);
	} else if ( preview ) {
		// Show preview
		self.showPreview();
	} else {
		// Start closing
		if ( 
			( config.xfd.type === 'tfd' && self.inputData.after === 'doMergeActions' ) ||
			( self.inputData.after === 'holdingCell' && self.inputData.holdcell !== 'ready' )
		) {
			// Don't delete/tag talkpages when using holding cell (except for 'ready for deletion')
			self.inputData.dontdeletetalk = true;
		}
		self.close();
		self.discussion.taskManager = new TaskManager(self.discussion);
		self.discussion.taskManager.start();
	}
};

// Evaluate multimode close
Dialog.prototype.evaluateMultimodeClose = function(preview) {
	self = this;
	var errors = [];

	// Check result summary specified
	if ( !self.inputData.result ) {
		errors.push('#closeXFD-resultSummary-input');
	}

	// Rationale
	var r = self.inputData.rationale;
	if ( r ) {
		// Prepend newline if it starts with a * or #
		r = r.replace(/^(\*|#)/, '\n$1');
		// Append a newline if the last line starts with a * or #
		var n = r.lastIndexOf('\n');
		if ( n > 0 && /(\*|#)/.test(r.slice(n+1,n+2) ) ) {
			r += '\n:';
		}
		self.inputData.rationale = r;
	}
	
	// Check each page action / target
	for ( var p in self.inputData.pageActions ) {
		// Check action specified
		if ( !self.inputData.pageActions[p].action ) {
			errors.push('#'+self.inputData.pageActions[p].id);
		} else if ( 
			self.inputData.pageActions[p] === 'redirect' ||
			self.inputData.pageActions[p] === 'merge'
		) {
			// Check target specified
			var targetInputId = self.inputData.pageActions[p].id.replace('action', 'tagert');
			if ( $('#'+targetInputId).val().trim() === '' ) {
				errors.push('#'+targetInputId);
			} else if ( !self.inputData.pageActions[p].target ) {
				alert('Bad ' + self.inputData.pageActions[p].action + 'target for ' +
				p +' – the title is invalid.');
				errors.push('#'+targetInputId);
			}
		}
	}

	// Check holding cell selection (merge)
	if (
		config.xfd.type === 'tfd' &&
		self.inputData.inPageActions('merge') &&
		!self.inputData.mergeHoldcell
	) {
		errors.push('#closeXFD-merge-holdcell');
	}
	// Check holding cell selection (delete)
	if (
		config.xfd.type === 'tfd' &&
		self.inputData.inPageActions('del') &&
		self.inputData.useHoldingCell &&
		!self.inputData.holdcell
	) {
		errors.push('#closeXFD-del-holdcell');
	}
	
	if ( errors.length > 0 ) {
		// Show errors
		self.showErrors(errors);
	} else if ( preview ) {
		// Show preview
		self.showPreview();
	} else {
		// Start closing
		if ( self.inputData.useHoldingCell && self.inputData.holdcell !== 'ready' ) {
			// Don't delete/tag talkpages when using holding cell (except for 'ready for deletion')
			self.inputData.dontdeletetalk = true;
		}
		self.close();
		self.discussion.taskManager = new TaskManager(self.discussion);
		self.discussion.taskManager.start();
	}	
};


/* ========== InputData class ===================================================================
   The raw data that the user entered in a dialog, and prototype get
   functions to obtain ready to use data
   ---------------------------------------------------------------------------------------------- */
// Constructor
var InputData = function(discussion) {
	this.discussion = discussion;
/* Defined by user interaction with dialog:
	this.speedy : {Boolean}
	this.customResult : {String}
	this.target : {Page object}
	this.result : {String}
	this.rationale : {String}
	this.resultPunct : {String}
	this.after : {String}
	this.deleteFirst : {Boolean}
	this.rcats : {String|Boolean false}
	this.useHoldingCell : {Boolean}
	this.holdcell : {String}
	this.mergeHoldcell : {String}
	this.dontdeletetalk : {Boolean}
	this.deleteredir : {Boolean}
	this.unlinkbackl : {Boolean}
*/
};

// ---------- InputData prototype --------------------------------------------------------------- */
// Setup for multimode close
InputData.prototype.initialiseMultimode = function() {
	this.multimode = true;
	this.pageActions = {};
};

// Check if an action has been specified for any of the pages (for multimode)
InputData.prototype.inPageActions = function(action) {
	var self = this;
	if ( !self.multimode ) return false;
	for ( var p in self.pageActions ) {
		if ( self.pageActions[p].action === action ) return true;
	}
	return false;
};

// --- Get functions ---

// Get array of Page objects for a result (for multimode)
InputData.prototype.getPages = function(result, result2, result3) {
	var self = this;
	
	if ( !self.multimode ) return false;
	
	if ( result2 == null ) result2 = false;
	if ( result3 == null ) result3 = false;
	
	var output = [];
	for ( var p in self.pageActions ) {
		if (
			self.pageActions[p].action === result ||
			self.pageActions[p].action === result2 ||
			self.pageActions[p].action === result3
		) {
			output.push(self.discussion.getPageByTitle(p));
		}
	}
	return output;
};

// Get the relist comment, escaping pipes which aren't within wikilinks or templates
InputData.prototype.getRelistComment = function() {
	return this.rationale.replace(/(\|)(?!(?:[^\[]*]|[^\{]*}))/g, '&#124;');
};

// Get the full result, for use as bolded result text or in edit summaries etc.
InputData.prototype.getResult = function() {
	if ( this.multimode ) {
		return this.result;
	} else {
		var output = '';
		if ( this.speedy ) {
			output += 'فوری ';
		}
		if (
			this.deleteFirst &&
			/(?:retarget|redirect|soft redirect)/.test(this.result)
		) {
			output += 'حذف اور ';
		}
		output += ( this.result === 'custom') ? this.customResult : this.result;
		return output;
	}
};

// Get the page title of the target - for a particular page if in multimode
InputData.prototype.getTarget = function(p) {
	if ( this.multimode && p ) {
		return this.pageActions[p].target.getPrefixedText();
	} else if ( /(?:retarget|redirect|soft redirect|merge)/.test(this.result) ) {
		return this.target && this.target.getPrefixedText();
	} else {
		return false;
	}
};

// Multimode: Get an array of page titles of targets for a particular action ('redirect' or 'merge') 
// Single result: Return an array with one item, the target page title
InputData.prototype.getTargetsArray = function(action) {
	var self = this;
	var output = [];
	if ( self.multimode ) {	
		for ( var p in self.pageActions ) {
			if ( self.pageActions[p] && self.pageActions[p].action === action ) {
				output.push(self.pageActions[p].target.getPrefixedText());
			}
		}
	} else {
		output.push(self.target.getPrefixedText());
	}
	return output;
};

// Multimode: Get an array of Titles objects of targets, for either 'redirect' or 'merge'
// Single result: Return an array with one item, the Title object for the target
InputData.prototype.getTargetsObjectsArray = function() {
	var self = this;
	var output = [];
	if ( self.multimode ) {	
		for ( var p in self.pageActions ) {
			if ( self.pageActions[p] && /(redirect|merge)/.test(self.pageActions[p].action) ) {
				output.push(self.pageActions[p].target);
			}
		}
	} else {
		output.push(self.target);
	}
	return output;
};

// Get an array of page titles to be merged to a particlar target (for multimode)
InputData.prototype.getPagesToBeMerged = function(target) {
	var self = this;
	var output = [];
	for ( var p in self.pageActions ) {
		if (
			self.pageActions[p] &&
			self.pageActions[p].action === 'merge' &&
			self.pageActions[p].target.getPrefixedText() === target
		) {
			output.push(p);
		}
	}
	return output;
};

// Get a link to the target - for a particular page if multimode - including fragment.
// Is formatted as a wikilink, with preceding colon if needed, unless raw is true.
InputData.prototype.getTargetLink = function(p, raw) {
	var targetObject = ( this.multimode && p ) ? this.pageActions[p].target : this.target;
	
	if ( !targetObject ) {
		return null;
	}

	var targetFrag = ( targetObject.getFragment() ) ? '#' + targetObject.getFragment() : '';
	var targetNS = targetObject.getNamespaceId();
	if ( raw ) {
		return targetObject.getPrefixedText() + targetFrag;
	} else if ( targetNS === 6 || targetNS === 14 ) {
		return "[[:" + targetObject.getPrefixedText() + targetFrag + "]]";
	} else {
		return "[[" + targetObject.getPrefixedText() + targetFrag + "]]";
	}
};

// Gets the rational, preceded by a period (unless its not a new sentence) and a space
InputData.prototype.getRationale = function() {
	return ( this.rationale ) ? this.resultPunct + ' ' + this.rationale : '';
};


/* ========== TaskManager class =================================================================
   Manages tasks - chooses which tasks to do, what data to pass to them, and
   keeps track of when (all) tasks are completed
   ---------------------------------------------------------------------------------------------- */
// Constructor
var TaskManager = function(discussion, inputDataObject) {
	this.discussion = discussion;
	if ( inputDataObject != null ) {
		this.inputData = inputDataObject;
	} else {
		this.inputData = discussion.dialog.inputData;
	}
	this.tasks = [];
	this.dfd = {};
};

// ---------- TaskManager prototype ------------------------------------------------------------- */
// Sanity checks - confirm with user if there are a lot of pages or pages in unexpected namespaces
TaskManager.prototype.makeSanityCheckWarnings = function() {
	var self = this;
	
	var relisting = !self.inputData.result;
	var multimode = self.inputData.multimode;
	var warnings = [];

	// Check if closing dicussion early
	if ( !self.discussion.isOld ) {
		if ( self.discussion.isRelisted ) {
			warnings.push('آخری نامزدگی کو ابھی سات دن پورے نہیں ہوئے ہیں')
		} else {
			warnings.push('نامزدگی کو ابھی سات دن مکمل نہیں ہوئے ہیں')
		}
	}
	
	// Check for mass actions when closing:
	if ( !relisting ) {
		// Only if nominated pages will be edited
		if (
			( relisting && /(ffd|tfd)/.test(config.xfd.type) ) ||
			( !relisting && !multimode && self.inputData.after !== 'noAction' ) ||
			( multimode && self.inputData.getPages('na').length !== self.discussion.pages.length )
		) {
			if ( self.discussion.pages.length > 3 ) {
				warnings.push('Mass actions will be peformed (' + self.discussion.pages.length +
				' nominated pages detected).');
				if ( self.discussion.pages.length > 50 ) {
					warnings.push('Only the first 50 pages may be processed, depending on the ' +
					'closing options selected.');
				}
			}
		}
	}
	
	// Check target page namespace:
	var targetObjectsArray = self.inputData.getTargetsObjectsArray();
	if ( targetObjectsArray ) {
		$.each(targetObjectsArray, function(_i, t) {
			if ( t && !hasCorrectNamespace(t)) {
				warnings.push( 'Target page [["' + t.getPrefixedText() + '"]] is not in the ' +
				config.mw.namespaces[(config.xfd.ns_number[0]).toString()] + ' namespace.');
			}
		});
	}
	
	//Check namespaces of nominated pages
	var wrongNamespacePages = !self.discussion.isBasicMode() && self.discussion.pages.filter(function(p) {
		return !hasCorrectNamespace(p);
	});
	if ( wrongNamespacePages.length > 0 ) {
		warnings.push(
			'The following pages are not in the ' +
			config.mw.namespaces[(config.xfd.ns_number[0]).toString()] + 
			' namespace:<ul><li>[[' +
			wrongNamespacePages.map(function(p){ return p.getPrefixedText(); }).join(']]</li><li>[[') +
			']]</li>'
		);
	}

	if ( warnings.length === 0 ) {
		return false;
	} else {
		return '<p>' + warnings.join('</p><p>') + '</p>';
	}
};

// If nominated pages are redirects (at venues other than RfD), the script can't know if this was
// appropriate, where results such as 'Delete' should be applied to the target, or an out-of-process
// redirection, where results such as 'Delete' should be applied to the redirect
/**ResolveRedirects
 * @returns {Promise<Boolean>} `true` when completed and okay to continue; rejected if aborted by user
 */
TaskManager.prototype.resolveRedirects = function() {
	var self = this;
	
	// No need to process for RfD, or if basic closure, or no after actions
	var relisting = !self.inputData.result;
	var multimode = self.inputData.multimode;
	if (
		// At RfD
		config.xfd.type === 'rfd' ||
		// Basic mode (no pages detected)
		self.discussion.isBasicMode() ||
		// Relisting for other than FfD/TfD
		( relisting && !/(ffd|tfd)/.test(config.xfd.type) ) ||
		// Not multimode, no after actions
		( !relisting && !multimode && self.inputData.after === 'noAction' ) ||
		// Multimode, no action for every page
		( multimode && self.inputData.getPages('na').length === self.discussion.pages.length )
	) {
		// Nominate pages expected to be redirects, no need to resolve them
		return $.Deferred().resolve(true).promise();
	}
	
	return API.get( {
		action: 'query',
		titles: self.discussion.getPageTitles().join('|'),
		redirects: 1,
		prop: 'info',
		inprop: 'talkid'
	} )
	.then( function(result) {
		if ( result.query && result.query.redirects ) {
			// Redirects are present, need to ask user what to do
			var isRedirect = function(pageName) {
				return !!result.query.redirects.find(function(redirect) {
					return redirect.title === pageName;
				});
			};

			var redirectTargetPages = arrayFromResponsePages(result)
			.filter(function(page) {
				return isRedirect(page.title);
			});

			var redirections = result.query.redirects.map(function(redirect) {
				return '<li>[[' + redirect.from + ']] → [[' + redirect.to + ']]</li>';
			});

			var redirects_msg = "The following nominated pages are redirects to other pages:<ul>" +
				redirections.join("") + "</ul>";
			
			return multiButtonConfirm({
				title: 'Use redirects or targets?',
				message: redirects_msg,
				actions: [
					{ label: 'Cancel', flags: 'safe' },
					{ label: 'Use redirects', action: 'reject' },
					{ label: 'Use targets', action: 'accept', flags: 'progressive' }
				],
				size: 'medium'
			})
			.then(function(action) {
				if ( action === 'accept' ) {
					// Update discussion's page data
					self.discussion.pages = self.discussion.pages.map(function(page) {
						if ( !isRedirect(page.getPrefixedText()) ) {
							return page;
						}
						// This is a redirect - create new page object for target
						var target = redirectTargetPages.find(function(target) {
							return target.title === page.getPrefixedText();
						});
						var targetPage = mw.Title.newFromText(target);
						setExistence(targetPage, target.pageid > 0);
						setExistence(targetPage.getTalkPage(), target.talkid > 0);
						return targetPage;
					});
					return true;
				} else if ( action === 'reject' ) {
					// No need to update discussion's page data
					return true;
				} else {
					// Abort closing, reset discussion links
					self.discussion.showLinks();
					return $.Deferred().reject('Cancelled by user');
				}
			});

		} else {
			// No redirects present, just get on with it
			return true;
		}
	});
};

// --- Initialise ---
// Set up for relist
TaskManager.prototype.initialiseForRelist = function() {
	var self = this;

	self.tasks.push(new Task('getRelistInfo', self.discussion));
	
	// Object to store gathered info
	self.relistInfo = {};
	
	switch ( config.xfd.type ) {
		case 'نبح':
			Array.prototype.push.apply(self.tasks, [
				new Task('updateDiscussion', self.discussion),
				new Task('updateOldLogPage', self.discussion),
				new Task('updateNewLogPage', self.discussion)
			]);
			// Deferred for when this relist is finished, keep track of it via its array index
			self.afdLogEditIndex = config.track.afdLogEdit.push($.Deferred()) - 1;
			// Notify user
			self.discussion.setStatus('Waiting... (to avoid edit conflicts, previous relistings '+
			'need to be completed)');
			break;
		case 'mfd':
			self.tasks.push(new Task('updateDiscussion', self.discussion));
			break;
		case 'rfd':
			Array.prototype.push.apply(self.tasks, [
				new Task('updateOldLogPage', self.discussion),
				new Task('updateNewLogPage', self.discussion),
				( !self.discussion.isBasicMode() && self.discussion.pages.length > 1 )
					? new Task('updateNomTemplates', self.discussion)
					: ''
			]
			.filter(function(v){ return !!v; })
			);
			break;
		default: // ffd, tfd
			Array.prototype.push.apply(self.tasks, [
				new Task('updateOldLogPage', self.discussion),
				new Task('updateNewLogPage', self.discussion),
				( !self.discussion.isBasicMode() ) ? new Task('updateNomTemplates', self.discussion) : ''
			]
			.filter(function(v){ return !!v; })
			);
			break;		
	}
	
	self.initialiseFinally();
};

TaskManager.prototype.initialiseForMultimodeClose = function() {
	// Set up for multimode
	var self = this;

	// Close discussion
	self.tasks.push(new Task('closeDisc', self.discussion));
	// Add Old xfd templates
	if (
		self.inputData.inPageActions('keep') ||
		self.inputData.inPageActions('redirect') ||
		( self.inputData.inPageActions('merge') && config.xfd.type !== 'tfd' ) ||
		self.inputData.inPageActions('disambig')	
	) {
		self.tasks.push(new Task('addOldXfd',
			self.discussion, self.inputData.getPages(
				'keep', 'redirect', ( /(afd|mfd)/.test(config.xfd.type) ) ? 'merge' : 'disambig'
			)
		));
	}
	// For keep results:
	if ( self.inputData.inPageActions('keep') ) {
		self.tasks.push(new Task('removeNomTemplates',
			self.discussion, self.inputData.getPages('keep')));
	}
	// For redirect results:
	if ( self.inputData.inPageActions('redirect') ) {
		self.tasks.push(new Task('redirect',
			self.discussion, self.inputData.getPages('redirect')));
	}
	// For merge (not holding cell) results:
	if ( config.xfd.type !== 'tfd' && self.inputData.inPageActions('merge') ) {
		self.tasks.push(new Task('addMergeTemplates',
			self.discussion, self.inputData.getPages('merge')));
	}
	// For disambiguate results:
	if ( self.inputData.inPageActions('disambig') ) {
		self.tasks.push(new Task('disambiguate',
			self.discussion, self.inputData.getPages('disambig')));
	}
	// For delete (not holding cell) results:
	if ( self.inputData.inPageActions('del') && !self.inputData.useHoldingCell ) {
		Array.prototype.push.apply(self.tasks, [
			new Task('peformDeletion', self.discussion, self.inputData.getPages('del')),
			( self.inputData.dontdeletetalk ) ? null : new Task('deleteTalk',
				self.discussion, self.inputData.getPages('del')),
			( self.inputData.deleteredir ) ? new Task('deleteRedirects',
				self.discussion, self.inputData.getPages('del')) : null,
			( self.inputData.unlinkbackl ) ? new Task('unlinkBacklinks',
				self.discussion, self.inputData.getPages('del')) : null
		]
		.filter(function(v){ return !!v; })
		);
	}
	// For TfD holding cell results
	if ( config.xfd.type === 'tfd' ) {
		var doHcMerge = self.inputData.inPageActions('merge');
		var doHcDelete = self.inputData.inPageActions('del') && self.inputData.useHoldingCell;
		if ( doHcMerge && doHcDelete ) {
			// Both 'merge' and 'delete' (via holding cell) results 
			Array.prototype.push.apply(self.tasks, [
				new Task('addBeingDeleted', self.discussion,
					self.inputData.getPages('merge', 'del')),
				new Task('addToHoldingCell', self.discussion,
					self.inputData.getPages('merge', 'del')),
				( self.inputData.dontdeletetalk ) ? null : new Task('tagTalkWithSpeedy',
					self.discussion, self.inputData.getPages('merge', 'del'))
			]
			.filter(function(v){ return !!v; })
			);			
		} else if ( doHcMerge ) {
			// Only 'merge' (via holding cell) results
			Array.prototype.push.apply(self.tasks, [
				new Task('addBeingDeleted', self.discussion, self.inputData.getPages('merge')),
				new Task('addToHoldingCell', self.discussion, self.inputData.getPages('merge'))
			]
			);
		} else if ( doHcDelete ) {
			// Only 'delete' (via holding cell) results
			Array.prototype.push.apply(self.tasks, [
				new Task('addBeingDeleted', self.discussion, self.inputData.getPages('del')),
				new Task('addToHoldingCell', self.discussion, self.inputData.getPages('del')),
				( self.inputData.dontdeletetalk ) ? null : new Task('tagTalkWithSpeedy',
					self.discussion, self.inputData.getPages('del'))
			]
			.filter(function(v){ return !!v; })
			);	
		}
	}
	
	self.initialiseFinally();
};
	
TaskManager.prototype.initialiseForClose = function() {
	var self = this;
	
	// Set up for close
	self.tasks.push(new Task('closeDisc', self.discussion));
	
	var addHoldcellTasks = function(){
		Array.prototype.push.apply(self.tasks, [
			new Task('addBeingDeleted', self.discussion),
			new Task('addToHoldingCell', self.discussion),
			( self.inputData.dontdeletetalk ) ? null : new Task('tagTalkWithSpeedy',
				self.discussion)
		]
		.filter(function(v){ return !!v; })
		);		
	};
	
	
	switch ( self.inputData.after ) {
		case 'doKeepActions':
			Array.prototype.push.apply(self.tasks, [
				new Task('addOldXfd', self.discussion),
				new Task('removeNomTemplates', self.discussion)
			]);
			break;
		case 'doRedirectActions':
			Array.prototype.push.apply(self.tasks, [
				new Task('addOldXfd', self.discussion),
				new Task('redirect', self.discussion),
				( self.inputData.result === 'soft redirect' ) ? null : new Task(
					'removeCircularLinks', self.discussion)
			]
			.filter(function(v){ return !!v; })
			);
			break;			
		case 'doMergeActions':
			if ( config.xfd.type === 'tfd' ) {
				addHoldcellTasks();
			} else {
				Array.prototype.push.apply(self.tasks, [
					new Task('addOldXfd', self.discussion),
					new Task('addMergeTemplates', self.discussion)
				]);
			}
			break;
		case 'doDisambigActions':
			Array.prototype.push.apply(self.tasks, [
				new Task('addOldXfd', self.discussion),
				new Task('disambiguate', self.discussion)
			]);
			break;
		case 'doDeleteActions':
			Array.prototype.push.apply(self.tasks, [
				new Task('peformDeletion', self.discussion),
				( self.inputData.dontdeletetalk ) ? null : new Task('deleteTalk',
					self.discussion),
				( self.inputData.deleteredir ) ? new Task('deleteRedirects',
					self.discussion) : null,
				( self.inputData.unlinkbackl ) ? new Task('unlinkBacklinks',
					self.discussion) : null
				
			]
			.filter(function(v){ return !!v; })
			);
			break;
		case 'holdingCell':
			addHoldcellTasks();
			break;
	}
	
	self.initialiseFinally();
	
};

// After initialising for relist/close, set up deferred objects and start initial task
TaskManager.prototype.initialiseFinally = function() {
	var self = this;
	
	// Deferred objects
	self.dfd.initialTask = $.Deferred().done(function() {
		// When initial task is done, start others (if any)
		if ( self.tasks.length > 1 ) {
			$.each(self.tasks.slice(1), function(_i, t) {
				if ( t.start ) {
					t.start();	// TODO: set task status, errors, and warning here, from the returned promise
				} else { // TODO: Investigate what the point of this is.
					self.tasks[_i+1].start();
				}
			});
		}
	});
	
	if ( self.getTaskByName('unlinkBacklinks') ) {
		// When unlinkBacklinks query is completed, it is okay to delete redirects
		self.dfd.ublQuery = $.Deferred();
	} else if ( self.getTaskByName('deleteRedirects') ) {
		self.dfd.ublQuery = $.Deferred().resolve();
	}
	
	// Start initial task
	if ( self.afdLogEditIndex ) {
		$.when( config.track.afdLogEdit[self.afdLogEditIndex-1] )
		.then( function() { 
			self.discussion.setStatus('مکرر نامزد کیا جا رہا ہے۔۔۔');
			self.tasks[0].start();
			} );
	} else {
		self.tasks[0].start();
	}
};

TaskManager.prototype.start = function() {
	var self = this;

	var sanityCheckWarnings = self.makeSanityCheckWarnings();
	$.when(
		( sanityCheckWarnings ) 
		? multiButtonConfirm({
			title: 'انتباہ',
			message: sanityCheckWarnings,
			actions: [
				{ label: 'منسوخ', flags: 'safe' },
				{ label: 'جاری رکھیں', action: 'proceed', flags: 'progressive' }
			],
			size: 'large'
		  })
		: $.Deferred().resolve('proceed')
	)
	.then(function(action) {
		if ( action !== 'proceed' ) {
			// Reset discussion links
			self.discussion.showLinks();
			return;
		}
		// Resolve redirects...
		return self.resolveRedirects();
	})
	.then(function(okayToProceed) {
		if ( !okayToProceed ) {
			return;
		}
		// ... then initialise
		var isRelisting = !self.inputData.result;
		if ( isRelisting ) {
			self.initialiseForRelist();
		} else if ( self.inputData.multimode ) {
			self.initialiseForMultimodeClose();
		} else {
			self.initialiseForClose();
		}
	});
};

TaskManager.prototype.getTaskByName = function(name) {
	var self = this;
	for ( var i=0; i<self.tasks.length; i++ ) {
		if ( self.tasks[i].name === name ) {
			return self.tasks[i];
		}
	}
	return false;
};

TaskManager.prototype.makeTaskNote = function(task) {
	$notice = $('<p>')
	.addClass('xfdc-task-' + task.status)
	.addClass(task.name)
	.append(
		$('<span>').append(task.description),
		': ',
		$('<strong>').append(task.getStatusText()),
		$('<span>').append(task.errors),
		$('<span>').append(task.warnings)
	);
	return $notice;
};

TaskManager.prototype.updateTaskNotices = function(task, skipFinishedCheck) {
	var self = this;
	var $notices = self.discussion.get$notices();
	if ( task && $notices.find('.'+task.name).length ) {
		// Update specified task
		var $taskNotice = self.discussion.get$notices().find('.'+task.name);
		$taskNotice.after(self.makeTaskNote(task)).remove();
	} else {
		// Update all tasks
		self.discussion.setNotices(
			self.tasks.map(function(t) { return self.makeTaskNote(t); })
		);
	}
	var allFin = self.tasks.every(function(t) {
			return t.isFinished();
	});
	
	// If every task is finished
	if ( !skipFinishedCheck && allFin ) {
		if ( self.afdLogEditIndex ) {
			config.track.afdLogEdit[self.afdLogEditIndex].resolve();
		}
		self.discussion.setFinished();
	}
};

TaskManager.prototype.abortTasks = function(reason) {
	var self = this;
	
	$.each(self.tasks, function(_i, t) {
		t.status = 'ساقط';
	});
	self.updateTaskNotices(null, true);
	if ( self.afdLogEditIndex ) {
		config.track.afdLogEdit[self.afdLogEditIndex].resolve();
	}
	self.discussion.setFinished(reason);
};

/* ========== Task class =========================================================================
   Tasks represent the action or series of actions the script will peform for
   the closer. Each task object also has a description, status, and (if any)
   error or warning messages, which the TaskManager uses to display the task
   notice on the page.   
   ---------------------------------------------------------------------------------------------- */
// Constructor
var Task = function(taskname, discussion, pages) {
	this.discussion = discussion;
	this.inputData = discussion.taskManager.inputData;
	this.name = taskname;
	
	this.status = 'waiting';
	this.errors = [];
	this.warnings = [];
	this.tracking = {};
	
	if ( pages != null ) {
		this.pages = pages;
	} else {
		this.pages = null;
	}

	var plural = ( this.pages ) ? this.pages.length > 1 : this.discussion.pages.length > 1;
	
	switch ( taskname ) {
		case 'closeDisc':
			this.description = 'اختتام گفتگو';
			break;
		case 'addOldXfd':
			this.description = 'تبادلۂ خیال صفحہ کی تجدید' + (( plural ) ? 's' : '');
			break;
		case 'removeNomTemplates':
		case 'addMergeTemplates':
		case 'disambiguate':
			this.description = 'صفحہ کی تجدید' + (( plural ) ? 's' : '');
			break;
		case 'peformDeletion':
			this.description = 'حذف صفحہ' + (( plural ) ? 's' : '');
			break;
		case 'addBeingDeleted':
			this.description = 'سانچہ کی تجدید' + (( plural ) ? 's' : '');
			break;
		case 'addToHoldingCell':
			this.description = 'Listing at holding cell';
			break;
		case 'deleteTalk':
			this.description = 'حذف تبادلۂ خیال' + (( plural ) ? 's' : '');
			break;
		case 'tagTalkWithSpeedy':
			this.description = 'تبادلۂ خیال صفحہ کی ٹیگ کاری' + (( plural ) ? 's' : '') +
			' for speedy deletion';
			break;
		case 'deleteRedirects':
			this.description = 'حذف رجوع مکررات';
			break;
		case 'unlinkBacklinks':
			this.description = 'روابط کی بے ربطی';
			break;
		case 'redirect':
			if ( discussion.dialog.inputData.deleteFirst ) {
				this.description = 'حذف صفحہ' + (( plural ) ? 's' : '') +
				' and replacing with redirect' + (( plural ) ? 's' : '');
			} else if ( config.xfd.type === 'rfd' ) {
				this.description = 'Retargeting redirect' + (( plural ) ? 's' : '');
			} else {
				this.description = 'صفحہ سازی' + (( plural ) ? 's' : '') +
				' into redirect' + (( plural ) ? 's' : '');
			}
			break;
		case 'removeCircularLinks':
			this.description = 'Unlinking circular links on redirect target';
			break;
		case 'getRelistInfo':
			this.description = 'مکرر نامزدگی کی تیاری';
			break;
		case 'updateDiscussion':
			this.description = 'گفتگو کی تجدید';
			break;
		case 'updateOldLogPage':
			this.description = 'نوشتہ سے حذف کیا جا رہا ہے';
			break;
		case 'updateNewLogPage':
			this.description = 'آج کے نوشتہ میں اندراج';
			break;
		case 'updateNomTemplates':
			this.description = 'نامزدگی کے سانچہ میں ربط کی تجدید' +
				(( plural ) ? 's' : '');
			break;
		default:
			this.description = taskname;
			break;
	}
};

// ---------- Task prototype -------------------------------------------------------------------- */
// Notice-related functions
Task.prototype.setDescription = function(d) {
	var self = this;
	this.description = d;
	this.discussion.taskManager.updateTaskNotices(self);
};
Task.prototype.setStatus = function(s) {
	var self = this;
	this.status = s;
	this.discussion.taskManager.updateTaskNotices(self);
};
Task.prototype.updateStatus = function() {
	var self = this;
	this.discussion.taskManager.updateTaskNotices(self);
};
Task.prototype.getStatusText = function() {
	var self = this;
	switch ( self.status ) {
		// Not yet started:
		case 'waiting':
			return 'منتظر۔۔۔';
		// In progress:
		case 'started':
			var $msg = $('<span>').append(
					$('<img>').attr({
					'src':'//upload.wikimedia.org/wikipedia/commons/thumb/f/f8/Ajax-loader%282%29.gif/'+
						'40px-Ajax-loader%282%29.gif',
					'width':'20',
					'height':'5'
				})
			);
			if ( self.showTrackingProgress ) {
				var counts = this.tracking[self.showTrackingProgress];
				$msg.append(
					$('<span>')
					.css('font-size', '88%')
					.append(
						'&nbsp;(' +
						(counts.success + counts.skipped) +
						'&thinsp;/&thinsp;' +
						counts.total +
						')'
					)
				);
			}
			return $msg;
		// Finished:
		case 'done':
			if (
				self.discussion.taskManager.dfd.initialTask.state() === 'pending' &&
				self.name === self.discussion.taskManager.tasks[0].name
			) {
				self.discussion.taskManager.dfd.initialTask.resolve();
			}
			return 'مکمل!';
		case 'ساقط':
		case 'failed':
		case 'skipped':
			return extraJs.toSentenceCase(self.status) + '';
		default:
			// unknown
			return '';
	}
};
Task.prototype.isFinished = function() {
	return $.inArray(this.status, ['done', 'ساقط', 'failed', 'skipped']) !== -1;
};
Task.prototype.addError = function(e, critical) {
	var self = this;
	this.errors.push($('<span>').addClass('xfdc-notice-error').append(e));
	if ( critical ) {
		this.status = 'failed';
	}
	this.discussion.taskManager.updateTaskNotices(self);
};
Task.prototype.addWarning = function(w) {
	var self = this;
	this.warnings.push($('<span>').addClass('xfdc-notice-warning').append(w));
	this.discussion.taskManager.updateTaskNotices(self);
};
Task.prototype.addApiError = function(code, jqxhr, explanation, critical) {
	var self = this;
	self.addError([
		makeErrorMsg(code, jqxhr),
		' – ',
		$('<span>').append(explanation)
	], !!critical);
};
Task.prototype.setupTracking = function(key, total, allDoneCallback, allSkippedCallback) {
	var self = this;
	if ( allDoneCallback == null && allSkippedCallback == null ) {
		allDoneCallback = function() { this.setStatus('مکمل'); };
		allSkippedCallback = function() { this.setStatus('چھوڑ دیا'); };
	}
	this.tracking[key] = {
		success: 0,
		skipped: 0,
		total: total,
		dfd: $.Deferred()
			.done($.proxy(allDoneCallback, self))
			.fail($.proxy(allSkippedCallback, self))
	};
};
Task.prototype.track = function(key, success) {
	if ( success ) {
		this.tracking[key].success++;
	} else {
		this.tracking[key].skipped++;
	}

	if ( key === this.showTrackingProgress ) {
		this.updateStatus();
	}

	if ( this.tracking[key].skipped === this.tracking[key].total ) {
		this.tracking[key].dfd.reject();
	} else if ( this.tracking[key].success + this.tracking[key].skipped === this.tracking[key].total ) {
		this.tracking[key].dfd.resolve();
	}
};


Task.prototype.start = function() {
	return this.doTask[this.name](this);
};

// Code to actually do the tasks
Task.prototype.doTask = {};
// ---------- Closing tasks -------------
// Close discussion
Task.prototype.doTask.closeDisc = function(self) {
	
	// Notify task is started -- current (to-be-deprecated) method 
	self.setStatus('started');
	
	// Get nomination page content and remove {Closing} etc templates if present
	return API.get( {
		action: 'query',
		titles: self.discussion.nomPage,
		prop: 'revisions',
		rvprop: 'content|timestamp',
		rvsection: self.discussion.sectionNumber,
		indexpageids: 1
	} )
	.then( function(response) {
		var id = response.query.pageids;
		return response.query.pages[ id ].revisions[ 0 ];
	} )
	.then( function(revision) {
		var contents = revision['*'];
		var lastEditTime = revision.timestamp;
		
		if ( contents.includes(config.xfd.wikitext.alreadyClosed) ) {
			return $.Deferred().reject('ساقط', null, 'گفتگو پہلے ہی موقوف کر دی گئی ہے (صفحہ کو دوبارہ '+
				'لوڈ کریں)');
		}
		
		/* Start-time check only possible for AFDs/MFDs. Other venues have multiple discussions on
		 * the same page, which would give false-positives when a different section was edited.
		 */
		if ( config.xfd.type === 'نبح' || config.xfd.type === 'mfd' ) {
			var editedSinceScriptStarted = config.startTime < new Date(lastEditTime);
			if ( editedSinceScriptStarted ) {
				return $.Deferred().reject('aborted: edit conflict detected');
			}
		}

		var section_heading = contents.slice(0, contents.indexOf('\n'));
		
		var decodeHtml = function(t) {
			return $('<div>').html(t).text();
		};
		
		var plain_section_heading = decodeHtml(	section_heading
			.replace(/(?:^\s*=*\s*|\s*=*\s*$)/g, '') // remove heading markup
			.replace(/\[\[\:?(?:[^\]]+\|)?([^\]]+)\]\]/g, '$1') // replace link markup with link text
			.replace(/{{\s*[Tt]l[a-z]?\s*\|\s*([^}]+)}}/g, '{{$1}}') // replace tl templates
			.replace(/s*}}/, '}}') // remove any extra spaces after replacing tl templates
			.replace(/\s{2,}/g, ' ') // collapse multiple spaces into a single space
			.trim()
		);
		
		var isCorrectSection = plain_section_heading === self.discussion.sectionHeader;
		if ( !isCorrectSection ) {
			return $.Deferred().reject('aborted: possible edit conflict (found section heading `' +
			plain_section_heading + '`)');
		}

		var xfd_close_top = config.xfd.wikitext.closeTop
			.replace(/__RESULT__/, self.inputData.getResult() || '&thinsp;')
			.replace(/__TO_TARGET__/, ( self.inputData.getTarget() ) ? ' to ' +
				self.inputData.getTargetLink() : '')
			.replace(/__RATIONALE__/, ( self.inputData.getRationale() || ''))
			.replace(/__SIG__/, config.user.sig);
		
		var section_contents = contents.slice(contents.indexOf('\n')+1)
			.replace(
				/({{closing}}|{{AfDh}}|{{AfDb}}|\{\{REMOVE THIS TEMPLATE WHEN CLOSING THIS AfD\|.?\}\}|\{\{گفتگو بند کرتے وقت اس سانچہ کو ہٹا دیں\|.?\}\}|<noinclude>\[\[زمرہ:مکرر نامزد شدہ نبح گفتگو\|.*?\]\]<\/noinclude>)/gi,
				'');
		var updated_top = ( config.xfd.type === 'نبح' || config.xfd.type === 'mfd' ) ?
			xfd_close_top + '\n' + section_heading :
			section_heading + '\n' + xfd_close_top;
		var updated_section = updated_top + '\n' + section_contents.trim() + '\n' + config.xfd.wikitext.closeBottom;
		
		return $.Deferred().resolve(updated_section, lastEditTime);
	} )
	.then( function(updated_section, lastEditTime) {
		return API.postWithToken( 'csrf', {
			action: 'edit',
			title: self.discussion.nomPage,
			section: self.discussion.sectionNumber,
			text: updated_section,
			summary: '/* ' + self.discussion.sectionHeader + ' */ کا نتیجہ: ' +
				self.inputData.getResult() + config.script.advert,
			basetimestamp: lastEditTime
		} );
	} )
	.then(
		function() {
			self.setStatus('done'); // Current (to-be-deprecated) method of setting status
			return 'done'; // Future method of setting status
		},
		function(code, jqxhr, abortReason) {
			var message = [
				'صفحہ ',
				extraJs.makeLink(self.discussion.nomPage),
				' میں ترمیم نہیں ہو سکی ',
				'؛ گفتگو کو بند نہیں کیا جا سکا'
			];
			self.addApiError(code, jqxhr, message);
			var abortMessage = ( code.indexOf("aborted") === 0 && abortReason ) || '';
			self.discussion.taskManager.abortTasks(abortMessage);
			return $.Deferred().reject(code, jqxhr, message);
		}
	);

};

// Add old xfd template to talk pages
Task.prototype.doTask.addOldXfd = function(self) {

	// Notify task is started
	self.setStatus('started');
	
	// Function to make an edit with the api.
	// Mode can be 'text' (overwrite existing content) or 'appendtext' or 'prependtext'
	var apiEditTalk = function (pageTitle, newWikitext, mode, redirect) {
		var req = {
			action: 'edit',
			title: pageTitle,
			section: '0',
			summary: 'Old ' + config.xfd.type.toUpperCase() + ' – ' + self.discussion.nomDate +
				': ' + self.inputData.getResult() + config.script.advert,
			redirect: !!redirect
		};
		req[mode] = ( mode === 'appendtext' ) ? '\n' + newWikitext : newWikitext;
		API.postWithToken( 'csrf', req )
		.done( function() {
			self.track('processed', true);
		})
		.fail( function(code, jqxhr) {
			self.track('processed', false);
			self.addApiError(code, jqxhr, [
				'Could not edit talk page ',
				extraJs.makeLink(pageTitle)
				]);
		} ); 
	};

	// Make wikitext of olf xfd template (non-AFD)
	var makeOldxfdWikitext = function(altpage) {
		result = config.xfd.wikitext.oldXfd
			.replace(/__DATE__/, self.discussion.nomDate)
			.replace(/__SECTION__/, self.discussion.sectionHeader)
			.replace(/__RESULT__/, self.inputData.getResult())
			.replace(/__FIRSTDATE__/, self.discussion.firstDate)
			.replace(/__SUBPAGE__/, self.discussion.getNomSubpage());
		if ( altpage ) {
			result = result.replace('}}', ' |altpage='+altpage+'}}');
		}
		return result;
	};
	
	// Add or update oldafdmulti template in section wikitext
	var makeNewWikitext = function(wikitext, pageTitle) {
		//Parts of this derived from https://en.wikipedia.org/wiki/User:Mr.Z-man/closeAFD2.js
		var titleObject = mw.Title.newFromText(pageTitle);
		var PAGENAME = titleObject.getMain();
		var SUBJECTPAGENAME = config.mw.monthNames[(titleObject.getNamespaceId()-1).toString()] +
			PAGENAME; 
		var oldafdmulti = '{{Old AfD multi';
		var numtest = /[A-z]+([0-9]+)/i;
		var count = 0;
		var i = 0;

		var makeTemplateRegexPatt = function(templateNameRegexString, flags, prevLine) {
			return new RegExp((( prevLine ) ? '\\n?' : '') +
			// start template
			'\\{\\{' + templateNameRegexString + '\\s*' +
			// start capturing group
			"(\\|" +
				// account for sub-templates within the template
				"(?:.|\\n)*?(?:(?:\\{\\{" +			
					// account for sub-sub-templates within sub-templates
					"(?:.|\\n)*?(?:(?:\\{\\{" +
						"(?:.|\\n)*?" +
					"\\}\\})(?:.|\\n)*?)*?" +
				"\\}\\})(?:.|\\n)*?)*" +
			// end capturing group, end template
			")\\}\\}\\n?", flags);
		};
		
		var makeParams = function(pattern) {
			var params = {};
			// Pattern to find `|param=value` or `|value`, where `value` can only contain a pipe
			// if within square brackets (i.e. wikilinks) or braces (i.e. templates)
			var partsPatt = /\|(?!(?:[^{]+}|[^\[]+]))(?:.|\s)*?(?=(?:\||$)(?!(?:[^{]+}|[^\[]+])))/g;
			// Execute pattern to obtain capturing group match (at index 1 of the resulting array)
			var old = pattern.exec(wikitext);
			if ( old ) {
				var unnamedParamCount = 0;
				var parts = old[1].match(partsPatt);
				for ( var i=0; i<parts.length; i++ ) {
					if ( parts[i].trim() === '|' ) {
						continue;
					}
					var equalsIndex = parts[i].indexOf('=');
					if ( equalsIndex === -1 ) {
						//unnamed parameter
						unnamedParamCount++;
						params[unnamedParamCount.toString()] = parts[i].slice(1).trim();
					} else {
						params[parts[i].slice(1, equalsIndex).trim()] = parts[i]
							.slice(equalsIndex+1).trim();
					}
				}
			}
			return params;
		};
		
		// Find old AFDs
		var oldAfdPatt = makeTemplateRegexPatt('(?:old|afd) ?(?:old|afd) ?(?:multi|full)?', 'i');
		var oldAfdParams = makeParams(oldAfdPatt);
		for (var p in oldAfdParams) {
			oldafdmulti += ' |' + p + '=' + oldAfdParams[p];
			var num = numtest.exec(p);
			var res = ( num ) ? parseInt(num[1]) : 1;
			if (res > count) {
				count = res;
			}
		}
		
		// Find old TFDs
		var oldTfdPatt = makeTemplateRegexPatt('(?:old|tfd|Previous) ?(?:tfd|tfd|end)(?:full)?', 'gi');
		var oldTfdTemplates = wikitext.match(oldTfdPatt);
		if ( oldTfdTemplates ) {
			for (i=0; i<oldTfdTemplates.length; i++) {
				count++;
				var oldTfdParams = makeParams(oldTfdPatt);
				var oldTfdDate = oldTfdParams.date;
				var oldTfdResult = oldTfdParams.result || 'keep';
				var oldTfdLink = "{{subst:#ifexist:Wikipedia:Templates for deletion/Log/"+
					( oldTfdParams.link || "{{subst:Date|"+oldTfdParams.date+"|dmy}}" )+
					"|Wikipedia:Templates for deletion/Log/" +
					( oldTfdParams.link || "{{subst:Date|"+oldTfdParams.date+"|dmy}}" )+
					"#" + ( oldTfdParams['1'] || oldTfdParams.disc || "Template:"+PAGENAME )+
					"|Wikipedia:Templates for discussion/Log/"+
					( oldTfdParams.link || "{{subst:Date|"+oldTfdParams.date+"|dmy}}" )+
					"#" + ( oldTfdParams['1'] || oldTfdParams.disc || "Template:"+PAGENAME )+
					"}}";
				oldafdmulti += " |date" + count + "=" + oldTfdDate +
				" |result" + count + "='''" + extraJs.toSentenceCase(oldTfdResult) + "'''" +
				" |link" + count + "={{canonicalurl:" + oldTfdLink + "}}";
				wikitext = wikitext.replace(oldTfdTemplates[i], '');
			}
		}
		
		// Find old FFDs
		var oldFfdPatt = makeTemplateRegexPatt('old ?(?:f|i)fd(?:full)?', 'gi');
		var oldFfdTemplates = wikitext.match(oldFfdPatt);			
		if ( oldFfdTemplates ) {
			for (i=0; i<oldFfdTemplates.length; i++) {
				count++;
				var oldFfdParams = makeParams(oldFfdPatt);
				var oldFfdDate = oldFfdParams.date || '';
				var oldFfdResult = oldFfdParams.result || 'keep';
				var oldFfdLink = "{{subst:#ifexist:Wikipedia:Images and media for deletion/"+
					"{{subst:#iferror:{{subst:#time:|"+oldFfdParams.date+"}}|"+oldFfdParams.date+
					"|{{subst:#time:Y F j|"+oldFfdParams.date+"}}}}"+
					"|Wikipedia:Images and media for deletion/{{subst:#iferror:{{subst:#time:|"+
					oldFfdParams.date+"}}|"+oldFfdParams.date+"|{{#time:Y F j|"+oldFfdParams.date+
					"}}}}#File:" + ( oldFfdParams.page || PAGENAME ) +
					"|{{subst:#ifexist:Wikipedia:Files for deletion/{{subst:#iferror:{{subst:#time:|"+
					oldFfdParams.date+"}}|"+oldFfdParams.date+"|{{subst:#time:Y F j|"+
					oldFfdParams.date+"}}}}"+
					"|Wikipedia:Files for deletion/{{subst:#iferror:{{subst:#time:|"+oldFfdParams.date+"}}|"+
					oldFfdParams.date+"|{{subst:#time:Y F j|"+oldFfdParams.date+"}}}}#" +
					( oldFfdParams.page || SUBJECTPAGENAME )+
					"|Wikipedia:Files for discussion/{{subst:#iferror:{{subst:#time:|"+oldFfdParams.date+"}}|"+
					oldFfdParams.date+"|{{subst:#time:Y F j|"+oldFfdParams.date+"}}}}#" +
					( oldFfdParams.page || SUBJECTPAGENAME )+
					"}}}}";
				oldafdmulti += " |date" + count + "=" + oldFfdDate +
				" |result" + count + "='''" +
				extraJs.toSentenceCase(oldFfdResult.replace(/'''/g, '')) + "'''" +
				" |link" + count + "={{canonicalurl:" + oldFfdLink + "}}";
				wikitext = wikitext.replace(oldFfdTemplates[i], '');
			}
		}
		
		// Find old MFDs
		var oldMfdPatt = makeTemplateRegexPatt('(?:old ?mfd|mfdend|mfdold)(?:full)?', 'gi');
		var oldMfdTemplates = wikitext.match(oldMfdPatt);			
		if ( oldMfdTemplates ) {
			for (i=0; i<oldMfdTemplates.length; i++) {
				count++;
				var oldMfdParams = makeParams(oldMfdPatt);
				var oldMfdDate = oldMfdParams.date || '';
				var oldMfdResult = oldMfdParams.result || 'keep';
				var oldMfdLink = "Wikipedia:Miscellany for deletion/" +
					( oldMfdParams.votepage || oldMfdParams.title ||
					oldMfdParams.page || SUBJECTPAGENAME );
				oldafdmulti += " |date" + count + "=" + oldMfdDate +
				" |result" + count + "='''" +
				extraJs.toSentenceCase(oldMfdResult.replace(/'''/g, '')) + "'''" +
				" |link" + count + "={{canonicalurl:" + oldMfdLink + "}}";
				wikitext = wikitext.replace(oldMfdTemplates[i], '');
			}
		}				

		// Find old RFDs
		var oldRfdPatt = makeTemplateRegexPatt('old?(?: |-)?rfd(?:full)?', 'gi');
		var oldRfdTemplates = wikitext.match(oldRfdPatt);			
		if ( oldRfdTemplates ) {
			for (i=0; i<oldRfdTemplates.length; i++) {
				count++;
				var oldRfdParams = makeParams(oldRfdPatt);
				var oldRfdDate = oldRfdParams.date || '';
				var oldRfdResult = oldRfdParams.result || 'keep';
				var oldRfdLink;
				if ( oldRfdParams.rawlink ) {
					oldRfdLink = oldRfdParams.rawlink.slice(2, oldRfdParams.rawlink.indexOf('|'));
				} else {
					oldRfdLink = "Wikipedia:Redirects for discussion/Log/" +
					( oldRfdParams.page || oldRfdParams.date + "#" + SUBJECTPAGENAME );
				}
				oldafdmulti += " |date" + count + "=" + oldRfdDate +
				" |result" + count + "='''" +
				extraJs.toSentenceCase(oldRfdResult.replace(/'''/g, '')) + "'''" +
				" |link" + count + "={{canonicalurl:" + oldRfdLink + "}}";
				wikitext = wikitext.replace(oldRfdTemplates[i], '');
			}
		}	

		// For non-AFDs, if no old banners were found, prepend process-specific banner to content
		if ( config.xfd.type !== 'نبح' && count === 0 ) {
			return config.xfd.wikitext.oldXfd
				.replace(/__DATE__/, self.discussion.nomDate)
				.replace(/__SECTION__/, self.discussion.sectionHeader)
				.replace(/__RESULT__/, self.inputData.getResult())
				.replace(/__FIRSTDATE__/, self.discussion.firstDate)
				.replace(/__SUBPAGE__/, self.discussion.getNomSubpage()) + wikitext;
		}
		
		// Otherwise, add current discussion to oldafdmulti
		count++;
		var c = count.toString();
		var currentResult = self.inputData.getResult();
		if (count === 1) {
			c = '';
		} else {
			currentResult = extraJs.toSentenceCase(currentResult);
		}
		var currentNompageParamAndValue = ( config.xfd.type === 'نبح' ) ? ' |page'+c + '=' +
			self.discussion.getNomSubpage() : ' |link'+c + '='+
			'{{canonicalurl:' + self.discussion.getNomPageLink() + '}}';
			
		oldafdmulti += ' |date'+c + '=' + self.discussion.nomDate +
			' |result'+c+ "='''" + currentResult + "'''" + currentNompageParamAndValue + '}}';
		if ( oldAfdPatt.test(wikitext) ) {
			// Override the existing oldafdmulti
			newtext = wikitext.replace(oldAfdPatt, oldafdmulti+'\n');
		} else {
			// Prepend to content ([[WP:Talk page layout]] is too complicated to automate)
			newtext = oldafdmulti+'\n'+wikitext;
		}
		return newtext;
	};
	
	var confirmRedirectReplacement = function(talkTitle) {
		OO.ui.confirm('"' + talkTitle + '" is currently a redirect. Okay ' +
		'to replace with Old RFD template?')
		.done( function ( confirmed ) {
			if ( confirmed ) {
				// Make the edit, replacing previous content
				apiEditTalk(talkTitle, makeOldxfdWikitext(), 'text');
			} else {
				// Skip
				self.addWarning([
					extraJs.makeLink(talkTitle),
					' skipped'
				]);
				self.track('processed', true);
			}
		} );
	};
	
	var processTalkTitles = function (result) {
		var page_ids = result.query.pageids;
		for ( var i=0; i < page_ids.length; i++ ) {
			var talkTitle = result.query.pages[ page_ids[i] ].title;
			// Check there's a corresponding nominated page
			var pageObj = self.discussion.getPageByTalkTitle(talkTitle);
			if ( !pageObj ) {
				self.addError([
					'API query result included unexpected talk page title ',
					extraJs.makeLink(talkTitle),
					'; this talk page will not be edited'
				]);
				self.track('processed', false);
				continue;
			}
			// Check corresponding page exists
			if ( !pageObj.exists() ) {
				self.addError([
					'The subject page for ',
					extraJs.makeLink(talkTitle),
					' موجود نہیں ہے؛ اس تبادلہ خیال صفحہ میں ترمیم نہیں کی جائے گی'
				]);
				self.track('processed', false);
				continue;
			}
			// Check redirect status
			if ( config.xfd.type !== 'نبح' && result.query.pages[ page_ids[i] ].redirect === '' ) {
				// Is a redirect...
				if ( config.xfd.type === 'rfd' ) {
					// for RFD, ask what to do
					confirmRedirectReplacement(talkTitle);
				} else if ( config.xfd.type === 'mfd' ) {
					// For MFD, edit the redirect's target page, using the altpage parameter
					apiEditTalk(talkTitle, makeOldxfdWikitext(pageObj.getPrefixedText()),
						'prependtext', true); 
				} else {
					// For other venues, append rather than prepend old xfd template
					apiEditTalk(talkTitle, makeOldxfdWikitext(), 'appendtext');
				}
			} else {
				// Not a redirect. Attempt to find and consolidate existing banners
				var oldwikitext = '';
				if ( parseInt(page_ids[i]) > 0 ) {
					oldwikitext = result.query.pages[ page_ids[i] ].revisions[0]['*'];
				}				
				apiEditTalk(talkTitle, makeNewWikitext(oldwikitext, talkTitle), 'text');
			}
		}
		
	};
		
	// Get talk pages
	var talkTitles = self.discussion.getTalkTitles(self.pages);
	if ( talkTitles.length === 0 ) {
		self.addWarning('موجود نہیں');
		self.setStatus('done');
	} else {
		// get talk page redirect status from api
		self.setupTracking('processed', talkTitles.length);
		
		API.get( {
			action: 'query',
			prop: 'revisions|info',
			rvprop: 'content',
			rvsection: '0',
			titles: talkTitles,
			indexpageids: 1
		} )
		.done( processTalkTitles )
		.fail( function(code, jqxhr) {
			self.addApiError(code, jqxhr, 'Could not read contents of talk page' +
				( talkTitles.length > 1 ) ? 's' : '');
			self.setStatus('failed');
		} );
	}
	
};

Task.prototype.doTask.removeNomTemplates = function(self, mergeWikitext) {

	// Notify task is started
	self.setStatus('started');

	apiEditPage = function(pageTitle, updatedWikitext) {
		API.postWithToken( 'csrf', {
			action: 'edit',
			title: pageTitle,
			text: updatedWikitext,
			summary: config.xfd.type.toUpperCase() + ' کا نتیجہ: ' +
				self.inputData.getResult() + config.script.advert
		} )
		.done( function() {
			self.track('edit', true);
		} )
		.fail( function(code, jqxhr) {
			self.track('edit', false);
			self.addApiError(code, jqxhr, [
				'Could not edit page ',
				extraJs.makeLink(pageTitle)
			]);
		} );
	};
	
	var processPages = function (result) {
		//Get old wikitext, or make error if page doesn't exist, or if page is in wrong namespace
		var page_ids = result.query.pageids;
		for (var i = 0; i < page_ids.length; i++) {
			var pageTitle = result.query.pages[ page_ids[i] ].title;
			// Check there's a corresponding nominated page
			var pageObj = self.discussion.getPageByTitle(pageTitle, {'moduledocs': true});
			if ( !pageObj ) {
				self.addError([
					'API query result included unexpected title ',
					extraJs.makeLink(pageTitle),
					'; this page will not be edited'
				]);
				self.track('edit', false);
				continue;
			}
			// Check that page exists
			if ( parseInt(page_ids[i]) < 0 ) { 
				self.addError([
					extraJs.makeLink(pageTitle),
					' موجود نہیں ہے لہذا ترمیم نہیں کی جائے گی'
				]);
				self.track('edit', false);
				continue;
			}

			// Existing wikitext
			var oldWikitext = result.query.pages[ page_ids[i] ].revisions[0]['*'];
			
			// Start building updated wikitext
			var updatedWikitext = '';
			
			// For merging, unless the page is itself a merge target, prepend the mergeWikitext
			if (
				mergeWikitext != null &&
				$.inArray(pageTitle, self.inputData.getTargetsArray('merge')) === -1 
			) {
				updatedWikitext = mergeWikitext[pageTitle];
			}
			
			// Remove nom template if present, or warn if not found
			if ( config.xfd.hasNomTemplate(oldWikitext) ) {
				updatedWikitext += config.xfd.removeNomTemplate(oldWikitext);
			} else {
				self.addWarning([
					'صفحہ ',
					extraJs.makeLink(pageTitle),
					' پر نامزدگی کا سانچہ نہیں ملا'
				]);
				if ( updatedWikitext === '' ) {
					// Skip - nothing to change
					self.track('edit', false);
					continue;
				} else {
					updatedWikitext += oldWikitext;
				}
			}
			
			// Make the edit
			apiEditPage(pageTitle, updatedWikitext);
		}
	};

	//get each page's wikitext through api
	var pageTitles = self.discussion.getPageTitles(self.pages, {'moduledocs': true});
	if ( pageTitles.length === 0 ) {
		self.addWarning('موجود نہیں');
		self.setStatus('failed');
		return;
	}
	
	self.setupTracking(
		'edit',
		pageTitles.length,
		( mergeWikitext ) ? function() { this.track('alldone', true); } : null,
		( mergeWikitext ) ? function() { this.track('alldone', false); } : null
	);

	API.get( {
		action: 'query',
		titles: pageTitles.join('|'),
		prop: 'revisions',
		rvprop: 'content',
		indexpageids: 1,
		rawcontinue: ''
	} )
	.done( processPages )
	.fail( function(code, jqxhr) {
		self.addApiError(code, jqxhr, 'Could not read contents of nominated page' +
			( pageTitles.length > 1 ) ? 's' : '');
		self.setStatus('failed');
	} );
};

Task.prototype.doTask.addMergeTemplates = function(self) {

	// Notify task is started
	self.setStatus('started');

	// Edit function
	editTargetTalk = function(pageTitle, prependWikitext) {
		API.postWithToken( 'csrf', {
			action: 'edit',
			title: pageTitle,
			prependtext: prependWikitext,
			summary: '[[' + self.discussion.getNomPageLink() + ']] کا نتیجہ: ' +
				self.inputData.getResult() + config.script.advert
		} )
		.done( function() {
			self.track('editTargetTalk', true);
		})
		.fail( function(code, jqxhr) {
			self.track('editTargetTalk', false);
			self.addApiError(code, jqxhr, [
				'Could not edit page ',
				extraJs.makeLink(pageTitle)
			]);
		} );
	};
	
	// Get targets and their talk pages
	var targets = extraJs.uniqueArray(self.inputData.getTargetsArray('merge'));
	
	self.setupTracking('alldone', 2);
	self.setupTracking(
		'editTargetTalk',
		targets.length,
		function() { this.track('alldone', true); },
		function() { this.track('alldone', false); }
	);
	
	// Strings for merge templates
	var debate = self.discussion.getNomSubpage();
	var today = new Date();
	var curdate = today.getUTCDate().toString() + ' ' + config.mw.monthNames[today.getUTCMonth()] + ' ' +
		today.getUTCFullYear().toString();
	
	// Object to hold the 'merge to' template for each page
	var mergetoWikitext = {};
	
	// For each target...
	for ( var i=0; i<targets.length; i++ ) {
		// Pages to be merged to target
		var mergeTitles;
		if ( self.inputData.multimode ) {
			mergeTitles = self.inputData.getPagesToBeMerged(targets[i]);
		} else {
			mergeTitles = self.discussion.getPageTitles(self.pages);
		}

		// Make 'merge to' template for pages to be merged
		var mergetoTemplate = config.xfd.wikitext.mergeTo
			.replace(/__TARGET__/, targets[i])
			.replace(/__DEBATE__/, debate)
			.replace(/__DATE__/, curdate)		
			.replace(/__TARGETTALK__/, mw.Title.newFromText(targets[i]).getTalkPage().getPrefixedText());

		// Make 'merge from' template for the target's talk page
		var mergefromTemplates = [];
		for (var ii=0; ii<mergeTitles.length; ii++) {
			mergefromTemplates.push(
				config.xfd.wikitext.mergeFrom
				.replace(/__NOMINATED__/, mergeTitles[ii])
				.replace(/__DEBATE__/, debate)
				.replace(/__DATE__/, curdate)
			);
			// Add 'merge to' template to holding object
			mergetoWikitext[mergeTitles[ii]] = mergetoTemplate;
		}
		
		// Check if the target is one of the nominated pages
		if ( $.inArray(targets[i], self.discussion.getPageTitles(self.pages)) !== -1 ) {
			// No need to add 'merge from' template - this was a nominated page, and will have an 
			// old xfd template put on its talkpage specify the merge result.
			self.track('editTargetTalk', false);
			continue;
			
		} else {
			// Edit target talkpage
			editTargetTalk(mw.Title.newFromText(targets[i]).getTalkPage().getPrefixedText(), mergefromTemplates.join(''));
		}
	}
	
	// Replace nomination templates with 'merge to' templates
	self.doTask.removeNomTemplates(self, mergetoWikitext);
	
};

Task.prototype.doTask.disambiguate = function(self) {

	// Notify task is started
	self.setStatus('started');

	apiEditPage = function(pageTitle, updatedWikitext) {
		API.postWithToken( 'csrf', {
			action: 'edit',
			title: pageTitle,
			text: updatedWikitext,
			summary: config.xfd.type.toUpperCase() + ' کا نتیجہ: ' +
				self.inputData.getResult() + config.script.advert
		} )
		.done( function() {
			self.track('edit', true);
		} )
		.fail( function(code, jqxhr) {
			self.track('edit', false);
			self.addApiError(code, jqxhr, [
				'Could not edit page ',
				extraJs.makeLink(pageTitle)
			]);
		} );
	};
	
	var processPages = function (result) {
		//Get old wikitext, or make error if page doesn't exist, or if page is in wrong namespace
		var page_ids = result.query.pageids;
		for (var i = 0; i < page_ids.length; i++) {
			var pageTitle = result.query.pages[ page_ids[i] ].title;
			// Check there's a corresponding nominated page
			var pageObj = self.discussion.getPageByTitle(pageTitle);
			if ( !pageObj ) {
				self.addError([
					'API query result included unexpected title ',
					extraJs.makeLink(talkTitle),
					'; this page will not be edited'
				]);
				self.track('edit', false);
				continue;
			}
			// Check that page exists
			if ( parseInt(page_ids[i]) < 0 ) { 
				self.addError([
					extraJs.makeLink(talkTitle),
					' موجود نہیں ہے لہذا ترمیم نہیں کی جائے گی'
				]);
				self.track('edit', false);
				continue;
			}

			var oldWikitext = result.query.pages[ page_ids[i] ].revisions[0]['*'];
			var updatedWikitext = '';
			
			if ( config.xfd.regex.fullNomTemplate.test(oldWikitext) ) {
				updatedWikitext = oldWikitext.replace(config.xfd.regex.fullNomTemplate, '').trim();
			} else {
				self.addWarning([
					'صفحہ ',
					extraJs.makeLink(pageTitle),
					'پر نامزدگی کا سانچہ نہیں ملا '
				]);
				updatedWikitext = oldWikitext.replace(/^#REDIRECT/mi, '*');
			}
			
			if ( !/(?:disambiguation|disambig|dab|Mil-unit-dis|Numberdis|ضد ابہام|ضدابہام)[^{]*}}/i.test(updatedWikitext) ) {
				updatedWikitext += '\n{{Disambiguation cleanup|{{subst:DATE}}}}';
				updatedWikitext.trim();
			}

			apiEditPage(pageTitle, updatedWikitext);
		}
	};

	//get each page's wikitext through api
	var pageTitles = self.discussion.getPageTitles(self.pages);
	if ( pageTitles.length === 0 ) {
		self.addWarning('موجود نہیں');
		self.setStatus('failed');
		return;
	}
	
	self.setupTracking('edit', pageTitles.length);

	API.get( {
		action: 'query',
		titles: pageTitles.join('|'),
		prop: 'revisions',
		rvprop: 'content',
		indexpageids: 1,
		rawcontinue: ''
	} )
	.done( processPages )
	.fail( function(code, jqxhr) {
		self.addApiError(code, jqxhr, 'Could not read contents of nominated page' +
			( pageTitles.length > 1 ) ? 's' : '');
		self.setStatus('failed');
	} );
};

Task.prototype.doTask.peformDeletion = function(self) {

	// Notify task is started
	self.setStatus('started');
	
	// Delete with the api
	apiDeletePage = function(pageTitle) {
		API.postWithToken( 'csrf', {
			action: 'delete',
			title: pageTitle,
			reason: '[[' + self.discussion.getNomPageLink() + ']]' + config.script.advert
		} )
		.done( function() {
			self.track('del', true);
		} )
		.fail( function(code, jqxhr) {
			self.track('del', false);
			self.addApiError(code, jqxhr, [
				'Could not delete page ',
				extraJs.makeLink(pageTitle)
			]);
		} );
	};
	
	var pages = self.pages || self.discussion.pages;
	self.setupTracking('del', pages.length);
	// For each page, check it exists, then delete with api or add warning
	for ( var i=0; i<pages.length; i++ ) {
		if ( pages[i].exists() ) {
			apiDeletePage(pages[i].getPrefixedText());
		} else {
			self.addWarning([
				extraJs.makeLink(pages[i].getPrefixedText()),
				' چھوڑ دیا: موجود نہیں ہے (شاید کسی نے حذف کر دیا ہے)'
			]);
			self.track('del', false);
		}
	}
};

Task.prototype.doTask.addBeingDeleted = function(self) {

	// Notify task is started
	self.setStatus('started');

	// Merge targets and pages to be merged (if any)
	var mergeTargets = [];
	var mergeTitles = [];
	if ( self.inputData.inPageActions('merge') || self.inputData.result === 'merge' ) {
		mergeTargets = self.inputData.getTargetsArray('merge');
		mergeTitles = self.discussion.getPageTitles( 
			(self.inputData.multimode ) ? self.inputData.getPages('merge') : self.pages
		);
	}

	// Edit with the Api
	var apiEditTemplate = function(pageTitle, newWikitext, editsum) {
		API.postWithToken( 'csrf', {
			action: 'edit',
			title: pageTitle,
			text: newWikitext,
			summary: editsum
		} )
		.done( function() {
			self.track('edit', true);
		} )
		.fail( function(code, jqxhr) {
			self.track('edit', false);
			self.addApiError(code, jqxhr, [
				'Could not edit page ',
				extraJs.makeLink(pageTitle)
			]);
		} );
	};

	var processTemplates = function (result) {
		var page_ids = result.query.pageids;
		for (var i = 0; i < page_ids.length; i++) {
			var pageTitle = result.query.pages[ page_ids[i] ].title;
			// Check there's a corresponding nominated page
			var pageObj = self.discussion.getPageByTitle(pageTitle, {'moduledocs': true});
			if ( !pageObj ) {
				self.addError([
					'API query result included unexpected page ',
					extraJs.makeLink(pageTitle),
					'; this page will not be edited'
				]);
				self.track('edit', false);
				continue;
			}
			// Check that page exists
			if ( parseInt(page_ids[i]) < 0 ) { 
				self.addError([
					extraJs.makeLink(pageTitle),
					' موجود نہیں ہے (شاید حذف کر دیا گیا ہے)'
				]);
				self.track('edit', false);
				continue;
			}
			
			// Replace {Template for discussion/dated} or {Tfm/dated} (which
			// may or may not be noincluded)
			var isModule = ( pageTitle.indexOf('Module:') === 0 );
			var inclusionTag = ( isModule ) ? 'includeonly' : 'noinclude';
			var oldWikitext = result.query.pages[ page_ids[i] ].revisions[0]['*'];
			var updatedWikitext = '';
			var editsum = '';
			if ( $.inArray(pageTitle, mergeTargets) !== -1 ) {
				// If this is a merge target, don't add anything - just remove nom template
				updatedWikitext = config.xfd.removeNomTemplate(oldWikitext);
				editsum = '[[' + self.discussion.getNomPageLink() + ']] کا نتیجہ: ' +
					self.inputData.getResult() + config.script.advert;
			} else if ( self.inputData.holdcell === 'ready' ) {
				// If holding cell section is 'ready for deletion', tag for speedy deletion
				updatedWikitext = '<' + inclusionTag + '>{{Db-xfd|fullvotepage=' +
					self.discussion.getNomPageLink() + '}}</' + inclusionTag + '>' +
					config.xfd.removeNomTemplate(oldWikitext);
				editsum = '[[WP:G6|G6]] Speedy deletion nomination, per [[' +
					self.discussion.getNomPageLink() + ']]' + config.script.advert;
			} else {
				// Add Being deleted template, remove nom template
				updatedWikitext = '<' + inclusionTag + '>{{حذف کیا جا رہا ہے|' + self.discussion.nomDate + '|' +
					mw.util.wikiUrlencode(self.discussion.sectionHeader);
				if ( $.inArray(pageTitle, mergeTitles) !== -1 ) {
					// If being merged, set merge parameter
					updatedWikitext += '|merge=' + self.inputData.getTarget(pageTitle);
				}
				updatedWikitext += '}}</' + inclusionTag + '>' + config.xfd.removeNomTemplate(oldWikitext);
				editsum = 'Per [[' + self.discussion.getNomPageLink() + ']], '+
				'added {{being deleted}}' + config.script.advert;
			}

			// Make the edit
			apiEditTemplate(pageTitle, updatedWikitext, editsum);

		}
		
	};

	var templateTitles = self.discussion.getPageTitles(self.pages, {'moduledocs': true});

	self.setupTracking('edit', templateTitles.length);
	//get page wikitext through api
	API.get( {
		action: 'query',
		titles: templateTitles.join('|'),
		prop: 'revisions',
		rvprop: 'content',
		indexpageids: 1,
		rawcontinue: ''
	} )
	.done( processTemplates )
	.fail( function(code, jqxhr) {
		self.addApiError(code, jqxhr, 'Could not read contents of nominated page' +
		( templateTitles.length > 1 ) ? 's' : '');
		self.setStatus('failed');
	} );
	
};

Task.prototype.doTask.addToHoldingCell = function(self) {

	// Notify task is started
	self.setStatus('started');
	
	holdCellSections = [
		self.inputData.holdcell || null,
		self.inputData.mergeHoldcell || null
	]
	.filter(function(v){ return !!v; })
	.map(function(v){ return config.xfd.holdingCellSectionNumber[v]; });
	
	self.setupTracking('processed', holdCellSections.length);

	//Function to make an edit
	var apiEditHoldingCell = function(newWikitext, sectionNum) {
		API.postWithToken( 'csrf', {
			action: 'edit',
			title: config.xfd.subpagePath + 'Holding cell',
			section: sectionNum,
			text: newWikitext,
			summary: 'Listing template(s) per [[:' + self.discussion.getNomPageLink() + ']]' +
			config.script.advert
		} )
		.done( function() {
			self.track('processed', true);
		})
		.fail( function(code, jqxhr) {
			self.track('processed', false);
			self.addApiError(code, jqxhr, 'Could not add templates to holding cell');
		} ); 
	};
	
	var processHoldingCellPage = function (result) {
		var p_id = result.query.pageids;
		// Get page contents, make all headings level 3 so sections can be counted
		var p_contents = result.query.pages[ p_id ].revisions[0]['*'].replace(/====/gi, '===');
		var sectionsArray = p_contents.split('===');
		
		for ( var i=0; i<holdCellSections.length; i++) {
			var isMergeSection = (4 <= holdCellSections[i]) && ( holdCellSections[i] <= 10);
			
			var listingPages;
			if ( !self.inputData.multimode ) {
				listingPages = self.discussion.pages;
			} else if ( isMergeSection ) {
				listingPages = self.inputData.getPages('merge');
			} else {
				listingPages = self.inputData.getPages('del');
			}
			
			var tfdlTemplates = '';
			for (ii = 0; ii < listingPages.length; ii++) {
				//Check namespace and existance
				if ( !hasCorrectNamespace(listingPages[ii]) ) {
					self.addError([
						extraJs.makeLink(listingPages[ii]),
						' is not in the ' + config.mw.namespaces[config.xfd.ns_number[0].toString()] +
						' namespace, and will not be listed at the holding cell'
					]);
				} else if ( !listingPages[ii].exists() ) {
					self.addError([
						extraJs.makeLink(listingPages[ii]),
						' does not exist, and will not be listed at the holding cell'
					]);
				} else {
					tfdlTemplates += '*{{tfdl|' + listingPages[ii].getMain() +
					'|' + self.discussion.nomDate +
					'|section=' + self.discussion.sectionHeader +
					(( holdCellSections[i] === 14 ) ? '|delete=1' : '') +
					(( listingPages[ii].getNamespaceId() === 828 ) ? '|ns=Module' : '') +
					'}}\n';
				}
			}
			
			if ( tfdlTemplates === '' ) {
				// If all don't exist or are in wrong namespace, then there's nothing to do
				self.track('processed', false);
				continue;
			}
			
			// Make new section wikitext
			var heading = sectionsArray[(holdCellSections[i]*2-1)];
			var contents = sectionsArray[(holdCellSections[i]*2)];
			// Remove "* ''None currently''" except if inside a <!--html comment-->
			contents = contents.replace(/\n*^\*\s*''None currently''\s*$(?![^<]*?-->)/gim, '');
			// Merge subsections have level-4 headings
			var headingLevel = ( (4 <= holdCellSections[i]) &&
				( holdCellSections[i] <= 10) ) ? '====' : '===';
			var newWikitext = headingLevel + heading + headingLevel + '\n' +
				contents.trim() + '\n' + tfdlTemplates;
				
			// If this isn't the first iteration of for-loop, wait a bit to avoid self-edit conflict
			if ( i > 0 ) {
				var start = new Date().getTime();
				var waited = 0;
				while ( waited < 1000 ) {
					waited = new Date().getTime() - start;
				}
			}
			apiEditHoldingCell(newWikitext, holdCellSections[i]);
		}
	};

	//get holding cell contents
	API.get( {
		action: 'query',
		titles: config.xfd.subpagePath + 'Holding cell',
		prop: 'revisions',
		rvprop: 'content',
		indexpageids: 1,
		rawcontinue: ''
	} )
	.done( processHoldingCellPage )
	.fail( function(code, jqxhr) {
		self.addApiError(code, jqxhr, 'Could not read contents of holding cell');
		self.setStatus('failed');
	} );
	
	
	
	
};

Task.prototype.doTask.deleteTalk = function(self) {

	// Notify task is started
	self.setStatus('started');

	// Get talk pages
	var talkTitles = self.discussion.getTalkTitles(self.pages);
	if ( talkTitles.length === 0 ) {
		self.addWarning('موجود نہیں');
		self.setStatus('چھوڑ دیا');
	} else {
		// get talk page redirect status from api
		self.setupTracking('del', talkTitles.length);
		
		//For each talk page, check that it exists and is eligible for a G8 speedy, then delete it
		talkTitles.map( function(talkTitle) {
			var subjectPage = self.discussion.getPageByTalkTitle(talkTitle);
			var isUserTalkBasePage = ( subjectPage.getNamespaceId() === 2 ) && ( !talkTitle.includes('/') );

			if ( !subjectPage.getTalkPage().exists() ) {
				self.addWarning([
					extraJs.makeLink(talkTitle),
					' چھوڑ دیا: موجود نہیں ہے (شاید کسی نے حذف کر دیا ہے)'
				]);
				self.track('del', false);
				return;
			}
			if ( isUserTalkBasePage ) {
				self.addWarning([
					extraJs.makeLink(talkTitle),
					' skipped: base user talk page (not eligible for G8 speedy deletion)'
				]);
				self.track('del', false);
				return;
			}

			// Delete with the api			
			return API.postWithToken('csrf', {
				action: 'delete',
				title: talkTitle,
				reason: '[[en:WP:CSD#G8|ع8]]: حذف شدہ صفحہ کا تبادلۂ خیال صفحہ' + config.script.advert
			}).then(
				function() {
					self.track('del', true);
				},
				function(code, jqxhr) {
					self.track('del', false);
					self.addApiError(code, jqxhr, [
						'صفحہ حذف نہیں ہو سکا ',
						extraJs.makeLink(talkTitle)
					]);
				}
			);
		});
	}
};

Task.prototype.doTask.tagTalkWithSpeedy = function(self) {

	// Notify task is started
	self.setStatus('started');

	var apiTagPage = function(pageTitle) {
		API.postWithToken( 'csrf', {
			action: 'edit',
			title: pageTitle,
			prependtext: '{{Db-talk}}\n',
			summary: '[[en:WP:G8|G8]] فوری حذف شدگی کے لیے نامزد، بمطابق [[:' +
				self.discussion.getNomPageLink() + ']]' + config.script.advert
		} )
		.done( function() {
			self.track('tag', true);
		} )
		.fail( function(code, jqxhr) {
			self.track('tag', false);
			self.addApiError(code, jqxhr, [
				'Could not delete page ',
				extraJs.makeLink(pageTitle)
			]);
		} );
	};
	
	// Get talk pages
	var talkTitles = self.discussion.getTalkTitles(self.pages);
	if ( talkTitles.length === 0 ) {
		self.addWarning('موجود نہیں');
		self.setStatus('چھوڑ دیا');
	} else {
		// get talk page redirect status from api
		self.setupTracking('tag', talkTitles.length);
		
		//For each talk page, check that it exists, then tag it
		for ( var i=0; i < talkTitles.length; i++ ) {
			var subjectPage = self.discussion.getPageByTalkTitle(talkTitles[i]);
			if ( subjectPage.getTalkPage.exists() ) {
				apiTagPage(talkTitles[i]);
			} else {
				self.addWarning([
					extraJs.makeLink(talkTitles[i]),
					' چھوڑ دیا: موجود نہیں ہے (شاید کسی نے حذف کر دیا ہے)'
				]);
				self.track('tag', false);
			}
		}
	}
};

Task.prototype.doTask.deleteRedirects = function(self) {
	// If unlinking backlinks, wait until that task has the info it needs
	$.when(self.discussion.taskManager.dfd.ublQuery).then(function(){
		
		// Notify task is started
		self.setStatus('started');
		self.setupTracking('alldone', 2);
		
		var redirectTitles = [];
		var redirectTalkPageIds = [];
		
		// Delete redirect with the api
		apiDeleteRedir = function(_i, pageTitle) {
			API.postWithToken( 'csrf', {
				action: 'delete',
				title: pageTitle,
				reason: 'حذف رجوع مکرر: [[' + self.discussion.getNomPageLink() + ']] کا نتیجہ: ' +
					self.inputData.getResult() + config.script.advert
			} )
			.done( function() {
				self.track('delRedir', true);
			} )
			.fail( function(code, jqxhr) {
				self.track('delRedir', false);
				self.addApiError(code, jqxhr, [
					'Could not delete redirect ',
					extraJs.makeLink(pageTitle)
				]);
			} );
		};

		// Delete redirect talkpage with the api
		apiDeleteRedirTalk = function(_i, talkpageid) {
			API.postWithToken( 'csrf', {
				action: 'delete',
				pageid: talkpageid,
				reason: '[[en:WP:CSD#G8|ع8]]: حذف شدہ صفحہ کا تبادلۂ خیال صفحہ' + config.script.advert
			} )
			.done( function() {
				self.track('delRedirTalk', true);
			} )
			.fail( function(code, jqxhr) {
				self.track('delRedirTalk', false);
				self.addApiError(code, jqxhr, [
					'حذف نہیں کیا جا سکا ',
					$('<a>').attr({
						'href':'https://ur.wikipedia.org/wiki/?curid='+talkpageid,
						'target':'_blank'
					}).text('redirect talk page <'+talkpageid+'>')
				]);
			} );
		};
		
		// If okay to delete redirects
		var doDeleteRedirects = function() {
			// Set tracking
			self.setupTracking(
				'delRedir',
				redirectTitles.length,
				function() { this.track('alldone', true); },
				function() { this.track('alldone', false); }
			);
			// Delete each redirect
			$.each(redirectTitles, apiDeleteRedir);
					
			// Check if talk pages were found
			if ( redirectTalkPageIds.length === 0 ) {
				self.track('alldone', true);
				return;
			}
			// Set tracking
			self.setupTracking(
				'delRedirTalk',
				redirectTalkPageIds.length,
				function() { this.track('alldone', true); },
				function() { this.track('alldone', false); }
			);
			// Delete each talk page
			$.each(redirectTalkPageIds, apiDeleteRedirTalk);
		};
		
		var processRedirects = function(result) {
			if ( !result.query || !result.query.pages ) {
				// No results
				self.addWarning('موجود نہیں');
				self.setStatus('چھوڑ دیا');
				return;
			}
			// Gather redirect titles into array
			$.each(result.query.pages, function(_i, info) {
				redirectTitles.push(info.title);
				if ( info.talkid ) { redirectTalkPageIds.push(info.talkid); }
			});
			// Continue query if needed
			if ( result.continue ) {
				apiQuery($.extend(query, result.continue));
				return;
			}
			
			// Check if redirects were found
			if ( redirectTitles.length === 0 ) {
				self.addWarning('موجود نہیں');
				self.setStatus('چھوڑ دیا');
				return;
			}
		
			// Warn if there will be mass action, allow user to cancel
			if ( redirectTitles.length >= 10 ) {

				var processAction = function(action) {
					if ( action === 'accept' ) {
						doDeleteRedirects();
					} else if ( action === 'show' ) {
						multiButtonConfirm({
							title: 'انتباہ',
							message: 'Mass action to be peformed: delete '+ redirectTitles.length +
							' redirects:<ul><li>[[' + redirectTitles.join(']]</li><li>[[') + ']]</li></ul>',
							actions: [
								{ label:'منسوخ', flags:'safe' },
								{ label:'رجوع مکرر حذف کریں', action:'accept', flags:'progressive' }
							],
							size: 'medium'
						})
						.then(processAction);
					} else {
						self.addWarning('صارف نے منسوخ کر دیا');
						self.setStatus('چھوڑ دیا');
					}
				};

				multiButtonConfirm({
					title: 'انتباہ',
					message: 'Mass action to be peformed: delete '+ redirectTitles.length + ' redirects.',
					actions: [
						{ label:'منسوخ', flags:'safe' },
						{ label:'رجوع مکرر دیکھیں۔۔۔', action:'show' },
						{ label:'رجوع مکرر حذف کریں', action:'accept', flags:'progressive' }
					],
					size: 'medium'
				})
				.then(processAction);
			} else {
				doDeleteRedirects();
			}
		};
		
		// Get redirects
		var query = {
			action: 'query',
			titles: self.discussion.getPageTitles(self.pages).join('|'),
			generator: 'redirects',
			grdlimit: 500,
			prop: 'info',
			inprop: 'talkid'
		};
		var apiQuery = function(q) {
			API.get( q )
			.done( processRedirects )
			.fail( function(code, jqxhr) {
				self.addApiError(code, jqxhr, 'رجوع مکرر نہیں مل سکے');
				self.setStatus('failed');
			} );
		};
		apiQuery(query);
	});
};

Task.prototype.doTask.unlinkBacklinks = function(self) {

	// Notify task is started
	self.setStatus('started');
	
	var pageTitles = self.discussion.getPageTitles(self.pages);
	var redirectTitles = [];
	// Ignore the following titles, and any of their subpages
	var ignoreTitleBases = [
		'Template:WPUnited States Article alerts',
		'Template:Article alerts columns',
		'Template:Did you know nominations'
	];
	var getBase = function(title) {
		return title.split('/')[0];
	};
	var blresults = [];
	var iuresults = [];
	
	//convert results (arrays of objects) to titles (arrays of strings), removing duplicates
	var flattenToTitles = function(results) {
		return results.reduce(
			function(flatTitles, result) {
				if ( result.redirlinks ) {
					if ( !redirectTitles.includes(result.title)) {
						redirectTitles.push(result.title);
					}
					return flatTitles.concat(
						result.redirlinks.reduce(
							function(flatRedirLinks, redirLink) {
								if (
									flatTitles.includes(redirLink.title) ||
									pageTitles.includes(redirLink.title) ||
									ignoreTitleBases.includes(getBase(redirLink.title))
								) {
									return flatRedirLinks;
								} else {
									return flatRedirLinks.concat(redirLink.title);
								}
							},
							[]
						)
					);
				} else if (
					result.redirect === '' ||
					flatTitles.includes(result.title) ||
					pageTitles.includes(result.title) ||
					ignoreTitleBases.includes(getBase(result.title))
				) {
					return flatTitles;
				} else {
					return flatTitles.concat(result.title);
				}
			},
			[]
		);
	};

	var apiEditPage = function(pageTitle, newWikitext) {
		API.postWithToken( 'csrf', {
			action: 'edit',
			title: pageTitle,
			text: newWikitext,
			summary: 'حذف ربط' +
				(( config.xfd.type === 'ffd' ) ? ' / file usage(s)' : '' ) +
				': [[' + self.discussion.getNomPageLink() + ']] کا نتیجہ: ' +
				self.inputData.getResult() + config.script.advert,
			minor: 1,
			nocreate: 1
		} )
		.done( function() {
			self.track('unlink', true);
		} )
		.fail( function(code, jqxhr) {
			self.track('unlink', false);
			self.addApiError(code, jqxhr, [
				'صفحہ ',
				extraJs.makeLink(pageTitle),
				' سے روابط نہیں ہٹائے جا سکے'
			]);
		} );
	};

	/**
	 * @param {String} pageTitle
	 * @param {String} wikitext
	 * @returns {Promise(String)} updated wikitext, with any list items either removed or unlinked
	 */
	var checkListItems = function(pageTitle, wikitext) {
		// Find lines marked with {{subst:void}}, and the preceding section heading (if any)
		var toReview = /^{{subst:void}}(.*)$/m.exec(wikitext);
		if ( !toReview ) {
			// None found, no changes needed
			return $.Deferred().resolve(wikitext).promise();
		}
		// Find the preceding heading, if any
		var precendingText = wikitext.split('{{subst:void}}')[0];
		var allHeadings = precendingText.match(/^=+.+?=+$/gm);
		var heading = ( !allHeadings ) ? null : allHeadings[allHeadings.length - 1].replace(/(^=* *| *=*$)/g, '');
		// Prompt user
		return multiButtonConfirm({
			title: 'بے ربطی کے بعد جملہ پر نظر ثانی کریں',
			message: '[[' + pageTitle +
				( ( heading ) ? '#' +
					mw.util.wikiUrlencode(
						heading.replace(/\[\[([^\|\]]*?)\|([^\]]*?)\]\]/, '$2')
						.replace(/\[\[([^\|\]]*?)\]\]/, '$1')
					) + ']]' : ']]' ) +
				': ' +
				'<pre>' + toReview[1] + '</pre>',
			actions: [
				{ label:'باقی رکھیں', action:'keep' },
				{ label:'ہٹا دیں', action:'remove'}
			],
			size: 'medium'
		})
		.then(function(action) {
			if ( action === 'keep' ) {
				// Remove the void from the start of the line
				wikitext = wikitext.replace(/^{{subst:void}}/m, '');
			} else {
				// Remove the whole line
				wikitext = wikitext.replace(/^{{subst:void}}.*\n?/m, '');
			}
			// Iterate, in case there is more to be reviewed
			return checkListItems(pageTitle, wikitext);
		});
	};
	
	var processUnlinkPages = function(result) {
		if ( !result.query || !result.query.pages ) {
			// No results
			self.addApiError('result.query.pages نہیں ملا', null, 'صفحوں کے مندرجات نہیں پڑھے جا سکے؛ '+
				'روابط نہیں ہٹائے جا سکے');
			console.log('[نبح بند] اے پی آئی نقص: result.query.pages نہیں ملا۔۔۔ نتیجہ =');
			console.log(result);
			self.setStatus('failed');
			return;
		}
		// For each page, pass the wikitext through the unlink function
		var pages = arrayFromResponsePages(result);
		pages.reduce(
			function(previous, page) {
				return $.when(previous).then(function(){
					var oldWikitext = page.revisions[0]['*'];
					var newWikitext = extraJs.unlink(
						oldWikitext,
						pageTitles.concat(redirectTitles),
						page.ns,
						!!page.categories
					);
					if ( oldWikitext !== newWikitext ) {
						var confirmedPromise = checkListItems(page.title, newWikitext);
						confirmedPromise.then(function(updatedWikitext) {
							apiEditPage(page.title, updatedWikitext);
						});
						return confirmedPromise;
					} else {
						self.addWarning(['چھوڑ دیا ',
							extraJs.makeLink(page.title),
							' (راست روابط نہیں ہے)'
						]);
						self.track('unlink', false);
						return true;
					}
				});
			},
			true);
	};

	var apiReadFail = function(code, jqxhr) {
		self.addApiError(code, jqxhr, 'صفحوں کے مندرجات نہیں پڑھے جا سکے؛ '+
			'روابط نہیں ہٹائے جا سکے');
		self.setStatus('failed');
	};
	
	var processResults = function() {
		// Flatten results arrays 
		if ( blresults.length !== 0 ) {
			blresults = flattenToTitles(blresults);
		}
		if ( iuresults.length !== 0 ) {
			iuresults = flattenToTitles(iuresults);
			// Remove image usage titles that are also in backlikns results 
			iuresults = iuresults.filter(function(t) { return $.inArray(t, blresults) === -1; });
		}

		// Check if, after flattening, there are still backlinks or image uses
		if ( blresults.length === 0 && iuresults.length === 0 ) {
			self.addWarning('موجود نہیں');
			self.setStatus('چھوڑ دیا');
			return;
		}

		
		// Ask user for confirmation
		var heading = 'روابط کی بے ربطی';
		if ( iuresults.length !== 0 ) {
			heading += '('; 
			if ( blresults.length !== 0 ) {
				heading += 'اور ';
			}
			heading += 'فائل کے استعمالات)';
		}
		heading += '';
		var para = '<p>ذیل میں موجود '+ (blresults.length + iuresults.length) + ' صفحوں میں ترمیم کی جائے گی، '+
			'تاہم اگر کسی صفحہ میں مطلوبہ ربط سانچہ کے ذریعہ شامل ہوا ہے تو اسے نہیں ہٹایا جا سکتا۔ </p>'+
			'<p>اگر آپ تمام صفحوں کی بجائے چند صفحات میں تبدیلی کرنا چاہتے ہیں تو پلک کا آلہ بے ربطی استعمال کریں۔ </p>'+
			'<p>نیز درج ذیل صفحوں کا بغور جائزہ لینے کے بعد اسے انتہائی احتیاط سے استعمال کریں '+
			'اور اس بات کا بھی خیال رکھیں کہ اس طرح کی خودکار ترامیم بوٹ پالیسی میں آسکتی ہیں۔ '+
			'</p><hr>';
		var list = '<ul>';
		if ( blresults.length !== 0 ) {
			list += '<li>[[' + blresults.join(']]</li><li>[[') + ']]</li>';
		}
		if ( iuresults.length !== 0 ) {
			list += '<li>[[' + iuresults.join(']]</li><li>[[') + ']]</li>';
		}
		list += '<ul>';
		
		multiButtonConfirm({
			title: heading,
			message: para + list,
			actions: [
				{ label: 'منسوخ کریں', flags: 'safe' },
				{ label: 'روابط ہٹائیں', action: 'accept', flags: 'progressive' }
			],
			size: 'medium'
		})
		.then(function(action) {
			if ( action ) {
				var unlinkTitles = iuresults.concat(blresults);
				self.setupTracking('unlink', unlinkTitles.length);
				self.showTrackingProgress = 'unlink';
				// get wikitext of titles, check if disambig - in lots of 50 (max for Api)
				for (var ii=0; ii<unlinkTitles.length; ii+=50) {
					API.get( {
						action: 'query',
						titles: unlinkTitles.slice(ii, ii+49).join('|'),
						prop: 'categories|revisions',
						clcategories: 'زمرہ:جملہ ضد ابہام صفحات',
						rvprop: 'content',
						indexpageids: 1
					} )
					.done( processUnlinkPages )
					.fail( apiReadFail );
				}
			} else {
				self.addWarning('صارف نے منسوخ کر دیا');
				self.setStatus('چھوڑ دیا');
			}
		});
	};

	// Queries
	var blParams = {
		list: 'backlinks',
		blfilterredir: 'nonredirects',
		bllimit: 'max',
		blnamespace: config.xfd.ns_unlink,
		blredirect: 1
	};
	var iuParams = {
		list: 'backlinks|imageusage',
		iutitle: '',
		iufilterredir: 'nonredirects',
		iulimit: 'max',
		iunamespace: config.xfd.ns_unlink,
		iuredirect: 1
	};
	var query = pageTitles.map(function(page) {
		return $.extend(
			{ action: 'query' },
			blParams,
			{ bltitle: page },
			( config.xfd.type === 'ffd' ) ? iuParams : null,
			( config.xfd.type === 'ffd' ) ? { iutitle: page } : null
		);
	});
	// Variable for incrementing current query
	var qIndex = 0;
	// Function to do Api query
	var apiQuery = function(q) {
		API.get( q )
		.done( processBacklinks )
		.fail( function(code, jqxhr) {
			self.addApiError(code, jqxhr, 'روابط نہیں مل سکے');
			self.setStatus('failed');
			// Allow delete redirects task to begin
			self.discussion.taskManager.dfd.ublQuery.resolve();
		} );
	};
	// Process api callbacks
	var processBacklinks = function(result) {
		// Gather backlink results into array
		if ( result.query.backlinks ) {
			blresults = blresults.concat(result.query.backlinks);
		}
		// Gather image usage results into array
		if ( result.query.imageusage ) {
			iuresults = iuresults.concat(result.query.imageusage);
		}
		// Continue current query if needed
		if ( result.continue ) {
			apiQuery($.extend({}, query[qIndex], result.continue));
			return;
		}
		// Start next query, unless this is the final query
		qIndex++;
		if ( qIndex < query.length ) {
			apiQuery(query[qIndex]);
			return;
		}
		// Allow delete redirects task to begin
		self.discussion.taskManager.dfd.ublQuery.resolve();
		// Check if any backlinks or image uses were found
		if ( blresults.length === 0 && iuresults.length === 0 ) {
			self.addWarning('موجود نہیں');
			self.setStatus('چھوڑ دیا');
			return;
		}
		// Process the results
		processResults();
	};
	// Get started
	apiQuery(query[qIndex]);
	
};

Task.prototype.doTask.redirect = function(self) {

	// Notify task is started
	self.setStatus('started');
	
	var pageTitles = self.discussion.getPageTitles(self.pages);
	var deleteFirst = self.inputData.deleteFirst;
	var softRedirect = self.inputData.result === 'soft redirect';
	var rcats = self.inputData.rcats;
	var rcatshell = ( rcats ) ? "\n\n{{Rcat shell|\n" + rcats + "\n}}" : '';

	self.setupTracking('redir', pageTitles.length);

	// Make a redirect
	apiMakeRedirect = function(pageTitle) {
		var newWikitext;
		if ( pageTitle.indexOf('Module:') === 0 ) {
			var targetPage = self.inputData.getTargetLink(pageTitle, true);
			if ( targetPage.indexOf('Module:') !== 0 ) {
				self.track('redir', false);
				self.addError([
					'Could not redirect ',
					extraJs.makeLink(pageTitle),
					' because target (',
					extraJs.makeLink(targetPage),
					') is not a module'
				], true);
				return false;
			}
			newWikitext = 'return require( "' + targetPage + '" )';
		} else if ( softRedirect ) {
			newWikitext = '{{Soft redirect|' + self.inputData.getTargetLink(pageTitle, true) +
			'}}' + rcatshell;
		} else {
			newWikitext = "#REDIRECT " + self.inputData.getTargetLink(pageTitle) + rcatshell;
		}
		
		API.postWithToken( 'csrf', {
			action: 'edit',
			title: pageTitle,
			text: newWikitext,
			summary: '[[:' + self.discussion.getNomPageLink() + ']] کا نتیجہ: ' +
				self.inputData.getResult() + config.script.advert
		} )
		.done( function() {
			self.track('redir', true);
		} )
		.fail( function(code, jqxhr) {
			self.track('redir', false);
			self.addApiError(code, jqxhr, [
				'Could not edit page ',
				extraJs.makeLink(pageTitle)
			]);
		} );
	};

	// Delete before redirecting
	apiDelAndRedir = function(pageTitle) {
		API.postWithToken( 'csrf', {
			action: 'delete',
			title: pageTitle,
			reason: '[[' + self.discussion.getNomPageLink() + ']]' + config.script.advert
		} )
		.done( function() {
			apiMakeRedirect(pageTitle);
		} )
		.fail( function(code, jqxhr) {
			self.track('redir', false);
			self.addApiError(code, jqxhr, [
				'Could not delete page ',
				extraJs.makeLink(pageTitle)
			]);
		} );
	};
	
	// For each page, check that it isn't a target, then redirect, or delete and redirect
	for ( var i=0; i<pageTitles.length; i++ ) {
		if ( $.inArray(pageTitles[i], self.inputData.getTargetsArray('redirect')) !== -1 ) {
			//Skip (don't make a page redirect to itself)
			self.track('redir', false);
		} else if ( deleteFirst ) {
			apiDelAndRedir(pageTitles[i]);
		} else {
			apiMakeRedirect(pageTitles[i]);
		}
	}
};

Task.prototype.doTask.removeCircularLinks = function(self) {

	// Notify task is started
	self.setStatus('started');

	var pageTitles = self.discussion.getPageTitles();	
	var targetTitles = self.inputData.getTargetsArray('redirect');
	self.setupTracking('uncircle', targetTitles.length);

	// Edit with the Api
	var apiEditPage = function(pageTitle, newWikitext) {
		API.postWithToken( 'csrf', {
			action: 'edit',
			title: pageTitle,
			text: newWikitext,
			summary: 'Unlinking circular redirects: [[:' + self.discussion.getNomPageLink() +
				']] کا نتیجہ: ' + self.inputData.getResult() + config.script.advert
		} )
		.done( function() {
			self.track('uncircle', true);
		} )
		.fail( function(code, jqxhr) {
			self.track('uncircle', false);
			self.addApiError(code, jqxhr, [
				'Could not edit page ',
				extraJs.makeLink(pageTitle)
			]);
		} );
	};
	
	processTargets = function (result) {
		$.each(result.query.pages, function(_i, page) {
			console.log('_i = ' + _i + '\npage.title = ' + page.title);
			var oldWikitext = page.revisions[ 0 ][ '*' ];
			// Don't remove selflinks
			var unlinkPageTitles = pageTitles.filter(function(t){
				return t !== page.title;
			});
			var newWikitext = extraJs.unlink(oldWikitext, unlinkPageTitles);
			
			// Check if any changes need to be made; if redirect target is a nominated page, remove
			// nomination template and adjust edit summary
			if ( newWikitext === oldWikitext ) {
				// No links to unlink
				self.addWarning('موجود نہیں');
				self.track('uncircle', false);
			} else if ( $.inArray(page.title, pageTitles) !== -1 ) {
				// Target is one of the nominated pages - also remove nom template if present
				newWikitext = config.xfd.removeNomTemplate(newWikitext);
				apiEditPage(page.title, newWikitext);
			} else {
				apiEditPage(page.title, newWikitext);
			}
		});
	};
	
	// Get the wikitext of each target
	API.get( {
		action: 'query',
		titles: targetTitles.join('|'),
		prop: 'revisions',
		rvprop: 'content',
		indexpageids: 1,
		rawcontinue: ''
	} )	
	.done( processTargets )
	.fail( function(code, jqxhr) {
		self.track('redir', false);
		self.addApiError(code, jqxhr,
			'Could not read contents of redirect target' + ( targetTitles.length > 1 ) ? 's' : '');
	} );	
};

// --- Relisting tasks ---
Task.prototype.doTask.getRelistInfo = function(self) {

	// Notify task is started
	self.setStatus('شروع ہو گیا');
	
	self.setupTracking('done', 1);

	var now = new Date();
	var today = now.getUTCDate() + ' ' + config.mw.monthNames[now.getUTCMonth()] +
		' ' + now.getUTCFullYear();
	var todaysLogpage = config.xfd.path + today;
	
	// Set log page link in task message
	if ( config.xfd.type !== 'mfd' ) {
		self.discussion.taskManager.getTaskByName('updateNewLogPage').setDescription([
			extraJs.makeLink(todaysLogpage, "آج کے نوشتہ"),
			' میں اندراج'
		]);
	}
	
	var processAfdLogpages = function(result) {
		
		// Get wikitext
		var newlogtext = '';
		var oldlogtext = '';
		var ids = result.query.pageids;
		
		// Check how many log pages were found
		if ( ids.length === 1 ) {
			// Abort if only one log page found
			self.discussion.taskManager.abortTasks(
				'discussion already transcluded to today\'s log page'
			);
			return;
		}
		
		// Identify log pages
		if ( result.query.pages[ ids[0] ].title === todaysLogpage ) {
			// ids[0] is the new log page
			newlogtext = result.query.pages[ ids[0] ].revisions[0]['*'];
			oldlogtext = result.query.pages[ ids[1] ].revisions[0]['*'];
		} else {
			// ids[0] is the old log page
			newlogtext = result.query.pages[ ids[1] ].revisions[0]['*'];
			oldlogtext = result.query.pages[ ids[0] ].revisions[0]['*'];
		}
		
		// Abort if already relisted
		var t = mw.RegExp.escape(self.discussion.nomPage);
		var oldPatt = new RegExp('<!-- ?\\{\\{' + t + '\\}\\} ?-->', 'i');
		var newPatt = new RegExp('\\{\\{' + t + '\\}\\}', 'i');
		if ( oldPatt.test(oldlogtext) || newPatt.test(newlogtext) ) {
			self.discussion.taskManager.abortTasks('discussion has been relisted already');
			return;
		}
		
		// Updated new log wikitext:
		var newlogreg = new RegExp('<!-- Add new entries to the TOP of the following list -->','i');
		self.discussion.taskManager.relistInfo.newLogWikitext = newlogtext.replace(
			newlogreg,
			'<!-- Add new entries to the TOP of the following list -->\n{{' +
			self.discussion.nomPage + '}}<!--Relisted-->'
		);
		
		// Updated old log wikitext:
		var oldlogreg = new RegExp("(\\{\\{" + t + "\\}\\})", 'i' );
		self.discussion.taskManager.relistInfo.oldlogTransclusion = oldlogreg.test(oldlogtext);
		self.discussion.taskManager.relistInfo.oldLogWikitext = oldlogtext.replace(
			oldlogreg, '<!-- $1 -->');
		
		// Ready to proceed to next tasks
		self.track('done', true);
	};

	// TFD, RRD
	var processTodaysLogpage = function(result) {
		var _id = result.query.pageids;
		var _contents = result.query.pages[ _id ].revisions[ 0 ][ '*' ];
		var _h4 = _contents.match('====');
		if ( _h4 ) {
			// there is at least 1 level 4 heading on page - can prepend to section #2
			self.discussion.taskManager.relistInfo.newLogEditType = 'prependtext';
			self.discussion.taskManager.relistInfo.newLogSection = 2;
		} else {
			// there are no level 4 headings on page - can append to section #1
			self.discussion.taskManager.relistInfo.newLogEditType = 'appendtext';
			self.discussion.taskManager.relistInfo.newLogSection = 1;

		}
		// Ready to proceed to next tasks
		self.track('done', true);	
	};

	var processNomPage = function (result) {
		// Discussion wikitext
		var id = result.query.pageids;
		var oldWikitext = result.query.pages[ id ].revisions[0]['*'];
		var heading = oldWikitext.slice(0, oldWikitext.indexOf('\n'));

		// Abort if discussion is already closed
		if ( oldWikitext.indexOf('xfd-closed') !== -1 ) {
			self.discussion.taskManager.abortTasks('گفتگو بند کر دی گئی ہے');
			return;
		}
		
		// Relist template
		var relists = oldWikitext
			.match(/\[\[Wikipedia:Deletion process#Relisting discussions\|Relisted\]\]/g);
		var relistTemplate = '\n{{subst:Relist|1=' + self.inputData.getRelistComment() +
			'|2=' + (( relists ) ? relists.length + 1 : 1) + '}}\n';
		
		// New/relisted discussion
		var newWikitext = oldWikitext.trim() + relistTemplate;
		
		// Wikitext for old log page
		var oldLogWikitext = '';
		
		if ( config.xfd.type === 'نبح' ) {
			// Update link to log page
			newWikitext = newWikitext.replace(
				/\[\[ویکیپیڈیا:مضامین برائے حذف\/نوشتہ\/\d{1,2} \w+ \d{4}#/,
				'[[' + todaysLogpage + '#'
			);
		
		} else if ( config.xfd.type === 'ffd' || config.xfd.type === 'tfd' ) {
			// Discussion on old log page gets closed
			var xfdCloseTop = config.xfd.wikitext.closeTop
				.replace(/__RESULT__/, 'مکرر نامزد کیا گیا')
				.replace(/__TO_TARGET__/, ' on [[' + todaysLogpage + '#' +
					self.discussion.sectionHeader + '|' + today + ']]')
				.replace(/__RATIONALE__/, '')
				.replace(/__SIG__/, config.user.sig);
			// List of nominated pages
			var pagesList = '';
			if ( !self.discussion.isBasicMode() ) {
				pagesList = self.discussion.pages.reduce(function(list, page) {
					var namespaceParam = ( page.getNamespaceId() === 828 ) ? '|module=Module' : '';
					return list + config.xfd.wikitext.pagelinks.replace('__PAGE__',	page.getMain() + namespaceParam);
				}, '');
			}
			oldLogWikitext = heading + '\n' + xfdCloseTop + '\n' +
			pagesList + config.xfd.wikitext.closeBottom;
			
		} else if ( config.xfd.type === 'mfd' ) {
			// Find first linebreak after last pagelinks templats
			var splitIndex = newWikitext.indexOf('\n', newWikitext.lastIndexOf(':{{pagelinks'));
			// Add time stamp for bot to properly relist
			newWikitext = (newWikitext.slice(0, splitIndex).trim() +
			'\n{{subst:mfdr}}\n' + newWikitext.slice(splitIndex+1).trim());
		
		} else if ( config.xfd.type === 'rfd' ) {
			var topWikitext = '====' + self.discussion.sectionHeader + '====';
			if ( oldWikitext.indexOf('*<span id=') !== oldWikitext.lastIndexOf('*<span id=') ) {
				// Multiple redirects were nominted, so keep nominated redirects' anchors
				// Find linebreak prior to first span with an id
				var splitIndex1 = oldWikitext.indexOf('\n', oldWikitext.indexOf('*<span id=')-2);
				// Find linebreak after the last span with an id
				var splitIndex2 = oldWikitext.indexOf('\n', oldWikitext.lastIndexOf('*<span id='));
				var rfdAnchors = oldWikitext.slice(splitIndex1, splitIndex2)
					.replace(/\*<span/g, '<span') // remove bullets
					.replace(/^(?!<span).*$\n?/gm, '') // remove lines which don't start with a span
					.replace(/>.*$\s*/gm, '></span>') // remove content within or after span
					.trim();
				topWikitext += '\n<noinclude>' + rfdAnchors + '</noinclude>';
			}
			oldLogWikitext = topWikitext + '\n{{subst:rfd relisted|page=' + today +
			'|' + self.discussion.sectionHeader + '}}';
		}
		
		// Store wikitext in task manager
		self.discussion.taskManager.relistInfo = {
			'today': today,
			'newWikitext': newWikitext,
			'oldLogWikitext': oldLogWikitext
		};
		
		// For AfDs, check that it's still transcluded to old log page...		
		if ( config.xfd.type === 'نبح' ) {
			// Get array of "embeddedin" pages which are logpages (should only be one)
			var eiLogpages = result.query.embeddedin.filter(function(ei) {
				return ei.title.indexOf(config.xfd.path) !== -1;
			});
			// Abort if none found
			if ( eiLogpages.length === 0 ) {
				self.addError('نوشتہ کا پرانا صفحہ نہیں مل سکا');
				self.discussion.taskManager.abortTasks('');
				return;
			}
			// Warn if multiple log pages were found
			if ( eiLogpages.length > 1 ) {
				for (var i = 1; i < eiLogpages.length; i++) {
					self.addWarning([
						'Note: transcluded on additional log page: ',
						extraJs.makeLink(
							eiLogpages[i].title,
							eiLogpages[i].title.replace(config.xfd.path, '')
						)
					]);
				}
			}
			// Set old log page 
			var oldLogpage = eiLogpages[0];
			// Abort if old log page is actually today's logpage
			if ( oldLogpage.title === todaysLogpage ) {
				self.addError('آج کے نوشتہ میں پہلے سے شامل ہے');
				self.discussion.taskManager.abortTasks('');
				return;
			}
			// Add old log page link in task message
			self.discussion.taskManager.getTaskByName('updateOldLogPage').setDescription([
				'Removing from ',
				extraJs.makeLink(oldLogpage.title, 'old log page')
			]);
			// Store old log title
			self.discussion.taskManager.relistInfo.oldlogtitle = oldLogpage.title;
			self.discussion.taskManager.relistInfo.newLogEditType = 'text';
			// Get logpages' wikitext
			API.get( {
				action: 'query',
				titles: oldLogpage.title + '|' + todaysLogpage,
				prop: 'revisions',
				rvprop: 'content',
				indexpageids: 1,
				rawcontinue: ''
			} )
			.done( processAfdLogpages )
			.fail( function(code, jqxhr) {
				self.addApiError(code, jqxhr, 'Could not read contents of log pages');
				self.discussion.taskManager.abortTasks('');
			} );
			
		} else if ( config.xfd.type === 'tfd' || config.xfd.type === 'rfd' ) {
			//New discussions on top of log page, so need to check out current log page wikitext
			API.get( {
				action: 'query',
				titles: todaysLogpage,
				prop: 'revisions',
				rvprop: 'content',
				indexpageids: 1,
				rawcontinue: ''
			} )
			.done( processTodaysLogpage )
			.fail( function(code, jqxhr) {
				self.addApiError(code, jqxhr, [
					'Could not read contents of  ',
					extraJs.makeLink(todaysLogpage, "today's log page")
				]);
				self.discussion.taskManager.abortTasks('');
			} );
				
		} else { // ffd, mfd
			self.discussion.taskManager.relistInfo.newLogEditType = 'appendtext';
			// Ready to proceed to next task(s)
			self.track('done', true);
		}
	};
	
	var query = {
			action: 'query',
			titles: self.discussion.nomPage,
			prop: 'revisions',
			indexpageids: 1,
			rawcontinue: 1,
			rvprop: 'content',
			rvsection: self.discussion.sectionNumber
		};
	if ( config.xfd.type === 'نبح' ) {
		$.extend(query, { 
			list: 'embeddedin',
			eititle: self.discussion.nomPage,
			einamespace: config.xfd.ns_logpages,
			eifilterredir: 'nonredirects',
			eilimit: 500
		});
		// Need to fetch whole page, in order to check if afd is already closed
		delete query.rvsection;
	}
	API.get( query )
	.done( processNomPage )
	.fail( function(code, jqxhr) {
		self.addApiError(code, jqxhr,
			[	'Could not read contents of page ',
				extraJs.makeLink(self.discussion.nomPage),
				'; could not relist discussion'	]
		);
		self.discussion.taskManager.abortTasks('');
	} );
	
};	

Task.prototype.doTask.updateDiscussion = function(self) {

	// Notify task is started
	self.setStatus('started');
	
	self.setupTracking('edit', 1);
	
	var relistInfo = self.discussion.taskManager.relistInfo;
	
	var params = {
		action: 'edit',
		title: self.discussion.nomPage,
		text: relistInfo.newWikitext,
		summary: 'مکرر نامزدگی' + config.script.advert
	};
	if ( config.xfd.type === 'mfd' ) {
		params.section = self.discussion.sectionNumber;
	}
	
	API.postWithToken( 'csrf', params )
	.done( function() {
		self.track('edit', true);
	} )
	.fail( function(code, jqxhr) {
		self.track('edit', false);
		self.addApiError(code, jqxhr, 'Could not edit ' + config.xfd.type.toUpperCase() +
		' discussion');
	} );
};

Task.prototype.doTask.updateOldLogPage = function(self) {

	// Notify task is started
	self.setStatus('started');
	
	self.setupTracking('edit', 1);
	
	var relistInfo = self.discussion.taskManager.relistInfo;
	
	var params = {
		action: 'edit',
		title: ( config.xfd.type === 'نبح' ) ? relistInfo.oldlogtitle : self.discussion.nomPage,
		text: relistInfo.oldLogWikitext,
		summary: (( config.xfd.type === 'نبح' ) ? 'Relisting [[:' + self.discussion.nomPage +
			']]' : '/* ' + self.discussion.sectionHeader + ' */ Relisted on [[:' +
			config.xfd.path + relistInfo.today + '#' + self.discussion.sectionHeader +
			'|' + relistInfo.today + ']]') + config.script.advert
	};
	
	if ( config.xfd.type === 'نبح' ) {
		// Skip if transclusion wasn't found
		if ( !relistInfo.oldlogTransclusion ) {
			self.track('edit', false);
			self.addError('Transclusion not found on old log page; could not be commented out');
			return;
		}
	} else {
		params.section = self.discussion.sectionNumber;
	}
	
	API.postWithToken( 'csrf', params )
	.done( function() {
		self.track('edit', true);
	} )
	.fail( function(code, jqxhr) {
		self.track('edit', false);
		self.addApiError(code, jqxhr, 'Could not edit old ' + config.xfd.type.toUpperCase() +
		' log page');
	} );
};

Task.prototype.doTask.updateNewLogPage = function(self) {

	// Notify task is started
	self.setStatus('شروع ہو گیا');
	
	self.setupTracking('edit', 1);
	
	var relistInfo = self.discussion.taskManager.relistInfo;
	if ( relistInfo.newLogEditType === 'appendtext' ) {
		relistInfo.newWikitext = '\n' + relistInfo.newWikitext;
	}
	
	var params = {
		action: 'edit',
		title: config.xfd.path + relistInfo.today,
		summary: '' + (( config.xfd.type === 'نبح' ) ? '[[:' + self.discussion.nomPage +
		']] کی مکرر نامزدگی' : '"' + self.discussion.sectionHeader + '"') + config.script.advert
	};
	if ( config.xfd.type === 'نبح' ) {
		params.text = relistInfo.newLogWikitext;
	} else {
		params[relistInfo.newLogEditType]  = relistInfo.newWikitext;
	}
	
	if ( /(tfd|rfd)/.test(config.xfd.type) ) {
		params.section = relistInfo.newLogSection;
	}
	
	API.postWithToken( 'csrf', params )
	.done( function() {
		self.track('edit', true);
	} )
	.fail( function(code, jqxhr) {
		self.track('edit', false);
		self.addApiError(code, jqxhr, 'Could not edit today\'s ' + config.xfd.type.toUpperCase() +
		' log page');
	} );
};

Task.prototype.doTask.updateNomTemplates = function(self) {

	// Notify task is started
	self.setStatus('started');

	var relistInfo = self.discussion.taskManager.relistInfo;
	
	var pageTitles = self.discussion.getPageTitles(null, {'moduledocs':true});
	self.setupTracking('edit', pageTitles.length);

	API.get( {
		action: 'query',
		titles: pageTitles.join('|'),
		prop: 'revisions',
		rvprop: 'content',
		indexpageids: 1
	} )
	.then(
		function(response) {
			return $.map(response.query.pages, function(page, id) {
				var pageObj = self.discussion.getPageByTitle(page.title, {'moduledocs':true});
				if ( !pageObj ) {
					self.addError([
						'API query result included unexpected title ',
						extraJs.makeLink(page.title),
						'; this page will not be edited'
					]);
					self.track('edit', false);
					return;
				}
				var pageExists = parseInt(id) > 0;
				if ( !pageExists ) { 
					self.addError([
						extraJs.makeLink(page.title),
						' does not exist and will not be edited'
					]);
					self.track('edit', false);
					return;
				}
				var updatedWikitext = page.revisions[0]['*'].replace(
					config.xfd.regex.relistPattern,
					config.xfd.wikitext.relistReplace
						.replace("__TODAY__", relistInfo.today)
						.replace("__SECTION_HEADER__", self.discussion.sectionHeader)
				);
				var noChangesToMake = updatedWikitext === page.revisions[0]['*'];
				if ( noChangesToMake ) {
					self.track('edit', false);
					self.addWarning([
						'چھوڑ دیا ',
						extraJs.makeLink(page.title),
						': نامزدگی کا سانچہ نہیں ملا'
					]);
					return;				
				}

				return API.postWithToken( 'csrf', {
					action: 'edit',
					title: page.title,
					text: updatedWikitext,
					summary: 'تجدید ' + config.xfd.type.toUpperCase() +
						' template: discussion was relisted' + config.script.advert
				} )
				.done( function() {
					self.track('edit', true);
				} )
				.fail( function(code, jqxhr) {
					self.track('edit', false);
					self.addApiError(code, jqxhr,
						['صفحہ ', extraJs.makeLink(page.title), 'میں ترمیم نہیں کی جا سکی']
					);
				} );
			})
		},
		function(code, jqxhr) {
			self.addApiError(code, jqxhr, 'Could not read contents of nominated page' +
			( pageTitles.length > 1 ) ? 's' : '');
			self.setStatus('failed');
		}
	);
};


/* ========== ShowHideTag class =================================================================
   The 'tag' at the bottom of screen that toggles the visibility of closed discussions.
   ---------------------------------------------------------------------------------------------- */
// Constructor
var ShowHideTag = function() {
	// Determine previous state from localStorage
	try {
		if ( !!localStorage.getItem('xfdc-closedHidden') ) {
			this.isHidden = true;
		} else {
			this.isHidden = false;
		}
	} catch(e) {
		// If localStorage not available, default to not hidden
		this.isHidden = false;
	}
};

// ---------- ShowHideTag prototype ------------------------------------------------------------- */
ShowHideTag.prototype.hideClosed = function() {
	this.isHidden = true;
	try {
		localStorage.setItem('xfdc-closedHidden', true);
	}  catch(e) {}
	$('.xfd-closed, .tfd-closed, #XFDcloser-showhide-hide').hide();
	$('#XFDcloser-showhide-show').show();
};

ShowHideTag.prototype.showClosed = function() {
	this.isHidden = false;
	try {
		localStorage.setItem('xfdc-closedHidden', '');
	}  catch(e) {} 
	$('.xfd-closed, .tfd-closed, #XFDcloser-showhide-hide').show();
	$('#XFDcloser-showhide-show').hide();
};
	
ShowHideTag.prototype.initialise = function() {
	var self = this;
	$('<div>')
	.attr('id', 'XFDcloser-showhide')
	.append(
		$('<a>')
		.attr('id', 'XFDcloser-showhide-hide')
		.text('بند گفتگو کو چھپائیں')
		.toggle(!self.isHidden)
		.on('click', self.hideClosed),
		$('<a>')
		.attr('id', 'XFDcloser-showhide-show')
		.text('Show closed discussions')
		.toggle(self.isHidden)
		.on('click', self.showClosed)
	)
	.appendTo('body');
};
   
/* ========== Get started ======================================================================= */
// Initialise show/hide closed discussions tag, unless there is only one discussion on the page
if ( $('#mw-content-text ' + config.xfd.html.head).length > 1 ) {
	config.showHide = new ShowHideTag();
	config.showHide.initialise();
}

// Set up discussion object for each discussion
$(config.xfd.html.head + ' > span.mw-headline')
.not('.XFDcloser-ignore')
.each(function(i) {
	var d = Discussion.newFromHeadlineSpan(i, this);
	if ( d ) {
		try {
			if ( d.isBasicMode() ) {
				d.showLinks();
			} else {
				d.retrieveExtraInfo()
				.then(
					function() { d.showLinks(); },
					function(failMessage) {
						// set "basic" mode, show "basic" links with the failure message 
						d.pages = false;
						d.showLinks(failMessage);
					}
				);
			}
		} catch(e) {
			console.warn('[نبح بند] Could not retrieve page info for ' + $(this).text() +
			' [see error below]');
			console.warn(e);
		}
	}
});

// If showHide state is hidden, hide any headings that may have had class 'xfd-closed' added
if ( config.showHide && config.showHide.isHidden ) {
	config.showHide.hideClosed();
}

/* ==========  End of full file closure wrappers ================================================ */
});
});
/* </nowiki> */