Home  

Random  

Nearby  



Log in  



Settings  



Donate  



About Wikipedia  

Disclaimers  



Wikipedia





User:BilledMammal/MovePlus.js





User page  

Talk  



Language  

Watch  

View source  





Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
//movePlus
//<nowiki>
var movePlus = {
 numberOfMoves: 0,
 multiMove: false,
 destinations: [],
 parsedDate: undefined,
 pages: [],
 templateIndex: -1,
 moveQueue: [],
 editQueue: [],
 linkAdjustWarning: '\t<span style="color: red;"><b>Warning:</b></span> 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 = "<button id='movePlusClose'>Close</button><button id='movePlusRelist'>Relist</button><button id='movePlusNotify'>Notify WikiProjects</button><span id='movePlusRelistOptions' style='display:none'><input id='movePlusRelistComment' placeholder='Relisting comment' oninput='if(this.value.length>20){this.size=this.value.length} else{this.size=20}'/><br><button id='movePlusConfirm'>Confirm relist</button><button id='movePlusCancel'>Cancel relist</button></span>";
  $('#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 [[User:BilledMammal/Move+|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 [[WP:RMNOMIN|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 [[WP:RM/TR#Requests to revert undiscussed moves|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 = `<span style="color: red;"><b>Warning:</b></span> ${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, '{{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<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 + ' <span style="color: red;">(<b>Warning:</b> Page has history)</span>';
   }
   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 [[WP:RMTR|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<currList.length; 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}.`);
   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 [[WP:ROUNDROBIN|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 [[WP:ROBIN|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-[[WP:ROUNDROBIN|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-[[WP:ROUNDROBIN|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-[[WP:ROUNDROBIN|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-[[WP:ROUNDROBIN|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}):<br>Updating <a href="https://en.wikipedia.org/wiki/${item.title}" target="_blank" rel="noopener noreferrer">${item.title}</a>...`;
    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) : `[[${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, ':<small>\'\'\'Relisting comment\'\'\': ' + relistingComment + ' ~~~~</small>\n\n' + line);
     break;
    }
   }

   if (!nextSection) {
    text += '\n:<small>\'\'\'Relisting comment\'\'\': ' + relistingComment + ' ~~~~</small>';
   }
  }
  
  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<wikiProjectBannerShellHeaders.length; 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<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<wikiProjectsToNotify.length; 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<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[[File:Information.svg|30px|left]] 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 [[' + pageAndSection + '\|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, ':<small>Note: [[' + wikiProjectToNotify + '|' + wikiProjectToNotify.slice(15) + ']] has been notified of this discussion. ~~~~</small>\n\n' + line);
     } else {
      var textToInsert = ':<small>Note: ';
      for (var j=0; j<wikiProjectsNotified.length; j++) {
       var wikiProjectToNotify = wikiProjectsNotified[j];
       textToInsert += '[[' + wikiProjectToNotify + '|' + 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. ~~~~</small>\n\n';
      discussionPageText = discussionPageText.replace(regex, textToInsert + line);
     }
     break;
    }
   }
  }

  if (!nextSection) {
   if (wikiProjectsNotified.length == 1) {
    var wikiProjectToNotify = wikiProjectsNotified[0];
    discussionPageText+='\n:<small>Note: [[' + wikiProjectToNotify + '|' + wikiProjectToNotify.slice(15) + ']] has been notified of this discussion. ~~~~</small>';
   } else {
    discussionPageText += '\n:<small>Note: ';
    for (var j=0; j<wikiProjectsNotified.length; j++) {
     var wikiProjectToNotify = wikiProjectsNotified[j];
     discussionPageText += '[[' + wikiProjectToNotify + '|' + 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. ~~~~</small>';
   }
  }

  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);
}
//</nowiki>

Retrieved from "https://en.wikipedia.org/w/index.php?title=User:BilledMammal/MovePlus.js&oldid=1233592923"
 



Last edited on 9 July 2024, at 22:23  


Languages

 



This page is not available in other languages.
 

Wikipedia


This page was last edited on 9 July 2024, at 22:23 (UTC).

Content is available under CC BY-SA 4.0 unless otherwise noted.



Privacy policy

About Wikipedia

Disclaimers

Contact Wikipedia

Code of Conduct

Developers

Statistics

Cookie statement

Terms of Use

Desktop