User:BilledMammal/MovePlus.js

//movePlus // var movePlus = { numberOfMoves: 0, multiMove: false, destinations: [], parsedDate: undefined, pages: [], templateIndex: -1, moveQueue: [], editQueue: [], linkAdjustWarning: '\t Warning: This will automatically update pages, retargeting all links from the current value to the value you specify. You take full responsibility for any action you perform using this script.' }; window.movePlus = movePlus;

$.when(	mw.loader.using([ 'mediawiki.api', 'ext.gadget.morebits', 'ext.gadget.libExtraUtil' ]),	$.ready ).then(function {	if (document.getElementById("requestedmovetag") !== null && Morebits.pageNameNorm.indexOf("alk:") !== -1 && mw.config.get('wgCategories').includes('Requested moves') && !document.getElementById("wikiPreview") && mw.config.get('wgDiffOldId') == null) {		document.getElementById("requestedmovetag").innerHTML = "Close Relist Notify WikiProjects 20){this.size=this.value.length} else{this.size=20}'/> Confirm relist Cancel relist ";		$('#movePlusClose').click(movePlus.callback);		$('#movePlusRelist').click(movePlus.confirmRelist);		$('#movePlusConfirm').click(movePlus.relist); $('#movePlusCancel').click(movePlus.cancelRelist); $('#movePlusNotify').click(movePlus.notify); }	var portletLink = mw.util.addPortletLink("p-cactions", "#movePlusMove", "Move\+",		"ca-movepages", "Move pages (expanded options)");

$( portletLink ).click(movePlus.displayWindowMove);

});

movePlus.confirmRelist = function movePlusConfirmRelist(e) { if (e) e.preventDefault; document.getElementById("movePlusRelistOptions").style.display = "inline"; document.getElementById("movePlusClose").style.display = "none"; document.getElementById("movePlusRelist").style.display = "none"; document.getElementById("movePlusNotify").style.display = "none"; };

movePlus.cancelRelist = function movePlusCancelRelist(e) { if (e) e.preventDefault; document.getElementById("movePlusRelistOptions").style.display = "none"; document.getElementById("movePlusClose").style.display = "inline"; document.getElementById("movePlusRelist").style.display = "inline"; document.getElementById("movePlusNotify").style.display = "inline"; };

movePlus.advert = ' using Move+';

movePlus.preEvaluate = async function { try { const talkPageContent = await loadTalkPage; return extractTemplateData(talkPageContent); } catch (error) { console.error('Error during pre-evaluation:', error); } };

async function loadTalkPage {

var title_obj = mw.Title.newFromText(Morebits.pageNameNorm); const talkpage = new Morebits.wiki.page(title_obj.getTalkPage.toText, 'Retrive move proposals.'); return new Promise((resolve, reject) => {		talkpage.load(function(talkpage) { if (talkpage.exists) { resolve(talkpage.getPageText); } else { reject('Page does not exist'); }		}, reject);	}); }

function extractTemplateData(text) { const templatesOnPage = extraJs.parseTemplates(text, false); let templateData = {};

templatesOnPage.forEach(template => {		if (template.name.toLowerCase === "requested move/dated") {			templateData = { ...templateData, ...parseRequestedMoveTemplate(template) };		}	});

return templateData; }

function parseRequestedMoveTemplate(template) { const data = { moves: [], multiMove: template.parameters.some(param => param.name === "multiple") };

const pairs = {};

template.parameters.forEach(param => {		const match = param.name.toString.match(/^(current|new)?(\d+)$/);		if (match) {			const type = match[1] ? match[1] : "new";			const index = match[2];

if (!pairs[index]) { pairs[index] = {}; }

if (!pairs[index][type] || param.value != "") { pairs[index][type] = param.value; }		} 	});	if(!pairs[1]["current"]) { 		let title_obj = mw.Title.newFromText(Morebits.pageNameNorm);		pairs[1]["current"] = title_obj.getSubjectPage.toText;	}

Object.keys(pairs).forEach(index => {		const pair = pairs[index];		if (pair.current && pair.new) {			data.moves.push({current: pair.current, destination: pair.new});		}	});

return data; }

movePlus.callback = async function movePlusCallback(e) { e.preventDefault(e);

try { const evaluationData = await movePlus.preEvaluate; if (evaluationData) { movePlus.displayWindowClose(evaluationData); } else { throw new Error("Failed to retrieve necessary data for processing."); }	} catch (error) { console.error('Error during callback execution:', error); } };

