User:HouseBlaster/MoveToDraft.js

Source: Wikipedia, the free encyclopedia.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
/******************************************************************************
 MoveToDraft
-------------
Version 2.5.7.1
-------------
A script to move articles to draft space, including cleanup and author notification.
- Moves page to draftspace
- Checks if any files used are non-free
- Checks if any redirects pointed to the page
- Comments out non-free files, turn categories into links, add afc draft template, add redirects
- Adds notification message on author talk page
- Updates talk page banners
- Logs draftification in user subpage
- HouseBlaster's personal version: allows editing of the textbox, removes some headers. Otherwise, it functions the exact same way as [[User:MPGuy2824/MoveToDraft.js]].
 derived from https://en.wikipedia.org/wiki/User:MPGuy2824/MoveToDraft.js
******************************************************************************/
/* jshint laxbreak: true, undef: true, maxerr:999, esversion:6 */
/* globals console, window, document, $, mw, OO */
// <nowiki>
$.when(
	// Resource loader modules
	mw.loader.using(['mediawiki.util', 'mediawiki.api', 'mediawiki.Title']),
	// Page ready
	$.ready
).then(function() {
/* ========== Config ======================================================= */
var config = {
	// MediaWiki configuration values
	mw: mw.config.get( [
		"wgArticleId",
		"wgCurRevisionId",
		"wgPageName",
		"wgUserGroups",
		"wgUserName",
		"wgMonthNames",
		"wgNamespaceNumber",
		"wgTitle"
	] ),
	// Script info
	script: {
		// For edit summaries
		location: "User:HouseBlaster/MoveToDraft.js",
		version: "2.5.7.1"
	}
};

/* ========== API ========================================================== */
var API = new mw.Api( {
	ajax: {
		headers: { 
			"Api-User-Agent": "MoveToDraft/" + config.script.version + 
				" ( https://en.wikipedia.org/wiki/User:MPGuy2824/MoveToDraft )"
		}
	}
} );

var moveToDraft = function() {

/* ========== Additional config ============================================ */
// Wikitext strings
config.wikitext = {
	"editsummary":	window.m2d_editsummary || window.m2d_rationale ||
			 "[[WP:AFC|AFC]] draft",
	"logMsg":		"#[[$1]] moved to [[$2]] at ~~~~~",
	"notification":	window.m2d_notification || "Thanks for creating [[Draft:$1|$1]]. Unfortunately, it is not ready for publishing$3.\nYour article is now a draft where you can improve it 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. ~~~~",
	"notification_heading": "[[Draft:$1|$1]] moved to draftspace",
	"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
config.changeTags = 'moveToDraft';
config.draftReasons = [
	"it has no sources",
	"it needs more sources to establish notability",
	"it has too many problems of language or grammar",
	"it is a poor translation",
	"it is promotional and reads like an advertisement",
	"you may have a possible Conflict of Interest"
];
config.minimumAgeInMinutes = 60;
config.maximumAgeInDays = 90;

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

// 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"
 */
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.
 */
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 patt_isRedirect = /^\s*#redirect/i;

	var checkedPageTriageStatus = false;

	// Function to check if all done
	var checkPageData = function() {
		if (
			config.pagedata.author != null &&
			config.pagedata.oldwikitext != null &&
			config.pagedata.redirects != null &&
			checkedPageTriageStatus
		) {
			//all done - go to next screen
			setTaskStatus(1, "done");
			showContributorScreen();
		}
	};

	/* ---------- 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[config.mw.wgArticleId].revisions;
		//blanking the contributor array
		config.pagedata.contributors = {};
		for ( let i = 0; i < revisions.length; i++ ) {
			if ( !patt_isRedirect.test(revisions[i]["*"]) ) {
				if (config.pagedata.author == undefined) {
					config.pagedata.author = revisions[i].user;
					config.pagedata.creationTimestamp = revisions[i].timestamp;
				}

				//Calculating contribs per editor
				if (config.pagedata.contributors[revisions[i].user] == undefined) {
					config.pagedata.contributors[revisions[i].user] = 1;
				} else {
					config.pagedata.contributors[revisions[i].user]++;
				}

				//Find if there is a comment which indicats prev draftification
				if( revisions[i].comment.search(/moved page \[\[.*\]\] to \[\[Draft:/) !== -1) {
					config.pagedata.previousDraftification = true;
				}
			}
		}
		// Check that we actually found an author (i.e. not all revisions were
		// redirects)
		if ( config.pagedata.author == null ) {
			API.abort();
			var retry = confirm("Could not retrieve page author:\n"+makeErrorMsg(c, r)+"\n\nTry again?");
			if ( retry ) {
				screen0();
			} else {
				$("#M2D-modal").remove();
			}
		}

		// Get the timestamp of the last edit
		config.pagedata.lastEditTimestamp = revisions[revisions.length - 1].timestamp;

		sortContributorsByEdits();

		checkPageData();
	};

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

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

		config.pagedata.contributors = {};
		for (let i in sortable) {
			if (i >= 5 ) {
				// only show the top 5 contributors
				break;
			}
			config.pagedata.contributors [sortable[i].c] = sortable[i].e;
		}
	};

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


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

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

	//Get author
	setTaskStatus(0, "started");
	API.get( {
		action: "query",
		pageids: config.mw.wgArticleId,
		prop: ["revisions", "info"],
		rvprop: "content",
		rvlimit: 1,
		rvdir: "newer"
	} )
	.done( processAuthorQuery )
	.fail( function(c,r) {
		if ( r.textStatus === "abort" ) { return; }

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

	setTaskStatus(0, "done");
	setTaskStatus(1, "started");

	/* ---------- Current wikitext ----------------------------------------- */

	API.get( {
		action: "query",
		pageids: config.mw.wgArticleId,
		prop: "revisions",
		rvprop: "content"
	} )
	.done( function(result) {
		config.pagedata.oldwikitext = result.query.pages[config.mw.wgArticleId].revisions[0]["*"];
		checkPageData();
	} )
	.fail( function(c,r) {
		if ( r.textStatus === "abort" ) { return; }

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

	//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
			config.pagedata.redirects = false;
			checkPageData();
			return;
		}
		// Gather redirect titles into array
		$.each(result.query.pages, function(_id, info) {
			redirectTitles.push(info.title);
		});
		// Continue query if needed
		if ( result.continue ) {
			doRedirectsQuery($.extend(redirectsQuery, result.continue));
			return;
		}

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

		// Set redirects
		config.pagedata.redirects = ( redirectTitles.length === 0 ) ? false : redirectTitles;
		checkPageData();
	};

	var redirectsQuery = {
		action: "query",
		pageids: config.mw.wgArticleId,
		generator: "redirects",
		grdlimit: 500
	};
	var doRedirectsQuery = function(q) {
		API.get( q )
		.done( processRedirectsQuery )
		.fail( function(c,r) {
			if ( r.textStatus === "abort" ) { return; }

			API.abort();
			var retry = confirm("Could not retrieve redirects:\n" + makeErrorMsg(c, r) +
				"\n\nTry again? (or Cancel to skip)");
			if ( retry ) {
				screen0();
			} else {
				config.pagedata.redirects = false;
				checkPageData();
			}
		} );
	};
	doRedirectsQuery(redirectsQuery);

	/* ---------- Review (Page Triage) status ------------------------------ */

	API.get( {
		action: "pagetriagelist",
		page_id: config.mw.wgArticleId
	} )
	.done( function(result) {
		if ( !result.pagetriagelist.pages.length ) {
			var keepGoing = confirm("WARNING: Page has already been reviewed by a New Page Patroller. Are you sure you want to draftify this page?");
			if ( !keepGoing ) {
				API.abort();
				$("#M2D-modal").remove();
				return;
			}
		}
		checkedPageTriageStatus = true;
		checkPageData();
	} )
	.fail( function(c,r) {
		if ( r.textStatus === "abort" ) { return; }

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

};

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

	switch ( status ) {
		case "started":
			$("#M2D-task" + taskNumber).css({"color":"#00F", "font-weight":"bold"});
			$("#M2D-status" + taskNumber).text("In process");
			break;
		case "done":
			$("#M2D-task" + taskNumber).css({"color":"#000", "font-weight":""});
			$("#M2D-status" + taskNumber).html("&check;");
			$("#M2D-status" + taskNumber).css("color", "green");
			break;
		case "skipped":
			$("#M2D-task" + taskNumber).css({"color":"#F00", "font-weight":""});
			$("#M2D-status" + taskNumber).text("Skipped!");
			break;
	}
	$("#M2D-status" + taskNumber)
		.css("margin", "0.75em");

	if (extraText !== undefined) {
		$("#M2D-status" + taskNumber).append(" (" + extraText + ")");
	}
};

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

	// First check the page hasn't been draftified in the meantime
	API.get({
		action: "query",
		pageids: 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 !== config.mw.wgNamespaceNumber) {
			return $.Deferred().reject("moveToDraft-movednamespace");
		}

		return API.postWithToken( "csrf", {
			action: "move",
			fromid: config.mw.wgArticleId,
			to: config.inputdata.newTitle,
			movetalk: 1,
			noredirect: 1,
			reason: config.inputdata.rationale,
			tags: config.changeTags
		} );
	})
	.done( function() {
		if (
			-1 === $.inArray("sysop", config.mw.wgUserGroups) &&
			-1 === $.inArray("extendedmover", config.mw.wgUserGroups)
		) {
			// Newly created redirect to be tagged for speedy deletion
			tagRedirect();
			return;
		}
		setTaskStatus(0, "done");
		getImageInfo();
	} )
	.fail( 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;
		} else if (c === "moveToDraft-alreadydraft") {
			alert("Aborted: The page has already been moved out of mainspace.");
			$("#M2D-modal").remove();
			window.location.reload();
			return;
		}

		var retry = confirm("Could not move page:\n"+ makeErrorMsg(c, r)+"\n\nTry again?");
		if ( retry ) {
			movePage();
		} else {
			showOptionsScreen(true);
		}
	} );
};

