ব্যবহারকারী:Tanbiruzzaman/খসড়ায় স্থানান্তর/core.js

লক্ষ্য করুন: প্রকাশ করার পর, পরিবর্তনগুলো দেখতে আপনাকে আপনার ব্রাউজারের ক্যাশে পরিষ্কার করার প্রয়োজন হতে পারে।

  • ফায়ারফক্স / সাফারি: পুনরায় লোড-এ ক্লিক করার সময় শিফট টিপে ধরে রাখুন, অথবা হয় Ctrl-F5 বা Ctrl-R টিপুন (ম্যাকে ⌘-R টিপুন)
  • গুগল ক্রোম: Ctrl-Shift-R (ম্যাকে ⌘-Shift-R) টিপুন
  • ইন্টারনেট এক্সপ্লোরার / এজ: Ctrl ধরে রাখা অবস্থায় Refresh-এ ক্লিক করুন, অথবা Ctrl-F5 টিপুন
  • অপেরা: Ctrl-F5 টিপুন।
/******************************************************************************
 Companion file for MoveToDraft
******************************************************************************/
/* jshint laxbreak: true, undef: true, maxerr:999 */
/* globals console, window, document, $, mw */
// <nowiki>

// Wikitext strings
window.config.wikitext = {
	"editsummary": window.m2d_editsummary || window.m2d_rationale ||
		"[[WP:AFC|AFC]] draft",
	"logMsg": "#[[$1]] moved to [[$2]] at ~~~~~",
	"notificationHeading": "[[Draft:$1|$1]] moved to draftspace",
	"notificationTemplate": window.m2d_notification ||
		"Thanks for your contributions to [[Draft:$1|$1]]. Unfortunately, I do not think it is ready for publishing at this time$3.\nI have converted your article to a draft which you can improve, undisturbed for a while.\n\nPlease see more information at [[Help:Unreviewed new page]].\nWhen the article is ready for publication, please click on the \"Submit your draft for review!\" button at the top of the page OR move the page back. ~~~~",
	"rationale": window.m2d_rationale ||
		"[[WP:DRAFTIFY|Not ready]] for mainspace, incubate in draftspace."
};
// Custom change tag to be applied to all M2D actions, create at Special:Tags
window.config.changeTags = 'moveToDraft';
window.config.draftReasons = [
	{ "long" : "it has no sources", "short" : "no sources" },
	{ "long" : "it needs more sources to establish notability",
		"short" : "more sources needed" },
	{ "long" : "it has too many problems of language or grammar",
		"short" : "language/grammar problems" },
	{ "long" : "it is a poor translation", "short" : "poor translation" },
	{ "long" : "it is promotional and reads like an advertisement",
		"short" : "promotional/ad" },
	{ "long" : "you may have a possible Conflict of Interest",
		"short" : "possible COI" }
];
window.config.minimumAgeInMinutes = 60;
window.config.maximumAgeInDays = 90;

window.config.doNotLog = (
        window.m2d_doNotLog ? true : false );
// Page data -- to be retreived later from api
window.config.pagedata = {};
window.config.pagedata.previousDraftification = false;
window.config.pagedata.contributors = {};
window.config.pagedata.notifyList = [];
window.config.processTimer = [];

// Helper functions
// - prettify an encoded page title (or at least replace underscores with
// spaces)
var getPageText = function(p) {
	var t = mw.Title.newFromText( decodeURIComponent(p) );
	if (t) {
		return t.getPrefixedText();
	} else {
		return p.replace(/_/g, " ");
	}
};

/**
 * makeApiErrorMsg
 *
 * Makes an error message, suitable for displaying to a user, from the values
 * that the MediaWiki Api passes to the failure callback function, e.g.
 * `new mw.Api.get(queryObject}).done(successCallback).fail(failureCallback)`
 *
 * @param {string} code
 *  First paramater passed to failure callback function.
 * @param {jQuery.jqXHR} jqxhr
 *  Second paramater passed to failure callback function.
 * @return {string} Error message details, with in a format like
 *  "(API|HTTP) error: details"
 */
var makeErrorMsg = function(code, jqxhr) {
	var details = '';
	if ( code === 'http' && jqxhr.textStatus === 'error' ) {
		details = 'HTTP error ' + jqxhr.xhr.status;
	} else if ( code === 'http' ) {
		details = 'HTTP error: ' + jqxhr.textStatus;
	} else if ( code === 'ok-but-empty' ) {
		details = 'Error: Got an empty response from the server';
	} else {
		details = 'API error: ' + code;
	}
	return details;
};

/**
 * makeLink
 *
 * Makes a link to a en.Wikipedia page that opens in a new tab/window.
 * 
 * @requires {Module} mediawiki.util
 * @param {string} linktarget
 *  The target page.
 * @param {string} linktext
 *  Text to display in the link. Optional, if not provided the target will be used.
 * @return {jQuery} jQuery object containing the `<a>` element.
 */
var makeLink = function(linktarget, linktext) {
	if ( linktext == null ) {
		linktext = linktarget;
	}
	return $('<a>').attr({
		'href': mw.util.getUrl(linktarget),
		'target':'_blank'
	}).text(linktext);
};

/* ========== Tasks ======================================================== */