movePlus.displayWindowClose = function movePlusDisplayWindowClose(data) {

let checkboxStates = {};

movePlus.Window = new Morebits.simpleWindow(600, 450); movePlus.Window.setTitle( "Close requested move" ); movePlus.Window.setScriptName('Move+'); movePlus.Window.addFooterLink('RM Closing instruction', 'WP:RMCI'); movePlus.Window.addFooterLink('Script documentation', 'User:BilledMammal/Move+'); movePlus.Window.addFooterLink('Give feedback', 'User talk:BilledMammal/Move+'); var form = new Morebits.quickForm(function(e) {		movePlus.evaluate(e, data);	}); setupForm; function setupForm { var resultContainer = form.append({			type: 'div',			style: 'display: flex; flex-direction: row; gap: 10px;'		}); var resultField = setupResultOptions(resultContainer); setupCustomResult(resultField); var movedOptionsField = setupMoveOptions(resultContainer); setupCustomTitles; setupClosingComment(form); }	function setupResultOptions(container) { var resultField = container.append({			type: 'field',			label: 'Result',			style: 'flex: 1;'		});

resultField.append({			type: 'radio',			name: 'result',			required: true,			list: [				{					label: 'Moved',					value: 'moved',					event: function { updateResultOptions('moved'); }				},				{					label: 'Not moved',					value: 'not moved',					event: function { updateResultOptions('not moved'); }				},				{					label: 'No consensus',					value: 'no consensus',					event: function { updateResultOptions('no consensus'); }				},				{					label: 'Custom',					value: 'custom',					event: function { updateResultOptions('custom'); }				}			]		}); return resultField; }	function updateResultOptions(result) { const customResultDisplay = document.getElementsByName('customResult')[0]; const movedOptionsDisplay = document.getElementsByName('movedOptionsField')[0]; const customTitlesDisplay = document.getElementById('customTitles'); const checkboxes = document.querySelectorAll('input[name="movedOptionsInputs"]');

// Default settings customResultDisplay.style.display = 'none'; customResultDisplay.required = false; movedOptionsDisplay.style.display = 'none'; customTitlesDisplay.style.display = 'none'; // Unset move options if (result != 'moved') { checkboxes.forEach(checkbox => {				if (checkbox.checked) {						checkboxStates[checkbox.value] = true;						checkbox.checked = false;						const event = new Event('change');						checkbox.dispatchEvent(event);				} else {					checkboxStates[checkbox.value] = false;				}			}); }

switch (result) { case 'moved': movedOptionsDisplay.style.display = 'block'; // Reset move options checkboxes.forEach(checkbox => {					if (checkboxStates[checkbox.value]) {						checkbox.checked = true;						const event = new Event('change');						checkbox.dispatchEvent(event);					}				}); break; case 'custom': customResultDisplay.style.display = 'inline'; customResultDisplay.required = true; break; }	}	function setupMoveOptions(container) { let originalClosingComment = ''; const movedOptionsField = container.append({			type: 'field',			label: 'Specify move type',			style: 'display: none; flex: 1;',			name: 'movedOptionsField'		});

movedOptionsField.append({			type: 'checkbox',			name: 'movedOptionsInputs',			list: [				{				label: 'Close as uncontested',					value: 'moved-uncontested',					tooltip: 'We treat discussions where no objections have been raised, but community support has also not been demonstrated, as uncontested technical requests.',					event: function(event) {						const closingComment = document.getElementsByName('closingComment')[0];						if (event.target.checked) {							originalClosingComment = closingComment ? closingComment.value : '';							closingComment.value = 'Moved as an uncontested request with minimal participation. If there is any objection within a reasonable time frame, please ask me to reopen the discussion; if I am not available, please ask at the technical requests page.';						} else {							closingComment.value = originalClosingComment; }					}				},				{					label: 'Specify different titles', value: 'moved-different-title', tooltip: 'If no title was origionally proposed, or if there is a consensus to move to a title other than that which was origionally proposed.', event: function { if (event.target.checked) { customTitles.style.display = 'block'; } else { customTitles.style.display = 'none'; }					}				}			]		});		return movedOptionsField;	}	function setupCustomResult(resultField) {		resultField.append({ type: 'input', name: 'customResult', style: 'display: none;' });	}

function setupCustomTitles { const customTitles = form.append({			type: 'field',			label: 'Specify titles',			id: 'customTitles',			name: 'customTitles',			style: 'display: none;'		}); data.moves.forEach((pair, index) => {			const titleField = customTitles.append({ type: 'div', className: 'customTitleInput', style: 'display: flex; align-items: center; margin-bottom: 5px;' });

titleField.append({				type: 'div',				style: 'flex: 0 1 47.5%; text-align: left;',				label: pair.current			});

titleField.append({				type: 'div',				style: 'flex: 0 1 5%; text-align: center;',				label: '→'			});

const inputDiv = titleField.append({				type: 'div',				style: 'flex: 1;'			});

inputDiv.append({				type: 'input',				name: pair.current,				value: pair.destination,				style: 'width: 95%; text-align: left;'			}); });		const toggleButton = customTitles.append({ type: 'button', label: 'Hide titles', event: function(event) { const titleInputs = document.querySelectorAll('.customTitleInput'); const button = event.target; titleInputs.forEach(input => {					if (input.style.display === 'none' || input.style.display === '') {						input.style.display = 'flex';						button.value = 'Hide titles';					} else {						input.style.display = 'none';						button.value = 'Show titles';					}				}); }		});

}

function setupClosingComment(form) { const closingCommentField = form.append({			type: 'field',			label: 'Closing comment'		});

closingCommentField.append({			type: 'textarea',			name: 'closingComment'		}); }	form.append({ type: 'submit', label: 'Submit' });

var formResult = form.render; movePlus.Window.setContent(formResult); movePlus.Window.display;

};

movePlus.displayWindowMove = function movePlusDisplayWindowMove {

var title_obj = mw.Title.newFromText(Morebits.pageNameNorm); movePlus.title = title_obj.getSubjectPage.toText; movePlus.displayWindowInit; movePlus.displayWindowAction; }

movePlus.displayWindowInit = function movePlusDisplayWindowInit {

movePlus.Window = new Morebits.simpleWindow(600, 450); movePlus.Window.setScriptName('Move+'); movePlus.Window.addFooterLink('Moving instructions', 'Wikipedia:Moving a page'); movePlus.Window.addFooterLink('Script documentation', 'User:BilledMammal/Move+'); movePlus.Window.addFooterLink('Give feedback', 'User talk:BilledMammal/Move+'); }

movePlus.displayWindowAction = function movePlusDisplayWindowAction {

var moveData = [{ current: movePlus.title, target: '' }];	var retargetData = [{ current: '', target: '' }];	var config = { move: { data: moveData, reason: '', label: 'Specify moves', reasonLabel: 'Move reason', reasonName: 'moveReason', actionLimit: 100, buttonLabel: 'Add move', title: 'Move pages', information: '' },		retarget: { data: retargetData, reason: '', label: 'Specify link retargets', reasonLabel: 'Link retarget reason', reasonName: 'retargetReason', actionLimit: 2, buttonLabel: 'Add retarget', title: 'Retarget links', information: movePlus.linkAdjustWarning }	};	function updateForm(action) { movePlus.Window.setTitle(config[action].title); function updateActionDataFromForm { config[action].data = []; config[action].reason = document.querySelector(`textarea[name="${config[action].reasonName}"]`).value; var currentInputs = document.querySelectorAll('input[name="curr"]'); var targetInputs = document.querySelectorAll('input[name="dest"]');

currentInputs.forEach((input, index) => {				config[action].data.push({ current: input.value, target: targetInputs[index].value });			});		};		var form = new Morebits.quickForm(function(e) {			e.preventDefault;			movePlus.params = Morebits.quickForm.getInputData(e.target);

var currentPages = []; var targetPages = []; $('input[name="curr"]').each(function(index) {				var currentPage = $(this).val;				var targetPage = $('input[name="dest"]').eq(index).val;

if (currentPage && targetPage) { currentPages.push(currentPage); targetPages.push(targetPage); }			});			if (action == 'move') {				movePlus.movePages(currentPages, targetPages, movePlus.params.moveReason, false);			}			if (action == 'retarget') {				movePlus.retargetLinks(currentPages, targetPages, movePlus.params.retargetReason);			}		}); movePlus.appendOptions(form, action, updateForm, updateActionDataFromForm);

var actionsContainer = form.append({			type: 'field',			label: config[action].label,			id: 'actionList',			name: 'actionList'		});

config[action].data.forEach((data, index) => {			const titleField = actionsContainer.append({ type: 'div', className: 'titleInput', style: 'display: flex; align-items: center; margin-bottom: 5px;' });			const currentDiv = titleField.append({ type: 'div', style: 'flex: 0 1 47.5%; text-align: left;' });

currentDiv.append({				type: 'input',				name: 'curr',				value: data.current,				placeholder: 'Current page',				required: true,				style: 'width: 95%; text-align: left;'			});

titleField.append({				type: 'div',				style: 'flex: 0 1 5%; text-align: center;',				label: '→'			}); const destDiv = titleField.append({				type: 'div',				style: 'flex: 0 1 47.5%; text-align: left;'			});

destDiv.append({				type: 'input',				name: 'dest',				value: data.target,					required: true,				placeholder: 'Target page',				style: 'width: 95%; text-align: left;'			});

titleField.append({				type: 'button',				label: 'Remove',				disabled: config[action].data.length < 2 ? true : false,				event: function {					updateActionDataFromForm;					config[action].data.splice(index, 1);					updateForm(action);				}			}); });

actionsContainer.append({			type: 'button',			label: config[action].buttonLabel,			disabled: config[action].data.length < config[action].actionLimit ? false : true,			event: function {				updateActionDataFromForm;				config[action].data.push({ current: , target:  });				updateForm(action);			}		});

movePlus.appendReason(form, config[action].reasonLabel, config[action].reasonName, config[action].reason); form.append({			type: 'div',			label: config[action].information,			style: 'margin-left: 15px; margin-right: 15px;'		});

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

var formResult = form.render; movePlus.Window.setContent(formResult); movePlus.appendReasonAlert(config[action].reasonLabel, config[action].reasonName); movePlus.Window.display; }

updateForm('move'); }

movePlus.retargetLinks = async function movePlusRetargetLinks(currentLinks, targetLinks, reason) { var form = new Morebits.quickForm; var actionContainer = form.append({		type: 'field',		label: 'Retargeting'	}); actionContainer.append({		type: 'div',		className: 'movePlusProgressBox',		label: ''	}); var multiple = currentLinks[1] ? true : false; var config = { currTarget: targetLinks[0], destTarget: multiple ? targetLinks[1] : '' }	var formResult = form.render; movePlus.Window.setContent(formResult); movePlus.Window.display; const progressBox = document.querySelector('.movePlusProgressBox'); movePlus.linkEditSummary = reason + ': '; await movePlus.correctLinks(currentLinks[0], multiple ? currentLinks[1] : '', config, progressBox); progressBox.innerText = 'Done.' setTimeout(function{ movePlus.Window.close; }, 1250);

}

movePlus.appendOptions = function movePlusAppendOptions(form, action, updateForm, updateActionDataFromForm) {

var optionsContainer = form.append({		type: 'field',		label: 'Options',		style: 'display: flex; flex-direction: row;'	}); optionsContainer.append({		type: 'button',		label: 'Move pages',		name: 'movePages',		disabled: action == 'move' ? true : false,		event: function {			updateActionDataFromForm;			updateForm('move');		}	}); optionsContainer.append({		type: 'button',		label: 'Retarget page links',		name: 'retargetLinks',		disabled: action == 'retarget' ? true : false,		event: function {			updateActionDataFromForm;			updateForm('retarget');		}	});

}

movePlus.appendReason = function movePlusAppendReason(form, label, name, value) { var moveReason = form.append({		type: 'field',		label: label	}); moveReason.append({		type: 'textarea',		name: name,		value: value,			required: true	}); moveReason.append({		type: 'div',		name: name + 'Alert',		style: 'display: block',		label: ''	}); }

movePlus.appendReasonAlert = function appendReasonAlert(label, name) {

const reasonAlert = document.getElementsByName(name + 'Alert')[0]; $(`textarea[name="${name}"]`).on('input', function {		if (this.value.length > 400) {			reasonAlert.innerHTML = ` Warning: ${label} contains ${this.value.length} characters. It may be truncated in the edit summary.`;			reasonAlert.style.display = 'block';		} else {			reasonAlert.style.display = 'none';		}	});

}

movePlus.evaluate = function(e, data) { var form = e.target; movePlus.params = Morebits.quickForm.getInputData(form);

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

var title_obj = mw.Title.newFromText(Morebits.pageNameNorm); movePlus.title = title_obj.getSubjectPage.toText; movePlus.talktitle = title_obj.getTalkPage.toText; var result = movePlus.params.result; if(result == 'custom'){ result = movePlus.params.customResult; }	var closingComment = movePlus.params.closingComment; if(closingComment != ""){ closingComment = ' ' + closingComment; closingComment = closingComment.replace(/\|/g, "|"); closingComment = closingComment.replace(/=/g, "="); }	if (movePlus.params.movedOptionsInputs.includes('moved-different-title')) { data.moves.forEach(function(pair, index) {			if (movePlus.params[pair.current]) {				data.moves[index].destination = movePlus.params[pair.current];			}		}); }	var talkpage = new Morebits.wiki.page(movePlus.talktitle, 'Closing move.'); talkpage.load(function(talkpage) {		var text = talkpage.getPageText;		var templatesOnPage = extraJs.parseTemplates(text,false);		var oldMovesPresent = [];		var template;		for (var i = 0; i < templatesOnPage.length; i++) {			if (templatesOnPage[i].name.toLowerCase == "old moves" || templatesOnPage[i].name.toLowerCase == "old move") {				oldMovesPresent.push(templatesOnPage[i]);			} else if (templatesOnPage[i].name.toLowerCase == "requested move/dated") {				template = templatesOnPage[i];			}		}

var templateFound = false; var numberOfMoves = 0; var line; var templateIndex = -1; var parsedDate; var rmSection; var nextSection = false; var textToFind = text.split('\n'); for (var i = 0; i < textToFind.length; i++) { line = textToFind[i]; if(templateFound == false){ if(/{{[Rr]equested move\/dated/.test(line)){ templateFound = true; templateIndex = i;				} } else if(templateFound == true){ if (/ \(UTC\)/.test(line)){ line = line.substring(line.indexOf("This is a contested technical request")); parsedDate = line.match(/, ([0-9]{1,2} (January|February|March|April|May|June|July|August|September|October|November|December) [0-9]{4}) \(UTC\)/)[1]; break; } else if(/→/.test(line)){ numberOfMoves++; }			}		}

for (var i = templateIndex; i >= 0; i--) { line = textToFind[i]; if (line.match(/^(==)[^=].+\1/)) { rmSection = line.match(/^(==)[^=](.+)\1/)[2].trim; break; }		}

for (var i = templateIndex+1; i < textToFind.length; i++) { line = textToFind[i]; if (line.match(/^(==)[^=].+\1/)) { nextSection = true; var escapedLine = line.replace(/[-[\]{}*+?.,\\^$|#\s]/g, '\\$&') var regex = new RegExp('(' + escapedLine + ')(?![\s\S]*(' + escapedLine + '))', 'm'); text = text.replace(regex, '\n\n' + line); break; }		}		var userGroupText = ""; if(Morebits.userIsInGroup('sysop')){ userGroupText = ""; } else if(Morebits.userIsInGroup('extendedmover')){ userGroupText = "|pmc=y"; } else{ userGroupText = "|nac=y"; }		text = text.replace(//, "" + result + "." + closingComment + userGroupText +"");

if (!nextSection) { text += '\n'; }		var multiMove = data.multiMove; var moveSectionPlain = rmSection;

var date = parsedDate; var from = '';

var destination = data.moves[0].destination if(destination == "?"){ destination = ""; }

var link = 'Special:Permalink/' + talkpage.getCurrentID + '#' + moveSectionPlain;

var archives = text.match(/{{[Aa]rchives/); if(archives == null){ archives = text.match(/{{[Aa]rchive box/); if(archives == null){ archives = text.match(/{{[Aa]rchivebox/); if(archives == null){ archives = text.match(/==.*==/); }			}		}

if (oldMovesPresent.length == 0) { if(result == "moved"){ from = '|from=' + movePlus.title; }			text = text.replace(archives[0], '{{old move'+ '|date=' + date + from + '|destination=' + destination + '|result=' + result + '|link=' + link +'}}\n\n' + archives[0]); } else if (oldMovesPresent.length == 1) { var isValidFormat = false; var isListFormat = false; var numOldMoves = 0; for (var i = 0; i < oldMovesPresent[0].parameters.length; i++) { var parameterName = oldMovesPresent[0].parameters[i].name; parameterName = parameterName.toString; if (parameterName == "list") { isListFormat = true; break; } else if (parameterName == "result1") { isValidFormat = true; numOldMoves++; } else if (parameterName.includes("result")) { numOldMoves++; }			}

if (isValidFormat && !isListFormat) { var oldMovesText = oldMovesPresent[0].wikitext; numOldMoves++; if(result == "moved"){ from = '|from' + numOldMoves + '=' + movePlus.title; }				var newTextToAdd = '|date' + numOldMoves + '=' + date + from + '|destination' + numOldMoves + '=' + destination + '|result' + numOldMoves + '=' + result + '|link' + numOldMoves + '=' + link + '}}'; oldMovesText = oldMovesText.substring(0, oldMovesText.length-2) + newTextToAdd; text = text.replace(oldMovesPresent[0].wikitext, oldMovesText); } else if (isListFormat) { if(result == "moved"){ from = '|from=' + movePlus.title; }				text = text.replace(archives[0], '{{old move'+ '|date=' + date + from + '|destination=' + destination + '|result=' + result + '|link=' + link +'}}\n\n' + archives[0]); } else { var oldMovesText = '{{' + oldMovesPresent[0].name; for (var i = 0; i < oldMovesPresent[0].parameters.length; i++) { if (oldMovesPresent[0].parameters[i].name == "date") { oldMovesText += '|date1=' + oldMovesPresent[0].parameters[i].value; } else if (oldMovesPresent[0].parameters[i].name == "from") { oldMovesText += '|name1=' + oldMovesPresent[0].parameters[i].value; } else if (oldMovesPresent[0].parameters[i].name == "destination") { oldMovesText += '|destination1=' + oldMovesPresent[0].parameters[i].value; } else if (oldMovesPresent[0].parameters[i].name == "result") { oldMovesText += '|result1=' + oldMovesPresent[0].parameters[i].value; } else if (oldMovesPresent[0].parameters[i].name == "link") { oldMovesText += '|link1=' + oldMovesPresent[0].parameters[i].value; } else { oldMovesText += oldMovesPresent[0].parameters[i].wikitext; }				}				if(result == "moved"){ from = '|from2=' + movePlus.title; }				var newTextToAdd = '|date2=' + date + from + '|destination2=' + destination + '|result2=' + result + '|link2=' + link + '}}'; oldMovesText += newTextToAdd; text = text.replace(oldMovesPresent[0].wikitext, oldMovesText); }		} else { var oldMovesText = '{{Old moves'; var numOldMoves = 1; for (var i = 0; i < oldMovesPresent.length; i++) { for (var j = 0; j < oldMovesPresent[i].parameters.length; j++) { if (oldMovesPresent[i].parameters[j].name == "date") { oldMovesText += '|date' + numOldMoves + '=' + oldMovesPresent[i].parameters[j].value; } else if (oldMovesPresent[i].parameters[j].name == "from") { oldMovesText += '|name' + numOldMoves + '=' + oldMovesPresent[i].parameters[j].value; } else if (oldMovesPresent[i].parameters[j].name == "destination") { oldMovesText += '|destination' + numOldMoves + '=' + oldMovesPresent[i].parameters[j].value; } else if (oldMovesPresent[i].parameters[j].name == "result") { oldMovesText += '|result' + numOldMoves + '=' + oldMovesPresent[i].parameters[j].value; } else if (oldMovesPresent[i].parameters[j].name == "link") { oldMovesText += '|link' + numOldMoves + '=' + oldMovesPresent[i].parameters[j].value; } else { oldMovesText += oldMovesPresent[i].parameters[j].wikitext; }				}				numOldMoves++; }			if(result == "moved"){ from = '|from' + numOldMoves + '=' + movePlus.title; }			var newTextToAdd = '|date' + numOldMoves + '=' + date + from + '|destination' + numOldMoves + '=' + destination + '|result' + numOldMoves + '=' + result + '|link' + numOldMoves + '=' + link + '}}'; oldMovesText += newTextToAdd; text = text.replace(oldMovesPresent[0].wikitext, oldMovesText); for (var i = 1; i < oldMovesPresent.length; i++) { text = text.replace(oldMovesPresent[i].wikitext, ""); }		}		talkpage.setPageText(text); talkpage.setEditSummary('Closing requested move; ' + result + movePlus.advert); talkpage.save(Morebits.status.actionCompleted('Moved closed.')); if(multiMove == true){ var otherDestinations = [] var otherPages = [] for(var m=1; m<data.moves.length; m++) { otherDestinations.push(data.moves[m].destination); otherPages.push(data.moves[m].current); }			var pagesLeft = otherPages.length; for(var j=0; j<otherPages.length; j++){ var otherTitle_obj = mw.Title.newFromText(otherPages[j]); movePlus.otherTalktitle = otherTitle_obj.getTalkPage.toText; var otherPage = new Morebits.wiki.page(movePlus.otherTalktitle, 'Adding {{old move}} to ' + movePlus.otherTalktitle + '.'); otherPage.load(function(otherPage) {					var otherText = otherPage.getPageText;

var templatesOnOtherPage = extraJs.parseTemplates(otherText,false); var otherOldMovesPresent = []; for (var i = 0; i < templatesOnOtherPage.length; i++) { if (templatesOnOtherPage[i].name.toLowerCase == "old moves" || templatesOnOtherPage[i].name.toLowerCase == "old move") { otherOldMovesPresent.push(templatesOnOtherPage[i]); }					}					var title = mw.Title.newFromText(otherPage.getPageName).getSubjectPage.toText; var OMcurr = otherPages[otherPages.indexOf(title)]; var OMdest = otherDestinations[otherPages.indexOf(title)]; var otherFrom = ''; if(OMdest == "?"){ OMdest == ""; }					var otherDestination = OMdest; var otherArchives = otherText.match(/{{[Aa]rchives/); if(otherArchives == null){ otherArchives = otherText.match(/{{[Aa]rchive box/); if(otherArchives == null){ otherArchives = otherText.match(/{{[Aa]rchivebox/); if(otherArchives == null){ otherArchives = otherText.match(/==.*==/); if(otherArchives == null){ //Otherwise, skip it									otherArchives = '' }							}						}					}

if (otherOldMovesPresent.length == 0) { if(result == "moved"){ otherFrom = '|from=' + OMcurr; }						otherText = otherText.replace(otherArchives[0], '{{old move'+ '|date=' + date + otherFrom + '|destination=' + otherDestination + '|result=' + result + '|link=' + link +'}}\n\n' + otherArchives[0]); } else if (otherOldMovesPresent.length == 1) { var isValidFormat = false; var isListFormat = false; var numOldMoves = 0; for (var i = 0; i < otherOldMovesPresent[0].parameters.length; i++) { var parameterName = otherOldMovesPresent[0].parameters[i].name; parameterName = parameterName.toString; if (parameterName == "list") { isListFormat = true; break; } else if (parameterName == "result1") { isValidFormat = true; numOldMoves++; } else if (parameterName.includes("result")) { numOldMoves++; }						}						if (isValidFormat && !isListFormat) { var oldMovesText = otherOldMovesPresent[0].wikitext; numOldMoves++; if(result == "moved"){ otherFrom = '|from' + numOldMoves + '=' + OMcurr; }							var newTextToAdd = '|date' + numOldMoves + '=' + date + otherFrom + '|destination' + numOldMoves + '=' + otherDestination + '|result' + numOldMoves + '=' + result + '|link' + numOldMoves + '=' + link + '}}'; oldMovesText = oldMovesText.substring(0, oldMovesText.length-2) + newTextToAdd; otherText = otherText.replace(otherOldMovesPresent[0].wikitext, oldMovesText); } else if (isListFormat) { if(result == "moved"){ otherFrom = '|from=' + OMcurr; }							otherText = otherText.replace(otherArchives[0], '{{old move'+ '|date=' + date + otherFrom + '|destination=' + otherDestination + '|result=' + result + '|link=' + link +'}}\n\n' + otherArchives[0]); } else { var oldMovesText = '{{' + otherOldMovesPresent[0].name; for (var i = 0; i < otherOldMovesPresent[0].parameters.length; i++) { if (otherOldMovesPresent[0].parameters[i].name == "date") { oldMovesText += '|date1=' + otherOldMovesPresent[0].parameters[i].value; } else if (otherOldMovesPresent[0].parameters[i].name == "from") { oldMovesText += '|name1=' + otherOldMovesPresent[0].parameters[i].value; } else if (otherOldMovesPresent[0].parameters[i].name == "destination") { oldMovesText += '|destination1=' + otherOldMovesPresent[0].parameters[i].value; } else if (otherOldMovesPresent[0].parameters[i].name == "result") { oldMovesText += '|result1=' + otherOldMovesPresent[0].parameters[i].value; } else if (otherOldMovesPresent[0].parameters[i].name == "link") { oldMovesText += '|link1=' + otherOldMovesPresent[0].parameters[i].value; } else { oldMovesText += otherOldMovesPresent[0].parameters[i].wikitext; }							}							if(result == "moved"){ otherFrom = '|from2=' + OMcurr; }							var newTextToAdd = '|date2=' + date + otherFrom + '|destination2=' + otherDestination + '|result2=' + result + '|link2=' + link + '}}'; oldMovesText += newTextToAdd; otherText = otherText.replace(otherOldMovesPresent[0].wikitext, oldMovesText); }					} else { var oldMovesText = '{{Old moves'; var numOldMoves = 1; for (var i = 0; i < otherOldMovesPresent.length; i++) { for (var j = 0; j < otherOldMovesPresent[i].parameters.length; j++) { if (otherOldMovesPresent[i].parameters[j].name == "date") { oldMovesText += '|date' + numOldMoves + '=' + otherOldMovesPresent[i].parameters[j].value; } else if (otherOldMovesPresent[i].parameters[j].name == "from") { oldMovesText += '|name' + numOldMoves + '=' + otherOldMovesPresent[i].parameters[j].value; } else if (otherOldMovesPresent[i].parameters[j].name == "destination") { oldMovesText += '|destination' + numOldMoves + '=' + otherOldMovesPresent[i].parameters[j].value; } else if (otherOldMovesPresent[i].parameters[j].name == "result") { oldMovesText += '|result' + numOldMoves + '=' + otherOldMovesPresent[i].parameters[j].value; } else if (otherOldMovesPresent[i].parameters[j].name == "link") { oldMovesText += '|link' + numOldMoves + '=' + otherOldMovesPresent[i].parameters[j].value; } else { oldMovesText += otherOldMovesPresent[i].parameters[j].wikitext; }							}							numOldMoves++; }						if(result == "moved"){ otherFrom = '|from' + numOldMoves + '=' + OMcurr; }						var newTextToAdd = '|date' + numOldMoves + '=' + date + otherFrom + '|destination' + numOldMoves + '=' + otherDestination + '|result' + numOldMoves + '=' + result + '|link' + numOldMoves + '=' + link + '}}'; oldMovesText += newTextToAdd; otherText = otherText.replace(otherOldMovesPresent[0].wikitext, oldMovesText); for (var i = 1; i < otherOldMovesPresent.length; i++) { otherText = otherText.replace(otherOldMovesPresent[i].wikitext, ""); }					}

otherPage.setPageText(otherText); otherPage.setEditSummary('Closing requested move; ' + result + movePlus.advert); otherPage.save(Morebits.status.actionCompleted('Moved closed.')); pagesLeft--; });			}			if(result == "moved"){				var waitInterval = setInterval(function{ if(pagesLeft == 0){ movePlus.movePages([movePlus.title].concat(otherPages),[destination].concat(otherDestinations),link); clearInterval(waitInterval); }				}, 500);			} else{				setTimeout(function{ location.reload }, 2000);			}		} else if(result == "moved"){			var emptyArray = [];			movePlus.movePages([movePlus.title],[destination],link);		} else{			setTimeout(function{ location.reload }, 2000);			}	}); }; getTalkPageTitles = function getTalkPageTitles(curr, dest) { var currTitleObj = new mw.Title(curr); var destTitleObj = new mw.Title(dest);

var currTalkPage = currTitleObj.getTalkPage.getPrefixedText; var destTalkPage = destTitleObj.getTalkPage.getPrefixedText;

return { currTalk: currTalkPage, destTalk: destTalkPage }; }

getPageByTitle = function getPageByTitle(data, title) { return data.find(page => page.title === title); }

movePlus.checkPage = async function movePlusCheckPage(curr, dest) { let talkPages = getTalkPageTitles(curr, dest); let query = { action: 'query', prop: 'info|revisions', inprop: 'protection', titles: `${curr} | ${talkPages.currTalk} | ${dest} | ${talkPages.destTalk}`, format: 'json', rvprop: 'ids' };	let redirectsQuery = { ...query, redirects: 1 }

let protection = { curr: { article: {} },		dest: { article: {} },		createLabel: function(type) { let protections = []; let data = this[type]; let label = ''; if (data.move) protections.push("move"); if (data.create) protections.push("create");

if (protections.length > 0) { const formattedProtection = protections.join(" and "); label = ` (${formattedProtection} protected)`; }			//As only sysop's can overwrite pages with history we only warn sysops if (data.history && Morebits.userIsInGroup('sysop')) { label = label + ' (Warning: Page has history) '; }			return label; },		checkProtection: function { return this.curr.all || this.dest.all },		checkSysopProtection: function { return this.curr.admin || this.dest.admin },		checkHistory: function { return this.dest.history },		checkRedirect: function { return this.dest.redirect },		checkTarget: function { return this.curr.target },		checkClosed: function { return (this.curr.article.redirect && this.dest.article.target) || (this.dest.article.redirect && this.curr.article.target) }	}	function getRedirectByTitle(data, title) { if (data) { let redirect = data.find(redirect => redirect.from === title); return redirect ? redirect : []; }		return []; }	try { const pageResponse = await new Morebits.wiki.api(`Accessing information on about edit restrictions on ${dest} and ${curr}`, query).post; const currData = getPageByTitle(pageResponse.response.query.pages, curr) const currTalkData = getPageByTitle(pageResponse.response.query.pages, talkPages.currTalk) const destData = getPageByTitle(pageResponse.response.query.pages, dest) const destTalkData = getPageByTitle(pageResponse.response.query.pages, talkPages.destTalk) function checkProtection(data, admin = false) { return data.protection.some(protection => {				if (admin) {					return protection.level == "sysop";				} else {					return !(Morebits.userIsInGroup(protection.level) || Morebits.userIsSysop);				}			}); }		function checkHistory(data) { if (data.revisions) { return data.revisions[0].parentid === 0; }			return true; }		function checkMissing(data) { return data.missing }		function processHistory(data, talkData) { protection.dest.history = !checkHistory(data) || !checkHistory(talkData); }		async function processRedirects { const redirectResponse = await new Morebits.wiki.api(`Accessing information about redirect status of ${curr} and ${dest}`, redirectsQuery).post; processRedirect(redirectResponse, dest, curr, protection.dest, protection.curr); processRedirect(redirectResponse, talkPages.destTalk, talkPages.currTalk, protection.dest, protection.curr); processRedirect(redirectResponse, curr, dest, protection.curr.article, protection.dest.article); processRedirect(redirectResponse, dest, curr, protection.dest.article, protection.curr.article); }		function processRedirect(redirectData, origin, target, originStatus, targetStatus) { let data = getRedirectByTitle(redirectData.response.query.redirects, origin) originStatus.redirect = originStatus.redirect !== undefined ? originStatus.redirect : true; targetStatus.target = targetStatus.target !== undefined ? targetStatus.target : true; if (data.length == 0) { if (!checkMissing(getPageByTitle(redirectData.response.query.pages, origin))) { originStatus.history = true; originStatus.redirect = false; targetStatus.target = false; }				return; }			if (data.to != target) { targetStatus.target = false; }		}		protection.curr.all = checkProtection(currData) || checkProtection(currTalkData); protection.curr.admin = checkProtection(currData, true) || checkProtection(currTalkData, true); protection.dest.all = checkProtection(destData) || checkProtection(destTalkData); protection.dest.admin = checkProtection(destData, true) || checkProtection(destTalkData, true);

protection.dest.history = !checkHistory(destData) || !checkHistory(destTalkData); await processRedirects;

return protection; } catch (error) { console.error('Failed to fetch page details:', error); throw error; } };

movePlus.movePages = function movePlusMovePages(currList, destList, link, closer = true){ movePlus.numberToRemove = currList.length; movePlus.talktitle = mw.Title.newFromText(Morebits.pageNameNorm).getTalkPage.toText; var pageAndSection = link; var moveSummary, rmtrReason; var promises = []; var configurations = []; function sanitizeClassName(name) { return name.replace(/[^a-zA-Z0-9\-_]/g, '-'); }	if (closer) { if (movePlus.params.movedOptionsInputs.includes('moved-uncontested')) { moveSummary = 'Moved, as an uncontested technical request, per ' + pageAndSection + ''; rmtrReason = 'Per lack of objection at ' + pageAndSection + '.'; } else { moveSummary = 'Moved per ' + pageAndSection + ''; rmtrReason = 'Per consensus at ' + pageAndSection + '.'; }	} else { moveSummary = link; rmtrReason = link; }	var form = new Morebits.quickForm; var movesContainer = form.append({		type: 'field',		label: 'Moves'	}); function addButtons(curr, dest, config) { const moveContainer = movesContainer.append({			type: 'div',			className: 'movePlusMovePagesRow' + sanitizeClassName(curr),			style: 'display: flex; flex-direction: column; margin-bottom: 7px',			label: ''		}); const rowContainer = moveContainer.append({			type: 'div',			className: 'movePlusMovePagesSubRow' + sanitizeClassName(curr),			style: 'display: flex; flex-direction: row;',			label: ''		}); const actionContainer = rowContainer.append({			type: 'div',			style: 'display: flex; flex-direction: column; flex: 75%; text-align: left;',			className: 'moves'		}); const optionsContainer = rowContainer.append({			type: 'div',			style: 'display: flex; flex: 25%; text-align: left;',			className: 'moveOptions' + sanitizeClassName(curr)		});

actionContainer.append({			type: 'div',			className: 'movePlusMovePagesLabel',			label: config.label		});

actionContainer.append({			type: 'div',			className: 'movePlusProgressBox',			name: sanitizeClassName(curr),			label: '',			style: 'margin-left: 15px'		}); const buttonsContainer = actionContainer.append({			type: 'div',			style: 'display: flex; flex-direction: row; margin-left: 15px'		}); const linksContainer = moveContainer.append({			type: 'field',			label: 'Specify new link targets',			style: 'display: none',			className: 'specifyLinks' + sanitizeClassName(curr),			name: 'specifyLinks',			id: 'specifyLinks'		}); addRedirectSpecification(linksContainer, curr, dest); addRedirectSpecification(linksContainer, dest, curr); linksContainer.append({			type: 'div',			label: movePlus.linkAdjustWarning		}); var isSysop = Morebits.userIsInGroup('sysop'); var isMover = Morebits.userIsInGroup('extendedmover'); if (!config.isProtected) { if (config.hasHistory) { if (isMover || isSysop) { addCheckboxes(optionsContainer, curr, isSysop, true, true, !config.isClosed); if (isSysop) { addButton(buttonsContainer, curr, dest, "moveOverPage", true); }					addButton(buttonsContainer, curr, dest, 'roundRobin'); } else { addButton(buttonsContainer, curr, dest, 'technicalRequest', config.isSysopProtected); }			} else if (config.isRedirect && !config.isTarget) { if (isSysop || isMover) { addCheckboxes(optionsContainer, curr, true, true, true, false); addButton(buttonsContainer, curr, dest, "moveOverPage") } else { addButton(buttonsContainer, curr, dest, 'technicalRequest', config.isSysopProtected); }			} else { addButton(buttonsContainer, curr, dest, "move"); if (isSysop || isMover) { addCheckboxes(optionsContainer, curr, true, true, true, false); } else { addCheckboxes(optionsContainer, curr, false, false, true, false); }			}		} else { addButton(buttonsContainer, curr, dest, 'technicalRequest', config.isSysopProtected); }	}	function addRedirectSpecification(container, origin, target) { const titleField = container.append({			type: 'div',			className: 'titleInput',			style: 'display: flex; align-items: center; margin-bottom: 5px;'		}); const currentDiv = titleField.append({			type: 'div',			style: 'flex: 0 1 47.5%; text-align: left;'		});

currentDiv.append({			type: 'div',			label: origin,			style: 'width: 95%; text-align: left;'		});

titleField.append({			type: 'div',			style: 'flex: 0 1 5%; text-align: center;',			label: '→'		}); const futDiv = titleField.append({			type: 'div',			style: 'flex: 0 1 47.5%; text-align: left;'		});

futDiv.append({			type: 'input',			id: 'specifyLinks' + sanitizeClassName(origin),			value: target,				placeholder: 'Future page',			style: 'width: 95%; text-align: left;'		}); }	function addCheckboxes(container, label, suppressRedirect, moveSubpages, moveTalkPage, correctLinks) { let options = []; if(correctLinks) { options.push({				name: 'correctLinks' + sanitizeClassName(label),				label: "Correct links",				checked: false,				event: function {					if (event.target.checked) {						document.querySelector('.specifyLinks' + sanitizeClassName(label)).style.display = 'block';					} else {						document.querySelector('.specifyLinks' + sanitizeClassName(label)).style.display = 'none';					}				}			}); }		if(suppressRedirect) { options.push({					name: 'suppressRedirect' + sanitizeClassName(label),				label: "Suppress redirect",				checked: false			}); };		if(moveSubpages) { options.push({				name: 'moveSubpages' + sanitizeClassName(label),				label: 'Move subpages',				checked: true			}); };		if(moveTalkPage) { options.push({				name: 'moveTalkPage' + sanitizeClassName(label),				label: 'Move talk page',				checked: true			}); };		container.append({			type: 'checkbox',			style: 'display: flex; flex-direction: column; align-items: left; margin-right: 10px;',			list: options		}); }	function addButton(container, curr, dest, type, admin = false) { let operation; let label; let id; let tooltip = ""; let disabled = false; switch (type) { case "move": operation = async function(progressBox) { config = checkCheckboxes; await movePlus.movePage(this.name, this.extra, moveSummary, progressBox, config); };				label = 'Move directly'; id = 'moveDirectly'; break; case "moveOverPage": operation = async function(progressBox) { config = checkCheckboxes; await movePlus.moveOverPage(this.name, this.extra, config.suppressRedirect, moveSummary, admin, progressBox, config.moveSubpages, config.moveTalkPage); };				label = 'Move directly (o)'; id = 'moveOverPage'; break; case "roundRobin": operation = async function(progressBox) { config = checkCheckboxes; await movePlus.moveRoundRobin(this.name, this.extra, moveSummary, progressBox, config); };				label = 'Move via Round Robin'; id = 'roundRobin'; break; case "technicalRequest": operation = async function(progressBox) { config = checkCheckboxes; await movePlus.submitRMTR(this.name, this.extra, admin, rmtrReason, progressBox); };				label = 'Submit technical request'; id = 'rmtr'; break; }

container.append({			type: 'button',			className: 'movePlusMovePages' + sanitizeClassName(curr),			name: curr,			extra: dest,			label: label,			tooltip: tooltip,			disabled: disabled,			id: id,			event: async function {				const rowElements = document.querySelectorAll('.movePlusMovePages' + sanitizeClassName(curr));				rowElements.forEach(element => { element.style.display = 'none'; });				const progressBox = document.querySelector('.movePlusProgressBox[name="' + sanitizeClassName(curr) + '"]');				if (progressBox) {					progressBox.textContent = 'In progress...';

try { await operation.call(this, progressBox); progressBox.textContent = 'Completed!'; setTimeout( => {							document.querySelector('.movePlusMovePagesRow' + sanitizeClassName(curr)).style.display = 'none';							movePlus.numberToRemove--;						}, 1000); } catch (error) { progressBox.textContent = 'Failed. Please implement manually and report this error to the script maintainer.'; }				}			}		});		function checkCheckboxes {			const suppressRedirectElem = document.querySelector('input[name="suppressRedirect' + sanitizeClassName(curr) + '"]');			const moveSubpagesElem = document.querySelector('input[name="moveSubpages' + sanitizeClassName(curr) + '"]');			const moveTalkPageElem = document.querySelector('input[name="moveTalkPage' + sanitizeClassName(curr) + '"]');			const correctLinksElem = document.querySelector('input[name="correctLinks' + sanitizeClassName(curr) + '"]');

let suppressRedirect = suppressRedirectElem ? suppressRedirectElem.checked : false; let moveSubpages = moveSubpagesElem ? moveSubpagesElem.checked : true; let moveTalkPage = moveTalkPageElem ? moveTalkPageElem.checked : true; let correctLinks = correctLinksElem ? correctLinksElem.checked : false; let currTarget, destTarget; if (correctLinks) { currTarget = document.querySelector('#specifyLinks' + sanitizeClassName(curr)).value; destTarget = document.querySelector('#specifyLinks' + sanitizeClassName(dest)).value; }			document.querySelector('.specifyLinks' + sanitizeClassName(curr)).style.display = 'none'; document.querySelector('.moveOptions' + sanitizeClassName(curr)).style.display = 'none'; return { suppressRedirect: suppressRedirect, moveSubpages: moveSubpages, moveTalkPage: moveTalkPage, correctLinks: correctLinks, currTarget: currTarget, destTarget: destTarget }		}	}	for(let i=0; i {			configurations[i] = {				label: currList[i] + data.createLabel("curr") + ' → ' + destList[i] + data.createLabel("dest"),				isProtected: data.checkProtection,				isSysopProtected: data.checkSysopProtection,				hasHistory: data.checkHistory,				isRedirect: data.checkRedirect,				isTarget: data.checkTarget,				isClosed: data.checkClosed			}		}); promises.push(promise) }	function setupMulti { var multiContainer = form.append({			type: 'field',			label: 'Multi-action',			style: 'display: flex; flex-direction: row'		}); multiContainer.append({			type: 'button',			label: 'Move all',			tooltip: 'Moves all pages. If there are multiple options it will move the page via round robin instead of overwriting history at the destination.',			event: async function {				const rows = document.querySelectorAll('[class^="movePlusMovePagesRow"]');				for (const row of rows) {					const moveDirectlyButton = row.querySelector('div span input#moveDirectly');					const roundRobinButton = row.querySelector('div span input#roundRobin');					const technicalRequestButton = row.querySelector('div span input#rmtr');					if (moveDirectlyButton) {						await moveDirectlyButton.click;					} else if (roundRobinButton) {						await roundRobinButton.click;					} else if (technicalRequestButton) {						await technicalRequestButton.click;					}				}			}		}); var multiOptionContainer = multiContainer.append({			type: 'div',			style: "display: flex; flex-direction: row; justify-content: flex-end; width: 85%;"		}); multiOptionContainer.append({			type: 'button',			label: 'Suppress all redirects',			event: function {				const buttons = document.querySelectorAll('input[name^="suppressRedirect"]');				const button = event.target;				const currentLabel = button.value;				buttons.forEach(button => { button.checked = currentLabel === 'Suppress all redirects'; });				button.value = currentLabel === 'Suppress all redirects' ? 'Suppress no redirects' : 'Suppress all redirects';			}		}); multiOptionContainer.append({			type: 'button',			label: 'Move no subpages',			event: function {				const buttons = document.querySelectorAll('input[name^="moveSubpages"]');				const button = event.target;				const currentLabel = button.value;				buttons.forEach(button => { button.checked = currentLabel === 'Move all subpages'; });				button.value = currentLabel === 'Move all subpages' ? 'Move no subpages' : 'Move all subpages';			}		}); multiOptionContainer.append({			type: 'button',			label: 'Move no talk pages',			event: function {				const buttons = document.querySelectorAll('input[name^="moveTalkPage"]');				const button = event.target;				const currentLabel = button.value;				buttons.forEach(button => { button.checked = currentLabel === 'Move all talk pages'; });				button.value = currentLabel === 'Move all talk pages' ? 'Move no talk pages' : 'Move all talk pages';			}		}); }

Promise.all(promises).then( => {		configurations.forEach((config, index) => { addButtons(currList[index], destList[index], config); });		setupMulti;		var formResult = form.render;		movePlus.Window.setContent(formResult);		movePlus.Window.display;		var moveInterval = setInterval(function{ if(movePlus.numberToRemove == 0){ movePlus.Window.close; clearInterval(moveInterval); setTimeout(function{ location.reload }, 750); }		}, 500);	}); };

movePlus.moveOverPage = function movePlusMoveOverPage(curr, dest, suppressRedirect, editSummary, warn, progressBox, subpages = true, talkpage = true) { let destSplit = movePlus.splitPageName(dest);

if (warn) { if (!confirm('Warning: You are about to delete a page with history. Do you want to proceed?')) { progressBox.innerText = 'Move cancelled'; throw new Error('Move cancelled.'); }	}	return new Promise((resolve, reject) => {		const moveTask = async => {			progressBox.innerText = `Moving ${curr} to ${dest}.`;

const url = 'https://en.wikipedia.org/w/index.php?title=Special:MovePage&action=submit';

const formData = new FormData; formData.append('wpNewTitleNs', destSplit.namespace); formData.append('wpNewTitleMain', destSplit.pageName); formData.append('wpReasonList', 'other'); formData.append('wpReason', editSummary + movePlus.advert); formData.append('wpWatch', '0'); formData.append('wpLeaveRedirect', suppressRedirect ? '0' : '1'); formData.append('wpMovetalk', talkpage ? '1' : '0'); formData.append('wpMovesubpages', subpages ? '1' : '0'); formData.append('wpDeleteAndMove', '1'); formData.append('wpMove', 'Move page'); formData.append('wpOldTitle', curr); formData.append('wpEditToken', mw.user.tokens.get('csrfToken'));

const response = await fetch(url, {				method: 'POST',				body: formData,				credentials: 'include'			});

if (response.ok) { progressBox.innerText = `Moved ${curr} to ${dest}.`; Morebits.status.actionCompleted('Moved.'); resolve; } else { progressBox.innerText = `Failed to move ${curr} to ${dest}.`; reject('Move request failed'); }		};

movePlus.moveQueue.push(moveTask); movePlus.startMoveQueue; }); };

movePlus.movePage = function movePlusMovePage(from, to, editSummary, progressBox, config) {

return new Promise((resolve, reject) => {		const moveTask = => {			progressBox.innerText = `Moving ${from} to ${to}...`;			let pageToMove = new Morebits.wiki.page(from, `Moving ${from} to ${to}.`);			pageToMove.setMoveDestination(to);			pageToMove.setMoveSubpages(config.moveSubpages);			pageToMove.setMoveTalkPage(config.moveTalkPage);			pageToMove.setMoveSuppressRedirect(config.suppressRedirect);			pageToMove.setEditSummary(`${editSummary}${movePlus.advert}`);			console.log(`Moving ${from} to ${to}`);			pageToMove.move( => { progressBox.innerText = `Moved ${from} to ${to}.`; Morebits.status.actionCompleted('Moved.'); resolve; }, (error) => { reject(error); });		};		movePlus.moveQueue.push(moveTask);		movePlus.startMoveQueue;	}); };

movePlus.moveRoundRobin = async function movePlusMoveRoundRobin(curr, dest, editSummary, progressBox, config) { progressBox.innerText = 'Round robin pending...'; config.suppressRedirect = true; try { var destDetails = movePlus.splitPageName(dest); var intermediateTitle = `Draft:Move/${destDetails.pageName}`; editSummary = `${editSummary} via a round robin`; progressBox.innerText = `Moving ${dest} to ${intermediateTitle}...`; await movePlus.movePage(dest, intermediateTitle, editSummary, progressBox, config); progressBox.innerText = `Moving ${curr} to ${dest}...`; await movePlus.movePage(curr, dest, editSummary, progressBox, config); progressBox.innerText = `Moving ${intermediateTitle} to ${curr}...`; await movePlus.movePage(intermediateTitle, curr, editSummary, progressBox, config); progressBox.innerText = `Cleaning up round robin for ${curr} and ${dest}...`; await movePlus.roundRobinCleanup(curr, dest, config); if (config.moveTalkPage) { await movePlus.roundRobinCleanup(movePlus.getTalkPageName(curr), movePlus.getTalkPageName(dest), config); }		if (config.correctLinks) { movePlus.linkEditSummary = `Post-move cleanup, following swap of ${curr} and ${dest}: ` progressBox.innerText = `Correcting redirects for ${curr} and ${dest}...`; movePlus.correctRedirects(curr, dest, config); progressBox.innerText = `Correcting links for ${curr} and ${dest}...`; await movePlus.correctLinks(curr, dest, config, progressBox); }	} catch (error) { console.error('Error during move operation:', error); } };

movePlus.getLinksHere = async function movePlusGetLinksHere(page) {

let query = { action: 'query', prop: 'linkshere', titles: page, lhlimit: 'max', format: 'json', lhnamespace: `${movePlus.splitPageName(page).namespace}|10|14`, rawcontinue: 1 };	let pages = []; do { let response = await new Morebits.wiki.api(`Listing links to ${page}`, query).post; if (response.response.query.pages[0].linkshere) { pages = pages.concat(response.response.query.pages[0].linkshere); }		query.lhcontinue = response.response['query-continue'] ? response.response['query-continue'].linkshere.lhcontinue : 0; } while (query.lhcontinue) return pages;

}

movePlus.roundRobinCleanup = async function movePlusRoundRobinCleanup(curr, dest, config) {

async function queryPagesAndRedirects(page) { let details = movePlus.splitPageName(page); let query = { action: 'query', list: 'allpages', apfrom: `${details.pageName}/`, apto: `${details.pageName}0`, apnamespace: details.namespace, format: 'json' };

let allpages = []; if (config.moveSubpages) { const response = await new Morebits.wiki.api(`Listing subpages of ${details.pageName}`, query).post; allpages = response.response.query.allpages || []; }		let pageTitles = allpages.map(page => page.title).join('|'); if (pageTitles) { pageTitles += '|'; }		pageTitles += page; let pageQuery = { action: "query", format: "json", prop: "", titles: pageTitles }		const responsePages = await new Morebits.wiki.api(`Getting details of subpages of ${details.pageName}`, pageQuery).post; const pages = (responsePages.response.query.pages || []).filter(page => !page.missing); pageQuery.redirects = 1; const responseRedirects = await new Morebits.wiki.api(`Getting details of subpages of ${details.pageName}`, pageQuery).post; const redirects = responseRedirects.response.query.redirects || []; return {redirects, pages}; };	function getPagePairs(pages, pair, self) { return pages.map(page => {			const subpageName = movePlus.splitSubpageName(page.title, self);			const pagename = subpageName === '' ? pair : `${pair}/${subpageName}`;			return {				pagename: pagename,				target: page.title			};		}); }

function getRedirectPairs(redirects, pair, self) { return redirects.filter(redirect => redirect.from !== redirect.to).map(redirect => {			const subpageName = movePlus.splitSubpageName(redirect.from, self);			const pagename = subpageName === '' ? pair : `${pair}/${subpageName}`;			return {				pagename: pagename,				target: redirect.to			};		}); }

function findSelfRedirects(redirects, pair, self) { return redirects.filter(redirect => redirect.from === redirect.to).map(redirect => {			const subpageName = movePlus.splitSubpageName(redirect.from, self);			const target = subpageName === '' ? pair : `${pair}/${subpageName}`;			return {				pagename: redirect.from,				target: target			};		}); }

async function processPages(pairs, existingPages, existingRedirects) { const existingPageNames = new Set(existingPages.map(page => page.title)); const existingRedirectNames = new Set(existingRedirects.map(redirect => redirect.from)); for (const pair of pairs) { if (!existingPageNames.has(pair.pagename) && !existingRedirectNames.has(pair.pagename)) { await movePlus.createRedirect(pair.pagename, pair.target, 				`Redirecting to ${pair.target} as part of post-round robin cleanup${movePlus.advert}`); }		};	};	const currResponse = await queryPagesAndRedirects(curr); const destResponse = await queryPagesAndRedirects(dest);

const currRedirects = currResponse.redirects; const currPages = currResponse.pages;

const destRedirects = destResponse.redirects; const destPages = destResponse.pages;

const currPagePairs = getPagePairs(currPages, dest, curr); const currRedirectPairs = getRedirectPairs(currRedirects, dest, curr); const currSelfRedirects = findSelfRedirects(currRedirects, dest, curr);

const destPagePairs = getPagePairs(destPages, curr, dest); const destRedirectPairs = getRedirectPairs(destRedirects, curr, dest); const destSelfRedirects = findSelfRedirects(destRedirects, curr, dest); await processPages(currPagePairs, destPages, destRedirects); await processPages(currRedirectPairs, destPages, destRedirects);

await processPages(destPagePairs, currPages, currRedirects); await processPages(destRedirectPairs, currPages, currRedirects); for (const pair of currSelfRedirects.concat(destSelfRedirects)) { await movePlus.createRedirect(pair.pagename, pair.target, `Redirecting to ${pair.target} as part of post-round robin cleanup${movePlus.advert}`); } };

movePlus.correctRedirects = async function movePlusCorrectRedirects(curr, dest, config) {

if (config.currTarget != dest && config.currTarget != curr) { await movePlus.createRedirectPair(dest, config.destTarget, `Redirecting to ${config.destTarget} as part of post-round robin cleanup${movePlus.advert}`); }	if (config.destTarget != curr && config.destTarget != dest) { await movePlus.createRedirect(curr, config.currTarget, `Redirecting to ${config.currTarget} as part of post-round robin cleanup${movePlus.advert}`); } }

movePlus.correctLinks = async function movePlusCorrectLinks(curr, dest, config, progressBox) {

let currPages = []; let destPages = []; progressBox.innerText = `Getting links to ${curr}...`; if (curr != config.currTarget) { currPages = await movePlus.getLinksHere(curr); }	progressBox.innerText = `Getting links to ${dest}...`; if (dest != "" && dest != config.destTarget) { destPages = await movePlus.getLinksHere(dest); }	const pageIdsCurr = new Set(currPages.map(item => item.pageid)); const pageIdsDest = new Set(destPages.map(item => item.pageid)); await processBatches(curr, config.currTarget, currPages, pageIdsDest, progressBox, true, dest, config.destTarget); await processBatches(dest, config.destTarget, destPages, pageIdsCurr, progressBox, false); async function processBatches(origin, target, pages, otherPages, progressBox, processDuplicates, otherOrigin = "", otherTarget = "") { if (origin == target) { return; }		let query = { action: "query", format: "json", prop: "categories", titles: "", clcategories: "Category:All_disambiguation_pages|Category:All_set_index_articles" }

for (let i = 0; i < pages.length; i += 50) { const batch = pages.slice(i, i + 50); query.titles = batch.map(item => item.title).join('|'); const response = await new Morebits.wiki.api(`Listing categories of pages linking to ${origin}`, query).post; const dabPages = new Set; response.response.query.pages.forEach(page => {				if (page.categories && page.categories.length > 0) {					dabPages.add(page.pageid);				}			}); let j = i			for (const item of batch) { j++; //Skip archives if (item.ns != 0 && item.title.toLowerCase.includes('/archive')) { continue; }				progressBox.innerHTML = `${origin} → ${target} (${j}/${pages.length}): Updating ${item.title}...`; if (otherPages.has(item.pageid)) { if (processDuplicates) { await movePlus.correctLink(item, origin, target, dabPages.has(item.pageid), otherOrigin, otherTarget); }				} else { await movePlus.correctLink(item, origin, target, dabPages.has(item.pageid)); }			}		}	} }

movePlus.correctLink = function movePlusCorrectLink(item, from, to, dab, otherFrom = "", otherTo = "") {

return new Promise((resolve, reject) => {		console.log(`Updating links at ${item.title}`);		let page = new Morebits.wiki.page(item.title, `Updating links at ${item.title}`);		page.load(function(linkCorrection) { let originalText = page.getPageText; if (!allowBots(originalText, mw.config.get('wgUserName'))) { console.log(`Forbidden from making edits at ${item.title}`) resolve; return; }			if (otherFrom != "" && otherTo != "") { var updatedText = updateTextRoundRobin(originalText, item, from, to, otherFrom, otherTo, dab); } else { var updatedText = updateText(originalText, item, from, to, dab); }			if (updatedText.matches == 0) { console.log(`No edits to make at ${item.title}`); resolve; return; }			let text = updatedText.text; let editSummary = updatedText.editSummary; const editTask = => { page.setPageText(text); page.setEditSummary(editSummary); page.setMinorEdit(true); page.setBotEdit(true); console.log(`Successfully updated ${item.title}`); page.save( => {					Morebits.status.actionCompleted(`Replaced link to ${from} with link to ${to} at ${item.title}.`);					resolve;				}, (error) => {					reject(error);				}); };			movePlus.editQueue.push(editTask); movePlus.startEditQueue; });	});	function updateText(text, item, from, to, dab) { let updatedText = processText(text, item, from, to, dab); let editSummary = `${movePlus.linkEditSummary}Changed link from ${from} to ${to}${updatedText.matches > 1 ? ` (×${updatedText.matches})` : ""}${movePlus.advert}`; return {text: updatedText.text, editSummary: editSummary, matches: updatedText.matches}; }	function updateTextRoundRobin(text, item, from, to, otherFrom, otherTo, dab) { let stageOneText = processText(text, item, from, "INTERMEDIARYSTAGE", dab); let stageTwoText = processText(stageOneText.text, item, otherFrom, otherTo, dab); let updatedText = processText(stageTwoText.text, item, "INTERMEDIARYSTAGE", to, dab); let editSummary = `${movePlus.linkEditSummary}Changed link from ${from} to ${to} ${stageOneText.matches > 1 ? `(×${stageOneText.matches}) ` : ""}and from ${otherFrom} to ${otherTo}${stageTwoText.matches > 1 ? ` (×${stageTwoText.matches})` : ""}${movePlus.advert}`; return {text: updatedText.text, editSummary: editSummary, matches: stageOneText.matches + stageTwoText.matches}; }	function processText(text, item, from, to, dab) {

function escapeRegExp(string) { return string.replace(/[.*+?^${}|[\]\\]/g, '\\$&'); }		const linkRegex = new RegExp(`\\[\\[${escapeRegExp(from)}(\\|.+|#.*)?\\]\\]`, 'g'); const matches = text.match(linkRegex); if (!matches) { return {text : text, matches: 0} }		let updatedText = text.replace(linkRegex, match => {			const hashedMatch = match.includes('#');			const pipedMatch = match.includes('|');			if (hashedMatch) {				return match.replace(from, to);			}				if (item.redirect || (dab && !pipedMatch)) {				return `${to}`;			}			return pipedMatch ? match.replace(from, to) : `${from}`;		}); return {text: updatedText, matches: matches.length};

} };

movePlus.createRedirectPair = async function movePlusCreateRedirectPair(page, target, editSummary, config) {

await movePlus.createRedirect(page, target, editSummary); if (config.moveTalkPage) { let currTalk = movePlus.GetTalkPageName(page); let targetTalk = movePlus.GetTalkPageName(target); await movePlus.createRedirect(currTalk, targetTalk, editSummary); } }

movePlus.createRedirect = async function movePlusCreateRedirect(page, target, editSummary) { return new Promise((resolve, reject) => {		const editTask = => {			let redirect = new Morebits.wiki.page(page, `Creating redirect to ${target}`);			redirect.load(function(redirect) { redirect.setPageText(`#REDIRECT ${target}\n{{Rcat shell|\n{{R from move}}\n}}`); redirect.setEditSummary(editSummary); console.log(`Attempting to redirect ${page} to ${target}`); redirect.save( => {					Morebits.status.actionCompleted(`Cleaned up ${page}.`);					resolve;				}, (error) => {					reject(error);				}); });		};		movePlus.editQueue.push(editTask);		movePlus.startEditQueue;	}); };

movePlus.splitPageName = function movePlusSplitPageName(page) { const namespaceSeparator = ':'; const namespaceMap = { '': 0,		'Talk': 1, 'User': 2, 'User talk': 3, 'Wikipedia': 4, 'Wikipedia talk': 5, 'File': 6, 'File talk': 7, 'MediaWiki': 8, 'MediaWiki talk': 9, 'Template': 10, 'Template talk': 11, 'Help': 12, 'Help talk': 13, 'Category': 14, 'Category talk': 15, 'Portal': 100, 'Draft': 118, 'Draft talk': 119, 'TimedText': 710, 'TimedText talk': 711, 'Module': 828, 'Module talk': 829 };

const separatorIndex = page.indexOf(namespaceSeparator); if (separatorIndex === -1) { return { namespace: 0, pageName: page }; }

const potentialNamespace = page.substring(0, separatorIndex).trim; const pageName = page.substring(separatorIndex + 1).trim;

const namespaceNumber = namespaceMap.hasOwnProperty(potentialNamespace) ? namespaceMap[potentialNamespace] : 0;

if (namespaceNumber === 0) { return { namespace: 0, pageName: page }; }

return { namespace: namespaceNumber, pageName: pageName }; };

movePlus.getTalkPageName = function movePlusGetTalkPageName(page) { const talkNamespaceMap = { 0: 'Talk', 2: 'User talk', 4: 'Wikipedia talk', 6: 'File talk', 8: 'MediaWiki talk', 10: 'Template talk', 12: 'Help talk', 14: 'Category talk', 100: 'Portal talk', 118: 'Draft talk', 710: 'TimedText talk', 828: 'Module talk' };

const splitResult = movePlus.splitPageName(page); const talkNamespace = talkNamespaceMap[splitResult.namespace];

return `${talkNamespace}:${splitResult.pageName}`; };

movePlus.splitSubpageName = function movePlusSplitSubpageName(page, self) { let selfIndex = page.indexOf(self);

if (selfIndex === -1 || selfIndex === page.length - self.length) { return ""; }

return page.substring(selfIndex + self.length + 1); }

movePlus.submitRMTR = function movePlusSubmitRMTR(curr, dest, adminRequired, reason, progressBox) { progressBox.innerText = `Submitting technical request for ${curr} to ${dest}...`;

var rmtr = new Morebits.wiki.page('Wikipedia:Requested moves/Technical requests', 'Submitting request at WP:RM/TR'); rmtr.load(function(page) {		if (adminRequired) {			rmtr.setAppendText('{{subst:RMassist|1=' + curr + '|2=' + dest + '|reason=' + reason + '}}');		} else {			var text = rmtr.getPageText;			var textToFind = /\n{1,}(==== ?Requests to revert undiscussed moves ?====)/i;			var rmtrText = '{{subst:RMassist|1=' + curr + '|2=' + dest + '|reason=' + reason + '}}';			text = text.replace(textToFind, '\n' + rmtrText + '\n\n$1');			rmtr.setPageText(text);		}		rmtr.setEditSummary('Add request' + movePlus.advert);		rmtr.save( => { progressBox.innerText = 'Technical request submitted'; Morebits.status.actionCompleted('Requested.') });	}); };

movePlus.relist = function movePlusRelist(e) { if (e) e.preventDefault; var title_obj = mw.Title.newFromText(Morebits.pageNameNorm); movePlus.talktitle = title_obj.getTalkPage.toText; var talkpage = new Morebits.wiki.page(movePlus.talktitle, 'Relisting.'); var relistingComment = document.getElementById('movePlusRelistComment').value; talkpage.load(function(talkpage) {		var text = talkpage.getPageText;

var templateFound = false; var sig; var line; var templateIndex = -1; var textToFind = text.split('\n'); for (var i = 0; i < textToFind.length; i++) { line = textToFind[i]; if(templateFound == false){ if(/{{[Rr]equested move\/dated/.test(line)){ templateFound = true; templateIndex = i;				} } else if(templateFound == true){ if (/ \(UTC\)/.test(line)){ sig = line; break; }			}		}		text = text.replace(sig, sig + " {{subst:RM relist}}"); if(relistingComment != ''){ var nextSection = false; for (var i = templateIndex+1; i < textToFind.length; i++) { line = textToFind[i]; if (line.match(/^(==)[^=].+\1/)) { nextSection = true; var escapedLine = line.replace(/[-[\]{}*+?.,\\^$|#\s]/g, '\\$&') var regex = new RegExp('(' + escapedLine + ')(?![\s\S]*(' + escapedLine + '))', 'm'); text = text.replace(regex, ': \'\'\'Relisting comment\'\'\': ' + relistingComment + ' ~ \n\n' + line); break; }			}

if (!nextSection) { text += '\n: \'\'\'Relisting comment\'\'\': ' + relistingComment + ' ~ '; }		}		talkpage.setPageText(text); talkpage.setEditSummary('Relisted requested move' + movePlus.advert); talkpage.save(Morebits.status.actionCompleted('Relisted.')); document.getElementById("requestedmovetag").innerHTML = ""; setTimeout(function{ location.reload }, 2000); }); };

movePlus.notify = function movePlusNotify(e) { if (e) e.preventDefault; var wikiProjectTemplates = document.getElementsByClassName("wpb-project_link"); var wikiProjectNames = []; var wikiProjects = []; for(var i=0; i<wikiProjectTemplates.length; i++){ var wikiProjectName = wikiProjectTemplates[i].innerHTML; var wikiProjectTalk = mw.Title.newFromText(wikiProjectTemplates[i].innerHTML).getTalkPage.toText; if (!wikiProjectNames.includes(wikiProjectName)) { wikiProjectNames.push(wikiProjectName); wikiProjects.push(wikiProjectTalk); }	}

var wikiProjectBannerShellHeaders = document.getElementsByClassName("wpb-header-combined"); for (var i=0; i 2) { subprojectList = subprojectList.children[2]; if (subprojectList.hasChildNodes && subprojectList.children.length > 0) { subprojectList = subprojectList.children; for (var j=0; j<subprojectList.length; j++) { var wikiProjectName = subprojectList[j].title; var wikiProjectTalk = mw.Title.newFromText(subprojectList[j].title).getTalkPage.toText; if (!wikiProjectNames.includes(wikiProjectName)) { wikiProjectNames.push(wikiProjectName); wikiProjects.push(wikiProjectTalk); }				}			}		}	}	if(wikiProjects.length == 0){ mw.notify('No WikiProject banners found on this page'); } else{ var Window = new Morebits.simpleWindow(600, 450); Window.setTitle( "Notify WikiProjects about requested move" ); Window.setScriptName('movePlus'); Window.addFooterLink('Script documentation', 'User:BilledMammal/movePlus'); Window.addFooterLink('Give feedback', 'User talk:BilledMammal/movePlus');

var form = new Morebits.quickForm(movePlus.notifyCheck);

form.append({			type: 'div',			label: 'WikiProjects with banners on this page:'		});

form.append({			type: 'checkbox',			name: 'wikiProject',			list: wikiProjects.map(function (wp) { var wplabel = wikiProjectNames[wikiProjects.indexOf(wp)]; return { type: 'option', label: wplabel, value: wp }; })		});

if(wikiProjects[0] != 'none'){ form.append({ type: 'submit', label: 'Notify selected WikiProject(s)' }); }

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

movePlus.notifyCheck = function(e) { var form = e.target; movePlus.params = Morebits.quickForm.getInputData(form);

Morebits.simpleWindow.setButtonsEnabled(false); Morebits.status.init(form); var wikiProjectsToNotify = movePlus.params.wikiProject;

if (wikiProjectsToNotify.length == 0) { Morebits.status.error('Error', 'No WikiProjects selected'); } else { var uniqueWikiProjects = []; var wikiProjectCount = 0; for (var i=0; i 0) {					movePlus.notifyGetSection(uniqueWikiProjects);				}			}); }	} };

movePlus.notifyGetSection = function(wikiProjectsToNotify) { var title_obj = mw.Title.newFromText(Morebits.pageNameNorm); movePlus.talktitle = title_obj.getTalkPage.toText; var talkpage = new Morebits.wiki.page(movePlus.talktitle, 'Getting section.'); talkpage.load(function(talkpage) {		var text = talkpage.getPageText;		var line;		var templateIndex = -1;		var rmSection;		var textToFind = text.split('\n');		for (var i = 0; i < textToFind.length; i++) {				line = textToFind[i];			if(/{{[Rr]equested move\/dated/.test(line)){				templateIndex = i;				break;			}		}

for (var i = templateIndex; i >= 0; i--) { line = textToFind[i]; if (line.match(/^(==)[^=].+\1/)) { rmSection = line.match(/^(==)[^=](.+)\1/)[2].trim; break; }		}

movePlus.notifyEvaluate(wikiProjectsToNotify, rmSection); }); };

movePlus.notifyEvaluate = function(wikiProjectsToNotify, moveSection) { var wikiProjectsNotified = []; var wikiProjectCount = 0; for (var j=0; j<wikiProjectsToNotify.length; j++) { var talkpage = new Morebits.wiki.page(wikiProjectsToNotify[j], 'Notifying ' + wikiProjectsToNotify[j] + '.'); talkpage.setFollowRedirect(true); talkpage.load(function(talkpage) {			var wikiProjectToNotify = talkpage.getPageName;			var text = talkpage.getPageText;			movePlus.talktitle = mw.Title.newFromText(Morebits.pageNameNorm).getTalkPage.toText;			var pageAndSection = movePlus.talktitle + "#" + moveSection;			var notified;			if(confirm("\"" + wikiProjectToNotify + "\" may have already been notified of the discussion. Do you wish to proceed?")){				text += "\n\n== Requested move at " + pageAndSection + " ==\n There is a requested move discussion at " + pageAndSection + " that may be of interest to members of this WikiProject. ~";

talkpage.setPageText(text); talkpage.setEditSummary('Notifying of requested move' + movePlus.advert); talkpage.save(Morebits.status.actionCompleted('Notified.')); notified = true; } else{ var cancelNotify = new Morebits.status('Error', 'Notification canceled', 'error'); notified = false; }			if(notified){ wikiProjectsNotified.push(wikiProjectToNotify); }			wikiProjectCount++;

if (wikiProjectCount == wikiProjectsToNotify.length && wikiProjectsNotified.length > 0) { movePlus.notifyListOnTalkPage(wikiProjectsNotified); }		});	} };

movePlus.notifyListOnTalkPage = function(wikiProjectsNotified) { var discussionPage = new Morebits.wiki.page(movePlus.talktitle, 'Adding note about notification to requested move'); discussionPage.load(function(discussionPage) {		var discussionPageText = discussionPage.getPageText;		var templateFound = false;		var line;		var nextSection = false;		var textToFind = discussionPageText.split('\n');		for (var i = 0; i < textToFind.length; i++) {				line = textToFind[i];			if(templateFound == false){				if(/{{[Rr]equested move\/dated/.test(line)){					templateFound = true;				}			} else if(templateFound == true){				if (line.match(/^(==)[^=].+\1/)) {					nextSection = true;					var escapedLine = line.replace(/[-[\]{}*+?.,\\^$|#\s]/g, '\\$&')					var regex = new RegExp('(' + escapedLine + ')(?![\s\S]*(' + escapedLine + '))', 'm');					if (wikiProjectsNotified.length == 1) {						var wikiProjectToNotify = wikiProjectsNotified[0];						discussionPageText = discussionPageText.replace(regex, ': Note: ' + wikiProjectToNotify.slice(15) + ' has been notified of this discussion. ~ \n\n' + line);					} else {						var textToInsert = ': Note: ';						for (var j=0; j<wikiProjectsNotified.length; j++) {							var wikiProjectToNotify = wikiProjectsNotified[j];							textToInsert +=  + wikiProjectToNotify.slice(15) + ;							if (j == wikiProjectsNotified.length-2) {								if (wikiProjectsNotified.length == 2) {									textToInsert += ' and ';								} else {									textToInsert += ', and ';								}							} else if (j != wikiProjectsNotified.length-1) {								textToInsert += ', ';							}						}						textToInsert += ' have been notified of this discussion. ~ \n\n';						discussionPageText = discussionPageText.replace(regex, textToInsert + line);					}					break;				}			}		}

if (!nextSection) { if (wikiProjectsNotified.length == 1) { var wikiProjectToNotify = wikiProjectsNotified[0]; discussionPageText+='\n: Note: ' + wikiProjectToNotify.slice(15) + ' has been notified of this discussion. ~ ';			} else { discussionPageText += '\n: Note: '; for (var j=0; j<wikiProjectsNotified.length; j++) { var wikiProjectToNotify = wikiProjectsNotified[j]; discussionPageText +=  + wikiProjectToNotify.slice(15) + ; if (j == wikiProjectsNotified.length-2) { if (wikiProjectsNotified.length == 2) { discussionPageText += ' and '; } else { discussionPageText += ', and '; }					} else if (j != wikiProjectsNotified.length-1) { discussionPageText += ', '; }				}				discussionPageText += ' have been notified of this discussion. ~ ';			}		}

discussionPage.setPageText(discussionPageText); discussionPage.setEditSummary('Added note about notifying WikiProject about requested move' + movePlus.advert); discussionPage.save(Morebits.status.actionCompleted('Note added.')); setTimeout(function{ location.reload }, 2000); }); };

//Queues movePlus.processMoveQueue = function movePlusProcessMoveQueue { if (movePlus.moveQueue.length > 0) { let moveTask = movePlus.moveQueue.shift; moveTask; } };

movePlus.startMoveQueue = function movePlusStartMoveQueue {

if (!movePlus.moveInterval) { var moveRateLimit = Morebits.userIsInGroup('sysop') ? 39 : Morebits.userIsInGroup('extendedmover') ? 15 : 7;		movePlus.moveInterval = setInterval(movePlus.processMoveQueue, 60000 / moveRateLimit); } };

movePlus.processEditQueue = function movePlusProcessEditQueue { if (movePlus.editQueue.length > 0) { let editTask = movePlus.editQueue.shift; editTask; } };

movePlus.startEditQueue = function movePlusStartEditQueue {

if (!movePlus.editInterval) { var editRateLimit = 20; movePlus.editInterval = setInterval(movePlus.processEditQueue, 60000 / editRateLimit); } };

function allowBots(text, user){ if (!new RegExp("\\{\\{\\s*(nobots|bots[^}]*)\\s*\\}\\}", "i").test(text)) return true; return (new RegExp("\\{\\{\\s*bots\\s*\\|\\s*deny\\s*=\\s*([^}]*,\\s*)*" + user.replace(/([\(\)\*\+\?\.\-\:\!\=\/\^\$])/g, "\\$1") + "\\s*(?=[,\\}])[^}]*\\s*\\}\\}", "i").test(text)) ? false : new RegExp("\\{\\{\\s*((?!nobots)|bots(\\s*\\|\\s*allow\\s*=\\s*((?!none)|([^}]*,\\s*)*" + user.replace(/([\(\)\*\+\?\.\-\:\!\=\/\^\$])/g, "\\$1") + "\\s*(?=[,\\}])[^}]*|all))?|bots\\s*\\|\\s*deny\\s*=\\s*(?!all)[^}]*|bots\\s*\\|\\s*optout=(?!all)[^}]*)\\s*\\}\\}", "i").test(text); } //