var tagRedirect = function() {
	$("#M2D-status0").html("Done,<br/>Tagging redirect for speedy deletion...");
	API.postWithToken( "csrf", {
		action: "edit",
		title: config.mw.wgPageName,
		prependtext: '{{Db-r2}}\n',
		summary: '[[WP:R2|R2]] speedy deletion request (article moved to draftspace)',
		tags: config.changeTags
	} )
	.done( function() {
		setTaskStatus(0, "done");
		getImageInfo();
	} )
	.fail( function(c,r) {
		if ( r.textStatus === "abort" ) { return; }

		var retry = confirm("Could not tag redirect for speedy deletion:\n"+
			makeErrorMsg(c, r) + "\n\nTry again?");
		if ( retry ) {
			tagRedirect();
		} else {
			setTaskStatus(0, "skipped");
			getImageInfo();
		}
	} );
};

//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);
				}
			});
		}
		editWikitext(nonfreefiles);
	};

	API.get( {
		action: "query",
		pageids: config.mw.wgArticleId,
		generator: "images",
		gimlimit: "max",
		prop: "categories",
		cllimit: "max",
		clcategories: "Category:All non-free media",
	} )
	.done( function(result){
		setTaskStatus(1, "done");
		processImageInfo(result);
	} )
	.fail( function(c,r) {
		if ( r.textStatus === "abort" ) { return; }

		var 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 ) {
			getImageInfo();
		} else {
			setTaskStatus(1, "skipped");
			editWikitext([]);
		}
	} );

};