// Grab page data - initial author, current wikitext, any redirects, if Draft:
// page already exists
var grabPageData = function() {

	var isNonSignificantEdit = function ( revision ) {
		//edits marked as minor
		if ( revision.minor === "" ) {
			return true;
		}

		if ( revision.anon === "" ) {
			return true;
		}
		
		const tagsToIgnore = [ "pagetriage", "mw-undo", "AWB", "twinkle",
			"shortdesc helper", "mw-manual-revert", "mw-undo", "mw-rollback",
			"mw-reverted", window.config.changeTags ];
		const intersectionArray = revision.tags.filter(
				(element) => tagsToIgnore.includes(element));
		if ( intersectionArray.length > 0 ) {
			return true;
		}
		return false;
	};

	/* ---------- Initial author ------------------------------------------- */

	/* Try making an api call for just the first revision - but if that is a
		redirect, then get 'max' number of revisions, and look for first
		non-redirect revision - use this as the initial author,	not the creator
		of the redirect.
	*/
	var processMaxRvAuthorQuery = function (result) {
		var revisions = result.query.pages[window.config.mw.wgArticleId].revisions;
		var patternForRedirect = /^\s*#redirect/i;

		// blanking the contributor array
		window.config.pagedata.contributors = {};
		window.config.pagedata.lastEditTimestamp = revisions[revisions.length - 1].timestamp;
		for ( let i = 0; i < revisions.length; i++ ) {
			if ( !patternForRedirect.test(revisions[i]["*"]) ) {
				if (window.config.pagedata.author === undefined) {
					window.config.pagedata.author = revisions[i].user;
					window.config.pagedata.creationTimestamp = revisions[i].timestamp;
				}

				// Find if there is a comment which indicates previous draftification
				if( revisions[i].comment.search(/moved page \[\[.*\]\] to \[\[Draft:/) !== -1) {
					window.config.pagedata.previousDraftification = true;
				}

				if( isNonSignificantEdit( revisions[i] ) ) {
					continue;
				}

				// Ignoring edits made by the current user
				// and non-significant edits (previous if condition)
				if( revisions[i].user !== window.config.mw.wgUserName ) {
					window.config.pagedata.lastEditTimestamp = revisions[i].timestamp;
				}

				// Calculating contribs per editor
				if (window.config.pagedata.contributors[revisions[i].user] === undefined) {
					window.config.pagedata.contributors[revisions[i].user] = 1;
				} else {
					window.config.pagedata.contributors[revisions[i].user]++;
				}
			}
		}
		// Check that we actually found an author (i.e. not all revisions were
		// redirects)
		if ( window.config.pagedata.author == null ) {
			window.API.abort();
			const retry = confirm("Could not retrieve page author.\n\nTry again?");
			if ( retry ) {
				screen0();
			} else {
				$("#M2D-modal").remove();
			}
		}

		return removeBotsfromContributorList()
		.then( sortContributorsByEdits );
	};

	var removeBotsfromContributorList = function () {
		// Query to get bots from the contributor list
		return window.API.get( {
			action: "query",
			list: "users",
			ususers: Object.keys( window.config.pagedata.contributors ).join( "|" ),
			usprop: ["groups"]
		} )
		.then( function(result) {
			for ( let userResult of result.query.users ) {
				if ( userResult.groups.indexOf( "bot" ) > -1 ) {
					window.config.pagedata.contributors[ userResult.name ] = 0;
				}
			}
			return Promise.resolve();
		} );
	};

	var sortContributorsByEdits = function () {
		// temporary array to ease sorting
		let sortable = [];
		for ( let contributor in window.config.pagedata.contributors ) {
			if ( contributor !== window.config.mw.wgUserName ) {
				sortable.push({"c": contributor,
					"e": window.config.pagedata.contributors[contributor] });
			}
		}

		sortable.sort( function( a, b ) {
			// make sure that the page creator is on the top
			if( a.c === window.config.pagedata.author ) {
				return -100;
			}
			if( b.c === window.config.pagedata.author ) {
				return 100;
			}
			// sorting by number of edits
			return b.e - a.e;
		});

		window.config.pagedata.contributors = {};
		for ( let i in sortable ) {
			if ( i >= 5 || sortable[ i ].e < 1 ) {
				// only show the top 5 contributors with more than 0 edits
				break;
			}
			window.config.pagedata.contributors [ sortable[ i ].c ] = sortable[ i ].e;
		}
		return Promise.resolve();
	};

	var processAuthorQuery = function (result) {
		// Check if page is currently a redirect
		if ( result.query.pages[window.config.mw.wgArticleId].redirect ) {
			window.API.abort();
			alert("Error: " + window.config.mw.wgPageName + " is a redirect");
			return;
		}

		// query to look for first non-redirect revision
		return window.API.get( {
			action: "query",
			pageids: window.config.mw.wgArticleId,
			prop: "revisions",
			rvprop: ["user", "content", "timestamp", "comment", "flags", "tags"],
			rvlimit: "max",
			rvdir: "newer"
		} )
		.then( processMaxRvAuthorQuery )
		.catch( function( c, r ) {
			if ( r.textStatus === "abort" ) { return; }

			window.API.abort();
			const retry = confirm("Could not retrieve page author:\n"+makeErrorMsg(c, r)+"\n\nTry again?");
			if ( retry ) {
				screen0();
			} else {
				$("#M2D-modal").remove();
			}
		} );
	};

	//Get contributor Data
	var getContributorData = function() {
		setTaskStatus( 0, "started" );
		return window.API.get( {
			action: "query",
			pageids: window.config.mw.wgArticleId,
			prop: ["revisions", "info"],
			rvprop: "content",
			rvlimit: 1,
			rvdir: "newer"
		} )
		.then( processAuthorQuery )
		.then( function() {
			setTaskStatus( 0, "done" );
			return Promise.resolve();
		})
		.catch( function( c, r ) {
			if ( r.textStatus === "abort" ) { return; }

			window.API.abort();
			const retry = confirm("Could not retrieve page author:\n"+makeErrorMsg(c, r)+"\n\nTry again?");
			if ( retry ) {
				screen0();
			} else {
				$("#M2D-modal").remove();
			}
			return Promise.reject( "getContributorData failed!" );
		} );
	};

	/* ---------- Current wikitext ----------------------------------------- */
	var getCurrentWikiText = function() {
		setTaskStatus( 1, "started" );
		window.API.get( {
			action: "query",
			pageids: window.config.mw.wgArticleId,
			prop: "revisions",
			rvprop: "content"
		} )
		.then( function(result) {
			window.config.pagedata.oldwikitext = result.query.pages[window.config.mw.wgArticleId].revisions[0]["*"];
			setTaskStatus( 1, "done" );
			return Promise.resolve();
		} )
		.catch( function( c, r ) {
			if ( r.textStatus === "abort" ) { return; }

			window.API.abort();
			const retry = confirm("Could not retrieve page wikitext:\n" +
				makeErrorMsg(c, r) + "\n\nTry again?"
				);
			if ( retry ) {
				screen0();
			} else {
				$("#M2D-modal").remove();
			}
			return Promise.reject( "getCurrentWikiText failed!" );
		} );
	};

	//TODO(?): also get proposed Draft: page (to check if it is empty or not)

	/* ---------- Redirects ------------------------------------------------ */
	var redirectTitles = [];

	var processRedirectsQuery = function(result) {
		if ( !result.query || !result.query.pages ) {
			// No results
			window.config.pagedata.redirects = false;
			return Promise.resolve();
		}

		// Gather redirect titles into array
		$.each(result.query.pages, function(_id, info) {
			redirectTitles.push(info.title);
		});

		// Continue query if needed
		if ( result.continue ) {
			doRedirectsQuery( result.continue );
			return Promise.resolve();
		}

		// Check if redirects were found
		if ( redirectTitles.length === 0 ) {
			window.config.pagedata.redirects = false;
			return Promise.resolve();
		}

		// Set redirects
		window.config.pagedata.redirects = redirectTitles;
		return Promise.resolve();
	};


	var doRedirectsQuery = function(extraQueryParam = null) {
		setTaskStatus( 2, "started" );

		const redirectsQuery = {
			action: "query",
			pageids: window.config.mw.wgArticleId,
			generator: "redirects",
			grdlimit: 500
		};

		return window.API.get( $.extend( redirectsQuery, extraQueryParam ) )
		.then( processRedirectsQuery )
		.then( function() {
			setTaskStatus( 2, "done" );
			return Promise.resolve();
		})
		.catch( function( c, r ) {
			if ( r.textStatus === "abort" ) { return; }

			window.API.abort();
			const retry = confirm("Could not retrieve redirects:\n" + makeErrorMsg(c, r) +
				"\n\nTry again? (or Cancel to skip)");
			if ( retry ) {
				screen0();
			} else {
				window.config.pagedata.redirects = false;
				setTaskStatus( 2, "skipped" );
				return Promise.resolve();
			}
		} );
	};
	
	const promises = [];
	promises.push( getContributorData() );
	promises.push( getCurrentWikiText() );
	promises.push( doRedirectsQuery() );

	Promise.allSettled( promises )
		.then( showContributorScreen )
		.catch( console.log.bind( console ) );
};

//Sets the status of tasks in the progress screen
var setTaskStatus = function( taskNumber, status, extraText ) {
	var timeTaken;
	if( taskNumber < 0 || taskNumber > 6 ) {
		//Invalid tasknumber
		return;
	}

	switch ( status ) {
		case "started":
			$( "#M2D-task" + taskNumber ).css( { "color": "#00F", "font-weight": "bold" } );
			$( "#M2D-status" + taskNumber ).text( "In process" );
			//Set the start time
			window.config.processTimer[ taskNumber ] = new Date();
			break;
		case "done":
			$( "#M2D-task" + taskNumber ).css( { "color": "#000", "font-weight": "" } );
			$( "#M2D-status" + taskNumber ).html( "&check;" );
			$( "#M2D-status" + taskNumber ).css( "color", "green" );
			timeTaken = (new Date()) - window.config.processTimer[ taskNumber ];
			break;
		case "skipped":
			$( "#M2D-task" + taskNumber ).css( { "color": "#F00", "font-weight": "" } );
			$( "#M2D-status" + taskNumber ).text( "Skipped!" );
			break;
	}

	if ( extraText !== undefined && extraText !== "" ) {
		$( "#M2D-status" + taskNumber ).append( " (" + extraText + ")" );
	}
	if ( timeTaken !== undefined ) {
		$( "#M2D-status" + taskNumber ).append( 
			$('<span>').append( " (" + timeTaken + "ms)" )
				.css('color', 'rgba(0, 0, 0, 0.4)')
		);
	}
};

//Move page
var movePage = function() {
	setTaskStatus( 0, "started" );

	// First check the page hasn't been draftified in the meantime
	return window.API.get({
		action: "query",
		pageids: window.config.mw.wgArticleId,
		format: "json",
		formatversion: "2"
	}).then(function(response) {
		var page = response && response.query && response.query.pages &&
			response.query.pages[0];
		if (!page) {
			return $.Deferred().reject();
		} else if (page.missing) {
			return $.Deferred().reject("moveToDraft-pagemissing");
		} else if (page.ns === 118 /* Draft NS */) {
			return $.Deferred().reject("moveToDraft-alreadydraft");
		} else if (page.ns !== window.config.mw.wgNamespaceNumber) {
			return $.Deferred().reject("moveToDraft-movednamespace");
		}

		return window.API.postWithToken( "csrf", {
			action: "move",
			fromid: window.config.mw.wgArticleId,
			to: window.config.inputdata.newTitle,
			movetalk: 1,
			noredirect: 1,
			reason: window.config.inputdata.rationale + " " +
				window.config.draftReasonsShort,
			tags: window.config.changeTags
		} );
	})
	.then( function() {
		setTaskStatus( 0, "done" );
		return Promise.resolve();
	} )
	.catch( function( c, r ) {
		if ( r && r.textStatus === "abort" ) {
			return;
		} else if (c === "moveToDraft-pagemissing") {
			alert( "The page no longer appears to exists. It may have been deleted." );
			$( "#M2D-modal" ).remove();
			window.location.reload();
			return;
		} else if (c === "moveToDraft-alreadydraft") {
			alert( "Aborted: The page has already been moved to draftspace." );
			$( "#M2D-modal" ).remove();
			window.location.reload();
			return;
		}

		const retry = confirm( "Could not move page:\n"
			+ makeErrorMsg( c, r )
			+ "\n\nTry again ?" );
		if ( retry ) {
			return movePage();
		} else {
			showOptionsScreen(true);
			return Promise.reject( "Move failed!" );
		}
	} );
};

var tagRedirect = function() {
	setTaskStatus( 6, "started" );
	if (
		-1 < $.inArray("sysop", window.config.mw.wgUserGroups) ||
		-1 < $.inArray("extendedmover", window.config.mw.wgUserGroups)
	) {
		setTaskStatus( 6, "skipped" );
		return Promise.resolve();
	}

	return window.API.postWithToken( "csrf", {
		action: "edit",
		title: window.config.mw.wgPageName,
		prependtext: '{{Db-r2}}\n',
		summary: '[[WP:R2|R2]] speedy deletion request (article moved to draftspace)',
		tags: window.config.changeTags
	} )
	.then( function() {
		// This null edit is needed until https://phabricator.wikimedia.org/T321192 is fixed
		return window.API.postWithToken( 'csrf', {
			action: 'edit',
			title: window.config.mw.wgPageName,
			prependtext: '',
			summary: 'Null edit',
			tags: window.config.changeTags
		} );
	} )
	.then( function() {
		setTaskStatus( 6, "done" );
		return Promise.resolve();
	} )
	.catch( function( c, r ) {
		if ( r.textStatus === "abort" ) { return; }

		const retry = confirm( "Could not tag redirect for speedy deletion:\n"+
			makeErrorMsg(c, r) + "\n\nTry again ?" );
		if ( retry ) {
			return tagRedirect();
		} else {
			setTaskStatus( 6, "skipped" );
			return Promise.resolve();
		}
	} );
};

//Find which images are non-free
var getImageInfo = function() {
	setTaskStatus( 1, "started" );

	var processImageInfo = function( result ) {
		var nonFreeFiles = [];
		if ( result && result.query ) {
			$.each(result.query.pages, function( id, page ) {
				if ( id > 0 && page.categories ) {
					nonFreeFiles.push( page.title );
				}
			});
		}

		setTaskStatus( 1, "done", nonFreeFiles.length + " found" );
		return Promise.resolve( nonFreeFiles );
	};

	return window.API.get( {
		action: "query",
		pageids: window.config.mw.wgArticleId,
		generator: "images",
		gimlimit: "max",
		prop: "categories",
		cllimit: "max",
		clcategories: "Category:All non-free media",
	} )
	.then( function(result){
		return processImageInfo( result );
	} )
	.catch( function( c, r ) {
		if ( r.textStatus === "abort" ) {
			return Promise.resolve( [] );
		}

		const retry = confirm("Could not find if there are non-free files:\n"+ makeErrorMsg(c, r)+"\n\n[Okay] to try again, or [Cancel] to skip");
		if ( retry ) {
			return getImageInfo();
		} else {
			setTaskStatus( 1, "skipped" );
			return Promise.resolve( [] );
		}
	} );

};

//Comment out non-free files, turn categories into links, add afc draft template, list any redirects
var editWikitext = function( nonFreeFiles ) {
	setTaskStatus( 2, "started" );

	var redirectsList = ( !window.config.pagedata.redirects ) ? "" : "\n"+
		"<!-- Note: The following pages were redirects to [[" + window.config.mw.wgPageName +
		"]] before draftification:\n" +
		"*[[" + window.config.pagedata.redirects.join("]]\n*[[") + "]]\n-->\n";

	var wikitext = "{{subst:AFC draft|" + window.config.pagedata.author + "}}\n" +
		redirectsList +
		window.config.pagedata.oldwikitext
			.replace(/(\s*\{\{Drafts moved from mainspace(\|[^\}]+)?\}\})+/gm, "")
			.replace(/((\[\[:?\s*[Cc]ategory\s*:[^\]]*\]\]\s*)+)/gm,
			"{{Draft categories|\n$1\n}}\n") +
		"\n{{subst:Drafts moved from mainspace}}";

	// non-free files
	//  (derived from [[WP:XFDC]] - https://en.wikipedia.org/wiki/User:Evad37/XFDcloser.js )
	if ( nonFreeFiles.length > 0 ) {
		// Start building regex strings
		var normalRegexStr = "(";
		var galleryRegexStr = "(";
		var freeRegexStr = "(";
		for (let i=0; i<nonFreeFiles.length; i++ ) {
			// Take off namespace prefix
			const filename = nonFreeFiles[i].replace(/^.*?:/, "");
			// For regex matching: first character can be either upper or lower case, special
			// characters need to be escaped, spaces can be either spaces or underscores
			const filenameRegexStr = "[" + mw.util.escapeRegExp(filename.slice(0, 1).toUpperCase()) +
			mw.util.escapeRegExp(filename.slice(0, 1).toLowerCase()) + "]" +
			mw.util.escapeRegExp(filename.slice(1)).replace(/ /g, "[ _]");
			// Add to regex strings
			normalRegexStr += "\\[\\[\\s*(?:[Ii]mage|[Ff]ile)\\s*:\\s*" + filenameRegexStr +
			"\\s*\\|?.*?(?:(?:\\[\\[.*?\\]\\]).*?)*\\]\\]";
			galleryRegexStr += "^\\s*(?:[Ii]mage|[Ff]ile):\\s*" + filenameRegexStr + ".*?$";
			freeRegexStr += "\\|\\s*(?:[\\w\\s]+\\=)?\\s*(?:(?:[Ii]mage|[Ff]ile):\\s*)?" +
			filenameRegexStr;

			if ( i+1 === nonFreeFiles.length ) {
				normalRegexStr += ")(?![^<]*?-->)";
				galleryRegexStr += ")(?![^<]*?-->)";
				freeRegexStr += ")(?![^<]*?-->)";
			} else {
				normalRegexStr += "|";
				galleryRegexStr += "|";
				freeRegexStr += "|";
			}
		}

		// Check for normal file usage, i.e. [[File:Foobar.png|...]]
		var normalRegex = new RegExp( normalRegexStr, "g");
		wikitext = wikitext.replace(normalRegex, "<!-- Commented out: $1 -->");

		// Check for gallery usage, i.e. instances that must start on a new line, eventually
		// preceded with some space, and must include File: or Image: prefix
		var galleryRegex = new RegExp( galleryRegexStr, "mg" );
		wikitext = wikitext.replace(galleryRegex, "<!-- Commented out: $1 -->");

		// Check for free usages, for example as template argument, might have the File: or Image:
		// prefix excluded, but must be preceeded by an |
		var freeRegex = new RegExp( freeRegexStr, "mg" );
		wikitext = wikitext.replace(freeRegex, "<!-- Commented out: $1 -->");
	}

	return window.API.postWithToken( "csrf", {
		action: "edit",
		pageid: window.config.mw.wgArticleId,
		text: wikitext,
		summary: window.config.wikitext.editsummary,
		tags: window.config.changeTags
	} )
	.then( function(){
		setTaskStatus( 2, "done" );
		return Promise.resolve();
	} )
	.catch( function( c, r ) {
		if ( r.textStatus === "abort" ) {
			return Promise.resolve();
		}

		const retry = confirm("Could not edit draft artice:\n"+ makeErrorMsg(c, r)+"\n\n[Okay] to try again, or [Cancel] to skip");
		if ( retry ) {
			editWikitext(nonFreeFiles);
		} else {
			setTaskStatus( 2, "skipped" );
			return Promise.resolve();
		}
	} );
};

