User:Ahecht/Scripts/pageswap-core.js

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.
//jshint -W083

function pageSwap(prefix, moveReason, debug) {
	var config = {
		link: "[[" + prefix + ".js|pageswap]]",
		intermediatePrefix: "Draft:Move/",
		portletLink: "Swap" + (debug ? " (debug)" : ""),
		portletAlt: "Perform a revision history swap / round-robin move",
		swapButton: 'Swap pages' + (debug ? " (debug)" : ""),
		confirmButton: 'Confirm' + (debug ? " (debug)" : ""),
		confirmMessageHeader: '<strong>Round-robin configuration:</strong><ul><li>',
		confirmMessageFooter: '<p>Press "Confirm" to proceed.</p>',
		statusMessageHeader: '<strong>Performing page swap:</strong><ul>',
		indentStyle: ' style="margin-left: 1.6em;"',
		introText: '<p style="font: bold 1.2em sans-serif;">Please post bug ' +
			'reports/comments/suggestions for the pageswap script at ' +
			'[[User talk:Ahecht]]. To revert to the previous ' +
			'dialogue-based version of this script, use ' +
			'[[User:Ahecht/Scripts/pageswap_1.5.2.js]] instead.</p>' +
			'<p>Using the form below will [[Wikipedia:Moving a page#Swapping ' +
			'two pages|swap]] two pages using the [[User:Ahecht/Scripts/' +
			'pageswap|pageswap]] script, moving all of their history to the new ' +
			'names. <strong>Links to the old page titles will not be changed' +
			'</strong>. ' +
			'Be sure to check <strong>[[Special:MyContributions]]</strong> for ' +
			'[[Special:DoubleRedirects|double]] or [[Special:BrokenRedirects|' +
			'broken redirects]] and [[Wikipedia:Red link|red links]]. ' +
			'You are responsible for making sure that links continue to point ' +
			'where they are supposed to go and for doing all post-move cleanup ' +
			'listed under [[User:Ahecht/Scripts/pageswap#Out_of_scope|Out of ' +
			'scope]] in the script\'s documentation.</p>' +
			'<p><strong>Note:</strong>' +
			'This can be a drastic and unexpected change for a popular page; ' +
			'please be sure you understand the consequences of this before ' +
			'proceeding. Please read [[Wikipedia:Moving a page]] for more ' +
			'detailed instructions.</p>',
		doneMsgCleanup: 'Please do post-move cleanup as necessary',
		doneMsgRedlink: 'create new red-linked talk pages/subpages if ' +
			'there are incoming links (check your [[Special:MyContributions|' +
			'contribs]] for "Talk:" and subpage redlinks)',
		doneMsgRedir: 'correct any moved redirects (including on talk pages and ' +
			'subpages)',
		doneSubpages: 'The following subpages were moved, and may need new or updated redirects:',
		errorMsg: 'Error adding swap form to page!'
	}, params = {
		currTitle: {}, destTitle: {}, confirmMessages: [], statusMessages: [],
		defaultMoveTalk: true,
		done: false, cleanup: (
			typeof pagemoveDoPostMoveCleanup === 'undefined' ?
			true : 
			pagemoveDoPostMoveCleanup
		)
	};
	
	function wikiLink(message) {
		message = message.replace(/\[\[(.*?)\]\]/g, function(s,v) {
			v = v.split("|");
			var urlSuffix="", aClass="";

			if (v[0].search(new RegExp("^"+config.intermediatePrefix)) > -1) {
				// Make intermediate page appear as redlink
				aClass='new';
			} else if (v[0].search(/^\R¬/) > -1) {
				// display redirects marked with R¬
				v[0] = v[0].replace(/^R¬/, '');
				aClass='mw-redirect';
				if (v.length == 1) {
					urlSuffix = "?redirect=no";
				}
			}
			var url = mw.config.get("wgServer") + 
				mw.config.get("wgArticlePath").replace("$1", encodeURI(v[0].replace(/ /g,"_")));
			return '<a href="'+url+urlSuffix+'" class="'+aClass+'" title="'+v[0].replace(/_/g," ")+'">' +
				(v[1] || v[0]) + '</a>';
		} );
		return message;
	}
	
	function showConfirm(message, type='notice', done=false) {
		if (message !== '') {
			params.confirmMessages.push(wikiLink(message));
		}
		var types = ['notice', 'success', 'warning', 'error'];
		if (types.indexOf(type) > types.indexOf(psConfirm.type)) {
			psConfirm.setType(type);
		}
		var label = new OO.ui.HtmlSnippet(
			config.confirmMessageHeader+
			params.confirmMessages.join("</li><li>")+
			"</li></ul>"+
			(done ? config.confirmMessageFooter : '')
		);
		psConfirm.setLabel(label);
		psConfirm.toggle(true);
		psConfirm.scrollElementIntoView();
	}
	
	function showStatus(message, type='notice', done=false, indent=false) {
		if (message !== '') {
			params.statusMessages.push("<li"+(indent ? config.indentStyle : '')+
				">"+wikiLink(message)+"</li>");
		}
		
		if (done) {
			params.done = true;
			if (params.allSpArr.length) {
				params.statusMessages.push(
					wikiLink("<li>"+config.doneSubpages+"<ul><li>[[" + 
					params.allSpArr.join("]]</li><li>[[") + "]]</li></ul></li>")
				);
			}
			psContribsButton.toggle(true);
		}
		
		var types = ['notice', 'success', 'warning', 'error'];
		if (types.indexOf(type) > types.indexOf(psStatus.type)) {
			psStatus.setType(type);
		}
		
		var doneMessage = "";
		if (params.done) {
			var doneMessages = [config.doneMsgCleanup];
			if (!params.talkRedirect || params.moveSubpages) {doneMessages.push(config.doneMsgRedlink)}
			if (!params.fixSelfRedirect || params.moveSubpages) {doneMessages.push(config.doneMsgRedir)}
			
			if (doneMessages.length < 3) {
				doneMessage = doneMessages.join(" and ");
			} else {
				doneMessage = doneMessages.slice(0, -1).join(', ')+', and '+doneMessages.slice(-1);
			}
			doneMessage = "<p>"+wikiLink(doneMessage)+".</p>";
		}
		
		var label = new OO.ui.HtmlSnippet(
			config.statusMessageHeader+
			params.statusMessages.join('')+
			"</ul>"+doneMessage
		);

		psStatus.setLabel(label);
		psStatus.toggle(true);
		psStatus.scrollElementIntoView();
	}
	
	function getPagesData() {
		// get page data, normalize titles
		var ret = {valid: true, invalidReason: ''};
		var titlesString = " [["+params.currTitle.title+"]] or [["+params.destTitle.title+"]]. ";
		var queryData = {action:'query', format:'json', prop:'info', inprop:'talkid',
				intestactions:'move|create', titles: (params.currTitle.title + "|" + params.destTitle.title),
				list:'logevents', leprop:'timestamp', letype:'move', letitle: params.currTitle.title, lelimit:'1'
			};
		var query = $.ajax({ 
			url: mw.util.wikiScript('api'), async:false,
			error: function (jqXHR, textStatus, errorThrown) {
				var errStr = "Error '"+(jqXHR.status||textStatus)+
					"' fetching API data on "+titlesString+". "+
					(errorThrown||jqXHR.responseText).replace("\n","");
				console.warn(errStr);console.log(queryData);console.log(jqXHR);
				ret = {valid: false, invalidReason: errStr};
			},
			data: queryData
		}).responseJSON;
		if (typeof query === 'undefined' || typeof query.query === 'undefined') {
			return {valid: false, invalidReason: ret.invalidReason+
				"Error parsing API data on"+titlesString};
		}
		if (!ret.valid) {return ret;}
		
		query = query.query;
		if (typeof query.pages !== 'undefined' && typeof query.logevents !== 'undefined') {
			for (var kn in query.normalized) {
				if (params.currTitle.title == query.normalized[kn].from) {
					params.currTitle.title = query.normalized[kn].to;
				}
				if (params.destTitle.title == query.normalized[kn].from) {
					params.destTitle.title = query.normalized[kn].to;
				}
			}
			for (var kp in query.pages) {
				if (params.currTitle.title == query.pages[kp].title) {
					params.currTitle = query.pages[kp];
				}
				if (params.destTitle.title == query.pages[kp].title) {
					params.destTitle = query.pages[kp];
				}
				if (kp < 0) {
					ret.valid = false;
					if (typeof query.pages[kp].missing !== 'undefined') {
						ret.invalidReason += "Unable to find [["+query.pages[kp].title+"]]. ";
					} else if (typeof query.pages[kp].invalid !== 'undefined' &&
						typeof query.pages[kp].invalidreason !== 'undefined') {
						ret.invalidReason += query.pages[kp].invalidreason;
					} else {
						ret.invalidReason += "Unable to get page data for"+titlesString;
					}
				}
			}
			for (var kl in query.logevents) {
				var lastMove = (Date.now()-Date.parse(query.logevents[kl].timestamp))/(1000*60);
				if ( lastMove < 60 ) {
					showConfirm("<b>Warning: [[" + params.currTitle.title + "]] was last moved " +
						Math.round(lastMove) + " minute(s) ago.</b>",
						'warning');
				}
			}
		} else {
			ret = {valid: false, invalidReason: "Unable to get page data for"+titlesString};
		}

		return ret;
	}
			
	/**
	 * Given namespace data, title, title namespace, returns expected title of page
	 * Along with title without prefix
	 * Precondition, title, titleNs is a subject page!
	 */
	function getTalkPageName(title, titleNs) {
		var ret = {};
		var nsData = mw.config.get("wgFormattedNamespaces");
		var prefixLength = nsData['' + titleNs].length === 0 ?
			0 : nsData['' + titleNs].length + 1;
		ret.titleWithoutPrefix = title.substring(prefixLength, title.length);
		ret.talkTitle = nsData['' + ((Math.floor(titleNs / 2)*2) + 1)] + ':' +
			ret.titleWithoutPrefix;
		return ret;
	}

	/**
	 * Given two (normalized) titles, find their namespaces, if they are redirects,
	 * if have a talk page, whether the current user can move the pages, suggests
	 * whether movesubpages should be allowed, whether talk pages need to be checked
	 */
	function swapValidate() {
		 // get page data, normalize titles
		var ret =	getPagesData();
		if (ret.valid === false || params.currTitle.title === null ||
			params.destTitle.title === null || params === null
		) {
			ret.valid = false;
			ret.invalidReason += "Failed to validate swap.";
			return ret;
		}

		ret.allowMoveSubpages = true;
		ret.checkTalk = true;
		for (const k of ["currTitle", "destTitle"]) {
			if (k == "-1" || params[k].ns < 0) {
				ret.valid = false;
				ret.invalidReason = ("Page " + params[k].title + " does not exist.");
				return ret;
			}
			// enable only in ns 0..5,12,13,118,119 (Main,Talk,U,UT,WP,WT,H,HT,D,DT)
			if ((params[k].ns >= 6 && params[k].ns <= 9) ||
			 (params[k].ns >= 10 && params[k].ns <= 11 && !params.uPerms.allowSwapTemplates) ||
			 (params[k].ns >= 14 && params[k].ns <= 117) ||
			 (params[k].ns >= 120)) {
				ret.valid = false;
				ret.invalidReason = ("Namespace of " + params[k].title + " (" +
					params[k].ns + ") not supported.\n\nLikely reasons:\n" +
					"- Names of pages in this namespace relies on other pages\n" +
					"- Namespace features heavily-transcluded pages\n" +
					"- Namespace involves subpages: swaps produce many redlinks\n" +
					"\n\nIf the move is legitimate, consider a careful manual swap.");
				return ret;
			}
			if (params.currTitle.title == params[k].title) {
				ret.currTitle   = params[k].title;
				ret.currNs      = params[k].ns;
				ret.currTalkId  = params[k].talkid; // could be undefined
				ret.currCanMove = params[k].actions.move === '';
				ret.currIsRedir = params[k].redirect === '';
			}
			if (params.destTitle.title == params[k].title) {
				ret.destTitle   = params[k].title;
				ret.destNs      = params[k].ns;
				ret.destTalkId  = params[k].talkid; // could be undefined
				ret.destCanMove = params[k].actions.move === '';
				ret.destIsRedir = params[k].redirect === '';
			}
		}

		if (!ret.valid) return ret;
		if (!ret.currCanMove) {
			ret.valid = false;
			ret.invalidReason = ('' + ret.currTitle + " is immovable. Aborting");
			return ret;
		}
		if (!ret.destCanMove) {
			ret.valid = false;
			ret.invalidReason = ('' + ret.destTitle + " is immovable. Aborting");
			return ret;
		}
		if (ret.currNs % 2 !== ret.destNs % 2) {
			ret.valid = false;
			ret.invalidReason = "Namespaces don't match: one is a talk page.";
			return ret;
		}
		ret.currNsAllowSubpages = params.nsData['' + ret.currNs].subpages !== '';
		ret.destNsAllowSubpages = params.nsData['' + ret.destNs].subpages !== '';

		// if same namespace (subpages allowed), if one is subpage of another,
		// disallow movesubpages
		if (ret.currTitle.startsWith(ret.destTitle + '/') ||
				ret.destTitle.startsWith(ret.currTitle + '/')) {
			if (ret.currNs !== ret.destNs) {
				ret.valid = false;
				ret.invalidReason = "Strange.\n" + ret.currTitle + " in ns " +
					ret.currNs + "\n" + ret.destTitle + " in ns " + ret.destNs +
					". Disallowing.";
				return ret;
			}

			ret.allowMoveSubpages = ret.currNsAllowSubpages;
			if (!ret.allowMoveSubpages)
				ret.addlInfo = "One page is a subpage. Disallowing move-subpages";
		}

		if (ret.currNs % 2 === 1) {
			ret.checkTalk = false; // no need to check talks, already talk pages
		} else { // ret.checkTalk = true;
			var currTPData = getTalkPageName(ret.currTitle, ret.currNs);
			ret.currTitleWithoutPrefix = currTPData.titleWithoutPrefix;
			ret.currTalkName = currTPData.talkTitle;
			var destTPData = getTalkPageName(ret.destTitle, ret.destNs);
			ret.destTitleWithoutPrefix = destTPData.titleWithoutPrefix;
			ret.destTalkName = destTPData.talkTitle;
			// possible: ret.currTalkId undefined, but subject page has talk subpages
		}
		return ret;
	}

	/**
	 * Given two talk page titles (may be undefined), retrieves their pages for comparison
	 * Assumes that talk pages always have subpages enabled.
	 * Assumes that pages are not identical (subject pages were already verified)
	 * Assumes namespaces are okay (subject pages already checked)
	 * (Currently) assumes that the malicious case of subject pages
	 *   not detected as subpages and the talk pages ARE subpages
	 *   (i.e. A and A/B vs. Talk:A and Talk:A/B) does not happen / does not handle
	 * Returns structure indicating whether move talk should be allowed
	 */
	function talkValidate(checkTalk, talk1, talk2) {
		var ret = {};
		ret.allowMoveTalk = true;
		if (!checkTalk) { return ret; } // currTitle destTitle already talk pages
		if (talk1 === undefined || talk2 === undefined) {
			showStatus("Unable to validate talk. Disallowing movetalk to be safe.", 'warning');
			ret.allowMoveTalk = false;
			return ret;
		}
		ret.currTDNE = true;
		ret.destTDNE = true;
		ret.currTCanCreate = true;
		ret.destTCanCreate = true;
		var talkTitleArr = [talk1, talk2];
		if (talkTitleArr.length !== 0) {
			var talkData = JSON.parse($.ajax({
				url: mw.util.wikiScript('api'), async:false,
				error: function (jsondata) {
					showStatus("Unable to get info on talk pages.", 'warning');
					return ret;
				},
				data: { action:'query', format:'json', prop:'info',
					intestactions:'move|create', titles:talkTitleArr.join('|') }
			}).responseText).query.pages;
			for (var id in talkData) {
				if (talkData[id].title === talk1) {
					ret.currTDNE = talkData[id].invalid === '' || talkData[id].missing === '';
					ret.currTTitle = talkData[id].title;
					ret.currTCanMove = talkData[id].actions.move === '';
					ret.currTCanCreate = talkData[id].actions.create === '';
					ret.currTalkIsRedir = talkData[id].redirect === '';
				} else if (talkData[id].title === talk2) {
					ret.destTDNE = talkData[id].invalid === '' || talkData[id].missing === '';
					ret.destTTitle = talkData[id].title;
					ret.destTCanMove = talkData[id].actions.move === '';
					ret.destTCanCreate = talkData[id].actions.create === '';
					ret.destTalkIsRedir = talkData[id].redirect === '';
				} else {
					showStatus("Found pageid ("+talkData[id].title+") not matching given ids ("+
						talk1+" and "+talk2+").", 'error');
					return {};
				}
			}
		}

		ret.allowMoveTalk = (ret.currTCanCreate && ret.currTCanMove) &&
			(ret.destTCanCreate && ret.destTCanMove);
		return ret;
	}

	/**
	 * Given existing title (not prefixed with "/"), optionally searching for talk,
	 *   finds subpages (incl. those that are redirs) and whether limits are exceeded
	 * As of 2016-08, uses 2 api get calls to get needed details:
	 *   whether the page can be moved, whether the page is a redirect
	 */
	function getSubpages(nsData, title, titleNs, isTalk) {
		if ((!isTalk) && nsData['' + titleNs].subpages !== '') { return { data:[] }; }
		var titlePageData = getTalkPageName(title, titleNs);
		var queryData = { action:'query', format:'json', list:'allpages',
				apnamespace:(isTalk ? ((titleNs % 2) +1) : titleNs),
				apfrom:(titlePageData.titleWithoutPrefix + '/'),
				apto:(titlePageData.titleWithoutPrefix + '0'),
				aplimit:101 };
		var subpages = JSON.parse($.ajax({
			url: mw.util.wikiScript('api'), async:false,
			error: function (jqXHR, textStatus, errorThrown) {
				var errStr = "API error '"+(jqXHR.status||textStatus)+
					"' when searching for subpages of "+title+". "+
					(errorThrown||jqXHR.responseText).replace("\n","");
				console.warn(errStr);console.log(queryData);console.log(jqXHR);
				return { error:errStr+" Subpages may exist." };
			},
			data: queryData
		}).responseText).query;
		
		if (typeof subpages === 'object' && typeof subpages.allpages !== 'undefined') {
			subpages = subpages.allpages;
		} else {
			console.warn("API did not return 'allpages' when querying subpage data:");console.log(subpages);
			return { error:"API did not return subpage data. Subpages may exist." };
		}

		// put first 50 in first arr (need 2 queries due to api limits)
		var subpageids = [[],[]];
		for (var idx in subpages) {
			subpageids[idx < 50 ? 0 : 1].push( subpages[idx].pageid );
		}

		if (subpageids[0].length === 0) { return { data:[] }; }
		if (subpageids[1].length === 51) { return { error:"100+ subpages. Aborting" }; }
		var dataret = [];
		var subpageData0 = $.ajax({ 
			url: mw.util.wikiScript('api'), async:false,
			error: function (jsondata) {
				return { error:"Unable to fetch subpage data." }; },
			data: { action:'query', format:'json', prop:'info', intestactions:'move|create',
				pageids:subpageids[0].join('|') }
		}).responseJSON.query.pages;
		for (var k0 in subpageData0) {
			dataret.push({
				title:subpageData0[k0].title,
				isRedir:subpageData0[k0].redirect === '',
				canMove:subpageData0[k0].actions.move === ''
			});
		}

		if (subpageids[1].length === 0) { 
			return { data:dataret }; 
		}
		var subpageData1 = $.ajax({ 
			url: mw.util.wikiScript('api'), async: false,
			error: function (jsondata) {
				return { error:"Unable to fetch subpage data." }; },
			data: { action:'query', format:'json', prop:'info', intestactions:'move|create',
				pageids:subpageids[1].join('|') }
		}).responseJSON.query.pages;
		for (var k1 in subpageData1) {
			dataret.push({
				title:subpageData1[k1].title,
				isRedir:subpageData1[k1].redirect === '',
				canMove:subpageData1[k1].actions.move === ''
			});
		}
		return { data:dataret };
	}

	/**
	 * Prints subpage data given retrieved subpage information returned by getSubpages
	 * Returns a suggestion whether movesubpages should be allowed
	 */
	function printSubpageInfo(basepage, currSp) {
		var ret = {};
		var currSpArr = [];
		var currSpCannotMove = [];
		var redirCount = 0;
		for (var kcs in currSp.data) {
			if (!currSp.data[kcs].canMove) {
				currSpCannotMove.push(currSp.data[kcs].title);
			}
			currSpArr.push((currSp.data[kcs].isRedir ? "(R) " : "  ") +
				currSp.data[kcs].title);
			if (currSp.data[kcs].isRedir)
				redirCount++;
		}

		if (currSpArr.length > 0) {
			if (currSpCannotMove.length > 0) {
				showConfirm("Disabling move-subpages." +
					"The following " + currSpCannotMove.length + " (of " +
					currSpArr.length + ") total subpages of [[" +
					basepage + "]] CANNOT be moved:<ul><li>[[" +
					currSpCannotMove.join("]]</li><li>[[") + "]]</li></ul>",
					'warning');
			} else {
				showConfirm(currSpArr.length + " total subpages of [[" + basepage + "]]" +
					(redirCount !== 0 ? (" (" + redirCount + " redirects):") : ":") +
					"<ul><li>[[" + currSpArr.join("]]</li><li>[[") + "]]</li></ul>");
			}
		}

		ret.allowMoveSubpages = currSpCannotMove.length === 0;
		ret.noNeed = currSpArr.length === 0;
		ret.spArr = currSpArr;
		return ret;
	}

	function createMissingTalk(vData, vTData) {
		if (params.moveTalk && params.talkRedirect) {
			var fromTalk, toTalk;
			if (vTData.currTDNE && !vTData.destTDNE) {
				fromTalk = vData.destTalkName;
				toTalk = vData.currTalkName;
			} else if (vTData.destTDNE && !vTData.currTDNE) {
				fromTalk = vData.currTalkName;
				toTalk = vData.destTalkName;
			}
			
			if (fromTalk && toTalk) {
				setTimeout(() => {
					if (params.talkRedirect) {
						var talkRedirect = {
							action:'edit',
							title:fromTalk,
							createonly: true,
							text: "#REDIRECT [[" + toTalk + "]]\n{{R from move}}",
							summary: "Create redirect to [[" + toTalk + "]] using " + config.link,
							watchlist: params.watch
						};
						showStatus("Creating talk page redirect [[R¬"+fromTalk+"]] → [["+toTalk+"]]...");
						if (debug) {
							console.log(talkRedirect);
							showStatus("Redirect [[R¬"+fromTalk+"]] → [["+toTalk+
								"]] simulated successfully!.", 'success', true, true);
						} else {
							new mw.Api().postWithEditToken(talkRedirect).done(function (reslttr) {
								showStatus("Redirect [[R¬"+fromTalk+"]] → [[" +toTalk +
									"]] created successfully!", 'success', true, true);
							}).fail(function (codetr, reslttr) {
								showStatus("Failed to create redirect! " + 
									(reslttr.error.info || (codetr + ".")),
									'error', true, true);
							});
						}
					} else { showStatus('', 'notice', true); }
				}, 250);
			} else { showStatus('', 'notice', true); }
		} else { showStatus('', 'notice', true); }
	}

	/**
	 * After successful page swap, post-move cleanup:
	 * Make talk page redirect
	 * TODO more reasonable cleanup/reporting as necessary
	 * vData.(curr|dest)IsRedir
	 */
	/** TO DO:
	 *Check if talk is self redirect
	 */
	function doPostMoveCleanup(vData, vTData, current = "currTitle", destination = "destTitle") {
		if (params.fixSelfRedirect) {// Check for self redirect
			for (const thisPage of [current, destination]){
				var otherPage = (thisPage == current) ? destination : current;
				var rData = $.ajax({
					url: mw.util.wikiScript('api'), async:false,
					error: function (jsondata) {
						showStatus("Unable to get info about " + vData[thisPage] + ".", 'error');
					},
					data: { action:'query', format:'json', redirects:'true', titles: vData[thisPage] }
				}).responseJSON.query;
				
				if (rData && rData.redirects &&
					(rData.redirects[0].from == rData.redirects[0].to ||
						(debug && rData.redirects[0].to == vData[otherPage])
					)
				) {
					var parseData = $.ajax({
						url: mw.util.wikiScript('api'), async:false,
						error: function (jsondata) {
							showStatus("Unable to fetch contents of " + vData[thisPage] + ".",
								'error');
						},
						data: {
							action:'parse', format:'json', prop:'wikitext', page: vData[thisPage]
						}
					}).responseJSON.parse;
					
					if (parseData) {
						var redirRE = new RegExp("^\\s*#REDIRECT +\\[\\[ *.* *\\]\\]", "i");
						if (parseData.wikitext['*'].search(redirRE) > -1) {
							showStatus("Retargeting redirect at [[R¬" + vData[thisPage] +
								"]] to [["	+ vData[otherPage] + "]]...");
							var retargetRedirect = {
									action:'edit',
									title: vData[thisPage],
									text: parseData.wikitext['*'].replace(redirRE,
										'#REDIRECT [['+vData[otherPage]+']]'),
									summary : "Retarget redirect to [[" +
										vData[otherPage] + "]] using " +
										config.link,
									watchlist: params.watch
								};
							if (debug) {
								showStatus("Redirect at [[R¬"+vData[thisPage]
									+"]] simulated retargeted to [["+vData[otherPage] + "]].",
									'success', false, true);
							} else {
								new mw.Api().postWithEditToken(retargetRedirect).done(function (result, jqXHR) {
									if (typeof result.edit !== 'undefined') {
										new mw.Api().get( {
											action: 'query', prop: '', redirects: 1,
											titles: result.edit.title
										}).done( function (data) {
											if (typeof data.query.redirects !== 'undefined') {
												showStatus("Redirect at [[R¬"+
													data.query.redirects[0].from + 
													"]] retargeted to [[" +
													data.query.redirects[0].to + 
													"]].", 'success', false, true);
											} else {
												console.warn("Error parsing redirects after retargeting:");
												console.warn(data);
											}
										}).fail( function (codeart, rsltart) {
											console.warn("Error fetching page after retargeting:");
											console.warn(codeart);console.warn(rsltart);
										});
									} else {
										console.warn("Error parsing result of retargeting:");
										console.warn(result);console.warn(jqXHR);
									}
								}).fail(function (codert, resultrt) {
									showStatus("Failed to retarget redirect at [[R¬"+vData[thisPage]+
									"]] to [["+vData[otherPage]+"]]. "+
									(resultrt.error.info || (codert + ".")), 'error', false, true);
								});
							}
						} else {
							showStatus("Failed to retarget redirect at [[R¬"+vData[thisPage]+
								"]] to [["+vData[otherPage]+"]]: String not found.", 'warning');
						}
					} else {
						showStatus("Failed to check contents of [[R¬"+vData[thisPage]+"]]: " + err + ".",
							'error');
					}
				}
			}	
			
			if (current == "currTitle") {
				doPostMoveCleanup(vData, vTData,"currTalkName", "destTalkName");
			} else {
				createMissingTalk(vData, vTData);
			}
		} else { //Option to fix self-redirects not selected, skipping
			createMissingTalk(vData, vTData);
		}
	}


	/**
	 * Swaps the two pages (given all prerequisite checks)
	 * Optionally moves talk pages and subpages
	 */
	function swapPages(vData, vTData) {
		if (params.currTitle.title === null || params.destTitle.title === null ||
				params.moveReason === null || params.moveReason === '') {
			showStatus("Titles are null, or move reason given was empty. Swap not done", 'error');
			return false;
		}

		var intermediateTitle = config.intermediatePrefix + params.currTitle.title;
		var pOne = { action:'move', from:params.destTitle.title, to:intermediateTitle,
			reason:"[[WP:PMRC#4|Round-robin history swap]] step 1 using " + config.link,
			watchlist:params.watch, noredirect:1 };
		var pTwo = { action:'move', from:params.currTitle.title, to:params.destTitle.title,
			reason:params.moveReason,
			watchlist:params.watch, noredirect:1 };
		var pTre = { action:'move', from:intermediateTitle, to:params.currTitle.title,
			reason:"[[WP:PMRC#4|Round-robin history swap]] step 3 using " + config.link,
			watchlist:params.watch, noredirect:1 };
		if (params.moveTalk) {
			pOne.movetalk = 1; pTwo.movetalk = 1; pTre.movetalk = 1;
		}
		if (params.moveSubpages) {
			pOne.movesubpages = 1; pTwo.movesubpages = 1; pTre.movesubpages = 1;
		}
		
		var currTitle = params.currTitle.title;
		var destTitle = params.destTitle.title;
		if (vData.destIsRedir) {currTitle = "R¬" + currTitle;}
		if (vData.currIsRedir) {destTitle = "R¬" + destTitle;}
		if (debug) {
			showStatus("Simulating round-robin history swap...");
			var completeMessage = "Round-robin history swap of [[" +
				currTitle + "]]&nbsp;([[Special:WhatLinksHere/" + 
				params.currTitle.title + "|links]]) and [[" +
				destTitle + "]]&nbsp;([[Special:WhatLinksHere/" + 
				params.destTitle.title + "|links]]) simulated successfully!";
			if (params.talkRedirect || params.fixSelfRedirect) {
				showStatus(completeMessage, 'success', false, true);
				doPostMoveCleanup(vData, vTData);
			} else {
				showStatus(completeMessage, 'success', true, true);
			}
		} else {
			showStatus("Doing round-robin history swap...");
			showStatus("Step 1 ([[" + destTitle + "]] → [[" + 
				intermediateTitle + "]])...", 'notice', false, true);
			new mw.Api().postWithEditToken(pOne).done(function (reslt1) {
				showStatus("Step 2 ([[" + currTitle + "]] → [[" + 
					destTitle + "]])...", 'notice', false, true);
				new mw.Api().postWithEditToken(pTwo).done(function (reslt2) {
					showStatus("Step 3 ([[" + intermediateTitle + "]] → [[" + 
						currTitle + "]])...", 'notice', false, true);
					new mw.Api().postWithEditToken(pTre).done(function (reslt3) {
						var completeMessage = "Round-robin history swap of [[" +
							currTitle + "]]&nbsp;([[Special:WhatLinksHere/" + 
							params.currTitle.title + "|links]]) and [[" +
							destTitle + "]]&nbsp;([[Special:WhatLinksHere/" + 
							params.destTitle.title + 
							"|links]]) completed successfully!";
						if (params.talkRedirect || params.fixSelfRedirect) {
							showStatus(completeMessage, 'success', false, true);
							doPostMoveCleanup(vData, vTData);
						} else {
							showStatus(completeMessage, 'success', true, true);
						}
					}).fail(function (code3, reslt3) {
						showStatus("Failed on third move ([[" + 
							intermediateTitle + "]] → [[" +
							params.currTitle.title + "]])! " + 
							(reslt3.error.info || (code3 + ".")),
							'error', true, true);
					});
				}).fail(function (code2, reslt2) {
					showStatus("Failed on second move ([[" + 
						params.currTitle.title + "]] → [[" + 
						params.destTitle.title + "]])! " + 
						(reslt2.error.info || (code2 + ".")),
						'error', true, true);
				});
			}).fail(function (code1, reslt1) {
				showStatus("Failed on first move ([[" + 
					params.destTitle.title + "]] → [[" +
					intermediateTitle + "]])! " + 
					(reslt1.error.info || (code1 + ".")),
					'error', true, true);
			});
		}
	}

	/**
	 * Given two titles, normalizes, does prerequisite checks for talk/subpages,
	 * prompts user for config before swapping the titles
	 */
	function roundrobin() {
		// get ns info (nsData.query.namespaces)
		params.nsData = {};
		try {
			params.nsData = $.ajax({ 
				url: mw.util.wikiScript('api'), async:false,
				error: function (jsondata) { showConfirm("Unable to get info about namespaces", 'error'); },
				data: { action:'query', format:'json', meta:'siteinfo', siprop:'namespaces' }
			}).responseJSON.query.namespaces;
		} catch (error) {
			console.error(error);
			showConfirm("Unable to get info about namespaces", 'error');
			return;
		}
		
		// validate namespaces, not identical, can move
		var vData = swapValidate();
		if (!vData.valid) { showConfirm(vData.invalidReason, 'error'); return; }
		if (vData.addlInfo !== undefined) { showConfirm(vData.addlInfo, 'warning'); }

		// subj subpages
		var currSp = getSubpages(params.nsData, vData.currTitle, vData.currNs, false);
		if (currSp.error !== undefined) { showConfirm(currSp.error, 'error'); return; }
		var currSpFlags = printSubpageInfo(vData.currTitle, currSp);
		var destSp = getSubpages(params.nsData, vData.destTitle, vData.destNs, false);
		if (destSp.error !== undefined) { showConfirm(destSp.error, 'error'); return; }
		var destSpFlags = printSubpageInfo(vData.destTitle, destSp);

		var vTData = talkValidate(vData.checkTalk, vData.currTalkName, vData.destTalkName);

		// future goal: check empty subpage DESTINATIONS on both sides (subj, talk)
		//   for create protection. disallow move-subpages if any destination is salted
		var currTSp = getSubpages(params.nsData, vData.currTitle, vData.currNs, true);
		if (currTSp.error !== undefined) { showConfirm(currTSp.error, 'error'); return; }
		var currTSpFlags = printSubpageInfo(vData.currTalkName, currTSp);
		var destTSp = getSubpages(params.nsData, vData.destTitle, vData.destNs, true);
		if (destTSp.error !== undefined) { showConfirm(destTSp.error, 'error'); return; }
		var destTSpFlags = printSubpageInfo(vData.destTalkName, destTSp);

		var noSubpages = currSpFlags.noNeed && destSpFlags.noNeed &&
			currTSpFlags.noNeed && destTSpFlags.noNeed;
		// If one ns disables subpages, other enables subpages, AND HAS subpages,
		//   consider abort. Assume talk pages always safe (TODO fix)
		var subpageCollision = (vData.currNsAllowSubpages && !destSpFlags.noNeed) ||
			(vData.destNsAllowSubpages && !currSpFlags.noNeed);

		// TODO future: currTSpFlags.allowMoveSubpages && destTSpFlags.allowMoveSubpages
		// needs to be separate check. If talk subpages immovable, should not affect subjspace
		if (!subpageCollision && !noSubpages && vData.allowMoveSubpages &&
				(currSpFlags.allowMoveSubpages && destSpFlags.allowMoveSubpages) &&
				(currTSpFlags.allowMoveSubpages && destTSpFlags.allowMoveSubpages)) {
			console.log("Subpage move check OK");
		} else if (subpageCollision) {
			params.moveSubpages = false;
			showConfirm("One namespace does not have subpages enabled. Disallowing move subpages",
				'warning');
		}
		
		if (params.moveSubpages) { 
			params.allSpArr = currSpFlags.spArr.concat(
				destSpFlags.spArr,
				currTSpFlags.spArr,
				destTSpFlags.spArr
			);
		} else {
			params.allSpArr = [];
		}
		
		// TODO: count subpages and make restrictions?
		if (vData.checkTalk && (!vTData.currTDNE || !vTData.destTDNE || params.moveSubpages)) {
			if (vTData.allowMoveTalk) {
				console.log("Talk move check OK");
			} else {
				params.moveTalk = false;
				showConfirm("Disallowing moving talk. " +
					(!vTData.currTCanCreate ? (vData.currTalkName + " is create-protected. ")
					: (!vTData.destTCanCreate ? (vData.destTalkName + " is create-protected. ")
					: "Talk page is immovable.")), 'warning');
			}
		}
		
		var currTitle = params.currTitle.title;
		var destTitle = params.destTitle.title;
		if (vData.currIsRedir) {currTitle = "R¬" + currTitle;}
		if (vData.destIsRedir) {destTitle = "R¬" + destTitle;}
		showConfirm("Swapping [["+currTitle+"]] → [["+destTitle+"]]");
		showConfirm("Reason: "+params.moveReason);
		if (debug) {
			showConfirm("Move talk: "+params.moveTalk+", Move subpages: "+params.moveSubpages);
			showConfirm("Talk redirect: "+params.talkRedirect+
				", Fix self-redirect: "+params.fixSelfRedirect);
		}
		if (params.moveSubpages) {
			if (params.allSpArr.length > 0) {
				var subpagesToMoveList = params.allSpArr.length+" subpages to move:"+
				"<ul><li>[[" + params.allSpArr.join("]]</li><li>[[") + "]]</li></ul>";
				//showConfirm(subpagesToMoveList);
			} else {
				showConfirm("No subpages found to move.");
			}
		}
		
		showConfirm('', 'notice', true);
		psSwap.setDisabled(false).setLabel(config.confirmButton).off('click').on('click', function() {
				psSwap.setDisabled(true).setLabel(config.swapButton);
				swapPages(vData, vTData);
			});
	}

	function titleInput(title) {
		var nsObj = {value: title.ns || 0, $overlay: true};
		var tObj = {value: title.title || '', $overlay: true};
		if (typeof title.ns !== 'undefined' && typeof title.title !== 'undefined') {
			var re = '^'+mw.config.get("wgFormattedNamespaces")[title.ns]+':';
			tObj.value = title.title.replace(new RegExp(re),'');
		}
		return new mw.widgets.ComplexTitleInputWidget({namespace: nsObj, title: tObj});
	}
	
	function assembleTitle(field) {
		if (field.namespace.value == 0) { return {ns: 0, title: field.title.value}; }
		return {
			ns: field.namespace.value,
			title: mw.config.get("wgFormattedNamespaces")[field.namespace.value]+":"+field.title.value
		};
	}
	
	/**
	 * Determine namespace of title
	 */
	function psParseTitle(title) {
		var ns, titleMain;
		title = title.replace("_"," ");
		for (var k in mw.config.get("wgFormattedNamespaces")) {
			var nsName = mw.config.get("wgFormattedNamespaces")[k],
				match = title.match(new RegExp("^"+nsName+":(.*)$","i"));
			if (match) {
				ns = k;
				titleMain = match[1];
				break;
			}
		}
		var ret = {ns: (ns || 0), title: title, titleMain: (titleMain || title)};
		return ret;
	}
	
	/**
	 * If user is able to perform swaps
	 */
	function checkUserPermissions() {
		var ret = {};
		ret.canSwap = true;
		var reslt = $.ajax({
			url: mw.util.wikiScript('api'), async:false,
			error: function (jsondata) { 
				mw.notify("Swapping pages unavailable.", { title: 'Page Swap Error', type: 'error' });
				return ret;
			},
			data: { action:'query', format:'json', meta:'userinfo', uiprop:'rights' }
		}).responseJSON.query.userinfo;
	
		// check userrights for suppressredirect and move-subpages
		var rightslist = reslt.rights;
		ret.canSwap =
				$.inArray('suppressredirect', rightslist) > -1 &&
				$.inArray('move-subpages', rightslist) > -1;
		ret.allowSwapTemplates =
				$.inArray('templateeditor', rightslist) > -1;
		
		
		return ret;
	}
	
	/**
	 * Script execution starts here:
	 */
	//Read the old title from the URL or the relevant pagename
	params.currTitle.title = mw.util.getParamValue("wpOldTitle") || mw.config.get("wgRelevantPageName");
	if (document.getElementsByName("wpOldTitle")[0] &&
		document.getElementsByName("wpOldTitle")[0].value != ''
	){
		//If the hidden form field element has a value, use that instead
		params.currTitle.title = document.getElementsByName("wpOldTitle")[0].value;
	}
	//Parse out title and namespace
	params.currTitle = psParseTitle(params.currTitle.title);
	
	//Read the new title from the URL or make it blank
	params.destTitle.title = mw.util.getParamValue("wpNewTitle") || '';
	//Parse out title and namespace
	params.destTitle = psParseTitle(params.destTitle.title);
	if (document.getElementsByName("wpNewTitleMain")[0] &&
		document.getElementsByName("wpNewTitleMain")[0].value != '' &&
		document.getElementsByName("wpNewTitleNs")[0]
	){
		//If the Move page form exists, use the values from that instead
		params.destTitle.title = document.getElementsByName("wpNewTitleMain")[0].value;
		params.destTitle.ns = document.getElementsByName("wpNewTitleNs")[0].value;
		if (params.destTitle.ns != 0) {
			params.destTitle.title = mw.config.get("wgFormattedNamespaces")[params.destTitle.ns] + 
				":" + params.destTitle.title;
		}
	}
	
	params.uPerms = checkUserPermissions();
	
	if (!params.uPerms.canSwap) {
		mw.loader.using( [ 'mediawiki.notification' ], function () {
			mw.notify("User rights insufficient for action.", { title: 'Page Swap Error', type: 'error' });
			return;
		} );
	}
	
	$( '#firstHeading' ).text(function(i, t) {return t.replace('Move', 'Swap');});
	document.title = document.title.replace("Move", "Swap");
	$( '#movepagetext' ).html( wikiLink(config.introText) );
		
	var reasonList = [];
	if ($( '#wpReasonList' )[0]) {
		reasonList.push({
			data: $( '#wpReasonList' ).children("option").get(0).value,
			label: $( '#wpReasonList' ).children("option").get(0).text
		});
		reasonList.push({optgroup: $( '#wpReasonList' ).children("optgroup").get(0).label});
		$( '#wpReasonList' ).children("optgroup").children("option").get().forEach(
			option => reasonList.push({data: option.value, label: option.text})
		);
	}
	
	var psFieldset = new OO.ui.FieldsetLayout({
			label: 'Swap page', classes: ['container'], id: 'psFieldset'
		}),
		psOldTitle = titleInput(params.currTitle),
		psNewTitle = titleInput(params.destTitle),
		psReasonList = new OO.ui.DropdownInputWidget({
			options: reasonList, id: 'psReasonList', $overlay: true
		}),
		psReasonOther = new OO.ui.TextInputWidget({value: moveReason, id: 'psReasonOther'}),
		psMovetalk = new OO.ui.CheckboxInputWidget({selected: params.defaultMoveTalk, id: 'psMovetalk'}),
		psMoveSubpages = new OO.ui.CheckboxInputWidget({selected: true, id: 'psMoveSubpages'}),
		psTalkRedirect = new OO.ui.CheckboxInputWidget({selected: params.cleanup, id: 'psTalkRedirect'}),
		psFixSelfRedirect = new OO.ui.CheckboxInputWidget({selected: params.cleanup, id: 'psFixSelfRedirect'}),
		psWatch = new OO.ui.CheckboxInputWidget({selected: false, id: 'psWatch'}),
		psConfirm = new OO.ui.MessageWidget({type: 'notice', showClose: false, id: 'psConfirm'}),
		psSwap = new OO.ui.ButtonInputWidget({
			label: config.swapButton,
			disabled: true, framed: true,
			flags: ['primary','progressive'],
			id: 'psSwap'
		}),
		psStatus = new OO.ui.MessageWidget({type: 'notice', showClose: true, id: 'psStatus'}),
		psContribsButton = new OO.ui.ButtonWidget({
			label: 'Open contribs page', title: 'Special:MyContributions',
			href: mw.config.get("wgServer") +
				mw.config.get("wgArticlePath").replace("$1", "Special:MyContributions"),
			framed: true, flags: ['primary', 'progressive'],
			id: 'psContribsButton', target: '_blank'
		});
	
	psFieldset.addItems( [
		new OO.ui.FieldLayout(psOldTitle, {align: 'top', label: 'Old title:', id: 'psOldTitle'}),
		new OO.ui.FieldLayout(psNewTitle, {align: 'top', label: 'New title:', id: 'psNewTitle'}),
		new OO.ui.FieldLayout(psReasonList, {align: 'top', label: 'Reason:'}),
		new OO.ui.FieldLayout(psReasonOther, {align: 'top', label: 'Other/additional reason:'}),
		new OO.ui.FieldLayout(psMovetalk, {align: 'inline',
			label: 'Move associated talk page',
			title: 'Move associated talk page'
		}),
		new OO.ui.FieldLayout(psMoveSubpages, {align: 'inline',
			label: 'Move subpages',
			title: 'Move up to 100 subpages of the source and/or target pages'
		}),
		new OO.ui.FieldLayout(psTalkRedirect, {align: 'inline',
			label: 'Leave a redirect to new talk page if needed',
			title: 'If one of the pages you\'re swapping has a talk page and ' +
				'the other doesn\'t, create a redirect from the missing talk ' +
				'page to the new talk page location. This is useful when ' +
				'swapping a page with its redirect so that links to the old ' +
				'talk page will continue to work.'
		}),
		new OO.ui.FieldLayout(psFixSelfRedirect, {align: 'inline',
			label: 'Fix self-redirects',
			title: 'When swapping a page with its redirect, update the ' +
				'redirect to point to the new page name so that it is not ' +
				'pointing to itself. This will not update redirects on subpages.'
		}),
		new OO.ui.FieldLayout(psWatch, {align: 'inline',
			label: 'Watch source page and target page',
			title: 'Add both source page and target page to your watchlist'
		}),
		new OO.ui.FieldLayout(psConfirm, {}),
		new OO.ui.FieldLayout(psSwap, {}),
		new OO.ui.FieldLayout(psStatus, {}),
		new OO.ui.FieldLayout(psContribsButton, {})
		]);
	
	function checkTitles() {
		if (psOldTitle.namespace.value%2==1 || psNewTitle.namespace.value%2==1) {
			if (psMovetalk.isDisabled() == false) {
				psMovetalk.setDisabled(true);
				params.defaultMoveTalk = psMovetalk.isSelected();
				psMovetalk.setSelected(false);
			}
		} else if (psMovetalk.isDisabled()) {
			psMovetalk.setDisabled(false);
			psMovetalk.setSelected(params.defaultMoveTalk);
		}
		psConfirm.toggle(false).setType('notice');
		params.currTitle = assembleTitle(psOldTitle);
		params.destTitle = assembleTitle(psNewTitle);
		var titlesMatch = (params.currTitle.title==params.destTitle.title);
		psOldTitle.title.setValidityFlag(psOldTitle.title.value!='' && !titlesMatch );
		psNewTitle.title.setValidityFlag(psNewTitle.title.value!='' && !titlesMatch );
		psSwap.setLabel(config.swapButton).off('click').on('click', clickSwap 
			).setDisabled(psOldTitle.title.value=='' || psNewTitle.title.value=='' || titlesMatch );
	}
	
	function clickSwap() {
		psConfirm.toggle(false).setType('notice');
		psStatus.toggle(false).setType('notice');
		psSwap.setDisabled(true);
		Object.assign(params, params, {
			confirmMessages: [],
			statusMessages: [],
			currTitle: assembleTitle(psOldTitle),
			destTitle: assembleTitle(psNewTitle),
			moveReason: psReasonOther.value,
			moveTalk: psMovetalk.isDisabled() ? false : psMovetalk.selected,
			moveSubpages: psMoveSubpages.selected,
			talkRedirect: psTalkRedirect.selected,
			fixSelfRedirect: psFixSelfRedirect.selected,
			watch: psWatch.selected ? 'watch' : 'unwatch',
		});
		
		if (psReasonList.value != 'other') {
			params.moveReason = psReasonList.value +
				(psReasonOther.value == '' ? '' : '. ' + psReasonOther.value); 
		} else if (psReasonOther.value == '') {
			params.moveReason = 'Swap [[' + params.currTitle.title + ']] and [[' +
				params.destTitle.title + ']] ([[WP:SWAP]])';
		}

		roundrobin();
	}

	checkTitles();

	/**
	 * Re-check form on any change
	 */
	psOldTitle.namespace.off('change').on( 'change', checkTitles );
	psOldTitle.title.setValidation( function(v) {
		checkTitles(); return (v!='' && params.currTitle.title!=params.destTitle.title);
	} );
	psNewTitle.namespace.off('change').on( 'change', checkTitles );
	psNewTitle.title.setValidation( function(v) {
		checkTitles(); return (v!='' && params.currTitle.title!=params.destTitle.title);
	} );
	psReasonList.off('change').on( 'change', checkTitles );
	psReasonOther.off('change').on( 'change', checkTitles );
	psMovetalk.off('change').on( 'change', checkTitles );
	psMoveSubpages.off('change').on( 'change', checkTitles );
	psTalkRedirect.off('change').on( 'change', checkTitles );
	psFixSelfRedirect.off('change').on( 'change', checkTitles );
	psWatch.off('change').on( 'change', checkTitles );
	
	/**
	 * Set button and status field actions
	 */
	psSwap.off('click').on( 'click', clickSwap );
	psStatus.off('close').on( 'close', function() {
		params.statusMessages = [];
		psStatus.setType('notice');
		psContribsButton.toggle(false);
	} ).off('toggle').on( 'toggle', function() {
		if (!psStatus.isVisible()) {
			params.statusMessages = [];
			psStatus.setType('notice');
			psContribsButton.toggle(false);
		}
	} );
	psConfirm.toggle(false);
	psStatus.toggle(false);
	
	$( '#movepage' ).hide(); //hide old form
	$( '#movepage-loading' ).remove(); //remove loading message
	$( "div.mw-message-box-error" ).hide(); //hide error message
	$( '#psFieldset' ).remove(); //remove old form if script started twice
	$( "div.movepage-wrapper" ).prepend( psFieldset.$element ); //add swap form
	if( !$( '#psFieldset' ).length ){ //something went wrong
		mw.notify(config.errorMsg, {type: 'error', title: "Error:" });
		document.getElementById("mw-movepage-table").style.display="block";
		$( '#movepage' ).show();
		$( "div.mw-message-box-error" ).show(); 
	}

	return true;
}