//@ts-check
/**
* A gadget that allows to send a message to the blocked user
* directly from the Special:Block page. It provides the administrator
* with some common messages to choose from.
*
* @author ]
* <nowiki>
*/
$(function(){
// Load data from JSON files (editable by ordinary admins)
var jsonData = require('./wiadomosci-o-blokadzie.json');
var MSG = jsonData.messages;
var blockMessages = jsonData.presets;
/*
Message parameter documentation:
bm-section-intro: $1 = blocked user name
bm-form-title: $1 = blocked user name
bm-subject-default: $1 = today date
*/
/**
* Creates a widget with a list of preset block messages
* @class
* @param {string} title The title of the widget
* @param {PresetMessage} messages The list of messages to choose from
*/
function PresetMessages(title, messages){
this.title = title;
this.messages = messages;
this.listeners = ;
/** The root DOM element of the messages selector */
this.$element = $('<div>')
.addClass('bm-preset-messages')
.append($('<h3>').text(this.title));
this.$list = $('<ul>').appendTo(this.$element);
this.fillList();
// Fire a hook so that user can add their own messages
mw.hook('userjs.block-messenger.presets').fire(this);
/*
Example hook listener:
mw.hook('userjs.block-messenger.presets').add((m) => m.addMessage(...));
*/
}
/**
* Sets the messages to display in the widget
* @param {PresetMessage} messages The messages to display in the widget
*/
PresetMessages.prototype.setMessages = function(messages){
this.messages = messages;
this.fillList();
};
/**
* Adds a message or multiple messages to the widget, without removing the existing ones
* @param {PresetMessage | PresetMessage} message A message or messages to be added to the widget
*/
PresetMessages.prototype.addMessage = function(message){
if(Array.isArray(message)){
this.messages.push.apply(this.messages, message);
}else{
this.messages.push(message);
}
this.fillList();
};
/**
* Fills a list with the provided messages.
*/
PresetMessages.prototype.fillList = function(){
var tm = this;
this.$list.empty();
this.messages.forEach(function(msg){
var $li = $('<li>').appendTo(tm.$list);
var $button = $('<button>').attr('type', 'button').appendTo($li);
var fullText = msg.text;
$button.attr('title', fullText);
var $title = $('<span>').addClass('bm-title').text(msg.title);
$button.append($title);
var displayText = fullText;
if(displayText.length > 150){
displayText = displayText.substring(0, 150) + '…';
}
var $text = $('<span>').addClass('bm-text').text(displayText);
$button.append($text);
$button.on('click', function() {
for(var j = 0; j < tm.listeners.length; j++){
tm.listeners(fullText);
}
});
});
};
/**
* Adds a listener to the widget
* @param {(text: string) => void} listener A function to be fired when a message is selected
*/
PresetMessages.prototype.addOnSelect = function(listener){
this.listeners.push(listener);
};
/**
* Creates a widget with a preview of the message
* @class
* @param {string} targetPage The page to which the message will be eventually posted
* @param {string} previewLabel The label of the preview box
* @param {string} emptyMessage The message to be displayed when the preview is empty
*/
function PreviewBox(targetPage, previewLabel, emptyMessage){
this.targetPage = targetPage;
this.emptyMessage = emptyMessage;
this.api = new mw.Api({
parameters: {
format: 'json',
formatversion: 2
}
});
this.$element = $('<div>')
.addClass('bm-preview-box bm-no-preview')
.attr('data-label', previewLabel);
this.update('', '');
}
/**
* Sets the label of the preview box
* @param {string} label New label
*/
PreviewBox.prototype.setLabel = function(label){
this.$element.attr('data-label', label);
};
/**
* Updates the preview with the provided wikitext
* @param {string} sectionTitle The title of the new section
* @param {string} wikitext The wikitext to be previewed
*/
PreviewBox.prototype.update = function(sectionTitle, wikitext){
if(sectionTitle === this.sectionTitle && wikitext === this.wikitext){
return;
}
this.sectionTitle = sectionTitle;
this.wikitext = wikitext;
this.$element.removeClass('bm-no-preview');
if(sectionTitle == '' || wikitext == ''){
this.$element.html('<i class="bm-preview-empty">' + this.emptyMessage + '</i>');
return;
}
var apiParams = {
action: 'parse',
prop: 'text',
title: this.targetPage,
text: wikitext,
pst: 1,
section: 'new',
sectiontitle: sectionTitle,
disablelimitreport: 1,
disableeditsection: 1,
preview: 1,
sectionpreview: 1
}
var pb = this;
this.api.get(apiParams).done(function(data){
pb.$element.html(data.parse.text);
});
};
/**
* Creates a new instance of the block messenger
* @class
* @param {string} blockedUserName The name of the blocked user
*/
function BlockMessenger(blockedUserName) {
this.blockedUserName = blockedUserName;
mw.messages.set(MSG);
this.api = new mw.Api({
parameters: {
format: 'json',
formatversion: 2,
errorformat: 'html',
errorsuselocal: true
}
});
//! Initialize the UI below
/** The root DOM element of the BlockMessenger */
this.$element = $('<div>')
.addClass('noprint bm-wrapper');
// Start the section with block messenger
var $intro = $('<p>')
.appendTo(this.$element)
.html(mw.message('bm-section-intro', this.blockedUserName).parse());
var $wrapperFormAndPreset = $('<div>')
.addClass('bm-form-wrapper')
.appendTo(this.$element);
var $form = $('<div>')
.addClass('bm-form')
.appendTo($wrapperFormAndPreset);
// Modify the links in the intro: lead to a new tab
var newTabSuffix = mw.message('bm-newtab-suffix').parse();
$intro.find('a')
.attr('target', '_blank')
.each(function(_, a) { a.title += newTabSuffix; });
this.makeRedLinks($intro);
var today = new Date();
var todayText = today.toLocaleDateString(mw.config.get('wgContentLanguage'));
var defaultSubject = mw.message('bm-subject-default', todayText).parse();
var defaultMessage = '';
// Add the message subject input
var subjectInput = new OO.ui.TextInputWidget({
name: 'bm-subject',
value: defaultSubject
});
var subjectField = new OO.ui.FieldLayout(subjectInput, {
align: 'top',
label: mw.message('bm-subject-label').parse()
});
// Add the message input
var messageInput = new OO.ui.MultilineTextInputWidget({
rows: 5,
autosize: true,
maxRows: 15,
value: defaultMessage
});
var messageField = new OO.ui.FieldLayout(messageInput, {
align: 'top',
label: mw.message('bm-message-label').parse(),
help: mw.message('bm-message-help').parse(),
helpInline: true
});
var fieldset = new OO.ui.FieldsetLayout({
label: mw.message('bm-form-title', this.blockedUserName).parse()
});
fieldset.addItems([
subjectField,
messageField
]);
$form.append(fieldset.$element);
// Create the preview box
var previewBox = new PreviewBox(
'User talk:' + this.blockedUserName,
mw.message('bm-preview-label').parse(),
mw.message('bm-preview-empty').parse()
);
$form.append(previewBox.$element);
// The "Send" button
var sendButton = new OO.ui.ButtonWidget({
label: mw.message('bm-send-button').parse(),
flags:
});
var sendField = new OO.ui.FieldLayout(sendButton);
$form.append(sendField.$element);
// Status field, to display errors, if any
var $statusBox = $('<div>')
.addClass('bm-status')
.appendTo($form);
// Add the preset messages panel
var presetMessages = new PresetMessages(
mw.message('bm-preset-header').parse(),
blockMessages
);
$wrapperFormAndPreset.append(presetMessages.$element);
presetMessages.addOnSelect(function(text){
if(messageInput.isDisabled()) return;
messageInput.setValue(text);
});
// Auto-preview
var bm = this;
var displayPreview = OO.ui.throttle(function () {
var wikitext = bm.transformWikitext(messageInput.getValue());
previewBox.update(subjectInput.getValue(), wikitext);
}, 750);
subjectInput.on('change', displayPreview);
messageInput.on('change', displayPreview);
// Prevent the user from closing the window
this.closeLock = mw.confirmCloseWindow({
test: function(){
// Prevent only when the user has typed something
if(subjectInput.getValue() != defaultSubject) return true;
if(messageInput.getValue() != defaultMessage) return true;
// Else, don't prevent
return false;
}
});
// Send the message
sendButton.on('click', function(){
var wikitext = bm.transformWikitext(messageInput.getValue());
var sectionTitle = subjectInput.getValue();
var errors = ;
if(sectionTitle == ''){
errors.push(mw.message('bm-error-no-subject').parse());
}
if(wikitext == ''){
errors.push(mw.message('bm-error-no-message').parse());
}
if(errors.length > 0){
$statusBox.html(errors.join('<br>'));
return;
}
subjectInput.setDisabled(true);
messageInput.setDisabled(true);
sendButton.setDisabled(true);
messageInput.pushPending();
$statusBox.html('');
bm.sendMessage('User talk:' + bm.blockedUserName, sectionTitle, wikitext)
.done(function(){
$statusBox.addClass('bm-success');
$statusBox.html(mw.message('bm-success').parse());
messageInput.popPending();
bm.releaseCloseLock();
// Hide unnecessary elements
$intro.remove();
fieldset.$element.remove();
presetMessages.$element.remove();
sendField.$element.remove();
previewBox.update(sectionTitle, wikitext);
previewBox.setLabel(mw.message('bm-preview-label-after').parse());
}).fail(function(reason){
$statusBox.html(reason);
subjectInput.setDisabled(false);
messageInput.setDisabled(false);
sendButton.setDisabled(false);
messageInput.popPending();
});
});
}
/**
* Transforms the wikitext, adding a missing signature if needed
* @param {string} wikitext The wikitext typed by the user
* @returns {string}
*/
BlockMessenger.prototype.transformWikitext = function(wikitext){
wikitext = wikitext.trim();
// Don't process an empty message
if(wikitext == '') return wikitext;
// Add a signature if needed
if(!/~{3,5}/.test(wikitext)){
wikitext += ' ~~~~';
}
return wikitext;
};
/**
* Releases the close lock, allowing the user to close the window
*/
BlockMessenger.prototype.releaseCloseLock = function(){
this.closeLock.release();
};
/**
* Sends a message to the user
* @param {string} page The page to send the message to
* @param {string} subject The message subject
* @param {string} content The message content
* @returns {JQuery.Promise}
*/
BlockMessenger.prototype.sendMessage = function(page, subject, content){
return this.api.newSection(page, subject, content, { redirect: true, watchlist: 'nochange' });
};
/**
* Scans the links in a container and makes them red if they point to
* a page that doesn't exist.
* @param {JQuery<HTMLElement>} $container Where to look for links
*/
BlockMessenger.prototype.makeRedLinks = function($container){
var wikiPages = ;
var anchors = ;
/**
* @param {string} href
* @returns {string|null}
*/
var extractTargetPage = function(href){
// Ignore external links
if(href.indexOf('https://wiki386.com/pl/') === -1) return null;
var pageMatch = /\/wiki\/(+)/.exec(href);
if(pageMatch === null) return null;
return pageMatch;
};
$container.find('a').each(function(_, a){
var href = a.getAttribute('href');
if(href === null) return;
var targetPage = extractTargetPage(href);
if(targetPage === null) return;
wikiPages.push(targetPage);
anchors.push(a);
});
if(wikiPages.length === 0) return;
this.api.get({
action: 'query',
prop: 'info',
titles: wikiPages.join('|'),
redirects: true
}).then(function(data){
var result = data.query;
var normalizer = {};
var pageExists = {};
if(result.normalized !== undefined){
for(var i = 0; i < result.normalized.length; i++){
normalizer.from] = result.normalized.to;
}
}
if(result.pages !== undefined){
for(var i = 0; i < result.pages.length; i++){
var page = result.pages;
pageExists = (page.missing !== true);
}
}
for(var i = 0; i < anchors.length; i++){
var anchor = anchors;
var pageTitle = extractTargetPage(anchor.getAttribute('href'));
if(pageTitle === null) continue;
if(normalizer !== undefined){
pageTitle = normalizer;
}
if(pageExists === false){
anchor.classList.add('new');
}
}
});
// Do nothing on failure, this is not critical
}
/**
* The gadget entry point. Checks whether we can run here
* and then invokes the actual gadget code.
*/
function main(){
// Ensure we are on the right page
var specialPage = mw.config.get('wgCanonicalSpecialPageName');
if(specialPage !== 'Block') return;
// Ensure the user has already been blocked
// (i.e. we see the confirmation page)
var userNameInput = document.querySelector('input');
if(userNameInput !== null) return;
// Get the blocked user name. Will be null if blocking an IP range
var blockedUserName = mw.config.get('wgRelevantUserName');
if(blockedUserName === null) return;
// Gadget is used so rarely that we shouldn't load dependencies at every page load
mw.loader.using([
'mediawiki.api',
'mediawiki.confirmCloseWindow',
'mediawiki.jqueryMsg',
'mediawiki.util',
'oojs-ui-core'
]).then(function(){
var messenger = new BlockMessenger(blockedUserName);
$('#mw-content-text').append(messenger.$element);
});
}
main();
});
/**
* @typedef {{title: string, text: string}} PresetMessage
*/
// </nowiki>