var notifyContributors = function() {
	if(window.config.pagedata.notifyList.length === 0 ||
			!window.config.inputdata.notifyEnable ) {
		setTaskStatus( 3, "skipped" );
		return Promise.resolve();
	}
	setTaskStatus( 3, "started" );

	const promises = [];
	for ( let i in window.config.pagedata.notifyList ) {
		promises.push( notifyContributor( window.config.pagedata.notifyList[i] ) );
	}

	return Promise.allSettled( promises )
		.then( function () {
			setTaskStatus( 3, "done" );
			return Promise.resolve();
		} );
};

var notifyContributor = function( contributor ) {
	return window.API.postWithToken( "csrf", {
		action: "discussiontoolsedit",
		page: "User talk:" + contributor,
		paction: "addtopic",
		sectiontitle: window.config.inputdata.notifyMsgHead,
		wikitext: window.config.inputdata.notifyMsg,
		tags: window.config.changeTags
	} )
	.catch( function( c, r ) {
		if ( r.textStatus === "abort" ) {
			return Promise.resolve();
		}

		const retry = confirm("Could not edit author talk page:\n"+ makeErrorMsg(c, r)+"\n\n[Okay] to try again, or [Cancel] to skip");
		if ( retry ) {
			return notifyContributor( contributor );
		}
		return Promise.resolve();
	} );
};

