Bureaucrats, editor, Interface administrators, Administrators (Semantic MediaWiki), Curators (Semantic MediaWiki), Editors (Semantic MediaWiki), Administrators
47,327
edits
No edit summary Tag: Manual revert |
No edit summary |
||
Line 42:
'use strict';
var calcstorage = 'rsw-calcsdata',
/**
* Caching for search suggestions
*
* @todo implement caching for mw.TitleInputWidget accroding to https://doc.wikimedia.org/mediawiki-core/master/js/#!/api/mw.widgets.TitleWidget-cfg-cache
*/
cache = {},
/**
* Internal variable to store references to each calculator on the page.
*/
calcStore = {},
/**
* Private helper methods for `Calc`
*
* Most methods here are called with `Function.prototype.call`
* and are passed an instance of `Calc` to access it's prototype
*/
helper = {
/**
* Add/change functionality of mw/OO.ui classes
* Added support for multiple namespaces to mw.widgets.TitleInputWidget
*/
initClasses: function initClasses() {
var hasOwn = Object.prototype.hasOwnProperty;
/**
* Get option widgets from the server response
* Changed to add support for multiple namespaces
*
* @param {Object} data Query result
* @return {OO.ui.OptionWidget[]} Menu items
*/
mw.widgets.TitleInputWidget.prototype.getOptionsFromData = function (data) {
var i,
len,
index,
pageExists,
pageExistsExact,
suggestionPage,
page,
redirect,
redirects,
currentPageName = new mw.Title(mw.config.get('wgRelevantPageName')).getPrefixedText(),
items = [],
titles = [],
titleObj = mw.Title.newFromText(this.getQueryValue()),
redirectsTo = {},
pageData = {},
namespaces = this.namespace.split('|').map(function (val) {
return parseInt(val, 10);
});
if (data.redirects) {
for (i = 0, len = data.redirects.length; i < len; i++) {
redirect = data.redirects[i];
redirectsTo[redirect.to] = redirectsTo[redirect.to] || [];
redirectsTo[redirect.to].push(redirect.from);
}
}
for (index in data.pages) {
suggestionPage = data.pages[index];
// When excludeCurrentPage is set, don't list the current page unless the user has type the full title
if (this.excludeCurrentPage && suggestionPage.title === currentPageName && suggestionPage.title !== titleObj.getPrefixedText()) {
// When excludeDynamicNamespaces is set, ignore all pages with negative namespace
if (this.excludeDynamicNamespaces && suggestionPage.ns < 0) {
}
pageData[suggestionPage.title] = {
known: suggestionPage.known !== undefined,
missing: suggestionPage.missing !== undefined,
redirect: suggestionPage.redirect !== undefined,
disambiguation: OO.getProp(suggestionPage, 'pageprops', 'disambiguation') !== undefined,
imageUrl: OO.getProp(suggestionPage, 'thumbnail', 'source'),
description: suggestionPage.description,
};
// Throw away pages from wrong namespaces. This can happen when 'showRedirectTargets' is true
// and we encounter a cross-namespace redirect.
if (this.namespace === null || namespaces.indexOf(suggestionPage.ns) >= 0) {
titles.push(suggestionPage.title);
}
redirects = hasOwn.call(redirectsTo, suggestionPage.title) ? redirectsTo[suggestionPage.title] : [];
for (i = 0, len =
pageData[redirects[i]] = {
missing:
known:
redirect: true,
disambiguation: false,
description:
// Sort index, just below its
index: suggestionPage.index +
originalData: suggestionPage
};
}
}
titles.sort(function (a, b) {
return pageData[a].index - pageData[b].index;
});
// If not found, run value through mw.Title to avoid treating a match as a
// mismatch where normalisation would make them matching (T50476)
pageExistsExact = hasOwn.call(pageData, this.getQueryValue()) && (!pageData[this.getQueryValue()].missing || pageData[this.getQueryValue()].known);
pageExists = pageExistsExact || titleObj && hasOwn.call(pageData, titleObj.getPrefixedText()) && (!pageData[titleObj.getPrefixedText()].missing || pageData[titleObj.getPrefixedText()].known);
if
// Offer the exact text as a suggestion
for (i = 0, len = titles.length; i < len; i++) {
page = hasOwn.call(pageData, titles[i]) ? pageData[titles[i]] : {};
items.push(this.createOptionWidget(this.getOptionWidgetData(titles[i], page)));
}
return items;
};
},
* Parse the calculator configuration
*
* @param lines {Array} An array containing the calculator's configuration
* @returns {Object} An object representing the calculator's configuration
*/
parseConfig: function parseConfig(lines) {
var defConfig = {
suggestns:
autosubmit:
name:
},
config = {
/
},
// used for debugging incorrect config names
validParams = ['form', 'param', 'result', 'suggestns', 'template', 'module', 'modulefunc', 'name', 'autosubmit'],
// used for debugging incorrect param types
validParamTypes = ['string', 'article', 'number', 'int', 'select', 'buttonselect', 'combobox', 'check', 'toggleswitch', 'togglebutton', 'hs', 'rsn', 'fixed', 'hidden', 'semihidden', 'group'],
configError = false;
// parse
// @example param=arg1|arg1|arg3|arg4
lines.forEach(function (line)
var temp = line.split('='),
/
if (temp.length < 2) {
}
// an equals is
// @example HTML label with attributes
// so join them back together to preserve
// this also allows support of HTML attributes in labels
if
temp[1] = temp.slice(1, temp.length).join('=');
}
param = temp[0].trim().toLowerCase();
args =
if (validParams.indexOf(param) === -1) {
// use console for easier
console.log('Unknown parameter: '
configError = true;
if (param ===
config.suggestns =
return;
}
if (param !== 'param')
config[param] =
}
// split
args = args.split(/\s*\|\s*/);
// store template params in an array to make life easier
config.tParams = config.tParams || [];
if (validParamTypes.indexOf(args[3]) === -1 && args[3] !== '' && args[3] !== undefined) {
// use console for easier debugging
configError = true;
}
if
var
if (tmphelp.length > 1)
if (tmphelp[0] === 'inline')
tmphelp[1] = tmphelp.slice(1, tmphelp.length).join('=');
help = helper.sanitiseLabels(tmphelp[1] || '');
} else {
tmphelp[0] = tmphelp.join('=');
help = helper.sanitiseLabels(tmphelp[0] || '');
}
} else {
help = helper.sanitiseLabels(tmphelp[0] || '');
}
}
config.tParams.push({
name: mw.html.escape(args[0]),
label: helper.sanitiseLabels(args[1] || args[0]),
def: mw.html.escape(args[2] || ''),
type: mw.html.escape(args[3] || ''),
range: args[4] || '',
rawtogs: args[5] || '',
inlhelp: inlinehelp,
help: help
});
});
if (configError) {
config.configError = 'This calculator\'s config contains errors. Please report it ' + '<a href="/w/RuneScape:User_help" title="RuneScape:User help">here</a> ' + 'or check the javascript console for details.';
}
config = $.extend(defConfig, config);
mw.log(config);
return config;
},
/**
* Generate a unique id for each input
*
* @param inputId {String} A string representing the id of an input
* @returns {String} A string representing the namespaced/prefixed id of an input
*/
getId: function getId(inputId) {
return [this.form, this.result, inputId].join('-');
},
/**
* Output an error to the UI
*
* @param error {String} A string representing the error message to be output
*/
showError: function showError(error) {
$('#' + this.result).empty().append($('<strong>').addClass('error').text(error));
},
/**
* Toggle the visibility and enabled status of fields/groups
*
* @param item {String} A string representing the current value of the widget
* @param toggles {object} An object representing arrays of items to be toggled keyed by widget values
*/
toggle: function toggle(item, toggles) {
var self = this;
var togitem = function togitem(widget, show) {
var param = self.tParams[self.indexkeys[widget]];
if (param.type === 'group') {
param.ooui.toggle(show);
param.ooui.getItems().forEach(function (child) {
if (!!child.setDisabled) {
child.setDisabled(!show);
} else if (!!child.getField().setDisabled) {
child.getField().setDisabled(!show);
}
});
} else if (param.type === 'semihidden') {
if (!!param.ooui.setDisabled) {
param.ooui.setDisabled(!show);
}
} else {
param.layout.toggle(show);
if (!!param.ooui.setDisabled) {
param.ooui.setDisabled(!show);
}
}
};
if (toggles[item]) {
toggles[item].on.forEach(function (widget) {
togitem(widget, true);
});
toggles[item].off.forEach(function (widget) {
togitem(widget, false);
});
} else if (toggles.not0 && !isNaN(parseFloat(item)) && parseFloat(item) !== 0) {
toggles.not0.on.forEach(function (widget) {
togitem(widget, true);
});
toggles.not0.off.forEach(function (widget) {
togitem(widget, false);
});
} else if (toggles.alltogs) {
toggles.alltogs.off.forEach(function (widget) {
togitem(widget, false);
});
}
},
/**
* Generate range and step for number and int inputs
*
* @param rawdata {string} The string representation of the range and steps
* @param type {string} The name of the field type (int or number)
* @returns {array} An array containing the min value, max value, step and button step.
*/
genRange: function genRange(rawdata, type) {
var tmp = rawdata.split(/\s*,\s*/),
rng = tmp[0].split(/\s*-\s*/),
step = tmp[1] || '',
bstep = tmp[2] || '',
min,
max,
parseFunc;
if (type === 'int') {
parseFunc = function parseFunc(x) {
return parseInt(x, 10);
};
} else {
parseFunc = parseFloat;
}
if (type === 'int') {
step = 1;
if (isNaN(parseInt(bstep, 10))) {
bstep = 1;
} else {
bstep = parseInt(bstep, 10);
}
} else {
if (isNaN(parseFloat(step))) {
step = 0.01;
} else {
step = parseFloat(step);
}
if (isNaN(parseFloat(bstep))) {
bstep = 1;
} else {
bstep = parseFloat(bstep);
}
}
// Accept negative values for either range position
if (rng.length === 3)
// 1 value is negative
if (rng[0] === '')
// First value
if (isNaN(parseFunc(rng[1]))) {
min =
} else {
min = 0 - parseFunc(rng[1]);
}
if (isNaN(parseFunc(rng[2]))) {
max = Infinity;
} else {
max = parseFunc(rng[2]);
}
} else if (rng[1] === '') {
// Second value negative
if (isNaN(parseFunc(rng[0]))) {
min = -Infinity;
} else {
min = parseFunc(rng[0]);
}
if (isNaN(parseFunc(rng[2]))) {
max = 0;
} else {
max = 0 - parseFunc(rng[2]);
}
}
} else if (rng.length === 4) {
// Both negative
if (isNaN(parseFunc(rng[1]))) {
min = -Infinity;
} else {
min = 0 - parseFunc(rng[1]);
}
if (isNaN(parseFunc(rng[3]))) {
max = 0;
} else {
max = 0 - parseFunc(rng[3]);
}
} else {
// No negatives
if (isNaN(parseFunc(rng[0]))) {
min = 0;
} else {
min = parseFunc(rng[0]);
}
if (isNaN(parseFunc(rng[1]))) {
max = Infinity;
} else {
max = parseFunc(rng[1]);
}
}
// Check min < max
if (max < min) {
return [max, min, step, bstep];
} else {
return [min, max, step, bstep];
}
},
/**
* Parse the toggles for an input
*
* @param rawdata {string} A string representing the toggles for the widget
* @param defkey {string} The default key for toggles
* @returns {object} An object representing the toggles in the format { ['widget value']:[ widget-to-toggle, group-to-toggle, widget-to-toggle2 ] }
*/
parseToggles: function parseToggles(rawdata, defkey) {
var tmptogs = rawdata.split(/\s*;\s*/),
allkeys = [],
allvals = [],
toggles = {};
if (tmptogs.length > 0 && tmptogs[0].length > 0) {
tmptogs.forEach(function (tog) {
var tmp = tog.split(/\s*=\s*/),
keys = tmp[0],
val = [];
if (tmp.length < 2) {
keys = [defkey];
val = tmp[0].split(/\s*,\s*/);
} else {
keys = tmp[0].split(/\s*,\s*/);
val = tmp[1].split(/\s*,\s*/);
}
if (keys.length === 1) {
var key = keys[0];
toggles[key] = {};
toggles[key].on = val;
allkeys.push(key);
} else {
keys.forEach(function (key) {
toggles[key] = {};
toggles[key].on = val;
allkeys.push(key);
});
}
allvals = allvals.concat(val);
});
allkeys = allkeys.filter(function (item, pos, arr) {
return arr.indexOf(item) === pos;
});
allkeys.forEach(function (key) {
toggles[key].off = allvals.filter(function (val) {
if (toggles[key].on.includes(val)) {
return false;
} else {
return
}
});
});
toggles.alltogs.off = allvals;
}
return toggles;
},
/**
* Form submission handler
*/
submitForm: function submitForm() {
var self = this,
code = '{{' +
formErrors =
paramVals =
if (self.modulefunc === undefined) {
self.modulefunc = 'main';
}
code = '{{#invoke:' + self.module + '|' +
} //</nowiki>
self.submitlayout.setNotices(['Validating fields, please wait.']);
self.submitlayout.fieldWidget.setDisabled(true);
// setup template for submission
self.tParams.forEach(function (param) {
if (param.type === 'hidden' || param.type !== 'group' && param.ooui.isDisabled() === false) {
var
// use separate error
// or every input gets flagged as an
error =
if (param.type === 'fixed' ||
val =
}
$input = $('#' + helper.getId.call(self, param.name) + '
if (param.type ===
if (val
val = val.getData();
}
} else {
val =
}
} else if (param.type
if (param.range)
val = param.range.split(',')[val ? 0 : 1];
}
} else if (param.type === 'toggleswitch' || param.type === 'togglebutton') {
if (param.range) {
val = param.range.split(',')[val ? 0 : 1];
}
}
if (!!param.ooui.hasFlag &&
} else if (param.type === 'article' && param.ooui.validateTitle && val.length >
var
prms
action:
prop:
titles:
var prom = new Promise(function
api.get(prms).then(function (ret) {
if (ret.query.pages && Object.keys(ret.query.pages).length) {
var nspaces = param.ooui.namespace.split('|'),
allNS = false;
if (nspaces.indexOf('*') >= 0) {
allNS = true;
}
return parseInt(ns,
for (var pgID in
if
if (ret.query.pages[pgID].ns !== undefined
}
}
}
} else {
reject(param);
}
});
});
apicalls.push(prom);
}
if (error) {
param.layout.setErrors([error]);
if (param.ooui.setValidityFlag !== undefined) {
param.ooui.setValidityFlag(false);
}
// TODO: Remove jsInvalid classes?
$input.addClass('jcInvalid');
formErrors.push(param.label[0].textContent + ': ' + error);
} else {
param.layout.setErrors([]);
if (param.ooui.setValidityFlag !== undefined) {
param.ooui.setValidityFlag(true);
}
// TODO: Remove jsInvalid classes?
$input.removeClass('jcInvalid');
// Save
// Save current parameter value for later calculator usage.
}
}
code += '|' + param.name + '=' + val;
}
});
Promise.all(apicalls).then(function (vals) {
// All article fields valid
self.submitlayout.setNotices([]);
self.submitlayout.fieldWidget.setDisabled(false);
if (formErrors.length > 0) {
self.submitlayout.setErrors(formErrors);
helper.showError.call(self, 'One or more fields contains an invalid value.');
return;
}
self.submitlayout.setErrors([]);
// Save all values
if (!rs.hasLocalStorage()) {
console.warn('Browser does not support localStorage, inputs will not be saved.');
} else {
mw.log('Saving inputs to localStorage');
localStorage.setItem(self.localname, JSON.stringify(paramVals));
}
code += '}}';
console.log(code);
helper.loadTemplate.call(self, code);
}, function (errparam) {
// An article field is invalid
self.submitlayout.setNotices([]);
self.submitlayout.fieldWidget.setDisabled(false);
errparam.layout.setErrors([errparam.error]);
formErrors.push(errparam.label[0].textContent + ': ' + errparam.error);
self.submitlayout.setErrors(formErrors);
helper.showError.call(self, 'One or more fields contains an invalid value.');
return;
});
},
/**
* Parse the template used to display the result of the form
*
* @param code {string} Wikitext to send to the API for parsing
*/
loadTemplate: function loadTemplate(code) {
var self = this,
params = {
action: 'parse',
text: code,
prop: 'text|limitreportdata',
title: mw.config.get('wgPageName'),
disablelimitreport: 'true',
contentmodel: 'wikitext',
format: 'json'
},
method = 'GET';
// experimental support for using VE to parse calc templates
if (!!mw.util.getParamValue('vecalc')) {
params
// has
page: 'No
paction:
wikitext:
format:
rswcalcautosubmit:
};
}
if (code.length > 1900) {
method =
}
$('#' + self.form + ' .jcSubmit').data('oouiButton').setDisabled(true);
// @todo time how long these calls take
$.ajax({
method: method,
url:
data: params
}).done(function (response) {
if (!!mw.util.getParamValue('vecalc')) {
html = $(response.visualeditor.content).contents();
} else
html = response.parse.text['*'];
}
if (response.parse.limitreportdata) {
var logs = response.parse.limitreportdata.filter(function (e) {
return e.name === 'scribunto-limitreport-logs';
});
var log_str = ['Scribunto logs:'];
var
while (log.hasOwnProperty('' + i))
log_str.push(log['' +
});
console.log(log_str.join('\n'));
}
}
helper.dispResult.call(self, html);
}).fail(function (_, error) {
$('#' + self.form + ' .jcSubmit').data('oouiButton').setDisabled(false);
helper.showError.call(self, error);
});
},
/**
* Display the calculator result on the page
*
* @param response {String} A string representing the HTML to be added to the page
*/
dispResult: function dispResult(html) {
var self = this;
$('#' + self.form + ' .jcSubmit').data('oouiButton').setDisabled(false);
$('#bodyContent').find('#' + this.result).empty().removeClass('jcError').html(html);
// allow scripts to hook into form submission
mw.hook('rscalc.submit').fire();
// run all standard page-init things so various JS works as expected, including:
// - sortable tables
// - collapsible sections
// - collapsed headers on
mw.hook('wikipage.content').fire($('#' + this.result));
/*
mw.loader.using('jquery.tablesorter', function () {
$('table.sortable:not(.jquery-tablesorter)').tablesorter();
});
mw.loader.using('jquery.makeCollapsible', function () {
$('.mw-collapsible').makeCollapsible();
});
*/
if ($('.rsw-chartjs-config').length) {
mw.loader.load('ext.gadget.Charts-core');
}
},
/**
* Sanitise any HTML used in labels
*
* @param html {string} A HTML string to be sanitised
* @returns {jQuery.object} A jQuery object representing the sanitised HTML
*/
sanitiseLabels: function sanitiseLabels(html) {
var whitelistAttrs = [
// mainly for span/div tags
'style',
// for anchor tags
'href', 'title',
// for img tags
'src', 'alt', 'height', 'width',
// misc
'class'],
whitelistTags = ['a', 'span', 'div', 'img', 'strong', 'b', 'em', 'i', 'br'],
// parse the HTML string, removing script tags at the same time
$html = $.parseHTML(html, /* document */null, /* keepscripts */false),
// append to a div so we can navigate the node tree
$div = $('<div>').append($html);
$div.find('*').each(function () {
var $this = $(this),
tagname = $this.prop('tagName').toLowerCase(),
attrs,
array,
href;
if (whitelistTags.indexOf(tagname) === -1) {
mw.log('Disallowed tagname: ' + tagname);
$this.remove();
return;
}
attrs = $this.prop('attributes');
array = Array.prototype.slice.call(attrs);
array.forEach(function (attr) {
if (whitelistAttrs.indexOf(attr.name) === -1) {
mw.log('Disallowed attribute: ' + attr.name + ', tagname: ' + tagname);
$this.removeAttr(attr.name);
return;
}
// make sure there's nasty in
href = $this.attr('href');
if
// disable warnings about script URLs
// jshint -W107
href.indexOf('javascript:') > -1
// the mw sanitizer doesn't like these
// so lets follow
// apparently it's something
// jshint +W107
mw.log('Script URL
}
});
return $div.contents();
},
/**
* Handlers for parameter input types
*/
tParams: {
/**
* Handler for 'fixed' inputs
*
* @param param {object} An object containing the configuration of a parameter
* @returns {OOUI.object} A OOUI object containing the new FieldLayout
*/
fixed: function fixed(param) {
var layconf = {
label: new OO.ui.HtmlSnippet(param.label),
align: 'right',
classes: ['jsCalc-field', 'jsCalc-field-fixed'],
value: param.def
};
if (param.help) {
layconf.helpInline = param.inlhelp;
layconf.help = new OO.ui.HtmlSnippet(param.help);
}
param.ooui = new OO.ui.LabelWidget({
label: param.def
});
return new OO.ui.FieldLayout(param.ooui, layconf);
},
/**
* Handler for select dropdowns
*
* @param param {object} An object containing the configuration of a parameter
* @param id {String} A string representing the id to be added to the input
* @returns {OOUI.object} A OOUI object containing the new FieldLayout
*/
select: function select(param, id) {
var self = this,
conf = {
label: 'Select an option',
options: [],
name: id,
id: id,
value: param.def,
dropdown: {
$overlay: true
}
},
layconf =
align:
classes: ['jsCalc-field', 'jsCalc-field-select']
},
opts = param.range.split(/\s*,\s*/),
def = opts[0];
param.error = 'Not a valid selection';
if (param.help)
layconf.helpInline =
layconf.help = new
}
opts.forEach(function (opt, i) {
var op
data:
label:
};
if (opt === param.def) {
op.selected =
}
});
param.toggles = helper.parseToggles(param.rawtogs, def);
param.ooui = new
if (Object.keys(param.toggles).length > 0) {
param.ooui.on('change', function (value) {
helper.toggle.call(self, value, param.toggles);
return new OO.ui.FieldLayout(param.ooui, layconf);
},
* Handler for button selects
*
* @param param {object} An object containing the configuration of a parameter
* @param id {String} A string representing the id to be added to the input
* @returns {OOUI.object} A OOUI object containing the new FieldLayout
buttonselect: function buttonselect(param, id) {
var self = this,
items: [],
classes: ['jsCalc-field',
},
opts =
param.error = 'Please select a valid
if
layconf.helpInline =
layconf.help = new OO.ui.HtmlSnippet(param.help);
}
var opid = opt.replace(/[^a-zA-Z0-9]/g, '');
var $opt = helper.sanitiseLabels(opt);
var txt = $opt.text().trim();
if (txt === '') {
txt = (i + 1).toString();
}
buttons[opid] =
label: new
});
if (param.def.length > 0 && opts.indexOf(param.def) > -1) {
def = param.def;
}
def = opts[0];
}
param.toggles = helper.parseToggles(param.rawtogs, def);
param.ooui = new OO.ui.ButtonSelectWidget(conf);
param.ooui.selectItemByData(def);
if (Object.keys(param.toggles).length > 0) {
param.ooui.on('choose', function (button) {
var item = button.getData();
helper.toggle.call(self, item, param.toggles);
});
}
return new OO.ui.FieldLayout(param.ooui, layconf);
},
/**
* Handler for comboboxes
*
* @param param {object} An object containing the configuration of a parameter
* @param id {String} A string representing the id to be added to the input
* @returns {OOUI.object} A OOUI object containing the new FieldLayout
*/
combobox: function combobox(param, id) {
var self = this,
conf = {
placeholder: 'Enter filter name',
options: [],
name: id,
id: id,
menu: {
filterFromInput: true
},
value: param.def,
$overlay: true
},
layconf = {
label: new OO.ui.HtmlSnippet(param.label),
align: 'right',
classes: ['jsCalc-field', 'jsCalc-field-combobox']
},
opts = param.range.split(/\s*,\s*/),
def = opts[0];
param.error = 'Not a valid selection';
if (param.help) {
layconf.helpInline = param.inlhelp;
layconf.help = new OO.ui.HtmlSnippet(param.help);
}
opts.forEach(function (opt) {
var op = {
data: opt,
label: opt
};
if (opt === param.def) {
op.selected = true;
def = opt;
}
conf.options.push(op);
});
var isvalid = function isvalid(val) {
return opts.indexOf(val) < 0 ? false : true;
};
conf.validate = isvalid;
param.toggles = helper.parseToggles(param.rawtogs, def);
param.ooui = new OO.ui.ComboBoxInputWidget(conf);
if (Object.keys(param.toggles).length > 0) {
param.ooui.on('change', function (value) {
helper.toggle.call(self, value, param.toggles);
});
}
return new OO.ui.FieldLayout(param.ooui, layconf);
},
/**
* Handler for checkbox inputs
*
* @param param {object} An object containing the configuration of a parameter
* @param id {String} A string representing the id to be added to the input
* @returns {OOUI.object} A OOUI object containing the new FieldLayout
*/
check: function check(param, id) {
var self = this,
conf = {
name: id,
id: id
},
layconf = {
label: new OO.ui.HtmlSnippet(param.label),
align: 'right',
classes: ['jsCalc-field', 'jsCalc-field-check']
};
param.toggles = helper.parseToggles(param.rawtogs, 'true');
param.error = 'Unknown error';
if (param.help) {
layconf.helpInline = param.inlhelp;
layconf.help = new OO.ui.HtmlSnippet(param.help);
}
if (param.def === 'true' || param.def === true || param.range !== undefined && param.def === param.range.split(/\s*,\s*/)[0]) {
conf.selected = true;
}
param.ooui = new OO.ui.CheckboxInputWidget(conf);
if (Object.keys(param.toggles).length > 0) {
param.ooui.on('change', function (selected) {
if (selected) {
helper.toggle.call(self, 'true', param.toggles);
} else {
helper.toggle.call(self, 'false', param.toggles);
}
}
}
return new OO.ui.FieldLayout(param.ooui, layconf);
},
* Handler for toggle switch inputs
*
* @param param {object} An object containing the configuration of a parameter
* @param id {String} A string representing the id to be added to the input
* @returns {OOUI.object} A OOUI object containing the new FieldLayout
*/
toggleswitch: function toggleswitch(param, id) {
var self =
conf =
id:
layconf =
label: new
classes:
param.toggles = helper.parseToggles(param.rawtogs, 'true');
param.error = 'Unknown
if (param.help)
layconf.helpInline =
layconf.help = new
if (param.def === 'true' || param.def === true || param.range !== undefined && param.def === param.range.split(/\s*,\s*/)[0]) {
conf.value =
param.ooui = new OO.ui.ToggleSwitchWidget(conf);
if (Object.keys(param.toggles).length > 0) {
param.ooui.on('change', function (selected) {
if (selected)
helper.toggle.call(self, 'true', param.toggles);
} else {
helper.toggle.call(self, 'false', param.toggles);
});
}
return new OO.ui.FieldLayout(param.ooui, layconf);
},
* Handler for toggle button
* @param param {object} An object containing the configuration of a parameter
* @param id {String} A string representing the id to be added to the input
* @returns {OOUI.object} A OOUI object containing the new FieldLayout
*/
togglebutton: function togglebutton(param, id) {
var self = this,
conf = {
id: id,
label: new OO.ui.HtmlSnippet(param.label)
},
layconf = {
label: '',
align: 'right',
classes: ['jsCalc-field', 'jsCalc-field-togglebutton']
};
param.toggles = helper.parseToggles(param.rawtogs, 'true');
param.error = 'Unknown error';
if (param.help) {
layconf.helpInline = param.inlhelp;
layconf.help = new OO.ui.HtmlSnippet(param.help);
}
if (param.def === 'true' || param.def === true || param.range !== undefined && param.def === param.range.split(/\s*,\s*/)[0]) {
conf.value = true;
}
param.ooui = new OO.ui.ToggleButtonWidget(conf);
if (Object.keys(param.toggles).length > 0) {
param.ooui.on('change', function (selected) {
if (selected) {
helper.toggle.call(self, 'true', param.toggles);
} else {
helper.toggle.call(self, 'false', param.toggles);
}
});
}
return new OO.ui.FieldLayout(param.ooui, layconf);
},
/**
* Handler for hiscore inputs
*
* @param param {object} An object containing the configuration of a parameter
* @param id {String} A string representing the id to be added to the input
* @returns {OOUI.object} A OOUI object containing the new FieldLayout
*/
hs: function hs(param, id) {
var self = this,
layconf = {
label: new OO.ui.HtmlSnippet(param.label),
align: 'right',
classes: ['jsCalc-field', 'jsCalc-field-hs']
},
lookups = {},
range = param.range.split(';'),
input1 = new OO.ui.TextInputWidget({
type: 'text',
id: id,
name: id,
value: param.def
}),
button1 = new OO.ui.ButtonInputWidget({
label: 'Lookup',
id: id + '-button',
name: id + '-button',
classes: ['jsCalc-field-hs-lookup'],
data: {
param: param.name
}
});
if (param.help) {
layconf.helpInline = param.inlhelp;
layconf.help = new OO.ui.HtmlSnippet(param.help);
}
var layout = new OO.ui.ActionFieldLayout(input1, button1, layconf);
var lookupHS = function lookupHS(event) {
var $t = $(event.target),
lookup = self.lookups[button1.getData().param],
// replace spaces with _ for the query
name = $('#' + lookup.id + ' input').val()
// @todo will this break for players with multiple spaces
// in their name? e.g. suomi's old display name
.replace(/\s+/g, '_'),
button = lookup.button;
button.setDisabled(true);
$.ajax({
url: '/cors/m=hiscore_oldschool/index_lite.ws?player=' + name,
dataType: 'text',
async: true,
timeout: 10000 // msec
}).done(function (data) {
var hsdata;
hsdata = data.trim().split(/\n+/g);
lookup.params.forEach(function (param) {
var id = helper.getId.call(self, param.param),
$input = $('#' + id + ' input'),
tParam = null,
val;
self.tParams.forEach(function (p) {
if (p.name === param.param) {
tParam = p;
}
});
if
}
if
val =
//tParam.ooui.setValue(param.skill);
} else
val =
//tParam.ooui.setValue(hsdata[param.skill].split(',')[param.val]);
}
if (!!tParam.ooui.setValue) {
} else if
tParam.ooui.selectItemByData(val);
} else if (tParam.type === 'fixed')
});
self.lsRSN = name;
localStorage.setItem('rsn', name);
}
button.setDisabled(false);
layout.setErrors([]);
}).fail(function (xhr, status) {
button.setDisabled(false);
var err = 'The player "' + name + '" does not exist, is banned or unranked, or we couldn\'t fetch your hiscores. Please enter the data manually.';
console.warn(status);
layout.setErrors([err]);
helper.showError.call(self, err);
});
};
button1.$element.click(lookupHS);
input1.$element.keydown(function (event) {
if (event.which === 13) {
lookupHS(event);
event.preventDefault();
}
});
/
if (self.lsRSN) {
lookups[param.name] =
id:
button:
params: []
};
range.forEach(function (el)
// to
if
return;
var spl =
param:
skill: spl[1],
val: spl[2]
});
});
// merge lookups into one
if (!self.lookups) {
} else
self.lookups = $.extend(self.lookups, lookups);
}
param.ooui = input1;
param.oouiButton = button1;
return layout;
},
/**
* Handler for Runescape name inputs
*
* @param param {object} An object containing the configuration of a parameter
* @param id {String} A string representing the id to be added to the input
* @returns {OOUI.object} A OOUI object containing the new FieldLayout
*/
rsn: function rsn(param, id) {
var self = this,
conf = {
type: 'text',
name: id,
id: id,
placeholder: 'Enter runescape name',
spellcheck: false,
maxLength: 12,
value: param.def
},
layconf = {
label: new OO.ui.HtmlSnippet(param.label),
align: 'right',
classes: ['jsCalc-field', 'jsCalc-field-string']
};
param.error = 'Invalid runescape name: RS names must be 1-12 characters long, can only contain letters, numbers, spaces, dashes and underscores. Names containing Mod are also not allowed.';
if (param.help) {
layconf.helpInline = param.inlhelp;
layconf.help = new OO.ui.HtmlSnippet(param.help);
}
// Use rsn loaded from localstorage, if available
if (self.lsRSN) {
conf.value =
}
var validrsn = function
if (name.search(/[^0-9a-zA-Z\-_\s]/) >= 0) {
return
} else {
if (name.toLowerCase().search(/(^mod\s|\smod\s|\smod$)/) >= 0) {
return false;
}
}
}
};
conf.validate = validrsn;
param.ooui = new OO.ui.TextInputWidget(conf);
return new OO.ui.FieldLayout(param.ooui, layconf);
},
/**
* Handler for integer inputs
*
* @param param {object} An object containing the configuration of a parameter
* @param id {String} A string representing the id to be added to the input
* @returns {OOUI.object} A OOUI object containing the new FieldLayout
*/
"int": function int(param, id) {
var self = this,
rng = helper.genRange(param.range, 'int'),
conf = {
min: rng[0],
max: rng[1],
step: rng[2],
showButtons: true,
buttonStep: rng[3],
allowInteger: true,
name: id,
id: id,
value: param.def || 0
},
layconf = {
label: new OO.ui.HtmlSnippet(param.label),
align: 'right',
classes: ['jsCalc-field', 'jsCalc-field-int']
},
error = 'Invalid integer. Must be between ' + rng[0] + ' and ' + rng[1];
param.toggles = helper.parseToggles(param.rawtogs, 'not0');
if (param.help) {
layconf.helpInline = param.inlhelp;
layconf.help = new OO.ui.HtmlSnippet(param.help);
}
if (rng[2] > 1) {
error += ' and a muiltiple of ' + rng[2];
}
param.error = error;
param.ooui = new OO.ui.NumberInputWidget(conf);
if (Object.keys(param.toggles).length > 0) {
param.ooui.on('change', function (value) {
helper.toggle.call(self, value, param.toggles);
});
}
return new OO.ui.FieldLayout(param.ooui, layconf);
},
/**
* Handler for number inputs
*
* @param param {object} An object containing the configuration of a parameter
* @param id {String} A string representing the id to be added to the input
* @returns {OOUI.object} A OOUI object containing the new FieldLayout
*/
number: function number(param, id) {
var self = this,
rng = helper.genRange(param.range, 'number'),
conf = {
min: rng[0],
max: rng[1],
step: rng[2],
showButtons: true,
buttonStep: rng[3],
name: id,
id: id,
value: param.def || 0
},
layconf = {
label: new OO.ui.HtmlSnippet(param.label),
align: 'right',
classes: ['jsCalc-field', 'jsCalc-field-number']
};
param.toggles = helper.parseToggles(param.rawtogs, 'not0');
param.error = 'Invalid interger. Must be between ' + rng[0] + ' and ' + rng[1] + ' and a multiple of ' + rng[2];
if (param.help) {
layconf.helpInline = param.inlhelp;
layconf.help = new OO.ui.HtmlSnippet(param.help);
}
param.ooui = new OO.ui.NumberInputWidget(conf);
if (Object.keys(param.toggles).length > 0) {
param.ooui.on('change', function (value) {
helper.toggle.call(self, value, param.toggles);
});
}
return new OO.ui.FieldLayout(param.ooui, layconf);
},
/**
* Handler for article inputs
*
* @param param {object} An object containing the configuration of a parameter
* @param id {String} A string representing the id to be added to the input
* @returns {OOUI.object} A OOUI object containing the new FieldLayout
*/
article: function article(param, id) {
var self = this,
conf = {
addQueryInput: false,
excludeCurrentPage: true,
showMissing: false,
showDescriptions: true,
validateTitle: true,
relative: false,
id: id,
name: id,
placeholder: 'Enter page name',
value: param.def
},
layconf = {
label: new OO.ui.HtmlSnippet(param.label),
align: 'right',
classes: ['jsCalc-field', 'jsCalc-field-article']
},
validNSnumbers = {
'_*': 'All',
'_-2': 'Media',
'_-1': 'Special',
_0: '(Main)',
_1: 'Talk',
_2: 'User',
_3: 'User talk',
_4: 'RuneScape',
_5: 'RuneScape talk',
_6: 'File',
_7: 'File talk',
_8: 'MediaWiki',
_9: 'MediaWiki talk',
_10: 'Template',
_11: 'Template talk',
_12: 'Help',
_13: 'Help talk',
_14: 'Category',
_15: 'Category talk',
_100: 'Update',
_101: 'Update talk',
_110: 'Forum',
_111: 'Forum talk',
_112: 'Exchange',
_113: 'Exchange talk',
_114: 'Charm',
_115: 'Charm talk',
_116: 'Calculator',
_117: 'Calculator talk',
_118: 'Map',
_119: 'Map talk',
_828: 'Module',
_829: 'Module talk'
},
validNSnames = {
all: '*',
media: -2,
special: -1,
main: 0,
'(main)': 0,
talk: 1,
user: 2,
'user talk': 3,
runescape: 4,
'runescape talk': 5,
file: 6,
'file talk': 7,
mediawiki: 8,
'mediawiki talk': 9,
template: 10,
'template talk': 11,
help: 12,
'help talk': 13,
category: 14,
'category talk': 15,
update: 100,
'update talk': 101,
forum: 110,
'forum talk': 111,
exchange: 112,
'exchange talk': 113,
charm: 114,
'charm talk': 115,
calculator: 116,
'calculator talk': 117,
map: 118,
'map talk': 119,
module: 828,
'module talk': 829
},
namespaces = '';
if (param.help) {
layconf.helpInline = param.inlhelp;
layconf.help = new OO.ui.HtmlSnippet(param.help);
}
if (param.range && param.range.length > 0) {
var names = param.range.split(/\s*,\s*/),
nsnumbers = [];
names.forEach(function (nmspace) {
nmspace = nmspace.toLowerCase();
if (validNSnumbers['_' + nmspace]) {
nsnumbers.push(nmspace);
} else if (validNSnames[nmspace]) {
nsnumbers.push(validNSnames[nmspace]);
}
});
if (nsnumbers.length < 1) {
conf.namespace = '0';
namespaces = '(Main) namespace';
} else if (nsnumbers.length < 2) {
conf.namespace = nsnumbers[0];
namespaces = nsnumbers[0] + ' namespace';
} else {
conf.namespace = nsnumbers.join('|');
var nsmap = function nsmap(num) {
return validNSnumbers['_' + num];
};
namespaces = nsnumbers.slice(0, -1).map(nsmap).join(', ') + ' or ' + nsnumbers.slice(-1).map(nsmap)[0] + ' namespaces';
}
} else if (self.suggestns && self.suggestns.length > 0) {
var nsnumbers = [];
self.suggestns.forEach(function (nmspace) {
nmspace = nmspace.toLowerCase();
if (validNSnumbers['_' + nmspace]) {
nsnumbers.push(nmspace);
} else if (validNSnames[nmspace]) {
nsnumbers.push(validNSnames[nmspace]);
}
});
if (nsnumbers.length < 1) {
conf.namespace = '0';
namespaces = '(Main) namespace';
} else if (nsnumbers.length < 2) {
conf.namespace = nsnumbers[0];
namespaces = nsnumbers[0] + ' namespace';
} else {
conf.namespace = nsnumbers.join('|');
var nsmap = function nsmap(num) {
return validNSnumbers['_' + num];
};
namespaces = nsnumbers.slice(0, -1).map(nsmap).join(', ') + ' or ' + nsnumbers.slice(-1).map(nsmap)[0] + ' namespaces';
}
} else {
conf.namespace = '0';
namespaces = '(Main) namespace';
}
param.error = 'Invalid page or page is not in ' + namespaces;
param.ooui = new mw.widgets.TitleInputWidget(conf);
return new OO.ui.FieldLayout(param.ooui, layconf);
},
/**
* Handler for group type params
*
* @param param {object} An object containing the configuration of a parameter
* @param id {String} A string representing the id to be added to the input
* @returns {OOUI.object} A OOUI object containing the new FieldLayout
*/
group: function group(param, id) {
param.ooui = new OO.ui.HorizontalLayout({
id: id,
classes: ['jsCalc-group']
});
if (param.label !== param.name) {
var label = new OO.ui.LabelWidget({
label: new OO.ui.HtmlSnippet(param.label),
classes: ['jsCalc-grouplabel']
});
param.ooui.addItems([label]);
}
return param.ooui;
},
/**
* Default handler for inputs
*
* @param param {object} An object containing the configuration of a parameter
* @param id {String} A string representing the id to be added to the input
* @returns {OOUI.object} A OOUI object containing the new FieldLayout
*/
def: function def(param, id) {
var layconf = {
label: new OO.ui.HtmlSnippet(param.label),
align: 'right',
classes: ['jsCalc-field', 'jsCalc-field-string'],
value: param.def
};
param.error = 'Unknown error';
if (param.help) {
layconf.helpInline = param.inlhelp;
layconf.help = new OO.ui.HtmlSnippet(param.help);
}
param.ooui = new OO.ui.TextInputWidget({
type: 'text',
name: id,
id: id
});
return new OO.ui.FieldLayout(param.ooui, layconf);
}
}
};
/**
Line 1,691 ⟶ 1,607:
*/
function Calc(elem) {
lines = lines.split('\n');
config = helper.parseConfig.call(this, lines);
}
});
}
self.lsRSN = localStorage.getItem('rsn');
mw.log(config);
}
return $('#jsForm-' + self.form).find('select, input');
};
}
Line 1,756 ⟶ 1,669:
*/
Calc.prototype.getId = function (id) {
return inputId;
};
Line 1,766 ⟶ 1,678:
*/
Calc.prototype.setupCalc = function () {
label: self.name,
classes:
id: 'jsForm-' + self.form
}),
submitButton,
submitButtonAction,
paramChangeAction,
timeout,
groupkeys = {};
self.tParams.forEach(function (param, index) {
// can skip any output here as the result is pulled from the
// param default in the config on submission
if (param.type === 'hidden') {
return;
}
var id = helper.getId.call(self, param.name),
method = helper.tParams[param.type] ? param.type : 'def';
// Generate list of items in group
if (param.type === 'group') {
var fields = param.range.split(/\s*,\s*/);
fields.forEach(function (field) {
groupkeys[field] =
}
param.layout = helper.tParams[method].call(self, param, id);
if (param.type === 'semihidden') {
param.layout.toggle(false);
}
// Add to group or form
if (groupkeys[param.name] || groupkeys[param.name] === 0) {
self.tParams[groupkeys[param.name]].ooui.addItems([param.layout]);
} else
fieldset.addItems([param.layout]);
}
});
// Run toggle for each field, check validity
self.tParams.forEach(function (param) {
if (param.toggles && Object.keys(param.toggles).length > 0) {
var val;
if (param.type === 'buttonselect') {
val = param.ooui.findSelectedItem().getData();
} else if (param.type === 'check') {
val = param.ooui.isSelected() ? 'true' : 'false';
} else if (param.type === 'toggleswitch' || param.type === 'togglebutton') {
val = param.ooui.getValue() ? 'true' : 'false';
} else {
val = param.ooui.getValue();
}
helper.toggle.call(self, val, param.toggles);
}
if (param.type === 'number' || param.type === 'int' || param.type === 'rsn') {
param.ooui.setValidityFlag();
}
});
submitButton = new OO.ui.ButtonInputWidget({
label: 'Submit',
flags: ['primary', 'progressive'],
classes: ['jcSubmit']
});
submitButtonAction = function submitButtonAction() {
helper.submitForm.call(self);
};
submitButton.on('click', submitButtonAction);
submitButton.$element.data('oouiButton', submitButton);
self.submitlayout = new OO.ui.FieldLayout(submitButton, {
label: ' ',
align: 'right',
classes: ['jsCalc-field', 'jsCalc-field-submit']
});
fieldset.addItems([self.submitlayout]);
// Auto-submit
if (['off', 'false', 'disabled'].indexOf(self.autosubmit) === -1) {
// We only want one of these pending at once
var timeoutFunc = function timeoutFunc(param) {
clearTimeout(timeout);
timeout = setTimeout(paramChangeAction, 500, param);
};
// Add
paramChangeAction = function paramChangeAction(widget) {
if (typeof widget.getFlags === 'undefined' || !widget.getFlags().includes('invalid')) {
helper.submitForm.call(self);
}
};
self.tParams.forEach(function (param) {
if (param.type === 'hidden' || param.type === 'hs' || param.type === 'group') {
return;
} else if (param.type === 'buttonselect') {
param.ooui.on('select', timeoutFunc, [param.ooui]);
}
param.ooui.on('change', timeoutFunc, [param.ooui]);
});
}
if (self.configError) {
fieldset.$element.append('<br>', self.configError);
}
$('#bodyContent').find('#' + self.form).empty().append(fieldset.$element);
// make buttonselects all the same height
return e.type === 'buttonselect';
}).forEach(function (e) {
var m = e.ooui.items.reduce(function (acc, curr) {
return Math.max(acc, curr.$element.find('> a.oo-ui-buttonElement-button').height());
}, 0);
e.ooui.items.forEach(function (e) {
return e.$element.find('> a.oo-ui-buttonElement-button').height(m);
});
});
};
Line 1,887 ⟶ 1,804:
*/
function lookupCalc(calcId) {
}
Line 1,894 ⟶ 1,811:
*/
function init() {
$('.jcConfig').each(function () {
var c = new Calc(this);
c.setupCalc();
calcStore[c.form] = c;
// if (c.autosubmit === 'true' || c.autosubmit === true) {
//
//
// allow scripts to hook into calc setup completion
mw.hook('rscalc.setupComplete').fire();
}
$(init);
rs.calc = {};
rs.calc.lookup = lookupCalc;
|