User:Qwerfjkl/scripts/massCFDS.js

// // todo: make counter inline, remove progresss and progressElement from editPAge, more dynamic reatelimit wait. // counter semi inline; adjust align in createProgressBar // Function to wipe the text content of the page inside #bodyContent function wipePageContent { var bodyContent = $('#bodyContent'); if (bodyContent) { bodyContent.empty; }   var header = $('#firstHeading'); if (header) { header.text('Mass CfDS'); }   $('title').text('Mass CfDS - Wikipedia'); }

function createProgressElement { var progressContainer = new OO.ui.PanelLayout({       padded: true,        expanded: false,        classes: ['sticky-container']    }); return progressContainer; }

function makeInfoPopup(info) { var infoPopup = new OO.ui.PopupButtonWidget({       icon: 'info',        framed: false,        label: 'More information',        invisibleLabel: true,        popup: {            head: true,            icon: 'infoFilled',            label: 'More information',            $content: $(` ${info} `),            padded: true,            align: 'force-left',            autoFlip: false        }    }); return infoPopup; }

function createTitleAndInputFieldWithLabel(label, placeholder, classes = []) { var input = new OO.ui.TextInputWidget({       placeholder: placeholder    });

var fieldset = new OO.ui.FieldsetLayout({       classes: classes    });

fieldset.addItems([       new OO.ui.FieldLayout(input, { label: label }),   ]);

return { container: fieldset, inputField: input, }; } // Function to create a title and an input field function createTitleAndInputField(title, placeholder, info = false) { var container = new OO.ui.PanelLayout({       expanded: false    });

var titleLabel = new OO.ui.LabelWidget({       label: $(` ${title} `)    });

var infoPopup = makeInfoPopup(info);

var inputField = new OO.ui.MultilineTextInputWidget({       placeholder: placeholder,        indicator: 'required',        rows: 10,        autosize: true    }); if (info) container.$element.append(titleLabel.$element, infoPopup.$element, inputField.$element); else container.$element.append(titleLabel.$element, inputField.$element); return { titleLabel: titleLabel, inputField: inputField, container: container, infoPopup: infoPopup }; }

// Function to create a title and an input field function createTitleAndSingleInputField(title, placeholder) { var container = new OO.ui.PanelLayout({       expanded: false    });

var titleLabel = new OO.ui.LabelWidget({       label: title    });

var inputField = new OO.ui.TextInputWidget({       placeholder: placeholder,        indicator: 'required'    });

container.$element.append(titleLabel.$element, inputField.$element);

return { titleLabel: titleLabel, inputField: inputField, container: container }; }

function createStartButton { var button = new OO.ui.ButtonWidget({       label: 'Start',        flags: ['primary', 'progressive']    });

return button; }

function createAbortButton { var button = new OO.ui.ButtonWidget({       label: 'Abort',        flags: ['primary', 'destructive']    });

return button; }

function createMessageElement { var messageElement = new OO.ui.MessageWidget({       type: 'progress',        inline: true,        progressType: 'infinite'    }); return messageElement; }

function createRatelimitMessage { var ratelimitMessage = new OO.ui.MessageWidget({       type: 'warning',        style: 'background-color: yellow;'    }); return ratelimitMessage; }

function createCompletedElement { var messageElement = new OO.ui.MessageWidget({       type: 'success',    }); return messageElement; }