var updateTalk = function() {
	setTaskStatus( 4, "started" );

	//if page exists, do a regex search/repace for class/importances parameters
	var processTalkWikitext = function(result) {
		var talkPageId = result.query.pageids[0];
		if ( talkPageId < 0 ) {
			setTaskStatus( 4, "done", "talk page does not exist" );
			return Promise.resolve();
		}
		var oldTalkWikitext = result.query.pages[talkPageId].revisions[0]["*"];
		var newTalkWikitext = oldTalkWikitext.replace(/(\|\s*(?:class|importance)\s*=\s*)[^|}]*(?=[^}]*\}\})/g, "$1");
		if ( newTalkWikitext === oldTalkWikitext ) {
			setTaskStatus( 4, "done", "no changes needed" );
			return Promise.resolve();
		}

		return window.API.postWithToken( "csrf", {
			action: "edit",
			pageid: talkPageId,
			section: "0",
			text: newTalkWikitext,
			summary: 'Remove class/importance from project banners',
			tags: window.config.changeTags
		} )
		.then( function(){
			setTaskStatus( 4, "done" );
			return Promise.resolve();
		} )
		.catch( function( c, r ) {
			if ( r.textStatus === "abort" ) {
				return Promise.resolve();
			}

			const retry = confirm("Could not edit draft's talk page:\n"
				+ makeErrorMsg(c, r)
				+ "\n\n[Okay] to try again, or [Cancel] to skip");
			if ( retry ) {
				return updateTalk();
			} else {
				setTaskStatus( 4, "skipped" );
				return Promise.resolve();
			}
		} );
	};

	//get talk page wikitext (section 0)
	return window.API.get( {
		action: "query",
		titles: window.config.inputdata.newTitle.replace("Draft:", "Draft talk:"),
		prop: "revisions",
		rvprop: "content",
		rvsection: "0",
		indexpageids: 1
	} )
	.then( processTalkWikitext )
	.catch( function( c, r ) {
		if ( r.textStatus === "abort" ) {
			return Promise.resolve();
		}

		const retry = confirm("Could not find draft's talk page:\n"
			+ makeErrorMsg(c, r)
			+ "\n\n[Okay] to try again, or [Cancel] to skip");
		if ( retry ) {
			return updateTalk();
		} else {
			setTaskStatus( 4, "skipped" );
			return Promise.resolve();
		}
	} );
};