//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 = ( !config.pagedata.redirects ) ? "" : "\n"+
		"<!-- Note: The following pages were redirects to [[" + config.mw.wgPageName +
		"]] before draftification:\n" +
		"*[[" + config.pagedata.redirects.join("]]\n*[[") + "]]\n-->\n";

	var wikitext = "{{subst:AFC draft|" + config.pagedata.author + "}}\n" +
		redirectsList +
		config.pagedata.oldwikitext.replace(/\[\[\s*[Cc]ategory\s*:/g, "[[:Category:") +
		"\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 normal_regex_str = "(";
		var gallery_regex_str = "(";
		var free_regex_str = "(";
		for (let i=0; i<nonfreefiles.length; i++ ) {
			// Take off namespace prefix
			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
			filename_regex_str = "[" + 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
			normal_regex_str += "\\[\\[\\s*(?:[Ii]mage|[Ff]ile)\\s*:\\s*" + filename_regex_str +
			"\\s*\\|?.*?(?:(?:\\[\\[.*?\\]\\]).*?)*\\]\\]";
			gallery_regex_str += "^\\s*(?:[Ii]mage|[Ff]ile):\\s*" + filename_regex_str + ".*?$";
			free_regex_str += "\\|\\s*(?:[\\w\\s]+\\=)?\\s*(?:(?:[Ii]mage|[Ff]ile):\\s*)?" +
			filename_regex_str;

			if ( i+1 === nonfreefiles.length ) {
				normal_regex_str += ")(?![^<]*?-->)";
				gallery_regex_str += ")(?![^<]*?-->)";
				free_regex_str += ")(?![^<]*?-->)";
			} else {
				normal_regex_str += "|";
				gallery_regex_str += "|";
				free_regex_str += "|";
			}
		}

		// Check for normal file usage, i.e. [[File:Foobar.png|...]]
		var normal_regex = new RegExp( normal_regex_str, "g");
		wikitext = wikitext.replace(normal_regex, "<!-- 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 gallery_regex = new RegExp( gallery_regex_str, "mg" );
		wikitext = wikitext.replace(gallery_regex, "<!-- 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 free_regex = new RegExp( free_regex_str, "mg" );
		wikitext = wikitext.replace(free_regex, "<!-- Commented out: $1 -->");
	}

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

		var 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");
			notifyContributors();
		}
	} );

};

