diff --git a/public/css/blue-twilight.css b/public/css/blue-twilight.css
index 36f0179..462dedf 100644
--- a/public/css/blue-twilight.css
+++ b/public/css/blue-twilight.css
@@ -2,10 +2,32 @@
margin-bottom: 15px;
}
+.album-expand-handle {
+ cursor: pointer;
+ margin-top: 5px;
+}
+
.card-header.card-danger {
color: #fff;
font-weight: bold;
}
+
+.photo .loading {
+ background-color: #ffffff;
+ display: none;
+ height: 100%;
+ left: 0;
+ opacity: 0.8;
+ position: absolute;
+ text-align: center;
+ top: 0;
+ width: 100%;
+ z-index: 1000;
+}
+
+.photo .loading img {
+ margin-top: 40px;
+}
html {
font-size: 14px !important;
}
@@ -27,11 +49,46 @@ textarea {
margin-top: 20px;
}
+.hidden {
+ display: none;
+}
+
.tab-content {
border: solid 1px rgb(221, 221, 221);
border-top: 0;
padding: 20px;
}
+.tether-element, .tether-element:after, .tether-element:before, .tether-element *, .tether-element *:after, .tether-element *:before {
+ box-sizing: border-box; }
+
+.tether-element {
+ position: absolute;
+ display: none; }
+ .tether-element.tether-open {
+ display: block; }
+
+.tether-element.tether-theme-basic {
+ max-width: 100%;
+ max-height: 100%; }
+ .tether-element.tether-theme-basic .tether-content {
+ border-radius: 5px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
+ font-family: inherit;
+ background: #fff;
+ color: inherit;
+ padding: 1em;
+ font-size: 1.1em;
+ line-height: 1.5em; }
+
+.tether-element, .tether-element:after, .tether-element:before, .tether-element *, .tether-element *:after, .tether-element *:before {
+ box-sizing: border-box; }
+
+.tether-element {
+ position: absolute;
+ display: none; }
+ .tether-element.tether-open {
+ display: block; }
+
/*!
* Bootstrap v4.0.0-alpha.6 (https://getbootstrap.com)
* Copyright 2011-2017 The Bootstrap Authors
diff --git a/public/css/blue-twilight.min.css b/public/css/blue-twilight.min.css
index d8acf84..d548ee7 100644
--- a/public/css/blue-twilight.min.css
+++ b/public/css/blue-twilight.min.css
@@ -1,4 +1,4 @@
-.admin-sidebar-card{margin-bottom:15px}.card-header.card-danger{color:#fff;font-weight:bold}html{font-size:14px !important}button,input,optgroup,select,textarea{cursor:pointer;font-family:inherit !important}.album-photo-cards .card{margin-bottom:15px}.container,.container-fluid{margin-top:20px}.tab-content{border:solid 1px #ddd;border-top:0;padding:20px}/*!
+.admin-sidebar-card{margin-bottom:15px}.album-expand-handle{cursor:pointer;margin-top:5px}.card-header.card-danger{color:#fff;font-weight:bold}.photo .loading{background-color:#fff;display:none;height:100%;left:0;opacity:.8;position:absolute;text-align:center;top:0;width:100%;z-index:1000}.photo .loading img{margin-top:40px}html{font-size:14px !important}button,input,optgroup,select,textarea{cursor:pointer;font-family:inherit !important}.album-photo-cards .card{margin-bottom:15px}.container,.container-fluid{margin-top:20px}.hidden{display:none}.tab-content{border:solid 1px #ddd;border-top:0;padding:20px}.tether-element,.tether-element:after,.tether-element:before,.tether-element *,.tether-element *:after,.tether-element *:before{box-sizing:border-box}.tether-element{position:absolute;display:none}.tether-element.tether-open{display:block}.tether-element.tether-theme-basic{max-width:100%;max-height:100%}.tether-element.tether-theme-basic .tether-content{border-radius:5px;box-shadow:0 2px 8px rgba(0,0,0,0.2);font-family:inherit;background:#fff;color:inherit;padding:1em;font-size:1.1em;line-height:1.5em}.tether-element,.tether-element:after,.tether-element:before,.tether-element *,.tether-element *:after,.tether-element *:before{box-sizing:border-box}.tether-element{position:absolute;display:none}.tether-element.tether-open{display:block}/*!
* Bootstrap v4.0.0-alpha.6 (https://getbootstrap.com)
* Copyright 2011-2017 The Bootstrap Authors
* Copyright 2011-2017 Twitter, Inc.
diff --git a/public/js/blue-twilight.js b/public/js/blue-twilight.js
index 4104251..1b8120d 100644
--- a/public/js/blue-twilight.js
+++ b/public/js/blue-twilight.js
@@ -1,148 +1,3 @@
-/**
- * This model is used by admin/show_album.blade.php to handle photo uploads.
- * @param album_id ID of the album the photos are being uploaded to
- * @param queue_token Unique token of the upload queue to save the photos to
- * @param language Array containing language strings
- * @param urls Array containing URLs
- * @constructor
- */
-function UploadPhotosViewModel(album_id, queue_token, language, urls) {
- this.el = '#upload-tab';
- this.data = {
- currentStatus: '',
- imagesFailed: 0,
- imagesTotal: 0,
- imagesUploaded: 0,
- isBulkUploadInProgress: false,
- isUploadInProgress: false,
- statusMessages: []
- };
- this.computed = {
- failedPercentage: function () {
- var result = 0;
-
- if (this.imagesTotal > 0)
- {
- result = (this.imagesFailed / this.imagesTotal) * 100;
- }
-
- return result.toFixed(2) + '%';
- },
- successfulPercentage: function () {
- var result = 0;
-
- if (this.imagesTotal > 0)
- {
- result = (this.imagesUploaded / this.imagesTotal) * 100;
- }
-
- return result.toFixed(2) + '%';
- }
- };
- this.methods = {
- // This method is called when an image is uploaded - regardless if it fails or not
- onUploadCompleted: function () {
- this.currentStatus = language.upload_status
- .replace(':current', (this.imagesUploaded + this.imagesFailed))
- .replace(':total', this.imagesTotal);
-
- if ((this.imagesFailed + this.imagesUploaded) >= this.imagesTotal) {
- this.isUploadInProgress = false;
-
- if (this.imagesFailed === 0 && this.imagesUploaded > 0) {
- window.location = urls.analyse;
- }
- }
- },
- // This method is called when an uploaded image fails
- onUploadFailed: function (data, file_name) {
- this.imagesFailed++;
- this.statusMessages.push({
- 'message_class': 'text-danger',
- 'message_text': language.image_failed.replace(':file_name', file_name)
- });
- this.onUploadCompleted();
- },
- // This method is called when an uploaded image succeeds
- onUploadSuccessful: function (data, file_name) {
- if (data.is_successful) {
- this.imagesUploaded++;
- // Don't add to statusMessages() array so user only sees errors
- /*self.statusMessages.push({
- 'message_class': 'text-success',
- 'message_text': language.image_uploaded.replace(':file_name', file_name)
- });*/
- this.onUploadCompleted();
- }
- else {
- this.onUploadFailed(data, file_name);
- }
- },
- uploadFile: function uploadImageFile(formObject, imageFile) {
- var self = this;
- var formData = new FormData();
- formData.append('album_id', album_id);
- formData.append('queue_token', queue_token);
- formData.append('photo[]', imageFile, imageFile.name);
-
- $.ajax(
- {
- contentType: false,
- data: formData,
- error: function (data) {
- self.onUploadFailed(data, imageFile.name);
- },
- method: $(formObject).attr('method'),
- processData: false,
- success: function (data) {
- self.onUploadSuccessful(data, imageFile.name);
- },
- url: $(formObject).attr('action')
- }
- );
- this.isUploadInProgress = true;
-
- this.currentStatus = language.upload_status
- .replace(':current', 0)
- .replace(':total', this.imagesTotal);
- },
- uploadIndividualFiles: function(event) {
- var fileSelect = $('input[type=file]', event.target);
-
- // Get the selected files
- var files = fileSelect[0].files;
-
- // Reset statistics
- this.currentStatus = '';
- this.statusMessages = [];
- this.imagesUploaded = 0;
- this.imagesFailed = 0;
- this.imagesTotal = files.length;
- this.isUploadInProgress = true;
-
- // Loop through each of the selected files and upload them individually
- for (var i = 0; i < files.length; i++)
- {
- var file = files[i];
-
- // We're only interested in image files
- if (!file.type.match('image.*'))
- {
- alert(language.not_an_image_file.replace(':file_name', file.name));
- this.onUploadFailed(null, file.name);
- continue;
- }
-
- // Upload the file
- this.uploadFile(event.target, file);
- }
-
- // Prevent standard form upload
- event.preventDefault();
- return false;
- }
- };
-}
/*!
* jQuery JavaScript Library v3.2.0
* https://jquery.com/
@@ -10388,13 +10243,992 @@ if ( !noGlobal ) {
return jQuery;
} );
-function StorageLocationViewModel()
-{
- this.el = '#storage-options';
- this.data = {
- storage_driver: 'LocalFilesystemSource'
+/**
+ * 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";
+ if (typeof define === "function" && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(["resources/assets/js/001-jquery"], factory);
+ } else if (typeof exports === "object") {
+ // Node. Does not work with strict CommonJS, but
+ // only CommonJS-like environments that support module.exports,
+ // like Node.
+ module.exports = factory(require("resources/assets/js/001-jquery"));
+ } else {
+ // 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-secondary";
+ }
+ }
+ });
+
+ 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 = {
+ 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 : "確認"
+ }
+ };
+
+ 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;
+}));
+
/*!
* Vue.js v2.2.0
* (c) 2014-2017 Evan You
@@ -19571,6 +20405,2423 @@ return Vue$3;
})));
+/*! tether 1.3.3 */
+
+(function(root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ define(factory);
+ } else if (typeof exports === 'object') {
+ module.exports = factory(require, exports, module);
+ } else {
+ root.Tether = factory();
+ }
+}(this, function(require, exports, module) {
+
+'use strict';
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+var TetherBase = undefined;
+if (typeof TetherBase === 'undefined') {
+ TetherBase = { modules: [] };
+}
+
+var zeroElement = null;
+
+// Same as native getBoundingClientRect, except it takes into account parent offsets
+// if the element lies within a nested document ( or