var logDraftification = function() {
	if (window.config.doNotLog) {
		setTaskStatus( 5, "skipped" );
		return Promise.resolve();
	}

	setTaskStatus( 5, "started" );
	var logpage = "User:" + window.config.mw.wgUserName + "/Draftify_log";
	var monthNames = window.config.mw.wgMonthNames.slice(1);
	var now = new Date();
	var heading = monthNames[now.getUTCMonth()] + " " +
			now.getUTCFullYear();

	// Get a list of sections of a page
	var getSections = function( pageTitle ) {
		return window.API.get( {
			action: "parse",
			page: pageTitle,
			prop: "sections"
		});
	};

	// Check if page exists
	var doesPageExist = function( pageTitle ) {
		return window.API.get( {
			action: "query",
			titles: pageTitle,
			prop: "revisions",
			rvlimit: 1,
			indexpageids: 1
		} )
		.then ( function( result ) {
			var id = result.query.pageids[0];
			return Promise.resolve( id >= 0 );
		} );
	};

	// Search within the result for the current month's heading
	var searchSectionsForText = function( result, heading ) {
		var sections = result.parse.sections;

		if ( sections.length > 18 ) {
			console.log( "Your draftify log file is large. " +
				"Consider archiving old logs by year." );
		}

		for ( let i = sections.length - 1; i >= 0; i-- ) {
			if ( sections[i].line === heading ) {
				// Found the current month's section
				return sections[i].index;
			}
		}
		return -1;
	};
	
	var queryParams;
	return doesPageExist( logpage )
	.then ( function( result ) {
		var editSummary = 'Logging [[' + window.config.inputdata.newTitle + ']] (' +
							window.config.draftReasonsShort + ')';
		if ( result ) {
			return getSections( logpage )
			.then ( function( result ) {
				const sectionNumber = searchSectionsForText( result, heading );
				if ( sectionNumber === -1 ) {
					// Section for current month needs to be created
					queryParams = {
						text: window.config.inputdata.logMsg,
						section: "new",
						sectiontitle: heading,
						summary: editSummary + ' (new month)',
					};
				} else {
					// Append log line to current month's section
					queryParams = {
						appendtext: "\n" + window.config.inputdata.logMsg,
						section: sectionNumber,
						summary: editSummary,
					};
				}
			} );
		} else {
			// Draftify_log page does not exist
			var createlog = confirm("Log draftification (at " +  logpage + ") ?");
			if ( !createlog ) {
				setTaskStatus( 5, "skipped" );
				return Promise.resolve();
			}
			const logpageWikitext = "This is a log of pages moved to draftspace using the [[User:Tanbiruzzaman/খসড়ায় স্থানান্তর|MoveToDraft]] script."
					+ "\n\n==" + heading + "==\n" + window.config.inputdata.logMsg;

			queryParams = {
				text: logpageWikitext,
				summary: editSummary + ' (first draftification)',
				nocreate: false
			};
			return Promise.resolve();
		}
	} )
	.then( () => {
		queryParams = $.extend({
				action: "edit",
				title: logpage,
				tags: window.config.changeTags
			},
			queryParams );
		return window.API.postWithToken( "csrf", queryParams );
	} )
	.then( function(){
		setTaskStatus( 5, "done" );
		return Promise.resolve();
	} )
	.catch( function( c, r ) {
		if ( r.textStatus === "abort" ) {
			return Promise.resolve();
		}

		const retry = confirm("Could not edit log page:\n" + makeErrorMsg(c, r) + "\n\n[Okay] to try again, or [Cancel] to skip");
		if ( retry ) {
			return logDraftification();
		} else {
			setTaskStatus( 5, "skipped" );
			return Promise.resolve();
		}
	} );
};

var setupHeader = function(subText) {
	$("#M2D-interface-header").append(
		$("<button>").text("X")
			.attr('title', 'Close')
			.css('float', 'right')
			.click(function(){
				$("#M2D-modal").remove();
			}),
		$('<div>')
			.append(
				makeLink(window.config.script.location, 'Move To Draft'),
				' <small>(v' + window.config.script.version + ')</small>',
				subText
			)
			.css({"font-weight": "bold", "font-size": "large",
					"margin": "0.25em", "text-align": "center"})
	);
};


var notifyChange = function() {
	$('#M2D-option-message-head').prop('disabled', !this.checked);
	for (let key in window.config.draftReasons) {
		$("#M2D-option-reasons-checkbox-"+key).prop("disabled", !this.checked);
	}
	$("#M2D-option-reasons-checkbox-other").prop("disabled", !this.checked);
	$("#M2D-reason-other").prop("disabled", !this.checked);

	if (this.checked) {
		$('#M2D-option-message-preview-outer').show();
	} else {
		$('#M2D-option-message-preview-outer').hide();
	}
};

