User:DR/test.js: Difference between revisions

From Test Wiki
Jump to navigation Jump to search
Content deleted Content added
DR (talk | contribs)
No edit summary
Tag: Reverted
DR (talk | contribs)
No edit summary
 
(18 intermediate revisions by the same user not shown)
Line 1: Line 1:
//<nowiki>
$(document).ready(function() {
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'oojs-ui'], function () {
function initializeMassDelete() {
'use strict';
$('#mw-content-text > p').remove();
const page = 'User:DR/Global rename blacklist';
$('#firstHeading').text('MassDelete');
const api = new mw.Api();
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 categoryInputField = new OO.ui.TextInputWidget({
return new BlacklistUserInputWidget();
placeholder: 'Category name (optional)',
}
id: 'categoryInput',
style: 'margin: 10px 0; padding: 5px;'
}),
fetchCategoryButton = new OO.ui.ButtonWidget({
label: 'Fetch Pages from Category',
flags: ['progressive'],
style: 'margin: 10px 0; padding: 5px;'
}),
pagesTextarea = new OO.ui.MultilineTextInputWidget({
placeholder: 'Enter list of pages (one per line)',
autosize: true,
rows: 10,
style: 'margin-top: 10px;'
}),
reasonInputField = new OO.ui.TextInputWidget({
placeholder: 'Reason for deletion',
style: 'margin-top: 10px;'
}),
deleteTalkPagesCheckbox = new OO.ui.CheckboxInputWidget({
selected: false
}),
previewButton = new OO.ui.ButtonWidget({
label: 'Preview Deletion',
flags: ['primary'],
style: 'margin-top: 10px;'
}),
startButton = new OO.ui.ButtonWidget({
label: 'Start Deletion',
icon: 'alert',
flags: ['primary', 'progressive'],
disabled: true,
style: 'margin-top: 10px;'
}),
cancelButton = new OO.ui.ButtonWidget({
label: 'Cancel',
flags: ['primary', 'destructive'],
href: 'https:' + mw.config.get('wgServer'),
style: 'margin-top: 10px;'
}),
logContainer = $("<div>").hide();


function BlacklistDialog(config) {
var labels = {
BlacklistDialog.super.call(this, config);
categoryLabel: $('<p>').text('Category name (optional)').css('font-weight', 'bold'),
}
pagesLabel: $('<p>').text('Pages to Delete:').css('font-weight', 'bold'),
OO.inheritClass(BlacklistDialog, OO.ui.ProcessDialog);
reasonLabel: $('<p>').text('Reason:').css('font-weight', 'bold'),
BlacklistDialog.static.name = 'BlacklistDialog';
deleteTalkPagesLabel: $('<p>').text('Delete associated talk pages').css('font-weight', 'bold')
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.categoryLabel, categoryInputField.$element, fetchCategoryButton.$element,
required: true,
labels.pagesLabel, pagesTextarea.$element,
rows: 2
labels.reasonLabel, reasonInputField.$element,
});
labels.deleteTalkPagesLabel, deleteTalkPagesCheckbox.$element,
'<br/>',
previewButton.$element,
startButton.$element,
cancelButton.$element,
'<br/>',
logContainer
);


this.expiryDropdown = new OO.ui.DropdownInputWidget({
function deletePage(page, reason, callback) {
options: [
(new mw.Api({
{ data: '1m', label: '1 month' },
ajax: {
{ data: '3m', label: '3 months' },
headers: {
{ data: '6m', label: '6 months' },
'Api-User-Agent': 'en:User:DreamRimmer/MassDelete.js'
{ data: '1y', label: '1 year' },
}
{ data: '2y', label: '2 years' },
}
{ data: 'infinite', label: 'No expiry' },
})).postWithToken('csrf', {
{ data: 'custom', label: 'Custom…' }
action: 'delete',
],
title: page,
value: '1m'
reason: reason
});
}, {
async: false
}).done(function(data) {
callback(null, data, page);
}).fail(function(code, data) {
callback(code, data, page);
});
}


this.customExpiryInput = new OO.ui.TextInputWidget({
function showAlert(message) {
placeholder: 'Custom expiry (e.g., 2027-12-31, 18 months)',
alert("Error: " + message);
disabled: true
}
});


this.expiryDropdown.on('change', () => {
function handleDeleteResponse(err, data, page) {
if (this.expiryDropdown.getValue() === 'custom') {
var logList = $("<ul>").appendTo(logContainer);
this.customExpiryInput.setDisabled(false);
} else {
this.customExpiryInput.setValue('');
this.customExpiryInput.setDisabled(true);
}
});


this.userBox = new OO.ui.TextInputWidget({value: loggedInUser, readOnly: true});
if (err) {
logList.append("<li>Failed to delete page <b>" + page + "</b>: " + err + "</li>");
} else {
logList.append("<li><b>" + page + "</b> deleted successfully.</li>");
}
}


