MediaWiki:Gadget-AjaxQuickDelete.js
Uwaga: aby zobaczyć zmiany po opublikowaniu, może zajść potrzeba wyczyszczenia pamięci podręcznej przeglądarki.
- Firefox / Safari: Przytrzymaj Shift podczas klikania Odśwież bieżącą stronę, lub naciśnij klawisze Ctrl+F5, lub Ctrl+R (⌘-R na komputerze Mac)
- Google Chrome: Naciśnij Ctrl-Shift-R (⌘-Shift-R na komputerze Mac)
- Internet Explorer / Edge: Przytrzymaj Ctrl, jednocześnie klikając Odśwież, lub naciśnij klawisze Ctrl+F5
- Opera: Naciśnij klawisze Ctrl+F5.
/* eslint-disable array-bracket-newline */
/* eslint-disable array-element-newline */
/* eslint-disable indent */
/* global $, mw, OO */
/**
* @author [[c:User:Ilmari Karonen]] (original code)
* @author [[c:User:DieBuche]] (rewritten & extended)
* @author [[c:User:Lupo]] (bot detection and encoding fixer)
* @author [[w:pl:User:Lampak]] (adaptation for Polish Wikipedia)
* @author [[w:pl:User:Msz2001]] (rewrite for OOUI and code cleanup)
*
* Originally written on Wikimedia Commons as a replacement for [[c:MediaWiki:Quick-delete-code.js]]
*
* Full list of authors:
* - original version: https://commons.wikimedia.org/w/index.php?title=MediaWiki:Gadget-AjaxQuickDelete.js&action=history
* - adapted on plwiki: https://pl.wikipedia.org/w/index.php?title=MediaWiki:Gadget-AjaxQuickDelete.js&action=history
*
* Idea for quick reasons for speedy deletion and copyvio source based on [[w:pl:User:Krzysiek 123456789]]'s fork of this gadget
*
* <nowiki>
*/
(function () {
//! Translatable messages
var MSG = {
toolboxLink: "Zgłoś do usunięcia",
dialogTitle: "Zgłaszanie do usunięcia",
dialogCancel: "Anuluj",
dialogSubmit: "Zatwierdź",
dnuReasonLabel: "Wyjaśnij dokładnie, dlaczego ta strona nie nadaje się do Wikipedii:",
dnuReasonPlaceholder: "Uzasadnienie zgłoszenia",
dnuReasonHelp: "Twoje zgłoszenie zostanie automatycznie podpisane.",
dnuRequestTypeLabel: "Rodzaj zgłoszenia",
dnuWikiprojectsToNotify: "Jakie wikiprojekty powiadomić?",
dnuSpeedyNotice: "Jeżeli strona spełnia <a href='https://pl.wikipedia.org/wiki/Wikipedia:Ekspresowe_kasowanie' target='_blank'><b>zasady ekspresowego kasowania</b></a>, zamiast rozpoczynać nad nią dyskusję możesz zgłosić ją do <b>ekspresowego kasowania</b>. Aby to zrobić, wybierz opcję „Ekspresowe kasowanie” powyżej i podaj powód, a następnie kliknij przycisk „Zatwierdź”.",
dnuLobbyMarker: "<!-- Nowe zgłoszenia wstawiaj poniżej tej linii. Nie usuwaj tej linii -->",
dnuSubpageCreateSummary: "Nowe zgłoszenie do usunięcia",
dnuRequestedPageEditSummary: "Zgłoszono do usunięcia",
dnuLobbyEditSummary: "Dodano [[:$1]]",
dnuTalkSectionTitle: "DNU: [[:$1]]",
dnuTalkSummary: "Strona [[:$1]] została zgłoszona do usunięcia",
dnuCheckSources: "{{Sprawdź w źródłach|$1}}",
dnuCheckSourcesInsert: "Wstaw szablon <kbd>{{Sprawdź w źródłach}}</kbd>",
ekReasonLabel: "Uzasadnienie zgłoszenia do ekspresowego kasowania:",
ekReasonPlaceholder: "Wpisz lub wybierz z listy powód zgłoszenia",
ekCopyvioLinkLabel: "Jeżeli treść narusza prawa autorskie, możesz podać link do oryginalnej publikacji",
ekCopyvioLinkPlaceholder: "Wklej link do strony, z której pochodzi artykuł",
ekSpeedyNotice: "Do ekspresowego kasowania można zgłaszać wyłącznie strony, które spełniają <a href='https://pl.wikipedia.org/wiki/Wikipedia:Ekspresowe_kasowanie' target='_blank'><b>zasady usuwania tym trybem</b></a>. W przeciwnym razie należy rozpocząć <b>dyskusję nad usunięciem</b>. Aby to zrobić, wybierz opcję „Dyskusja nad usunięciem” powyżej i podaj powód oraz rodzaj zgłoszenia, a następnie kliknij przycisk „Zatwierdź”.",
ekPageEditSummary: "Zgłoszono do [[:Kategoria:Ekspresowe kasowanie|ekspresowego kasowania]]",
apiFail: "Żądanie API zakończyło się błędem: $1",
cantGetAuthor: "Nie udało się pobrać autora strony.",
cantCreateRequestSubpage: "Nie udało się utworzyć podstrony Poczekalni dla zgłoszenia.",
cantPrependTemplate: "Nie udało się wstawić szablonu do artykułu. Podstrona dla zgłoszenia istnieje, ale proces należy dokończyć ręcznie. Wstaw na początek artykułu szablon $1, a następnie poinformuj autora artykułu o zgłoszeniu go do Poczekalni.",
cantAddToLobby: "Nie udało się dołączyć zgłoszenia na głównej stronie Poczekalni. Przejdź na stronę <a href='/wiki/$1'>$1</a>, a następnie dopisz na niej kod $2.",
cantNotifyUsers: "Zgłoszenie zostało utworzone, lecz nie udało się powiadomić o nim autora artykułu.",
cantNotifyWikiprojects: "Zgłoszenie zostało utworzone, lecz nie udało się powiadomić o nim wikiprojektów i autora artykułu.",
};
var api = new mw.Api({
parameters: {
format: 'json',
formatversion: 2,
tags: 'AjaxQuickDelete',
errorformat: 'html',
errorlang: mw.config.get('wgUserLanguage'),
errorsuselocal: true
}
});
//! List of the available deletion procedures
// label: the displayed text
// title: the tooltip text
// createView: a function with no arguments that creates a view with all the controls. The return type must comply with type View as at the end of the document.
var AVAILABLE_PROCEDURES = [
{
label: 'Dyskusja nad usunięciem',
title: 'Zgłoś do Poczekalni',
createView: createViewDNU
},
{
label: 'Ekspresowe kasowanie',
title: 'Zgłoś do Ekspresowego kasowania',
createView: createViewEK
}
];
//! List of request types available for DNU
var REQUEST_TYPES = [
{
templateParam: 'artykuł',
subpage: 'artykuły',
label: 'artykuł (niebędący biografią)',
isMetapage: false
},
{
templateParam: 'biografia',
subpage: 'biografie',
label: 'biografia',
isMetapage: false
},
{
templateParam: 'technikalia',
subpage: 'kwestie techniczne',
label: 'technikalia (np. szablon, kategoria)',
isMetapage: true
}
];
//! Users in these categories will not be notified about the deletion request
var NEGATIVE_USER_CATEGORIES = [
'Zmarli wikipedyści'
];
// The current state of the dialog (used to save the user input after closing)
var dialogState = null;
/**
* Checks what is the default request type for the current page
* @returns {number} The request type index
*/
function getDefaultRequestType(){
var NS = mw.config.get('wgNamespaceIds');
var namespace = mw.config.get('wgNamespaceNumber');
// Templates and categories go to technical bucket
if (namespace === NS.template || namespace === NS.category) {
return 2;
}
// Biographies, detected by categories
var categories = mw.config.get('wgCategories');
for(var i = 0; i < categories.length; i++){
var category = categories[i];
if(/^Urodzeni w /.test(category)
|| /^Zmarli w /.test(category)){
return 1;
}
}
// Everything else is an article
return 0;
}
//! List of presets for speedy deletion
var SPEEDY_PRESETS = [
'bezsens', 'brudnopis', 'dane osobowe', 'hoax', 'moje', 'nieaktualne',
'nieency', 'npa', 'or', 'plik', 'powiązany artykuł zamieniono na przekierowanie',
'redir', 'reklama', 'substub', 'techniczne', 'test', 'translator',
'wandalizm', 'wygłup', 'zniesławienie'
];
/**
* Computes the title of a subpage with the deletion request
* @param {string} pageTitle The title of the page that is requested to be deleted
* @param {number} requestType The index of request type in REQUEST_TYPES
* @returns {{fullTitle: string, subpage: string, lobbyPage: string}}
*/
function getDnuSubpageTitle(pageTitle, requestType) {
var pad0 = function (s) { s = "" + s; return (s.length > 1 ? s : "0" + s); }; // zero-pad to two digits
var date = new Date();
var requestSubpage = '';
requestSubpage += date.getUTCFullYear() + ':';
requestSubpage += pad0(date.getUTCMonth() + 1) + ':';
requestSubpage += pad0(date.getUTCDate()) + ':';
requestSubpage += pageTitle;
var lobbyPage = 'Wikipedia:Poczekalnia/';
lobbyPage += REQUEST_TYPES[requestType].subpage;
var requestFullTitle = lobbyPage + '/' + requestSubpage;
return {
fullTitle: requestFullTitle,
subpage: requestSubpage,
lobbyPage: lobbyPage
};
}
//! Actual start of the gadget implementation
/** Installs the gadget (entry point) */
function install() {
// Can't delete special pages
if(mw.config.get('wgNamespaceNumber') < 0) return;
// Create a toolbox link
var portletId = mw.config.get('skin') === 'timeless' ? 'p-pagemisc' : 'p-tb';
var link = mw.util.addPortletLink(portletId, '#', MSG.toolboxLink, 't-ajaxquickdelete', null);
// bind toolbox link
$(link).click(function (e) {
e.preventDefault();
mw.loader.using(
['oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui-windows', 'mediawiki.api', 'mediawiki.Title', 'mediawiki.messagePoster'],
function () {
displayRequestDeleteDialog();
}
);
});
}
//! Start of the dialog window definition
function displayRequestDeleteDialog() {
var currentView = null;
// Initialize the dialog's class
function RequestDeleteDialog(config) {
RequestDeleteDialog.super.call(this, config);
}
OO.inheritClass(RequestDeleteDialog, OO.ui.ProcessDialog);
RequestDeleteDialog.static.name = "RequestDeleteDialog";
RequestDeleteDialog.static.title = MSG.dialogTitle;
RequestDeleteDialog.static.actions = [
{
label: MSG.dialogCancel,
flags: "safe",
},
{
action: 'submit',
label: MSG.dialogSubmit,
flags: ["destructive", "primary"]
}
];
// Populate the dialog with controls
RequestDeleteDialog.prototype.initialize = function () {
RequestDeleteDialog.super.prototype.initialize.call(this);
// Create layout
var rootPanel = new OO.ui.PanelLayout({
padded: true,
expanded: false,
});
// Procedures select
var panels = [];
var procedureOptions = AVAILABLE_PROCEDURES.map(function (p, i) {
var view = p.createView();
panels.push(view.content);
if(i == 0) currentView = view;
return new OO.ui.ButtonOptionWidget({
data: {
index: i,
view: view
},
label: p.label,
title: p.title,
selected: i == 0
});
});
var procedureSelect = new OO.ui.ButtonSelectWidget({
items: procedureOptions,
classes: ['center']
});
var procedureWrapper = $('<div style="margin-bottom:12px">');
procedureWrapper.append(procedureSelect.$element);
// The container for panels for each procedure
var formBody = new OO.ui.StackLayout({
expanded: false,
items: panels
});
formBody.setItem(panels[0]);
rootPanel.$element.append(
procedureWrapper,
formBody.$element
);
this.$body.append(rootPanel.$element);
currentView.applySynchronizedState(dialogState);
// Handle procedure changes
procedureSelect.on('select', function (item) {
var state = currentView.getSynchronizedState();
currentView = item.data.view;
formBody.setItem(currentView.content);
currentView.applySynchronizedState(state);
});
};
RequestDeleteDialog.prototype.getActionProcess = function (action) {
var dialog = this;
if(action === 'submit') {
if(currentView) return currentView.proceed(dialog);
}
if(!action && currentView) {
dialogState = currentView.getSynchronizedState();
}
return RequestDeleteDialog.super.prototype.getActionProcess.call(this, action);
};
// Make the window
var requestDeleteDialog = new RequestDeleteDialog({
size: "large",
});
// Create and append a window manager
var windowManager = new OO.ui.WindowManager();
$(document.body).append(windowManager.$element);
windowManager.addWindows([requestDeleteDialog]);
windowManager.openWindow(requestDeleteDialog);
}
//! Define the views for different procedures of deletion
/**
* Creates a view for the standard deletion request
* @returns {View} The view
*/
function createViewDNU() {
// Create layout
var contentPanel = new OO.ui.PanelLayout({
expanded: false
});
var fieldset = new OO.ui.FieldsetLayout({});
var $reasonHelp = $('<div></div>');
$reasonHelp.append('<p>' + MSG.dnuReasonHelp + '</p>');
// Check sources link
var $checkSourcesLink = $('<a href="javascript:void(0)">' + MSG.dnuCheckSourcesInsert + '</a>');
$reasonHelp.append($checkSourcesLink);
// Reason field
var reasonInput = new OO.ui.MultilineTextInputWidget({
placeholder: MSG.dnuReasonPlaceholder,
multiline: true,
rows: 5,
});
var reasonLayout = new OO.ui.FieldLayout(reasonInput, {
label: MSG.dnuReasonLabel,
align: 'top',
help: $reasonHelp,
helpInline: true
});
$checkSourcesLink.on('click', function (e) {
e.preventDefault();
var pageTitle = mw.config.get('wgPageName').replace(/_/g, ' ');
var template = mw.format(MSG.dnuCheckSources, pageTitle);
var currentValue = reasonInput.getValue();
if(currentValue.indexOf(template) != -1) return;
reasonInput.setValue(template + '\n' + currentValue);
});
// Type of request
var typeOptions = REQUEST_TYPES.map(
function (t, i) { return { data: i, label: t.label }; }
);
var requestTypeSelect = new OO.ui.DropdownInputWidget({
options: typeOptions,
value: getDefaultRequestType()
});
var requestTypeLayout = new OO.ui.FieldLayout(requestTypeSelect, {
label: MSG.dnuRequestTypeLabel
});
var onRequestTypeChange = function(){
var type = REQUEST_TYPES[requestTypeSelect.getValue()];
if(!type) return;
$checkSourcesLink.css('visibility', type.isMetapage ? 'hidden' : 'visible');
};
requestTypeSelect.on('change', onRequestTypeChange);
onRequestTypeChange();
// Notify wikiprojects
var wikiprojectsSelect = new OO.ui.MenuTagMultiselectWidget({
inputPosition: 'inline',
options: []
});
var wikiprojectsLayout = new OO.ui.FieldLayout(wikiprojectsSelect, {
label: MSG.dnuWikiprojectsToNotify
});
// The wikiprojects select is populated asynchronously
gadget.getWikiprojects({includeGadgets: ['dnu']}).then(function (result) {
var wikiprojectOptions = result.wikiprojects.map(
function (wikiproject) {
return {
data: wikiproject.page,
label: wikiproject.name
};
}
);
wikiprojectsSelect.addOptions(wikiprojectOptions);
});
// Speedy deletion notice
var speedyNotice = $("<p>").html(MSG.dnuSpeedyNotice);
// Add the inputs to the fieldset
fieldset.addItems([
reasonLayout,
requestTypeLayout,
wikiprojectsLayout
]);
// And finally append all the controls to the window
contentPanel.$element.append(
fieldset.$element,
speedyNotice
);
return {
content: contentPanel,
proceed: function (dialog) {
var reason = reasonInput.getValue();
var requestType = requestTypeSelect.getValue();
var wikiprojects = wikiprojectsSelect.getValue();
return controlDnuRequest({
reason: reason,
requestType: requestType,
wikiprojects: wikiprojects,
dialog: dialog
});
},
getSynchronizedState: function () {
return {
reason: reasonInput.getValue()
};
},
applySynchronizedState: function (state) {
if(state && state.reason) reasonInput.setValue(state.reason);
}
};
}
/**
* Creates a view for the speedy deletion request
* @returns {View} The view
*/
function createViewEK() {
// Create layout
var contentPanel = new OO.ui.PanelLayout({
expanded: false
});
var fieldset = new OO.ui.FieldsetLayout({});
// Reason field
var reasonInput = new OO.ui.ComboBoxInputWidget({
placeholder: MSG.ekReasonPlaceholder,
options: SPEEDY_PRESETS.map(function (p) { return { data: p }; })
});
var reasonLayout = new OO.ui.FieldLayout(reasonInput, {
label: MSG.ekReasonLabel,
align: 'top'
});
// Copyvio source
var copyvioLinkInput = new OO.ui.TextInputWidget({
placeholder: MSG.ekCopyvioLinkPlaceholder
});
var copyvioLayout = new OO.ui.FieldLayout(copyvioLinkInput, {
label: MSG.ekCopyvioLinkLabel,
align: 'top'
});
// Speedy deletion notice
var speedyNotice = $("<p>").html(MSG.ekSpeedyNotice);
// Add the inputs to the fieldset
fieldset.addItems([
reasonLayout,
copyvioLayout
]);
// And finally append all the controls to the window
contentPanel.$element.append(
fieldset.$element,
speedyNotice
);
return {
content: contentPanel,
proceed: function (dialog) {
var reason = reasonInput.getValue();
var copyvioLink = copyvioLinkInput.getValue();
return controlEkRequest({
reason: reason,
copyvioLink: copyvioLink,
dialog: dialog
});
},
getSynchronizedState: function () {
return {
reason: reasonInput.getValue()
};
},
applySynchronizedState: function (state) {
if(state && state.reason) reasonInput.setValue(state.reason);
}
};
}
//! The actual steps that are executed during the nomination for deletion
/**
* Controls the flow of the DNU request
* @param {{reason: string, requestType: number, wikiprojects: string[], dialog: OO.ui.ProcessDialog}} data Data about the request
* @returns {{OO.ui.Process}} A process representing the activity
*/
function controlDnuRequest(data) {
var pageTitle = mw.config.get('wgPageName').replaceAll('_', ' ');
var requestPage = getDnuSubpageTitle(pageTitle, data.requestType);
// Prepare the template to be added to the article
var templateRequestType = REQUEST_TYPES[data.requestType].templateParam;
var pageTemplate = '{{DNU|' + templateRequestType + '|podstrona=' + requestPage.subpage + '}}';
var talkTemplate = "{{DNUinfo|" + pageTitle + '|' + requestPage.fullTitle + "|" + templateRequestType + "}}";
var talkMessage = talkTemplate + " ~~~~";
// Ensure that the reason will have the signature on the end
var reason = data.reason;
reason = reason.replace('~~~~', '');
reason = reason.trim() + ' ~~~~';
// Prepare the content of the request page
var subpageText = "== [[:" + pageTitle + "]] ==\n";
subpageText += ": {{lnDNU|" + pageTitle + "|strona={{subst:FULLPAGENAME}}}}\n";
subpageText += reason;
var usersToNotify = [];
var wikiprojectPages = data.wikiprojects;
// Construct the process to be invoked
return new OO.ui.Process()
.next(function () {
return getCurrentPageCreator().then(function (users) { usersToNotify = users; });
})
.next(function () {
return filterOutUsersInCategories(usersToNotify, NEGATIVE_USER_CATEGORIES)
.then(function (filteredUsers) { usersToNotify = filteredUsers; });
})
.next(function () {
return createRequestPage(requestPage.fullTitle, subpageText);
})
.next(function () {
return markArticleWithTemplate(pageTemplate, MSG.dnuRequestedPageEditSummary);
})
.next(function () {
return addRequestToLobby(requestPage.lobbyPage, requestPage.fullTitle);
})
.next(function () {
return notifyWikiprojects(wikiprojectPages, talkTemplate);
})
.next(function () {
return createMessagePosters(usersToNotify).then(function (posters) {
return notifyUsers(posters, talkMessage);
});
})
.next(function () {
data.dialog.close({ action: 'submit' });
reloadPage();
});
}
/**
* Controls the flow of the speedy deletion request
* @param {{reason: string, copyvioLink: string, dialog: OO.ui.ProcessDialog}} data Data about the request
* @returns {{OO.ui.Process}} A process representing the activity
*/
function controlEkRequest(data) {
var copyvioLink = data.copyvioLink || '';
if(copyvioLink.length > 0) {
copyvioLink = '|' + copyvioLink;
}
// Just in case, not to break the template
var reason = data.reason.replace('|', '{{!}}').replace('=', '{{=}}');
// Prepare the template to be added to the article
var pageTemplate = '{{ek|' + reason + copyvioLink + '}}';
return new OO.ui.Process()
.next(function () {
return markArticleWithTemplate(pageTemplate, MSG.ekPageEditSummary);
})
.next(function () {
data.dialog.close({ action: 'submit' });
reloadPage();
});
}
//! Utility functions
/**
* Retrieves the creators of the current page.
*
* @returns {jQuery.Promise<string[]>} A promise resolving to the current page creators.
*/
function getCurrentPageCreator() {
var deferred = $.Deferred();
// Prepare the query parameters for the API
var pageTitle = mw.config.get('wgPageName');
var query = {
curtimestamp: true,
action: 'query',
prop: 'revisions',
rvprop: 'user',
rvlimit: 1,
rvdir: 'newer',
titles: pageTitle
};
// Different query for File: namespace
var NS_FILE = mw.config.get('wgNamespaceIds').file;
if(mw.config.get('wgNamespaceNumber') === NS_FILE) {
query = {
curtimestamp: true,
action: 'query',
meta: 'tokens',
prop: 'imageinfo',
iiprop: ['user', 'sha1', 'comment'],
iilimit: 50,
titles: pageTitle
};
}
// Invoke the API
doAPICall(query).done(
function (result) {
var usersToNotify = [];
if(mw.config.get('wgNamespaceNumber') !== NS_FILE) {
// For articles, notify only the original author
// Provided that he/she is not an anon
var revision = result.query.pages[0].revisions[0];
if(revision.anon === undefined) {
var creator = revision.user;
usersToNotify = [creator];
}
} else {
// For files, notify each uploader but not anons
var info = result.query.pages[0].imageinfo;
for(var i = 0; i <= info.length; i++) {
if(info[i].anon === undefined) {
usersToNotify.push(info[i].user);
}
}
}
// Remove duplicates
usersToNotify = usersToNotify.filter(function (item, pos, self) {
return self.indexOf(item) == pos;
});
// Don't notify the user who makes the request nor IPs
var currentUser = mw.config.get('wgUserName');
usersToNotify = usersToNotify.filter(function (user) { return user != currentUser; });
// Resolve the promise with the list of users
deferred.resolve(usersToNotify);
}
).fail(function (reason) {
deferred.reject(new OO.ui.Error(MSG.cantGetAuthor + '\n' + reason));
});
return deferred.promise();
}
/**
* Filters out the users that are in the specified categories
*
* @param {string[]} users The original usernames that would be to be notified
* @param {string[]} negativeCategories An array of category names that exclude its members from being notified (skip the category namespace prefix)
* @returns {jQuery.Promise<string[]>} A promise resolving to the list of users to be notified
*/
function filterOutUsersInCategories(users, negativeCategories) {
var deferred = $.Deferred();
// Short-circuit if there are no categories or no users
if(negativeCategories.length === 0 || users.length === 0) {
return deferred.resolve(users).promise();
}
var userNS = mw.config.get('wgFormattedNamespaces')[2] + ':';
var userPages = users.map(function (user) {
return userNS + user;
});
// Add the Category: namespace prefix to the category names
// so that we can compare the input with the API response
var categoryNS = mw.config.get('wgFormattedNamespaces')[14] + ':';
negativeCategories = negativeCategories.map(function (category) {
return categoryNS + category;
});
var query = {
action: 'query',
prop: 'categories',
titles: userPages.join('|'),
formatversion: 2,
cllimit: 500
};
// Perhaps we should support `clcontinue` but no user will be in >500 categories ever
doAPICall(query).done(function (result) {
var filteredUsers = [];
var pages = result.query.pages;
pages.forEach(function (page) {
// Extract the category names from the response
var categories = page.categories || [];
var categoryNames = categories.map(function (category) {
return category.title;
});
// Whether the user is in one of `negativeCategories`
var includesNegativeCategory = categoryNames.some(function (category) {
return negativeCategories.indexOf(category) !== -1;
});
if(!includesNegativeCategory) {
// Strip the user namespace prefix so as not to change the data
var userName = page.title.replace(/^[^:]+:/, '');
filteredUsers.push(userName);
}
});
deferred.resolve(filteredUsers);
}).fail(function (reason) {
// Assume that the user is not in any of the categories
deferred.resolve(users);
});
return deferred.promise();
}
/**
* Creates the page with the request
*
* Expects a context to be bound as `this`.
* The deletion reason has to be passed as `reason: string` in the context.
* The name of the request page has to be passed as `requestPageTitle: string` in the context.
*
* @param {string} requestPageTitle The title of the page to be created
* @param {string} subpageText The content to be put on the new page
* @returns {jQuery.Promise<void>} A promise representing the API operation.
*/
function createRequestPage(requestPageTitle, subpageText) {
var deferred = $.Deferred();
var watchlist = 'watch';
if(window.AjaxDeleteDontWatchRequests) watchlist = 'nowatch';
// Try to create the request subpage
api.create(
requestPageTitle,
{
summary: MSG.dnuSubpageCreateSummary,
watchlist: watchlist
},
subpageText
).then(function () {
deferred.resolve();
}).fail(function (code, result) {
var errorMessage = api.getErrorMessage(result).text();
return deferred.reject(new OO.ui.Error(MSG.cantCreateRequestSubpage + '\n' + errorMessage));
});
return deferred.promise();
}
/**
* Prepends the template to the current page. If the current page is
* in the Template: namespace, the `template` parameter is automatically
* wrapped into noinclude tags.
*
* @param {string} template The template to be added
* @param {string} editSummary The edit summary on the article page
* @returns {jQuery.Promise<void>} A promise representing the API operation.
*/
function markArticleWithTemplate(template, editSummary) {
var deferred = $.Deferred();
var pageTitle = mw.config.get('wgPageName');
var pageNamespace = mw.config.get('wgNamespaceNumber');
var NS_TEMPLATE = mw.config.get('wgNamespaceIds').template;
if(pageNamespace == NS_TEMPLATE) {
template = '<noinclude>' + template + '</noinclude>';
}
// Prepare parameters
var params = {
action: 'edit',
title: pageTitle,
prependtext: template + '\n',
summary: editSummary,
watchlist: window.AjaxDeleteWatchReportedPage ? 'watch' : 'nochange',
nocreate: true
};
// Perform an API request to edit the page
// and resolve the promise on success.
api.postWithEditToken(params).done(function () {
deferred.resolve();
}).fail(function (code, result) {
var errorMessage = api.getErrorMessage(result).text();
return deferred.reject(new OO.ui.Error(
MSG.cantPrependTemplate.replace('$1', template) + '\n' + errorMessage,
{ recoverable: false }
));
});
return deferred.promise();
}
/**
* Embeds the deletion request subpage onto the lobby.
*
* @param {string} lobbyPage The lobby page, where to embed the request
* @param {string} requestPageTitle Full title of the subpage with the request
* @returns {jQuery.Promise<void>} A promise representing the API operation.
*/
function addRequestToLobby(lobbyPage, requestPageTitle) {
var deferred = $.Deferred();
var appendedText = '{{' + requestPageTitle + '}}';
appendTextToMarker(
lobbyPage,
MSG.dnuLobbyMarker,
appendedText,
MSG.dnuLobbyEditSummary.replace('$1', requestPageTitle)
).then(function () {
deferred.resolve();
}).fail(function (reason) {
deferred.reject(new OO.ui.Error(
MSG.cantAddToLobby
.replace('$1', lobbyPage)
.replace('$2', '{{' + requestPageTitle + '}}')
+ '\n' + reason,
{ recoverable: false }
));
});
return deferred.promise();
}
/**
* Creates a poster objects for all talk pages of the users
*
* @param {string[]} userNames Names of the users to notify
* @returns {jQuery.Promise<mw.messagePoster.MessagePoster[]>} A promise resolving to a list of message posters
*/
function createMessagePosters(userNames) {
var deferred = $.Deferred();
var userTalk = mw.config.get('wgFormattedNamespaces')[3] + ':';
var talkPages = userNames.map(function (user) {
return userTalk + user;
});
var posters = [];
var prevPosterPromise = $.Deferred().resolve().promise();
talkPages.forEach(function (talkPage) {
var title = new mw.Title(talkPage);
prevPosterPromise = prevPosterPromise.then(function () {
return mw.messagePoster.factory.create(title)
.then(function (poster) {
posters.push(poster);
});
});
});
prevPosterPromise.then(function () {
deferred.resolve(posters);
});
return deferred.promise();
}
/**
* Leaves a message on the users' talk pages
*
* @param {mw.message.MessagePoster[]} messagePosters MessagePosters to user talk pages
* @param {string} talkMessage The message to leave on the every talk page
* @returns {jQuery.Promise<void>} A promise representing the operation.
*/
function notifyUsers(messagePosters, talkMessage) {
var deferred = $.Deferred();
var pageTitle = mw.config.get('wgPageName').replaceAll('_', ' ');
var sectionTitle = MSG.dnuTalkSectionTitle.replace('$1', pageTitle);
// Post the messages sequentially, one after another
var lastPromise = $.Deferred().resolve().promise();
messagePosters.forEach(function (poster) {
lastPromise = lastPromise.then(function () {
return poster.post(sectionTitle, talkMessage, {
tags: 'AjaxQuickDelete'
});
});
});
// Resolve our promise only after all edits to the talk pages are finished
lastPromise.then(function () {
deferred.resolve();
}).fail(function (reason) {
deferred.reject(new OO.ui.Error(
MSG.cantNotifyUsers + '\n' + reason,
{ recoverable: false }
));
});
return deferred.promise();
}
/**
* Adds a notification to the wikiproject pages
*
* @param {string[]} wikiprojects List of the wikiproject pages to leave the message on
* @param {string} message The message to leave
* @returns {jQuery.Promise<void>} A promise representing the API operation.
*/
function notifyWikiprojects(wikiprojects, message) {
var deferred = $.Deferred();
var pageTitle = mw.config.get('wgPageName').replaceAll('_', ' ');
var editSummary = MSG.dnuTalkSummary.replace('$1', pageTitle);
// Notify the wikiprojects asynchronously, at the same time
var promises = [];
for(var i = 0; i < wikiprojects.length; i++) {
promises.push(appendTextToMarker(
wikiprojects[i],
MSG.dnuLobbyMarker,
message,
editSummary
));
}
// Resolve our promise only after all edits are finished
$.when.apply($, promises).then(function () {
deferred.resolve();
}).fail(function (reason) {
deferred.reject(new OO.ui.Error(
MSG.cantNotifyWikiprojects + '\n' + reason,
{ recoverable: false }
));
});
return deferred.promise();
}
/**
* Finds a specific marker on the page and appends to it
*
* @param {string} pageTitle The page to edit
* @param {string} marker The marker to be found
* @param {string} textToAppend String that will be inserted just after the marker
* @param {string} editSummary Summary of the edit
* @returns {jQuery.Promise}
*/
function appendTextToMarker(pageTitle, marker, textToAppend, editSummary) {
var deferred = $.Deferred();
api.edit(pageTitle, function (revision) {
// Replace the marker with itself + the new text
var newContent = revision.content.replace(
marker,
marker + '\n' + textToAppend
);
// Check if the marker was found; if not, append the text to the end
if(newContent == revision.content) newContent += '\n' + textToAppend;
return {
text: newContent,
summary: editSummary
};
}).then(function () {
deferred.resolve();
}).fail(function (code, result) {
var errorMessage = api.getErrorMessage(result).text();
return deferred.reject(
MSG.apiFail.replace('$1', errorMessage)
);
});
return deferred.promise();
}
/**
* Does a MediaWiki API request.
*
* Uses POST requests for everything for simplicity.
*
* @param {object} params Query parameters.
* @returns {jQuery.Promise} Promise resolving to the API response
*/
function doAPICall(params) {
var deferred = $.Deferred();
params.errorformat = 'html';
params.errorlang = mw.config.get('wgUserLanguage');
params.errorsuselocal = true;
api.post(params).done(function (result, jqXHR) {
deferred.resolve(result);
}).fail(function (code, result) {
var errorMessage = api.getErrorMessage(result).text();
return deferred.reject(
MSG.apiFail.replace('$1', errorMessage)
);
});
return deferred.promise();
}
function reloadPage() {
var pageTitle = mw.config.get('wgPageName');
var title = encodeURIComponent(pageTitle).
replace(/%3A/g, ':').replace(/%20/g, '_').
replace(/\(/g, '%28').replace(/\)/g, '%29').
replace(/%2F/g, '/');
location.href = mw.config.get('wgServer') + mw.config.get('wgArticlePath').replace("$1", title);
}
// Install the gadget
install();
})();
// </nowiki>
/**
* @typedef {{
* content: OO.ui.PanelLayout,
* proceed: (dialog: OO.ui.ProcessDialog) => OO.ui.Process,
* getSynchronizedState: () => {reason: string},
* applySynchronizedState: (state: {reason: string} | null) => void
* }} View
*/