Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/**
* AjaxDelete
*
* Allows to delete pages (through ?action=delete links) without leaving the current page.
* Supports deleting revisions and restoring pages (does not support restoring individual revisions)
* For personal use
* @author Sophiedp
* @author KockaAdmiralac
*/
(function() {
'use strict';
var config = mw.config.get([
'wgArticlePath',
'wgContentLanguage',
'wgFormattedNamespaces',
'wgNamespaceNumber',
'wgUserGroups'
]);
if (
window.AjaxDeleteLoaded ||
(
!$('#ca-delete').length &&
!/sysop|content-moderator|staff|wiki-specialist|soap|global-edit-reviewer/.test(config.wgUserGroups.join())
)
) {
return;
}
window.AjaxDeleteLoaded = true;
importArticles({
type: 'script',
articles: [
'u:dev:MediaWiki:I18n-js/code.js',
'u:dev:MediaWiki:ShowCustomModal.js',
'u:dev:MediaWiki:BannerNotification.js'
]
});
var AjaxDelete = {
config: window.AjaxDelete || {},
undelete: ['Undelete'],
toLoad: 3,
preload: function() {
if (--this.toLoad === 0) {
$.when(
window.dev.i18n.loadMessages('AjaxDelete'),
mw.loader.using(['mediawiki.api', 'mediawiki.Title'])
).then(this.init.bind(this));
}
},
init: function(i18n) {
this.i18n = i18n;
this.acw = typeof this.config.autoCheckWatch === 'undefined' ?
mw.user.options.get('watchdeletion') :
this.config.autoCheckWatch;
this.api = new mw.Api();
this.BannerNotification = dev.banners.BannerNotification;
this.buildDeleteReasons();
this.fetchUndeleteAliases();
$(document).click(this.click.bind(this));
if (
[-1, 1201, 2001].indexOf(config.wgNamespaceNumber) === -1 &&
!this.config.disableShortcut
) {
var bindShortcut = this.bindShortcut.bind(this);
var moduleName = mw.loader.getModuleNames().find(function(name) {
return name.indexOf('GlobalShortcuts-') === 0;
});
if (moduleName) {
mw.loader.using(moduleName).then(function() {
bindShortcut(window.Mousetrap);
});
}
}
},
buildDeleteReasons: function() {
var dr = this.config.deleteReasons,
idr = this.config.imageDeleteReasons;
this.reasons = ['', ''];
if (!dr || !idr) {
this.fetchDeleteReasons();
}
if (dr) {
this.reasons[0] = this.makeReasonsHTML1(dr);
}
if (idr) {
this.reasons[1] = this.makeReasonsHTML1(idr);
}
},
fetchUndeleteAliases: function() {
this.api.get({
action: 'query',
meta: 'siteinfo',
siprop: 'specialpagealiases',
smaxage: 86400,
maxage: 86400
}).done(this.cbAliasFetch.bind(this));
},
fetchDeleteReasons: function() {
this.api.get({
action: 'query',
meta: 'allmessages',
ammessages: 'deletereason-dropdown|filedelete-reason-dropdown',
amlang: config.wgContentLanguage
}).done(this.cbFetch.bind(this));
},
cbFetch: function(d) {
if (d.error) {
console.error(this.msg('errorfetch') + ': ' + d.error.code);
return;
}
var am = d.query.allmessages;
this.makeReasonsHTML2(am, 0);
this.makeReasonsHTML2(am, 1);
},
cbAliasFetch: function(d) {
var aliases = d.query.specialpagealiases;
for (var i in aliases) {
if (aliases[i].realname == 'Undelete') {
this.undelete = aliases[i].aliases;
}
}
},
makeReasonsHTML: function() {
var $select = $('<select>', {
id: 'AjaxDeleteReasonSelect'
});
if (!this.config.noOther) {
$select.append(
$('<option>', {
value: 'other',
text: this.i18n.msg('other').plain()
})
);
}
return $select;
},
makeReasonsHTML1: function(obj) {
var $select = this.makeReasonsHTML();
// Don't use $.each or $.map here because if the
// user specifies a "length" parameter in the configuration
// and some other stuff jQuery will interpret it
// as an array and bad things can happen
for (var key in obj) {
var value = obj[key];
if (typeof value === 'string') {
$select.append(
$('<option>', {
value: key,
text: value
})
);
} else {
var children = [];
for (var subKey in value) {
var subValue = value[subKey];
children.push(
$('<option>', {
value: subKey,
text: subValue
})
);
}
$select.append(
$('<optgroup>', {
label: key,
html: children
})
);
}
}
return $select;
},
makeReasonsHTML2: function(obj, index) {
if (this.reasons[index]) {
return;
}
var $select = this.makeReasonsHTML();
obj[index]['*'].trim().split('\n').forEach(function(line) {
line = line.trim(); // In case of \r or something dumb
if (line.charAt(1) === '*') {
var text = line.substring(2).trim();
$select.append(
$('<option>', {
value: text,
text: text
})
);
} else if (line.charAt(0) === '*') {
$select.append(
$('<optgroup>', {
label: line.substring(1).trim()
})
);
}
});
this.reasons[index] = $select;
},
click: function(e) {
var $target = $(e.target);
if (
e.ctrlKey || e.shiftKey ||
!$target.is('a[href]') ||
$('[data-tab-body="about"]').html() === ''
) {
return;
}
var url;
try {
url = new URL($target.prop('href'));
} catch(e) {
return;
}
if (!$target.is('.ignoreAjDel')) {
if (Object.fromEntries(new URLSearchParams(url.search)).action === 'delete') {
e.preventDefault();
this.doDelete(url, $target);
} else if (this.isUndelete(url)) {
e.preventDefault();
this.doUndelete(url, $target);
}
}
},
isUndelete: function(url) {
var special = config.wgFormattedNamespaces[-1];
return this.undelete.some(function(alias) {
return (
url.pathname.indexOf(mw.util.getUrl('Special:' + alias + '/')) === 0 ||
url.pathname.indexOf(mw.util.getUrl(special + ':' + alias + '/')) === 0 ||
url.pathname === mw.util.getUrl('Special:' + alias) ||
url.pathname === mw.util.getUrl(special + ':' + alias)
) &&
Object.fromEntries(new URLSearchParams(url.search)).target;
}) &&
!this.config.noUndelete &&
// URLs on undeletion history should not open the modal
!Object.fromEntries(new URLSearchParams(url.search)).timestamp;
},
doDelete: function(url, $target) {
this.action = 'delete';
var isImg = $target.is('a[href*="/wiki/File:"]'),
isRevImg = Object.fromEntries(new URLSearchParams(url.search)).oldimage ? Object.fromEntries(new URLSearchParams(url.search)).oldimage : false,
page = decodeURIComponent(url.pathname).replace(config.wgArticlePath.replace('$1', ''), '').replace(/_/g, ' '),
text = isImg ?
isRevImg ?
this.i18n.msg('deleteimgrev', isRevImg.split('!')[0], page) :
this.i18n.msg('deleteimg', page.replace('File:', '')) :
this.i18n.msg('deletepage', page);
this.page = page;
this.rev = isRevImg;
this.showModal([
$('<p>', {
id: 'AjaxDeleteText',
text: text.plain()
}),
$('<label>', {
id: 'AjaxDeleteReasonLabel',
'for': 'AjaxDeleteReasonSelect',
text: this.msg('reason') + ' '
}),
this.reasons[Number(isImg)],
$('<br>'),
$('<input>', {
id: 'AjaxDeleteCustomReason',
type: 'text'
}).attr('size', 50),
$('<br>'),
$('<input>', {
id: 'AjaxDeleteWatch',
type: 'checkbox'
}),
$('<label>', {
'for': 'AjaxDeleteWatch',
id: 'AjaxDeleteWatchLabel',
text: this.msg('watch')
})
]);
$('#AjaxDeleteWatch').prop('checked', this.acw);
},
showModal: function(elements) {
var cap = this.action.charAt(0).toUpperCase() + this.action.substring(1);
this.$currentModal = dev.showCustomModal(this.i18n.msg('title-' + this.action, this.page).plain(), {
id: 'Ajax' + cap + 'Modal',
content: $('<div>').append(elements),
buttons: [
{
defaultButton: true,
message: this.i18n.msg(this.action).escape(),
handler: function() {
this['handle' + cap]();
}.bind(this)
}
]
});
// User who wanted to delete something would probably
// want these focused
var $reason = $('#AjaxDeleteCustomReason, #AjaxUndeleteReason');
if ($('#AjaxDeleteReasonSelect option').length < 2) {
$reason.focus();
} else {
$('#AjaxDeleteReasonSelect').focus();
}
// When Enter is pressed, execute the deletion
$reason.keydown(this.keydown.bind(this));
},
keydown: function(event) {
if (event.which === 13 || event.which === 11) {
if (this.action === 'delete') {
this.handleDelete();
} else {
this.handleUndelete();
}
}
},
handleDelete: function() {
var customReason = $('#AjaxDeleteCustomReason').val(),
selectedReason = $('#AjaxDeleteReasonSelect').val();
this.apiCall(
(!selectedReason || selectedReason === 'other') ?
customReason :
customReason ?
selectedReason + ': ' + customReason :
selectedReason,
$('#AjaxDeleteWatch').prop('checked')
);
this.close();
},
apiCall: function(reason, watchlist) {
var params = {
action: this.action,
bot: true,
reason: reason,
title: this.page,
token: mw.user.tokens.get('csrfToken')
};
if (this.rev) {
params.oldimage = this.rev;
}
if (watchlist) {
params.watchlist = 'watch';
}
this.api.post(params).done(this.cbDone.bind(this)).fail(this.cbFail.bind(this));
},
cbDone: function(d) {
if (d.error) {
this.banner('error', d.error.code);
} else {
if (this.config.reload) {
location.reload();
} else {
this.banner('confirm');
$.get('/wiki/Special:BlankPage');
}
}
},
cbFail: function(code) {
this.banner('error', code || 'http');
},
banner: function(type, code) {
var msg = this.action;
if (this.rev) {
msg += 'rev';
}
msg = this.i18n.msg(type + msg, this.page).parse() + ' ';
if (code === 'http') {
msg += $('<a>', {
href: this.action === 'delete' ?
mw.util.getUrl(this.page, {
action: 'delete'
}) :
mw.util.getUrl('Special:Undelete/' + this.page),
text: this.msg('retry')
}).prop('outerHTML');
} else if (code) {
msg += ' (' + code + ')';
}
new this.BannerNotification(msg, type).show();
},
close: function() {
dev.showCustomModal.closeModal(this.$currentModal);
},
doUndelete: function(url) {
this.action = 'undelete';
if (Object.fromEntries(new URLSearchParams(url.search)).target) {
this.page = Object.fromEntries(new URLSearchParams(url.search)).target
.replace(/_/g, ' ');
} else {
this.page = decodeURIComponent(url.pathname)
.replace(/_/g, ' ');
this.undelete.forEach(function(alias) {
this.page = this.page
.replace(mw.util.getUrl('Special:' + alias + '/'), '');
}, this);
}
this.showModal([
$('<p>', {
id: 'AjaxDeleteText',
html: this.i18n.msg('undeletepage', this.page).parse()
}),
$('<input>', {
id: 'AjaxUndeleteReason',
type: 'text'
}).attr('size', 40)
]);
},
handleUndelete: function() {
this.apiCall($('#AjaxUndeleteReason').val().trim());
this.close();
},
bindShortcut: function(Mousetrap) {
Mousetrap.bind('d', this.shortcut);
},
shortcut: function() {
$('#ca-delete').click();
},
msg: function(code) {
return this.i18n.msg(code).plain();
}
};
var preload = AjaxDelete.preload.bind(AjaxDelete);
mw.hook('dev.i18n').add(preload);
mw.hook('dev.showCustomModal').add(preload);
mw.hook('dev.banners').add(preload);
})();