User:DR/test.js: Difference between revisions

From Test Wiki
Content deleted Content added
DR (talk | contribs)
No edit summary
DR (talk | contribs)
test
Line 1: Line 1:
//<nowiki>
//<nowiki>
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'oojs-ui'], function () {
$(document).ready(function() {
'use strict';
function initializeMassDelete() {
const page = 'User:DR/Global rename blacklist';
$('#mw-content-text > p').remove();
const api = new mw.Api();
$('#firstHeading').text('MassDelete');
let token = null;
async function getToken() {
if (token) return token;
const res = await api.get({
action: 'query',
meta: 'tokens',
type: 'csrf'
});
token = res.query.tokens.csrftoken;
return token;
}
function BlacklistUserInputWidget() {
OO.ui.Widget.call(this);
this.textInput = new OO.ui.TextInputWidget({
placeholder: 'Username to blacklist',
required: true,
rows: 1
});
this.regexInput = new OO.ui.TextInputWidget({
placeholder: 'Regex (auto-generated, editable)',
required: true
});
this.textInput.on('change', () => {
const val = this.textInput.getValue();
if (val) {
this.regexInput.setValue('^' + val.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&') + '$');
} else {
this.regexInput.setValue('');
}
});
this.textInput.$element.css('width', '48%');
this.regexInput.$element.css('width', '48%').css('margin-left', '2%');
this.layout = new OO.ui.HorizontalLayout({
items: [ this.textInput, this.regexInput ]
});
this.$element.addClass('blacklistUserInputWidget').css('margin-bottom', '5px');
this.$element.append(this.layout.$element);
}
OO.inheritClass(BlacklistUserInputWidget, OO.ui.Widget);


function createUserField() {
var deletionModeLabel = $('<p>').text('Deletion mode:').css('font-weight', 'bold');
return new BlacklistUserInputWidget();
var deletionModeSelect = new OO.ui.RadioSelectWidget({
}
items: [
new OO.ui.RadioSelectOptionWidget({ data: 'list', label: 'List of pages' }),
new OO.ui.RadioSelectOptionWidget({ data: 'category', label: 'Category' })
]
});
deletionModeSelect.selectItemByData('list');


function BlacklistDialog(config) {
var pagesTextarea = new OO.ui.MultilineTextInputWidget({
BlacklistDialog.super.call(this, config);
placeholder: 'Enter list of pages (one per line)',
}
autosize: true,
OO.inheritClass(BlacklistDialog, OO.ui.ProcessDialog);
rows: 10
BlacklistDialog.static.name = 'BlacklistDialog';
}),
BlacklistDialog.static.title = 'Global rename blacklist';
reasonInputField = new OO.ui.TextInputWidget({ placeholder: 'Reason for deletion' }),
BlacklistDialog.static.actions = [
deleteTalkCheckbox = new OO.ui.CheckboxInputWidget({ selected: true }),
previewButton = new OO.ui.ButtonWidget({ label: 'Preview Deletion', flags: ['primary'] }),
{ action: 'accept', label: 'Submit', flags: ['primary', 'progressive'] },
{ action: 'cancel', label: 'Cancel', flags: 'safe' }
startButton = new OO.ui.ButtonWidget({ label: 'Start Deletion', icon: 'alert', flags: ['primary', 'progressive'], disabled: true }),
];
cancelButton = new OO.ui.ButtonWidget({ label: 'Cancel', flags: ['primary', 'destructive'], href: 'https:' + mw.config.get('wgServer') }),
BlacklistDialog.prototype.initialize = function () {
logContainer = $('<div>').hide();
BlacklistDialog.super.prototype.initialize.apply(this, arguments);
const loggedInUser = mw.config.get('wgUserName');
this.userFieldset = new OO.ui.FieldsetLayout({ label: 'User(s) to blacklist' });
this.userFieldset.addItems([ createUserField() ]);
const addButton = new OO.ui.ButtonWidget({
label: '+',
flags: ['progressive']
});
addButton.on('click', () => {
this.userFieldset.addItems([ createUserField() ]);
this.updateSize();
});


this.commentInput = new OO.ui.MultilineTextInputWidget({
var labels = {
placeholder: 'Reason for blacklisting',
deletionModeLabel: deletionModeLabel,
required: true,
pagesLabel: $('<p>').text('Pages / Categories:').css('font-weight', 'bold'),
rows: 2
reasonLabel: $('<p>').text('Reason:').css('font-weight', 'bold'),
});
deleteTalkLabel: $('<label>')
.append(deleteTalkCheckbox.$element, $('<span>').text(' Also delete talk pages').css('padding-left', '5px'))
.css({ 'margin-top': '5px', 'margin-bottom': '5px' })
};


this.expiryDropdown = new OO.ui.DropdownInputWidget({
previewButton.$element.css('margin-top', '10px');
options: [
{ data: '1m', label: '1 month' },
{ data: '3m', label: '3 months' },
{ data: '6m', label: '6 months' },
{ data: '1y', label: '1 year' },
{ data: '2y', label: '2 years' },
{ data: 'infinite', label: 'No expiry' },
{ data: 'custom', label: 'Custom…' }
],
value: '1m'
});


this.customExpiryInput = new OO.ui.TextInputWidget({
$('#mw-content-text').append(
placeholder: 'Custom expiry (e.g., 2027-12-31, 18 months)',
labels.deletionModeLabel,
disabled: true
deletionModeSelect.$element,
});
labels.pagesLabel, pagesTextarea.$element,
labels.reasonLabel, reasonInputField.$element,
labels.deleteTalkLabel,
'<br/>',
previewButton.$element,
startButton.$element,
cancelButton.$element,
'<br/>',
logContainer
);


deletionModeSelect.on('choose', function(item) {
this.expiryDropdown.on('change', () => {
if (item.getData() === 'category') {
if (this.expiryDropdown.getValue() === 'custom') {
this.customExpiryInput.setDisabled(false);
pagesTextarea.setPlaceholder('Enter category names (one per line)');
} else {
} else {
this.customExpiryInput.setValue('');
pagesTextarea.setPlaceholder('Enter list of pages (one per line)');
this.customExpiryInput.setDisabled(true);
}
}
logContainer.empty().hide();
});
startButton.setDisabled(true);
});


this.userBox = new OO.ui.TextInputWidget({value: loggedInUser, readOnly: true});
function getPagesFromCategory(category) {
return $.ajax({
url: mw.util.wikiScript('api'),
data: { action: 'query', list: 'categorymembers', cmtitle: 'Category:' + category, cmlimit: 'max', format: 'json' },
dataType: 'json'
}).then(function(data) {
return data.query?.categorymembers?.map(item => item.title) || [];
});
}


this.content = new OO.ui.PanelLayout({ padded: true, expanded: false });
function deletePage(page, reason, deleteTalk, callback) {
this.content.$element.append(
var params = { action: 'delete', title: page, reason: reason };
new OO.ui.FieldsetLayout({
if (deleteTalk) params.deletetalk = true;
items: [
(new mw.Api({ ajax: { headers: { 'Api-User-Agent': 'en:User:DreamRimmer/MassDelete.js' } } }))
new OO.ui.FieldLayout(this.userBox, {label: 'Global renamer/Steward:', align: 'top'}),
.postWithToken('csrf', params, { async: false })
this.userFieldset,
.done(data => callback(null, data, page))
new OO.ui.FieldLayout(addButton),
.fail((code, data) => callback(code, data, page));
new OO.ui.FieldLayout(this.commentInput, { label: 'Comment:', align: 'top' }),
}
new OO.ui.FieldLayout(this.expiryDropdown, { label: 'Expiry:', align: 'top' }),
new OO.ui.FieldLayout(this.customExpiryInput, { label: '', align: 'top' })
]
}).$element
);
this.$body.append(this.content.$element);
};
BlacklistDialog.prototype.getBodyHeight = function () {
return this.content.$element.outerHeight(true) + 20;
};
BlacklistDialog.prototype.getActionProcess = function (action) {
if (action === 'accept') {
return new OO.ui.Process(async () => {
const widgets = this.userFieldset.items.filter(x => x instanceof BlacklistUserInputWidget);
const blacklistInputs = widgets.map(widget => ({
username: widget.textInput.getValue().trim(),
regex: widget.regexInput.getValue().trim()
}));
const blacklistUsers = blacklistInputs.filter(u => u.username && u.regex);
const comment = this.commentInput.getValue().trim();
if (!blacklistUsers.length) {
alert('Please enter at least one username and regex');
return;
}
if (!comment) {
alert('Please enter a comment.');
return;
}
const expiryMap = {
'1m': 1, '3m': 3, '6m': 6, '1y': 12, '2y': 24, 'infinite': 0
};
const expiryCode = this.expiryDropdown.getValue();
let expiryTxt = '';
if (expiryCode === 'custom') {
const val = this.customExpiryInput.getValue().trim();
if (val) expiryTxt = ` - expires ${val}`;
} else if (expiryCode !== 'infinite') {
const now = new Date();
now.setMonth(now.getMonth() + expiryMap[expiryCode]);
expiryTxt = ` - expires until ${now.toISOString().slice(0,10)}`;
}
const loggedInUser = mw.config.get('wgUserName');
const today = new Date().toISOString().slice(0,10);
let entry = `\n# Added on ${today} by ${loggedInUser}${expiryTxt} - ${comment}\n`;
blacklistUsers.forEach(obj => {
entry += obj.regex + '\n';
});
if (!token) await getToken();
const res = await api.get({
action: 'query',
prop: 'revisions',
titles: page,
rvprop: 'content',
format: 'json'
});
const pages = res.query.pages;
const data = pages[Object.keys(pages)[0]];
let content = data.revisions[0]['*'] || data.revisions[0].content || '';
const marker = '#\n #</pre> <!-- leave this line exactly as it is -->';
const idx = content.indexOf(marker);
if (idx === -1) {
alert('Could not find insertion point in blacklist.');
return;
}
const newContent = content.slice(0, idx) + entry + marker + content.slice(idx + marker.length);
await api.postWithToken('csrf', {
action: 'edit',
title: page,
text: newContent,
summary: '',
format: 'json'
});
alert('Blacklist updated!');
location.reload();
this.close({ action: 'accept' });
});
} else if (action === 'cancel') {
return new OO.ui.Process(() => {
this.close({ action: 'cancel' });
});
}
};


function init() {
function showAlert(message) { alert("Error: " + message); }
if (mw.config.get('wgPageName') === 'Global_rename_blacklist') {

mw.util.addPortletLink(
function handleDeleteResponse(err, data, page) {
'p-cactions',
var logList = $('<ul>').appendTo(logContainer);
'#',
logList.append(err ? `<li>Failed to delete page <b>${page}</b>: ${err}</li>` : `<li><b>${page}</b> deleted successfully.</li>`);
'Global rename blacklist',
}
'gru-blacklist-dialog-portlet',

'Global rename blacklist'
function previewDeleting() {
);
var mode = deletionModeSelect.findSelectedItem().getData(),
$('#gru-blacklist-dialog-portlet').on('click', function (e) {
rawInput = pagesTextarea.getValue().trim(),
e.preventDefault();
reason = reasonInputField.getValue().trim(),
const windowManager = new OO.ui.WindowManager();
deleteTalk = deleteTalkCheckbox.isSelected();
$(document.body).append(windowManager.$element);

const dialog = new BlacklistDialog({ size: 'medium' });
if (!rawInput || !reason) {
windowManager.addWindows([dialog]);
showAlert("Please fill in all fields.");
windowManager.openWindow(dialog);
return;
});
}
}
logContainer.empty().append("<h1><span class='mw-headline'>Deletion preview</span></h1>").show();
}
var logList = $('<ul>').appendTo(logContainer);
init();

if (mode === 'list') {
rawInput.split("\n").map(p => p.trim()).filter(Boolean).forEach(page => {
logList.append(`<li><b>${page}</b> will be deleted for reason: <b>${reason}</b></li>`);
});
if (deleteTalk) logList.append("<li>Talk pages will also be deleted if they exist.</li>");
startButton.setDisabled(false);
} else {
var categories = rawInput.split("\n").map(c => c.trim()).filter(Boolean);
Promise.all(categories.map(getPagesFromCategory)).then(results => {
var pages = [...new Set(results.flat())];
if (!pages.length) {
showAlert("No pages found in the provided category/categories.");
return;
}
pages.forEach(page => logList.append(`<li><b>${page}</b> will be deleted for reason: <b>${reason}</b></li>`));
if (deleteTalk) logList.append("<li>Talk pages will also be deleted if they exist.</li>");
startButton.setDisabled(false);
}).catch(() => showAlert("Failed to retrieve pages from category."));
}
}

function startDeleting() {
if (!confirm("Are you sure you want to delete the listed pages? This action cannot be undone.")) return;
var pages = logContainer.find('li b').map((_, el) => $(el).text()).get();
var reason = reasonInputField.getValue().trim();
var deleteTalk = deleteTalkCheckbox.isSelected();

function processNextPage(index = 0) {
if (index >= pages.length) return;
deletePage(pages[index], reason, deleteTalk, function(err, data) {
handleDeleteResponse(err, data, pages[index]);
setTimeout(() => processNextPage(index + 1), 2000);
});
}
processNextPage();
}

previewButton.on('click', previewDeleting);
startButton.on('click', startDeleting);
}

$.when(mw.loader.using('mediawiki.util'), $.ready).then(() => {
mw.util.addPortletLink('p-tb', mw.util.getUrl('Special:BlankPage/MassDelete'), 'MassDelete');
});

if (mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' && mw.config.get('wgTitle').split('/', 2)[1] === 'MassDelete') {
$.when(mw.loader.using('oojs-ui-core'), $.ready).then(initializeMassDelete);
}
});
});

//</nowiki>
//</nowiki>