User:Chaotic Enby/Unblock wizard.js

/**

* User:Chaotic Enby/Unblock wizard.js

*

* JavaScript used for submitting unblock requests.

* Used on User:Chaotic Enby/Unblock wizard.

* Loaded via mw:Snippets/Load JS and CSS by URL.

*

* Edits can be proposed via pinging me somewhere.

*

* Author: User:Chaotic Enby (derived from a script by User:SD0001)

* Licence: MIT

*/

/* jshint maxerr: 999 */

/* globals mw, $, OO */

/* */

(function () {

$.when(

$.ready,

mw.loader.using([

'mediawiki.util', 'mediawiki.api', 'mediawiki.Title',

'mediawiki.widgets', 'oojs-ui-core', 'oojs-ui-widgets'

])

).then(function () {

if (!(mw.config.get('wgPageName').includes('User:Chaotic_Enby/Unblock_wizard/')) ||

mw.config.get('wgAction') !== 'view') {

return;

}

init();

});

var afc = {}, ui = {}, block = {};

window.afc = afc;

afc.ui = ui;

var config = {

debounceDelay: 500,

redirectionDelay: 1000,

defaultAfcTopic: 'other'

};

// TODO: move to a separate JSON subpage, would be feasible once phab:T198758 is resolved

var messages = {

"document-title": "Wikipedia Unblock Wizard",

"page-title": "Wikipedia Unblock Wizard",

"explain-label": "Can you explain, in your own words, what you were blocked for?",

"future-label": "If unblocked, what edits would you make, and what (if applicable) would you do differently?",

"other-label": "Is there anything else that may be helpful to your unblock request?",

"accounts-label": "Please list all accounts you have used besides this one.",

"so-label": "Have you taken the standard offer?",

"explain-promo-label": "Can you explain, in your own words, why your edits were not promotional?",

"coi-label": "What is your relationship with the subjects you have been editing about?",

"future-promo-label": "If you are unblocked, what topic areas will you edit in?",

"username-label": "If you were blocked for having a promotional username, what new username do you want to pick?",

"clarification-label": "Is there anything specific you want to ask about your block?",

"submit-label": "Publish",

"footer-text": "If you are not sure about what to enter in a field, you can skip it. If you need help, you can ask on your talkpage with {{Help me}} or get live help via IRC or Discord.
Facing some issues in using this form? [/w/index.php?title=User_talk:Chaotic_Enby&action=edit§ion=new&preloadtitle=Issue%20with%20submission%20form&editintro=User_talk:Chaotic_Enby/editintro Report it].
",

"submitting-as": "Submitting as User:$1",

"validation-notitle": "User not found",

"validation-invalidtitle": "User page does not exist.",

"validation-missingtitle": "User page does not exist.",

"status-processing": "Processing ...",

"status-saving": "Saving talk page ...",

"editsummary-main": "Submitting using User:Chaotic Enby/Unblock wizard",

"status-redirecting": "Submission succeeded. Redirecting you to your talk page ...",

"status-redirecting-utrs": "Submission succeeded. Redirecting you to UTRS ...",

"captcha-label": "Please enter the letters appearing in the box below",

"captcha-placeholder": "Enter the letters here",

"captcha-helptip": "CAPTCHA security check. Click \"Publish\" again when done.",

"error-saving-main": "An error occurred ($1). Please try again or ask for help on your talk page.",

"error-main": "An error occurred ($1). Please try again or ask for help on your talk page.",

"copyright-notice": "By publishing changes, you agree to the Terms of Use, and you irrevocably agree to release your contribution under the CC BY-SA 4.0 License and the GFDL. You agree that a hyperlink or URL is sufficient attribution under the Creative Commons license."

};

var questionLabels = [];

var questionFields = {'explain': 0, 'future': 0, 'other': 0, 'accounts': 0, 'so': 2, 'explain-promo': 0, 'coi': 0, 'future-promo': 0, 'username': 1, 'clarification': 0};

var blockType = '';

function init() {

for (var key in messages) {

mw.messages.set('afcsw-' + key, messages[key]);

}

var apiOptions = {

parameters: {

format: 'json',

formatversion: '2'

},

ajax: {

headers: {

'Api-User-Agent': 'w:en:User:Chaotic Enby/Unblock wizard.js'

}

}

};

// Two different API objects so that aborts on the lookupApi don't stop the final

// evaluate process

afc.api = new mw.Api(apiOptions);

afc.lookupApi = new mw.Api(apiOptions);

afc.lookupApi.get({

"action": "query",

"meta": "userinfo",

"uiprop": "blockinfo"

}).then( setBlockData ).then( function ( block ) {

blockType = mw.config.get('wgPageName').slice(33);

debug(blockType);

switch (blockType) {

case "Sockpuppet":

questionLabels = ['accounts', 'so', 'other'];

break;

case "Promo":

if(true) { // to replace by an api call to check if the block is username-related

questionLabels = ['explain-promo', 'coi', 'future-promo', 'username', 'other'];

} else {

questionLabels = ['explain-promo', 'coi', 'future-promo', 'other'];

}

break;

case "Autoblock":

questionLabels = [];

break;

case "IP_hardblock":

questionLabels = [];

break;

case "Other":

questionLabels = ['explain', 'future', 'other'];

break;

case "Clarification":

questionLabels = ['clarification'];

break;

default:

questionLabels = [];

}

document.title = msg('document-title');

$('#firstHeading').text(msg('page-title'));

mw.util.addCSS(

// CSS adjustments for vector-2022: hide prominent page controls which are

// irrelevant and confusing while using the wizard

'.vector-page-toolbar { display: none } ' +

'.vector-page-titlebar #p-lang-btn { display: none } ' +

// Hide categories as well, prevents accidental HotCat usage

'#catlinks { display: none } '

);

constructUI();

});

}

function setBlockData(json) {

debug('block fetch query', json);

var userinfo = json.query.userinfo;

var errors = errorsFromPageData(userinfo);

if (errors.length) {

return block;

}

if("blockid" in userinfo){

debug('user block id: ' + userinfo.blockid + '');

block.id = userinfo.blockid;

block.by = userinfo.blockedby;

block.reason = userinfo.blockreason;

} else {

debug('user block id: none');

}

return block;

}

function constructUI() {

ui.itemsLayout = [];

ui.itemsInput = [];

var copyrightEligible = false;

for(var label of questionLabels){

switch(questionFields[label]) {

case 0:

ui.itemsInput.push(new OO.ui.MultilineTextInputWidget({

// placeholder: msg(label + '-placeholder'),

multiline: true,

autosize: true,

}));

copyrightEligible = true;

break;

case 1:

ui.itemsInput.push(new OO.ui.TextInputWidget({

// placeholder: msg(label + '-placeholder'),

maxLength: 85,

}));

copyrightEligible = true;

break;

case 2:

ui.itemsInput.push(new OO.ui.RadioSelectInputWidget({

align: 'inline',

}));

ui.itemsInput[ui.itemsInput.length - 1].setOptions([{label:"Yes", data:"Yes."}, {label:"No", data:"No."}]);

break;

default:

debug("Field type not found");

}

ui.itemsLayout.push(new OO.ui.FieldLayout(ui.itemsInput[ui.itemsInput.length - 1], {

label: msg(label + '-label'),

align: 'top',

// help: msg(label + '-helptip'),

helpInline: true

}));

}

ui.itemsLayout.push(ui.submitLayout = new OO.ui.FieldLayout(ui.submitButton = new OO.ui.ButtonWidget({

label: msg('submit-label'),

flags: [ 'progressive', 'primary' ],

})));

if(copyrightEligible){

ui.itemsLayout.push(new OO.ui.FieldLayout(new OO.ui.LabelWidget({

label: $('

')

.append(linkify(msg('copyright-notice')))

}), {

align: 'top'

}));

}

ui.fieldset = new OO.ui.FieldsetLayout({

classes: [ 'container' ],

items: ui.itemsLayout

});

ui.footerLayout = new OO.ui.FieldLayout(new OO.ui.LabelWidget({

label: $('

')

.append(linkify(msg('footer-text')))

}), {

align: 'top'

});

var asUser = mw.util.getParamValue('username');

if (asUser && asUser !== mw.config.get('wgUserName')) {

ui.fieldset.addItems([

new OO.ui.FieldLayout(new OO.ui.MessageWidget({

type: 'notice',

inline: true,

label: msg('submitting-as', asUser)

}))

], /* position */ 5); // just before submit button

}

// Attach

$('#unblock-wizard-container').empty().append(ui.fieldset.$element, ui.footerLayout.$element);

mw.track('counter.gadget_afcsw.opened');

ui.submitButton.on('click', handleSubmit);

initLookup();

// The default font size in monobook and modern are too small at 10px

mw.util.addCSS('.skin-modern .projectTagOverlay, .skin-monobook .projectTagOverlay { font-size: 130%; }');

afc.beforeUnload = function (e) {

var changedContent = false;

for (var [i, label] of questionLabels.entries()) {

if (ui.itemsInput[i].getValue() != "" && questionFields[label] != 2) {

changedContent = true;

}

if (ui.itemsInput[i].getValue() != "Yes." && questionFields[label] == 2) {

changedContent = true;

}

}

if(changedContent){

e.preventDefault();

}

e.returnValue = '';

return '';

};

$(window).on('beforeunload', afc.beforeUnload);

}

function initLookup() {

afc.lookupApi.abort(); // abort older API requests

var userTalk = "User talk:" + mw.config.get('wgUserName');

if (!mw.config.get('wgUserName')) { // empty

return; // here we should get the ip or something

}

debug('user talk page: "' + userTalk + '"');

// re-initialize

afc.oresTopics = null;

afc.talktext = null;

afc.pagetext = null;

afc.lookupApi.get({

"action": "query",

"prop": "revisions|description|info",

"titles": userTalk,

"rvprop": "content",

"rvslots": "main"

}).then(setPrefillsFromPageData);

}

function setPrefillsFromPageData(json) {

debug('page fetch query', json);

var page = json.query.pages[0];

var preNormalizedTitle = json.query.normalized && json.query.normalized[0] &&

json.query.normalized[0].from;

debug('page.title: "' + page.title + '"');

var errors = errorsFromPageData(page);

if (errors.length) {

return;

}

afc.pagetext = page.revisions[0].slots.main.content;

}

/**

* @param {Object} page - from query API response

* @returns {string[]}

*/

function errorsFromPageData(page) {

if (!page || page.invalid) {

return [msg('validation-invalidtitle')];

}

if (page.missing) {

return [msg('validation-missingtitle')];

}

return [];

}

/**

* @param {Object} page - from query API response

* @returns {string[]}

*/

function warningsFromPageData(page) {

var pagetext = page.revisions[0].slots.main.content;

var warnings = [];

// Show no refs warning

if (!/

warnings.push('warning-norefs');

}

// TODO: Show warning for use of deprecated/unreliable sources

// TODO: Show tip for avoiding peacock words or promotional language?

return warnings.map(function (warning) {

return new OO.ui.HtmlSnippet(linkify(msg(warning)));

});

}

/**

* @param {string} type

* @param {string} message

*/

function setMainStatus(type, message) {

if (!ui.mainStatusLayout || !ui.mainStatusLayout.isElementAttached()) {

ui.fieldset.addItems([

ui.mainStatusLayout = new OO.ui.FieldLayout(ui.mainStatusArea = new OO.ui.MessageWidget())

]);

}

ui.mainStatusArea.setType(type);

ui.mainStatusArea.setLabel(message);

}

/**

* @param {string} type

* @param {string} message

*/

function setTalkStatus(type, message) {

if (!ui.talkStatusLayout) {

ui.fieldset.addItems([

ui.talkStatusLayout = new OO.ui.FieldLayout(ui.talkStatusArea = new OO.ui.MessageWidget())

]);

}

ui.talkStatusArea.setType(type);

ui.talkStatusArea.setLabel(message);

}

function handleSubmit() {

setMainStatus('notice', msg('status-processing'));

mw.track('counter.gadget_afcsw.submit_attempted');

ui.submitButton.setDisabled(true);

ui.mainStatusLayout.scrollElementIntoView();

if (blockType == "IP_hardblock") {

setMainStatus('success', msg('status-redirecting-utrs'));

mw.track('counter.gadget_afcsw.submit_succeeded');

$(window).off('beforeunload', afc.beforeUnload);

setTimeout(function () {

location.href = "https://utrs-beta.wmflabs.org/public/appeal/account";

}, config.redirectionDelay);

} else {

var userTalk = "User talk:" + mw.config.get('wgUserName');

if (!mw.config.get('wgUserName')) { // empty

ui.fieldset.removeItems([ui.mainStatusLayout]);

ui.submitButton.setDisabled(false);

return; // really get the ip please

}

debug('debug user talk page: "' + userTalk + '"');

afc.api.get({

"action": "query",

"prop": "revisions|description",

"titles": userTalk,

"rvprop": "content",

"rvslots": "main",

}).then(function (json) {

var apiPage = json.query.pages[0];

var errors = errorsFromPageData(apiPage);

if (errors.length) {

ui.fieldset.removeItems([ui.mainStatusLayout]);

ui.submitButton.setDisabled(false);

debug(errors);

return;

}

debug("no errors");

var text = prepareUserTalkText(apiPage);

debug("text[0] = " + text[0]);

setMainStatus('notice', msg('status-saving'));

saveUserTalkPage(userTalk, text).then(function () {

setMainStatus('success', msg('status-redirecting'));

mw.track('counter.gadget_afcsw.submit_succeeded');

$(window).off('beforeunload', afc.beforeUnload);

setTimeout(function () {

location.href = mw.util.getUrl(userTalk);

}, config.redirectionDelay);

}, function (code, err) {

if (code === 'captcha') {

ui.fieldset.removeItems([ui.mainStatusLayout, ui.talkStatusLayout]);

ui.captchaLayout.scrollElementIntoView();

mw.track('counter.gadget_afcsw.submit_captcha');

} else {

setMainStatus('error', msg('error-saving-main', makeErrorMessage(code, err)));

mw.track('counter.gadget_afcsw.submit_failed');

mw.track('counter.gadget_afcsw.submit_failed_' + code);

}

ui.submitButton.setDisabled(false);

});

}).catch(function (code, err) {

setMainStatus('error', msg('error-main', makeErrorMessage(code, err)));

ui.submitButton.setDisabled(false);

mw.track('counter.gadget_afcsw.submit_failed');

mw.track('counter.gadget_afcsw.submit_failed_' + code);

});

}

}

function saveUserTalkPage(title, text) {

// TODO: handle edit conflict

var editParams = {

"action": "edit",

"title": title,

"text": text,

"summary": msg('editsummary-main')

};

if (ui.captchaLayout && ui.captchaLayout.isElementAttached()) {

editParams.captchaid = afc.captchaid;

editParams.captchaword = ui.captchaInput.getValue();

ui.fieldset.removeItems([ui.captchaLayout]);

}

return afc.api.postWithEditToken(editParams).then(function (data) {

if (!data.edit || data.edit.result !== 'Success') {

if (data.edit && data.edit.captcha) {

// Handle captcha for non-confirmed users

var url = data.edit.captcha.url;

afc.captchaid = data.edit.captcha.id; // abuse of global?

ui.fieldset.addItems([

ui.captchaLayout = new OO.ui.FieldLayout(ui.captchaInput = new OO.ui.TextInputWidget({

placeholder: msg('captcha-placeholder'),

required: true

}), {

warnings: [ new OO.ui.HtmlSnippet('') ],

label: msg('captcha-label'),

align: 'top',

help: msg('captcha-helptip'),

helpInline: true,

}),

], /* position */ 6); // just after submit button // TODO: fix number

// TODO: submit when enter key is pressed in captcha field

return $.Deferred().reject('captcha');

} else {

return $.Deferred().reject('unexpected-result');

}

}

});

}

/**

* @param {Object} page - page information from the API

* @returns {string} final talk page text to save

*/

function prepareUserTalkText(page) {

var text = page.revisions[0].slots.main.content;

var unblock = '';

// put unblock template

switch(blockType){

case "Autoblock":

if("id" in block){

unblock += '\n{{unblock-auto|2=\u003Cnowiki>' + block.reason + '\u003C/nowiki>|3=' + block.by + '|4=' + block.id + '}}\n';

} else {

unblock += '\n{{unblock-auto|2=REASON|3=THE BLOCKING ADMIN|4=BLOCK ID}}\n';

}

break;

case "Clarification":

if(ui.itemsInput[0].getValue()){

unblock += '\n{{Help me}}\n' + ui.itemsInput[0].getValue() + '\n~~' + '~~';

} else {

unblock += '\n{{Help me}}\n' + 'I would like a more detailed explanation for my block.' + '\n~~' + '~~';

}

break;

default:

unblock += '\n{{unblock|reason=';

for(var [i, label] of questionLabels.entries()){

unblock += "" + msg(label + '-label') + "" + "{{pb}}" + ui.itemsInput[i].getValue() + "{{pb}}";

}

unblock += '}}~~' + '~~\n';

}

// insert it at the bottom

text = text + unblock;

debug(text);

return text;

}

/**

* Load a JSON page from the wiki.

* Use API (instead of $.getJSON with action=raw) to take advantage of caching

* @param {string} page

* @returns {jQuery.Promise>}

**/

function getJSONPage (page) {

return afc.api.get({

action: 'query',

titles: page,

prop: 'revisions',

rvprop: 'content',

rvlimit: 1,

rvslots: 'main',

uselang: 'content',

maxage: '3600', // 1 hour

smaxage: '3600',

formatversion: 2

}).then(function (json) {

var content = json.query.pages[0].revisions[0].slots.main.content;

return JSON.parse(content);

}).catch(function (code, err) {

console.error(makeErrorMessage(code, err));

});

}

/**

* Expands wikilinks and external links into HTML.

* Used instead of mw.msg(...).parse() because we want links to open in a new tab,

* and we don't want tags to be mangled.

* @param {string} input

* @returns {string}

*/

function linkify(input) {

return input

.replace(

/\[\[:?(?:([^|\]]+?)\|)?([^\]|]+?)\]\]/g,

function(_, target, text) {

if (!target) {

target = text;

}

return '' + text + '';

}

)

// for ext links, display text should be given

.replace(

/\[(\S*?) (.*?)\]/g,

function (_, target, text) {

return '' + text + '';

}

);

}

function msg(key) {

var messageArgs = Array.prototype.slice.call(arguments, 1);

return mw.msg.apply(mw, ['afcsw-' + key].concat(messageArgs));

}

function makeErrorMessage(code, err) {

if (code === 'http') {

return 'http: there is no internet connectivity';

}

return code + (err && err.error && err.error.info ? ': ' + err.error.info : '');

}

function debug() {

Array.prototype.slice.call(arguments).forEach(function (arg) {

console.log(arg);

});

}

})(); // File-level closure to protect functions from being exposed to the global scope or overwritten

/* */