function createAbortMessage { // pretty much a duplicate of ratelimitMessage var abortMessage = new OO.ui.MessageWidget({       type: 'warning',    }); return abortMessage; }

function createNominationErrorMessage { // pretty much a duplicate of ratelimitMessage var nominationErrorMessage = new OO.ui.MessageWidget({       type: 'error',        text: 'Could not detect where to add new nomination.'    }); return nominationErrorMessage; }

function createFieldset(headingLabel) { var fieldset = new OO.ui.FieldsetLayout({       label: headingLabel,    }); return fieldset; }

function createMenuOptionWidget(data, label) { var menuOptionWidget = new OO.ui.MenuOptionWidget({       data: data,        label: label    }); return menuOptionWidget; } function createActionDropdown { var items = [ ['C2A-rename', 'C2A (rename)'], ['C2B-rename', 'C2B (rename)'], ['C2C-rename', 'C2C (rename)'], ['C2D-rename', 'C2D (rename)'], ['C2E-rename', 'C2E (rename)'], ['C2F-rename', 'C2F (rename)'], ['C2A-merge', 'C2A (merge)'], ['C2B-merge', 'C2B (merge)'], ['C2C-merge', 'C2C (merge)'], ['C2D-merge', 'C2D (merge)'], ['C2E-merge', 'C2E (merge)'], ['C2F-merge', 'C2F (merge)'], ].map(action => createMenuOptionWidget(...action));

var dropdown = new OO.ui.DropdownWidget({       label: 'Mass action',        menu: {            items        }    }); return dropdown; }

function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }

function makeLink(title) { return `${title}`; }

function parseHTML(html) { // Create a temporary div to parse the HTML var tempDiv = $(' ').html(html);

// Find all li elements var liElements = tempDiv.find('li');

// Array to store extracted hrefs var hrefs = [];

let existinghrefRegexp = /^https:\/\/en\.wikipedia.org\/wiki\/([^?&]+?)$/; let nonexistinghrefRegexp = /^https:\/\/en\.wikipedia\.org\/w\/index\.php\?title=([^&?]+?)&action=edit&redlink=1$/;

// Iterate through each li element liElements.each(function {        // Find all anchor (a) elements within the current li        let hrefline = [];        var anchorElements = $(this).find('a');

// Extract href attribute from each anchor element anchorElements.each(function {            var href = $(this).attr('href');            if (href) {                var existingMatch = existinghrefRegexp.exec(href);                var nonexistingMatch = nonexistinghrefRegexp.exec(href);                let page;                if (existingMatch) page = new mw.Title(existingMatch[1]);                if (nonexistingMatch) page = new mw.Title(nonexistingMatch[1]);                if (page && page.getNamespaceId > -1 && !page.isTalkPage) {                    hrefline.push(page.getPrefixedText);                }

}       });        hrefs.push(hrefline);    });

return hrefs; }