// --- Interface screens ---
//0) Initial screen
var screen0 = function() {
	$("#M2D-interface-header, #M2D-interface-content, #M2D-interface-footer").empty();
	setupHeader("...");

	$("#M2D-interface-content").text("Gathering data...");
	$("#M2D-interface-content").append(
		getProgressTasks(
			[	'Get contributor data',
				'Get current wikiText',
				'Get redirects',
			] )
	);

	$("#M2D-interface-footer").append(
		$('<button>').attr('id', 'M2D-abort').text('Abort uncompleted tasks'),
		$('<span>').attr('id', 'M2D-finished').hide().append(
			'Finished!',
			$('<button>').attr('id', 'M2D-close').text('Close')
				.css('margin-left', '0.5em')
		)
	);

	$("#M2D-close").click( function(){
		$("#M2D-modal").remove();
		window.location.reload();
	} );
	grabPageData();
};

//1) Contributors
var showContributorScreen = function() {

	var numContributors = Object.keys(window.config.pagedata.contributors).length;
	if( numContributors === 0) {
		console.log("No notifiable contributors");

		//show next screen
		showOptionsScreen();
		return;
	} else if( numContributors === 1) {
		console.log("Only one contributor. Moving to next screen");

		const singleContributor = Object.keys(window.config.pagedata.contributors)[0];
		window.config.pagedata.notifyList = [ singleContributor ];

		//show next screen
		showOptionsScreen();
		return;
	}

	$("#M2D-interface-header, #M2D-interface-content, #M2D-interface-footer").empty();
	setupHeader(": contributors");

	$("#M2D-interface-content").append(
			$('<div>')
				.css('padding-bottom', '1em')
				.append("View ",
					$('<a>').attr({
							'href': '?action=history',
							'target':'_blank'
						}).text("Page History")
			),
			$('<div>')
				.css('padding-bottom', '0.5em')
				.text("Choose contributors to send notifications to: ")
		)
		.css('margin', '1em');

	for ( const [key, edits] of Object.entries( window.config.pagedata.contributors ) ) {
		var contributor = `${key}`;
		var contributorForDivId = contributor.replace(/\W/g, "_");

		$('#M2D-interface-content').append(
			$('<div>').append(
				$('<input>').attr({'id':'M2D-option-authors-checkbox-'+contributorForDivId,
						'name':'contributors', 'type':'checkbox', 'value':contributor}),
				$('<label>').attr({'id':'M2D-option-authors-label-'+contributorForDivId,
						'for':'M2D-option-authors-checkbox-'+contributorForDivId})
					.text(`${key}`+" - "+`${edits}`+" edit" + ((`${edits}`!==`1`)?"s":"") )
					.css({'margin-left':'0.5em'}),
				$('<br/>')
			)
		);

		if( contributor === window.config.pagedata.author ) {
			$('#M2D-option-authors-label-'+contributorForDivId).append(' (Page Creator)');
		}

		if( window.config.pagedata.notifyList.includes( contributor ) ) {
			$('#M2D-option-authors-checkbox-'+contributorForDivId).prop('checked', true);
		}
	}

	$("#M2D-interface-footer").append(
		$('<button>').attr('id', 'M2D-next').text('Next'),
		$('<button>').attr('id', 'M2D-cancel').css('margin-left','3em').text('Cancel')
	);

	$("#M2D-cancel").click(function(){
		$("#M2D-modal").remove();
	});

	$("#M2D-next").click(function(){
		var markedCheckboxes = document.querySelectorAll('input[name="contributors"]:checked');
		window.config.pagedata.notifyList = [];
		for (let checkbox of markedCheckboxes ) {
			window.config.pagedata.notifyList.push( checkbox.value );
		}

		//show next screen
		showOptionsScreen();
	});
};


//2) User inputs
/**
 * 
 * @param {boolean} restoreValues Restore previously set values
 */
