User:Polygnotus/Scripts/Tarnished.js
//This userscript was written by BilledMammal
//https://en.wikipedia.org/wiki/User:BilledMammal/MovePlus.js
//I made some minor changes as requested over at https://en.wikipedia.org/wiki/Wikipedia:User_scripts/Requests#User:BilledMammal/MovePlus.js_and_User:Frost/moveplus.js
const WATCHLISTPAGES = false;
//movePlus
//
var movePlus = {
numberOfMoves: 0,
multiMove: false,
destinations: [],
parsedDate: undefined,
pages: [],
templateIndex: -1,
moveQueue: [],
editQueue: [],
linkAdjustWarning: '\tWarning: This will automatically update pages, retargeting all links from the current value to the value you specify. You take full responsibility for any action you perform using this script.'
};
window.movePlus = movePlus;
$.when(
mw.loader.using([ 'mediawiki.api', 'ext.gadget.morebits', 'ext.gadget.libExtraUtil' ]),
$.ready
).then(function() {
if (document.getElementById("requestedmovetag") !== null && Morebits.pageNameNorm.indexOf("alk:") !== -1 && mw.config.get('wgCategories').includes('Requested moves') && !document.getElementById("wikiPreview") && mw.config.get('wgDiffOldId') == null) {
document.getElementById("requestedmovetag").innerHTML = "
";$('#movePlusClose').click(movePlus.callback);
$('#movePlusRelist').click(movePlus.confirmRelist);
$('#movePlusConfirm').click(movePlus.relist);
$('#movePlusCancel').click(movePlus.cancelRelist);
$('#movePlusNotify').click(movePlus.notify);
}
var portletLink = mw.util.addPortletLink("p-cactions", "#movePlusMove", "Move\+",
"ca-movepages", "Move pages (expanded options)");
$( portletLink ).click(movePlus.displayWindowMove);
});
movePlus.confirmRelist = function movePlusConfirmRelist(e) {
if (e) e.preventDefault();
document.getElementById("movePlusRelistOptions").style.display = "inline";
document.getElementById("movePlusClose").style.display = "none";
document.getElementById("movePlusRelist").style.display = "none";
document.getElementById("movePlusNotify").style.display = "none";
};
movePlus.cancelRelist = function movePlusCancelRelist(e) {
if (e) e.preventDefault();
document.getElementById("movePlusRelistOptions").style.display = "none";
document.getElementById("movePlusClose").style.display = "inline";
document.getElementById("movePlusRelist").style.display = "inline";
document.getElementById("movePlusNotify").style.display = "inline";
};
movePlus.advert = ' using Move+';
movePlus.preEvaluate = async function() {
try {
const talkPageContent = await loadTalkPage();
return extractTemplateData(talkPageContent);
} catch (error) {
console.error('Error during pre-evaluation:', error);
}
};
async function loadTalkPage() {
var title_obj = mw.Title.newFromText(Morebits.pageNameNorm);
const talkpage = new Morebits.wiki.page(title_obj.getTalkPage().toText(), 'Retrive move proposals.');
return new Promise((resolve, reject) => {
talkpage.load(function(talkpage) {
if (talkpage.exists()) {
resolve(talkpage.getPageText());
} else {
reject('Page does not exist');
}
}, reject);
});
}
function extractTemplateData(text) {
const templatesOnPage = extraJs.parseTemplates(text, false);
let templateData = {};
templatesOnPage.forEach(template => {
if (template.name.toLowerCase() === "requested move/dated") {
templateData = { ...templateData, ...parseRequestedMoveTemplate(template) };
}
});
return templateData;
}
function parseRequestedMoveTemplate(template) {
const data = {
moves: [],
multiMove: template.parameters.some(param => param.name === "multiple")
};
const pairs = {};
template.parameters.forEach(param => {
const match = param.name.toString().match(/^(current|new)?(\d+)$/);
if (match) {
const type = match[1] ? match[1] : "new";
const index = match[2];
if (!pairs[index]) {
pairs[index] = {};
}
if (!pairs[index][type] || param.value != "") {
pairs[index][type] = param.value;
}
}
});
if(!pairs[1]["current"]) {
let title_obj = mw.Title.newFromText(Morebits.pageNameNorm);
pairs[1]["current"] = title_obj.getSubjectPage().toText();
}
Object.keys(pairs).forEach(index => {
const pair = pairs[index];
if (pair.current && pair.new) {
data.moves.push({current: pair.current, destination: pair.new});
}
});
return data;
}
movePlus.callback = async function movePlusCallback(e) {
e.preventDefault(e);
try {
const evaluationData = await movePlus.preEvaluate();
if (evaluationData) {
movePlus.displayWindowClose(evaluationData);
} else {
throw new Error("Failed to retrieve necessary data for processing.");
}
} catch (error) {
console.error('Error during callback execution:', error);
}
};
movePlus.displayWindowClose = function movePlusDisplayWindowClose(data) {
let checkboxStates = {};
movePlus.Window = new Morebits.simpleWindow(600, 450);
movePlus.Window.setTitle( "Close requested move" );
movePlus.Window.setScriptName('Move+');
movePlus.Window.addFooterLink('RM Closing instruction', 'WP:RMCI');
movePlus.Window.addFooterLink('Script documentation', 'User:BilledMammal/Move+');
movePlus.Window.addFooterLink('Give feedback', 'User talk:BilledMammal/Move+');
var form = new Morebits.quickForm(function(e) {
movePlus.evaluate(e, data);
});
setupForm();
function setupForm() {
var resultContainer = form.append({
type: 'div',
style: 'display: flex; flex-direction: row; gap: 10px;'
});
var resultField = setupResultOptions(resultContainer);
setupCustomResult(resultField);
var movedOptionsField = setupMoveOptions(resultContainer);
setupCustomTitles();
setupClosingComment(form);
}
function setupResultOptions(container) {
var resultField = container.append({
type: 'field',
label: 'Result',
style: 'flex: 1;'
});
resultField.append({
type: 'radio',
name: 'result',
required: true,
list: [
{
label: 'Moved',
value: 'moved',
event: function() { updateResultOptions('moved'); }
},
{
label: 'Not moved',
value: 'not moved',
event: function() { updateResultOptions('not moved'); }
},
{
label: 'No consensus',
value: 'no consensus',
event: function() { updateResultOptions('no consensus'); }
},
{
label: 'Custom',
value: 'custom',
event: function() { updateResultOptions('custom'); }
}
]
});
return resultField;
}
function updateResultOptions(result) {
const customResultDisplay = document.getElementsByName('customResult')[0];
const movedOptionsDisplay = document.getElementsByName('movedOptionsField')[0];
const customTitlesDisplay = document.getElementById('customTitles');
const checkboxes = document.querySelectorAll('input[name="movedOptionsInputs"]');
// Default settings
customResultDisplay.style.display = 'none';
customResultDisplay.required = false;
movedOptionsDisplay.style.display = 'none';
customTitlesDisplay.style.display = 'none';
// Unset move options
if (result != 'moved') {
checkboxes.forEach(checkbox => {
if (checkbox.checked) {
checkboxStates[checkbox.value] = true;
checkbox.checked = false;
const event = new Event('change');
checkbox.dispatchEvent(event);
} else {
checkboxStates[checkbox.value] = false;
}
});
}
switch (result) {
case 'moved':
movedOptionsDisplay.style.display = 'block';
// Reset move options
checkboxes.forEach(checkbox => {
if (checkboxStates[checkbox.value]) {
checkbox.checked = true;
const event = new Event('change');
checkbox.dispatchEvent(event);
}
});
break;
case 'custom':
customResultDisplay.style.display = 'inline';
customResultDisplay.required = true;
break;
}
}
function setupMoveOptions(container) {
let originalClosingComment = '';
const movedOptionsField = container.append({
type: 'field',
label: 'Specify move type',
style: 'display: none; flex: 1;',
name: 'movedOptionsField'
});
movedOptionsField.append({
type: 'checkbox',
name: 'movedOptionsInputs',
list: [
{
label: 'Close as uncontested',
value: 'moved-uncontested',
tooltip: 'We treat discussions where no objections have been raised, but community support has also not been demonstrated, as uncontested technical requests.',
event: function(event) {
const closingComment = document.getElementsByName('closingComment')[0];
if (event.target.checked) {
originalClosingComment = closingComment ? closingComment.value : '';
closingComment.value = 'Moved as an uncontested request with minimal participation. If there is any objection within a reasonable time frame, please ask me to reopen the discussion; if I am not available, please ask at the technical requests page.';
} else {
closingComment.value = originalClosingComment;
}
}
},
{
label: 'Specify different titles',
value: 'moved-different-title',
tooltip: 'If no title was origionally proposed, or if there is a consensus to move to a title other than that which was origionally proposed.',
event: function() {
if (event.target.checked) {
customTitles.style.display = 'block';
} else {
customTitles.style.display = 'none';
}
}
}
]
});
return movedOptionsField;
}
function setupCustomResult(resultField) {
resultField.append({
type: 'input',
name: 'customResult',
style: 'display: none;'
});
}
function setupCustomTitles() {
const customTitles = form.append({
type: 'field',
label: 'Specify titles',
id: 'customTitles',
name: 'customTitles',
style: 'display: none;'
});
data.moves.forEach((pair, index) => {
const titleField = customTitles.append({
type: 'div',
className: 'customTitleInput',
style: 'display: flex; align-items: center; margin-bottom: 5px;'
});
titleField.append({
type: 'div',
style: 'flex: 0 1 47.5%; text-align: left;',
label: pair.current
});
titleField.append({
type: 'div',
style: 'flex: 0 1 5%; text-align: center;',
label: '→'
});
const inputDiv = titleField.append({
type: 'div',
style: 'flex: 1;'
});
inputDiv.append({
type: 'input',
name: pair.current,
value: pair.destination,
style: 'width: 95%; text-align: left;'
});
});
const toggleButton = customTitles.append({
type: 'button',
label: 'Hide titles',
event: function(event) {
const titleInputs = document.querySelectorAll('.customTitleInput');
const button = event.target;
titleInputs.forEach(input => {
if (input.style.display === 'none' || input.style.display === '') {
input.style.display = 'flex';
button.value = 'Hide titles';
} else {
input.style.display = 'none';
button.value = 'Show titles';
}
});
}
});
}
function setupClosingComment(form) {
const closingCommentField = form.append({
type: 'field',
label: 'Closing comment'
});
closingCommentField.append({
type: 'textarea',
name: 'closingComment'
});
}
form.append({ type: 'submit', label: 'Submit' });
var formResult = form.render();
movePlus.Window.setContent(formResult);
movePlus.Window.display();
};
movePlus.displayWindowMove = function movePlusDisplayWindowMove() {
var title_obj = mw.Title.newFromText(Morebits.pageNameNorm);
movePlus.title = title_obj.getSubjectPage().toText();
movePlus.displayWindowInit();
movePlus.displayWindowAction();
}
movePlus.displayWindowInit = function movePlusDisplayWindowInit() {
movePlus.Window = new Morebits.simpleWindow(600, 450);
movePlus.Window.setScriptName('Move+');
movePlus.Window.addFooterLink('Moving instructions', 'Wikipedia:Moving a page');
movePlus.Window.addFooterLink('Script documentation', 'User:BilledMammal/Move+');
movePlus.Window.addFooterLink('Give feedback', 'User talk:BilledMammal/Move+');
}
movePlus.displayWindowAction = function movePlusDisplayWindowAction() {
var moveData = [{
current: movePlus.title,
target: ''
}];
var retargetData = [{
current: '',
target: ''
}];
var config = {
move: {
data: moveData,
reason: '',
label: 'Specify moves',
reasonLabel: 'Move reason',
reasonName: 'moveReason',
actionLimit: 100,
buttonLabel: 'Add move',
title: 'Move pages',
information: ''
},
retarget: {
data: retargetData,
reason: '',
label: 'Specify link retargets',
reasonLabel: 'Link retarget reason',
reasonName: 'retargetReason',
actionLimit: 2,
buttonLabel: 'Add retarget',
title: 'Retarget links',
information: movePlus.linkAdjustWarning
}
};
function updateForm(action) {
movePlus.Window.setTitle(config[action].title);
function updateActionDataFromForm() {
config[action].data = [];
config[action].reason = document.querySelector(`textarea[name="${config[action].reasonName}"]`).value;
var currentInputs = document.querySelectorAll('input[name="curr"]');
var targetInputs = document.querySelectorAll('input[name="dest"]');
currentInputs.forEach((input, index) => {
config[action].data.push({
current: input.value,
target: targetInputs[index].value
});
});
};
var form = new Morebits.quickForm(function(e) {
e.preventDefault();
movePlus.params = Morebits.quickForm.getInputData(e.target);
var currentPages = [];
var targetPages = [];
$('input[name="curr"]').each(function(index) {
var currentPage = $(this).val();
var targetPage = $('input[name="dest"]').eq(index).val();
if (currentPage && targetPage) {
currentPages.push(currentPage);
targetPages.push(targetPage);
}
});
if (action == 'move') {
movePlus.movePages(currentPages, targetPages, movePlus.params.moveReason, false);
}
if (action == 'retarget') {
movePlus.retargetLinks(currentPages, targetPages, movePlus.params.retargetReason);
}
});
movePlus.appendOptions(form, action, updateForm, updateActionDataFromForm);
var actionsContainer = form.append({
type: 'field',
label: config[action].label,
id: 'actionList',
name: 'actionList'
});
config[action].data.forEach((data, index) => {
const titleField = actionsContainer.append({
type: 'div',
className: 'titleInput',
style: 'display: flex; align-items: center; margin-bottom: 5px;'
});
const currentDiv = titleField.append({
type: 'div',
style: 'flex: 0 1 47.5%; text-align: left;'
});
currentDiv.append({
type: 'input',
name: 'curr',
value: data.current,
placeholder: 'Current page',
required: true,
style: 'width: 95%; text-align: left;'
});
titleField.append({
type: 'div',
style: 'flex: 0 1 5%; text-align: center;',
label: '→'
});
const destDiv = titleField.append({
type: 'div',
style: 'flex: 0 1 47.5%; text-align: left;'
});
destDiv.append({
type: 'input',
name: 'dest',
value: data.target,
required: true,
placeholder: 'Target page',
style: 'width: 95%; text-align: left;'
});
titleField.append({
type: 'button',
label: 'Remove',
disabled: config[action].data.length < 2 ? true : false,
event: function() {
updateActionDataFromForm();
config[action].data.splice(index, 1);
updateForm(action);
}
});
});
actionsContainer.append({
type: 'button',
label: config[action].buttonLabel,
disabled: config[action].data.length < config[action].actionLimit ? false : true,
event: function() {
updateActionDataFromForm();
config[action].data.push({ current: , target: });
updateForm(action);
}
});
movePlus.appendReason(form, config[action].reasonLabel, config[action].reasonName, config[action].reason);
form.append({
type: 'div',
label: config[action].information,
style: 'margin-left: 15px; margin-right: 15px;'
});
form.append({ type: 'submit', label: 'Submit' });
var formResult = form.render();
movePlus.Window.setContent(formResult);
movePlus.appendReasonAlert(config[action].reasonLabel, config[action].reasonName);
movePlus.Window.display();
}
updateForm('move');
}
movePlus.retargetLinks = async function movePlusRetargetLinks(currentLinks, targetLinks, reason) {
var form = new Morebits.quickForm();
var actionContainer = form.append({
type: 'field',
label: 'Retargeting'
});
actionContainer.append({
type: 'div',
className: 'movePlusProgressBox',
label: ''
});
var multiple = currentLinks[1] ? true : false;
var config = {
currTarget: targetLinks[0],
destTarget: multiple ? targetLinks[1] : ''
}
var formResult = form.render();
movePlus.Window.setContent(formResult);
movePlus.Window.display();
const progressBox = document.querySelector('.movePlusProgressBox');
movePlus.linkEditSummary = reason + ': ';
await movePlus.correctLinks(currentLinks[0], multiple ? currentLinks[1] : '', config, progressBox);
progressBox.innerText = 'Done.'
setTimeout(function(){ movePlus.Window.close(); }, 1250);
}
movePlus.appendOptions = function movePlusAppendOptions(form, action, updateForm, updateActionDataFromForm) {
var optionsContainer = form.append({
type: 'field',
label: 'Options',
style: 'display: flex; flex-direction: row;'
});
optionsContainer.append({
type: 'button',
label: 'Move pages',
name: 'movePages',
disabled: action == 'move' ? true : false,
event: function() {
updateActionDataFromForm();
updateForm('move');
}
});
optionsContainer.append({
type: 'button',
label: 'Retarget page links',
name: 'retargetLinks',
disabled: action == 'retarget' ? true : false,
event: function() {
updateActionDataFromForm();
updateForm('retarget');
}
});
}
movePlus.appendReason = function movePlusAppendReason(form, label, name, value) {
var moveReason = form.append({
type: 'field',
label: label
});
moveReason.append({
type: 'textarea',
name: name,
value: value,
required: true
});
moveReason.append({
type: 'div',
name: name + 'Alert',
style: 'display: block',
label: ''
});
}
movePlus.appendReasonAlert = function appendReasonAlert(label, name) {
const reasonAlert = document.getElementsByName(name + 'Alert')[0];
$(`textarea[name="${name}"]`).on('input', function() {
if (this.value.length > 400) {
reasonAlert.innerHTML = `Warning: ${label} contains ${this.value.length} characters. It may be truncated in the edit summary.`;
reasonAlert.style.display = 'block';
} else {
reasonAlert.style.display = 'none';
}
});
}
movePlus.evaluate = function(e, data) {
var form = e.target;
movePlus.params = Morebits.quickForm.getInputData(form);
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
var title_obj = mw.Title.newFromText(Morebits.pageNameNorm);
movePlus.title = title_obj.getSubjectPage().toText();
movePlus.talktitle = title_obj.getTalkPage().toText();
var result = movePlus.params.result;
if(result == 'custom'){
result = movePlus.params.customResult;
}
var closingComment = movePlus.params.closingComment;
if(closingComment != ""){
closingComment = ' ' + closingComment;
closingComment = closingComment.replace(/\|/g, "{{!}}");
closingComment = closingComment.replace(/=/g, "{{=}}");
}
if (movePlus.params.movedOptionsInputs.includes('moved-different-title')) {
data.moves.forEach(function(pair, index) {
if (movePlus.params[pair.current]) {
data.moves[index].destination = movePlus.params[pair.current];
}
});
}
var talkpage = new Morebits.wiki.page(movePlus.talktitle, 'Closing move.');
if (WATCHLISTPAGES) {
talkpage.setWatchlist('watch');
}
talkpage.load(function(talkpage) {
var text = talkpage.getPageText();
var templatesOnPage = extraJs.parseTemplates(text,false);
var oldMovesPresent = [];
var template;
for (var i = 0; i < templatesOnPage.length; i++) {
if (templatesOnPage[i].name.toLowerCase() == "old moves" || templatesOnPage[i].name.toLowerCase() == "old move") {
oldMovesPresent.push(templatesOnPage[i]);
} else if (templatesOnPage[i].name.toLowerCase() == "requested move/dated") {
template = templatesOnPage[i];
}
}
var templateFound = false;
var numberOfMoves = 0;
var line;
var templateIndex = -1;
var parsedDate;
var rmSection;
var nextSection = false;
var textToFind = text.split('\n');
for (var i = 0; i < textToFind.length; i++) {
line = textToFind[i];
if(templateFound == false){
if(/{{[Rr]equested move\/dated/.test(line)){
templateFound = true;
templateIndex = i;
}
} else if(templateFound == true){
if (/ \(UTC\)/.test(line)){
line = line.substring(line.indexOf("This is a contested technical request"));
parsedDate = line.match(/, ([0-9]{1,2} (January|February|March|April|May|June|July|August|September|October|November|December) [0-9]{4}) \(UTC\)/)[1];
break;
} else if(/→/.test(line)){
numberOfMoves++;
}
}
}
for (var i = templateIndex; i >= 0; i--) {
line = textToFind[i];
if (line.match(/^(==)[^=].+\1/)) {
rmSection = line.match(/^(==)[^=](.+)\1/)[2].trim();
break;
}
}
for (var i = templateIndex+1; i < textToFind.length; i++) {
line = textToFind[i];
if (line.match(/^(==)[^=].+\1/)) {
nextSection = true;
var escapedLine = line.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')
var regex = new RegExp('(' + escapedLine + ')(?![\s\S]*(' + escapedLine + '))', 'm');
text = text.replace(regex, '{{subst:RM bottom}}\n\n' + line);
break;
}
}
var userGroupText = "";
if(Morebits.userIsInGroup('sysop')){
userGroupText = "";
} else if(Morebits.userIsInGroup('extendedmover')){
userGroupText = "|pmc=y";
} else{
userGroupText = "|nac=y";
}
text = text.replace(/{{[Rr]equested move\/dated\|.*\n?[^\[]*}}/, "{{subst:RM top|" + result + "." + closingComment + userGroupText +"}}");
if (!nextSection) {
text += '\n{{subst:RM bottom}}';
}
var multiMove = data.multiMove;
var moveSectionPlain = rmSection;
var date = parsedDate;
var from = '';
var destination = data.moves[0].destination
if(destination == "?"){
destination = "";
}
var link = 'Special:Permalink/' + talkpage.getCurrentID() + '#' + moveSectionPlain;
var archives = text.match(/{{[Aa]rchives/);
if(archives == null){
archives = text.match(/{{[Aa]rchive box/);
if(archives == null){
archives = text.match(/{{[Aa]rchivebox/);
if(archives == null){
archives = text.match(/==.*==/);
}
}
}
if (oldMovesPresent.length == 0) {
if(result == "moved"){
from = '|from=' + movePlus.title;
}
text = text.replace(archives[0], '{{old move'+ '|date=' + date + from + '|destination=' + destination + '|result=' + result + '|link=' + link +'}}\n\n' + archives[0]);
} else if (oldMovesPresent.length == 1) {
var isValidFormat = false;
var isListFormat = false;
var numOldMoves = 0;
for (var i = 0; i < oldMovesPresent[0].parameters.length; i++) {
var parameterName = oldMovesPresent[0].parameters[i].name;
parameterName = parameterName.toString();
if (parameterName == "list") {
isListFormat = true;
break;
} else if (parameterName == "result1") {
isValidFormat = true;
numOldMoves++;
} else if (parameterName.includes("result")) {
numOldMoves++;
}
}
if (isValidFormat && !isListFormat) {
var oldMovesText = oldMovesPresent[0].wikitext;
numOldMoves++;
if(result == "moved"){
from = '|from' + numOldMoves + '=' + movePlus.title;
}
var newTextToAdd = '|date' + numOldMoves + '=' + date + from + '|destination' + numOldMoves + '=' + destination + '|result' + numOldMoves + '=' + result + '|link' + numOldMoves + '=' + link + '}}';
oldMovesText = oldMovesText.substring(0, oldMovesText.length-2) + newTextToAdd;
text = text.replace(oldMovesPresent[0].wikitext, oldMovesText);
} else if (isListFormat) {
if(result == "moved"){
from = '|from=' + movePlus.title;
}
text = text.replace(archives[0], '{{old move'+ '|date=' + date + from + '|destination=' + destination + '|result=' + result + '|link=' + link +'}}\n\n' + archives[0]);
} else {
var oldMovesText = '{{' + oldMovesPresent[0].name;
for (var i = 0; i < oldMovesPresent[0].parameters.length; i++) {
if (oldMovesPresent[0].parameters[i].name == "date") {
oldMovesText += '|date1=' + oldMovesPresent[0].parameters[i].value;
} else if (oldMovesPresent[0].parameters[i].name == "from") {
oldMovesText += '|name1=' + oldMovesPresent[0].parameters[i].value;
} else if (oldMovesPresent[0].parameters[i].name == "destination") {
oldMovesText += '|destination1=' + oldMovesPresent[0].parameters[i].value;
} else if (oldMovesPresent[0].parameters[i].name == "result") {
oldMovesText += '|result1=' + oldMovesPresent[0].parameters[i].value;
} else if (oldMovesPresent[0].parameters[i].name == "link") {
oldMovesText += '|link1=' + oldMovesPresent[0].parameters[i].value;
} else {
oldMovesText += oldMovesPresent[0].parameters[i].wikitext;
}
}
if(result == "moved"){
from = '|from2=' + movePlus.title;
}
var newTextToAdd = '|date2=' + date + from + '|destination2=' + destination + '|result2=' + result + '|link2=' + link + '}}';
oldMovesText += newTextToAdd;
text = text.replace(oldMovesPresent[0].wikitext, oldMovesText);
}
} else {
var oldMovesText = '{{Old moves';
var numOldMoves = 1;
for (var i = 0; i < oldMovesPresent.length; i++) {
for (var j = 0; j < oldMovesPresent[i].parameters.length; j++) {
if (oldMovesPresent[i].parameters[j].name == "date") {
oldMovesText += '|date' + numOldMoves + '=' + oldMovesPresent[i].parameters[j].value;
} else if (oldMovesPresent[i].parameters[j].name == "from") {
oldMovesText += '|name' + numOldMoves + '=' + oldMovesPresent[i].parameters[j].value;
} else if (oldMovesPresent[i].parameters[j].name == "destination") {
oldMovesText += '|destination' + numOldMoves + '=' + oldMovesPresent[i].parameters[j].value;
} else if (oldMovesPresent[i].parameters[j].name == "result") {
oldMovesText += '|result' + numOldMoves + '=' + oldMovesPresent[i].parameters[j].value;
} else if (oldMovesPresent[i].parameters[j].name == "link") {
oldMovesText += '|link' + numOldMoves + '=' + oldMovesPresent[i].parameters[j].value;
} else {
oldMovesText += oldMovesPresent[i].parameters[j].wikitext;
}
}
numOldMoves++;
}
if(result == "moved"){
from = '|from' + numOldMoves + '=' + movePlus.title;
}
var newTextToAdd = '|date' + numOldMoves + '=' + date + from + '|destination' + numOldMoves + '=' + destination + '|result' + numOldMoves + '=' + result + '|link' + numOldMoves + '=' + link + '}}';
oldMovesText += newTextToAdd;
text = text.replace(oldMovesPresent[0].wikitext, oldMovesText);
for (var i = 1; i < oldMovesPresent.length; i++) {
text = text.replace(oldMovesPresent[i].wikitext, "");
}
}
talkpage.setPageText(text);
talkpage.setEditSummary('Closing requested move; ' + result + movePlus.advert);
talkpage.save(Morebits.status.actionCompleted('Moved closed.'));
if(multiMove == true){
var otherDestinations = []
var otherPages = []
for(var m=1; m otherDestinations.push(data.moves[m].destination); otherPages.push(data.moves[m].current); } var pagesLeft = otherPages.length; for(var j=0; j var otherTitle_obj = mw.Title.newFromText(otherPages[j]); movePlus.otherTalktitle = otherTitle_obj.getTalkPage().toText(); var otherPage = new Morebits.wiki.page(movePlus.otherTalktitle, 'Adding {{old move}} to ' + movePlus.otherTalktitle + '.'); otherPage.load(function(otherPage) { var otherText = otherPage.getPageText(); var templatesOnOtherPage = extraJs.parseTemplates(otherText,false); var otherOldMovesPresent = []; for (var i = 0; i < templatesOnOtherPage.length; i++) { if (templatesOnOtherPage[i].name.toLowerCase() == "old moves" || templatesOnOtherPage[i].name.toLowerCase() == "old move") { otherOldMovesPresent.push(templatesOnOtherPage[i]); } } var title = mw.Title.newFromText(otherPage.getPageName()).getSubjectPage().toText(); var OMcurr = otherPages[otherPages.indexOf(title)]; var OMdest = otherDestinations[otherPages.indexOf(title)]; var otherFrom = ''; if(OMdest == "?"){ OMdest == ""; } var otherDestination = OMdest; var otherArchives = otherText.match(/{{[Aa]rchives/); if(otherArchives == null){ otherArchives = otherText.match(/{{[Aa]rchive box/); if(otherArchives == null){ otherArchives = otherText.match(/{{[Aa]rchivebox/); if(otherArchives == null){ otherArchives = otherText.match(/==.*==/); if(otherArchives == null){ //Otherwise, skip it otherArchives = '' } } } } if (otherOldMovesPresent.length == 0) { if(result == "moved"){ otherFrom = '|from=' + OMcurr; } otherText = otherText.replace(otherArchives[0], '{{old move'+ '|date=' + date + otherFrom + '|destination=' + otherDestination + '|result=' + result + '|link=' + link +'}}\n\n' + otherArchives[0]); } else if (otherOldMovesPresent.length == 1) { var isValidFormat = false; var isListFormat = false; var numOldMoves = 0; for (var i = 0; i < otherOldMovesPresent[0].parameters.length; i++) { var parameterName = otherOldMovesPresent[0].parameters[i].name; parameterName = parameterName.toString(); if (parameterName == "list") { isListFormat = true; break; } else if (parameterName == "result1") { isValidFormat = true; numOldMoves++; } else if (parameterName.includes("result")) { numOldMoves++; } } if (isValidFormat && !isListFormat) { var oldMovesText = otherOldMovesPresent[0].wikitext; numOldMoves++; if(result == "moved"){ otherFrom = '|from' + numOldMoves + '=' + OMcurr; } var newTextToAdd = '|date' + numOldMoves + '=' + date + otherFrom + '|destination' + numOldMoves + '=' + otherDestination + '|result' + numOldMoves + '=' + result + '|link' + numOldMoves + '=' + link + '}}'; oldMovesText = oldMovesText.substring(0, oldMovesText.length-2) + newTextToAdd; otherText = otherText.replace(otherOldMovesPresent[0].wikitext, oldMovesText); } else if (isListFormat) { if(result == "moved"){ otherFrom = '|from=' + OMcurr; } otherText = otherText.replace(otherArchives[0], '{{old move'+ '|date=' + date + otherFrom + '|destination=' + otherDestination + '|result=' + result + '|link=' + link +'}}\n\n' + otherArchives[0]); } else { var oldMovesText = '{{' + otherOldMovesPresent[0].name; for (var i = 0; i < otherOldMovesPresent[0].parameters.length; i++) { if (otherOldMovesPresent[0].parameters[i].name == "date") { oldMovesText += '|date1=' + otherOldMovesPresent[0].parameters[i].value; } else if (otherOldMovesPresent[0].parameters[i].name == "from") { oldMovesText += '|name1=' + otherOldMovesPresent[0].parameters[i].value; } else if (otherOldMovesPresent[0].parameters[i].name == "destination") { oldMovesText += '|destination1=' + otherOldMovesPresent[0].parameters[i].value; } else if (otherOldMovesPresent[0].parameters[i].name == "result") { oldMovesText += '|result1=' + otherOldMovesPresent[0].parameters[i].value; } else if (otherOldMovesPresent[0].parameters[i].name == "link") { oldMovesText += '|link1=' + otherOldMovesPresent[0].parameters[i].value; } else { oldMovesText += otherOldMovesPresent[0].parameters[i].wikitext; } } if(result == "moved"){ otherFrom = '|from2=' + OMcurr; } var newTextToAdd = '|date2=' + date + otherFrom + '|destination2=' + otherDestination + '|result2=' + result + '|link2=' + link + '}}'; oldMovesText += newTextToAdd; otherText = otherText.replace(otherOldMovesPresent[0].wikitext, oldMovesText); } } else { var oldMovesText = '{{Old moves'; var numOldMoves = 1; for (var i = 0; i < otherOldMovesPresent.length; i++) { for (var j = 0; j < otherOldMovesPresent[i].parameters.length; j++) { if (otherOldMovesPresent[i].parameters[j].name == "date") { oldMovesText += '|date' + numOldMoves + '=' + otherOldMovesPresent[i].parameters[j].value; } else if (otherOldMovesPresent[i].parameters[j].name == "from") { oldMovesText += '|name' + numOldMoves + '=' + otherOldMovesPresent[i].parameters[j].value; } else if (otherOldMovesPresent[i].parameters[j].name == "destination") { oldMovesText += '|destination' + numOldMoves + '=' + otherOldMovesPresent[i].parameters[j].value; } else if (otherOldMovesPresent[i].parameters[j].name == "result") { oldMovesText += '|result' + numOldMoves + '=' + otherOldMovesPresent[i].parameters[j].value; } else if (otherOldMovesPresent[i].parameters[j].name == "link") { oldMovesText += '|link' + numOldMoves + '=' + otherOldMovesPresent[i].parameters[j].value; } else { oldMovesText += otherOldMovesPresent[i].parameters[j].wikitext; } } numOldMoves++; } if(result == "moved"){ otherFrom = '|from' + numOldMoves + '=' + OMcurr; } var newTextToAdd = '|date' + numOldMoves + '=' + date + otherFrom + '|destination' + numOldMoves + '=' + otherDestination + '|result' + numOldMoves + '=' + result + '|link' + numOldMoves + '=' + link + '}}'; oldMovesText += newTextToAdd; otherText = otherText.replace(otherOldMovesPresent[0].wikitext, oldMovesText); for (var i = 1; i < otherOldMovesPresent.length; i++) { otherText = otherText.replace(otherOldMovesPresent[i].wikitext, ""); } } otherPage.setPageText(otherText); otherPage.setEditSummary('Closing requested move; ' + result + movePlus.advert); otherPage.save(Morebits.status.actionCompleted('Moved closed.')); pagesLeft--; }); } if(result == "moved"){ var waitInterval = setInterval(function(){ if(pagesLeft == 0){ movePlus.movePages([movePlus.title].concat(otherPages),[destination].concat(otherDestinations),link); clearInterval(waitInterval); } }, 500); } else{ setTimeout(function(){ location.reload() }, 2000); } } else if(result == "moved"){ var emptyArray = []; movePlus.movePages([movePlus.title],[destination],link); } else{ setTimeout(function(){ location.reload() }, 2000); } }); }; getTalkPageTitles = function getTalkPageTitles(curr, dest) { var currTitleObj = new mw.Title(curr); var destTitleObj = new mw.Title(dest); var currTalkPage = currTitleObj.getTalkPage().getPrefixedText(); var destTalkPage = destTitleObj.getTalkPage().getPrefixedText(); return { currTalk: currTalkPage, destTalk: destTalkPage }; } getPageByTitle = function getPageByTitle(data, title) { return data.find(page => page.title === title); } movePlus.checkPage = async function movePlusCheckPage(curr, dest) { let talkPages = getTalkPageTitles(curr, dest); let query = { action: 'query', prop: 'info|revisions', inprop: 'protection', titles: `${curr} | ${talkPages.currTalk} | ${dest} | ${talkPages.destTalk}`, format: 'json', rvprop: 'ids' }; let redirectsQuery = { ...query, redirects: 1 } let protection = { curr: { article: {} }, dest: { article: {} }, createLabel: function(type) { let protections = []; let data = this[type]; let label = ''; if (data.move) protections.push("move"); if (data.create) protections.push("create"); if (protections.length > 0) { const formattedProtection = protections.join(" and "); label = ` (${formattedProtection} protected)`; } //As only sysop's can overwrite pages with history we only warn sysops if (data.history && Morebits.userIsInGroup('sysop')) { label = label + ' (Warning: Page has history)'; } return label; }, checkProtection: function() { return this.curr.all || this.dest.all }, checkSysopProtection: function() { return this.curr.admin || this.dest.admin }, checkHistory: function() { return this.dest.history }, checkRedirect: function() { return this.dest.redirect }, checkTarget: function() { return this.curr.target }, checkClosed: function() { return (this.curr.article.redirect && this.dest.article.target) || (this.dest.article.redirect && this.curr.article.target) } } function getRedirectByTitle(data, title) { if (data) { let redirect = data.find(redirect => redirect.from === title); return redirect ? redirect : []; } return []; } try { const pageResponse = await new Morebits.wiki.api(`Accessing information on about edit restrictions on ${dest} and ${curr}`, query).post(); const currData = getPageByTitle(pageResponse.response.query.pages, curr) const currTalkData = getPageByTitle(pageResponse.response.query.pages, talkPages.currTalk) const destData = getPageByTitle(pageResponse.response.query.pages, dest) const destTalkData = getPageByTitle(pageResponse.response.query.pages, talkPages.destTalk) function checkProtection(data, admin = false) { return data.protection.some(protection => { if (admin) { return protection.level == "sysop"; } else { return !(Morebits.userIsInGroup(protection.level) || Morebits.userIsSysop); } }); } function checkHistory(data) { if (data.revisions) { return data.revisions[0].parentid === 0; } return true; } function checkMissing(data) { return data.missing } function processHistory(data, talkData) { protection.dest.history = !checkHistory(data) || !checkHistory(talkData); } async function processRedirects() { const redirectResponse = await new Morebits.wiki.api(`Accessing information about redirect status of ${curr} and ${dest}`, redirectsQuery).post(); processRedirect(redirectResponse, dest, curr, protection.dest, protection.curr); processRedirect(redirectResponse, talkPages.destTalk, talkPages.currTalk, protection.dest, protection.curr); processRedirect(redirectResponse, curr, dest, protection.curr.article, protection.dest.article); processRedirect(redirectResponse, dest, curr, protection.dest.article, protection.curr.article); } function processRedirect(redirectData, origin, target, originStatus, targetStatus) { let data = getRedirectByTitle(redirectData.response.query.redirects, origin) originStatus.redirect = originStatus.redirect !== undefined ? originStatus.redirect : true; targetStatus.target = targetStatus.target !== undefined ? targetStatus.target : true; if (data.length == 0) { if (!checkMissing(getPageByTitle(redirectData.response.query.pages, origin))) { originStatus.history = true; originStatus.redirect = false; targetStatus.target = false; } return; } if (data.to != target) { targetStatus.target = false; } } protection.curr.all = checkProtection(currData) || checkProtection(currTalkData); protection.curr.admin = checkProtection(currData, true) || checkProtection(currTalkData, true); protection.dest.all = checkProtection(destData) || checkProtection(destTalkData); protection.dest.admin = checkProtection(destData, true) || checkProtection(destTalkData, true); protection.dest.history = !checkHistory(destData) || !checkHistory(destTalkData); await processRedirects(); return protection; } catch (error) { console.error('Failed to fetch page details:', error); throw error; } }; movePlus.movePages = function movePlusMovePages(currList, destList, link, closer = true){ movePlus.numberToRemove = currList.length; movePlus.talktitle = mw.Title.newFromText(Morebits.pageNameNorm).getTalkPage().toText(); var pageAndSection = link; var moveSummary, rmtrReason; var promises = []; var configurations = []; function sanitizeClassName(name) { return name.replace(/[^a-zA-Z0-9\-_]/g, '-'); } if (closer) { if (movePlus.params.movedOptionsInputs.includes('moved-uncontested')) { moveSummary = 'Moved, as an uncontested technical request, per ' + pageAndSection + ''; rmtrReason = 'Per lack of objection at ' + pageAndSection + '.'; } else { moveSummary = 'Moved per ' + pageAndSection + ''; rmtrReason = 'Per consensus at ' + pageAndSection + '.'; } } else { moveSummary = link; rmtrReason = link; } var form = new Morebits.quickForm(); var movesContainer = form.append({ type: 'field', label: 'Moves' }); function addButtons(curr, dest, config) { const moveContainer = movesContainer.append({ type: 'div', className: 'movePlusMovePagesRow' + sanitizeClassName(curr), style: 'display: flex; flex-direction: column; margin-bottom: 7px', label: '' }); const rowContainer = moveContainer.append({ type: 'div', className: 'movePlusMovePagesSubRow' + sanitizeClassName(curr), style: 'display: flex; flex-direction: row;', label: '' }); const actionContainer = rowContainer.append({ type: 'div', style: 'display: flex; flex-direction: column; flex: 75%; text-align: left;', className: 'moves' }); const optionsContainer = rowContainer.append({ type: 'div', style: 'display: flex; flex: 25%; text-align: left;', className: 'moveOptions' + sanitizeClassName(curr) }); actionContainer.append({ type: 'div', className: 'movePlusMovePagesLabel', label: config.label }); actionContainer.append({ type: 'div', className: 'movePlusProgressBox', name: sanitizeClassName(curr), label: '', style: 'margin-left: 15px' }); const buttonsContainer = actionContainer.append({ type: 'div', style: 'display: flex; flex-direction: row; margin-left: 15px' }); const linksContainer = moveContainer.append({ type: 'field', label: 'Specify new link targets', style: 'display: none', className: 'specifyLinks' + sanitizeClassName(curr), name: 'specifyLinks', id: 'specifyLinks' }); addRedirectSpecification(linksContainer, curr, dest); addRedirectSpecification(linksContainer, dest, curr); linksContainer.append({ type: 'div', label: movePlus.linkAdjustWarning }); var isSysop = Morebits.userIsInGroup('sysop'); var isMover = Morebits.userIsInGroup('extendedmover'); if (!config.isProtected) { if (config.hasHistory) { if (isMover || isSysop) { addCheckboxes(optionsContainer, curr, isSysop, true, true, !config.isClosed); if (isSysop) { addButton(buttonsContainer, curr, dest, "moveOverPage", true); } addButton(buttonsContainer, curr, dest, 'roundRobin'); } else { addButton(buttonsContainer, curr, dest, 'technicalRequest', config.isSysopProtected); } } else if (config.isRedirect && !config.isTarget) { if (isSysop || isMover) { addCheckboxes(optionsContainer, curr, true, true, true, false); addButton(buttonsContainer, curr, dest, "moveOverPage") } else { addButton(buttonsContainer, curr, dest, 'technicalRequest', config.isSysopProtected); } } else { addButton(buttonsContainer, curr, dest, "move"); if (isSysop || isMover) { addCheckboxes(optionsContainer, curr, true, true, true, false); } else { addCheckboxes(optionsContainer, curr, false, false, true, false); } } } else { addButton(buttonsContainer, curr, dest, 'technicalRequest', config.isSysopProtected); } } function addRedirectSpecification(container, origin, target) { const titleField = container.append({ type: 'div', className: 'titleInput', style: 'display: flex; align-items: center; margin-bottom: 5px;' }); const currentDiv = titleField.append({ type: 'div', style: 'flex: 0 1 47.5%; text-align: left;' }); currentDiv.append({ type: 'div', label: origin, style: 'width: 95%; text-align: left;' }); titleField.append({ type: 'div', style: 'flex: 0 1 5%; text-align: center;', label: '→' }); const futDiv = titleField.append({ type: 'div', style: 'flex: 0 1 47.5%; text-align: left;' }); futDiv.append({ type: 'input', id: 'specifyLinks' + sanitizeClassName(origin), value: target, placeholder: 'Future page', style: 'width: 95%; text-align: left;' }); } function addCheckboxes(container, label, suppressRedirect, moveSubpages, moveTalkPage, correctLinks) { let options = []; if(correctLinks) { options.push({ name: 'correctLinks' + sanitizeClassName(label), label: "Correct links", checked: false, event: function() { if (event.target.checked) { document.querySelector('.specifyLinks' + sanitizeClassName(label)).style.display = 'block'; } else { document.querySelector('.specifyLinks' + sanitizeClassName(label)).style.display = 'none'; } } }); } if(suppressRedirect) { options.push({ name: 'suppressRedirect' + sanitizeClassName(label), label: "Suppress redirect", checked: false }); }; if(moveSubpages) { options.push({ name: 'moveSubpages' + sanitizeClassName(label), label: 'Move subpages', checked: true }); }; if(moveTalkPage) { options.push({ name: 'moveTalkPage' + sanitizeClassName(label), label: 'Move talk page', checked: true }); }; container.append({ type: 'checkbox', style: 'display: flex; flex-direction: column; align-items: left; margin-right: 10px;', list: options }); } function addButton(container, curr, dest, type, admin = false) { let operation; let label; let id; let tooltip = ""; let disabled = false; switch (type) { case "move": operation = async function(progressBox) { config = checkCheckboxes(); await movePlus.movePage(this.name, this.extra, moveSummary, progressBox, config); }; label = 'Move directly'; id = 'moveDirectly'; break; case "moveOverPage": operation = async function(progressBox) { config = checkCheckboxes(); await movePlus.moveOverPage(this.name, this.extra, config.suppressRedirect, moveSummary, admin, progressBox, config.moveSubpages, config.moveTalkPage); }; label = 'Move directly (o)'; id = 'moveOverPage'; break; case "roundRobin": operation = async function(progressBox) { config = checkCheckboxes(); await movePlus.moveRoundRobin(this.name, this.extra, moveSummary, progressBox, config); }; label = 'Move via Round Robin'; id = 'roundRobin'; break; case "technicalRequest": operation = async function(progressBox) { config = checkCheckboxes(); await movePlus.submitRMTR(this.name, this.extra, admin, rmtrReason, progressBox); }; label = 'Submit technical request'; id = 'rmtr'; break; } container.append({ type: 'button', className: 'movePlusMovePages' + sanitizeClassName(curr), name: curr, extra: dest, label: label, tooltip: tooltip, disabled: disabled, id: id, event: async function() { const rowElements = document.querySelectorAll('.movePlusMovePages' + sanitizeClassName(curr)); rowElements.forEach(element => { element.style.display = 'none'; }); const progressBox = document.querySelector('.movePlusProgressBox[name="' + sanitizeClassName(curr) + '"]'); if (progressBox) { progressBox.textContent = 'In progress...'; try { await operation.call(this, progressBox); progressBox.textContent = 'Completed!'; setTimeout(() => { document.querySelector('.movePlusMovePagesRow' + sanitizeClassName(curr)).style.display = 'none'; movePlus.numberToRemove--; }, 1000); } catch (error) { progressBox.textContent = 'Failed. Please implement manually and report this error to the script maintainer.'; } } } }); function checkCheckboxes() { const suppressRedirectElem = document.querySelector('input[name="suppressRedirect' + sanitizeClassName(curr) + '"]'); const moveSubpagesElem = document.querySelector('input[name="moveSubpages' + sanitizeClassName(curr) + '"]'); const moveTalkPageElem = document.querySelector('input[name="moveTalkPage' + sanitizeClassName(curr) + '"]'); const correctLinksElem = document.querySelector('input[name="correctLinks' + sanitizeClassName(curr) + '"]'); let suppressRedirect = suppressRedirectElem ? suppressRedirectElem.checked : false; let moveSubpages = moveSubpagesElem ? moveSubpagesElem.checked : true; let moveTalkPage = moveTalkPageElem ? moveTalkPageElem.checked : true; let correctLinks = correctLinksElem ? correctLinksElem.checked : false; let currTarget, destTarget; if (correctLinks) { currTarget = document.querySelector('#specifyLinks' + sanitizeClassName(curr)).value; destTarget = document.querySelector('#specifyLinks' + sanitizeClassName(dest)).value; } document.querySelector('.specifyLinks' + sanitizeClassName(curr)).style.display = 'none'; document.querySelector('.moveOptions' + sanitizeClassName(curr)).style.display = 'none'; return { suppressRedirect: suppressRedirect, moveSubpages: moveSubpages, moveTalkPage: moveTalkPage, correctLinks: correctLinks, currTarget: currTarget, destTarget: destTarget } } } for(let i=0; i let promise = movePlus.checkPage(currList[i], destList[i]).then(data => { configurations[i] = { label: currList[i] + data.createLabel("curr") + ' → ' + destList[i] + data.createLabel("dest"), isProtected: data.checkProtection(), isSysopProtected: data.checkSysopProtection(), hasHistory: data.checkHistory(), isRedirect: data.checkRedirect(), isTarget: data.checkTarget(), isClosed: data.checkClosed() } }); promises.push(promise) } function setupMulti() { var multiContainer = form.append({ type: 'field', label: 'Multi-action', style: 'display: flex; flex-direction: row' }); multiContainer.append({ type: 'button', label: 'Move all', tooltip: 'Moves all pages. If there are multiple options it will move the page via round robin instead of overwriting history at the destination.', event: async function() { const rows = document.querySelectorAll('[class^="movePlusMovePagesRow"]'); for (const row of rows) { const moveDirectlyButton = row.querySelector('div span input#moveDirectly'); const roundRobinButton = row.querySelector('div span input#roundRobin'); const technicalRequestButton = row.querySelector('div span input#rmtr'); if (moveDirectlyButton) { await moveDirectlyButton.click(); } else if (roundRobinButton) { await roundRobinButton.click(); } else if (technicalRequestButton) { await technicalRequestButton.click(); } } } }); var multiOptionContainer = multiContainer.append({ type: 'div', style: "display: flex; flex-direction: row; justify-content: flex-end; width: 85%;" }); multiOptionContainer.append({ type: 'button', label: 'Suppress all redirects', event: function() { const buttons = document.querySelectorAll('input[name^="suppressRedirect"]'); const button = event.target; const currentLabel = button.value; buttons.forEach(button => { button.checked = currentLabel === 'Suppress all redirects'; }); button.value = currentLabel === 'Suppress all redirects' ? 'Suppress no redirects' : 'Suppress all redirects'; } }); multiOptionContainer.append({ type: 'button', label: 'Move no subpages', event: function() { const buttons = document.querySelectorAll('input[name^="moveSubpages"]'); const button = event.target; const currentLabel = button.value; buttons.forEach(button => { button.checked = currentLabel === 'Move all subpages'; }); button.value = currentLabel === 'Move all subpages' ? 'Move no subpages' : 'Move all subpages'; } }); multiOptionContainer.append({ type: 'button', label: 'Move no talk pages', event: function() { const buttons = document.querySelectorAll('input[name^="moveTalkPage"]'); const button = event.target; const currentLabel = button.value; buttons.forEach(button => { button.checked = currentLabel === 'Move all talk pages'; }); button.value = currentLabel === 'Move all talk pages' ? 'Move no talk pages' : 'Move all talk pages'; } }); } Promise.all(promises).then(() => { configurations.forEach((config, index) => { addButtons(currList[index], destList[index], config); }); setupMulti(); var formResult = form.render(); movePlus.Window.setContent(formResult); movePlus.Window.display(); var moveInterval = setInterval(function(){ if(movePlus.numberToRemove == 0){ movePlus.Window.close(); clearInterval(moveInterval); setTimeout(function(){ location.reload() }, 750); } }, 500); }); }; movePlus.moveOverPage = function movePlusMoveOverPage(curr, dest, suppressRedirect, editSummary, warn, progressBox, subpages = true, talkpage = true) { let destSplit = movePlus.splitPageName(dest); if (warn) { if (!confirm('Warning: You are about to delete a page with history. Do you want to proceed?')) { progressBox.innerText = 'Move cancelled'; throw new Error('Move cancelled.'); } } return new Promise((resolve, reject) => { const moveTask = async () => { progressBox.innerText = `Moving ${curr} to ${dest}.`; const url = 'https://en.wikipedia.org/w/index.php?title=Special:MovePage&action=submit'; const formData = new FormData(); formData.append('wpNewTitleNs', destSplit.namespace); formData.append('wpNewTitleMain', destSplit.pageName); formData.append('wpReasonList', 'other'); formData.append('wpReason', editSummary + movePlus.advert); formData.append('wpWatch', '0'); formData.append('wpLeaveRedirect', suppressRedirect ? '0' : '1'); formData.append('wpMovetalk', talkpage ? '1' : '0'); formData.append('wpMovesubpages', subpages ? '1' : '0'); formData.append('wpDeleteAndMove', '1'); formData.append('wpMove', 'Move page'); formData.append('wpOldTitle', curr); formData.append('wpEditToken', mw.user.tokens.get('csrfToken')); const response = await fetch(url, { method: 'POST', body: formData, credentials: 'include' }); if (response.ok) { progressBox.innerText = `Moved ${curr} to ${dest}.`; Morebits.status.actionCompleted('Moved.'); resolve(); } else { progressBox.innerText = `Failed to move ${curr} to ${dest}.`; reject('Move request failed'); } }; movePlus.moveQueue.push(moveTask); movePlus.startMoveQueue(); }); }; movePlus.movePage = function movePlusMovePage(from, to, editSummary, progressBox, config) { return new Promise((resolve, reject) => { const moveTask = () => { progressBox.innerText = `Moving ${from} to ${to}...`; let pageToMove = new Morebits.wiki.page(from, `Moving ${from} to ${to}.`); if (WATCHLISTPAGES) { pageToMove.setWatchlist('watch'); } pageToMove.setMoveDestination(to); pageToMove.setMoveSubpages(config.moveSubpages); pageToMove.setMoveTalkPage(config.moveTalkPage); pageToMove.setMoveSuppressRedirect(config.suppressRedirect); pageToMove.setEditSummary(`${editSummary}${movePlus.advert}`); console.log(`Moving ${from} to ${to}`); pageToMove.move(() => { progressBox.innerText = `Moved ${from} to ${to}.`; Morebits.status.actionCompleted('Moved.'); resolve(); }, (error) => { reject(error); }); }; movePlus.moveQueue.push(moveTask); movePlus.startMoveQueue(); }); }; movePlus.moveRoundRobin = async function movePlusMoveRoundRobin(curr, dest, editSummary, progressBox, config) { progressBox.innerText = 'Round robin pending...'; config.suppressRedirect = true; try { var destDetails = movePlus.splitPageName(dest); var intermediateTitle = `Draft:Move/${destDetails.pageName}`; editSummary = `${editSummary} via a round robin`; progressBox.innerText = `Moving ${dest} to ${intermediateTitle}...`; await movePlus.movePage(dest, intermediateTitle, editSummary, progressBox, config); progressBox.innerText = `Moving ${curr} to ${dest}...`; await movePlus.movePage(curr, dest, editSummary, progressBox, config); progressBox.innerText = `Moving ${intermediateTitle} to ${curr}...`; await movePlus.movePage(intermediateTitle, curr, editSummary, progressBox, config); progressBox.innerText = `Cleaning up round robin for ${curr} and ${dest}...`; await movePlus.roundRobinCleanup(curr, dest, config); if (config.moveTalkPage) { await movePlus.roundRobinCleanup(movePlus.getTalkPageName(curr), movePlus.getTalkPageName(dest), config); } if (config.correctLinks) { movePlus.linkEditSummary = `Post-move cleanup, following swap of ${curr} and ${dest}: ` progressBox.innerText = `Correcting redirects for ${curr} and ${dest}...`; movePlus.correctRedirects(curr, dest, config); progressBox.innerText = `Correcting links for ${curr} and ${dest}...`; await movePlus.correctLinks(curr, dest, config, progressBox); } } catch (error) { console.error('Error during move operation:', error); } }; movePlus.getLinksHere = async function movePlusGetLinksHere(page) { let query = { action: 'query', prop: 'linkshere', titles: page, lhlimit: 'max', format: 'json', lhnamespace: `${movePlus.splitPageName(page).namespace}|10|14`, rawcontinue: 1 }; let pages = []; do { let response = await new Morebits.wiki.api(`Listing links to ${page}`, query).post(); if (response.response.query.pages[0].linkshere) { pages = pages.concat(response.response.query.pages[0].linkshere); } query.lhcontinue = response.response['query-continue'] ? response.response['query-continue'].linkshere.lhcontinue : 0; } while (query.lhcontinue) return pages; } movePlus.roundRobinCleanup = async function movePlusRoundRobinCleanup(curr, dest, config) { async function queryPagesAndRedirects(page) { let details = movePlus.splitPageName(page); let query = { action: 'query', list: 'allpages', apfrom: `${details.pageName}/`, apto: `${details.pageName}0`, apnamespace: details.namespace, format: 'json' }; let allpages = []; if (config.moveSubpages) { const response = await new Morebits.wiki.api(`Listing subpages of ${details.pageName}`, query).post(); allpages = response.response.query.allpages || []; } let pageTitles = allpages.map(page => page.title).join('|'); if (pageTitles) { pageTitles += '|'; } pageTitles += page; let pageQuery = { action: "query", format: "json", prop: "", titles: pageTitles } const responsePages = await new Morebits.wiki.api(`Getting details of subpages of ${details.pageName}`, pageQuery).post(); const pages = (responsePages.response.query.pages || []).filter(page => !page.missing); pageQuery.redirects = 1; const responseRedirects = await new Morebits.wiki.api(`Getting details of subpages of ${details.pageName}`, pageQuery).post(); const redirects = responseRedirects.response.query.redirects || []; return {redirects, pages}; }; function getPagePairs(pages, pair, self) { return pages.map(page => { const subpageName = movePlus.splitSubpageName(page.title, self); const pagename = subpageName === '' ? pair : `${pair}/${subpageName}`; return { pagename: pagename, target: page.title }; }); } function getRedirectPairs(redirects, pair, self) { return redirects.filter(redirect => redirect.from !== redirect.to).map(redirect => { const subpageName = movePlus.splitSubpageName(redirect.from, self); const pagename = subpageName === '' ? pair : `${pair}/${subpageName}`; return { pagename: pagename, target: redirect.to }; }); } function findSelfRedirects(redirects, pair, self) { return redirects.filter(redirect => redirect.from === redirect.to).map(redirect => { const subpageName = movePlus.splitSubpageName(redirect.from, self); const target = subpageName === '' ? pair : `${pair}/${subpageName}`; return { pagename: redirect.from, target: target }; }); } async function processPages(pairs, existingPages, existingRedirects) { const existingPageNames = new Set(existingPages.map(page => page.title)); const existingRedirectNames = new Set(existingRedirects.map(redirect => redirect.from)); for (const pair of pairs) { if (!existingPageNames.has(pair.pagename) && !existingRedirectNames.has(pair.pagename)) { await movePlus.createRedirect(pair.pagename, pair.target, `Redirecting to ${pair.target} as part of post-round robin cleanup${movePlus.advert}`); } }; }; const currResponse = await queryPagesAndRedirects(curr); const destResponse = await queryPagesAndRedirects(dest); const currRedirects = currResponse.redirects; const currPages = currResponse.pages; const destRedirects = destResponse.redirects; const destPages = destResponse.pages; const currPagePairs = getPagePairs(currPages, dest, curr); const currRedirectPairs = getRedirectPairs(currRedirects, dest, curr); const currSelfRedirects = findSelfRedirects(currRedirects, dest, curr); const destPagePairs = getPagePairs(destPages, curr, dest); const destRedirectPairs = getRedirectPairs(destRedirects, curr, dest); const destSelfRedirects = findSelfRedirects(destRedirects, curr, dest); await processPages(currPagePairs, destPages, destRedirects); await processPages(currRedirectPairs, destPages, destRedirects); await processPages(destPagePairs, currPages, currRedirects); await processPages(destRedirectPairs, currPages, currRedirects); for (const pair of currSelfRedirects.concat(destSelfRedirects)) { await movePlus.createRedirect(pair.pagename, pair.target, `Redirecting to ${pair.target} as part of post-round robin cleanup${movePlus.advert}`); } }; movePlus.correctRedirects = async function movePlusCorrectRedirects(curr, dest, config) { if (config.currTarget != dest && config.currTarget != curr) { await movePlus.createRedirectPair(dest, config.destTarget, `Redirecting to ${config.destTarget} as part of post-round robin cleanup${movePlus.advert}`); } if (config.destTarget != curr && config.destTarget != dest) { await movePlus.createRedirect(curr, config.currTarget, `Redirecting to ${config.currTarget} as part of post-round robin cleanup${movePlus.advert}`); } } movePlus.correctLinks = async function movePlusCorrectLinks(curr, dest, config, progressBox) { let currPages = []; let destPages = []; progressBox.innerText = `Getting links to ${curr}...`; if (curr != config.currTarget) { currPages = await movePlus.getLinksHere(curr); } progressBox.innerText = `Getting links to ${dest}...`; if (dest != "" && dest != config.destTarget) { destPages = await movePlus.getLinksHere(dest); } const pageIdsCurr = new Set(currPages.map(item => item.pageid)); const pageIdsDest = new Set(destPages.map(item => item.pageid)); await processBatches(curr, config.currTarget, currPages, pageIdsDest, progressBox, true, dest, config.destTarget); await processBatches(dest, config.destTarget, destPages, pageIdsCurr, progressBox, false); async function processBatches(origin, target, pages, otherPages, progressBox, processDuplicates, otherOrigin = "", otherTarget = "") { if (origin == target) { return; } let query = { action: "query", format: "json", prop: "categories", titles: "", clcategories: "Category:All_disambiguation_pages|Category:All_set_index_articles" } for (let i = 0; i < pages.length; i += 50) { const batch = pages.slice(i, i + 50); query.titles = batch.map(item => item.title).join('|'); const response = await new Morebits.wiki.api(`Listing categories of pages linking to ${origin}`, query).post(); const dabPages = new Set(); response.response.query.pages.forEach(page => { if (page.categories && page.categories.length > 0) { dabPages.add(page.pageid); } }); let j = i for (const item of batch) { j++; //Skip archives if (item.ns != 0 && item.title.toLowerCase().includes('/archive')) { continue; } progressBox.innerHTML = `${origin} → ${target} (${j}/${pages.length}): if (otherPages.has(item.pageid)) { if (processDuplicates) { await movePlus.correctLink(item, origin, target, dabPages.has(item.pageid), otherOrigin, otherTarget); } } else { await movePlus.correctLink(item, origin, target, dabPages.has(item.pageid)); } } } } } movePlus.correctLink = function movePlusCorrectLink(item, from, to, dab, otherFrom = "", otherTo = "") { return new Promise((resolve, reject) => { console.log(`Updating links at ${item.title}`); let page = new Morebits.wiki.page(item.title, `Updating links at ${item.title}`); if (WATCHLISTPAGES) { page.setWatchlist('watch'); } page.load(function(linkCorrection) { let originalText = page.getPageText(); if (!allowBots(originalText, mw.config.get('wgUserName'))) { console.log(`Forbidden from making edits at ${item.title}`) resolve(); return; } if (otherFrom != "" && otherTo != "") { var updatedText = updateTextRoundRobin(originalText, item, from, to, otherFrom, otherTo, dab); } else { var updatedText = updateText(originalText, item, from, to, dab); } if (updatedText.matches == 0) { console.log(`No edits to make at ${item.title}`); resolve(); return; } let text = updatedText.text; let editSummary = updatedText.editSummary; const editTask = () => { page.setPageText(text); page.setEditSummary(editSummary); page.setMinorEdit(true); page.setBotEdit(true); console.log(`Successfully updated ${item.title}`); page.save(() => { Morebits.status.actionCompleted(`Replaced link to ${from} with link to ${to} at ${item.title}.`); resolve(); }, (error) => { reject(error); }); }; movePlus.editQueue.push(editTask); movePlus.startEditQueue(); }); }); function updateText(text, item, from, to, dab) { let updatedText = processText(text, item, from, to, dab); let editSummary = `${movePlus.linkEditSummary}Changed link from ${from} to ${to}${updatedText.matches > 1 ? ` (×${updatedText.matches})` : ""}${movePlus.advert}`; return {text: updatedText.text, editSummary: editSummary, matches: updatedText.matches}; } function updateTextRoundRobin(text, item, from, to, otherFrom, otherTo, dab) { let stageOneText = processText(text, item, from, "INTERMEDIARYSTAGE", dab); let stageTwoText = processText(stageOneText.text, item, otherFrom, otherTo, dab); let updatedText = processText(stageTwoText.text, item, "INTERMEDIARYSTAGE", to, dab); let editSummary = `${movePlus.linkEditSummary}Changed link from ${from} to ${to} ${stageOneText.matches > 1 ? `(×${stageOneText.matches}) ` : ""}and from ${otherFrom} to ${otherTo}${stageTwoText.matches > 1 ? ` (×${stageTwoText.matches})` : ""}${movePlus.advert}`; return {text: updatedText.text, editSummary: editSummary, matches: stageOneText.matches + stageTwoText.matches}; } function processText(text, item, from, to, dab) { function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } const linkRegex = new RegExp(`\\[\\[${escapeRegExp(from)}(\\|.+|#.*)?\\]\\]`, 'g'); const matches = text.match(linkRegex); if (!matches) { return {text : text, matches: 0} } let updatedText = text.replace(linkRegex, match => { const hashedMatch = match.includes('#'); const pipedMatch = match.includes('|'); if (hashedMatch) { return match.replace(from, to); } if (item.redirect || (dab && !pipedMatch)) { return `${to}`; } if (pipedMatch) { var link = match.replace(from, to); link = link.replace(`${to}|${to}`, to); return link } return `${from}`; }); return {text: updatedText, matches: matches.length}; } }; movePlus.createRedirectPair = async function movePlusCreateRedirectPair(page, target, editSummary, config) { await movePlus.createRedirect(page, target, editSummary); if (config.moveTalkPage) { let currTalk = movePlus.GetTalkPageName(page); let targetTalk = movePlus.GetTalkPageName(target); await movePlus.createRedirect(currTalk, targetTalk, editSummary); } } movePlus.createRedirect = async function movePlusCreateRedirect(page, target, editSummary) { return new Promise((resolve, reject) => { const editTask = () => { let redirect = new Morebits.wiki.page(page, `Creating redirect to ${target}`); if (WATCHLISTPAGES) { redirect.setWatchlist('watch'); } redirect.load(function(redirect) { redirect.setPageText(`#REDIRECT ${target}\n{{Rcat shell|\n{{R from move}}\n}}`); redirect.setEditSummary(editSummary); console.log(`Attempting to redirect ${page} to ${target}`); redirect.save(() => { Morebits.status.actionCompleted(`Cleaned up ${page}.`); resolve(); }, (error) => { reject(error); }); }); }; movePlus.editQueue.push(editTask); movePlus.startEditQueue(); }); }; movePlus.splitPageName = function movePlusSplitPageName(page) { const namespaceSeparator = ':'; const namespaceMap = { '': 0, 'Talk': 1, 'User': 2, 'User talk': 3, 'Wikipedia': 4, 'Wikipedia talk': 5, 'File': 6, 'File talk': 7, 'MediaWiki': 8, 'MediaWiki talk': 9, 'Template': 10, 'Template talk': 11, 'Help': 12, 'Help talk': 13, 'Category': 14, 'Category talk': 15, 'Portal': 100, 'Draft': 118, 'Draft talk': 119, 'TimedText': 710, 'TimedText talk': 711, 'Module': 828, 'Module talk': 829 }; const separatorIndex = page.indexOf(namespaceSeparator); if (separatorIndex === -1) { return { namespace: 0, pageName: page }; } const potentialNamespace = page.substring(0, separatorIndex).trim(); const pageName = page.substring(separatorIndex + 1).trim(); const namespaceNumber = namespaceMap.hasOwnProperty(potentialNamespace) ? namespaceMap[potentialNamespace] : 0; if (namespaceNumber === 0) { return { namespace: 0, pageName: page }; } return { namespace: namespaceNumber, pageName: pageName }; }; movePlus.getTalkPageName = function movePlusGetTalkPageName(page) { const talkNamespaceMap = { 0: 'Talk', 2: 'User talk', 4: 'Wikipedia talk', 6: 'File talk', 8: 'MediaWiki talk', 10: 'Template talk', 12: 'Help talk', 14: 'Category talk', 100: 'Portal talk', 118: 'Draft talk', 710: 'TimedText talk', 828: 'Module talk' }; const splitResult = movePlus.splitPageName(page); const talkNamespace = talkNamespaceMap[splitResult.namespace]; return `${talkNamespace}:${splitResult.pageName}`; }; movePlus.splitSubpageName = function movePlusSplitSubpageName(page, self) { let selfIndex = page.indexOf(self); if (selfIndex === -1 || selfIndex === page.length - self.length) { return ""; } return page.substring(selfIndex + self.length + 1); } movePlus.submitRMTR = function movePlusSubmitRMTR(curr, dest, adminRequired, reason, progressBox) { progressBox.innerText = `Submitting technical request for ${curr} to ${dest}...`; var rmtr = new Morebits.wiki.page('Wikipedia:Requested moves/Technical requests', 'Submitting request at WP:RM/TR'); rmtr.load(function(page) { if (adminRequired) { rmtr.setAppendText('{{subst:RMassist|1=' + curr + '|2=' + dest + '|reason=' + reason + '}}'); } else { var text = rmtr.getPageText(); var textToFind = /\n{1,}(==== ?Requests to revert undiscussed moves ?====)/i; var rmtrText = '{{subst:RMassist|1=' + curr + '|2=' + dest + '|reason=' + reason + '}}'; text = text.replace(textToFind, '\n' + rmtrText + '\n\n$1'); rmtr.setPageText(text); } rmtr.setEditSummary('Add request' + movePlus.advert); rmtr.save(() => { progressBox.innerText = 'Technical request submitted'; Morebits.status.actionCompleted('Requested.') }); }); }; movePlus.relist = function movePlusRelist(e) { if (e) e.preventDefault(); var title_obj = mw.Title.newFromText(Morebits.pageNameNorm); movePlus.talktitle = title_obj.getTalkPage().toText(); var talkpage = new Morebits.wiki.page(movePlus.talktitle, 'Relisting.'); var relistingComment = document.getElementById('movePlusRelistComment').value; talkpage.load(function(talkpage) { var text = talkpage.getPageText(); var templateFound = false; var sig; var line; var templateIndex = -1; var textToFind = text.split('\n'); for (var i = 0; i < textToFind.length; i++) { line = textToFind[i]; if(templateFound == false){ if(/{{[Rr]equested move\/dated/.test(line)){ templateFound = true; templateIndex = i; } } else if(templateFound == true){ if (/ \(UTC\)/.test(line)){ sig = line; break; } } } text = text.replace(sig, sig + " {{subst:RM relist}}"); if(relistingComment != ''){ var nextSection = false; for (var i = templateIndex+1; i < textToFind.length; i++) { line = textToFind[i]; if (line.match(/^(==)[^=].+\1/)) { nextSection = true; var escapedLine = line.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') var regex = new RegExp('(' + escapedLine + ')(?![\s\S]*(' + escapedLine + '))', 'm'); text = text.replace(regex, ':\'\'\'Relisting comment\'\'\': ' + relistingComment + ' ~~~~\n\n' + line); break; } } if (!nextSection) { text += '\n:\'\'\'Relisting comment\'\'\': ' + relistingComment + ' ~~~~'; } } talkpage.setPageText(text); talkpage.setEditSummary('Relisted requested move' + movePlus.advert); talkpage.save(Morebits.status.actionCompleted('Relisted.')); document.getElementById("requestedmovetag").innerHTML = ""; setTimeout(function(){ location.reload() }, 2000); }); }; movePlus.notify = function movePlusNotify(e) { if (e) e.preventDefault(); var wikiProjectTemplates = document.getElementsByClassName("wpb-project_link"); var wikiProjectNames = []; var wikiProjects = []; for(var i=0; i var wikiProjectName = wikiProjectTemplates[i].innerHTML; var wikiProjectTalk = mw.Title.newFromText(wikiProjectTemplates[i].innerHTML).getTalkPage().toText(); if (!wikiProjectNames.includes(wikiProjectName)) { wikiProjectNames.push(wikiProjectName); wikiProjects.push(wikiProjectTalk); } } var wikiProjectBannerShellHeaders = document.getElementsByClassName("wpb-header-combined"); for (var i=0; i var subprojectList = wikiProjectBannerShellHeaders[i]; if (subprojectList.hasChildNodes() && subprojectList.children.length > 2) { subprojectList = subprojectList.children[2]; if (subprojectList.hasChildNodes() && subprojectList.children.length > 0) { subprojectList = subprojectList.children; for (var j=0; j var wikiProjectName = subprojectList[j].title; var wikiProjectTalk = mw.Title.newFromText(subprojectList[j].title).getTalkPage().toText(); if (!wikiProjectNames.includes(wikiProjectName)) { wikiProjectNames.push(wikiProjectName); wikiProjects.push(wikiProjectTalk); } } } } } if(wikiProjects.length == 0){ mw.notify('No WikiProject banners found on this page'); } else{ var Window = new Morebits.simpleWindow(600, 450); Window.setTitle( "Notify WikiProjects about requested move" ); Window.setScriptName('movePlus'); Window.addFooterLink('Script documentation', 'User:BilledMammal/movePlus'); Window.addFooterLink('Give feedback', 'User talk:BilledMammal/movePlus'); var form = new Morebits.quickForm(movePlus.notifyCheck); form.append({ type: 'div', label: 'WikiProjects with banners on this page:' }); form.append({ type: 'checkbox', name: 'wikiProject', list: wikiProjects.map(function (wp) { var wplabel = wikiProjectNames[wikiProjects.indexOf(wp)]; return { type: 'option', label: wplabel, value: wp }; }) }); if(wikiProjects[0] != 'none'){ form.append({ type: 'submit', label: 'Notify selected WikiProject(s)' }); } var formResult = form.render(); Window.setContent(formResult); Window.display(); } }; movePlus.notifyCheck = function(e) { var form = e.target; movePlus.params = Morebits.quickForm.getInputData(form); Morebits.simpleWindow.setButtonsEnabled(false); Morebits.status.init(form); var wikiProjectsToNotify = movePlus.params.wikiProject; if (wikiProjectsToNotify.length == 0) { Morebits.status.error('Error', 'No WikiProjects selected'); } else { var uniqueWikiProjects = []; var wikiProjectCount = 0; for (var i=0; i var talkpage = new Morebits.wiki.page(wikiProjectsToNotify[i], 'Checking ' + wikiProjectsToNotify[i] + '.'); talkpage.setFollowRedirect(true); talkpage.load(function(talkpage) { var wikiProjectToNotify = talkpage.getPageName(); if (!uniqueWikiProjects.includes(wikiProjectToNotify)) { uniqueWikiProjects.push(wikiProjectToNotify); } wikiProjectCount++; if (wikiProjectCount == wikiProjectsToNotify.length && uniqueWikiProjects.length > 0) { movePlus.notifyGetSection(uniqueWikiProjects); } }); } } }; movePlus.notifyGetSection = function(wikiProjectsToNotify) { var title_obj = mw.Title.newFromText(Morebits.pageNameNorm); movePlus.talktitle = title_obj.getTalkPage().toText(); var talkpage = new Morebits.wiki.page(movePlus.talktitle, 'Getting section.'); talkpage.load(function(talkpage) { var text = talkpage.getPageText(); var line; var templateIndex = -1; var rmSection; var textToFind = text.split('\n'); for (var i = 0; i < textToFind.length; i++) { line = textToFind[i]; if(/{{[Rr]equested move\/dated/.test(line)){ templateIndex = i; break; } } for (var i = templateIndex; i >= 0; i--) { line = textToFind[i]; if (line.match(/^(==)[^=].+\1/)) { rmSection = line.match(/^(==)[^=](.+)\1/)[2].trim(); break; } } movePlus.notifyEvaluate(wikiProjectsToNotify, rmSection); }); }; movePlus.notifyEvaluate = function(wikiProjectsToNotify, moveSection) { var wikiProjectsNotified = []; var wikiProjectCount = 0; for (var j=0; j var talkpage = new Morebits.wiki.page(wikiProjectsToNotify[j], 'Notifying ' + wikiProjectsToNotify[j] + '.'); talkpage.setFollowRedirect(true); talkpage.load(function(talkpage) { var wikiProjectToNotify = talkpage.getPageName(); var text = talkpage.getPageText(); movePlus.talktitle = mw.Title.newFromText(Morebits.pageNameNorm).getTalkPage().toText(); var pageAndSection = movePlus.talktitle + "#" + moveSection; var notified; if(confirm("\"" + wikiProjectToNotify + "\" may have already been notified of the discussion. Do you wish to proceed?")){ text += "\n\n== Requested move at " + pageAndSection + " ==\nFile:Information.svg There is a requested move discussion at " + pageAndSection + " that may be of interest to members of this WikiProject. ~~~~"; talkpage.setPageText(text); talkpage.setEditSummary('Notifying of requested move' + movePlus.advert); talkpage.save(Morebits.status.actionCompleted('Notified.')); notified = true; } else{ var cancelNotify = new Morebits.status('Error', 'Notification canceled', 'error'); notified = false; } if(notified){ wikiProjectsNotified.push(wikiProjectToNotify); } wikiProjectCount++; if (wikiProjectCount == wikiProjectsToNotify.length && wikiProjectsNotified.length > 0) { movePlus.notifyListOnTalkPage(wikiProjectsNotified); } }); } }; movePlus.notifyListOnTalkPage = function(wikiProjectsNotified) { var discussionPage = new Morebits.wiki.page(movePlus.talktitle, 'Adding note about notification to requested move'); discussionPage.load(function(discussionPage) { var discussionPageText = discussionPage.getPageText(); var templateFound = false; var line; var nextSection = false; var textToFind = discussionPageText.split('\n'); for (var i = 0; i < textToFind.length; i++) { line = textToFind[i]; if(templateFound == false){ if(/{{[Rr]equested move\/dated/.test(line)){ templateFound = true; } } else if(templateFound == true){ if (line.match(/^(==)[^=].+\1/)) { nextSection = true; var escapedLine = line.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') var regex = new RegExp('(' + escapedLine + ')(?![\s\S]*(' + escapedLine + '))', 'm'); if (wikiProjectsNotified.length == 1) { var wikiProjectToNotify = wikiProjectsNotified[0]; var displayName = wikiProjectToNotify.startsWith("Wikipedia talk:") ? wikiProjectToNotify.substring("Wikipedia talk:".length) : wikiProjectToNotify; discussionPageText = discussionPageText.replace(regex, ':Note: ' + displayName + ' has been notified of this discussion. ~~~~\n\n' + line); } else { var textToInsert = ':Note: '; for (var j=0; j var wikiProjectToNotify = wikiProjectsNotified[j]; var displayName = wikiProjectToNotify.startsWith("Wikipedia talk:") ? wikiProjectToNotify.substring("Wikipedia talk:".length) : wikiProjectToNotify; textToInsert += '' + displayName + ''; if (j == wikiProjectsNotified.length-2) { if (wikiProjectsNotified.length == 2) { textToInsert += ' and '; } else { textToInsert += ', and '; } } else if (j != wikiProjectsNotified.length-1) { textToInsert += ', '; } } textToInsert += ' have been notified of this discussion. ~~~~\n\n'; discussionPageText = discussionPageText.replace(regex, textToInsert + line); } break; } } } if (!nextSection) { if (wikiProjectsNotified.length == 1) { var wikiProjectToNotify = wikiProjectsNotified[0]; var displayName = wikiProjectToNotify.startsWith("Wikipedia talk:") ? wikiProjectToNotify.substring("Wikipedia talk:".length) : wikiProjectToNotify; discussionPageText+='\n:Note: ' + displayName + ' has been notified of this discussion. ~~~~'; } else { discussionPageText += '\n:Note: '; for (var j=0; j var wikiProjectToNotify = wikiProjectsNotified[j]; var displayName = wikiProjectToNotify.startsWith("Wikipedia talk:") ? wikiProjectToNotify.substring("Wikipedia talk:".length) : wikiProjectToNotify; discussionPageText += '' + displayName + ''; if (j == wikiProjectsNotified.length-2) { if (wikiProjectsNotified.length == 2) { discussionPageText += ' and '; } else { discussionPageText += ', and '; } } else if (j != wikiProjectsNotified.length-1) { discussionPageText += ', '; } } discussionPageText += ' have been notified of this discussion. ~~~~'; } } discussionPage.setPageText(discussionPageText); discussionPage.setEditSummary('Added note about notifying WikiProject about requested move' + movePlus.advert); discussionPage.save(Morebits.status.actionCompleted('Note added.')); setTimeout(function(){ location.reload() }, 2000); }); }; //Queues movePlus.processMoveQueue = function movePlusProcessMoveQueue() { if (movePlus.moveQueue.length > 0) { let moveTask = movePlus.moveQueue.shift(); moveTask(); } }; movePlus.startMoveQueue = function movePlusStartMoveQueue() { if (!movePlus.moveInterval) { var moveRateLimit = Morebits.userIsInGroup('sysop') ? 39 : Morebits.userIsInGroup('extendedmover') ? 15 : 7; movePlus.moveInterval = setInterval(movePlus.processMoveQueue, 60000 / moveRateLimit); } }; movePlus.processEditQueue = function movePlusProcessEditQueue() { if (movePlus.editQueue.length > 0) { let editTask = movePlus.editQueue.shift(); editTask(); } }; movePlus.startEditQueue = function movePlusStartEditQueue() { if (!movePlus.editInterval) { var editRateLimit = 20; movePlus.editInterval = setInterval(movePlus.processEditQueue, 60000 / editRateLimit); } }; function allowBots(text, user){ if (!new RegExp("\\{\\{\\s*(nobots|bots[^}]*)\\s*\\}\\}", "i").test(text)) return true; return (new RegExp("\\{\\{\\s*bots\\s*\\|\\s*deny\\s*=\\s*([^}]*,\\s*)*" + user.replace(/([\(\)\*\+\?\.\-\:\!\=\/\^\$])/g, "\\$1") + "\\s*(?=[,\\}])[^}]*\\s*\\}\\}", "i").test(text)) ? false : new RegExp("\\{\\{\\s*((?!nobots)|bots(\\s*\\|\\s*allow\\s*=\\s*((?!none)|([^}]*,\\s*)*" + user.replace(/([\(\)\*\+\?\.\-\:\!\=\/\^\$])/g, "\\$1") + "\\s*(?=[,\\}])[^}]*|all))?|bots\\s*\\|\\s*deny\\s*=\\s*(?!all)[^}]*|bots\\s*\\|\\s*optout=(?!all)[^}]*)\\s*\\}\\}", "i").test(text); } //
Updating ${item.title}...`;