User:Qwerfjkl/scripts/massCFDS.js
Appearance
< User:Qwerfjkl | scripts
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
This user script seems to have a documentation page at User:Qwerfjkl/scripts/massCFDS. |
// <nowiki>
// 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: $(`<p>${info}</p>`),
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: $(`<span>${title}</span>`)
});
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 `<a href="/wiki/${title}" target="_blank">${title}</a>`;
}
function parseHTML(html) {
// Create a temporary div to parse the HTML
var tempDiv = $('<div>').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('<span><span id="revertall-text">Reverting...</span> (<span id="revertall-done">0</span> / <span id="revertall-total">' + revertElements.length + '</span> done)</span>');
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. <a href="/wiki/Special:Diff/' + revid + '">Click here</a> 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) ? $('<span>').text('Retrying ').append($(makeLink(title))) : $('<span>').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($('<span>' + makeLink(title) + ' edited successfully</span><span class="masscfdsundo" data-revid="' + data.edit.newrevid + '" data-title="' + title + '"></span>'));
resolve();
} else {
messageElement.setType('error');
messageElement.setLabel($('<span>Error occurred while editing ' + makeLink(title) + ': ' + data + '</span>'));
console.error('Error occurred while prepending text to page:', data);
reject();
}
}).catch(function (error) {
messageElement.setType('error');
messageElement.setLabel($('<span>Error occurred while editing ' + makeLink(title) + ': ' + error + '</span>'));
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 [[Talk:Libyan civil war (2011)/Archive 13#Requested move 17 July 2023|past move discussion]] that resulted in [[First Libyan Civil War]] being moved to [[Libyan civil war (2011)]].'); // from [[:Special:Diff/1223231909#mw-diff-ntitle1]]
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:', '{{subst:cfr-speedy|Category:Bishops}}', info = 'A dollar sign <code>$</code> followed by a number, such as <code>$1</code>, 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(`{{subst:cfr-speedy|$1}}`);
nominationType = 'renaming';
break;
case "merge":
prependTextInputField.setValue(`{{subst:cfm-speedy|$1}}`);
nominationType = 'merging';
break;
}
C2X = dropdown.getMenu().findSelectedItem().getData().split("-").shift();
});
var titleListObj = createTitleAndInputField('List of titles (one per line, <code>Category:</code> prefix is optional)', 'Title1|Target1\nTitle2|Target2a|Target2b\nTitle3|Target3', info = 'You can specify targets by adding a pipe <code>|</code> and then the target, e.g. <code>Category:Example|Category:Target1|Category:Target2</code>. 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($('<span>Edits manually aborted. <a id="masscfdsrevertlink" onclick="revertEdits()">Revert?</a></span>'));
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 = ' ([[User:Qwerfjkl/scripts/massCFDS|via MassCfDS.js]])';
const categorySummary = `Tagging category for [[Wikipedia:Categories for discussion/Speedy|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 = /<!-- *PLACE NEW NOMINATIONS AT THE TOP OF THIS LIST, BELOW THIS LINE *-->/;
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);
// </nowiki>