No edit summary |
No edit summary |
||
Line 1: | Line 1: | ||
/ | // ==UserScript== | ||
// @name MediaWiki Clean Delete | |||
// @namespace MediaWikiScripts | |||
// @description Adds a 'Clean Delete' action link to pages for admins to delete pages and clean up incoming links and redirects. | |||
// ==/UserScript== | |||
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'oojs-ui'], function () { | |||
if ( | function addCleanDeleteLink() { | ||
if (mw.config.get('wgUserGroups').indexOf('sysop') !== -1) { | |||
if ($('#ca-cleandelete').length === 0) { | |||
var $link = $('<div>').attr('id', 'ca-cleandelete').attr('class', 'mw-list-item').append( | |||
$('<a>').attr('href', '#').attr('class', 'ca-cleandelete').text('Clean Delete').click(function (e) { | |||
e.preventDefault(); | |||
gatherAllDecisions(mw.config.get('wgPageName')); | |||
}) | |||
); | |||
$('#p-actions .tab-group').append($link); | |||
} | |||
} | } | ||
} | } | ||
function gatherAllDecisions(pageTitle) { | |||
var decisions = {}; | |||
var windowManager = new OO.ui.WindowManager(); | |||
$('body').append(windowManager.$element); | |||
askForLinkHandling(pageTitle, decisions, windowManager, function () { | |||
confirmDeletion(pageTitle, decisions, windowManager, function () { | |||
showLoadingDialog(windowManager, function (loadingDialog, updateLoadingText) { | |||
executeAllActions(pageTitle, decisions, function () { | |||
loadingDialog.close(); | |||
location.reload(); | |||
}, updateLoadingText); | |||
}); | |||
}); | |||
}); | |||
} | } | ||
function askForLinkHandling(pageTitle, decisions, windowManager, callback) { | |||
function LinkHandlingDialog(config) { | |||
LinkHandlingDialog.super.call(this, config); | |||
} | |||
OO.inheritClass(LinkHandlingDialog, OO.ui.ProcessDialog); | |||
LinkHandlingDialog.static.name = 'linkHandlingDialog'; | |||
LinkHandlingDialog.static.title = 'Handle Links and Redirects'; | |||
LinkHandlingDialog.static.actions = [ | |||
{ action: 'continue', label: 'Continue', flags: ['primary', 'progressive'] }, | |||
{ action: 'cancel', label: 'Cancel', flags: ['safe', 'close'] } | |||
]; | |||
LinkHandlingDialog.prototype.initialize = function () { | |||
LinkHandlingDialog.super.prototype.initialize.apply(this, arguments); | |||
this.radioSelect = new OO.ui.RadioSelectWidget({ | |||
items: [ | |||
new OO.ui.RadioOptionWidget({ data: 'delete', label: 'Delete Links and Redirects' }), | |||
new OO.ui.RadioOptionWidget({ data: 'change', label: 'Change Target of Links and Redirects' }) | |||
}); | ] | ||
}); | |||
var fieldset = new OO.ui.FieldsetLayout({ | |||
items: [ | |||
new OO.ui.FieldLayout(this.radioSelect, { | |||
label: 'Specify how you would like to handle all incoming links and redirects:', | |||
align: 'top' | |||
}) | |||
] | |||
}); | |||
}); | |||
( | this.content = new OO.ui.PanelLayout({ | ||
padded: true, | |||
expanded: false | |||
}); | |||
this.content.$element.append(fieldset.$element); | |||
this.$body.append(this.content.$element); | |||
}; | |||
LinkHandlingDialog.prototype.getActionProcess = function (action) { | |||
var dialog = this; | |||
var process = new OO.ui.Process(function () { | |||
if (action === 'continue') { | |||
var choice = dialog.radioSelect.findSelectedItem(); | |||
if (choice && choice.data === 'change') { | |||
dialog.close().then(function () { | |||
askForNewTarget(pageTitle, decisions, windowManager, callback); | |||
}); | |||
} else { | |||
decisions.links = ''; | |||
dialog.close().then(function () { | |||
callback(); | |||
}); | |||
} | |||
} else if (action === 'cancel') { | |||
dialog.close().then(function () { | |||
windowManager.destroy(); | |||
} | |||
} | |||
}); | }); | ||
} | } | ||
}); | }); | ||
} | return process; | ||
}; | |||
windowManager.addWindows([new LinkHandlingDialog()]); | |||
windowManager.openWindow('linkHandlingDialog'); | |||
} | } | ||
function | function askForNewTarget(pageTitle, decisions, windowManager, callback) { | ||
function NewTargetDialog(config) { | |||
NewTargetDialog.super.call(this, config); | |||
} | |||
OO.inheritClass(NewTargetDialog, OO.ui.ProcessDialog); | |||
NewTargetDialog.static.name = 'newTargetDialog'; | |||
NewTargetDialog.static.title = 'Specify New Target'; | |||
NewTargetDialog.static.actions = [ | |||
{ action: 'continue', label: 'Continue', flags: ['primary', 'progressive'] }, | |||
{ action: 'cancel', label: 'Cancel', flags: ['safe', 'close'] } | |||
]; | |||
NewTargetDialog.prototype.initialize = function () { | |||
NewTargetDialog.super.prototype.initialize.apply(this, arguments); | |||
this.input = new OO.ui.TextInputWidget({ | |||
value: '', | |||
placeholder: 'Enter new target' | |||
}); | |||
}) | var fieldset = new OO.ui.FieldsetLayout({ | ||
items: [ | |||
new OO.ui.FieldLayout(this.input, { | |||
label: 'Enter the new target page name to update all links and redirects:', | |||
align: 'top' | |||
}) | |||
] | |||
}); | |||
this.content = new OO.ui.PanelLayout({ | |||
padded: true, | |||
expanded: false | |||
}); | |||
this.content.$element.append(fieldset.$element); | |||
this.$body.append(this.content.$element); | |||
}; | |||
NewTargetDialog.prototype.getActionProcess = function (action) { | |||
var dialog = this; | |||
var process = new OO.ui.Process(function () { | |||
if (action === 'continue') { | |||
decisions.links = dialog.input.getValue(); | |||
dialog.close().then(function () { | |||
callback(); | |||
}); | |||
} else if (action === 'cancel') { | |||
dialog.close().then(function () { | |||
windowManager.destroy(); | |||
}); | |||
} | |||
}); | |||
return process; | |||
}; | |||
windowManager.addWindows([new NewTargetDialog()]); | |||
windowManager.openWindow('newTargetDialog'); | |||
} | } | ||
function confirmDeletion(pageTitle, decisions, windowManager, callback) { | |||
var dialog = new OO.ui.MessageDialog(); | |||
windowManager.addWindows([dialog]); | |||
windowManager.openWindow(dialog, { | |||
title: 'Confirm Page Deletion', | |||
}); | message: 'Are you sure you want to delete "' + pageTitle + '" after handling all links and redirects?', | ||
actions: [ | |||
{ label: 'Confirm Deletion', action: 'confirm', flags: ['primary', 'destructive'] }, | |||
{ label: 'Cancel', action: 'cancel' } | |||
] | |||
}).closed.then(function (data) { | |||
if (data.action === 'confirm') { | |||
decisions.deletion = true; | |||
callback(); | |||
} else { | |||
windowManager.destroy(); | |||
} | |||
}); | |||
} | |||
function showLoadingDialog(windowManager, callback) { | |||
function LoadingDialog(config) { | |||
LoadingDialog.super.call(this, config); | |||
} | |||
OO.inheritClass(LoadingDialog, OO.ui.ProcessDialog); | |||
LoadingDialog.static.name = 'loadingDialog'; | |||
LoadingDialog.static.title = 'Deleting Links and Redirects'; | |||
LoadingDialog.prototype.initialize = function () { | |||
LoadingDialog.super.prototype.initialize.apply(this, arguments); | |||
this.loadingLabel = new OO.ui.LabelWidget({ label: 'Starting...' }); | |||
}); | |||
this.content = new OO.ui.PanelLayout({ | |||
padded: true, | |||
expanded: false | |||
}); | |||
}); | |||
this.content.$element.append(this.loadingLabel.$element); | |||
this.$body.append(this.content.$element); | |||
}; | |||
LoadingDialog.prototype.getActionProcess = function (action) { | |||
return new OO.ui.Process(); | |||
}; | |||
windowManager.addWindows([new LoadingDialog()]); | |||
windowManager.openWindow('loadingDialog').then(function (opened) { | |||
opened.then(function (dialog) { | |||
callback(dialog, function (text) { | |||
dialog.loadingLabel.setLabel(text); | |||
}); | |||
}); | |||
}); | |||
} | } | ||
function executeAllActions(pageTitle, decisions, callback, updateLoadingText) { | |||
var api = new mw.Api(); | |||
var | |||
var tasks = [ | |||
function (resolve) { | |||
if (decisions.links === '') { | |||
updateLoadingText('Removing all links and redirects...'); | |||
removeAllLinksAndRedirects(pageTitle, api, function () { | |||
updateLoadingText('All links and redirects removed.'); | |||
resolve(); | |||
}); | |||
} else { | |||
updateLoadingText('Updating all links and redirects...'); | |||
updateAllLinksAndRedirects(pageTitle, decisions.links, api, function () { | |||
updateLoadingText('All links and redirects updated.'); | |||
resolve(); | |||
}); | |||
} | |||
}, | |||
function (resolve) { | |||
updateLoadingText('Deleting the page...'); | |||
performDeletion(pageTitle, api, function () { | |||
updateLoadingText('Page deleted.'); | |||
resolve(); | |||
}); | |||
} | |||
]; | |||
(function executeTasks(i) { | |||
if (i < tasks.length) { | |||
tasks[i](function () { | |||
executeTasks(i + 1); | |||
}); | |||
} else { | |||
callback(); | |||
}); | } | ||
})(0); | |||
} | |||
function updateAllLinksAndRedirects(pageTitle, newTarget, api, callback) { | |||
handleLinks(pageTitle, newTarget, 'update', api, function () { | |||
handleRedirects(pageTitle, newTarget, 'update', api, callback); | |||
}); | }); | ||
} | |||
function removeAllLinksAndRedirects(pageTitle, api, callback) { | |||
handleLinks(pageTitle, '', 'remove', api, function () { | |||
handleRedirects(pageTitle, '', 'remove', api, callback); | |||
}); | }); | ||
} | |||
function handleLinks(pageTitle, newTarget, action, api, callback) { | |||
api.get({ | |||
action: 'query', | |||
list: 'backlinks', | |||
bltitle: pageTitle, | |||
blfilterredir: 'nonredirects', | |||
bllimit: 'max' | |||
}).done(function (data) { | |||
if (data.query.backlinks) { | |||
var promises = data.query.backlinks.map(function (link) { | |||
return new Promise(function (resolve) { | |||
if (action === 'update') { | |||
updateLink(link.title, newTarget, api).then(resolve); | |||
} else { | |||
removeLink(link.title, api).then(resolve); | |||
} | |||
}); | |||
}); | |||
Promise.all(promises).then(callback); | |||
} else { | |||
callback(); | |||
} | } | ||
}).fail(function (error) { | |||
console.error('Error fetching backlinks:', error); | |||
updateLoadingText('Error fetching backlinks: ' + error); | |||
callback(); | |||
}); | }); | ||
} | } | ||
function handleRedirects(pageTitle, newTarget, action, api, callback) { | |||
function | api.get({ | ||
action: 'query', | |||
list: 'backlinks', | |||
bltitle: pageTitle, | |||
blfilterredir: 'redirects', | |||
bllimit: 'max' | |||
}).done(function (data) { | |||
if (data.query.backlinks) { | |||
var promises = data.query.backlinks.map(function (redirect) { | |||
return new Promise(function (resolve) { | |||
if (action === 'update') { | |||
updateLink(redirect.title, newTarget, api).then(resolve); | |||
} else { | |||
removeLink(redirect.title, api).then(resolve); | |||
} | |||
}); | |||
}); | |||
Promise.all(promises).then(callback); | |||
} else { | |||
callback(); | |||
} | } | ||
}).fail(function (error) { | |||
console.error('Error fetching redirects:', error); | |||
updateLoadingText('Error fetching redirects: ' + error); | |||
callback(); | |||
}); | |||
} | |||
function updateLink(pageTitle, newTarget, api) { | |||
return api.postWithToken('csrf', { | |||
} | action: 'edit', | ||
console. | title: pageTitle, | ||
text: function (text) { | |||
} | return text.replace(new RegExp('\\[\\[' + mw.util.escapeRegExp(pageTitle) + '(\\|[^\\]]+)?\\]\\]', 'g'), '[[' + newTarget + '$1]]'); | ||
}, | |||
summary: 'Updated redirect to new target [[' + newTarget + ']]' | |||
}).fail(function (error) { | |||
console.error('Error updating link:', error); | |||
}); | |||
} | } | ||
function | function removeLink(pageTitle, api) { | ||
return api.postWithToken('csrf', { | |||
action: 'edit', | |||
title: pageTitle, | |||
text: function (text) { | |||
return text.replace(new RegExp('\\[\\[' + mw.util.escapeRegExp(pageTitle) + '(\\|[^\\]]+)?\\]\\]', 'g'), ''); | |||
} | }, | ||
console. | summary: 'Removed link to [[' + pageTitle + ']]' | ||
}).fail(function (error) { | |||
} | console.error('Error removing link:', error); | ||
}); | |||
} | } | ||
function | function performDeletion(pageTitle, api, callback) { | ||
api.postWithToken('csrf', { | |||
action: 'delete', | |||
title: pageTitle, | |||
reason: 'Automated clean delete by admin' | |||
}).done(function () { | |||
console.log('Page deleted:', pageTitle); | |||
callback(); | |||
}).fail(function (error) { | |||
console.error('Error during clean deletion:', error); | |||
callback(); | |||
}); | |||
} | |||
} | } | ||
mw.hook('wikipage.content').add(addCleanDeleteLink); | |||
}); | }); | ||
Revision as of 22:41, 7 July 2024
// ==UserScript==
// @name MediaWiki Clean Delete
// @namespace MediaWikiScripts
// @description Adds a 'Clean Delete' action link to pages for admins to delete pages and clean up incoming links and redirects.
// ==/UserScript==
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'oojs-ui'], function () {
function addCleanDeleteLink() {
if (mw.config.get('wgUserGroups').indexOf('sysop') !== -1) {
if ($('#ca-cleandelete').length === 0) {
var $link = $('<div>').attr('id', 'ca-cleandelete').attr('class', 'mw-list-item').append(
$('<a>').attr('href', '#').attr('class', 'ca-cleandelete').text('Clean Delete').click(function (e) {
e.preventDefault();
gatherAllDecisions(mw.config.get('wgPageName'));
})
);
$('#p-actions .tab-group').append($link);
}
}
}
function gatherAllDecisions(pageTitle) {
var decisions = {};
var windowManager = new OO.ui.WindowManager();
$('body').append(windowManager.$element);
askForLinkHandling(pageTitle, decisions, windowManager, function () {
confirmDeletion(pageTitle, decisions, windowManager, function () {
showLoadingDialog(windowManager, function (loadingDialog, updateLoadingText) {
executeAllActions(pageTitle, decisions, function () {
loadingDialog.close();
location.reload();
}, updateLoadingText);
});
});
});
}
function askForLinkHandling(pageTitle, decisions, windowManager, callback) {
function LinkHandlingDialog(config) {
LinkHandlingDialog.super.call(this, config);
}
OO.inheritClass(LinkHandlingDialog, OO.ui.ProcessDialog);
LinkHandlingDialog.static.name = 'linkHandlingDialog';
LinkHandlingDialog.static.title = 'Handle Links and Redirects';
LinkHandlingDialog.static.actions = [
{ action: 'continue', label: 'Continue', flags: ['primary', 'progressive'] },
{ action: 'cancel', label: 'Cancel', flags: ['safe', 'close'] }
];
LinkHandlingDialog.prototype.initialize = function () {
LinkHandlingDialog.super.prototype.initialize.apply(this, arguments);
this.radioSelect = new OO.ui.RadioSelectWidget({
items: [
new OO.ui.RadioOptionWidget({ data: 'delete', label: 'Delete Links and Redirects' }),
new OO.ui.RadioOptionWidget({ data: 'change', label: 'Change Target of Links and Redirects' })
]
});
var fieldset = new OO.ui.FieldsetLayout({
items: [
new OO.ui.FieldLayout(this.radioSelect, {
label: 'Specify how you would like to handle all incoming links and redirects:',
align: 'top'
})
]
});
this.content = new OO.ui.PanelLayout({
padded: true,
expanded: false
});
this.content.$element.append(fieldset.$element);
this.$body.append(this.content.$element);
};
LinkHandlingDialog.prototype.getActionProcess = function (action) {
var dialog = this;
var process = new OO.ui.Process(function () {
if (action === 'continue') {
var choice = dialog.radioSelect.findSelectedItem();
if (choice && choice.data === 'change') {
dialog.close().then(function () {
askForNewTarget(pageTitle, decisions, windowManager, callback);
});
} else {
decisions.links = '';
dialog.close().then(function () {
callback();
});
}
} else if (action === 'cancel') {
dialog.close().then(function () {
windowManager.destroy();
});
}
});
return process;
};
windowManager.addWindows([new LinkHandlingDialog()]);
windowManager.openWindow('linkHandlingDialog');
}
function askForNewTarget(pageTitle, decisions, windowManager, callback) {
function NewTargetDialog(config) {
NewTargetDialog.super.call(this, config);
}
OO.inheritClass(NewTargetDialog, OO.ui.ProcessDialog);
NewTargetDialog.static.name = 'newTargetDialog';
NewTargetDialog.static.title = 'Specify New Target';
NewTargetDialog.static.actions = [
{ action: 'continue', label: 'Continue', flags: ['primary', 'progressive'] },
{ action: 'cancel', label: 'Cancel', flags: ['safe', 'close'] }
];
NewTargetDialog.prototype.initialize = function () {
NewTargetDialog.super.prototype.initialize.apply(this, arguments);
this.input = new OO.ui.TextInputWidget({
value: '',
placeholder: 'Enter new target'
});
var fieldset = new OO.ui.FieldsetLayout({
items: [
new OO.ui.FieldLayout(this.input, {
label: 'Enter the new target page name to update all links and redirects:',
align: 'top'
})
]
});
this.content = new OO.ui.PanelLayout({
padded: true,
expanded: false
});
this.content.$element.append(fieldset.$element);
this.$body.append(this.content.$element);
};
NewTargetDialog.prototype.getActionProcess = function (action) {
var dialog = this;
var process = new OO.ui.Process(function () {
if (action === 'continue') {
decisions.links = dialog.input.getValue();
dialog.close().then(function () {
callback();
});
} else if (action === 'cancel') {
dialog.close().then(function () {
windowManager.destroy();
});
}
});
return process;
};
windowManager.addWindows([new NewTargetDialog()]);
windowManager.openWindow('newTargetDialog');
}
function confirmDeletion(pageTitle, decisions, windowManager, callback) {
var dialog = new OO.ui.MessageDialog();
windowManager.addWindows([dialog]);
windowManager.openWindow(dialog, {
title: 'Confirm Page Deletion',
message: 'Are you sure you want to delete "' + pageTitle + '" after handling all links and redirects?',
actions: [
{ label: 'Confirm Deletion', action: 'confirm', flags: ['primary', 'destructive'] },
{ label: 'Cancel', action: 'cancel' }
]
}).closed.then(function (data) {
if (data.action === 'confirm') {
decisions.deletion = true;
callback();
} else {
windowManager.destroy();
}
});
}
function showLoadingDialog(windowManager, callback) {
function LoadingDialog(config) {
LoadingDialog.super.call(this, config);
}
OO.inheritClass(LoadingDialog, OO.ui.ProcessDialog);
LoadingDialog.static.name = 'loadingDialog';
LoadingDialog.static.title = 'Deleting Links and Redirects';
LoadingDialog.prototype.initialize = function () {
LoadingDialog.super.prototype.initialize.apply(this, arguments);
this.loadingLabel = new OO.ui.LabelWidget({ label: 'Starting...' });
this.content = new OO.ui.PanelLayout({
padded: true,
expanded: false
});
this.content.$element.append(this.loadingLabel.$element);
this.$body.append(this.content.$element);
};
LoadingDialog.prototype.getActionProcess = function (action) {
return new OO.ui.Process();
};
windowManager.addWindows([new LoadingDialog()]);
windowManager.openWindow('loadingDialog').then(function (opened) {
opened.then(function (dialog) {
callback(dialog, function (text) {
dialog.loadingLabel.setLabel(text);
});
});
});
}
function executeAllActions(pageTitle, decisions, callback, updateLoadingText) {
var api = new mw.Api();
var tasks = [
function (resolve) {
if (decisions.links === '') {
updateLoadingText('Removing all links and redirects...');
removeAllLinksAndRedirects(pageTitle, api, function () {
updateLoadingText('All links and redirects removed.');
resolve();
});
} else {
updateLoadingText('Updating all links and redirects...');
updateAllLinksAndRedirects(pageTitle, decisions.links, api, function () {
updateLoadingText('All links and redirects updated.');
resolve();
});
}
},
function (resolve) {
updateLoadingText('Deleting the page...');
performDeletion(pageTitle, api, function () {
updateLoadingText('Page deleted.');
resolve();
});
}
];
(function executeTasks(i) {
if (i < tasks.length) {
tasks[i](function () {
executeTasks(i + 1);
});
} else {
callback();
}
})(0);
}
function updateAllLinksAndRedirects(pageTitle, newTarget, api, callback) {
handleLinks(pageTitle, newTarget, 'update', api, function () {
handleRedirects(pageTitle, newTarget, 'update', api, callback);
});
}
function removeAllLinksAndRedirects(pageTitle, api, callback) {
handleLinks(pageTitle, '', 'remove', api, function () {
handleRedirects(pageTitle, '', 'remove', api, callback);
});
}
function handleLinks(pageTitle, newTarget, action, api, callback) {
api.get({
action: 'query',
list: 'backlinks',
bltitle: pageTitle,
blfilterredir: 'nonredirects',
bllimit: 'max'
}).done(function (data) {
if (data.query.backlinks) {
var promises = data.query.backlinks.map(function (link) {
return new Promise(function (resolve) {
if (action === 'update') {
updateLink(link.title, newTarget, api).then(resolve);
} else {
removeLink(link.title, api).then(resolve);
}
});
});
Promise.all(promises).then(callback);
} else {
callback();
}
}).fail(function (error) {
console.error('Error fetching backlinks:', error);
updateLoadingText('Error fetching backlinks: ' + error);
callback();
});
}
function handleRedirects(pageTitle, newTarget, action, api, callback) {
api.get({
action: 'query',
list: 'backlinks',
bltitle: pageTitle,
blfilterredir: 'redirects',
bllimit: 'max'
}).done(function (data) {
if (data.query.backlinks) {
var promises = data.query.backlinks.map(function (redirect) {
return new Promise(function (resolve) {
if (action === 'update') {
updateLink(redirect.title, newTarget, api).then(resolve);
} else {
removeLink(redirect.title, api).then(resolve);
}
});
});
Promise.all(promises).then(callback);
} else {
callback();
}
}).fail(function (error) {
console.error('Error fetching redirects:', error);
updateLoadingText('Error fetching redirects: ' + error);
callback();
});
}
function updateLink(pageTitle, newTarget, api) {
return api.postWithToken('csrf', {
action: 'edit',
title: pageTitle,
text: function (text) {
return text.replace(new RegExp('\\[\\[' + mw.util.escapeRegExp(pageTitle) + '(\\|[^\\]]+)?\\]\\]', 'g'), '[[' + newTarget + '$1]]');
},
summary: 'Updated redirect to new target [[' + newTarget + ']]'
}).fail(function (error) {
console.error('Error updating link:', error);
});
}
function removeLink(pageTitle, api) {
return api.postWithToken('csrf', {
action: 'edit',
title: pageTitle,
text: function (text) {
return text.replace(new RegExp('\\[\\[' + mw.util.escapeRegExp(pageTitle) + '(\\|[^\\]]+)?\\]\\]', 'g'), '');
},
summary: 'Removed link to [[' + pageTitle + ']]'
}).fail(function (error) {
console.error('Error removing link:', error);
});
}
function performDeletion(pageTitle, api, callback) {
api.postWithToken('csrf', {
action: 'delete',
title: pageTitle,
reason: 'Automated clean delete by admin'
}).done(function () {
console.log('Page deleted:', pageTitle);
callback();
}).fail(function (error) {
console.error('Error during clean deletion:', error);
callback();
});
}
mw.hook('wikipage.content').add(addCleanDeleteLink);
});
This page was edited 146 days ago on 08/26/2024. What links here