User:JJPMaster/Scripts/MovePlus.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
Documentation for this user script can be added at User:JJPMaster/Scripts/MovePlus. |
//Fork of [[User:BilledMammal/MovePlus.js]]
//<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 instructions', 'Wikipedia:Requested moves/Closing instructions');
movePlus.Window.addFooterLink('Script documentation', 'User:BilledMammal/Move+');
movePlus.Window.addFooterLink('Give feedback', 'User talk:JJPMaster');
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 are any objections 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 originally proposed, or if there is a consensus to move to a title other than that which was originally 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:JJPMaster');
}
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('Move request 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 (over redirect)';
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]] swap`;
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 swap 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}]]`;
}
if (pipedMatch) {
var link = match.replace(from, to);
link = link.replace(`${to}|${to}`, to);
return link
}
return `[[${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>