MediaWiki:Gadget-AjaxQuickDelete.js: Różnice pomiędzy wersjami
Wygląd
Usunięta treść Dodana treść
poprawki odnosnie znaku nowej linii (autor: User:Msz2001, Specjalna:Diff/66424473/66466182) |
m alfabetyczne sortowanie SPEEDY_PRESETS |
||
Linia 109: | Linia 109: | ||
//! List of presets for speedy deletion |
//! List of presets for speedy deletion |
||
var SPEEDY_PRESETS = [ |
var SPEEDY_PRESETS = [ |
||
' |
'bezsens', 'brudnopis', 'dane osobowe', 'hoax', 'moje', 'nieaktualne', |
||
' |
'nieency', 'npa', 'or', 'plik', 'redir', 'reklama', 'substub', |
||
' |
'techniczne', 'test', 'translator', 'wandalizm', 'wygłup', |
||
' |
'zniesławienie' |
||
]; |
]; |
||
Wersja z 12:16, 5 mar 2022
/* 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",
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 (AjaxQuickDelete)",
dnuRequestedPageEditSummary: "Zgłoszono do usunięcia (AjaxQuickDelete)",
dnuLobbyEditSummary: "Dodano [[:$1]] (AjaxQuickDelete)",
dnuTalkSectionTitle: "DNU: [[:$1]]",
dnuTalkSummary: "Strona [[:$1]] została zgłoszona do usunięcia (AjaxQuickDelete)",
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]] (AjaxQuickDelete)",
apiFail: "Żądanie API zakończyło się błędem: $1",
cantGetAuthor: "Nie udało się pobrać autora strony.",
cantResolveTalkRedirects: "Nie udało się wykryć przekierowań w dyskusjach użytkowników.",
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: "Nie udało się powiadomić autora o zgłoszeniu.",
cantNotifyWikiprojects: "Nie udało się powiadomić wikiprojektów o zgłoszeniu.",
apiAbort: "Połączenie zostało przerwane.",
apiTimeout: "Czas oczekiwania na odpowiedź serwera upłynął.",
apiNetworkError: "Wystąpił błąd sieci. Upewnij się, że masz połączenie z Internetem.",
apiHttpError: "Serwer zwrócił błąd $1.",
apiFormatError: "Serwer odpowiedział w nieprawidłowym formacie. Szczegóły błędu być może znajdują się w konsoli w narzędziach deweloperskich (zazwyczaj dostępnych po naciśnięciu F12).",
apiPageDeleted: "Strona została usunięta.",
apiGeneralError: "Serwer nie był w stanie przetworzyć żądania: $1"
};
//! 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 the interface:
// {
// content: OO.ui.PanelLayout,
// proceed: (OO.ui.ProcessDialog) => OO.ui.Process,
// getSynchronizedState: () => object,
// applySynchronizedState: (object) => void
// }
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ą)'
},
{
templateParam: 'biografia',
subpage: 'biografie',
label: 'biografia'
},
{
templateParam: 'technikalia',
subpage: 'kwestie techniczne',
label: 'technikalia (np. szablon, kategoria)'
}
];
//! List of presets for speedy deletion
var SPEEDY_PRESETS = [
'bezsens', 'brudnopis', 'dane osobowe', 'hoax', 'moje', 'nieaktualne',
'nieency', 'npa', 'or', 'plik', 'redir', 'reklama', 'substub',
'techniczne', 'test', 'translator', 'wandalizm', 'wygłup',
'zniesławienie'
];
//! List of special wikiprojects
var TECH_WIKIPROJECTS = [
{
label: 'Ilustrowanie',
page: 'Wikiprojekt:Ilustrowanie'
},
{
label: 'Infoboksy',
page: 'Wikiprojekt:Infoboksy'
},
{
label: 'Kategoryzacja',
page: 'Wikiprojekt:Kategoryzacja'
},
{
label: 'Sprzątanie Szablonów',
page: 'Wikiprojekt:Sprzątanie Szablonów'
}
];
/**
* 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
};
}
/**
* Downloads a list of active wikiprojects
* @returns {jQuery.Promise} A promise resolving to the list of wikiprojects
*/
function getWikiprojects() {
var deferred = $.Deferred();
// Don't download the wikiprojects everytime user visits the site
try {
var storage = window.localStorage;
if(storage) {
var fetchDate = storage.getItem('AjaxQuickDelete.wikiprojects.fetchDate');
if(fetchDate) {
var now = Date.now();
// 7 days * 24 hrs * 60 mins * 60 secs * 1000 ms = 604 800 000
if(now - parseInt(fetchDate) <= 604800000) {
var data = storage.getItem('AjaxQuickDelete.wikiprojects.list');
if(data) {
deferred.resolve(JSON.parse(data));
return deferred.promise();
}
}
}
}
} catch(e) {
console.warn('[AjaxQuickDelete] Error parsing storage data', e);
}
$.ajax('/w/index.php?title=Wikipedia:Wikiprojekt/Spis_wikiprojektów&action=raw')
.done(function (data) {
if(!data) return;
var active_wp = data.match(/=== Aktywne wikiprojekty według dziedzin wiedzy ===[\s\S]*?=== Aktywne wikiprojekty specjalne ===/)[0];
// positive lookbehind alternative (global match) by Adam Katz → https://stackoverflow.com/a/35143111
var regexp = /\[\[Wikiprojekt:((GLAM\/)?[^:|\]/#]+)\|/g; // from /(?<=\[\[Wikiprojekt:)[^:|\]\/#]+(?=\|)/g
var wikiprojects = [];
var matcher;
// eslint-disable-next-line no-cond-assign
while(matcher = regexp.exec(active_wp)) {
wikiprojects.push({
label: matcher[1],
page: 'Wikiprojekt:' + matcher[1]
});
}
/* Sorting strings with accented characters using "Intl.Collator" or "localeCompare"
→ http://www.jstips.co/en/javascript/sorting-strings-with-accented-characters/
localeCompare is more backwards compatible with basic support (no locale-sort) extending before Intl.Collator was introduced
→ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare
*/
wikiprojects.sort(function (a, b) {
return a.label.localeCompare(b.label, 'pl');
});
// Add special wikiprojects
for(var i = 0; i < TECH_WIKIPROJECTS.length; i++) {
wikiprojects.push(TECH_WIKIPROJECTS[i]);
}
// Store data in the localStorage
try {
var storage = window.localStorage;
if(storage) {
storage.setItem('AjaxQuickDelete.wikiprojects.fetchDate', Date.now());
storage.setItem('AjaxQuickDelete.wikiprojects.list', JSON.stringify(wikiprojects));
}
} catch(e) {
console.warn('[AjaxQuickDelete] Error saving storage data', e);
}
deferred.resolve(wikiprojects);
}).fail(function (reason) {
deferred.reject(reason);
});
return deferred.promise();
}
//! 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'],
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);
// 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);
}
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 {{content: OO.ui.PanelLayout, proceed: (dialog: OO.ui.ProcessDialog) => OO.ui.Process}} The view
*/
function createViewDNU() {
// Create layout
var contentPanel = new OO.ui.PanelLayout({
expanded: false
});
var fieldset = new OO.ui.FieldsetLayout({});
// 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'
});
// 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
});
var requestTypeLayout = new OO.ui.FieldLayout(requestTypeSelect, {
label: MSG.dnuRequestTypeLabel
});
// 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
getWikiprojects().then(function (wikiprojects) {
var wikiprojectOptions = wikiprojects.map(
function (wikiproject) {
return {
data: wikiproject,
label: wikiproject.label
};
}
);
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 {{content: OO.ui.PanelLayout, proceed: (dialog: OO.ui.ProcessDialog) => OO.ui.Process}} 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 + "}}\n";
subpageText += reason;
var usersToNotify = [];
var talkPages = [];
var wikiprojectPages = data.wikiprojects.map(function (p) { return p.page; });
// Construct the process to be invoked
return new OO.ui.Process()
.next(function () {
return getCurrentPageCreator().then(function (users) { usersToNotify = users; });
})
.next(function () {
return resolveTalkRedirects(usersToNotify).then(function (talk) { talkPages = talk; });
})
.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 notifyUsers(talkPages, talkMessage);
})
.next(function () {
return notifyWikiprojects(wikiprojectPages, talkTemplate);
})
.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 = {
formatversion: 2,
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 = {
formatversion: 2,
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();
}
/**
* Resolves the redirects on users' talk pages
*
* @param {string[]} users List of users for whom to resolve talk page redirects
* @returns {jQuery.Promise<string[]>} A promise resolving to the list of talk pages
*/
function resolveTalkRedirects(users) {
var deferred = $.Deferred();
// Add the 'User talk:' (NS #3) prefix to the user names
var userTalk = mw.config.get('wgFormattedNamespaces')[3] + ':';
var talkPages = users.map(function (user) {
return userTalk + user;
});
// Prepare the query
var query = {
action: 'query',
redirects: '',
titles: talkPages.join('|')
};
// Invoke the API
doAPICall(query).done(
function (result) {
// If there are no redirects, do nothing
if(!result.query || !result.query.redirects) {
return deferred.resolve(talkPages);
}
var redirs = result.query.redirects;
// First, get the list of redirected pages
var redirSources = [];
redirs.forEach(function (redir) {
redirSources.push(redir.from);
});
// Remove the redirected pages from the list
talkPages = talkPages.filter(function (page) {
return redirSources.indexOf(page) !== -1;
});
// Lastly, add the targets of redirects to the list
redirs.forEach(function (redir) {
talkPages.push(redir.to);
});
deferred.resolve(talkPages);
}
).fail(function (reason) {
deferred.reject(new OO.ui.Error(MSG.cantResolveTalkRedirects + '\n' + reason));
});
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
var api = new mw.Api();
api.create(
requestPageTitle,
{
summary: MSG.dnuSubpageCreateSummary,
watchlist: watchlist,
errorformat: 'html',
errorlang: mw.config.get('wgUserLanguage'),
errorsuselocal: true
},
subpageText
).then(function () {
deferred.resolve();
}).fail(function (code, result) {
var errorMessage = processApiError(code, result);
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,
summary: editSummary,
watchlist: 'nochange',
nocreate: true,
errorformat: 'html',
errorlang: mw.config.get('wgUserLanguage'),
errorsuselocal: true
};
var api = new mw.Api();
if(window.AjaxDeleteWatchReportedPage) params.watchlist = 'watch';
// 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 = processApiError(code, result);
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();
}
/**
* Leaves a message on the users' talk pages
*
* @param {string[]} talkPages The pages to leave the message on
* @param {string} talkMessage The message to leave on the every talk page
* @returns {jQuery.Promise<void>} A promise representing the operation.
*/
function notifyUsers(talkPages, talkMessage) {
var deferred = $.Deferred();
var pageTitle = mw.config.get('wgPageName').replaceAll('_', ' ');
var sectionTitle = MSG.dnuTalkSectionTitle.replace('$1', pageTitle);
var editSummary = MSG.dnuTalkSummary.replace('$1', pageTitle);
// Store each of the returned promises in an array and wait for all of them in the end
var promises = [];
for(var i = 0; i < talkPages.length; i++) {
var talkPage = talkPages[i];
promises.push(
appendSection(
talkPage,
sectionTitle,
talkMessage,
editSummary,
window.AjaxDeleteWatchUserTalk ? 'watch' : 'nochange'
));
}
// Resolve our promise only after all edits to the talk pages are finished
$.when.apply($, promises).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();
}
/**
* Adds a new section to the page
*
* @param {string} pageTitle The page to edit
* @param {string} sectionTitle Title of the new section
* @param {string} sectionText The content of the new section
* @param {string} editSummary Summary of the edit
* @param {string} watchlist Whether to watch the page
* @returns {jQuery.Promise}
*/
function appendSection(pageTitle, sectionTitle, sectionText, editSummary, watchlist) {
var deferred = $.Deferred();
watchlist = watchlist || 'nochange';
// Prepare parameters
var params = {
action: 'edit',
title: pageTitle,
sectiontitle: sectionTitle,
text: sectionText,
section: 'new',
summary: editSummary,
watchlist: watchlist,
errorformat: 'html',
errorlang: mw.config.get('wgUserLanguage'),
errorsuselocal: true
};
var api = new mw.Api();
// Perform an API request to edit the page
// and resolve the promise on success.
api.postWithToken('csrf', params).done(function () {
deferred.resolve();
}).fail(function (code, result) {
var errorMessage = processApiError(code, result);
return deferred.reject(
MSG.apiFail.replace('$1', errorMessage)
);
});
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();
var api = new mw.Api();
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,
errorformat: 'html',
errorlang: mw.config.get('wgUserLanguage'),
errorsuselocal: true
};
}).then(function () {
deferred.resolve();
}).fail(function (code, result) {
var errorMessage = processApiError(code, result);
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;
var api = new mw.Api();
api.post(params).done(function (result, jqXHR) {
deferred.resolve(result);
}).fail(function (code, result) {
var errorMessage = processApiError(code, result);
return deferred.reject(
MSG.apiFail.replace('$1', errorMessage)
);
});
return deferred.promise();
}
/**
* Processes the data obtained from the API in order to prepare the error message
*
* @param {string} code The error code returned by the API
* @param {object} result The data about the error
* @returns {string} The error message
*/
function processApiError(code, result) {
switch(code) {
case 'missingtitle':
return MSG.apiPageDeleted;
case 'http':
switch(result.textStatus) {
case 'abort':
return MSG.apiAbort;
case 'timeout':
return MSG.apiTimeout;
case 'error':
if(result.exception === '') {
return MSG.apiNetworkError;
} else {
// Doesn't occur for HTTP/2
return MSG.apiHttpError.replace('$1', result.exception);
}
case 'parseerror':
console.log(result.exception);
return MSG.apiFormatError;
}
}
// unknown, generic
console.error(result);
var resultInfo = new mw.Api().getErrorMessage(result);
resultInfo = resultInfo.text();
return MSG.apiGeneralError.replace('$1', resultInfo);
}
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>