this.content = new OO.ui.PanelLayout({ padded: true, expanded: false });
function previewDeleting() {
this.content.$element.append(
var pages = pagesTextarea.getValue().replace(/^\s*[\r\n]/gm, '').split("\n"),
new OO.ui.FieldsetLayout({
reason = reasonInputField.getValue().trim(),
items: [
deleteTalkPages = deleteTalkPagesCheckbox.isSelected();
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 (pages[0].trim() === "" || reason === "") {
if (mw.config.get('wgPageName') === 'User:DR/Global_rename_blacklist') {
showAlert("Please fill in all fields.");
mw.util.addPortletLink(
return;
'p-tb',
}
'#',

'Global rename blacklist',
logContainer.empty();
'gru-blacklist-dialog-portlet',
$("<h1>").wrapInner("<span class='mw-headline'>Deletion preview</span>").appendTo(logContainer);
'Global rename blacklist'
logContainer.show();
);

$('#gru-blacklist-dialog-portlet').on('click', function (e) {
var logList = $("<ul>").appendTo(logContainer);
e.preventDefault();

const windowManager = new OO.ui.WindowManager();
pages.forEach(function(page) {
$(document.body).append(windowManager.$element);
page = page.trim();
const dialog = new BlacklistDialog({ size: 'medium' });
logList.append("<li><b>" + page + "</b> will be deleted for reason: <b>" + reason + "</b></li>");
windowManager.addWindows([dialog]);
if (deleteTalkPages) {
windowManager.openWindow(dialog);
logList.append("<li>Associated talk page <b>Talk:" + page + "</b> will also be deleted.</li>");
});
}
}
});
}

init();
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() + " (using [[User:DreamRimmer/MassDelete|MassDelete.js]])",
deleteTalkPages = deleteTalkPagesCheckbox.isSelected();

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, function(err, data) {
handleDeleteResponse(err, data, page);
if (deleteTalkPages) {
var talkPage = "Talk:" + page;
deletePage(talkPage, reason, function(err, data) {
handleDeleteResponse(err, data, talkPage);
currentIndex++;
setTimeout(processNextPage, 2000);
});
} else {
currentIndex++;
setTimeout(processNextPage, 2000);
}
});
}

processNextPage();
}

function fetchCategoryMembers() {
var category = categoryInputField.getValue().trim();

if (category === "") {
showAlert("Please enter a category name.");
return;
}

var params = {
action: 'query',
list: 'categorymembers',
cmtitle: 'Category:' + category,
cmlimit: 'max',
format: 'json'
};

$.ajax({
url: mw.util.wikiScript('api'),
data: params,
dataType: 'json',
success: function(data) {
if (data.query && data.query.categorymembers.length > 0) {
var pages = data.query.categorymembers.map(function(member) {
return member.title;
}).join("\n");
var existingPages = pagesTextarea.getValue().trim();
if (existingPages) {
pagesTextarea.setValue(existingPages + "\n" + pages);
} else {
pagesTextarea.setValue(pages);
}
showAlert("Fetched " + data.query.categorymembers.length + " pages from category: " + category);
} else {
showAlert("No pages found in the category: " + category);
}
},
error: function(xhr, status, error) {
showAlert("Error fetching category members: " + error);
}
});
}

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);
});

fetchCategoryButton.on('click', fetchCategoryMembers);
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>

Latest revision as of 06:46, 31 May 2025

//<nowiki>
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'oojs-ui'], function () {
	'use strict';
	const page = 'User:DR/Global rename blacklist';
	const api = new mw.Api();
	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() {
		return new BlacklistUserInputWidget();
	}

	function BlacklistDialog(config) {
		BlacklistDialog.super.call(this, config);
	}
	OO.inheritClass(BlacklistDialog, OO.ui.ProcessDialog);
	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({
			placeholder: 'Reason for blacklisting',
			required: true,
			rows: 2
		});

		this.expiryDropdown = new OO.ui.DropdownInputWidget({
			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({
			placeholder: 'Custom expiry (e.g., 2027-12-31, 18 months)',
			disabled: true
		});

		this.expiryDropdown.on('change', () => {
			if (this.expiryDropdown.getValue() === 'custom') {
				this.customExpiryInput.setDisabled(false);
			} else {
				this.customExpiryInput.setValue('');
				this.customExpiryInput.setDisabled(true);
			}
		});

		this.userBox = new OO.ui.TextInputWidget({value: loggedInUser, readOnly: true});

		this.content = new OO.ui.PanelLayout({ padded: true, expanded: false });
		this.content.$element.append(
			new OO.ui.FieldsetLayout({
				items: [
					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') {
			mw.util.addPortletLink(
				'p-tb',
				'#',
				'Global rename blacklist',
				'gru-blacklist-dialog-portlet',
				'Global rename blacklist'
			);
			$('#gru-blacklist-dialog-portlet').on('click', function (e) {
				e.preventDefault();
				const windowManager = new OO.ui.WindowManager();
				$(document.body).append(windowManager.$element);
				const dialog = new BlacklistDialog({ size: 'medium' });
				windowManager.addWindows([dialog]);
				windowManager.openWindow(dialog);
			});
		}
	}
	init();
});
//</nowiki>