var notifyContributors = function() {
	if(config.pagedata.notifyList.length === 0) {
		setTaskStatus(3, "skipped");
		updateTalk();
		return;
	}
	setTaskStatus(3, "started");
	for (let i in config.pagedata.notifyList) {
		notifyContributor( config.pagedata.notifyList[i] );
	}
	setTaskStatus(3, "done");
	updateTalk();
};

var notifyContributor = function( contributor ) {
	if ( !config.inputdata.notifyEnable ) {
		updateTalk();
		return;
	}

	API.postWithToken( "csrf", {
		action: "edit",
		title: "User talk:" + contributor,
		section: "new",
		sectiontitle: config.inputdata.notifyMsgHead,
		text: config.inputdata.notifyMsg,
		tags: config.changeTags
	} )
	.fail( function(c,r) {
		if ( r.textStatus === "abort" ) { return; }

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

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

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

		API.postWithToken( "csrf", {
			action: "edit",
			pageid: talk_id,
			section: "0",
			text: new_talk_wikitext,
			summary: 'Remove class/importance from project banners',
			tags: config.changeTags
		} )
		.done( function(){
			setTaskStatus(4, "done");
			draftifyLog();
		} )
		.fail( function(c,r) {
			if ( r.textStatus === "abort" ) { return; }

			var 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 ) {
				updateTalk();
			} else {
				setTaskStatus(4, "skipped");
				draftifyLog();
			}
		} );
	};

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

		var 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 ) {
			updateTalk();
		} else {
			setTaskStatus(4, "skipped");
			draftifyLog();
		}
	} );

};