function handlepaste(widget, e) { var types, pastedData, parsedData; // Browsers that support the 'text/html' type in the Clipboard API (Chrome, Firefox 22+) if (e && e.clipboardData && e.clipboardData.types && e.clipboardData.getData) { // Check for 'text/html' in types list types = e.clipboardData.types; if (((types instanceof DOMStringList) && types.contains("text/html")) ||           ($.inArray && $.inArray('text/html', types) !== -1)) { // Extract data and pass it to callback pastedData = e.clipboardData.getData('text/html');

parsedData = parseHTML(pastedData);

// Check if it's an empty array if (!parsedData || parsedData.length === 0) { // Allow the paste event to propagate for plain text or empty array return true; }           let confirmed = confirm('You have pasted formatted text. Do you want this to be converted into wikitext?'); if (!confirmed) return true; processPaste(widget, pastedData);

// Stop the data from actually being pasted e.stopPropagation; e.preventDefault; return false; }   }

// Allow the paste event to propagate for plain text return true; }

function waitForPastedData(widget, savedContent) { // If data has been processed by the browser, process it   if (widget.getValue !== savedContent) { // Retrieve pasted content via widget's getValue var pastedData = widget.getValue;

// Restore saved content widget.setValue(savedContent);

// Call callback processPaste(widget, pastedData); }   // Else wait 20ms and try again else { setTimeout(function {            waitForPastedData(widget, savedContent);        }, 20); } }

function processPaste(widget, pastedData) { // Parse the HTML var parsedArray = parseHTML(pastedData); let stringOutput = ''; for (const pages of parsedArray) { stringOutput += pages.join('|') + '\n'; }   widget.insertContent(stringOutput); }

function getWikitext(pageTitle) { var api = new mw.Api;

var requestData = { "action": "query", "format": "json", "prop": "revisions", "titles": pageTitle, "formatversion": "2", "rvprop": "content", "rvlimit": "1", };   return api.get(requestData).then(function (data) {        var pages = data.query.pages;        return pages[0].revisions[0].content; // Return the wikitext    }).catch(function (error) {        console.error('Error fetching wikitext:', error);    }); }

// function to revert edits function revertEdits { var revertAllCount = 0; var revertElements = $('.masscfdsundo'); if (!revertElements.length) { $('#masscfdsrevertlink').replaceWith('Reverts done.'); } else { $('#masscfdsrevertlink').replaceWith(' Reverting... ( 0 / ' + revertElements.length + ' done) ');

revertElements.each(function (index, element) {           element = $(element); // jQuery-ify            var title = element.attr('data-title');            var revid = element.attr('data-revid');            revertEdit(title, revid)                .then(function  { element.text('. Reverted.'); revertAllCount++; $('#revertall-done').text(revertAllCount); }).catch(function { element.html('. Revert failed. Click here to view the diff.'); });       }).promise.done(function  {            $('#revertall-text').text('Reverts done.');        }); } }

function revertEdit(title, revid, retry = false) { var api = new mw.Api;

if (retry) { sleep(1000); }

var requestData = { action: 'edit', title: title, undo: revid, format: 'json' };   return new Promise(function (resolve, reject) {        api.postWithEditToken(requestData).then(function (data) { if (data.edit && data.edit.result === 'Success') { resolve(true); } else { console.error('Error occurred while undoing edit:', data); reject; }       }).catch(function (error) { console.error('Error occurred while undoing edit:', error); // handle: editconflict, ratelimit (retry) if (error == 'editconflict') { resolve(revertEdit(title, revid, retry = true)); } else if (error == 'ratelimited') { setTimeout(function { // wait a minute                    resolve(revertEdit(title, revid, retry = true));                }, 60000); } else { reject; }       });    }); }

function getUserData(titles) { var api = new mw.Api; return api.get({       action: 'query',        list: 'users',        ususers: titles,        usprop: 'blockinfo|groups', // blockinfo - check if indeffed, groups - check if bot        format: 'json'    }).then(function (data) {        return data.query.users;    }).catch(function (error) {        console.error('Error occurred while fetching page author:', error);        return false;    }); }

function getPageAuthor(title) { var api = new mw.Api; return api.get({       action: 'query',        prop: 'revisions',        titles: title,        rvprop: 'user',        rvdir: 'newer', // Sort the revisions in ascending order (oldest first)        rvlimit: 1,        format: 'json'    }).then(function (data) {        var pages = data.query.pages;        var pageId = Object.keys(pages)[0];        var revisions = pages[pageId].revisions;        if (revisions && revisions.length > 0) {

return revisions[0].user; } else { return false; }   }).catch(function (error) { console.error('Error occurred while fetching page author:', error); return false; }); }

// Function to create a list of page authors and filter duplicates function createAuthorList(titles) { var authorList = []; var promises = titles.map(function (title) {       return getPageAuthor(title);    }); return Promise.all(promises).then(async function (authors) {       let queryBatchSize = 50;        let authorTitles = authors.map(author => author.replace(/ /g, '_')); // Replace spaces with underscores        let filteredAuthorList = [];        for (let i = 0; i < authorTitles.length; i += queryBatchSize) {            let batch = authorTitles.slice(i, i + queryBatchSize);            let batchTitles = batch.join('|');

await getUserData(batchTitles) .then(response => {                   response.forEach(user => { if (user                           && (!user.blockexpiry || user.blockexpiry !== "infinite")                            && !user.groups.includes('bot')                            && !filteredAuthorList.includes('User talk:' + user.name)                        )

filteredAuthorList.push('User talk:' + user.name); });

})               .catch(error => { console.error("Error querying API:", error); });       }        return filteredAuthorList;    }).catch(function (error) {        console.error('Error occurred while creating author list:', error);        return authorList;    }); }

// Function to prepend text to a page function editPage(title, text, summary, progressElement, ratelimitMessage, progress, type, titlesDict, retry = false) { var api = new mw.Api;

var messageElement = createMessageElement;

messageElement.setLabel((retry) ? $(' ').text('Retrying ').append($(makeLink(title))) : $(' ').text('Editing ').append($(makeLink(title)))); progressElement.$element.append(messageElement.$element); var container = $('.sticky-container'); container.scrollTop(container.prop("scrollHeight")); if (retry) { sleep(1000); }

var requestData = { action: 'edit', title: title, summary: summary, format: 'json' };

if (type === 'prepend') { // cat requestData.nocreate = 1; // don't create new cat // parse title var targets = titlesDict[title];

for (let i = 0; i < targets.length; i++) { // we add 1 to i in the replace function because placeholders start from $1 not $0 let placeholder = '$' + (i + 1); text = text.replace(placeholder, targets[i]); }       text = text.replace(/\$\d/g, ''); // remove unmatched |$x requestData.prependtext = text.trim + '\n\n';

} else if (type === 'append') { // user requestData.appendtext = '\n\n' + text.trim; } else if (type === 'text') { requestData.text = text; }   return new Promise(function (resolve, reject) {        if (window.abortEdits) {            // hide message and return            messageElement.toggle(false);            resolve;            return;        }        api.postWithEditToken(requestData).then(function (data) { if (data.edit && data.edit.result === 'Success') { messageElement.setType('success'); messageElement.setLabel($(' ' + makeLink(title) + ' edited successfully  '));

resolve; } else {

messageElement.setType('error'); messageElement.setLabel($(' Error occurred while editing ' + makeLink(title) + ': ' + data + ' ')); console.error('Error occurred while prepending text to page:', data);

reject; }       }).catch(function (error) { messageElement.setType('error'); messageElement.setLabel($(' Error occurred while editing ' + makeLink(title) + ': ' + error + ' ')); console.error('Error occurred while prepending text to page:', error); // handle: editconflict, ratelimit (retry) if (error == 'editconflict') { editPage(title, text, summary, progressElement, ratelimitMessage, progress, type, titlesDict, retry = true).then(function {                    resolve;                }); } else if (error == 'ratelimited') { progress.setDisabled(true);

handleRateLimitError(ratelimitMessage).then(function {                    progress.setDisabled(false);                    editPage(title, text, summary, progressElement, ratelimitMessage, progress, type, titlesDict, retry = true).then(function  { resolve; });               });            }            else { reject; }       });    }); }

// global scope - needed to syncronise ratelimits var massCFDSratelimitPromise = null; // Function to handle rate limit errors function handleRateLimitError(ratelimitMessage) { var modify = !(ratelimitMessage.isVisible); // only do something if the element hasn't already been shown

if (massCFDSratelimitPromise !== null) { return massCFDSratelimitPromise; }

massCFDSratelimitPromise = new Promise(function (resolve) {       var remainingSeconds = 60;        var secondsToWait = remainingSeconds * 1000;        console.log('Rate limit reached. Waiting for ' + remainingSeconds + ' seconds...');

ratelimitMessage.setType('warning'); ratelimitMessage.setLabel('Rate limit reached. Waiting for ' + remainingSeconds + ' seconds...'); ratelimitMessage.toggle(true);

var countdownInterval = setInterval(function {            remainingSeconds--;            if (modify) {                ratelimitMessage.setLabel('Rate limit reached. Waiting for ' + remainingSeconds + ' second' + ((remainingSeconds === 1) ? '' : 's') + '...');           }

if (remainingSeconds <= 0 || window.abortEdits) { clearInterval(countdownInterval); massCFDSratelimitPromise = null; // reset ratelimitMessage.toggle(false); resolve; }       }, 1000);

// Use setTimeout to ensure the promise is resolved even if the countdown is not reached setTimeout(function {            clearInterval(countdownInterval);            ratelimitMessage.toggle(false);            massCFDSratelimitPromise = null; // reset            resolve;        }, secondsToWait); });   return massCFDSratelimitPromise; }

// Function to show progress visually function createProgressBar(label) { var progressBar = new OO.ui.ProgressBarWidget; progressBar.setProgress(0); var fieldlayout = new OO.ui.FieldLayout(progressBar, {       label: label,        align: 'inline'    }); return { progressBar: progressBar, fieldlayout: fieldlayout }; }

// Main function to execute the script async function runMassCFDS {

mw.util.addPortletLink('p-tb', mw.util.getUrl('Special:MassCFDS'), 'Mass CfDS', 'pt-masscfds', 'Create a mass CfDS nomination');

if (/Special:MassCFDS/i.test(mw.config.get('wgPageName'))) { // Load the required modules mw.loader.using('oojs-ui').done(function {            wipePageContent;            //   onbeforeunload = function {            //       return "Closing this tab will cause you to lose all progress.";            //   };            elementsToDisable = [];            var bodyContent = $('#bodyContent');

mw.util.addCSS(`.sticky-container {           bottom: 0;            width: 100%;            max-height: 600px;             overflow-y: auto;          }`);

var rationaleObj = createTitleAndSingleInputField('Rationale:', 'Per past move discussion that resulted in First Libyan Civil War being moved to Libyan civil war (2011).'); // from Special:Diff/1223231909 var rationaleContainer = rationaleObj.container; var rationaleInputField = rationaleObj.inputField; elementsToDisable.push(rationaleInputField);

bodyContent.append(rationaleContainer.$element);

var dropdown = createActionDropdown; elementsToDisable.push(dropdown); dropdown.$element.css('max-width', 'fit-content'); bodyContent.append(dropdown.$element);

var prependTextObj = createTitleAndInputField('Wikitext to tag category page with:', 'Category:Bishops', info = 'A dollar sign  followed by a number, such as , will be replaced with a target specified in the title field, or if not target is specified, will be removed.'); var prependTextLabel = prependTextObj.titleLabel; var prependTextInfoPopup = prependTextObj.infoPopup; var prependTextInputField = prependTextObj.inputField; elementsToDisable.push(prependTextInputField); var prependTextContainer = new OO.ui.PanelLayout({               expanded: false            });

prependTextContainer.$element.append(prependTextLabel.$element, prependTextInfoPopup.$element, dropdown.$element, prependTextInputField.$element); bodyContent.append(prependTextContainer.$element);

var nominationType = false; var C2X = false; dropdown.on('labelChange', function {                switch (dropdown.getMenu.findSelectedItem.getData.split("-").pop) {                    case "rename":                        prependTextInputField.setValue(`$1`);                        nominationType = 'renaming';                        break;                    case "merge":                        prependTextInputField.setValue(`$1`);                        nominationType = 'merging';                        break;                }                C2X = dropdown.getMenu.findSelectedItem.getData.split("-").shift;

});

var titleListObj = createTitleAndInputField('List of titles (one per line,  prefix is optional)', 'Title1|Target1\nTitle2|Target2a|Target2b\nTitle3|Target3', info = 'You can specify targets by adding a pipe   and then the target, e.g.  . These targets can be used in the category tagging step.'); var titleList = titleListObj.container; var titleListInputField = titleListObj.inputField; elementsToDisable.push(titleListInputField); let handler = handlepaste.bind(this, titleListInputField); let textInputElement = titleListInputField.$element.get(0); // Modern browsers. Note: 3rd argument is required for Firefox <= 6 if (textInputElement.addEventListener) { textInputElement.addEventListener('paste', handler, false); }           // IE <= 8 else { textInputElement.attachEvent('onpaste', handler); }

titleListObj.inputField.$element.on('paste', handlepaste); bodyContent.append(titleList.$element);

var startButton = createStartButton; elementsToDisable.push(startButton); bodyContent.append(startButton.$element);

startButton.on('click', async function {

// First check elements var error = false;

if (!(rationaleInputField.getValue.trim)) { rationaleInputField.setValidityFlag(false); error = true; } else { rationaleInputField.setValidityFlag(true); }

if (!titleListInputField.getValue.trim || !titleListInputField.getValue.includes('|') ) { // for CfDS there should always be a target titleListInputField.setValidityFlag(false); error = true; } else { titleListInputField.setValidityFlag(true); }

if (!nominationType) { // needed to select C2X // dropdown.setValidityFlag(false); error = true; } else { // dropdown.setValidityFlag(true); }

// Retreive titles, handle dups var titles = {}; var titleList = titleListInputField.getValue.split('\n'); function capitalise(s) { return s[0].toUpperCase + s.slice(1); }               function normalise(title) { return 'Category:' + capitalise(title.replace(/^ *[Cc]ategory:/, '').trim); }               titleList.forEach(function (title) {                    if (title) {                        var targets = title.split('|');                        var newTitle = targets.shift;                        newTitle = normalise(newTitle);                        if (!Object.keys(titles).includes(newTitle)) {                            titles[newTitle] = targets.map(normalise);                        }                    }                }); if (!(Object.keys(titles).length)) { titleListInputField.setValidityFlag(false); error = true; } else { titleListInputField.setValidityFlag(true); }

if (error) { return; }

for (let element of elementsToDisable) { element.setDisabled(true); }

var abortButton = createAbortButton; bodyContent.append(abortButton.$element); window.abortEdits = false; // initialise abortButton.on('click', function {

// Set abortEdits flag to true if (confirm('Are you sure you want to abort?')) { abortButton.setDisabled(true); window.abortEdits = true; }               });

function processContent(content, titles, textToModify, summary, type, doneMessage, headingLabel) { if (!Array.isArray(titles)) { var titlesDict = titles; titles = Object.keys(titles); }                   var fieldset = createFieldset(headingLabel);

content.append(fieldset.$element);

var progressElement = createProgressElement; fieldset.addItems([progressElement]);

var ratelimitMessage = createRatelimitMessage; ratelimitMessage.toggle(false); fieldset.addItems([ratelimitMessage]);

var progressObj = createProgressBar(`(0 / ${titles.length}, 0 errors)`); // with label var progress = progressObj.progressBar; var progressContainer = progressObj.fieldlayout; // Add margin or padding to the progress bar widget progress.$element.css('margin-top', '5px'); progress.pushPending; fieldset.addItems([progressContainer]);

let resolvedCount = 0; let rejectedCount = 0;

function updateCounter { progressContainer.setLabel(`(${resolvedCount} / ${titles.length}, ${rejectedCount} errors)`); }                   function updateProgress { var percentage = (resolvedCount + rejectedCount) / titles.length * 100; progress.setProgress(percentage);

}

function trackPromise(promise) { return new Promise((resolve, reject) => {                           promise                                .then(value => { resolvedCount++; updateCounter; updateProgress; resolve(value); })                               .catch(error => { rejectedCount++; updateCounter; updateProgress; resolve(error); });                       });                    }

return new Promise(async function (resolve) {                       var promises = [];                        for (const title of titles) {                            var promise = editPage(title, textToModify, summary, progressElement, ratelimitMessage, progress, type, titlesDict);                            promises.push(trackPromise(promise));                            await sleep(100); // space out calls                            await massCFDSratelimitPromise; // stop if ratelimit reached (global variable)                        }

Promise.allSettled(promises) .then(function {                                progress.toggle(false);                                if (window.abortEdits) {                                    var abortMessage = createAbortMessage;                                    abortMessage.setLabel($(' Edits manually aborted. Revert? '));

content.append(abortMessage.$element); } else { var completedElement = createCompletedElement; completedElement.setLabel(doneMessage); completedElement.$element.css('margin-bottom', '16px'); content.append(completedElement.$element); }                               resolve; })                           .catch(function (error) { console.error("Error occurred during title processing:", error); resolve; });                   });                }

var discussionPage = 'Wikipedia:Categories for discussion/Speedy';

const advSummary = ' (via MassCfDS.js)'; const categorySummary = `Tagging category for speedy ${nominationType ? nominationType : 'nomination'})` + advSummary;               const nominationSummary = 'Adding mass speedy nomination' + advSummary;

var batchesToProcess = []; const titlesForTagging = structuredClone(titles); var newNomPromise = new Promise(function (resolve) {               let nominationText = ;                    function makeCategoryNominationText(category, targets, first = false) {                        let targetText = ;                        if (targets.length) {                            if (targets.length === 2) {                                targetText = `to ${targets[0]} and ${targets[1]}`;                            }                            else if (targets.length > 2) {                                let lastTarget = targets.pop;                                targetText = 'to ' + targets.join(', ') + ', and ' + lastTarget + ;                            } else { // 1 target                                targetText = 'to ' + targets[0] + ;                            }                        } return `${first ? '' : '*'}* ${category} ${targetText}${first ? ' – ' + C2X + ': ' + rationaleInputField.getValue.trim + ' ~' : ''}\n`; }                   let firstCategory = Object.keys(titles)[0]; let firstTargets = titles[firstCategory]; delete titles[firstCategory];

nominationText += makeCategoryNominationText(firstCategory, firstTargets, first = true); for (const category in titles) { var targets = titles[category].slice; // copy array nominationText += makeCategoryNominationText(category, targets); }

var newText; var nominationRegex = //; getWikitext(discussionPage).then(function (wikitext) {                       if (!wikitext.match(nominationRegex)) {                            var nominationErrorMessage = createNominationErrorMessage;                            bodyContent.append(nominationErrorMessage.$element);                        } else {                            newText = wikitext.replace(nominationRegex, '$&\n' + nominationText.trimEnd); // $& contains all the matched text                            batchesToProcess.push({ content: bodyContent, titles: [discussionPage], textToModify: newText, summary: nominationSummary, type: 'text', doneMessage: 'Nomination added', headingLabel: 'Creating nomination' });                           resolve;                        }                    }).catch(function (error) {                        console.error('An error occurred in fetching wikitext:', error);                        resolve;                    }); });               await newNomPromise;                batchesToProcess.push({ content: bodyContent, titles: titlesForTagging, textToModify: prependTextInputField.getValue.trim, summary: categorySummary, type: 'prepend', doneMessage: 'All categories edited.', headingLabel: 'Tagging categories' });               let promise = Promise.resolve;                // abort handling is now only in the editPage function                for (const batch of batchesToProcess) {                    await processContent(...Object.values(batch));                }

promise.then( => {                   abortButton.setLabel('Revert');                    // All done                }).catch(err => {                    console.error('Error occurred:', err);                }); });

});   } }

// Run the script when the page is ready $(document).ready(runMassCFDS); //