From 0b4c39338e5e668065af21d54f6ce6c00c269086 Mon Sep 17 00:00:00 2001
From: Peter Jaszkowiak
Date: Wed, 7 Dec 2016 22:30:18 -0700
Subject: [PATCH] Translation bootbox wrapper
- Replaced minfied bootbox file with unminified one since it's minified at build anyways
- Removed existing override
- Made translator more verbose in dev mode; it now warns about missing translations
---
public/src/app.js | 1 -
public/src/modules/translator.js | 25 +-
public/src/overrides.js | 37 --
public/vendor/bootbox/bootbox.js | 830 +++++++++++++++++++++++++++
public/vendor/bootbox/bootbox.min.js | 7 -
public/vendor/bootbox/wrapper.js | 44 ++
src/meta/js.js | 3 +-
7 files changed, 895 insertions(+), 52 deletions(-)
create mode 100644 public/vendor/bootbox/bootbox.js
delete mode 100644 public/vendor/bootbox/bootbox.min.js
create mode 100644 public/vendor/bootbox/wrapper.js
diff --git a/public/src/app.js b/public/src/app.js
index 7c69d9fb61..fe12a7adfc 100644
--- a/public/src/app.js
+++ b/public/src/app.js
@@ -53,7 +53,6 @@ app.cacheBuster = null;
}
});
- overrides.overrideBootbox();
createHeaderTooltips();
app.showEmailConfirmWarning();
app.showCookieWarning();
diff --git a/public/src/modules/translator.js b/public/src/modules/translator.js
index 30b67ef3a8..c19c705eaf 100644
--- a/public/src/modules/translator.js
+++ b/public/src/modules/translator.js
@@ -5,10 +5,14 @@
function loadClient(language, namespace) {
return Promise.resolve(jQuery.getJSON(config.relative_path + '/api/language/' + language + '/' + encodeURIComponent(namespace)));
}
+ var warn = function () {};
+ if (typeof config === 'object' && config.environment === 'development') {
+ warn = console.warn.bind(console);
+ }
if (typeof define === 'function' && define.amd) {
// AMD. Register as a named module
define('translator', ['string'], function (string) {
- return factory(string, loadClient);
+ return factory(string, loadClient, warn);
});
} else if (typeof module === 'object' && module.exports) {
// Node
@@ -16,6 +20,13 @@
require('promise-polyfill');
var languages = require('../../../src/languages');
+ if (global.env === 'development') {
+ var winston = require('winston');
+ warn = function (a, b, c, d) {
+ winston.warn(a, b, c, d);
+ };
+ }
+
function loadServer(language, namespace) {
return new Promise(function (resolve, reject) {
languages.get(language, namespace, function (err, data) {
@@ -28,12 +39,12 @@
});
}
- module.exports = factory(require('string'), loadServer);
+ module.exports = factory(require('string'), loadServer, warn);
}());
} else {
- window.translator = factory(window.string, loadClient);
+ window.translator = factory(window.string, loadClient, warn);
}
-}(function (string, load) {
+}(function (string, load, warn) {
'use strict';
var assign = Object.assign || jQuery.extend;
function classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
@@ -238,6 +249,7 @@
}
if (namespace && !key) {
+ warn('Missing key in translation token "' + name + '"');
return Promise.resolve('[[' + namespace + ']]');
}
@@ -256,6 +268,7 @@
var translatedArgs = result.slice(1);
if (!translated) {
+ warn('Missing translation "' + name + '"');
return key;
}
var out = translated;
@@ -276,7 +289,7 @@
Translator.prototype.getTranslation = function getTranslation(namespace, key) {
var translation;
if (!namespace) {
- console.warn('[translator] Parameter `namespace` is ' + namespace + (namespace === '' ? '(empty string)' : ''));
+ warn('[translator] Parameter `namespace` is ' + namespace + (namespace === '' ? '(empty string)' : ''));
translation = Promise.resolve({});
} else {
translation = this.translations[namespace] = this.translations[namespace] || this.load(this.lang, namespace);
@@ -441,7 +454,7 @@
Translator.create(lang).translate(text).then(function (output) {
return cb(output);
}).catch(function (err) {
- console.error('Translation failed: ' + err.stack);
+ warn('Translation failed: ' + err.stack);
});
},
diff --git a/public/src/overrides.js b/public/src/overrides.js
index a5216910d2..7ee354a06d 100644
--- a/public/src/overrides.js
+++ b/public/src/overrides.js
@@ -111,43 +111,6 @@ if ('undefined' !== typeof window) {
});
}());
- overrides.overrideBootbox = function () {
- require(['translator'], function (translator) {
- var dialog = bootbox.dialog,
- prompt = bootbox.prompt,
- confirm = bootbox.confirm;
-
- function translate(modal) {
- var header = modal.find('.modal-header'),
- footer = modal.find('.modal-footer');
- translator.translate(header.html(), function (html) {
- header.html(html);
- });
- translator.translate(footer.html(), function (html) {
- footer.html(html);
- });
- }
-
- bootbox.dialog = function () {
- var modal = $(dialog.apply(this, arguments)[0]);
- translate(modal);
- return modal;
- };
-
- bootbox.prompt = function () {
- var modal = $(prompt.apply(this, arguments)[0]);
- translate(modal);
- return modal;
- };
-
- bootbox.confirm = function () {
- var modal = $(confirm.apply(this, arguments)[0]);
- translate(modal);
- return modal;
- };
- });
- };
-
overrides.overrideTimeago = function () {
var timeagoFn = $.fn.timeago;
if (parseInt(config.timeagoCutoff, 10) === 0) {
diff --git a/public/vendor/bootbox/bootbox.js b/public/vendor/bootbox/bootbox.js
new file mode 100644
index 0000000000..15a5527f4a
--- /dev/null
+++ b/public/vendor/bootbox/bootbox.js
@@ -0,0 +1,830 @@
+/**
+ * bootbox.js [v4.4.0]
+ *
+ * http://bootboxjs.com/license.txt
+ */
+
+// @see https://github.com/makeusabrew/bootbox/issues/180
+// @see https://github.com/makeusabrew/bootbox/issues/186
+(function (root, factory) {
+
+ "use strict";
+
+ // ** removed require.js and commonjs module definitions
+
+ // Browser globals (root is window)
+ root.bootbox = factory(root.jQuery);
+
+}(this, function init($, undefined) {
+
+ "use strict";
+
+ // the base DOM structure needed to create a modal
+ var templates = {
+ dialog:
+ "
" +
+ "
" +
+ "
" +
+ "
" +
+ "
" +
+ "
" +
+ "
",
+ header:
+ "
" +
+ "" +
+ "
",
+ footer:
+ "",
+ closeButton:
+ "",
+ form:
+ "",
+ inputs: {
+ text:
+ "",
+ textarea:
+ "",
+ email:
+ "",
+ select:
+ "",
+ checkbox:
+ "",
+ date:
+ "",
+ time:
+ "",
+ number:
+ "",
+ password:
+ ""
+ }
+ };
+
+ var defaults = {
+ // default language
+ locale: "en",
+ // show backdrop or not. Default to static so user has to interact with dialog
+ backdrop: "static",
+ // animate the modal in/out
+ animate: true,
+ // additional class string applied to the top level dialog
+ className: null,
+ // whether or not to include a close button
+ closeButton: true,
+ // show the dialog immediately by default
+ show: true,
+ // dialog container
+ container: "body"
+ };
+
+ // our public object; augmented after our private API
+ var exports = {};
+
+ /**
+ * @private
+ */
+ function _t(key) {
+ var locale = locales[defaults.locale];
+ return locale ? locale[key] : locales.en[key];
+ }
+
+ function processCallback(e, dialog, callback) {
+ e.stopPropagation();
+ e.preventDefault();
+
+ // by default we assume a callback will get rid of the dialog,
+ // although it is given the opportunity to override this
+
+ // so, if the callback can be invoked and it *explicitly returns false*
+ // then we'll set a flag to keep the dialog active...
+ var preserveDialog = $.isFunction(callback) && callback.call(dialog, e) === false;
+
+ // ... otherwise we'll bin it
+ if (!preserveDialog) {
+ dialog.modal("hide");
+ }
+ }
+
+ function getKeyLength(obj) {
+ // @TODO defer to Object.keys(x).length if available?
+ var k, t = 0;
+ for (k in obj) {
+ t ++;
+ }
+ return t;
+ }
+
+ function each(collection, iterator) {
+ var index = 0;
+ $.each(collection, function(key, value) {
+ iterator(key, value, index++);
+ });
+ }
+
+ function sanitize(options) {
+ var buttons;
+ var total;
+
+ if (typeof options !== "object") {
+ throw new Error("Please supply an object of options");
+ }
+
+ if (!options.message) {
+ throw new Error("Please specify a message");
+ }
+
+ // make sure any supplied options take precedence over defaults
+ options = $.extend({}, defaults, options);
+
+ if (!options.buttons) {
+ options.buttons = {};
+ }
+
+ buttons = options.buttons;
+
+ total = getKeyLength(buttons);
+
+ each(buttons, function(key, button, index) {
+
+ if ($.isFunction(button)) {
+ // short form, assume value is our callback. Since button
+ // isn't an object it isn't a reference either so re-assign it
+ button = buttons[key] = {
+ callback: button
+ };
+ }
+
+ // before any further checks make sure by now button is the correct type
+ if ($.type(button) !== "object") {
+ throw new Error("button with key " + key + " must be an object");
+ }
+
+ if (!button.label) {
+ // the lack of an explicit label means we'll assume the key is good enough
+ button.label = key;
+ }
+
+ if (!button.className) {
+ if (total <= 2 && index === total-1) {
+ // always add a primary to the main option in a two-button dialog
+ button.className = "btn-primary";
+ } else {
+ button.className = "btn-default";
+ }
+ }
+ });
+
+ return options;
+ }
+
+ /**
+ * map a flexible set of arguments into a single returned object
+ * if args.length is already one just return it, otherwise
+ * use the properties argument to map the unnamed args to
+ * object properties
+ * so in the latter case:
+ * mapArguments(["foo", $.noop], ["message", "callback"])
+ * -> { message: "foo", callback: $.noop }
+ */
+ function mapArguments(args, properties) {
+ var argn = args.length;
+ var options = {};
+
+ if (argn < 1 || argn > 2) {
+ throw new Error("Invalid argument length");
+ }
+
+ if (argn === 2 || typeof args[0] === "string") {
+ options[properties[0]] = args[0];
+ options[properties[1]] = args[1];
+ } else {
+ options = args[0];
+ }
+
+ return options;
+ }
+
+ /**
+ * merge a set of default dialog options with user supplied arguments
+ */
+ function mergeArguments(defaults, args, properties) {
+ return $.extend(
+ // deep merge
+ true,
+ // ensure the target is an empty, unreferenced object
+ {},
+ // the base options object for this type of dialog (often just buttons)
+ defaults,
+ // args could be an object or array; if it's an array properties will
+ // map it to a proper options object
+ mapArguments(
+ args,
+ properties
+ )
+ );
+ }
+
+ /**
+ * this entry-level method makes heavy use of composition to take a simple
+ * range of inputs and return valid options suitable for passing to bootbox.dialog
+ */
+ function mergeDialogOptions(className, labels, properties, args) {
+ // build up a base set of dialog properties
+ var baseOptions = {
+ className: "bootbox-" + className,
+ buttons: createLabels.apply(null, labels)
+ };
+
+ // ensure the buttons properties generated, *after* merging
+ // with user args are still valid against the supplied labels
+ return validateButtons(
+ // merge the generated base properties with user supplied arguments
+ mergeArguments(
+ baseOptions,
+ args,
+ // if args.length > 1, properties specify how each arg maps to an object key
+ properties
+ ),
+ labels
+ );
+ }
+
+ /**
+ * from a given list of arguments return a suitable object of button labels
+ * all this does is normalise the given labels and translate them where possible
+ * e.g. "ok", "confirm" -> { ok: "OK, cancel: "Annuleren" }
+ */
+ function createLabels() {
+ var buttons = {};
+
+ for (var i = 0, j = arguments.length; i < j; i++) {
+ var argument = arguments[i];
+ var key = argument.toLowerCase();
+ var value = argument.toUpperCase();
+
+ buttons[key] = {
+ label: _t(value)
+ };
+ }
+
+ return buttons;
+ }
+
+ function validateButtons(options, buttons) {
+ var allowedButtons = {};
+ each(buttons, function(key, value) {
+ allowedButtons[value] = true;
+ });
+
+ each(options.buttons, function(key) {
+ if (allowedButtons[key] === undefined) {
+ throw new Error("button key " + key + " is not allowed (options are " + buttons.join("\n") + ")");
+ }
+ });
+
+ return options;
+ }
+
+ exports.alert = function() {
+ var options;
+
+ options = mergeDialogOptions("alert", ["ok"], ["message", "callback"], arguments);
+
+ if (options.callback && !$.isFunction(options.callback)) {
+ throw new Error("alert requires callback property to be a function when provided");
+ }
+
+ /**
+ * overrides
+ */
+ options.buttons.ok.callback = options.onEscape = function() {
+ if ($.isFunction(options.callback)) {
+ return options.callback.call(this);
+ }
+ return true;
+ };
+
+ return exports.dialog(options);
+ };
+
+ exports.confirm = function() {
+ var options;
+
+ options = mergeDialogOptions("confirm", ["cancel", "confirm"], ["message", "callback"], arguments);
+
+ /**
+ * overrides; undo anything the user tried to set they shouldn't have
+ */
+ options.buttons.cancel.callback = options.onEscape = function() {
+ return options.callback.call(this, false);
+ };
+
+ options.buttons.confirm.callback = function() {
+ return options.callback.call(this, true);
+ };
+
+ // confirm specific validation
+ if (!$.isFunction(options.callback)) {
+ throw new Error("confirm requires a callback");
+ }
+
+ return exports.dialog(options);
+ };
+
+ exports.prompt = function() {
+ var options;
+ var defaults;
+ var dialog;
+ var form;
+ var input;
+ var shouldShow;
+ var inputOptions;
+
+ // we have to create our form first otherwise
+ // its value is undefined when gearing up our options
+ // @TODO this could be solved by allowing message to
+ // be a function instead...
+ form = $(templates.form);
+
+ // prompt defaults are more complex than others in that
+ // users can override more defaults
+ // @TODO I don't like that prompt has to do a lot of heavy
+ // lifting which mergeDialogOptions can *almost* support already
+ // just because of 'value' and 'inputType' - can we refactor?
+ defaults = {
+ className: "bootbox-prompt",
+ buttons: createLabels("cancel", "confirm"),
+ value: "",
+ inputType: "text"
+ };
+
+ options = validateButtons(
+ mergeArguments(defaults, arguments, ["title", "callback"]),
+ ["cancel", "confirm"]
+ );
+
+ // capture the user's show value; we always set this to false before
+ // spawning the dialog to give us a chance to attach some handlers to
+ // it, but we need to make sure we respect a preference not to show it
+ shouldShow = (options.show === undefined) ? true : options.show;
+
+ /**
+ * overrides; undo anything the user tried to set they shouldn't have
+ */
+ options.message = form;
+
+ options.buttons.cancel.callback = options.onEscape = function() {
+ return options.callback.call(this, null);
+ };
+
+ options.buttons.confirm.callback = function() {
+ var value;
+
+ switch (options.inputType) {
+ case "text":
+ case "textarea":
+ case "email":
+ case "select":
+ case "date":
+ case "time":
+ case "number":
+ case "password":
+ value = input.val();
+ break;
+
+ case "checkbox":
+ var checkedItems = input.find("input:checked");
+
+ // we assume that checkboxes are always multiple,
+ // hence we default to an empty array
+ value = [];
+
+ each(checkedItems, function(_, item) {
+ value.push($(item).val());
+ });
+ break;
+ }
+
+ return options.callback.call(this, value);
+ };
+
+ options.show = false;
+
+ // prompt specific validation
+ if (!options.title) {
+ throw new Error("prompt requires a title");
+ }
+
+ if (!$.isFunction(options.callback)) {
+ throw new Error("prompt requires a callback");
+ }
+
+ if (!templates.inputs[options.inputType]) {
+ throw new Error("invalid prompt type");
+ }
+
+ // create the input based on the supplied type
+ input = $(templates.inputs[options.inputType]);
+
+ switch (options.inputType) {
+ case "text":
+ case "textarea":
+ case "email":
+ case "date":
+ case "time":
+ case "number":
+ case "password":
+ input.val(options.value);
+ break;
+
+ case "select":
+ var groups = {};
+ inputOptions = options.inputOptions || [];
+
+ if (!$.isArray(inputOptions)) {
+ throw new Error("Please pass an array of input options");
+ }
+
+ if (!inputOptions.length) {
+ throw new Error("prompt with select requires options");
+ }
+
+ each(inputOptions, function(_, option) {
+
+ // assume the element to attach to is the input...
+ var elem = input;
+
+ if (option.value === undefined || option.text === undefined) {
+ throw new Error("given options in wrong format");
+ }
+
+ // ... but override that element if this option sits in a group
+
+ if (option.group) {
+ // initialise group if necessary
+ if (!groups[option.group]) {
+ groups[option.group] = $("").attr("label", option.group);
+ }
+
+ elem = groups[option.group];
+ }
+
+ elem.append("");
+ });
+
+ each(groups, function(_, group) {
+ input.append(group);
+ });
+
+ // safe to set a select's value as per a normal input
+ input.val(options.value);
+ break;
+
+ case "checkbox":
+ var values = $.isArray(options.value) ? options.value : [options.value];
+ inputOptions = options.inputOptions || [];
+
+ if (!inputOptions.length) {
+ throw new Error("prompt with checkbox requires options");
+ }
+
+ if (!inputOptions[0].value || !inputOptions[0].text) {
+ throw new Error("given options in wrong format");
+ }
+
+ // checkboxes have to nest within a containing element, so
+ // they break the rules a bit and we end up re-assigning
+ // our 'input' element to this container instead
+ input = $("");
+
+ each(inputOptions, function(_, option) {
+ var checkbox = $(templates.inputs[options.inputType]);
+
+ checkbox.find("input").attr("value", option.value);
+ checkbox.find("label").append(option.text);
+
+ // we've ensured values is an array so we can always iterate over it
+ each(values, function(_, value) {
+ if (value === option.value) {
+ checkbox.find("input").prop("checked", true);
+ }
+ });
+
+ input.append(checkbox);
+ });
+ break;
+ }
+
+ // @TODO provide an attributes option instead
+ // and simply map that as keys: vals
+ if (options.placeholder) {
+ input.attr("placeholder", options.placeholder);
+ }
+
+ if (options.pattern) {
+ input.attr("pattern", options.pattern);
+ }
+
+ if (options.maxlength) {
+ input.attr("maxlength", options.maxlength);
+ }
+
+ // now place it in our form
+ form.append(input);
+
+ form.on("submit", function(e) {
+ e.preventDefault();
+ // Fix for SammyJS (or similar JS routing library) hijacking the form post.
+ e.stopPropagation();
+ // @TODO can we actually click *the* button object instead?
+ // e.g. buttons.confirm.click() or similar
+ dialog.find(".btn-primary").click();
+ });
+
+ dialog = exports.dialog(options);
+
+ // clear the existing handler focusing the submit button...
+ dialog.off("shown.bs.modal");
+
+ // ...and replace it with one focusing our input, if possible
+ dialog.on("shown.bs.modal", function() {
+ // need the closure here since input isn't
+ // an object otherwise
+ input.focus();
+ });
+
+ if (shouldShow === true) {
+ dialog.modal("show");
+ }
+
+ return dialog;
+ };
+
+ exports.dialog = function(options) {
+ options = sanitize(options);
+
+ var dialog = $(templates.dialog);
+ var innerDialog = dialog.find(".modal-dialog");
+ var body = dialog.find(".modal-body");
+ var buttons = options.buttons;
+ var buttonStr = "";
+ var callbacks = {
+ onEscape: options.onEscape
+ };
+
+ if ($.fn.modal === undefined) {
+ throw new Error(
+ "$.fn.modal is not defined; please double check you have included " +
+ "the Bootstrap JavaScript library. See http://getbootstrap.com/javascript/ " +
+ "for more details."
+ );
+ }
+
+ each(buttons, function(key, button) {
+
+ // @TODO I don't like this string appending to itself; bit dirty. Needs reworking
+ // can we just build up button elements instead? slower but neater. Then button
+ // can just become a template too
+ buttonStr += "";
+ callbacks[key] = button.callback;
+ });
+
+ body.find(".bootbox-body").html(options.message);
+
+ if (options.animate === true) {
+ dialog.addClass("fade");
+ }
+
+ if (options.className) {
+ dialog.addClass(options.className);
+ }
+
+ if (options.size === "large") {
+ innerDialog.addClass("modal-lg");
+ } else if (options.size === "small") {
+ innerDialog.addClass("modal-sm");
+ }
+
+ if (options.title) {
+ body.before(templates.header);
+ }
+
+ if (options.closeButton) {
+ var closeButton = $(templates.closeButton);
+
+ if (options.title) {
+ dialog.find(".modal-header").prepend(closeButton);
+ } else {
+ closeButton.css("margin-top", "-10px").prependTo(body);
+ }
+ }
+
+ if (options.title) {
+ dialog.find(".modal-title").html(options.title);
+ }
+
+ if (buttonStr.length) {
+ body.after(templates.footer);
+ dialog.find(".modal-footer").html(buttonStr);
+ }
+
+
+ /**
+ * Bootstrap event listeners; used handle extra
+ * setup & teardown required after the underlying
+ * modal has performed certain actions
+ */
+
+ dialog.on("hidden.bs.modal", function(e) {
+ // ensure we don't accidentally intercept hidden events triggered
+ // by children of the current dialog. We shouldn't anymore now BS
+ // namespaces its events; but still worth doing
+ if (e.target === this) {
+ dialog.remove();
+ }
+ });
+
+ /*
+ dialog.on("show.bs.modal", function() {
+ // sadly this doesn't work; show is called *just* before
+ // the backdrop is added so we'd need a setTimeout hack or
+ // otherwise... leaving in as would be nice
+ if (options.backdrop) {
+ dialog.next(".modal-backdrop").addClass("bootbox-backdrop");
+ }
+ });
+ */
+
+ dialog.on("shown.bs.modal", function() {
+ dialog.find(".btn-primary:first").focus();
+ });
+
+ /**
+ * Bootbox event listeners; experimental and may not last
+ * just an attempt to decouple some behaviours from their
+ * respective triggers
+ */
+
+ if (options.backdrop !== "static") {
+ // A boolean true/false according to the Bootstrap docs
+ // should show a dialog the user can dismiss by clicking on
+ // the background.
+ // We always only ever pass static/false to the actual
+ // $.modal function because with `true` we can't trap
+ // this event (the .modal-backdrop swallows it)
+ // However, we still want to sort of respect true
+ // and invoke the escape mechanism instead
+ dialog.on("click.dismiss.bs.modal", function(e) {
+ // @NOTE: the target varies in >= 3.3.x releases since the modal backdrop
+ // moved *inside* the outer dialog rather than *alongside* it
+ if (dialog.children(".modal-backdrop").length) {
+ e.currentTarget = dialog.children(".modal-backdrop").get(0);
+ }
+
+ if (e.target !== e.currentTarget) {
+ return;
+ }
+
+ dialog.trigger("escape.close.bb");
+ });
+ }
+
+ dialog.on("escape.close.bb", function(e) {
+ if (callbacks.onEscape) {
+ processCallback(e, dialog, callbacks.onEscape);
+ }
+ });
+
+ /**
+ * Standard jQuery event listeners; used to handle user
+ * interaction with our dialog
+ */
+
+ dialog.on("click", ".modal-footer button", function(e) {
+ var callbackKey = $(this).data("bb-handler");
+
+ processCallback(e, dialog, callbacks[callbackKey]);
+ });
+
+ dialog.on("click", ".bootbox-close-button", function(e) {
+ // onEscape might be falsy but that's fine; the fact is
+ // if the user has managed to click the close button we
+ // have to close the dialog, callback or not
+ processCallback(e, dialog, callbacks.onEscape);
+ });
+
+ dialog.on("keyup", function(e) {
+ if (e.which === 27) {
+ dialog.trigger("escape.close.bb");
+ }
+ });
+
+ // the remainder of this method simply deals with adding our
+ // dialogent to the DOM, augmenting it with Bootstrap's modal
+ // functionality and then giving the resulting object back
+ // to our caller
+
+ $(options.container).append(dialog);
+
+ dialog.modal({
+ backdrop: options.backdrop ? "static": false,
+ keyboard: false,
+ show: false
+ });
+
+ if (options.show) {
+ dialog.modal("show");
+ }
+
+ // @TODO should we return the raw element here or should
+ // we wrap it in an object on which we can expose some neater
+ // methods, e.g. var d = bootbox.alert(); d.hide(); instead
+ // of d.modal("hide");
+
+ /*
+ function BBDialog(elem) {
+ this.elem = elem;
+ }
+
+ BBDialog.prototype = {
+ hide: function() {
+ return this.elem.modal("hide");
+ },
+ show: function() {
+ return this.elem.modal("show");
+ }
+ };
+ */
+
+ return dialog;
+
+ };
+
+ exports.setDefaults = function() {
+ var values = {};
+
+ if (arguments.length === 2) {
+ // allow passing of single key/value...
+ values[arguments[0]] = arguments[1];
+ } else {
+ // ... and as an object too
+ values = arguments[0];
+ }
+
+ $.extend(defaults, values);
+ };
+
+ exports.hideAll = function() {
+ $(".bootbox").modal("hide");
+
+ return exports;
+ };
+
+
+ /**
+ * standard locales. Please add more according to ISO 639-1 standard. Multiple language variants are
+ * unlikely to be required. If this gets too large it can be split out into separate JS files.
+ */
+ var locales = {
+ // ** removed all other languages
+ // ** use NodeBB translations instead
+ en : {
+ OK : "OK",
+ CANCEL : "Cancel",
+ CONFIRM : "OK"
+ },
+ };
+
+ exports.addLocale = function(name, values) {
+ $.each(["OK", "CANCEL", "CONFIRM"], function(_, v) {
+ if (!values[v]) {
+ throw new Error("Please supply a translation for '" + v + "'");
+ }
+ });
+
+ locales[name] = {
+ OK: values.OK,
+ CANCEL: values.CANCEL,
+ CONFIRM: values.CONFIRM
+ };
+
+ return exports;
+ };
+
+ exports.removeLocale = function(name) {
+ delete locales[name];
+
+ return exports;
+ };
+
+ exports.setLocale = function(name) {
+ return exports.setDefaults("locale", name);
+ };
+
+ exports.init = function(_$) {
+ return init(_$ || $);
+ };
+
+ return exports;
+}));
\ No newline at end of file
diff --git a/public/vendor/bootbox/bootbox.min.js b/public/vendor/bootbox/bootbox.min.js
deleted file mode 100644
index 1f85a1cf9e..0000000000
--- a/public/vendor/bootbox/bootbox.min.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/**
- * bootbox.js v4.4.0
- *
- * http://bootboxjs.com/license.txt
- * psychobunny - Removed require.js requirement
- */
-!function(a,b){"use strict";a.bootbox=b(a.jQuery)}(this,function a(b,c){"use strict";function d(a){var b=q[o.locale];return b?b[a]:q.en[a]}function e(a,c,d){a.stopPropagation(),a.preventDefault();var e=b.isFunction(d)&&d.call(c,a)===!1;e||c.modal("hide")}function f(a){var b,c=0;for(b in a)c++;return c}function g(a,c){var d=0;b.each(a,function(a,b){c(a,b,d++)})}function h(a){var c,d;if("object"!=typeof a)throw new Error("Please supply an object of options");if(!a.message)throw new Error("Please specify a message");return a=b.extend({},o,a),a.buttons||(a.buttons={}),c=a.buttons,d=f(c),g(c,function(a,e,f){if(b.isFunction(e)&&(e=c[a]={callback:e}),"object"!==b.type(e))throw new Error("button with key "+a+" must be an object");e.label||(e.label=a),e.className||(e.className=2>=d&&f===d-1?"btn-primary":"btn-default")}),a}function i(a,b){var c=a.length,d={};if(1>c||c>2)throw new Error("Invalid argument length");return 2===c||"string"==typeof a[0]?(d[b[0]]=a[0],d[b[1]]=a[1]):d=a[0],d}function j(a,c,d){return b.extend(!0,{},a,i(c,d))}function k(a,b,c,d){var e={className:"bootbox-"+a,buttons:l.apply(null,b)};return m(j(e,d,c),b)}function l(){for(var a={},b=0,c=arguments.length;c>b;b++){var e=arguments[b],f=e.toLowerCase(),g=e.toUpperCase();a[f]={label:d(g)}}return a}function m(a,b){var d={};return g(b,function(a,b){d[b]=!0}),g(a.buttons,function(a){if(d[a]===c)throw new Error("button key "+a+" is not allowed (options are "+b.join("\n")+")")}),a}var n={dialog:"
",header:"
",footer:"",closeButton:"",form:"",inputs:{text:"",textarea:"",email:"",select:"",checkbox:"",date:"",time:"",number:"",password:""}},o={locale:"en",backdrop:"static",animate:!0,className:null,closeButton:!0,show:!0,container:"body"},p={};p.alert=function(){var a;if(a=k("alert",["ok"],["message","callback"],arguments),a.callback&&!b.isFunction(a.callback))throw new Error("alert requires callback property to be a function when provided");return a.buttons.ok.callback=a.onEscape=function(){return b.isFunction(a.callback)?a.callback.call(this):!0},p.dialog(a)},p.confirm=function(){var a;if(a=k("confirm",["cancel","confirm"],["message","callback"],arguments),a.buttons.cancel.callback=a.onEscape=function(){return a.callback.call(this,!1)},a.buttons.confirm.callback=function(){return a.callback.call(this,!0)},!b.isFunction(a.callback))throw new Error("confirm requires a callback");return p.dialog(a)},p.prompt=function(){var a,d,e,f,h,i,k;if(f=b(n.form),d={className:"bootbox-prompt",buttons:l("cancel","confirm"),value:"",inputType:"text"},a=m(j(d,arguments,["title","callback"]),["cancel","confirm"]),i=a.show===c?!0:a.show,a.message=f,a.buttons.cancel.callback=a.onEscape=function(){return a.callback.call(this,null)},a.buttons.confirm.callback=function(){var c;switch(a.inputType){case"text":case"textarea":case"email":case"select":case"date":case"time":case"number":case"password":c=h.val();break;case"checkbox":var d=h.find("input:checked");c=[],g(d,function(a,d){c.push(b(d).val())})}return a.callback.call(this,c)},a.show=!1,!a.title)throw new Error("prompt requires a title");if(!b.isFunction(a.callback))throw new Error("prompt requires a callback");if(!n.inputs[a.inputType])throw new Error("invalid prompt type");switch(h=b(n.inputs[a.inputType]),a.inputType){case"text":case"textarea":case"email":case"date":case"time":case"number":case"password":h.val(a.value);break;case"select":var o={};if(k=a.inputOptions||[],!b.isArray(k))throw new Error("Please pass an array of input options");if(!k.length)throw new Error("prompt with select requires options");g(k,function(a,d){var e=h;if(d.value===c||d.text===c)throw new Error("given options in wrong format");d.group&&(o[d.group]||(o[d.group]=b("").attr("label",d.group)),e=o[d.group]),e.append("")}),g(o,function(a,b){h.append(b)}),h.val(a.value);break;case"checkbox":var q=b.isArray(a.value)?a.value:[a.value];if(k=a.inputOptions||[],!k.length)throw new Error("prompt with checkbox requires options");if(!k[0].value||!k[0].text)throw new Error("given options in wrong format");h=b(""),g(k,function(c,d){var e=b(n.inputs[a.inputType]);e.find("input").attr("value",d.value),e.find("label").append(d.text),g(q,function(a,b){b===d.value&&e.find("input").prop("checked",!0)}),h.append(e)})}return a.placeholder&&h.attr("placeholder",a.placeholder),a.pattern&&h.attr("pattern",a.pattern),a.maxlength&&h.attr("maxlength",a.maxlength),f.append(h),f.on("submit",function(a){a.preventDefault(),a.stopPropagation(),e.find(".btn-primary").click()}),e=p.dialog(a),e.off("shown.bs.modal"),e.on("shown.bs.modal",function(){h.focus()}),i===!0&&e.modal("show"),e},p.dialog=function(a){a=h(a);var d=b(n.dialog),f=d.find(".modal-dialog"),i=d.find(".modal-body"),j=a.buttons,k="",l={onEscape:a.onEscape};if(b.fn.modal===c)throw new Error("$.fn.modal is not defined; please double check you have included the Bootstrap JavaScript library. See http://getbootstrap.com/javascript/ for more details.");if(g(j,function(a,b){k+="",l[a]=b.callback}),i.find(".bootbox-body").html(a.message),a.animate===!0&&d.addClass("fade"),a.className&&d.addClass(a.className),"large"===a.size?f.addClass("modal-lg"):"small"===a.size&&f.addClass("modal-sm"),a.title&&i.before(n.header),a.closeButton){var m=b(n.closeButton);a.title?d.find(".modal-header").prepend(m):m.css("margin-top","-10px").prependTo(i)}return a.title&&d.find(".modal-title").html(a.title),k.length&&(i.after(n.footer),d.find(".modal-footer").html(k)),d.on("hidden.bs.modal",function(a){a.target===this&&d.remove()}),d.on("shown.bs.modal",function(){d.find(".btn-primary:first").focus()}),"static"!==a.backdrop&&d.on("click.dismiss.bs.modal",function(a){d.children(".modal-backdrop").length&&(a.currentTarget=d.children(".modal-backdrop").get(0)),a.target===a.currentTarget&&d.trigger("escape.close.bb")}),d.on("escape.close.bb",function(a){l.onEscape&&e(a,d,l.onEscape)}),d.on("click",".modal-footer button",function(a){var c=b(this).data("bb-handler");e(a,d,l[c])}),d.on("click",".bootbox-close-button",function(a){e(a,d,l.onEscape)}),d.on("keyup",function(a){27===a.which&&d.trigger("escape.close.bb")}),b(a.container).append(d),d.modal({backdrop:a.backdrop?"static":!1,keyboard:!1,show:!1}),a.show&&d.modal("show"),d},p.setDefaults=function(){var a={};2===arguments.length?a[arguments[0]]=arguments[1]:a=arguments[0],b.extend(o,a)},p.hideAll=function(){return b(".bootbox").modal("hide"),p};var q={bg_BG:{OK:"Ок",CANCEL:"Отказ",CONFIRM:"Потвърждавам"},br:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Sim"},cs:{OK:"OK",CANCEL:"Zrušit",CONFIRM:"Potvrdit"},da:{OK:"OK",CANCEL:"Annuller",CONFIRM:"Accepter"},de:{OK:"OK",CANCEL:"Abbrechen",CONFIRM:"Akzeptieren"},el:{OK:"Εντάξει",CANCEL:"Ακύρωση",CONFIRM:"Επιβεβαίωση"},en:{OK:"OK",CANCEL:"Cancel",CONFIRM:"OK"},es:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Aceptar"},et:{OK:"OK",CANCEL:"Katkesta",CONFIRM:"OK"},fa:{OK:"قبول",CANCEL:"لغو",CONFIRM:"تایید"},fi:{OK:"OK",CANCEL:"Peruuta",CONFIRM:"OK"},fr:{OK:"OK",CANCEL:"Annuler",CONFIRM:"D'accord"},he:{OK:"אישור",CANCEL:"ביטול",CONFIRM:"אישור"},hu:{OK:"OK",CANCEL:"Mégsem",CONFIRM:"Megerősít"},hr:{OK:"OK",CANCEL:"Odustani",CONFIRM:"Potvrdi"},id:{OK:"OK",CANCEL:"Batal",CONFIRM:"OK"},it:{OK:"OK",CANCEL:"Annulla",CONFIRM:"Conferma"},ja:{OK:"OK",CANCEL:"キャンセル",CONFIRM:"確認"},lt:{OK:"Gerai",CANCEL:"Atšaukti",CONFIRM:"Patvirtinti"},lv:{OK:"Labi",CANCEL:"Atcelt",CONFIRM:"Apstiprināt"},nl:{OK:"OK",CANCEL:"Annuleren",CONFIRM:"Accepteren"},no:{OK:"OK",CANCEL:"Avbryt",CONFIRM:"OK"},pl:{OK:"OK",CANCEL:"Anuluj",CONFIRM:"Potwierdź"},pt:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Confirmar"},ru:{OK:"OK",CANCEL:"Отмена",CONFIRM:"Применить"},sq:{OK:"OK",CANCEL:"Anulo",CONFIRM:"Prano"},sv:{OK:"OK",CANCEL:"Avbryt",CONFIRM:"OK"},th:{OK:"ตกลง",CANCEL:"ยกเลิก",CONFIRM:"ยืนยัน"},tr:{OK:"Tamam",CANCEL:"İptal",CONFIRM:"Onayla"},zh_CN:{OK:"OK",CANCEL:"取消",CONFIRM:"确认"},zh_TW:{OK:"OK",CANCEL:"取消",CONFIRM:"確認"}};return p.addLocale=function(a,c){return b.each(["OK","CANCEL","CONFIRM"],function(a,b){if(!c[b])throw new Error("Please supply a translation for '"+b+"'")}),q[a]={OK:c.OK,CANCEL:c.CANCEL,CONFIRM:c.CONFIRM},p},p.removeLocale=function(a){return delete q[a],p},p.setLocale=function(a){return p.setDefaults("locale",a)},p.init=function(c){return a(c||b)},p});
\ No newline at end of file
diff --git a/public/vendor/bootbox/wrapper.js b/public/vendor/bootbox/wrapper.js
new file mode 100644
index 0000000000..6988c4398d
--- /dev/null
+++ b/public/vendor/bootbox/wrapper.js
@@ -0,0 +1,44 @@
+/* global bootbox */
+
+require(['translator'], function (shim) {
+ "use strict";
+
+ var translator = shim.Translator.create();
+ var dialog = bootbox.dialog;
+ bootbox.dialog = function (options) {
+ var translate = [
+ translator.translate(options.message),
+ options.title && translator.translate(options.title),
+ ].concat(Object.keys(options.buttons).map(function (key) {
+ if (/cancel|confirm|ok/.test(key)) {
+ return null;
+ }
+ return translator.translate(options.buttons[key].label).then(function (label) {
+ options.buttons[key].label = label;
+ });
+ }));
+
+ Promise.all(translate).then(function (translations) {
+ options.message = translations[0];
+ options.title = translations[1];
+
+ dialog.call(bootbox, options);
+ });
+ };
+
+ Promise.all([
+ translator.translateKey('modules:bootbox.ok', []),
+ translator.translateKey('modules:bootbox.cancel', []),
+ translator.translateKey('modules:bootbox.confirm', []),
+ ]).then(function (translations) {
+ var lang = shim.getLanguage();
+ bootbox.addLocale(lang, {
+ OK: translations[0],
+ CANCEL: translations[1],
+ CONFIRM: translations[2],
+ });
+
+ bootbox.setLocale(lang);
+ });
+});
+
diff --git a/src/meta/js.js b/src/meta/js.js
index 626fa0ecd8..05f045f283 100644
--- a/src/meta/js.js
+++ b/src/meta/js.js
@@ -25,7 +25,8 @@ module.exports = function (Meta) {
'public/vendor/jquery/textcomplete/jquery.textcomplete.js',
'public/vendor/requirejs/require.js',
'public/src/require-config.js',
- 'public/vendor/bootbox/bootbox.min.js',
+ 'public/vendor/bootbox/bootbox.js',
+ 'public/vendor/bootbox/wrapper.js',
'public/vendor/tinycon/tinycon.js',
'public/vendor/xregexp/xregexp.js',
'public/vendor/xregexp/unicode/unicode-base.js',