var draftifyLog = function() {
	if (config.doNotLog) {
		$("#M2D-finished, #M2D-abort").toggle();
		return;
	}

	setTaskStatus(5, "started");

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

	var processLogWikitext = function(result) {
		var logpage_wikitext = "";

		var id = result.query.pageids[0];
		if ( id < 0 ) {
			var createlog = confirm("Log draftification (at " +  logpage + ") ?");
			if ( !createlog ) {
				setTaskStatus(5, "skipped");
				$("#M2D-finished, #M2D-abort").toggle();
				return;
			}
			logpage_wikitext = "This is a log of pages moved to draftspace using the [[User:MPGuy2824/MoveToDraft|MoveToDraft]] script."; 
		} else {
			logpage_wikitext = result.query.pages[id].revisions[0]["*"].trim();
		}

		if ( !headingPatt.test(logpage_wikitext) ) {
			logpage_wikitext += "\n\n" + heading;
		}
		logpage_wikitext += "\n" + config.inputdata.logMsg;

		API.postWithToken( "csrf", {
			action: "edit",
			title: logpage,
			text: logpage_wikitext,
			summary: 'Logging [['+config.inputdata.newTitle+']]',
			tags: config.changeTags
		} )
		.done( function(){
			setTaskStatus(5, "done");
			$("#M2D-finished, #M2D-abort").toggle();
		} )
		.fail( function(c,r) {
			if ( r.textStatus === "abort" ) { return; }

			var retry = confirm("Could not edit log page:\n"+ makeErrorMsg(c, r)+"\n\n[Okay] to try again, or [Cancel] to skip");
			if ( retry ) {
				draftifyLog();
			} else {
				setTaskStatus(5, "skipped");
				$("#M2D-finished, #M2D-abort").toggle();
			}
		} );
	};

	//get log page wikitext
	API.get( {
		action: "query",
		titles: logpage,
		prop: "revisions",
		rvprop: "content",
		indexpageids: 1
	} )
	.done( processLogWikitext )
	.fail( function(c,r) {
		if ( r.textStatus === "abort" ) { return; }

		var retry = confirm("Could not find log page:\n"+ makeErrorMsg(c, r)+"\n\n[Okay] to try again, or [Cancel] to skip");
		if ( retry ) {
			draftifyLog();
		} else {
			setTaskStatus(5, "skipped");
			$("#M2D-finished, #M2D-abort").toggle();
		}
	} );
};

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(config.script.location, 'Move To Draft'),
				' <small>(v' + 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 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("Loading...");
	$("#M2D-interface-content").append(
		$('<ul>').attr('id', 'M2D-tasks').css("color", "#888").append(
			$('<li>').attr('id', 'M2D-task0').append(
				'Getting contributor data',
				$('<span>').attr('id','M2D-status0')
			),
			$('<li>').attr('id', 'M2D-task1').append(
				'Getting page data',
				$('<span>').attr('id','M2D-status1')
			)
		)
	);

	$("#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(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(config.pagedata.contributors)[0]
		config.pagedata.notifyList.push( 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', '0.5em')
			.text("Choose contributors to send notifications to: ")
		)
		.css('margin', '1em');

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

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

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

		if( config.pagedata.notifyList.includes( contributor ) ) {
			$('#M2D-option-authors-checkbox-'+contributor).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');
		config.pagedata.notifyList = [];
		for (let  checkbox of markedCheckboxes ) {
			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: 'darkred',
				'font-weight': 'bold',
			}),
			$('<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')
		),

		$('<div>').css('margin-bottom','0.5em').append(
			$('<label>').attr({'for':'M2D-option-movelog', 'id':'M2D-option-movelog-label'})
				.css('display','block').text('Reason for move (log summary):'),
			$('<textarea>').attr({'rows':'1', 'name':'M2D-option-movelog', 'id':'M2D-option-movelog'})
				.css('width','99%')
		),

		$('<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): '))
		),

		$('<label>').attr({'for':'M2D-option-message-head', 'id':'M2D-option-message-head-label'})
			.css({'display':'block', 'margin-top':'0.5em'}).text('Notification heading'),
		$('<textarea>').attr({'id':'M2D-option-message-head', 'rows':'1'})
			.css({'width':'99%', 'margin-bottom':'0.5em'}),

		$('<label>').attr({'id':'M2D-option-reasons-label'})
			.css({'display':'block', 'margin-top':'0.5em'}).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'}),

		$('<label>').attr({'for':'M2D-option-message', 'id':'M2D-option-message-label'})
			.css('display','block').text('Notification message:'),
		$('<textarea>').attr({'id':'M2D-option-message', 'rows':'6'})
			.css('width','99%'),
	);

	for (let key in 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(config.draftReasons[key].charAt(0).toUpperCase() + config.draftReasons[key].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(config.wikitext.rationale);
	$('#M2D-option-newtitle').val(getPageText(config.mw.wgPageName)).change(function() {
		$('#M2D-option-message-head').val(
			$('#M2D-option-message-head').val().trim()
			.replace(/\[\[Draft\:.*?\|/, "[[Draft:" + $('#M2D-option-newtitle').val().trim() + "|")
		);
		$('#M2D-option-message').val(
			$('#M2D-option-message').val().trim()
			.replace(/\[\[Draft\:.*?\|/, "[[Draft:" + $('#M2D-option-newtitle').val().trim() + "|")
		);
	});

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

	if (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(
			" " + config.pagedata.notifyList.toString().replace(/,/g, ", ") + " "
		);
	}

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

	$('#M2D-option-message-head').val(config.wikitext.notification_heading.replace(/\$1/g, getPageText(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(config.inputdata.rationale);
		$('#M2D-option-newtitle').val(config.inputdata.newTitle);
		$('#M2D-option-author').val(config.inputdata.authorName);
		$('#M2D-option-message-enable').prop('checked', config.inputdata.notifyEnable);
		$('#M2D-option-message-head').val(config.inputdata.notifyMsgHead);
		$('#M2D-option-message').val(config.inputdata.notifyMsg);
	}


	$("#M2D-next").click(function(){
		//Gather inputs
		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:		$('#M2D-option-message').val().trim()
		};
		config.inputdata.logMsg = config.wikitext.logMsg
			.replace(/\$1/g, getPageText(config.mw.wgPageName))
			.replace(/\$2/g, config.inputdata.newTitle);

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

		if ( config.inputdata.rationale.length === 0 ) {
			errors.push("Move log reason is empty");
		}
		if ( config.inputdata.notifyEnable ) {
			if ( config.inputdata.notifyMsgHead.length === 0 ) {
				errors.push("Notification heading is empty");
			}
			if ( 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();
	});
};


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

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

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

	if ( lastEditAgeInMinutes < config.minimumAgeInMinutes ) {
		$(divId).text('');
		$(divId).append(
			"Draftifying may not be appropriate per ",
			makeLink("WP:DRAFTIFY"),
			", since this article was edited less than ",
			config.minimumAgeInMinutes,
			" minutes ago."
		);
	} else if ( articleAgeInDays > config.maximumAgeInDays ) {
		$(divId).text('');
		$(divId).append(
			"Draftifying may not be appropriate per ",
			makeLink("WP:DRAFTIFY"),
			", since this article is more than ",
			config.maximumAgeInDays,
			" days old."
		);
	} else if ( config.pagedata.previousDraftification ) {
		$(divId).text('');
		$(divId).append(
			"Draftifying may not be appropriate per ",
			makeLink("WP:DRAFTIFY"),
			", since this article has been previously draftified."
		);
	}
};

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

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

	//Other reasons text box
	var otherText = $("#M2D-reason-other").val();
	if ( otherText !== '' ) {
		draftifyReasons.other = otherText;
		reasons.push('other');
		$('#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]] + "'''";
		if (reasons.length > 1) {
			for (let i = 1; i < (reasons.length - 1); i++) {
				reasonText += ", '''" + draftifyReasons[reasons[i]] + "'''";
			}
			reasonText += " and '''" + draftifyReasons[reasons[reasons.length-1]] + "'''";
		}
	}

	$('#M2D-option-message').val(config.wikitext.notification
			.replace(/\$1/g, getPageText(config.mw.wgPageName))
			.replace(/\$3/g, reasonText)
	);

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

};

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

	$("#M2D-interface-content").append(
		$('<ul>').attr('id', 'M2D-tasks').css("color", "#888").append(
			$('<li>').attr('id', 'M2D-task0').append(
				'Moving page',
				$('<span>').attr('id','M2D-status0')
			),
			$('<li>').attr('id', 'M2D-task1').append(
				'Checking images',
				$('<span>').attr('id','M2D-status1')
			),
			$('<li>').attr('id', 'M2D-task2').append(
				'Editing page wikitext',
				$('<span>').attr('id','M2D-status2')
			),
			config.inputdata.notifyEnable ?
				$('<li>').attr('id', 'M2D-task3').append(
					'Notifying contributor/s',
					$('<span>').attr('id','M2D-status3')
				)
				: '',
			$('<li>').attr('id', 'M2D-task4').append(
				'Updating talk page banners',
				$('<span>').attr('id','M2D-status4')
			),

			$('<li>').attr('id', 'M2D-task5').append(
				'Logging',
				config.doNotLog
					? $('<span>').attr('font-size', '90%' ).text('disabled')
					: $('<span>').attr('id','M2D-status5')
			)
		)
	);

	$("#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(){
		API.abort();
		$("#M2D-modal").remove();
		window.location.reload();
	} );

	//Start the first task. The rest are done sequentially as each task is completed (or skipped).
	movePage();
};

// --- 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-footor").css("min-height", "3em");

	// Initial interface content
	screen0();
});

// End of function moveToDraft
};

/* ========== Log draftifications for a user ==================================================== */
function logDraftifications(username, fromDate) {
	var targetUser = username;
	if (!targetUser && targetUser!=="") {
		var pageNameParts = config.mw.wgPageName.split('/');
		targetUser = (pageNameParts.length > 1) ?  pageNameParts[1] : '';
	}
	$('#mw-content-text').empty();
	// TODO: Form for setting user
	var today = new Date().toISOString().slice(0,10);
	var MoveToDraftEpoch = "2017-05-29";
	$('#mw-content-text').append(
		$(`<form id='draftifyLogForm' style='border: 1px solid #ccc; margin: 1em 0; padding: 0 0.5em;'>
			<div style="display:inline-block;padding:0.5em">
				<label for="draftifyUsername">User:</label>
				<input type="text" name="username" id="draftifyUsername" />
			</div>
			<div style="display:inline-block;padding:0.5em">
				<label for="draftifyFromDate">From date (and earlier)</label>
				<input type="date" id="draftifyFromDate" name="fromDate" value="${fromDate || today}" />
			</div>
			<div style="display:inline-block;padding:0.5em">
				<input type="submit" value="Show" />
			</div>
			</form>
		`)
	);
	$('#draftifyUsername').val(targetUser);
	$('#draftifyLogForm').on('submit', function(e) {
		e.preventDefault();
		$('#draftifyLog, #draftifyLogWikitext').show();
		logDraftifications($('#draftifyUsername').val(), $('#draftifyFromDate').val());
	});

	$('#mw-content-text').append(
		$(`<table id='draftifyLog' class='wikitable sortable' style='width:100%'>
		<thead><tr>
			<th scope='col'>From</th>
			<th scope='col'>To</th>
			<th scope='col'>Time</th>
			<th scope='col'>User</th>
			<th scope='col'>Reason</th>
		</tr></thead>
		<tbody></tbody>
		<tfoot><tr>
			<td colspan=5 id="draftifyStatus">Loading...</td>
		</tr></tfoot>
		</table>
		<textarea id="draftifyLogWikitext" disabled="disabled" rows="10">
		`)
	);

	$('#draftifyLogWikitext').val(`{|class="wikitable"
|-
!scope='col'|From
!scope='col'|To
!scope='col'|Time
!scope='col'|User
!scope='col'|Reason
|}`);

	var query = {
		action: "query",
		format: "json",
		list: "logevents",
		leprop: "title|timestamp|comment|details|user",
		letype: "move",
		lenamespace: "0",
		lelimit: "500",
		lestart: (fromDate || today) + "T23:59:59Z"
	};
	if (targetUser) {
		query.leuser = targetUser;
	}

	var continueInfo = {};

	function onLoadMoreClick(e) {
		e.preventDefault();
		$('#draftifyStatus').empty().text("Loading...");
		searchAndShowResults();
	}

	function parseLogTable(wikitext) {
		API.post({
			"action": "parse",
			"format": "json",
			"text": wikitext,
			"prop": "text",
			"contentmodel": "wikitext"
		}).then(function(response) {
			$parsedLogTable = $(response.parse.text["*"]);
			$('#draftifyLog tbody').empty().append(
				$parsedLogTable.find('tr').slice(1)
			);
		});
	}

	function searchAndShowResults() {
		API.get( $.extend({}, query, continueInfo) )
			.then(function(response) {
				// Store continuing info, if any
				continueInfo = response.continue || {};
				// Reset status, add a "Load more" if there are more results
				$('#draftifyStatus').empty().append(
					response.continue
						? $('<a>').css("cursor", "pointer").text('Load more').click(onLoadMoreClick)
						: null
				);
				// Filter to only MoveToDraft script moves
				var draftifyEvents = response.query && response.query.logevents && response.query.logevents.filter(function(logevent) {
					return logevent.params.target_ns === 118; // Moved to Draft namespace
				});
				var noDraftifyEvents = !draftifyEvents || !draftifyEvents.length;

				switch(true) {
					case noDraftifyEvents && !response.continue:
						$('#draftifyStatus').empty().text(
							$('#draftifyLog tbody tr').length == 0 ? "No results" : "No further results"
						);
						break;
					case noDraftifyEvents:
						// Continue with next batch of results, otherwise table will initially have no results but a load more link,
						// or clicking "Load more" will appear to show "Loading..." but not actually add any results
						searchAndShowResults();
						break;
					case !response.continue:
						$('#draftifyStatus').empty().text("No further results");
						/* falls through */
					default:
						draftifyEvents.forEach(function(logevent) {
							var fromTitle = logevent.title;
							var toTitle = logevent.params.target_title;
							var timeOfMove = new Date(logevent.timestamp).toUTCString().replace("GMT", "(UTC)");
							var user = logevent.user;
							var comment = logevent.comment;
							var wikitext = $('#draftifyLogWikitext').val().replace("|}", `|-
|[[${fromTitle}]]
|[[${toTitle}]]
|${timeOfMove}
|[[User:${user}|${user}]]
|${comment}
|}`);
							$('#draftifyLogWikitext').val(wikitext);
							parseLogTable(wikitext);
						});
				}
			});
	}

	// Run by default, unless page loaded without a /username suffix
	if (username || username==="") {
		searchAndShowResults();
	} else {
		$('#draftifyLog, #draftifyLogWikitext').hide();
	}

// End of function logDraftifications
}

/* ========== Setup ============================================================================= */
// Access draftifications using Special:Draftify_log/USER_NAME
var isDraftifyLogPage = config.mw.wgPageName.indexOf("Special:Draftify_log") === 0;
var isUserPage = config.mw.wgNamespaceNumber === 2 || config.mw.wgNamespaceNumber === 3;
if (isDraftifyLogPage) {
	document.title = "Draftify log - Wikipedia";
	$('h1').text("Draftify log");
	$('#mw-content-text').empty()
	.text("Loading...")
	.before(
		$('<span>').append(
			'Note: This page only works with the ',
			$('<a>').attr('href','/wiki/User:MPGuy2824/MoveToDraft').text('MoveToDraft'),
			' userscript installed.'
		),
		$('<hr>')
	);
	logDraftifications();
} else if (isUserPage) {
	var user = config.mw.wgTitle.split('/')[0];
	var url = mw.util.getUrl("Special:Draftify_log/" + user);
	mw.util.addPortletLink( (window.m2d_portlet||'p-cactions'), url, 'Draftify log', 'ca-m2dlog', null, null, "#ca-move");
}

// Only operate in article namespace
if( config.mw.wgNamespaceNumber !== 0 ) {
	return;
}

// Only operate for existing pages
if ( config.mw.wgCurRevisionId === 0 ) {
	return;
}

moveToDraft();

});
// </nowiki>