Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions src/modules/twinklespeedy.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ Twinkle.speedy = function twinklespeedy() {
return;
}

mw.loader.load('codex-styles');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be loaded only if needed (inside createCodexButton).


Twinkle.addPortletLink(Twinkle.speedy.callback, 'CSD', 'tw-csd', Morebits.userIsSysop ? 'Delete page according to WP:CSD' : 'Request speedy deletion according to WP:CSD');
Twinkle.speedy.addButton();
};

Twinkle.speedy.data = [
Expand Down Expand Up @@ -1010,6 +1013,156 @@ Twinkle.speedy.initDialog = function twinklespeedyInitDialog(callbackfunc) {
Twinkle.speedy.callback.priorDeletionCount();
};

// Fast-track deletion without form
Twinkle.speedy.instantDelete = function twinklespeedyInstantDelete() {
// check if CSD is already on the page and fill in custom rationale
if (!Twinkle.speedy.hasCSD) {
return;
}

const csdReason = decodeURIComponent($('#delete-reason').text()).replace(/\+/g, ' ');

// These three consts are literally copying what is returned by the default params
// This is not optimal at all, and if we could scan the CSD code instead of making it an ugly custom thing
// I'd be all for it
const values = ['reason'];

const templateParams = [[]];
templateParams[0][1] = csdReason;

const normalizeds = values.map((value) => Twinkle.speedy.normalizeHash[value]);

// analyse each criterion to determine whether to watch the page, prompt for summary, or notify the creator
let watchPage, promptForSummary;
normalizeds.forEach((norm) => {
if (Twinkle.getPref('watchSpeedyPages').includes(norm)) {
watchPage = Twinkle.getPref('watchSpeedyExpiry');
}
if (Twinkle.getPref('promptForSpeedyDeletionSummary').includes(norm)) {
promptForSummary = true;
}
});

const warnusertalk = !Twinkle.speedy.hasCSD && normalizeds.some((norm, index) => Twinkle.getPref('warnUserOnSpeedyDelete').includes(norm) &&
!(norm === 'g6' && values[index] !== 'copypaste') && !(norm === 'g5' && values[index] !== 'gs'));

const welcomeuser = warnusertalk && normalizeds.some((norm) => Twinkle.getPref('welcomeUserOnSpeedyDeletionNotification').includes(norm));

const params = {
values: values,
normalizeds: normalizeds,
watch: watchPage,
deleteTalkPage: Twinkle.getPref('deleteTalkPageOnDelete'),
deleteRedirects: Twinkle.getPref('deleteRedirectsOnDelete'),
warnUser: warnusertalk,
welcomeuser: welcomeuser,
promptForSummary: promptForSummary,
templateParams: templateParams
};

Twinkle.speedy.callbacks.sysop.main(params);
};

Twinkle.speedy.instantDecline = function twinklespeedyInstantDecline() {
const wikipediaPage = new Morebits.wiki.Page(mw.config.get('wgPageName'), 'Tagging page');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Tagging page" is not what this does

wikipediaPage.load(Twinkle.speedy.instantDecline.main);
};

Twinkle.speedy.instantDecline.main = function twinklespeedyInstantDeclineMain(pageObj) {
// finds the db template
const text = pageObj.getPageText();
const tag = /(?:\{\{\s*(db|delete|db-.*?|speedy deletion-.*?)(?:\s*\||\s*\}\}))/.exec(text)[1];
let wikitextObj = new Morebits.wikitext.page(text);

// and then removes it
wikitextObj.removeTemplate(tag);

if (wikitextObj.getText()[0] === '\n') {
wikitextObj = new Morebits.wikitext.page(wikitextObj.getText().slice(1));
}

pageObj.setPageText(wikitextObj.getText());

const splitTag = tag.split(/-(.*)/).length > 1 ? tag.split(/-(.*)/)[1] : tag;

if (splitTag in Twinkle.speedy.normalizeHash) {
if (Twinkle.speedy.normalizeHash[splitTag] === 'db') {
pageObj.setEditSummary('Declining speedy deletion (no code provided).');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"(no code provided)" is unnecessary

} else {
pageObj.setEditSummary('Declining speedy deletion ([[WP:CSD#' + Twinkle.speedy.normalizeHash[splitTag].toUpperCase() + '|CSD ' + Twinkle.speedy.normalizeHash[splitTag].toUpperCase() + ']]).');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edit summary needs to include reason for declining. In other places we've done this using prompt().

}
} else {
pageObj.setEditSummary('Declining speedy deletion (multiple criteria).');
}
pageObj.setChangeTags(Twinkle.changeTags);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't handle watchlisting, which should be based on watchSpeedyPages pref, if not a new pref.

pageObj.save(window.location.reload.bind(window.location));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We normally show a completion success message and then reload after a couple of seconds. Also, let's not use .bind - the same could have been written as () => window.location.reload().

};

Twinkle.speedy.createCodexButton = function twinkleSpeedyCreateCodexButton({ label, action = 'neutral', weight = 'normal', size = 'medium', onClick }) {
const button = document.createElement('button');
button.classList.add('cdx-button');

// Action classes
if (action !== 'neutral') {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can drop all these ifs.

button.classList.add(`cdx-button--action-${action}`);
}

// Weight classes
if (weight !== 'normal') {
button.classList.add(`cdx-button--weight-${weight}`);
}

// Size classes
if (size !== 'medium') {
button.classList.add(`cdx-button--size-${size}`);
}

button.textContent = label;

if (typeof onClick === 'function') {
button.addEventListener('click', onClick);
}

return button;
};

// Adds a quick "Delete" button to a speedy deletion template
Twinkle.speedy.addButton = function twinklespeedyAddButton() {
if (!Morebits.userIsSysop) {
return;
}

const buttonNodes = {
delete: Twinkle.speedy.createCodexButton( { label: 'Delete', action: 'destructive', weight: 'primary', size: 'medium', onClick: Twinkle.speedy.instantDelete } ),
decline: Twinkle.speedy.createCodexButton( { label: 'Decline', action: 'neutral', weight: 'normal', size: 'medium', onClick: Twinkle.speedy.instantDecline } )
};

const buttonContainer = document.createElement('div');
buttonContainer.style.display = 'flex';
buttonContainer.style.gap = '1em';
buttonContainer.style.justifyContent = 'center';

// Append the buttons to the container
buttonContainer.appendChild(buttonNodes.delete);
buttonContainer.appendChild(buttonNodes.decline);

const contestButton = document.querySelector(
'table.ambox-speedy form[name="commentbox"].mw-inputbox-form-inline'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of using such complex and fragile selectors, it's better to add a class or id to the button and target that

);

if (contestButton) {
// Replace the "Contest this deletion" link if it exists
contestButton.replaceWith(buttonContainer);
} else {
// Otherwise, add it at the end of the template notice
const notice = document.querySelector('table.ambox-speedy div.mbox-text-span');
if (notice) {
notice.appendChild(document.createTextNode(' ')); // space separator
notice.appendChild(buttonContainer);
}
}
};

Twinkle.speedy.callback.modeChanged = function twinklespeedyCallbackModeChanged(form) {
const namespace = mw.config.get('wgNamespaceNumber');

Expand Down
Loading