User:Polygnotus/Scripts/Tarnished.js

//This userscript was written by BilledMammal

//https://en.wikipedia.org/wiki/User:BilledMammal/MovePlus.js

//I made some minor changes as requested over at https://en.wikipedia.org/wiki/Wikipedia:User_scripts/Requests#User:BilledMammal/MovePlus.js_and_User:Frost/moveplus.js

const WATCHLISTPAGES = false;

//movePlus

//

var movePlus = {

numberOfMoves: 0,

multiMove: false,

destinations: [],

parsedDate: undefined,

pages: [],

templateIndex: -1,

moveQueue: [],

editQueue: [],

linkAdjustWarning: '\tWarning: 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 = "";

$('#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.');

if (WATCHLISTPAGES) {

talkpage.setWatchlist('watch');

}

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, '{{subst:RM bottom}}\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(/{{[Rr]equested move\/dated\|.*\n?[^\[]*}}/, "{{subst:RM top|" + result + "." + closingComment + userGroupText +"}}");

if (!nextSection) {

text += '\n{{subst:RM bottom}}';

}

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

otherDestinations.push(data.moves[m].destination);

otherPages.push(data.moves[m].current);

}

var pagesLeft = otherPages.length;

for(var j=0; 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

let promise = movePlus.checkPage(currList[i], destList[i]).then(data => {

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}.`);

if (WATCHLISTPAGES) {

pageToMove.setWatchlist('watch');

}

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}`);

if (WATCHLISTPAGES) {

page.setWatchlist('watch');

}

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}`;

}

if (pipedMatch) {

var link = match.replace(from, to);

link = link.replace(`${to}|${to}`, to);

return link

}

return `${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}`);

if (WATCHLISTPAGES) {

redirect.setWatchlist('watch');

}

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

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

var subprojectList = wikiProjectBannerShellHeaders[i];

if (subprojectList.hasChildNodes() && subprojectList.children.length > 2) {

subprojectList = subprojectList.children[2];

if (subprojectList.hasChildNodes() && subprojectList.children.length > 0) {

subprojectList = subprojectList.children;

for (var j=0; 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

var talkpage = new Morebits.wiki.page(wikiProjectsToNotify[i], 'Checking ' + wikiProjectsToNotify[i] + '.');

talkpage.setFollowRedirect(true);

talkpage.load(function(talkpage) {

var wikiProjectToNotify = talkpage.getPageName();

if (!uniqueWikiProjects.includes(wikiProjectToNotify)) {

uniqueWikiProjects.push(wikiProjectToNotify);

}

wikiProjectCount++;

if (wikiProjectCount == wikiProjectsToNotify.length && uniqueWikiProjects.length > 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

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 + " ==\nFile:Information.svg 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];

var displayName = wikiProjectToNotify.startsWith("Wikipedia talk:") ?

wikiProjectToNotify.substring("Wikipedia talk:".length) :

wikiProjectToNotify;

discussionPageText = discussionPageText.replace(regex, ':Note: ' + displayName + ' has been notified of this discussion. ~~~~\n\n' + line);

} else {

var textToInsert = ':Note: ';

for (var j=0; j

var wikiProjectToNotify = wikiProjectsNotified[j];

var displayName = wikiProjectToNotify.startsWith("Wikipedia talk:") ?

wikiProjectToNotify.substring("Wikipedia talk:".length) :

wikiProjectToNotify;

textToInsert += '' + displayName + '';

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];

var displayName = wikiProjectToNotify.startsWith("Wikipedia talk:") ?

wikiProjectToNotify.substring("Wikipedia talk:".length) :

wikiProjectToNotify;

discussionPageText+='\n:Note: ' + displayName + ' has been notified of this discussion. ~~~~';

} else {

discussionPageText += '\n:Note: ';

for (var j=0; j

var wikiProjectToNotify = wikiProjectsNotified[j];

var displayName = wikiProjectToNotify.startsWith("Wikipedia talk:") ?

wikiProjectToNotify.substring("Wikipedia talk:".length) :

wikiProjectToNotify;

discussionPageText += '' + displayName + '';

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);

}

//