User:DR/test.js: Difference between revisions
From Test Wiki
Content deleted Content added
No edit summary |
No edit summary |
||
| (11 intermediate revisions by the same user not shown) | |||
| 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 pagesTextarea = new OO.ui.MultilineTextInputWidget({ |
|||
return new BlacklistUserInputWidget(); |
|||
placeholder: 'Enter list of pages (one per line)', |
|||
} |
|||
autosize: true, |
|||
rows: 10 |
|||
}), |
|||
reasonInputField = new OO.ui.TextInputWidget({ |
|||
placeholder: 'Reason for deletion' |
|||
}), |
|||
deleteTalkCheckbox = new OO.ui.CheckboxInputWidget({ |
|||
selected: true |
|||
}), |
|||
previewButton = new OO.ui.ButtonWidget({ |
|||
label: 'Preview Deletion', |
|||
flags: ['primary'] |
|||
}), |
|||
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') |
|||
}), |
|||
logContainer = $("<div>").hide(); |
|||
function BlacklistDialog(config) { |
|||
var labels = { |
|||
BlacklistDialog.super.call(this, config); |
|||
pagesLabel: $('<p>').text('Pages to Delete:').css('font-weight', 'bold'), |
|||
} |
|||
reasonLabel: $('<p>').text('Reason:').css('font-weight', 'bold'), |
|||
OO.inheritClass(BlacklistDialog, OO.ui.ProcessDialog); |
|||
deleteTalkLabel: $('<label>').append(deleteTalkCheckbox.$element, ' Also delete talk pages').css('margin', '10px 0') |
|||
BlacklistDialog.static.name = 'BlacklistDialog'; |
|||
}; |
|||
BlacklistDialog.static.title = 'Global rename blacklist'; |
|||
BlacklistDialog.static.actions = [ |
|||
{ action: 'accept', label: 'Submit', flags: ['primary', 'progressive'] }, |
|||
{ action: 'cancel', label: 'Cancel', flags: 'safe' } |
|||
]; |
|||
BlacklistDialog.prototype.initialize = function () { |
|||
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({ |
|||
$('#mw-content-text').append( |
|||
placeholder: 'Reason for blacklisting', |
|||
labels.pagesLabel, pagesTextarea.$element, |
|||
required: true, |
|||
labels.reasonLabel, reasonInputField.$element, |
|||
rows: 2 |
|||
'<br/>', |
|||
}); |
|||
labels.deleteTalkLabel, |
|||
'<br/>', |
|||
previewButton.$element, |
|||
startButton.$element, |
|||
cancelButton.$element, |
|||
'<br/>', |
|||
logContainer |
|||
); |
|||
this.expiryDropdown = new OO.ui.DropdownInputWidget({ |
|||
function deletePage(page, reason, deleteTalk, callback) { |
|||
options: [ |
|||
var params = { |
|||
{ data: '1m', label: '1 month' }, |
|||
action: 'delete', |
|||
{ data: '3m', label: '3 months' }, |
|||
title: page, |
|||
{ data: '6m', label: '6 months' }, |
|||
reason: reason |
|||
{ data: '1y', label: '1 year' }, |
|||
}; |
|||
{ data: '2y', label: '2 years' }, |
|||
if (deleteTalk) { |
|||
{ data: 'infinite', label: 'No expiry' }, |
|||
params.deletetalk = true; |
|||
{ data: 'custom', label: 'Custom…' } |
|||
} |
|||
], |
|||
value: '1m' |
|||
}); |
|||
this.customExpiryInput = new OO.ui.TextInputWidget({ |
|||
(new mw.Api({ |
|||
placeholder: 'Custom expiry (e.g., 2027-12-31, 18 months)', |
|||
ajax: { |
|||
disabled: true |
|||
headers: { |
|||
}); |
|||
'Api-User-Agent': 'en:User:DreamRimmer/MassDelete.js' |
|||
} |
|||
} |
|||
})).postWithToken('csrf', params, { |
|||
async: false |
|||
}).done(function(data) { |
|||
callback(null, data, page); |
|||
}).fail(function(code, data) { |
|||
callback(code, data, page); |
|||
}); |
|||
} |
|||
this.expiryDropdown.on('change', () => { |
|||
function showAlert(message) { |
|||
if (this.expiryDropdown.getValue() === 'custom') { |
|||
alert("Error: " + message); |
|||
this.customExpiryInput.setDisabled(false); |
|||
} |
|||
} else { |
|||
this.customExpiryInput.setValue(''); |
|||
this.customExpiryInput.setDisabled(true); |
|||
} |
|||
}); |
|||
this.userBox = new OO.ui.TextInputWidget({value: loggedInUser, readOnly: true}); |
|||
function handleDeleteResponse(err, data, page) { |
|||
var logList = $("<ul>").appendTo(logContainer); |
|||
this.content = new OO.ui.PanelLayout({ padded: true, expanded: false }); |
|||
if (err) { |
|||
this.content.$element.append( |
|||
logList.append("<li>Failed to delete page <b>" + page + "</b>: " + err + "</li>"); |
|||
new OO.ui.FieldsetLayout({ |
|||
} else { |
|||
items: [ |
|||
logList.append("<li><b>" + page + "</b> deleted successfully.</li>"); |
|||
new OO.ui.FieldLayout(this.userBox, {label: 'Global renamer/Steward:', align: 'top'}), |
|||
} |
|||
this.userFieldset, |
|||
} |
|||
new OO.ui.FieldLayout(addButton), |
|||
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() { |
|||
if (mw.config.get('wgPageName') === 'User:DR/Global_rename_blacklist') { |
|||
var pages = pagesTextarea.getValue().replace(/^\s*[\r\n]/gm, '').split("\n"), |
|||
mw.util.addPortletLink( |
|||
reason = reasonInputField.getValue().trim(), |
|||
'p-tb', |
|||
deleteTalk = deleteTalkCheckbox.isSelected(); |
|||
'#', |
|||
'Global rename blacklist', |
|||
if (pages[0].trim() === "" || reason === "") { |
|||
'gru-blacklist-dialog-portlet', |
|||
showAlert("Please fill in all fields."); |
|||
'Global rename blacklist' |
|||
return; |
|||
); |
|||
} |
|||
$('#gru-blacklist-dialog-portlet').on('click', function (e) { |
|||
e.preventDefault(); |
|||
logContainer.empty(); |
|||
const windowManager = new OO.ui.WindowManager(); |
|||
$("<h1>").wrapInner("<span class='mw-headline'>Deletion preview</span>").appendTo(logContainer); |
|||
$(document.body).append(windowManager.$element); |
|||
logContainer.show(); |
|||
const dialog = new BlacklistDialog({ size: 'medium' }); |
|||
windowManager.addWindows([dialog]); |
|||
var logList = $("<ul>").appendTo(logContainer); |
|||
windowManager.openWindow(dialog); |
|||
}); |
|||
pages.forEach(function(page) { |
|||
} |
|||
page = page.trim(); |
|||
} |
|||
logList.append("<li><b>" + page + "</b> will be deleted for reason: <b>" + reason + "</b></li>"); |
|||
init(); |
|||
}); |
|||
if (deleteTalk) { |
|||
logList.append("<li>Talk pages will also be deleted if they exist.</li>"); |
|||
} |
|||
startButton.setDisabled(false); |
|||
} |
|||
function startDeleting() { |
|||
if (!confirm("Are you sure you want to delete the listed pages? This action cannot be undone.")) { |
|||
return; |
|||
} |
|||
var pages = pagesTextarea.getValue().replace(/^\s*[\r\n]/gm, '').split("\n"), |
|||
reason = reasonInputField.getValue().trim(), |
|||
deleteTalk = deleteTalkCheckbox.isSelected(), |
|||
suffix = ""; |
|||
if (mw.config.get("wgGlobalGroups").indexOf("global-sysop") >= 0) { |
|||
suffix = " ([[m:GS|global sysop]] action)"; |
|||
} else if (mw.config.get("wgGlobalGroups").indexOf("steward") >= 0) { |
|||
suffix = " ([[m:stewards|steward]] action)"; |
|||
} else if (mw.config.get("wgUserGroups").indexOf("sysop") >= 0) { |
|||
suffix = " (sysop action)"; |
|||
} |
|||
reason += suffix + " (using [[:meta:User:DreamRimmer/MassDelete|MassDelete.js]])"; |
|||
if (pages[0].trim() === "" || reason === "") { |
|||
showAlert("Please fill in all fields."); |
|||
return; |
|||
} |
|||
logContainer.empty(); |
|||
$("<h1>").wrapInner("<span class='mw-headline'>Deletion log</span>").appendTo(logContainer); |
|||
logContainer.show(); |
|||
var currentIndex = 0; |
|||
function processNextPage() { |
|||
if (currentIndex >= pages.length) { |
|||
return; |
|||
} |
|||
var page = pages[currentIndex].trim(); |
|||
deletePage(page, reason, deleteTalk, function(err, data) { |
|||
handleDeleteResponse(err, data, page); |
|||
currentIndex++; |
|||
setTimeout(processNextPage, 2000); |
|||
}); |
|||
} |
|||
processNextPage(); |
|||
} |
|||
pagesTextarea.on('change', function() { |
|||
previewButton.setDisabled(pagesTextarea.getValue().trim() === '' || reasonInputField.getValue().trim() === ''); |
|||
startButton.setDisabled(true); |
|||
}); |
|||
reasonInputField.on('change', function() { |
|||
previewButton.setDisabled(pagesTextarea.getValue().trim() === '' || reasonInputField.getValue().trim() === ''); |
|||
startButton.setDisabled(true); |
|||
}); |
|||
previewButton.on('click', previewDeleting); |
|||
startButton.on('click', startDeleting); |
|||
} |
|||
$.when(mw.loader.using('mediawiki.util'), $.ready).then(function() { |
|||
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(function() { |
|||
initializeMassDelete(); |
|||
}); |
|||
} |
|||
}); |
}); |
||
//</nowiki> |
//</nowiki> |
||