var showOptionsScreen = function(restoreValues) {
	$("#M2D-interface-header, #M2D-interface-content, #M2D-interface-footer").empty();
	setupHeader(": options");

	$("#M2D-interface-content").append(
		$('<div>').css('margin-bottom','0.5em').append(
			$('<div>')
			.attr({'id': 'M2D-warning'})
			.css({
				display: 'block',
				color: 'darkblue',
				'font-weight': 'bold',
			}).append(
				'Please ensure draftifying is appropriate per ',
				makeLink("WP:DRAFTIFY")
			),
			$('<div>')
					.css({ display: 'block', color: 'blue', 'font-style': 'italic', margin: '0.5em auto'})
					.append(
						'Add maintenance tags first if you wish, then check any or all boxes that match the issues with this article, then submit the message.'
					),
			$('<label>').attr('for','M2D-option-newtitle').append(
				'Move to ',
				$('<b>').text('Draft:')
			),
			$('<input>').attr({'type':'text', 'name':'M2D-option-newtitle', 'id':'M2D-option-newtitle'})
				.css({'margin-left': '0.25em',
						'width': '25em'})
		),

		$('<div>').css('margin-bottom','0.5em').append(
			$('<label>').attr({'for':'M2D-option-movelog', 'id':'M2D-option-movelog-label'})
				.html('<b>Reason for move (log summary):</b>'),
			$('<input>').attr({'type':'text', 'name':'M2D-option-movelog', 'id':'M2D-option-movelog'})
				.css({'width':'44em', 'margin-left':'0.25em',
					'background-color': '#e8e8e8',
					'border': '1px solid #808080'})
		),

		$('<div>').attr({'id':'M2D-option-author'}).append(
			$('<input>').attr({'type':'checkbox', 'id':'M2D-option-message-enable'})
				.css('margin-right', '0.25em')
				.prop('checked', true),
			$('<label>').attr({'for':'M2D-option-message-enable'}).append(
				$('<b>').text( 'Notify contributor/s: '))
		).css('margin-bottom', '0.5em'),

		$('<div>').css('margin-bottom','0.5em').append(
			$('<label>').attr({'for':'M2D-option-message-head', 'id':'M2D-option-message-head-label'})
				.css({'margin-top':'0.5em'}).append(
					$('<b>').text('Notification heading:')
				),
			$('<input>').attr({'id':'M2D-option-message-head', 'type':'text'})
				.css({'width':'50em', 'margin-left':'0.25em',
					'background-color': '#e8e8e8',
					'border': '1px solid #808080'})
		),

		$('<div>').css('margin-bottom','0.5em').append(
			$('<label>').attr({'id':'M2D-option-reasons-label'})
				.css({'margin-top':'0.5em'}).append(
					$('<b>').text('Reason/s:')
				),
			$('<div>').attr({'id':'M2D-option-reasons'})
				.css({'width':'99%', 'columns': '2'}),

			$('<div>').attr({'id':'M2D-option-reasons-other'})
				.css({'width':'99%', 'margin-bottom':'0.5em'})
		),

		$('<div>').attr({'id':'M2D-option-message-preview-outer'}).append(
			$('<label>').attr({'for':'M2D-option-message-preview', 'id':'M2D-option-message-label'})
				.css({'display':'block', 'font-weight':'bold'})
				.text('Notification preview:'),
			$('<div>').attr({'id':'M2D-option-message-preview'})
				.css({'width': '98%', 'background': '#fff',
					'border': '1px solid #0002', 'padding': '0 0.5em'})
		)
	);

	for (let key in window.config.draftReasons) {
		$("#M2D-option-reasons").append(
			$('<div>').append(
				$('<input>').attr({'id':'M2D-option-reasons-checkbox-'+key, 'type':'checkbox', 'value':key})
					.change( reasonChange ),
				$('<label>').attr({'id':'M2D-option-reasons-label-'+key, 'for':'M2D-option-reasons-checkbox-'+key})
					.text(window.config.draftReasons[key].long.charAt(0).toUpperCase() + window.config.draftReasons[key].long.slice(1))
					.css({'margin-left':'0.5em'}),
				$('<br/>')
			)
		);
	}

	//text box and label
	$("#M2D-option-reasons-other").append(
		$('<input>').attr({'id':'M2D-option-reasons-checkbox-other', 'type':'checkbox', 'value':'other'})
			.css({'float':'left'}),
		$('<label>').attr({'for':'M2D-reason-other', 'id':'M2D-reason-other-label'})
			.css({'float':'left', 'margin':'auto 0.5em'}).text('Other/additional reasons:'),
		$('<textarea>').attr({'id':'M2D-reason-other', 'rows':'1'})
			.css({'width':'75%'})
			.on("input keyup paste", reasonChange )
	);

	//Adding a warning, if needed
	setDraftifyWarning("#M2D-warning");

	//Setting one of the checkboxes as checked by default
	$('#M2D-option-reasons-checkbox-0').prop('checked', true);

	$('#M2D-option-movelog').val(window.config.wikitext.rationale);
	$('#M2D-option-newtitle').val(getPageText(window.config.mw.wgPageName)).change(function() {
		$('#M2D-option-message-head').val(
			$('#M2D-option-message-head').val().trim()
			.replace(/\[\[Draft:.*?\|/, "[[Draft:" + $('#M2D-option-newtitle').val().trim() + "|" )
		);
		window.config.notificationMessage = 
			window.config.notificationMessage.trim()
			.replace(/\[\[Draft:.*?\|/, "[[Draft:" + $('#M2D-option-newtitle').val().trim() + "|" );
	});

	$('#M2D-option-message-enable').change(notifyChange);

	if (window.config.pagedata.notifyList.length === 0) {
		$("#M2D-option-author").append( " None ");
		$("#M2D-option-message-enable").prop("checked", false);
		$("#M2D-option-message-enable").prop("disabled", true);
		notifyChange();
	} else {
		$("#M2D-option-author").append(
			" " + window.config.pagedata.notifyList.toString().replace(/,/g, ", ") + " "
		);
	}

	if( Object.keys(window.config.pagedata.contributors).length > 1) {
		$("#M2D-option-author").append(
			$('<a>').text("Change")
				.click(showContributorScreen)
		);
	}

	$('#M2D-option-message-head').val(
		window.config.wikitext.notificationHeading.replace(/\$1/g, getPageText(window.config.mw.wgPageName))
	);
	reasonChange();

	$("#M2D-interface-footer").append(
		$('<button>').attr('id', 'M2D-next').text('Continue'),
		$('<button>').attr('id', 'M2D-cancel').css('margin-left','3em').text('Cancel')
	);

	$("#M2D-cancel").click(function(){
		$("#M2D-modal").remove();
	});

	if (restoreValues) {
		$( '#M2D-option-movelog' ).val( window.config.inputdata.rationale );
		$( '#M2D-option-newtitle' ).val( window.config.inputdata.newTitle.replace( "Draft:", "" ) );
		$( '#M2D-option-author' ).val( window.config.inputdata.authorName );
		$( '#M2D-option-message-enable' ).prop( 'checked', window.config.inputdata.notifyEnable );
		$( '#M2D-option-message-head' ).val( window.config.inputdata.notifyMsgHead );
		window.config.notificationMessage = window.config.inputdata.notifyMsg;
	}


	$("#M2D-next").click(function(){
		//Gather inputs
		window.config.inputdata = {
			rationale:		$('#M2D-option-movelog').val().trim(),
			newTitle:		"Draft:" + $('#M2D-option-newtitle').val().trim(),
			authorName:		$('#M2D-option-author').val().trim(),
			notifyEnable:	$('#M2D-option-message-enable').prop('checked'),
			notifyMsgHead:	$('#M2D-option-message-head').val().trim(),
			notifyMsg:		window.config.notificationMessage.trim()
		};
		window.config.inputdata.logMsg = window.config.wikitext.logMsg
			.replace(/\$1/g, getPageText(window.config.mw.wgPageName))
			.replace(/\$2/g, window.config.inputdata.newTitle)
			+ ' (' + window.config.draftReasonsShort + ')';

		//Verify inputs
		var errors=[];
		if ( window.config.inputdata.newTitle.length === 0 ) {
			errors.push("Invalid draft title");
		}

		if ( window.config.inputdata.rationale.length === 0 ) {
			errors.push("Move log reason is empty");
		}
		if ( window.config.inputdata.notifyEnable ) {
			if ( window.config.inputdata.notifyMsgHead.length === 0 ) {
				errors.push("Notification heading is empty");
			}
			if ( window.config.inputdata.notifyMsg.length === 0 ) {
				errors.push("Notification message is empty");
			}
		}
		if ( errors.length >= 1 ) {
			alert("Error:\n\n" + errors.join(";\n"));
			return;
		}

		//start process off
		showProgressScreen();
		startTheProcess();
	});
};

var startTheProcess = function() {
	movePage()
		.then( getImageInfo )
		.then( function ( nonFreeFiles ) {
			const promises = [];
			promises.push( editWikitext( nonFreeFiles ) );
			promises.push( notifyContributors() );
			promises.push( updateTalk() );
			promises.push( logDraftification() );
			promises.push( tagRedirect() );
			return Promise.allSettled( promises );
		} )
		.then( function() {
				$("#M2D-finished, #M2D-abort").toggle();
				setTimeout( function () {
					window.location.reload();
				}, 2000 );
			}
		)
		.catch( console.log.bind( console ) );
};

//Checks if draftification is appropriate and warns the user accordingly
var setDraftifyWarning = function( divId ) {
	var now = new Date();

	var articleCreatedOn = new Date( window.config.pagedata.creationTimestamp );
	var articleAgeInDays = Math.round( ( now - articleCreatedOn ) / ( 1000 * 60 * 60 * 24 ) );

	var articleEditedOn = new Date( window.config.pagedata.lastEditTimestamp );
	var lastEditAgeInMinutes = Math.round( ( now - articleEditedOn ) / ( 1000 * 60 ) );
	var extraText = '';

	if ( window.config.pagedata.previousDraftification ) {
		extraText = ", since this article has been previously draftified.";
	} else if ( articleAgeInDays > window.config.maximumAgeInDays ) {
		extraText = ", since this article is more than " + 
			window.config.maximumAgeInDays +
			" days old.";
	} else if ( lastEditAgeInMinutes < window.config.minimumAgeInMinutes ) {
		extraText = ", since this article was edited less than " +
			window.config.minimumAgeInMinutes +
			" minutes ago.";
	} else {
		return;
	}

	$(divId).text( '' )
		.css( { "color": "red", "font-size": "large" } );
	$(divId).append(
		"Draftifying isn't appropriate per ",
		makeLink( "WP:DRAFTIFY" ),
		extraText
	);
};

//Called when the state of any of the reason checkboxes changes
var reasonChange = function() {
	var reasons = [];
	var reasonsShort = [];
	for (let key in window.config.draftReasons) {
		if ( $("#M2D-option-reasons-checkbox-"+key).prop("checked") === true ) {
			reasons.push(key);
			reasonsShort.push(window.config.draftReasons[key].short);
		}
	}

	//Cloning the array
	var draftifyReasons = JSON.parse(JSON.stringify(window.config.draftReasons));

	//Other reasons text box
	var otherText = $("#M2D-reason-other").val();
	if ( otherText !== '' ) {
		draftifyReasons.other = { 'long': otherText };
		reasons.push('other');
		reasonsShort.push('custom reason');
		$('#M2D-option-reasons-checkbox-other').prop("checked", true);
	} else {
		$('#M2D-option-reasons-checkbox-other').prop("checked", false);
	}

	var reasonText = "";
	if (reasons.length === 0) {
		$('#M2D-next').prop('disabled', true);
	} else {
		$('#M2D-next').prop('disabled', false);
		reasonText = " because '''" + draftifyReasons[reasons[0]].long + "'''";
		if (reasons.length > 1) {
			for (let i = 1; i < (reasons.length - 1); i++) {
				reasonText += ", '''" + draftifyReasons[reasons[i]].long + "'''";
			}
			reasonText += " and '''" + draftifyReasons[reasons[reasons.length-1]].long + "'''";
		}
	}

	if (reasonsShort.length === 0) {
		reasonsShort.push("unspecified");
	}

	window.config.draftReasonsShort = "Reason/s: " + reasonsShort.join(', ');
	window.config.notificationMessage = window.config.wikitext.notificationTemplate
			.replace(/\$1/g, getPageText($('#M2D-option-newtitle').val()))
			.replace(/\$3/g, reasonText);

	//Converting the message text into a preview
	window.API.get( {
		action: 'parse',
		text: "==" + $('#M2D-option-message-head').val() + "==\n" + window.config.notificationMessage,
		disableeditsection: true,
		contentmodel: 'wikitext'
	} )
	.then( function(result) {
		$('#M2D-option-message-preview').html( result.parse.text['*'] );
	} );
};

// Progress tasks
var getProgressTasks = function( tasks ) {
	var output = $('<ul>').attr('id', 'M2D-tasks').css("color", "#888");
	//Resetting to empty array
	window.config.processTimer = [];
	for ( const [ index, taskText ] of Object.entries( tasks ) ) {
		output.append(
			$('<li>').attr('id', 'M2D-task' + index ).append(
				taskText,
				$('<span>').attr('id','M2D-status' + index )
					.css( "margin", "0.75em" )
			)
		);
	}
	return output;
};

//3) Progress indicators
var showProgressScreen = function() {
	$("#M2D-interface-header, #M2D-interface-content, #M2D-interface-footer").empty();
	setupHeader(": In progress...");

	$("#M2D-interface-content").append(
		getProgressTasks(
			[	'Moving page',
				'Searching for non-free images',
				'Editing page wikitext',
				'Notifying contributor/s',
				'Updating talk page banners',
				'Logging',
				'Marking redirect for deletion'
			] )
	);

	$("#M2D-interface-footer").append(
		$('<button>').attr('id', 'M2D-abort').text('Abort uncompleted tasks'),
		$('<span>').attr('id', 'M2D-finished').hide().append(
			'Finished!',
			$('<button>').attr('id', 'M2D-close').text('Close')
				.css('margin-left', '0.5em')
		)
	);

	$("#M2D-close").click( function(){
		$("#M2D-modal").remove();
		window.location.reload();
	} );
	$("M2D-abort").click( function(){
		window.API.abort();
		$("#M2D-modal").remove();
		window.location.reload();
	} );
};

// --- Add link to 'More' menu (or user-specified portlet) which starts everything ---
mw.util.addPortletLink( ( window.m2d_portlet||'p-cactions' ), '#', 'Move to draft', 'ca-m2d', null, null, "#ca-move");
$( '#ca-m2d' ).on( 'click', function( e ) {
	e.preventDefault();
	// Add interface shell
	$('body').prepend('<div id="M2D-modal">'+
		'<div id="M2D-interface">'+
			'<div id="M2D-interface-header"></div>'+
			'<hr>'+
			'<div id="M2D-interface-content"></div>'+
			'<hr>'+
			'<div id="M2D-interface-footer"></div>'+
		'</div>'+
	'</div>');

	// Interface styling
	$("#M2D-modal").css({
		"position": "fixed",
		"z-index": "1001",
		"left": "0",
		"top": "0",
		"width": "100%",
		"height": "100%",
		"overflow": "auto",
		"background-color": "rgba(0,0,0,0.4)"
	});
	$("#M2D-interface").css({
		"background-color": "#f0f0f0",
		"margin": "7% auto",
		"padding": "2px 20px",
		"border": "1px solid #888",
		"width": "80%",
		"max-width": "60em",
		"font-size": "90%"
	});
	$("#M2D-interface-content").css("min-height", "7em");
	$("#M2D-interface-footer").css("min-height", "2em");

	// Initial interface content
	screen0();
});
// </nowiki>