Browse Source

Updates to release v4.3.2 fix #634 fix #635 fix #636 fix #641 fix #642 fix #643

Kartik Visweswaran 9 years ago
parent
commit
95f055b934
43 changed files with 7286 additions and 4192 deletions
  1. 52 2
      CHANGE.md
  2. 18 2
      README.md
  3. 132 19
      css/fileinput.css
  4. 2 7
      css/fileinput.min.css
  5. 3214 2784
      js/fileinput.js
  6. 0 15
      js/fileinput.min.js
  7. 68 59
      js/locales/fileinput_locale_LANG.js
  8. 10 1
      js/locales/fileinput_locale_ar.js
  9. 69 60
      js/locales/fileinput_locale_bg.js
  10. 10 1
      js/locales/fileinput_locale_ca.js
  11. 10 1
      js/locales/fileinput_locale_cr.js
  12. 68 59
      js/locales/fileinput_locale_cz.js
  13. 68 59
      js/locales/fileinput_locale_da.js
  14. 67 58
      js/locales/fileinput_locale_de.js
  15. 68 59
      js/locales/fileinput_locale_el.js
  16. 69 60
      js/locales/fileinput_locale_es.js
  17. 70 61
      js/locales/fileinput_locale_fa.js
  18. 60 42
      js/locales/fileinput_locale_fi.js
  19. 68 59
      js/locales/fileinput_locale_fr.js
  20. 69 60
      js/locales/fileinput_locale_hu.js
  21. 10 1
      js/locales/fileinput_locale_id.js
  22. 70 61
      js/locales/fileinput_locale_it.js
  23. 10 1
      js/locales/fileinput_locale_ja.js
  24. 69 60
      js/locales/fileinput_locale_nl.js
  25. 68 59
      js/locales/fileinput_locale_pl.js
  26. 68 59
      js/locales/fileinput_locale_pt-BR.js
  27. 68 59
      js/locales/fileinput_locale_pt.js
  28. 70 61
      js/locales/fileinput_locale_ro.js
  29. 70 61
      js/locales/fileinput_locale_ru.js
  30. 68 59
      js/locales/fileinput_locale_sk.js
  31. 68 59
      js/locales/fileinput_locale_th.js
  32. 69 60
      js/locales/fileinput_locale_tr.js
  33. 70 61
      js/locales/fileinput_locale_uk.js
  34. 11 1
      js/locales/fileinput_locale_zh-TW.js
  35. 69 60
      js/locales/fileinput_locale_zh.js
  36. 812 0
      js/plugins/purify.js
  37. 0 0
      js/plugins/purify.min.js
  38. 1330 0
      js/plugins/sortable.js
  39. 2 0
      js/plugins/sortable.min.js
  40. 45 0
      js/themes/theme-fa.js
  41. 45 0
      js/themes/theme-gly.js
  42. 1 1
      nuget/Package.nuspec
  43. 1 1
      package.json

+ 52 - 2
CHANGE.md

@@ -1,9 +1,9 @@
 Change Log: `bootstrap-fileinput`
 =================================
 
-## version 4.3.2 (under development)
+## version 4.3.2
 
-**Date:** 14-May-2016
+**Date:** 22-May-2016
 
 1. (bug #595): Correct initialization of `allowedPreviewTypes`.
 2. (enh #600): Synchronize latest package on NuGet.
@@ -13,6 +13,56 @@ Change Log: `bootstrap-fileinput`
 6. (enh #618): Update German Translations.
 7. (enh #632): Find correct filename in IE9.
 8. (enh #633): New property `maxFilePreviewSize` to control preview of large size files.
+9. (enh #634): Enhance ability for PDF and HTML preview.
+    - Enhanced PDF support as PDF embedding is now possible for `initialPreview`. In addition a new template for PDF is available within `previewTemplates`.
+    - HTML Preview is enhanced with a better template. The plugin also now includes support for `DOMPurify` plugin (and available in plugins folder). This processes and cleans the HTML from XSS before previewing. This behavior can be controlled via `purifyHtml` property that defaults to `true`.
+10. (enh #635): Various preview enhancements. Previews will be revamped with various functionality:
+    - Add ability to zoom every thumbnail to a modal preview. So all types of files (images, videos, pdf, text etc) can be previewed in a larger zoom dialog window.
+    - Automatic slideshow like interface for zoom preview modal. One can navigate left or right to view previous or next content in the preview. In addition to button navigation, keyboard navigation (via left/right arrow keys) is also available.
+    - Borderless maximized mode and Full Screen mode available for preview.
+    - Auto disable the previous or next button when the first or last file/image is reached.
+    - Now `initialPreview` can be setup MORE easier without writing or returning entire markup. Thus the new functionality will enable to use built in `previewTemplates`.  
+    - A new boolean property `initialPreviewAsData` is available to control the above. If set to `true`, it will allow developers to now pass just the data within `initialPreview` (instead of complete markup) and the markup will be auto generated using `previewTemplates`.
+    - New property `initialPreviewFileType` to set the default file type for initial preview. Defaults to `image`. Must be on of the keys in `fileTypeSettings`.
+    - All the other settings can be controlled via `initialPreviewConfig`. The new properties available within `initialPreviewConfig` are:
+       - `type`: Override `initialPreviewFileType` at global level and set a separate type for each file in the initial preview.
+       - `previewAsData`: boolean property to override the `initialPreviewAsData` setting at global level
+    - New zoom preview control buttons:
+       - `prev`
+       - `next`
+       - `fullscreen`
+       - `borderless`
+       - `toggleheader`
+       - `close`
+    - The other new settings to control zoomed preview:
+        - `previewZoomSettings`: Will allow to set the CSS style (e.g. width, height and other CSS style settings) for each zoomed content type (i.e. `image`, `pdf`, `video` etc.).
+        - `previewZoomButtonIcons`: Ability to set the labels for previous, next, fullscreen, borderless, and close buttons.
+        - `previewZoomButtonTitles`: Ability to set the titles for previous, next,  fullscreen, borderless, and close buttons.
+        - `previewZoomButtonClasses`:  Ability to set the CSS classes for previous, next,  fullscreen, borderless, and close buttons.
+    - Modifications to all language locales JS for accomodating new translations
+11. (enh #636): File action enhancements.
+    - Zoom and Drag buttons will be shown as an additional file action buttons in addition to `upload` and `remove`
+    - New boolean properties `showZoom`, `showDrag`, `showRemove`, `showUpload` are now added to `fileActionSettings` to control display of these buttons
+    - New properties `zoomIcon`, `zoomClass`, `zoomTitle` are available within `fileActionSettings` for controlling the zoom button styles and display.
+    - New properties `dragIcon`, `dragClass`, `dragTitle` are available within `fileActionSettings` for controlling the drag indicator styles and display.
+    - New properties `actionZoom` and `actionDrag` are available within `layoutTemplates` to configure the markup of the zoom and drag buttons.
+12. (enh #639): Add ability to just require package in nodejs
+13. (enh #640): Ability to theme and provide font awesome theme. New property `theme` added.
+14. (enh #641): Wrap readFile(index + 1) in a function to prevent 'unsafe-eval' blocking with CSP.
+15. (enh #642): Reorganize JS code into proper folders. Following folders will be added/maintained
+    - `locales`: all translation JS files will be located here
+    - `themes`: all theme JS files will be located here
+    - `plugins`: third party JS plugins that will be used to work with bootstrap-fileinput
+16. (enh #643):Implement rearranging / sorting functionality for initial preview. 
+    - Add ability to rearrange and sort thumbnails by drag & drop. This feature will use the [Sortable plugin](https://github.com/RubaXa/Sortable) which will be included in the `js/plugins` folder.
+    - This feature will be available only for **initial preview thumbnails** for both ajax and form uploads.
+    - New property for drag indicator and drag behavior configurations will be included in `fileActionSettings`:
+        - `showDrag`
+        - `dragIcon`
+        - `dragClass`
+        - `dragTitle`
+        - `dragSettings`
+    - New template `actionDrag` will be available within `layoutTemplates` to configure your drag indicator markup.
 
 ## version 4.3.1
 

+ 18 - 2
README.md

@@ -60,18 +60,34 @@ Step 1: Load the following assets in your header.
 <!-- canvas-to-blob.min.js is only needed if you wish to resize images before upload.
      This must be loaded before fileinput.min.js -->
 <script src="path/to/js/plugins/canvas-to-blob.min.js" type="text/javascript"></script>
+<!-- sortable.min.js is only needed if you wish to sort / rearrange files in initial preview.
+     This must be loaded before fileinput.min.js -->
+<script src="path/to/js/plugins/sortable.min.js" type="text/javascript"></script>
+<!-- purify.min.js is only needed if you wish to purify HTML content in your preview for HTML files.
+     This must be loaded before fileinput.min.js -->
+<script src="path/to/js/plugins/purify.min.js" type="text/javascript"></script>
+<!-- the main fileinput plugin file -->
 <script src="path/to/js/fileinput.min.js"></script>
 <!-- bootstrap.js below is only needed if you wish to the feature of viewing details
      of text file preview via modal dialog -->
 <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js" type="text/javascript"></script>
+<!-- optionally if you need a theme like font awesome theme you can include 
+    it as mentioned below -->
+<script src="path/to/js/fileinput_theme_fa.js"></script>
 <!-- optionally if you need translation for your language then include 
     locale file as mentioned below -->
 <script src="path/to/js/fileinput_locale_<lang>.js"></script>
 ```
 
-If you noticed, you need to load the `jquery.min.js` and `bootstrap.min.css` in addition to the `fileinput.min.css` and `fileinput.min.js`. The locale file `fileinput_locale_<lang>.js` can be optionally included for translating for your language if needed.
+If you noticed, you need to load the `jquery.min.js` and `bootstrap.min.css` in addition to the `fileinput.min.css` and `fileinput.min.js`. The theme file `fileinput_locale_fa.js` can be optionally included for the font awesome icons styling. The locale file `fileinput_locale_<lang>.js` can be optionally included for translating for your language if needed.
+
+OTHER PLUGINS: 
+-------------
+- The `canvas-to-blob.min.js` file is the source for the [JavaScript-Canvas-to-Blob plugin by blueimp](https://github.com/blueimp/JavaScript-Canvas-to-Blob). It is required to be loaded before `fileinput.min.js` if you wish to use the image resize feature of the **bootstrap-fileinput** plugin. 
+- The `sortable.min.js` file is the source for the [Sortable plugin by rubaxa](https://github.com/rubaxa/Sortable). It is required to be loaded before `fileinput.min.js` if you wish to sort the thumbnails in the initial preview.
+- The `purify.min.js` file is the source for the [DomPurify plugin by cure53](https://github.com/cure53/DOMPurify). It is required to be loaded before `fileinput.min.js` if you wish to purify your HTML for HTML content preview.
 
-NOTE: The `canvas-to-blob.min.js` file is the source for the [JavaScript-Canvas-to-Blob plugin by blueimp](https://github.com/blueimp/JavaScript-Canvas-to-Blob). It is required to be loaded before `fileinput.js` if you wish to use the image resize feature of the **bootstrap-fileinput** plugin. For ease of access, the plugin source for JavaScript-Canvas-to-Blob is included in the `js/plugins` folder of this project repository.
+For ease of access, the sources for the above plugins are included in the `js/plugins` folder of this project repository.
 
 Step 2: Initialize the plugin on your page. For example,
 

+ 132 - 19
css/fileinput.css

@@ -1,13 +1,12 @@
 /*!
- * @copyright Copyright &copy; Kartik Visweswaran, Krajee.com, 2014 - 2015
- * @package bootstrap-fileinput
- * @version 4.3.2
+ * bootstrap-fileinput v4.3.2
+ * http://plugins.krajee.com/file-input
  *
- * File input styling for Bootstrap 3.0
- * Built for Yii Framework 2.0
  * Author: Kartik Visweswaran
- * Year: 2015
- * For more Yii related demos visit http://demos.krajee.com
+ * Copyright: 2014 - 2016, Kartik Visweswaran, Krajee.com
+ *
+ * Licensed under the BSD 3-Clause
+ * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md
  */
 .file-loading {
     top: 0;
@@ -55,7 +54,7 @@
     height: 25px;
 }
 
-.file-preview-detail-modal {
+.file-zoom-dialog {
     text-align: left;
 }
 
@@ -92,6 +91,7 @@
 }
 
 .file-preview-frame {
+    position: relative;
     display: table;
     margin: 8px;
     height: 160px;
@@ -108,25 +108,33 @@
 }
 
 .file-preview-image {
-    height: 160px;
     vertical-align: middle;
 }
 
 .file-preview-text {
-    text-align: left;
-    width: 160px;
-    margin-bottom: 2px;
+    display: block;
     color: #428bca;
-    background: #fff;
-    overflow-x: hidden;
+    border: 1px solid #ddd;
+    font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
+    outline: none;
+    padding: 8px;
+    resize: none;
+}
+
+.file-preview-html {
+    border: 1px solid #ddd;
+    padding: 8px;
+    overflow: auto;
+}
+
+.file-zoom-dialog .file-preview-text {
+    font-size: 1.2em;
 }
 
 .file-preview-other {
     display: table-cell;
     text-align: center;
     vertical-align: middle;
-    width: 160px;
-    height: 160px;
     border: 2px solid #999;
     border-radius: 30px;
 }
@@ -170,7 +178,7 @@
 }
 
 .file-upload-indicator {
-    padding-top: 2px;
+    display: inline;
     cursor: default;
     opacity: 0.8;
     width: 60%;
@@ -229,6 +237,10 @@
     opacity: 0.65;
 }
 
+.file-thumb-progress {
+    height: 10px;
+}
+
 .file-thumb-progress .progress, .file-thumb-progress .progress-bar {
     height: 10px;
     font-size: 9px;
@@ -246,8 +258,109 @@
     right: 0;
 }
 
+.file-zoom-fullscreen.modal {
+    position: fixed;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    left: 0;
+}
+
+.file-zoom-fullscreen .modal-dialog {
+    position: fixed;
+    margin: 0;
+    width: 100%;
+    height: 100%;
+    padding: 0;
+}
+
+.file-zoom-fullscreen .modal-content {
+    border-radius: 0;
+    box-shadow: none;
+}
+
+.file-zoom-fullscreen .modal-body {
+    overflow-y: auto;
+}
+
+.file-zoom-dialog .modal-body {
+    position: relative !important;
+}
+
+.file-zoom-dialog .btn-navigate {
+    position: absolute;
+    padding: 0;
+    margin: 0;
+    background: transparent;
+    text-decoration: none;
+    outline: none;
+    opacity: 0.7;
+    top: 45%;
+    font-size: 4em;
+    color: #1c94c4;
+}
+
+.file-zoom-dialog .floating-buttons {
+    position: absolute;
+    top: 5px;
+    right: 10px;
+}
+
+.floating-buttons, .floating-buttons .btn {
+    z-index: 3000;
+}
+
+.file-zoom-dialog .kv-zoom-actions .btn,
+.floating-buttons .btn {
+    margin-left: 3px;
+}
+
+.file-zoom-dialog .btn-navigate:not([disabled]):hover,
+.file-zoom-dialog .btn-navigate:not([disabled]):focus {
+    outline: none;
+    box-shadow: none;
+    opacity: 0.5;
+}
+
+.file-zoom-dialog .btn-navigate[disabled] {
+    opacity: 0.3;
+}
+
+.file-zoom-dialog .btn-prev {
+    left: 1px;
+}
+
+.file-zoom-dialog .btn-next {
+    right: 1px;
+}
+
+.file-drag-handle {
+    display: inline;
+    margin-right: 2px;
+    font-size: 16px;
+    cursor: move;
+    cursor: -webkit-grabbing;
+}
+
+.file-drag-handle:hover {
+    opacity: 0.7;
+}
+
+.file-zoom-content {
+    height: 480px;
+    text-align: center;
+}
+
+.file-preview-initial.sortable-chosen {
+    background-color: #d9edf7;
+}
+
+.file-preview-frame.sortable-ghost {
+    background-color: #eee;
+}
+
 /* IE 10 fix */
 .btn-file ::-ms-browse {
-    width:100%;
-    height:100%;
+    width: 100%;
+    height: 100%;
 }

File diff suppressed because it is too large
+ 2 - 7
css/fileinput.min.css


+ 3214 - 2784
js/fileinput.js

@@ -1,2784 +1,3214 @@
-/*!
- * @copyright Copyright &copy; Kartik Visweswaran, Krajee.com, 2014 - 2015
- * @version 4.3.2
- *
- * File input styled for Bootstrap 3.0 that utilizes HTML5 File Input's advanced features including the FileReader API.
- *
- * The plugin drastically enhances the HTML file input to preview multiple files on the client before upload. In
- * addition it provides the ability to preview content of images, text, videos, audio, html, flash and other objects.
- * It also offers the ability to upload and delete files using AJAX, and add files in batches (i.e. preview, append,
- * or remove before upload).
- *
- * Author: Kartik Visweswaran
- * Copyright: 2015, Kartik Visweswaran, Krajee.com
- * For more JQuery plugins visit http://plugins.krajee.com
- * For more Yii related demos visit http://demos.krajee.com
- */
-(function (factory) {
-    "use strict";
-    if (typeof define === 'function' && define.amd) { // jshint ignore:line
-        // AMD. Register as an anonymous module.
-        define(['jquery'], factory); // jshint ignore:line
-    } else { // noinspection JSUnresolvedVariable
-        if (typeof module === 'object' && module.exports) { // jshint ignore:line
-            // Node/CommonJS
-            // noinspection JSUnresolvedVariable
-            module.exports = factory(require('jquery')); // jshint ignore:line
-        } else {
-            // Browser globals
-            factory(window.jQuery);
-        }
-    }
-}(function ($) {
-    "use strict";
-
-    $.fn.fileinputLocales = {};
-
-    var NAMESPACE, objUrl, compare, isIE, isEdge, handler, previewCache, getNum, hasFileAPISupport, hasDragDropSupport,
-        hasFileUploadSupport, addCss, STYLE_SETTING, OBJECT_PARAMS, DEFAULT_PREVIEW, defaultFileActionSettings, tMain1,
-        tMain2, tPreview, tIcon, tClose, tCaption, tBtnDefault, tBtnLink, tBtnBrowse, tModal, tProgress, tFooter,
-        tActions, tActionDelete, tActionUpload, tZoom, tGeneric, tHtml, tImage, tText, tVideo, tAudio, tFlash, tObject,
-        tOther, defaultLayoutTemplates, defaultPreviewTemplates, defaultPreviewTypes, defaultPreviewSettings, FileInput,
-        defaultFileTypeSettings, isEmpty, isArray, isSet, getElement, uniqId, htmlEncode, replaceTags, cleanMemory,
-        findFileName;
-
-    NAMESPACE = '.fileinput';
-    //noinspection JSUnresolvedVariable
-    objUrl = window.URL || window.webkitURL;
-    compare = function (input, str, exact) {
-        return input !== undefined && (exact ? input === str : input.match(str));
-    };
-    isIE = function (ver) {
-        // check for IE versions < 11
-        if (navigator.appName !== 'Microsoft Internet Explorer') {
-            return false;
-        }
-        if (ver === 10) {
-            return new RegExp('msie\\s' + ver, 'i').test(navigator.userAgent);
-        }
-        var div = document.createElement("div"), status;
-        div.innerHTML = "<!--[if IE " + ver + "]> <i></i> <![endif]-->";
-        status = div.getElementsByTagName("i").length;
-        document.body.appendChild(div);
-        div.parentNode.removeChild(div);
-        return status;
-    };
-    isEdge = function () {
-        return new RegExp('Edge\/[0-9]+', 'i').test(navigator.userAgent);
-    };
-    handler = function ($el, event, callback, skipNS) {
-        var ev = skipNS ? event : event.split(' ').join(NAMESPACE + ' ') + NAMESPACE;
-        $el.off(ev).on(ev, callback);
-    };
-    previewCache = {
-        data: {},
-        init: function (obj) {
-            var content = obj.initialPreview, id = obj.id;
-            if (content.length > 0 && !isArray(content)) {
-                content = content.split(obj.initialPreviewDelimiter);
-            }
-            previewCache.data[id] = {
-                content: content,
-                config: obj.initialPreviewConfig,
-                tags: obj.initialPreviewThumbTags,
-                delimiter: obj.initialPreviewDelimiter,
-                template: obj.previewGenericTemplate,
-                msg: function (n) {
-                    return obj._getMsgSelected(n);
-                },
-                initId: obj.previewInitId,
-                footer: obj._getLayoutTemplate('footer').replace(/\{progress}/g, obj._renderThumbProgress()),
-                isDelete: obj.initialPreviewShowDelete,
-                caption: obj.initialCaption,
-                actions: function (showUpload, showDelete, disabled, url, key) {
-                    return obj._renderFileActions(showUpload, showDelete, disabled, url, key);
-                }
-            };
-        },
-        fetch: function (id) {
-            return previewCache.data[id].content.filter(function (n) {
-                return n !== null;
-            });
-        },
-        count: function (id, all) {
-            return !!previewCache.data[id] && !!previewCache.data[id].content ?
-                (all ? previewCache.data[id].content.length : previewCache.fetch(id).length) : 0;
-        },
-        get: function (id, i, isDisabled) {
-            var ind = 'init_' + i, data = previewCache.data[id], config = data.config[i],
-                previewId = data.initId + '-' + ind, out, $tmp, frameClass = ' file-preview-initial';
-            /** @namespace config.frameClass */
-            /** @namespace config.frameAttr */
-            isDisabled = isDisabled === undefined ? true : isDisabled;
-            if (data.content[i] === null) {
-                return '';
-            }
-            if (!isEmpty(config) && !isEmpty(config.frameClass)) {
-                frameClass += ' ' + config.frameClass;
-            }
-            out = data.template
-                .replace(/\{previewId}/g, previewId)
-                .replace(/\{frameClass}/g, frameClass)
-                .replace(/\{fileindex}/g, ind)
-                .replace(/\{content}/g, data.content[i])
-                .replace(/\{footer}/g, previewCache.footer(id, i, isDisabled));
-            if (data.tags.length && data.tags[i]) {
-                out = replaceTags(out, data.tags[i]);
-            }
-            if (!isEmpty(config) && !isEmpty(config.frameAttr)) {
-                $tmp = $(document.createElement('div')).html(out);
-                $tmp.find('.file-preview-initial').attr(config.frameAttr);
-                out = $tmp.html();
-                $tmp.remove();
-            }
-            return out;
-        },
-        add: function (id, content, config, tags, append) {
-            var data = $.extend(true, {}, previewCache.data[id]), index;
-            if (!isArray(content)) {
-                content = content.split(data.delimiter);
-            }
-            if (append) {
-                index = data.content.push(content) - 1;
-                data.config[index] = config;
-                data.tags[index] = tags;
-            } else {
-                index = content.length - 1;
-                data.content = content;
-                data.config = config;
-                data.tags = tags;
-            }
-            previewCache.data[id] = data;
-            return index;
-        },
-        set: function (id, content, config, tags, append) {
-            var data = $.extend(true, {}, previewCache.data[id]), i, chk;
-            if (!content || !content.length) {
-                return;
-            }
-            if (!isArray(content)) {
-                content = content.split(data.delimiter);
-            }
-            chk = content.filter(function (n) {
-                return n !== null;
-            });
-            if (!chk.length) {
-                return;
-            }
-            if (data.content === undefined) {
-                data.content = [];
-            }
-            if (data.config === undefined) {
-                data.config = [];
-            }
-            if (data.tags === undefined) {
-                data.tags = [];
-            }
-            if (append) {
-                for (i = 0; i < content.length; i++) {
-                    if (content[i]) {
-                        data.content.push(content[i]);
-                    }
-                }
-                for (i = 0; i < config.length; i++) {
-                    if (config[i]) {
-                        data.config.push(config[i]);
-                    }
-                }
-                for (i = 0; i < tags.length; i++) {
-                    if (tags[i]) {
-                        data.tags.push(tags[i]);
-                    }
-                }
-            } else {
-                data.content = content;
-                data.config = config;
-                data.tags = tags;
-            }
-            previewCache.data[id] = data;
-        },
-        unset: function (id, index) {
-            var chk = previewCache.count(id);
-            if (!chk) {
-                return;
-            }
-            if (chk === 1) {
-                previewCache.data[id].content = [];
-                previewCache.data[id].config = [];
-                previewCache.data[id].tags = [];
-                return;
-            }
-            previewCache.data[id].content[index] = null;
-            previewCache.data[id].config[index] = null;
-            previewCache.data[id].tags[index] = null;
-        },
-        out: function (id) {
-            var html = '', data = previewCache.data[id], caption, len = previewCache.count(id, true);
-            if (len === 0) {
-                return {content: '', caption: ''};
-            }
-            for (var i = 0; i < len; i++) {
-                html += previewCache.get(id, i);
-            }
-            caption = data.msg(previewCache.count(id));
-            return {content: html, caption: caption};
-        },
-        footer: function (id, i, isDisabled) {
-            var data = previewCache.data[id];
-            isDisabled = isDisabled === undefined ? true : isDisabled;
-            if (data.config.length === 0 || isEmpty(data.config[i])) {
-                return '';
-            }
-            var config = data.config[i],
-                caption = isSet('caption', config) ? config.caption : '',
-                width = isSet('width', config) ? config.width : 'auto',
-                url = isSet('url', config) ? config.url : false,
-                key = isSet('key', config) ? config.key : null,
-                disabled = (url === false) && isDisabled,
-                actions = data.isDelete ? data.actions(false, true, disabled, url, key) : '',
-                footer = data.footer.replace(/\{actions}/g, actions);
-            return footer.replace(/\{caption}/g, caption)
-                .replace(/\{width}/g, width)
-                .replace(/\{indicator}/g, '')
-                .replace(/\{indicatorTitle}/g, '');
-        }
-    };
-    getNum = function (num, def) {
-        def = def || 0;
-        if (typeof num === "number") {
-            return num;
-        }
-        if (typeof num === "string") {
-            num = parseFloat(num);
-        }
-        return isNaN(num) ? def : num;
-    };
-    hasFileAPISupport = function () {
-        return !!(window.File && window.FileReader);
-    };
-    hasDragDropSupport = function () {
-        var div = document.createElement('div');
-        /** @namespace div.draggable */
-        /** @namespace div.ondragstart */
-        /** @namespace div.ondrop */
-        return !isIE(9) && !isEdge() && // Fix for MS Edge drag & drop support bug
-            (div.draggable !== undefined || (div.ondragstart !== undefined && div.ondrop !== undefined));
-    };
-    hasFileUploadSupport = function () {
-        return hasFileAPISupport() && window.FormData;
-    };
-    addCss = function ($el, css) {
-        $el.removeClass(css).addClass(css);
-    };
-    STYLE_SETTING = 'style="width:{width};height:{height};"';
-    OBJECT_PARAMS = '      <param name="controller" value="true" />\n' +
-        '      <param name="allowFullScreen" value="true" />\n' +
-        '      <param name="allowScriptAccess" value="always" />\n' +
-        '      <param name="autoPlay" value="false" />\n' +
-        '      <param name="autoStart" value="false" />\n' +
-        '      <param name="quality" value="high" />\n';
-    DEFAULT_PREVIEW = '<div class="file-preview-other">\n' +
-        '   <span class="{previewFileIconClass}">{previewFileIcon}</span>\n' +
-        '</div>';
-    defaultFileActionSettings = {
-        removeIcon: '<i class="glyphicon glyphicon-trash text-danger"></i>',
-        removeClass: 'btn btn-xs btn-default',
-        removeTitle: 'Remove file',
-        uploadIcon: '<i class="glyphicon glyphicon-upload text-info"></i>',
-        uploadClass: 'btn btn-xs btn-default',
-        uploadTitle: 'Upload file',
-        indicatorNew: '<i class="glyphicon glyphicon-hand-down text-warning"></i>',
-        indicatorSuccess: '<i class="glyphicon glyphicon-ok-sign text-success"></i>',
-        indicatorError: '<i class="glyphicon glyphicon-exclamation-sign text-danger"></i>',
-        indicatorLoading: '<i class="glyphicon glyphicon-hand-up text-muted"></i>',
-        indicatorNewTitle: 'Not uploaded yet',
-        indicatorSuccessTitle: 'Uploaded',
-        indicatorErrorTitle: 'Upload Error',
-        indicatorLoadingTitle: 'Uploading ...'
-    };
-    tMain1 = '{preview}\n' +
-        '<div class="kv-upload-progress hide"></div>\n' +
-        '<div class="input-group {class}">\n' +
-        '   {caption}\n' +
-        '   <div class="input-group-btn">\n' +
-        '       {remove}\n' +
-        '       {cancel}\n' +
-        '       {upload}\n' +
-        '       {browse}\n' +
-        '   </div>\n' +
-        '</div>';
-    tMain2 = '{preview}\n<div class="kv-upload-progress hide"></div>\n{remove}\n{cancel}\n{upload}\n{browse}\n';
-    tPreview = '<div class="file-preview {class}">\n' +
-        '    {close}' +
-        '    <div class="{dropClass}">\n' +
-        '    <div class="file-preview-thumbnails">\n' +
-        '    </div>\n' +
-        '    <div class="clearfix"></div>' +
-        '    <div class="file-preview-status text-center text-success"></div>\n' +
-        '    <div class="kv-fileinput-error"></div>\n' +
-        '    </div>\n' +
-        '</div>';
-    tClose = '<div class="close fileinput-remove">&times;</div>\n';
-    tIcon = '<span class="glyphicon glyphicon-file kv-caption-icon"></span>';
-    tCaption = '<div tabindex="500" class="form-control file-caption {class}">\n' +
-        '   <div class="file-caption-name"></div>\n' +
-        '</div>\n';
-    //noinspection HtmlUnknownAttribute
-    tBtnDefault = '<button type="{type}" tabindex="500" title="{title}" class="{css}" {status}>{icon}{label}</button>';
-    tBtnLink = '<a href="{href}" tabindex="500" title="{title}" class="{css}" {status}>{icon}{label}</a>';
-    tBtnBrowse = '<div tabindex="500" class="{css}" {status}>{icon}{label}</div>';
-    tModal = '<div id="{id}" class="file-preview-detail-modal modal fade" tabindex="-1">\n' +
-        '  <div class="modal-dialog modal-lg">\n' +
-        '    <div class="modal-content">\n' +
-        '      <div class="modal-header">\n' +
-        '        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>\n' +
-        '        <h3 class="modal-title">{heading} <small>{title}</small></h3>\n' +
-        '      </div>\n' +
-        '      <div class="modal-body">\n' +
-        '           <pre>{body}</pre>\n' +
-        '      </div>\n' +
-        '    </div>\n' +
-        '  </div>\n' +
-        '</div>';
-    tProgress = '<div class="progress">\n' +
-        '    <div class="{class}" role="progressbar"' +
-        ' aria-valuenow="{percent}" aria-valuemin="0" aria-valuemax="100" style="width:{percent}%;">\n' +
-        '        {percent}%\n' +
-        '     </div>\n' +
-        '</div>';
-    tFooter = '<div class="file-thumbnail-footer">\n' +
-        '    <div class="file-footer-caption" title="{caption}">{caption}</div>\n' +
-        '    {progress} {actions}\n' +
-        '</div>';
-    tActions = '<div class="file-actions">\n' +
-        '    <div class="file-footer-buttons">\n' +
-        '        {upload}{delete}{other}' +
-        '    </div>\n' +
-        '    <div class="file-upload-indicator" title="{indicatorTitle}">{indicator}</div>\n' +
-        '    <div class="clearfix"></div>\n' +
-        '</div>';
-    tActionDelete = '<button type="button" class="kv-file-remove {removeClass}" ' +
-        'title="{removeTitle}" {dataUrl}{dataKey}>{removeIcon}</button>\n';
-    tActionUpload = '<button type="button" class="kv-file-upload {uploadClass}" title="{uploadTitle}">' +
-        '   {uploadIcon}\n</button>\n';
-    tZoom = '<button type="button" class="btn btn-default btn-xs btn-block" title="{zoomTitle}: {caption}" onclick="{dialog}">\n' +
-        '   {zoomInd}\n' +
-        '</button>\n';
-    tGeneric = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}">\n' +
-        '   {content}\n' +
-        '   {footer}\n' +
-        '</div>\n';
-    tHtml = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}">\n' +
-        '    <object class="file-object" data="{data}" type="{type}" width="{width}" height="{height}">\n' +
-        '       ' + DEFAULT_PREVIEW + '\n' +
-        '    </object>\n' +
-        '   {footer}\n' +
-        '</div>';
-    tImage = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}">\n' +
-        '   <img src="{data}" class="file-preview-image" title="{caption}" alt="{caption}" ' + STYLE_SETTING + '>\n' +
-        '   {footer}\n' +
-        '</div>\n';
-    tText = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}">\n' +
-        '   <pre class="file-preview-text" title="{caption}" ' + STYLE_SETTING + '>{data}</pre>\n' +
-        '   {zoom}\n' +
-        '   {footer}\n' +
-        '</div>';
-    tVideo = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}"' +
-        ' title="{caption}" ' + STYLE_SETTING + '>\n' +
-        '   <video width="{width}" height="{height}" controls>\n' +
-        '       <source src="{data}" type="{type}">\n' +
-        '       ' + DEFAULT_PREVIEW + '\n' +
-        '   </video>\n' +
-        '   {footer}\n' +
-        '</div>\n';
-    tAudio = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}"' +
-        ' title="{caption}" ' + STYLE_SETTING + '>\n' +
-        '   <audio controls>\n' +
-        '       <source src="' + '{data}' + '" type="{type}">\n' +
-        '       ' + DEFAULT_PREVIEW + '\n' +
-        '   </audio>\n' +
-        '   {footer}\n' +
-        '</div>';
-    tFlash = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}"' +
-        ' title="{caption}" ' + STYLE_SETTING + '>\n' +
-        '   <object class="file-object" type="application/x-shockwave-flash" width="{width}" height="{height}" data="{data}">\n' +
-        OBJECT_PARAMS + '       ' + DEFAULT_PREVIEW + '\n' +
-        '   </object>\n' +
-        '   {footer}\n' +
-        '</div>\n';
-    tObject = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}"' +
-        ' title="{caption}" ' + STYLE_SETTING + '>\n' +
-        '   <object class="file-object" data="{data}" type="{type}" width="{width}" height="{height}">\n' +
-        '       <param name="movie" value="{caption}" />\n' +
-        OBJECT_PARAMS + '         ' + DEFAULT_PREVIEW + '\n' +
-        '   </object>\n' +
-        '   {footer}\n' +
-        '</div>';
-    tOther = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}"' +
-        ' title="{caption}" ' + STYLE_SETTING + '>\n' +
-        '   <div class="file-preview-other-frame">\n' +
-        '   ' + DEFAULT_PREVIEW + '\n' +
-        '   </div>\n' +
-        '   <div class="file-preview-other-footer">{footer}</div>\n' +
-        '</div>';
-    defaultLayoutTemplates = {
-        main1: tMain1,
-        main2: tMain2,
-        preview: tPreview,
-        close: tClose,
-        zoom: tZoom,
-        icon: tIcon,
-        caption: tCaption,
-        modal: tModal,
-        progress: tProgress,
-        footer: tFooter,
-        actions: tActions,
-        actionDelete: tActionDelete,
-        actionUpload: tActionUpload,
-        btnDefault: tBtnDefault,
-        btnLink: tBtnLink,
-        btnBrowse: tBtnBrowse
-    };
-    defaultPreviewTemplates = {
-        generic: tGeneric,
-        html: tHtml,
-        image: tImage,
-        text: tText,
-        video: tVideo,
-        audio: tAudio,
-        flash: tFlash,
-        object: tObject,
-        other: tOther
-    };
-    defaultPreviewTypes = ['image', 'html', 'text', 'video', 'audio', 'flash', 'object'];
-    defaultPreviewSettings = {
-        image: {width: "auto", height: "160px"},
-        html: {width: "213px", height: "160px"},
-        text: {width: "160px", height: "136px"},
-        video: {width: "213px", height: "160px"},
-        audio: {width: "213px", height: "80px"},
-        flash: {width: "213px", height: "160px"},
-        object: {width: "160px", height: "160px"},
-        other: {width: "160px", height: "160px"}
-    };
-    defaultFileTypeSettings = {
-        image: function (vType, vName) {
-            return compare(vType, 'image.*') || compare(vName, /\.(gif|png|jpe?g)$/i);
-        },
-        html: function (vType, vName) {
-            return compare(vType, 'text/html') || compare(vName, /\.(htm|html)$/i);
-        },
-        text: function (vType, vName) {
-            return compare(vType, 'text.*') || compare(vType, /\.(xml|javascript)$/i) ||
-                compare(vName, /\.(txt|md|csv|nfo|ini|json|php|js|css)$/i);
-        },
-        video: function (vType, vName) {
-            return compare(vType, 'video.*') && (compare(vType, /(ogg|mp4|mp?g|webm|3gp)$/i) ||
-                compare(vName, /\.(og?|mp4|webm|mp?g|3gp)$/i));
-        },
-        audio: function (vType, vName) {
-            return compare(vType, 'audio.*') && (compare(vType, /(ogg|mp3|mp?g|wav)$/i) ||
-                compare(vName, /\.(og?|mp3|mp?g|wav)$/i));
-        },
-        flash: function (vType, vName) {
-            return compare(vType, 'application/x-shockwave-flash', true) || compare(vName, /\.(swf)$/i);
-        },
-        object: function (vType, vName) {
-            return compare(vType, 'application/pdf', true) || compare(vName, /\.(pdf)$/i);
-        },
-        other: function () {
-            return true;
-        }
-    };
-    isEmpty = function (value, trim) {
-        return value === undefined || value === null || value.length === 0 || (trim && $.trim(value) === '');
-    };
-    isArray = function (a) {
-        return Array.isArray(a) || Object.prototype.toString.call(a) === '[object Array]';
-    };
-    isSet = function (needle, haystack) {
-        return (typeof haystack === 'object' && needle in haystack);
-    };
-    getElement = function (options, param, value) {
-        return (isEmpty(options) || isEmpty(options[param])) ? value : $(options[param]);
-    };
-    uniqId = function () {
-        return Math.round(new Date().getTime() + (Math.random() * 100));
-    };
-    htmlEncode = function (str) {
-        return str.replace(/&/g, '&amp;')
-            .replace(/</g, '&lt;')
-            .replace(/>/g, '&gt;')
-            .replace(/"/g, '&quot;')
-            .replace(/'/g, '&apos;');
-    };
-    replaceTags = function (str, tags) {
-        var out = str;
-        if (!tags) {
-            return out;
-        }
-        $.each(tags, function (key, value) {
-            if (typeof value === "function") {
-                value = value();
-            }
-            out = out.split(key).join(value);
-        });
-        return out;
-    };
-    cleanMemory = function ($thumb) {
-        var data = $thumb.is('img') ? $thumb.attr('src') : $thumb.find('source').attr('src');
-        /** @namespace objUrl.revokeObjectURL */
-        objUrl.revokeObjectURL(data);
-    };
-    findFileName = function (filePath) {
-        var sepIndex = filePath.lastIndexOf('/');
-        if (sepIndex === -1) {
-            sepIndex = filePath.lastIndexOf('\\');
-        }
-        return filePath.split(filePath.substring(sepIndex, sepIndex + 1)).pop();
-    };
-    FileInput = function (element, options) {
-        var self = this;
-        self.$element = $(element);
-        if (!self._validate()) {
-            return;
-        }
-        self.isPreviewable = hasFileAPISupport();
-        self.isIE9 = isIE(9);
-        self.isIE10 = isIE(10);
-        if (self.isPreviewable || self.isIE9) {
-            self._init(options);
-            self._listen();
-        } else {
-            self.$element.removeClass('file-loading');
-        }
-    };
-
-    FileInput.prototype = {
-        constructor: FileInput,
-        _init: function (options) {
-            var self = this, $el = self.$element, t;
-            $.each(options, function (key, value) {
-                switch (key) {
-                    case 'minFileCount':
-                    case 'maxFileCount':
-                    case 'maxFileSize':
-                        self[key] = getNum(value);
-                        break;
-                    default:
-                        self[key] = value;
-                        break;
-                }
-            });
-            if (isEmpty(self.allowedPreviewTypes)) {
-                self.allowedPreviewTypes = defaultPreviewTypes;
-            }
-            self.fileInputCleared = false;
-            self.fileBatchCompleted = true;
-            if (!self.isPreviewable) {
-                self.showPreview = false;
-            }
-            self.uploadFileAttr = !isEmpty($el.attr('name')) ? $el.attr('name') : 'file_data';
-            self.reader = null;
-            self.formdata = {};
-            self.clearStack();
-            self.uploadCount = 0;
-            self.uploadStatus = {};
-            self.uploadLog = [];
-            self.uploadAsyncCount = 0;
-            self.loadedImages = [];
-            self.totalImagesCount = 0;
-            self.ajaxRequests = [];
-            self.isError = false;
-            self.ajaxAborted = false;
-            self.cancelling = false;
-            t = self._getLayoutTemplate('progress');
-            self.progressTemplate = t.replace('{class}', self.progressClass);
-            self.progressCompleteTemplate = t.replace('{class}', self.progressCompleteClass);
-            self.progressErrorTemplate = t.replace('{class}', self.progressErrorClass);
-            self.dropZoneEnabled = hasDragDropSupport() && self.dropZoneEnabled;
-            self.isDisabled = self.$element.attr('disabled') || self.$element.attr('readonly');
-            self.isUploadable = hasFileUploadSupport() && !isEmpty(self.uploadUrl);
-            self.slug = typeof options.slugCallback === "function" ? options.slugCallback : self._slugDefault;
-            self.mainTemplate = self.showCaption ? self._getLayoutTemplate('main1') : self._getLayoutTemplate('main2');
-            self.captionTemplate = self._getLayoutTemplate('caption');
-            self.previewGenericTemplate = self._getPreviewTemplate('generic');
-            if (self.resizeImage && (self.maxImageWidth || self.maxImageHeight)) {
-                self.imageCanvas = document.createElement('canvas');
-                self.imageCanvasContext = self.imageCanvas.getContext('2d');
-            }
-            if (isEmpty(self.$element.attr('id'))) {
-                self.$element.attr('id', uniqId());
-            }
-            if (self.$container === undefined) {
-                self.$container = self._createContainer();
-            } else {
-                self._refreshContainer();
-            }
-            self.$dropZone = self.$container.find('.file-drop-zone');
-            self.$progress = self.$container.find('.kv-upload-progress');
-            self.$btnUpload = self.$container.find('.fileinput-upload');
-            self.$captionContainer = getElement(options, 'elCaptionContainer', self.$container.find('.file-caption'));
-            self.$caption = getElement(options, 'elCaptionText', self.$container.find('.file-caption-name'));
-            self.$previewContainer = getElement(options, 'elPreviewContainer', self.$container.find('.file-preview'));
-            self.$preview = getElement(options, 'elPreviewImage', self.$container.find('.file-preview-thumbnails'));
-            self.$previewStatus = getElement(options, 'elPreviewStatus', self.$container.find('.file-preview-status'));
-            self.$errorContainer = getElement(options, 'elErrorContainer',
-                self.$previewContainer.find('.kv-fileinput-error'));
-            if (!isEmpty(self.msgErrorClass)) {
-                addCss(self.$errorContainer, self.msgErrorClass);
-            }
-            self.$errorContainer.hide();
-            self.fileActionSettings = $.extend(true, defaultFileActionSettings, options.fileActionSettings);
-            self.previewInitId = "preview-" + uniqId();
-            self.id = self.$element.attr('id');
-            previewCache.init(self);
-            self._initPreview(true);
-            self._initPreviewDeletes();
-            self.options = options;
-            self._setFileDropZoneTitle();
-            self.$element.removeClass('file-loading');
-            if (self.$element.attr('disabled')) {
-                self.disable();
-            }
-        },
-        _validate: function () {
-            var self = this, $exception;
-            if (self.$element.attr('type') === 'file') {
-                return true;
-            }
-            $exception = '<div class="help-block alert alert-warning">' +
-                '<h4>Invalid Input Type</h4>' +
-                'You must set an input <code>type = file</code> for <b>bootstrap-fileinput</b> plugin to initialize.' +
-                '</div>';
-            self.$element.after($exception);
-            return false;
-        },
-        _errorsExist: function () {
-            var self = this, $err;
-            if (self.$errorContainer.find('li').length) {
-                return true;
-            }
-            $err = $(document.createElement('div')).html(self.$errorContainer.html());
-            $err.find('span.kv-error-close').remove();
-            $err.find('ul').remove();
-            return $.trim($err.text()).length ? true : false;
-        },
-        _errorHandler: function (evt, caption) {
-            var self = this, err = evt.target.error;
-            /** @namespace err.NOT_FOUND_ERR */
-            /** @namespace err.SECURITY_ERR */
-            /** @namespace err.NOT_READABLE_ERR */
-            if (err.code === err.NOT_FOUND_ERR) {
-                self._showError(self.msgFileNotFound.replace('{name}', caption));
-            } else if (err.code === err.SECURITY_ERR) {
-                self._showError(self.msgFileSecured.replace('{name}', caption));
-            } else if (err.code === err.NOT_READABLE_ERR) {
-                self._showError(self.msgFileNotReadable.replace('{name}', caption));
-            } else if (err.code === err.ABORT_ERR) {
-                self._showError(self.msgFilePreviewAborted.replace('{name}', caption));
-            } else {
-                self._showError(self.msgFilePreviewError.replace('{name}', caption));
-            }
-        },
-        _addError: function (msg) {
-            var self = this, $error = self.$errorContainer;
-            if (msg && $error.length) {
-                $error.html(self.errorCloseButton + msg);
-                handler($error.find('.kv-error-close'), 'click', function () {
-                    $error.fadeOut('slow');
-                });
-            }
-        },
-        _resetErrors: function (fade) {
-            var self = this, $error = self.$errorContainer;
-            self.isError = false;
-            self.$container.removeClass('has-error');
-            $error.html('');
-            if (fade) {
-                $error.fadeOut('slow');
-            } else {
-                $error.hide();
-            }
-        },
-        _showFolderError: function (folders) {
-            var self = this, $error = self.$errorContainer, msg;
-            if (!folders) {
-                return;
-            }
-            msg = self.msgFoldersNotAllowed.replace(/\{n}/g, folders);
-            self._addError(msg);
-            addCss(self.$container, 'has-error');
-            $error.fadeIn(800);
-            self._raise('filefoldererror', [folders, msg]);
-        },
-        _showUploadError: function (msg, params, event) {
-            var self = this, $error = self.$errorContainer, ev = event || 'fileuploaderror', e = params && params.id ?
-            '<li data-file-id="' + params.id + '">' + msg + '</li>' : '<li>' + msg + '</li>';
-            if ($error.find('ul').length === 0) {
-                self._addError('<ul>' + e + '</ul>');
-            } else {
-                $error.find('ul').append(e);
-            }
-            $error.fadeIn(800);
-            self._raise(ev, [params, msg]);
-            self.$container.removeClass('file-input-new');
-            addCss(self.$container, 'has-error');
-            return true;
-        },
-        _showError: function (msg, params, event) {
-            var self = this, $error = self.$errorContainer, ev = event || 'fileerror';
-            params = params || {};
-            params.reader = self.reader;
-            self._addError(msg);
-            $error.fadeIn(800);
-            self._raise(ev, [params, msg]);
-            if (!self.isUploadable) {
-                self._clearFileInput();
-            }
-            self.$container.removeClass('file-input-new');
-            addCss(self.$container, 'has-error');
-            self.$btnUpload.attr('disabled', true);
-            return true;
-        },
-        _noFilesError: function (params) {
-            var self = this, label = self.minFileCount > 1 ? self.filePlural : self.fileSingle,
-                msg = self.msgFilesTooLess.replace('{n}', self.minFileCount).replace('{files}', label),
-                $error = self.$errorContainer;
-            self._addError(msg);
-            self.isError = true;
-            self._updateFileDetails(0);
-            $error.fadeIn(800);
-            self._raise('fileerror', [params, msg]);
-            self._clearFileInput();
-            addCss(self.$container, 'has-error');
-        },
-        _parseError: function (jqXHR, errorThrown, fileName) {
-            /** @namespace jqXHR.responseJSON */
-            var self = this, errMsg = $.trim(errorThrown + ''),
-                dot = errMsg.slice(-1) === '.' ? '' : '.',
-                text = jqXHR.responseJSON !== undefined && jqXHR.responseJSON.error !== undefined ?
-                    jqXHR.responseJSON.error : jqXHR.responseText;
-            if (self.cancelling && self.msgUploadAborted) {
-                errMsg = self.msgUploadAborted;
-            }
-            if (self.showAjaxErrorDetails && text) {
-                text = $.trim(text.replace(/\n\s*\n/g, '\n'));
-                text = text.length > 0 ? '<pre>' + text + '</pre>' : '';
-                errMsg += dot + text;
-            } else {
-                errMsg += dot;
-            }
-            self.cancelling = false;
-            return fileName ? '<b>' + fileName + ': </b>' + errMsg : errMsg;
-        },
-        _parseFileType: function (file) {
-            var self = this, isValid, vType, cat, i;
-            for (i = 0; i < defaultPreviewTypes.length; i += 1) {
-                cat = defaultPreviewTypes[i];
-                isValid = isSet(cat, self.fileTypeSettings) ? self.fileTypeSettings[cat] : defaultFileTypeSettings[cat];
-                vType = isValid(file.type, file.name) ? cat : '';
-                if (!isEmpty(vType)) {
-                    return vType;
-                }
-            }
-            return 'other';
-        },
-        _parseFilePreviewIcon: function (content, fname) {
-            var self = this, ext, icn = self.previewFileIcon;
-            if (fname && fname.indexOf('.') > -1) {
-                ext = fname.split('.').pop();
-                if (self.previewFileIconSettings && self.previewFileIconSettings[ext]) {
-                    icn = self.previewFileIconSettings[ext];
-                }
-                if (self.previewFileExtSettings) {
-                    $.each(self.previewFileExtSettings, function (key, func) {
-                        if (self.previewFileIconSettings[key] && func(ext)) {
-                            icn = self.previewFileIconSettings[key];
-                        }
-                    });
-                }
-            }
-            if (content.indexOf('{previewFileIcon}') > -1) {
-                return content.replace(/\{previewFileIconClass}/g, self.previewFileIconClass).replace(
-                    /\{previewFileIcon}/g, icn);
-            }
-            return content;
-        },
-        _raise: function (event, params) {
-            var self = this, e = $.Event(event);
-            if (params !== undefined) {
-                self.$element.trigger(e, params);
-            } else {
-                self.$element.trigger(e);
-            }
-            if (e.isDefaultPrevented()) {
-                return false;
-            }
-            if (!e.result) {
-                return e.result;
-            }
-            switch (event) {
-                // ignore these events
-                case 'filebatchuploadcomplete':
-                case 'filebatchuploadsuccess':
-                case 'fileuploaded':
-                case 'fileclear':
-                case 'filecleared':
-                case 'filereset':
-                case 'fileerror':
-                case 'filefoldererror':
-                case 'fileuploaderror':
-                case 'filebatchuploaderror':
-                case 'filedeleteerror':
-                case 'filecustomerror':
-                case 'filesuccessremove':
-                    break;
-                // receive data response via `filecustomerror` event`
-                default:
-                    self.ajaxAborted = e.result;
-                    break;
-            }
-            return true;
-        },
-        _listen: function () {
-            var self = this, $el = self.$element, $form = $el.closest('form'), $cont = self.$container;
-            handler($el, 'change', $.proxy(self._change, self));
-            handler(self.$btnFile, 'click', $.proxy(self._browse, self));
-            handler($form, 'reset', $.proxy(self.reset, self));
-            handler($cont.find('.fileinput-remove:not([disabled])'), 'click', $.proxy(self.clear, self));
-            handler($cont.find('.fileinput-cancel'), 'click', $.proxy(self.cancel, self));
-            self._initDragDrop();
-            if (!self.isUploadable) {
-                handler($form, 'submit', $.proxy(self._submitForm, self));
-            }
-            handler(self.$container.find('.fileinput-upload'), 'click', $.proxy(self._uploadClick, self));
-        },
-        _initDragDrop: function () {
-            var self = this, $zone = self.$dropZone;
-            if (self.isUploadable && self.dropZoneEnabled && self.showPreview) {
-                handler($zone, 'dragenter dragover', $.proxy(self._zoneDragEnter, self));
-                handler($zone, 'dragleave', $.proxy(self._zoneDragLeave, self));
-                handler($zone, 'drop', $.proxy(self._zoneDrop, self));
-                handler($(document), 'dragenter dragover drop', self._zoneDragDropInit);
-            }
-        },
-        _zoneDragDropInit: function (e) {
-            e.stopPropagation();
-            e.preventDefault();
-        },
-        _zoneDragEnter: function (e) {
-            var self = this, hasFiles = $.inArray('Files', e.originalEvent.dataTransfer.types) > -1;
-            self._zoneDragDropInit(e);
-            if (self.isDisabled || !hasFiles) {
-                e.originalEvent.dataTransfer.effectAllowed = 'none';
-                e.originalEvent.dataTransfer.dropEffect = 'none';
-                return;
-            }
-            addCss(self.$dropZone, 'file-highlighted');
-        },
-        _zoneDragLeave: function (e) {
-            var self = this;
-            self._zoneDragDropInit(e);
-            if (self.isDisabled) {
-                return;
-            }
-            self.$dropZone.removeClass('file-highlighted');
-        },
-        _zoneDrop: function (e) {
-            var self = this;
-            e.preventDefault();
-            /** @namespace e.originalEvent.dataTransfer */
-            if (self.isDisabled || isEmpty(e.originalEvent.dataTransfer.files)) {
-                return;
-            }
-            self._change(e, 'dragdrop');
-            self.$dropZone.removeClass('file-highlighted');
-        },
-        _uploadClick: function (e) {
-            var self = this, $btn = self.$container.find('.fileinput-upload'), $form,
-                isEnabled = !$btn.hasClass('disabled') && isEmpty($btn.attr('disabled'));
-            if (e && e.isDefaultPrevented()) {
-                return;
-            }
-            if (!self.isUploadable) {
-                if (isEnabled && $btn.attr('type') !== 'submit') {
-                    $form = $btn.closest('form');
-                    // downgrade to normal form submit if possible
-                    if ($form.length) {
-                        $form.trigger('submit');
-                    }
-                    e.preventDefault();
-                }
-                return;
-            }
-            e.preventDefault();
-            if (isEnabled) {
-                self.upload();
-            }
-        },
-        _submitForm: function () {
-            var self = this, $el = self.$element, files = $el.get(0).files;
-            if (files && self.minFileCount > 0 && self._getFileCount(files.length) < self.minFileCount) {
-                self._noFilesError({});
-                return false;
-            }
-            return !self._abort({});
-        },
-        _clearPreview: function () {
-            var self = this, $thumbs = !self.showUploadedThumbs ? self.$preview.find('.file-preview-frame') :
-                self.$preview.find('.file-preview-frame:not(.file-preview-success)');
-            $thumbs.remove();
-            if (!self.$preview.find('.file-preview-frame').length || !self.showPreview) {
-                self._resetUpload();
-            }
-            self._validateDefaultPreview();
-        },
-        _initPreview: function (isInit) {
-            var self = this, cap = self.initialCaption || '', out;
-            if (!previewCache.count(self.id)) {
-                self._clearPreview();
-                if (isInit) {
-                    self._setCaption(cap);
-                } else {
-                    self._initCaption();
-                }
-                return;
-            }
-            out = previewCache.out(self.id);
-            cap = isInit && self.initialCaption ? self.initialCaption : out.caption;
-            self.$preview.html(out.content);
-            self._setCaption(cap);
-            if (!isEmpty(out.content)) {
-                self.$container.removeClass('file-input-new');
-            }
-        },
-        _initPreviewDeletes: function () {
-            var self = this, deleteExtraData = self.deleteExtraData || {},
-                resetProgress = function () {
-                    var hasFiles = self.isUploadable ? previewCache.count(self.id) : self.$element.get(0).files.length;
-                    if (self.$preview.find('.kv-file-remove').length === 0 && !hasFiles) {
-                        self.reset();
-                        self.initialCaption = '';
-                    }
-                };
-
-            self.$preview.find('.kv-file-remove').each(function () {
-                var $el = $(this), vUrl = $el.data('url') || self.deleteUrl, vKey = $el.data('key');
-                if (isEmpty(vUrl) || vKey === undefined) {
-                    return;
-                }
-                var $frame = $el.closest('.file-preview-frame'), cache = previewCache.data[self.id],
-                    settings, params, index = $frame.data('fileindex'), config, extraData;
-                index = parseInt(index.replace('init_', ''));
-                config = isEmpty(cache.config) && isEmpty(cache.config[index]) ? null : cache.config[index];
-                extraData = isEmpty(config) || isEmpty(config.extra) ? deleteExtraData : config.extra;
-                if (typeof extraData === "function") {
-                    extraData = extraData();
-                }
-                params = {id: $el.attr('id'), key: vKey, extra: extraData};
-                settings = $.extend(true, {}, {
-                    url: vUrl,
-                    type: 'POST',
-                    dataType: 'json',
-                    data: $.extend(true, {}, {key: vKey}, extraData),
-                    beforeSend: function (jqXHR) {
-                        self.ajaxAborted = false;
-                        self._raise('filepredelete', [vKey, jqXHR, extraData]);
-                        if (self.ajaxAborted) {
-                            jqXHR.abort();
-                        } else {
-                            addCss($frame, 'file-uploading');
-                            addCss($el, 'disabled');
-                        }
-                    },
-                    success: function (data, textStatus, jqXHR) {
-                        var n, cap;
-                        if (isEmpty(data) || isEmpty(data.error)) {
-                            previewCache.unset(self.id, index);
-                            n = previewCache.count(self.id);
-                            cap = n > 0 ? self._getMsgSelected(n) : '';
-                            self._raise('filedeleted', [vKey, jqXHR, extraData]);
-                            self._setCaption(cap);
-                        } else {
-                            params.jqXHR = jqXHR;
-                            params.response = data;
-                            self._showError(data.error, params, 'filedeleteerror');
-                            $frame.removeClass('file-uploading');
-                            $el.removeClass('disabled');
-                            resetProgress();
-                            return;
-                        }
-                        $frame.removeClass('file-uploading').addClass('file-deleted');
-                        $frame.fadeOut('slow', function () {
-                            self._clearObjects($frame);
-                            $frame.remove();
-                            resetProgress();
-                            if (!n && self.getFileStack().length === 0) {
-                                self._setCaption('');
-                                self.reset();
-                            }
-                        });
-                    },
-                    error: function (jqXHR, textStatus, errorThrown) {
-                        var errMsg = self._parseError(jqXHR, errorThrown);
-                        params.jqXHR = jqXHR;
-                        params.response = {};
-                        self._showError(errMsg, params, 'filedeleteerror');
-                        $frame.removeClass('file-uploading');
-                        resetProgress();
-                    }
-                }, self.ajaxDeleteSettings);
-                handler($el, 'click', function () {
-                    if (!self._validateMinCount()) {
-                        return false;
-                    }
-                    $.ajax(settings);
-                });
-            });
-        },
-        _clearObjects: function ($el) {
-            $el.find('video audio').each(function () {
-                this.pause();
-                $(this).remove();
-            });
-            $el.find('img object div').each(function () {
-                $(this).remove();
-            });
-        },
-        _clearFileInput: function () {
-            var self = this, $el = self.$element, $srcFrm, $tmpFrm, $tmpEl;
-            if (isEmpty($el.val())) {
-                return;
-            }
-            // Fix for IE ver < 11, that does not clear file inputs. Requires a sequence of steps to prevent IE
-            // crashing but still allow clearing of the file input.
-            if (self.isIE9 || self.isIE10) {
-                $srcFrm = $el.closest('form');
-                $tmpFrm = $(document.createElement('form'));
-                $tmpEl = $(document.createElement('div'));
-                $el.before($tmpEl);
-                if ($srcFrm.length) {
-                    $srcFrm.after($tmpFrm);
-                } else {
-                    $tmpEl.after($tmpFrm);
-                }
-                $tmpFrm.append($el).trigger('reset');
-                $tmpEl.before($el).remove();
-                $tmpFrm.remove();
-            } else { // normal input clear behavior for other sane browsers
-                $el.val('');
-            }
-            self.fileInputCleared = true;
-        },
-        _resetUpload: function () {
-            var self = this;
-            self.uploadCache = {content: [], config: [], tags: [], append: true};
-            self.uploadCount = 0;
-            self.uploadStatus = {};
-            self.uploadLog = [];
-            self.uploadAsyncCount = 0;
-            self.loadedImages = [];
-            self.totalImagesCount = 0;
-            self.$btnUpload.removeAttr('disabled');
-            self._setProgress(0);
-            addCss(self.$progress, 'hide');
-            self._resetErrors(false);
-            self.ajaxAborted = false;
-            self.ajaxRequests = [];
-            self._resetCanvas();
-        },
-        _resetCanvas: function () {
-            var self = this;
-            if (self.canvas && self.imageCanvasContext) {
-                self.imageCanvasContext.clearRect(0, 0, self.canvas.width, self.canvas.height);
-            }
-        },
-        _hasInitialPreview: function () {
-            var self = this;
-            return !self.overwriteInitial && previewCache.count(self.id);
-        },
-        _resetPreview: function () {
-            var self = this, out, cap;
-            if (previewCache.count(self.id)) {
-                out = previewCache.out(self.id);
-                self.$preview.html(out.content);
-                cap = self.initialCaption ? self.initialCaption : out.caption;
-                self._setCaption(cap);
-            } else {
-                self._clearPreview();
-                self._initCaption();
-            }
-        },
-        _clearDefaultPreview: function () {
-            var self = this;
-            self.$preview.find('.file-default-preview').remove();
-        },
-        _validateDefaultPreview: function () {
-            var self = this;
-            if (!self.showPreview || isEmpty(self.defaultPreviewContent)) {
-                return;
-            }
-            self.$preview.html('<div class="file-default-preview">' + self.defaultPreviewContent + '</div>');
-            self.$container.removeClass('file-input-new');
-        },
-        _resetPreviewThumbs: function (isAjax) {
-            var self = this, out;
-            if (isAjax) {
-                self._clearPreview();
-                self.clearStack();
-                return;
-            }
-            if (self._hasInitialPreview()) {
-                out = previewCache.out(self.id);
-                self.$preview.html(out.content);
-                self._setCaption(out.caption);
-                self._initPreviewDeletes();
-            } else {
-                self._clearPreview();
-            }
-        },
-        _getLayoutTemplate: function (t) {
-            var self = this,
-                template = isSet(t, self.layoutTemplates) ? self.layoutTemplates[t] : defaultLayoutTemplates[t];
-            if (isEmpty(self.customLayoutTags)) {
-                return template;
-            }
-            return replaceTags(template, self.customLayoutTags);
-        },
-        _getPreviewTemplate: function (t) {
-            var self = this,
-                template = isSet(t, self.previewTemplates) ? self.previewTemplates[t] : defaultPreviewTemplates[t];
-            if (isEmpty(self.customPreviewTags)) {
-                return template;
-            }
-            return replaceTags(template, self.customPreviewTags);
-        },
-        _getOutData: function (jqXHR, responseData, filesData) {
-            var self = this;
-            jqXHR = jqXHR || {};
-            responseData = responseData || {};
-            filesData = filesData || self.filestack.slice(0) || {};
-            return {
-                form: self.formdata,
-                files: filesData,
-                filenames: self.filenames,
-                extra: self._getExtraData(),
-                response: responseData,
-                reader: self.reader,
-                jqXHR: jqXHR
-            };
-        },
-        _getMsgSelected: function (n) {
-            var self = this, strFiles = n === 1 ? self.fileSingle : self.filePlural;
-            return self.msgSelected.replace('{n}', n).replace('{files}', strFiles);
-        },
-        _getThumbs: function (css) {
-            css = css || '';
-            return this.$preview.find('.file-preview-frame:not(.file-preview-initial)' + css);
-        },
-        _getExtraData: function (previewId, index) {
-            var self = this, data = self.uploadExtraData;
-            if (typeof self.uploadExtraData === "function") {
-                data = self.uploadExtraData(previewId, index);
-            }
-            return data;
-        },
-        _initXhr: function (xhrobj, previewId, fileCount) {
-            var self = this;
-            if (xhrobj.upload) {
-                xhrobj.upload.addEventListener('progress', function (event) {
-                    var pct = 0, position = event.loaded || event.position, total = event.total;
-                    /** @namespace event.lengthComputable */
-                    if (event.lengthComputable) {
-                        pct = Math.ceil(position / total * 100);
-                    }
-                    if (previewId) {
-                        self._setAsyncUploadStatus(previewId, pct, fileCount);
-                    } else {
-                        self._setProgress(Math.ceil(pct));
-                    }
-                }, false);
-            }
-            return xhrobj;
-        },
-        _ajaxSubmit: function (fnBefore, fnSuccess, fnComplete, fnError, previewId, index) {
-            var self = this, settings;
-            self._raise('filepreajax', [previewId, index]);
-            self._uploadExtra(previewId, index);
-            settings = $.extend(true, {}, {
-                xhr: function () {
-                    var xhrobj = $.ajaxSettings.xhr();
-                    return self._initXhr(xhrobj, previewId, self.getFileStack().length);
-                },
-                url: self.uploadUrl,
-                type: 'POST',
-                dataType: 'json',
-                data: self.formdata,
-                cache: false,
-                processData: false,
-                contentType: false,
-                beforeSend: fnBefore,
-                success: fnSuccess,
-                complete: fnComplete,
-                error: fnError
-            }, self.ajaxSettings);
-            self.ajaxRequests.push($.ajax(settings));
-        },
-        _initUploadSuccess: function (out, $thumb, allFiles) {
-            var self = this, append, data, index, $newThumb, content, config, tags, i;
-            if (!self.showPreview || typeof out !== 'object' || $.isEmptyObject(out)) {
-                return;
-            }
-            if (out.initialPreview !== undefined && out.initialPreview.length > 0) {
-                self.hasInitData = true;
-                content = out.initialPreview || [];
-                config = out.initialPreviewConfig || [];
-                tags = out.initialPreviewThumbTags || [];
-                append = out.append === undefined || out.append ? true : false;
-                self.overwriteInitial = false;
-                if ($thumb !== undefined) {
-                    if (!allFiles) {
-                        index = previewCache.add(self.id, content, config[0], tags[0], append);
-                        data = previewCache.get(self.id, index, false);
-                        $newThumb = $(data).hide();
-                        $thumb.after($newThumb).fadeOut('slow', function () {
-                            $newThumb.fadeIn('slow').css('display:inline-block');
-                            self._initPreviewDeletes();
-                            self._clearFileInput();
-                            $thumb.remove();
-                        });
-                    } else {
-                        i = $thumb.attr('data-fileindex');
-                        self.uploadCache.content[i] = content[0];
-                        self.uploadCache.config[i] = config[0];
-                        self.uploadCache.tags[i] = tags[0];
-                        self.uploadCache.append = append;
-                    }
-                } else {
-                    previewCache.set(self.id, content, config, tags, append);
-                    self._initPreview();
-                    self._initPreviewDeletes();
-                }
-            }
-        },
-        _initSuccessThumbs: function () {
-            var self = this;
-            if (!self.showPreview) {
-                return;
-            }
-            self._getThumbs('.file-preview-success').each(function () {
-                var $thumb = $(this), $remove = $thumb.find('.kv-file-remove');
-                $remove.removeAttr('disabled');
-                handler($remove, 'click', function () {
-                    var out = self._raise('filesuccessremove', [$thumb.attr('id'), $thumb.data('fileindex')]);
-                    cleanMemory($thumb);
-                    if (out === false) {
-                        return;
-                    }
-                    $thumb.fadeOut('slow', function () {
-                        $thumb.remove();
-                        if (!self.$preview.find('.file-preview-frame').length) {
-                            self.reset();
-                        }
-                    });
-                });
-            });
-        },
-        _checkAsyncComplete: function () {
-            var self = this, previewId, i;
-            for (i = 0; i < self.filestack.length; i++) {
-                if (self.filestack[i]) {
-                    previewId = self.previewInitId + "-" + i;
-                    if ($.inArray(previewId, self.uploadLog) === -1) {
-                        return false;
-                    }
-                }
-            }
-            return (self.uploadAsyncCount === self.uploadLog.length);
-        },
-        _uploadExtra: function (previewId, index) {
-            var self = this, data = self._getExtraData(previewId, index);
-            if (data.length === 0) {
-                return;
-            }
-            $.each(data, function (key, value) {
-                self.formdata.append(key, value);
-            });
-        },
-        _uploadSingle: function (i, files, allFiles) {
-            var self = this, total = self.getFileStack().length, formdata = new FormData(), outData,
-                previewId = self.previewInitId + "-" + i, $thumb, chkComplete, $btnUpload, $btnDelete,
-                hasPostData = self.filestack.length > 0 || !$.isEmptyObject(self.uploadExtraData),
-                fnBefore, fnSuccess, fnComplete, fnError, updateUploadLog, params = {id: previewId, index: i};
-            self.formdata = formdata;
-            if (self.showPreview) {
-                $thumb = $('#' + previewId + ':not(.file-preview-initial)');
-                $btnUpload = $thumb.find('.kv-file-upload');
-                $btnDelete = $thumb.find('.kv-file-remove');
-                $('#' + previewId).find('.file-thumb-progress').removeClass('hide');
-            }
-            if (total === 0 || !hasPostData || ($btnUpload && $btnUpload.hasClass('disabled')) || self._abort(params)) {
-                return;
-            }
-            updateUploadLog = function (i, previewId) {
-                self.updateStack(i, undefined);
-                self.uploadLog.push(previewId);
-                if (self._checkAsyncComplete()) {
-                    self.fileBatchCompleted = true;
-                }
-            };
-            chkComplete = function () {
-                if (!self.fileBatchCompleted) {
-                    return;
-                }
-                setTimeout(function () {
-                    if (self.showPreview) {
-                        previewCache.set(
-                            self.id,
-                            self.uploadCache.content,
-                            self.uploadCache.config,
-                            self.uploadCache.tags,
-                            self.uploadCache.append
-                        );
-                        if (self.hasInitData) {
-                            self._initPreview();
-                            self._initPreviewDeletes();
-                        }
-                    }
-                    self.unlock();
-                    self._clearFileInput();
-                    self._raise('filebatchuploadcomplete', [self.filestack, self._getExtraData()]);
-                    self.uploadCount = 0;
-                    self.uploadStatus = {};
-                    self.uploadLog = [];
-                    self._setProgress(100);
-                }, 100);
-            };
-            fnBefore = function (jqXHR) {
-                outData = self._getOutData(jqXHR);
-                self.fileBatchCompleted = false;
-                if (self.showPreview) {
-                    if (!$thumb.hasClass('file-preview-success')) {
-                        self._setThumbStatus($thumb, 'Loading');
-                        addCss($thumb, 'file-uploading');
-                    }
-                    $btnUpload.attr('disabled', true);
-                    $btnDelete.attr('disabled', true);
-                }
-                if (!allFiles) {
-                    self.lock();
-                }
-                self._raise('filepreupload', [outData, previewId, i]);
-                $.extend(true, params, outData);
-                if (self._abort(params)) {
-                    jqXHR.abort();
-                    self._setProgressCancelled();
-                }
-            };
-            fnSuccess = function (data, textStatus, jqXHR) {
-                outData = self._getOutData(jqXHR, data);
-                $.extend(true, params, outData);
-                setTimeout(function () {
-                    if (isEmpty(data) || isEmpty(data.error)) {
-                        if (self.showPreview) {
-                            self._setThumbStatus($thumb, 'Success');
-                            $btnUpload.hide();
-                            self._initUploadSuccess(data, $thumb, allFiles);
-                        }
-                        self._raise('fileuploaded', [outData, previewId, i]);
-                        if (!allFiles) {
-                            self.updateStack(i, undefined);
-                        } else {
-                            updateUploadLog(i, previewId);
-                        }
-                    } else {
-                        self._showUploadError(data.error, params);
-                        self._setPreviewError($thumb, i);
-                        if (allFiles) {
-                            updateUploadLog(i, previewId);
-                        }
-                    }
-                }, 100);
-            };
-            fnComplete = function () {
-                setTimeout(function () {
-                    if (self.showPreview) {
-                        $btnUpload.removeAttr('disabled');
-                        $btnDelete.removeAttr('disabled');
-                        $thumb.removeClass('file-uploading');
-                    }
-                    if (!allFiles) {
-                        self.unlock(false);
-                        self._clearFileInput();
-                    } else {
-                        chkComplete();
-                    }
-                    self._initSuccessThumbs();
-                }, 100);
-            };
-            fnError = function (jqXHR, textStatus, errorThrown) {
-                var errMsg = self._parseError(jqXHR, errorThrown, (allFiles ? files[i].name : null));
-                setTimeout(function () {
-                    if (allFiles) {
-                        updateUploadLog(i, previewId);
-                    }
-                    self.uploadStatus[previewId] = 100;
-                    self._setPreviewError($thumb, i);
-                    $.extend(true, params, self._getOutData(jqXHR));
-                    self._showUploadError(errMsg, params);
-                }, 100);
-            };
-            formdata.append(self.uploadFileAttr, files[i], self.filenames[i]);
-            formdata.append('file_id', i);
-            self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, previewId, i);
-        },
-        _uploadBatch: function () {
-            var self = this, files = self.filestack, total = files.length, params = {}, fnBefore, fnSuccess, fnError,
-                fnComplete, hasPostData = self.filestack.length > 0 || !$.isEmptyObject(self.uploadExtraData),
-                setAllUploaded;
-            self.formdata = new FormData();
-            if (total === 0 || !hasPostData || self._abort(params)) {
-                return;
-            }
-            setAllUploaded = function () {
-                $.each(files, function (key) {
-                    self.updateStack(key, undefined);
-                });
-                self._clearFileInput();
-            };
-            fnBefore = function (jqXHR) {
-                self.lock();
-                var outData = self._getOutData(jqXHR);
-                if (self.showPreview) {
-                    self._getThumbs().each(function () {
-                        var $thumb = $(this), $btnUpload = $thumb.find('.kv-file-upload'),
-                            $btnDelete = $thumb.find('.kv-file-remove');
-                        if (!$thumb.hasClass('file-preview-success')) {
-                            self._setThumbStatus($thumb, 'Loading');
-                            addCss($thumb, 'file-uploading');
-                        }
-                        $btnUpload.attr('disabled', true);
-                        $btnDelete.attr('disabled', true);
-                    });
-                }
-                self._raise('filebatchpreupload', [outData]);
-                if (self._abort(outData)) {
-                    jqXHR.abort();
-                    self._setProgressCancelled();
-                }
-            };
-            fnSuccess = function (data, textStatus, jqXHR) {
-                /** @namespace data.errorkeys */
-                var outData = self._getOutData(jqXHR, data), $thumbs = self._getThumbs(), key = 0,
-                    keys = isEmpty(data) || isEmpty(data.errorkeys) ? [] : data.errorkeys;
-                if (isEmpty(data) || isEmpty(data.error)) {
-                    self._raise('filebatchuploadsuccess', [outData]);
-                    setAllUploaded();
-                    if (self.showPreview) {
-                        $thumbs.each(function () {
-                            var $thumb = $(this), $btnUpload = $thumb.find('.kv-file-upload');
-                            $thumb.find('.kv-file-upload').hide();
-                            self._setThumbStatus($thumb, 'Success');
-                            $thumb.removeClass('file-uploading');
-                            $btnUpload.removeAttr('disabled');
-                        });
-                        self._initUploadSuccess(data);
-                    } else {
-                        self.reset();
-                    }
-                } else {
-                    if (self.showPreview) {
-                        $thumbs.each(function () {
-                            var $thumb = $(this), $btnDelete = $thumb.find('.kv-file-remove'),
-                                $btnUpload = $thumb.find('.kv-file-upload');
-                            $thumb.removeClass('file-uploading');
-                            $btnUpload.removeAttr('disabled');
-                            $btnDelete.removeAttr('disabled');
-                            if (keys.length === 0) {
-                                self._setPreviewError($thumb);
-                                return;
-                            }
-                            if ($.inArray(key, keys) !== -1) {
-                                self._setPreviewError($thumb);
-                            } else {
-                                $thumb.find('.kv-file-upload').hide();
-                                self._setThumbStatus($thumb, 'Success');
-                                self.updateStack(key, undefined);
-                            }
-                            key++;
-                        });
-                        self._initUploadSuccess(data);
-                    }
-                    self._showUploadError(data.error, outData, 'filebatchuploaderror');
-                }
-            };
-            fnComplete = function () {
-                self._setProgress(100);
-                self.unlock();
-                self._initSuccessThumbs();
-                self._clearFileInput();
-                self._raise('filebatchuploadcomplete', [self.filestack, self._getExtraData()]);
-            };
-            fnError = function (jqXHR, textStatus, errorThrown) {
-                var outData = self._getOutData(jqXHR), errMsg = self._parseError(jqXHR, errorThrown);
-                self._showUploadError(errMsg, outData, 'filebatchuploaderror');
-                self.uploadFileCount = total - 1;
-                if (!self.showPreview) {
-                    return;
-                }
-                self._getThumbs().each(function () {
-                    var $thumb = $(this), key = $thumb.attr('data-fileindex');
-                    $thumb.removeClass('file-uploading');
-                    if (self.filestack[key] !== undefined) {
-                        self._setPreviewError($thumb);
-                    }
-                });
-                self._getThumbs().removeClass('file-uploading');
-                self._getThumbs(' .kv-file-upload').removeAttr('disabled');
-                self._getThumbs(' .kv-file-delete').removeAttr('disabled');
-            };
-            $.each(files, function (key, data) {
-                if (!isEmpty(files[key])) {
-                    self.formdata.append(self.uploadFileAttr, data, self.filenames[key]);
-                }
-            });
-            self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError);
-        },
-        _uploadExtraOnly: function () {
-            var self = this, params = {}, fnBefore, fnSuccess, fnComplete, fnError;
-            self.formdata = new FormData();
-            if (self._abort(params)) {
-                return;
-            }
-            fnBefore = function (jqXHR) {
-                self.lock();
-                var outData = self._getOutData(jqXHR);
-                self._raise('filebatchpreupload', [outData]);
-                self._setProgress(50);
-                params.data = outData;
-                params.xhr = jqXHR;
-                if (self._abort(params)) {
-                    jqXHR.abort();
-                    self._setProgressCancelled();
-                }
-            };
-            fnSuccess = function (data, textStatus, jqXHR) {
-                var outData = self._getOutData(jqXHR, data);
-                if (isEmpty(data) || isEmpty(data.error)) {
-                    self._raise('filebatchuploadsuccess', [outData]);
-                    self._clearFileInput();
-                    self._initUploadSuccess(data);
-                } else {
-                    self._showUploadError(data.error, outData, 'filebatchuploaderror');
-                }
-            };
-            fnComplete = function () {
-                self._setProgress(100);
-                self.unlock();
-                self._clearFileInput();
-                self._raise('filebatchuploadcomplete', [self.filestack, self._getExtraData()]);
-            };
-            fnError = function (jqXHR, textStatus, errorThrown) {
-                var outData = self._getOutData(jqXHR), errMsg = self._parseError(jqXHR, errorThrown);
-                params.data = outData;
-                self._showUploadError(errMsg, outData, 'filebatchuploaderror');
-            };
-            self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError);
-        },
-        _initFileActions: function () {
-            var self = this;
-            if (!self.showPreview) {
-                return;
-            }
-            self.$preview.find('.kv-file-remove').each(function () {
-                var $el = $(this), $frame = $el.closest('.file-preview-frame'), hasError,
-                    id = $frame.attr('id'), ind = $frame.attr('data-fileindex'), n, cap, status;
-                handler($el, 'click', function () {
-                    status = self._raise('filepreremove', [id, ind]);
-                    if (status === false || !self._validateMinCount()) {
-                        return false;
-                    }
-                    hasError = $frame.hasClass('file-preview-error');
-                    cleanMemory($frame);
-                    $frame.fadeOut('slow', function () {
-                        self.updateStack(ind, undefined);
-                        self._clearObjects($frame);
-                        $frame.remove();
-                        if (id && hasError) {
-                            self.$errorContainer.find('li[data-file-id="' + id + '"]').fadeOut('fast', function () {
-                                $(this).remove();
-                                if (!self._errorsExist()) {
-                                    self._resetErrors();
-                                }
-                            });
-                        }
-                        var filestack = self.getFileStack(true), len = filestack.length, chk = previewCache.count(
-                            self.id),
-                            hasThumb = self.showPreview && self.$preview.find('.file-preview-frame').length;
-                        self._clearFileInput();
-                        if (len === 0 && chk === 0 && !hasThumb) {
-                            self.reset();
-                        } else {
-                            n = chk + len;
-                            cap = n > 1 ? self._getMsgSelected(n) : (filestack[0] ? self._getFileNames()[0] : '');
-                            self._setCaption(cap);
-                        }
-                        self._raise('fileremoved', [id, ind]);
-                    });
-                });
-            });
-            self.$preview.find('.kv-file-upload').each(function () {
-                var $el = $(this);
-                handler($el, 'click', function () {
-                    var $frame = $el.closest('.file-preview-frame'),
-                        ind = $frame.attr('data-fileindex');
-                    if (!$frame.hasClass('file-preview-error')) {
-                        self._uploadSingle(ind, self.filestack, false);
-                    }
-                });
-            });
-        },
-        _hideFileIcon: function () {
-            if (this.overwriteInitial) {
-                this.$captionContainer.find('.kv-caption-icon').hide();
-            }
-        },
-        _showFileIcon: function () {
-            this.$captionContainer.find('.kv-caption-icon').show();
-        },
-        _previewDefault: function (file, previewId, isDisabled) {
-            if (!this.showPreview) {
-                return;
-            }
-            var self = this, frameClass = '', fname = file ? file.name : '',
-                /** @namespace objUrl.createObjectURL */
-                data = objUrl.createObjectURL(file), ind = previewId.slice(previewId.lastIndexOf('-') + 1),
-                config = self.previewSettings.other || defaultPreviewSettings.other,
-                footer = self._renderFileFooter(file.name, config.width),
-                previewOtherTemplate = self._parseFilePreviewIcon(self._getPreviewTemplate('other'), fname);
-            if (isDisabled === true) {
-                if (!self.isUploadable) {
-                    footer += '<div class="file-other-error" title="' + self.fileActionSettings.indicatorErrorTitle +
-                        '">' + self.fileActionSettings.indicatorError + '</div>';
-                }
-            }
-            self._clearDefaultPreview();
-            self.$preview.append("\n" + previewOtherTemplate
-                    .replace(/\{previewId}/g, previewId)
-                    .replace(/\{frameClass}/g, frameClass)
-                    .replace(/\{fileindex}/g, ind)
-                    .replace(/\{caption}/g, self.slug(file.name))
-                    .replace(/\{width}/g, config.width)
-                    .replace(/\{height}/g, config.height)
-                    .replace(/\{type}/g, file.type)
-                    .replace(/\{data}/g, data)
-                    .replace(/\{footer}/g, footer));
-            if (isDisabled === true && self.isUploadable) {
-                self._setThumbStatus($('#' + previewId), 'Error');
-            }
-        },
-        _previewFile: function (i, file, theFile, previewId, data) {
-            if (!this.showPreview) {
-                return;
-            }
-            var self = this, cat = self._parseFileType(file), fname = file ? file.name : '', caption = self.slug(fname),
-                content, strText, types = self.allowedPreviewTypes, mimes = self.allowedPreviewMimeTypes,
-                tmplt = self._getPreviewTemplate(cat), chkTypes = types && types.indexOf(cat) >= 0, id,
-                config = isSet(cat, self.previewSettings) ? self.previewSettings[cat] : defaultPreviewSettings[cat],
-                chkMimes = mimes && mimes.indexOf(file.type) !== -1,
-                footer = self._renderFileFooter(caption, config.width), modal = '',
-                ind = previewId.slice(previewId.lastIndexOf('-') + 1);
-            if (chkTypes || chkMimes) {
-                tmplt = self._parseFilePreviewIcon(tmplt, fname.split('.').pop());
-                if (cat === 'text') {
-                    strText = htmlEncode(theFile.target.result);
-                    id = 'text-' + uniqId();
-                    content = tmplt.replace(/\{zoom}/g, self._getLayoutTemplate('zoom'));
-                    modal = self._getLayoutTemplate('modal').replace('{id}', id)
-                        .replace(/\{title}/g, caption)
-                        .replace(/\{body}/g, strText).replace(/\{heading}/g, self.msgZoomModalHeading);
-                    content = content.replace(/\{previewId}/g, previewId).replace(/\{caption}/g, caption)
-                            .replace(/\{width}/g, config.width).replace(/\{height}/g, config.height)
-                            .replace(/\{frameClass}/g, '').replace(/\{zoomInd}/g, self.zoomIndicator)
-                            .replace(/\{footer}/g, footer).replace(/\{fileindex}/g, ind)
-                            .replace(/\{type}/g, file.type).replace(/\{zoomTitle}/g, self.msgZoomTitle)
-                            .replace(/\{dialog}/g, "$('#" + id + "').modal('show')")
-                            .replace(/\{data}/g, strText) + modal;
-                } else {
-                    content = tmplt.replace(/\{previewId}/g, previewId).replace(/\{caption}/g, caption)
-                        .replace(/\{frameClass}/g, '').replace(/\{type}/g, file.type).replace(/\{fileindex}/g, ind)
-                        .replace(/\{width}/g, config.width).replace(/\{height}/g, config.height)
-                        .replace(/\{footer}/g, footer).replace(/\{data}/g, data);
-                }
-                self._clearDefaultPreview();
-                self.$preview.append("\n" + content);
-                self._validateImage(i, previewId, caption, file.type);
-            } else {
-                self._previewDefault(file, previewId);
-            }
-        },
-        _slugDefault: function (text) {
-            return isEmpty(text) ? '' : String(text).replace(/[\-\[\]\/\{}:;#%=\(\)\*\+\?\\\^\$\|<>&"']/g, '_');
-        },
-        _readFiles: function (files) {
-            this.reader = new FileReader();
-            var self = this, $el = self.$element, $preview = self.$preview, reader = self.reader,
-                $container = self.$previewContainer, $status = self.$previewStatus, msgLoading = self.msgLoading,
-                msgProgress = self.msgProgress, previewInitId = self.previewInitId, numFiles = files.length,
-                settings = self.fileTypeSettings, ctr = self.filestack.length, readFile,
-                maxPreviewSize = self.maxFilePreviewSize && parseFloat(self.maxFilePreviewSize),
-                canPreview = $preview.length && (!maxPreviewSize || isNaN(maxPreviewSize)),
-                throwError = function (msg, file, previewId, index) {
-                    var p1 = $.extend(true, {}, self._getOutData({}, {}, files), {id: previewId, index: index}),
-                        p2 = {id: previewId, index: index, file: file, files: files};
-                    self._previewDefault(file, previewId, true);
-                    if (self.isUploadable) {
-                        self.addToStack(undefined);
-                    }
-                    setTimeout(function(){readFile(index + 1);}, 100);
-                    self._initFileActions();
-                    if (self.removeFromPreviewOnError) {
-                        $('#' + previewId).remove();
-                    }
-                    return self.isUploadable ? self._showUploadError(msg, p1) : self._showError(msg, p2);
-                };
-
-            self.loadedImages = [];
-            self.totalImagesCount = 0;
-
-            $.each(files, function (key, file) {
-                var func = self.fileTypeSettings.image || defaultFileTypeSettings.image;
-                if (func && func(file.type)) {
-                    self.totalImagesCount++;
-                }
-            });
-            readFile = function (i) {
-                if (isEmpty($el.attr('multiple'))) {
-                    numFiles = 1;
-                }
-                if (i >= numFiles) {
-                    if (self.isUploadable && self.filestack.length > 0) {
-                        self._raise('filebatchselected', [self.getFileStack()]);
-                    } else {
-                        self._raise('filebatchselected', [files]);
-                    }
-                    $container.removeClass('file-thumb-loading');
-                    $status.html('');
-                    return;
-                }
-                var node = ctr + i, previewId = previewInitId + "-" + node, isText, file = files[i],
-                    caption = self.slug(file.name), fileSize = (file.size || 0) / 1000, checkFile, fileExtExpr = '',
-                    previewData = objUrl.createObjectURL(file), fileCount = 0, j, msg, typ, chk,
-                    fileTypes = self.allowedFileTypes, strTypes = isEmpty(fileTypes) ? '' : fileTypes.join(', '),
-                    fileExt = self.allowedFileExtensions, strExt = isEmpty(fileExt) ? '' : fileExt.join(', ');
-                if (!isEmpty(fileExt)) {
-                    fileExtExpr = new RegExp('\\.(' + fileExt.join('|') + ')$', 'i');
-                }
-                fileSize = fileSize.toFixed(2);
-                if (self.maxFileSize > 0 && fileSize > self.maxFileSize) {
-                    msg = self.msgSizeTooLarge.replace('{name}', caption)
-                        .replace('{size}', fileSize)
-                        .replace('{maxSize}', self.maxFileSize);
-                    self.isError = throwError(msg, file, previewId, i);
-                    return;
-                }
-                if (!isEmpty(fileTypes) && isArray(fileTypes)) {
-                    for (j = 0; j < fileTypes.length; j += 1) {
-                        typ = fileTypes[j];
-                        checkFile = settings[typ];
-                        chk = (checkFile !== undefined && checkFile(file.type, caption));
-                        fileCount += isEmpty(chk) ? 0 : chk.length;
-                    }
-                    if (fileCount === 0) {
-                        msg = self.msgInvalidFileType.replace('{name}', caption).replace('{types}', strTypes);
-                        self.isError = throwError(msg, file, previewId, i);
-                        return;
-                    }
-                }
-                if (fileCount === 0 && !isEmpty(fileExt) && isArray(fileExt) && !isEmpty(fileExtExpr)) {
-                    chk = compare(caption, fileExtExpr);
-                    fileCount += isEmpty(chk) ? 0 : chk.length;
-                    if (fileCount === 0) {
-                        msg = self.msgInvalidFileExtension.replace('{name}', caption).replace('{extensions}',
-                            strExt);
-                        self.isError = throwError(msg, file, previewId, i);
-                        return;
-                    }
-                }
-                if (!self.showPreview) {
-                    self.addToStack(file);
-                    setTimeout(function(){readFile(i + 1);}, 100);
-                    self._raise('fileloaded', [file, previewId, i, reader]);
-                    return;
-                }
-                if (!canPreview && fileSize > maxPreviewSize) {
-                    $container.addClass('file-thumb-loading');
-                    self._previewDefault(file, previewId);
-                    self._initFileActions();
-                    self._updateFileDetails(numFiles);
-                    readFile(i + 1);
-                    return;
-                }
-                if ($preview.length && FileReader !== undefined) {
-                    $status.html(msgLoading.replace('{index}', i + 1).replace('{files}', numFiles));
-                    $container.addClass('file-thumb-loading');
-                    reader.onerror = function (evt) {
-                        self._errorHandler(evt, caption);
-                    };
-                    reader.onload = function (theFile) {
-                        self._previewFile(i, file, theFile, previewId, previewData);
-                        self._initFileActions();
-                    };
-                    reader.onloadend = function () {
-                        msg = msgProgress.replace('{index}', i + 1).replace('{files}', numFiles)
-                            .replace('{percent}', 50).replace('{name}', caption);
-                        setTimeout(function () {
-                            $status.html(msg);
-                            self._updateFileDetails(numFiles);
-                            readFile(i + 1);
-                        }, 100);
-                        self._raise('fileloaded', [file, previewId, i, reader]);
-                    };
-                    reader.onprogress = function (data) {
-                        if (data.lengthComputable) {
-                            var fact = (data.loaded / data.total) * 100, progress = Math.ceil(fact);
-                            msg = msgProgress.replace('{index}', i + 1).replace('{files}', numFiles)
-                                .replace('{percent}', progress).replace('{name}', caption);
-                            setTimeout(function () {
-                                $status.html(msg);
-                            }, 100);
-                        }
-                    };
-                    isText = isSet('text', settings) ? settings.text : defaultFileTypeSettings.text;
-                    if (isText(file.type, caption)) {
-                        reader.readAsText(file, self.textEncoding);
-                    } else {
-                        reader.readAsArrayBuffer(file);
-                    }
-                } else {
-                    self._previewDefault(file, previewId);
-                    setTimeout(function () {
-                        readFile(i + 1);
-                        self._updateFileDetails(numFiles);
-                    }, 100);
-                    self._raise('fileloaded', [file, previewId, i, reader]);
-                }
-                self.addToStack(file);
-            };
-
-            readFile(0);
-            self._updateFileDetails(numFiles, false);
-        },
-        _updateFileDetails: function (numFiles) {
-            var self = this, $el = self.$element, fileStack = self.getFileStack(),
-                name = (isIE(9) && findFileName($el.val())) ||
-                    ($el[0].files[0] && $el[0].files[0].name) || (fileStack.length && fileStack[0].name) || '',
-                label = self.slug(name), n = self.isUploadable ? fileStack.length : numFiles,
-                nFiles = previewCache.count(self.id) + n, log = n > 1 ? self._getMsgSelected(nFiles) : label;
-            if (self.isError) {
-                self.$previewContainer.removeClass('file-thumb-loading');
-                self.$previewStatus.html('');
-                self.$captionContainer.find('.kv-caption-icon').hide();
-            } else {
-                self._showFileIcon();
-            }
-            self._setCaption(log, self.isError);
-            self.$container.removeClass('file-input-new file-input-ajax-new');
-            if (arguments.length === 1) {
-                self._raise('fileselect', [numFiles, label]);
-            }
-            if (previewCache.count(self.id)) {
-                self._initPreviewDeletes();
-            }
-        },
-        _setThumbStatus: function ($thumb, status) {
-            var self = this;
-            if (!self.showPreview) {
-                return;
-            }
-            var icon = 'indicator' + status, msg = icon + 'Title',
-                css = 'file-preview-' + status.toLowerCase(),
-                $indicator = $thumb.find('.file-upload-indicator'),
-                config = self.fileActionSettings;
-            $thumb.removeClass('file-preview-success file-preview-error file-preview-loading');
-            if (status === 'Error') {
-                $thumb.find('.kv-file-upload').attr('disabled', true);
-            }
-            $indicator.html(config[icon]);
-            $indicator.attr('title', config[msg]);
-            $thumb.addClass(css);
-        },
-        _setProgressCancelled: function () {
-            var self = this;
-            self._setProgress(100, self.$progress, self.msgCancelled);
-        },
-        _setProgress: function (p, $el, error) {
-            var self = this, pct = Math.min(p, 100), template = pct < 100 ? self.progressTemplate :
-                (error ? self.progressErrorTemplate : self.progressCompleteTemplate);
-            $el = $el || self.$progress;
-            if (!isEmpty(template)) {
-                $el.html(template.replace(/\{percent}/g, pct));
-                if (error) {
-                    $el.find('[role="progressbar"]').html(error);
-                }
-            }
-        },
-        _setFileDropZoneTitle: function () {
-            var self = this, $zone = self.$container.find('.file-drop-zone');
-            $zone.find('.' + self.dropZoneTitleClass).remove();
-            if (!self.isUploadable || !self.showPreview || $zone.length === 0 || self.getFileStack().length > 0 || !self.dropZoneEnabled) {
-                return;
-            }
-            if ($zone.find('.file-preview-frame').length === 0 && isEmpty(self.defaultPreviewContent)) {
-                $zone.prepend('<div class="' + self.dropZoneTitleClass + '">' + self.dropZoneTitle + '</div>');
-            }
-            self.$container.removeClass('file-input-new');
-            addCss(self.$container, 'file-input-ajax-new');
-        },
-        _setAsyncUploadStatus: function (previewId, pct, total) {
-            var self = this, sum = 0;
-            self._setProgress(pct, $('#' + previewId).find('.file-thumb-progress'));
-            self.uploadStatus[previewId] = pct;
-            $.each(self.uploadStatus, function (key, value) {
-                sum += value;
-            });
-            self._setProgress(Math.ceil(sum / total));
-
-        },
-        _validateMinCount: function () {
-            var self = this, len = self.isUploadable ? self.getFileStack().length : self.$element.get(0).files.length;
-            if (self.validateInitialCount && self.minFileCount > 0 && self._getFileCount(
-                    len - 1) < self.minFileCount) {
-                self._noFilesError({});
-                return false;
-            }
-            return true;
-        },
-        _getFileCount: function (fileCount) {
-            var self = this, addCount = 0;
-            if (self.validateInitialCount && !self.overwriteInitial) {
-                addCount = previewCache.count(self.id);
-                fileCount += addCount;
-            }
-            return fileCount;
-        },
-        _getFileName: function (file) {
-            return file && file.name ? this.slug(file.name) : undefined;
-        },
-        _getFileNames: function (skipNull) {
-            var self = this;
-            return self.filenames.filter(function (n) {
-                return (skipNull ? n !== undefined : n !== undefined && n !== null);
-            });
-        },
-        _setPreviewError: function ($thumb, i, val) {
-            var self = this;
-            if (i) {
-                self.updateStack(i, val);
-            }
-            if (self.removeFromPreviewOnError) {
-                $thumb.remove();
-            } else {
-                self._setThumbStatus($thumb, 'Error');
-            }
-        },
-        _checkDimensions: function (i, chk, $img, $thumb, fname, type, params) {
-            var self = this, msg, dim, tag = chk === 'Small' ? 'min' : 'max', limit = self[tag + 'Image' + type],
-                $imgEl, isValid;
-            if (isEmpty(limit) || !$img.length) {
-                return;
-            }
-            $imgEl = $img[0];
-            dim = (type === 'Width') ? $imgEl.naturalWidth || $imgEl.width : $imgEl.naturalHeight || $imgEl.height;
-            isValid = chk === 'Small' ? dim >= limit : dim <= limit;
-            if (isValid) {
-                return;
-            }
-            msg = self['msgImage' + type + chk].replace('{name}', fname).replace('{size}', limit);
-            self._showUploadError(msg, params);
-            self._setPreviewError($thumb, i, null);
-        },
-        _validateImage: function (i, previewId, fname, ftype) {
-            var self = this, $preview = self.$preview, params, w1, w2,
-                $thumb = $preview.find("#" + previewId), $img = $thumb.find('img');
-            fname = fname || 'Untitled';
-            if (!$img.length) {
-                return;
-            }
-            handler($img, 'load', function () {
-                w1 = $thumb.width();
-                w2 = $preview.width();
-                if (w1 > w2) {
-                    $img.css('width', '100%');
-                    $thumb.css('width', '97%');
-                }
-                params = {ind: i, id: previewId};
-                self._checkDimensions(i, 'Small', $img, $thumb, fname, 'Width', params);
-                self._checkDimensions(i, 'Small', $img, $thumb, fname, 'Height', params);
-                if (!self.resizeImage) {
-                    self._checkDimensions(i, 'Large', $img, $thumb, fname, 'Width', params);
-                    self._checkDimensions(i, 'Large', $img, $thumb, fname, 'Height', params);
-                }
-                self._raise('fileimageloaded', [previewId]);
-                self.loadedImages.push({ind: i, img: $img, thumb: $thumb, pid: previewId, typ: ftype});
-                self._validateAllImages();
-                objUrl.revokeObjectURL($img.attr('src'));
-            });
-        },
-        _validateAllImages: function () {
-            var self = this, i, config, $img, $thumb, pid, ind, params = {}, errFunc;
-            if (self.loadedImages.length !== self.totalImagesCount) {
-                return;
-            }
-            self._raise('fileimagesloaded');
-            if (!self.resizeImage) {
-                return;
-            }
-            errFunc = self.isUploadable ? self._showUploadError : self._showError;
-            for (i = 0; i < self.loadedImages.length; i++) {
-                config = self.loadedImages[i];
-                $img = config.img;
-                $thumb = config.thumb;
-                pid = config.pid;
-                ind = config.ind;
-                params = {id: pid, 'index': ind};
-                if (!self._getResizedImage($img[0], config.typ, pid, ind)) {
-                    errFunc(self.msgImageResizeError, params, 'fileimageresizeerror');
-                    self._setPreviewError($thumb, ind);
-                }
-            }
-            self._raise('fileimagesresized');
-        },
-        _getResizedImage: function (image, type, pid, ind) {
-            var self = this, width = image.naturalWidth, height = image.naturalHeight, ratio = 1,
-                maxWidth = self.maxImageWidth || width, maxHeight = self.maxImageHeight || height,
-                isValidImage = (width && height), chkWidth, chkHeight,
-                canvas = self.imageCanvas, context = self.imageCanvasContext;
-            if (!isValidImage) {
-                return false;
-            }
-            if (width === maxWidth && height === maxHeight) {
-                return true;
-            }
-            type = type || self.resizeDefaultImageType;
-            chkWidth = width > maxWidth;
-            chkHeight = height > maxHeight;
-            if (self.resizePreference === 'width') {
-                ratio = chkWidth ? maxWidth / width : (chkHeight ? maxHeight / height : 1);
-            } else {
-                ratio = chkHeight ? maxHeight / height : (chkWidth ? maxWidth / width : 1);
-            }
-            self._resetCanvas();
-            width *= ratio;
-            height *= ratio;
-            canvas.width = width;
-            canvas.height = height;
-            try {
-                context.drawImage(image, 0, 0, width, height);
-                canvas.toBlob(function (blob) {
-                    self._raise('fileimageresized', [pid, ind]);
-                    self.filestack[ind] = blob;
-                }, type, self.resizeQuality);
-                return true;
-            }
-            catch (err) {
-                return false;
-            }
-        },
-        _initBrowse: function ($container) {
-            var self = this;
-            self.$btnFile = $container.find('.btn-file');
-            self.$btnFile.append(self.$element);
-        },
-        _initCaption: function () {
-            var self = this, cap = self.initialCaption || '';
-            if (self.overwriteInitial || isEmpty(cap)) {
-                self.$caption.html('');
-                return false;
-            }
-            self._setCaption(cap);
-            return true;
-        },
-        _setCaption: function (content, isError) {
-            var self = this, title, out, n, cap, stack = self.getFileStack();
-            if (!self.$caption.length) {
-                return;
-            }
-            if (isError) {
-                title = $('<div>' + self.msgValidationError + '</div>').text();
-                n = stack.length;
-                if (n) {
-                    cap = n === 1 && stack[0] ? self._getFileNames()[0] : self._getMsgSelected(n);
-                } else {
-                    cap = self._getMsgSelected(self.msgNo);
-                }
-                out = '<span class="' + self.msgValidationErrorClass + '">' + self.msgValidationErrorIcon +
-                    (isEmpty(content) ? cap : content) + '</span>';
-            } else {
-                if (isEmpty(content)) {
-                    return;
-                }
-                title = $('<div>' + content + '</div>').text();
-                out = self._getLayoutTemplate('icon') + title;
-            }
-            self.$caption.html(out);
-            self.$caption.attr('title', title);
-            self.$captionContainer.find('.file-caption-ellipsis').attr('title', title);
-        },
-        _createContainer: function () {
-            var self = this,
-                $container = $(document.createElement("div"))
-                    .attr({"class": 'file-input file-input-new'})
-                    .html(self._renderMain());
-            self.$element.before($container);
-            self._initBrowse($container);
-            return $container;
-        },
-        _refreshContainer: function () {
-            var self = this, $container = self.$container;
-            $container.before(self.$element);
-            $container.html(self._renderMain());
-            self._initBrowse($container);
-        },
-        _renderMain: function () {
-            var self = this, dropCss = (self.isUploadable && self.dropZoneEnabled) ? ' file-drop-zone' : 'file-drop-disabled',
-                close = !self.showClose ? '' : self._getLayoutTemplate('close'),
-                preview = !self.showPreview ? '' : self._getLayoutTemplate('preview')
-                    .replace(/\{class}/g, self.previewClass)
-                    .replace(/\{dropClass}/g, dropCss),
-                css = self.isDisabled ? self.captionClass + ' file-caption-disabled' : self.captionClass,
-                caption = self.captionTemplate.replace(/\{class}/g, css + ' kv-fileinput-caption');
-            return self.mainTemplate.replace(/\{class}/g, self.mainClass)
-                .replace(/\{preview}/g, preview)
-                .replace(/\{close}/g, close)
-                .replace(/\{caption}/g, caption)
-                .replace(/\{upload}/g, self._renderButton('upload'))
-                .replace(/\{remove}/g, self._renderButton('remove'))
-                .replace(/\{cancel}/g, self._renderButton('cancel'))
-                .replace(/\{browse}/g, self._renderButton('browse'));
-        },
-        _renderButton: function (type) {
-            var self = this, tmplt = self._getLayoutTemplate('btnDefault'), css = self[type + 'Class'],
-                title = self[type + 'Title'], icon = self[type + 'Icon'], label = self[type + 'Label'],
-                status = self.isDisabled ? ' disabled' : '', btnType = 'button';
-            switch (type) {
-                case 'remove':
-                    if (!self.showRemove) {
-                        return '';
-                    }
-                    break;
-                case 'cancel':
-                    if (!self.showCancel) {
-                        return '';
-                    }
-                    css += ' hide';
-                    break;
-                case 'upload':
-                    if (!self.showUpload) {
-                        return '';
-                    }
-                    if (self.isUploadable && !self.isDisabled) {
-                        tmplt = self._getLayoutTemplate('btnLink').replace('{href}', self.uploadUrl);
-                    } else {
-                        btnType = 'submit';
-                    }
-                    break;
-                case 'browse':
-                    tmplt = self._getLayoutTemplate('btnBrowse');
-                    break;
-                default:
-                    return '';
-            }
-            css += type === 'browse' ? ' btn-file' : ' fileinput-' + type + ' fileinput-' + type + '-button';
-            if (!isEmpty(label)) {
-                label = ' <span class="' + self.buttonLabelClass + '">' + label + '</span>';
-            }
-            return tmplt.replace('{type}', btnType)
-                .replace('{css}', css)
-                .replace('{title}', title)
-                .replace('{status}', status)
-                .replace('{icon}', icon)
-                .replace('{label}', label);
-        },
-        _renderThumbProgress: function () {
-            return '<div class="file-thumb-progress hide">' + this.progressTemplate.replace(/\{percent}/g,
-                    '0') + '</div>';
-        },
-        _renderFileFooter: function (caption, width) {
-            var self = this, config = self.fileActionSettings, footer, out, template = self._getLayoutTemplate(
-                'footer');
-            if (self.isUploadable) {
-                footer = template.replace(/\{actions}/g, self._renderFileActions(true, true, false, false, false));
-                out = footer.replace(/\{caption}/g, caption)
-                    .replace(/\{width}/g, width)
-                    .replace(/\{progress}/g, self._renderThumbProgress())
-                    .replace(/\{indicator}/g, config.indicatorNew)
-                    .replace(/\{indicatorTitle}/g, config.indicatorNewTitle);
-            } else {
-                out = template.replace(/\{actions}/g, '')
-                    .replace(/\{caption}/g, caption)
-                    .replace(/\{progress}/g, '')
-                    .replace(/\{width}/g, width)
-                    .replace(/\{indicator}/g, '')
-                    .replace(/\{indicatorTitle}/g, '');
-            }
-            out = replaceTags(out, self.previewThumbTags);
-            return out;
-        },
-        _renderFileActions: function (showUpload, showDelete, disabled, url, key) {
-            if (!showUpload && !showDelete) {
-                return '';
-            }
-            var self = this,
-                vUrl = url === false ? '' : ' data-url="' + url + '"',
-                vKey = key === false ? '' : ' data-key="' + key + '"',
-                btnDelete = self._getLayoutTemplate('actionDelete'),
-                btnUpload = '',
-                template = self._getLayoutTemplate('actions'),
-                otherButtons = self.otherActionButtons.replace(/\{dataKey}/g, vKey),
-                config = self.fileActionSettings,
-                removeClass = disabled ? config.removeClass + ' disabled' : config.removeClass;
-            btnDelete = btnDelete
-                .replace(/\{removeClass}/g, removeClass)
-                .replace(/\{removeIcon}/g, config.removeIcon)
-                .replace(/\{removeTitle}/g, config.removeTitle)
-                .replace(/\{dataUrl}/g, vUrl)
-                .replace(/\{dataKey}/g, vKey);
-            if (showUpload) {
-                btnUpload = self._getLayoutTemplate('actionUpload')
-                    .replace(/\{uploadClass}/g, config.uploadClass)
-                    .replace(/\{uploadIcon}/g, config.uploadIcon)
-                    .replace(/\{uploadTitle}/g, config.uploadTitle);
-            }
-            return template
-                .replace(/\{delete}/g, btnDelete)
-                .replace(/\{upload}/g, btnUpload)
-                .replace(/\{other}/g, otherButtons);
-        },
-        _browse: function (e) {
-            var self = this;
-            self._raise('filebrowse');
-            if (e && e.isDefaultPrevented()) {
-                return;
-            }
-            if (self.isError && !self.isUploadable) {
-                self.clear();
-            }
-            self.$captionContainer.focus();
-        },
-        _change: function (e) {
-            var self = this, $el = self.$element;
-            if (!self.isUploadable && isEmpty($el.val()) && self.fileInputCleared) { // IE 11 fix
-                self.fileInputCleared = false;
-                return;
-            }
-            self.fileInputCleared = false;
-            var tfiles, msg, total, isDragDrop = arguments.length > 1, isAjaxUpload = self.isUploadable, i = 0, f, n, len,
-                files = isDragDrop ? e.originalEvent.dataTransfer.files : $el.get(0).files, ctr = self.filestack.length,
-                isSingleUpload = isEmpty($el.attr('multiple')), flagSingle = (isSingleUpload && ctr > 0), folders = 0,
-                throwError = function (mesg, file, previewId, index) {
-                    var p1 = $.extend(true, {}, self._getOutData({}, {}, files), {id: previewId, index: index}),
-                        p2 = {id: previewId, index: index, file: file, files: files};
-                    return self.isUploadable ? self._showUploadError(mesg, p1) : self._showError(mesg, p2);
-                };
-            self.reader = null;
-            self._resetUpload();
-            self._hideFileIcon();
-            if (self.isUploadable) {
-                self.$container.find('.file-drop-zone .' + self.dropZoneTitleClass).remove();
-            }
-            if (isDragDrop) {
-                tfiles = [];
-                while (files[i]) {
-                    f = files[i];
-                    if (!f.type && f.size % 4096 === 0) {
-                        folders++;
-                    } else {
-                        tfiles.push(f);
-                    }
-                    i++;
-                }
-            } else {
-                if (e.target.files === undefined) {
-                    tfiles = e.target && e.target.value ? [
-                        {name: e.target.value.replace(/^.+\\/, '')}
-                    ] : [];
-                } else {
-                    tfiles = e.target.files;
-                }
-            }
-            if (isEmpty(tfiles) || tfiles.length === 0) {
-                if (!isAjaxUpload) {
-                    self.clear();
-                }
-                self._showFolderError(folders);
-                self._raise('fileselectnone');
-                return;
-            }
-            self._resetErrors();
-            len = tfiles.length;
-            total = self._getFileCount(self.isUploadable ? (self.getFileStack().length + len) : len);
-            if (self.maxFileCount > 0 && total > self.maxFileCount) {
-                if (!self.autoReplace || len > self.maxFileCount) {
-                    n = (self.autoReplace && len > self.maxFileCount) ? len : total;
-                    msg = self.msgFilesTooMany.replace('{m}', self.maxFileCount).replace('{n}', n);
-                    self.isError = throwError(msg, null, null, null);
-                    self.$captionContainer.find('.kv-caption-icon').hide();
-                    self._setCaption('', true);
-                    self.$container.removeClass('file-input-new file-input-ajax-new');
-                    return;
-                }
-                if (total > self.maxFileCount) {
-                    self._resetPreviewThumbs(isAjaxUpload);
-                }
-            } else {
-                if (!isAjaxUpload || flagSingle) {
-                    self._resetPreviewThumbs(false);
-                    if (flagSingle) {
-                        self.clearStack();
-                    }
-                } else {
-                    if (isAjaxUpload && ctr === 0 && (!previewCache.count(self.id) || self.overwriteInitial)) {
-                        self._resetPreviewThumbs(true);
-                    }
-                }
-            }
-            if (self.isPreviewable) {
-                self._readFiles(tfiles);
-            } else {
-                self._updateFileDetails(1);
-            }
-            self._showFolderError(folders);
-        },
-        _abort: function (params) {
-            var self = this, data;
-            if (self.ajaxAborted && typeof self.ajaxAborted === "object" && self.ajaxAborted.message !== undefined) {
-                data = $.extend(true, {}, self._getOutData(), params);
-                data.abortData = self.ajaxAborted.data || {};
-                data.abortMessage = self.ajaxAborted.message;
-                self.cancel();
-                self._setProgress(100, self.$progress, self.msgCancelled);
-                self._showUploadError(self.ajaxAborted.message, data, 'filecustomerror');
-                return true;
-            }
-            return false;
-        },
-        _resetFileStack: function () {
-            var self = this, i = 0, newstack = [], newnames = [];
-            self._getThumbs().each(function () {
-                var $thumb = $(this), ind = $thumb.attr('data-fileindex'),
-                    file = self.filestack[ind];
-                if (ind === -1) {
-                    return;
-                }
-                if (file !== undefined) {
-                    newstack[i] = file;
-                    newnames[i] = self._getFileName(file);
-                    $thumb.attr({
-                        'id': self.previewInitId + '-' + i,
-                        'data-fileindex': i
-                    });
-                    i++;
-                } else {
-                    $thumb.attr({
-                        'id': 'uploaded-' + uniqId(),
-                        'data-fileindex': '-1'
-                    });
-                }
-            });
-            self.filestack = newstack;
-            self.filenames = newnames;
-        },
-        clearStack: function () {
-            var self = this;
-            self.filestack = [];
-            self.filenames = [];
-            return self.$element;
-        },
-        updateStack: function (i, file) {
-            var self = this;
-            self.filestack[i] = file;
-            self.filenames[i] = self._getFileName(file);
-            return self.$element;
-        },
-        addToStack: function (file) {
-            var self = this;
-            self.filestack.push(file);
-            self.filenames.push(self._getFileName(file));
-            return self.$element;
-        },
-        getFileStack: function (skipNull) {
-            var self = this;
-            return self.filestack.filter(function (n) {
-                return (skipNull ? n !== undefined : n !== undefined && n !== null);
-            });
-        },
-        lock: function () {
-            var self = this;
-            self._resetErrors();
-            self.disable();
-            if (self.showRemove) {
-                addCss(self.$container.find('.fileinput-remove'), 'hide');
-            }
-            if (self.showCancel) {
-                self.$container.find('.fileinput-cancel').removeClass('hide');
-            }
-            self._raise('filelock', [self.filestack, self._getExtraData()]);
-            return self.$element;
-        },
-        unlock: function (reset) {
-            var self = this;
-            if (reset === undefined) {
-                reset = true;
-            }
-            self.enable();
-            if (self.showCancel) {
-                addCss(self.$container.find('.fileinput-cancel'), 'hide');
-            }
-            if (self.showRemove) {
-                self.$container.find('.fileinput-remove').removeClass('hide');
-            }
-            if (reset) {
-                self._resetFileStack();
-            }
-            self._raise('fileunlock', [self.filestack, self._getExtraData()]);
-            return self.$element;
-        },
-        cancel: function () {
-            var self = this, xhr = self.ajaxRequests, len = xhr.length, i;
-            if (len > 0) {
-                for (i = 0; i < len; i += 1) {
-                    self.cancelling = true;
-                    xhr[i].abort();
-                }
-            }
-            self._setProgressCancelled();
-            self._getThumbs().each(function () {
-                var $thumb = $(this), ind = $thumb.attr('data-fileindex');
-                $thumb.removeClass('file-uploading');
-                if (self.filestack[ind] !== undefined) {
-                    $thumb.find('.kv-file-upload').removeClass('disabled').removeAttr('disabled');
-                    $thumb.find('.kv-file-remove').removeClass('disabled').removeAttr('disabled');
-                }
-                self.unlock();
-            });
-            return self.$element;
-        },
-        clear: function () {
-            var self = this, cap;
-            self.$btnUpload.removeAttr('disabled');
-            self._getThumbs().find('video,audio,img').each(function () {
-                cleanMemory($(this));
-            });
-            self._resetUpload();
-            self.clearStack();
-            self._clearFileInput();
-            self._resetErrors(true);
-            self._raise('fileclear');
-            if (self._hasInitialPreview()) {
-                self._showFileIcon();
-                self._resetPreview();
-                self._initPreviewDeletes();
-                self.$container.removeClass('file-input-new');
-            } else {
-                self._getThumbs().each(function () {
-                    self._clearObjects($(this));
-                });
-                if (self.isUploadable) {
-                    previewCache.data[self.id] = {};
-                }
-                self.$preview.html('');
-                cap = (!self.overwriteInitial && self.initialCaption.length > 0) ? self.initialCaption : '';
-                self._setCaption(cap);
-                self.$caption.attr('title', '');
-                addCss(self.$container, 'file-input-new');
-                self._validateDefaultPreview();
-            }
-            if (self.$container.find('.file-preview-frame').length === 0) {
-                if (!self._initCaption()) {
-                    self.$captionContainer.find('.kv-caption-icon').hide();
-                }
-            }
-            self._hideFileIcon();
-            self._raise('filecleared');
-            self.$captionContainer.focus();
-            self._setFileDropZoneTitle();
-            return self.$element;
-        },
-        reset: function () {
-            var self = this;
-            self._resetPreview();
-            self.$container.find('.fileinput-filename').text('');
-            self._raise('filereset');
-            addCss(self.$container, 'file-input-new');
-            if (self.$preview.find('.file-preview-frame').length || self.isUploadable && self.dropZoneEnabled) {
-                self.$container.removeClass('file-input-new');
-            }
-            self._setFileDropZoneTitle();
-            self.clearStack();
-            self.formdata = {};
-            return self.$element;
-        },
-        disable: function () {
-            var self = this;
-            self.isDisabled = true;
-            self._raise('filedisabled');
-            self.$element.attr('disabled', 'disabled');
-            self.$container.find(".kv-fileinput-caption").addClass("file-caption-disabled");
-            self.$container.find(".btn-file, .fileinput-remove, .fileinput-upload, .file-preview-frame button").attr(
-                "disabled",
-                true);
-            self._initDragDrop();
-            return self.$element;
-        },
-        enable: function () {
-            var self = this;
-            self.isDisabled = false;
-            self._raise('fileenabled');
-            self.$element.removeAttr('disabled');
-            self.$container.find(".kv-fileinput-caption").removeClass("file-caption-disabled");
-            self.$container.find(
-                ".btn-file, .fileinput-remove, .fileinput-upload, .file-preview-frame button").removeAttr("disabled");
-            self._initDragDrop();
-            return self.$element;
-        },
-        upload: function () {
-            var self = this, totLen = self.getFileStack().length, params = {},
-                i, outData, len, hasExtraData = !$.isEmptyObject(self._getExtraData());
-            if (self.minFileCount > 0 && self._getFileCount(totLen) < self.minFileCount) {
-                self._noFilesError(params);
-                return;
-            }
-            if (!self.isUploadable || self.isDisabled || (totLen === 0 && !hasExtraData)) {
-                return;
-            }
-            self._resetUpload();
-            self.$progress.removeClass('hide');
-            self.uploadCount = 0;
-            self.uploadStatus = {};
-            self.uploadLog = [];
-            self.lock();
-            self._setProgress(2);
-            if (totLen === 0 && hasExtraData) {
-                self._uploadExtraOnly();
-                return;
-            }
-            len = self.filestack.length;
-            self.hasInitData = false;
-            if (self.uploadAsync) {
-                outData = self._getOutData();
-                self._raise('filebatchpreupload', [outData]);
-                self.fileBatchCompleted = false;
-                self.uploadCache = {content: [], config: [], tags: [], append: true};
-                self.uploadAsyncCount = self.getFileStack().length;
-                for (i = 0; i < len; i++) {
-                    self.uploadCache.content[i] = null;
-                    self.uploadCache.config[i] = null;
-                    self.uploadCache.tags[i] = null;
-                }
-                for (i = 0; i < len; i++) {
-                    if (self.filestack[i] !== undefined) {
-                        self._uploadSingle(i, self.filestack, true);
-                    }
-                }
-                return;
-            }
-            self._uploadBatch();
-            return self.$element;
-        },
-        destroy: function () {
-            var self = this, $cont = self.$container;
-            $cont.find('.file-drop-zone').off();
-            self.$element.insertBefore($cont).off(NAMESPACE).removeData();
-            $cont.off().remove();
-            return self.$element;
-        },
-        refresh: function (options) {
-            var self = this, $el = self.$element;
-            options = options ? $.extend(true, {}, self.options, options) : self.options;
-            self.destroy();
-            $el.fileinput(options);
-            if ($el.val()) {
-                $el.trigger('change.fileinput');
-            }
-            return $el;
-        }
-    };
-
-    $.fn.fileinput = function (option) {
-        if (!hasFileAPISupport() && !isIE(9)) {
-            return;
-        }
-        var args = Array.apply(null, arguments), retvals = [];
-        args.shift();
-        this.each(function () {
-            var self = $(this), data = self.data('fileinput'), options = typeof option === 'object' && option,
-                lang = options.language || self.data('language') || 'en', loc = {}, opts;
-
-            if (!data) {
-                if (lang !== 'en' && !isEmpty($.fn.fileinputLocales[lang])) {
-                    loc = $.fn.fileinputLocales[lang];
-                }
-                opts = $.extend(true, {}, $.fn.fileinput.defaults, $.fn.fileinputLocales.en, loc, options, self.data());
-                data = new FileInput(this, opts);
-                self.data('fileinput', data);
-            }
-
-            if (typeof option === 'string') {
-                retvals.push(data[option].apply(data, args));
-            }
-        });
-        switch (retvals.length) {
-            case 0:
-                return this;
-            case 1:
-                return retvals[0];
-            default:
-                return retvals;
-        }
-    };
-
-    $.fn.fileinput.defaults = {
-        language: 'en',
-        showCaption: true,
-        showPreview: true,
-        showRemove: true,
-        showUpload: true,
-        showCancel: true,
-        showClose: true,
-        showUploadedThumbs: true,
-        autoReplace: false,
-        mainClass: '',
-        previewClass: '',
-        captionClass: '',
-        mainTemplate: null,
-        initialCaption: '',
-        initialPreview: [],
-        initialPreviewDelimiter: '*$$*',
-        initialPreviewConfig: [],
-        initialPreviewThumbTags: [],
-        previewThumbTags: {},
-        initialPreviewShowDelete: true,
-        removeFromPreviewOnError: false,
-        deleteUrl: '',
-        deleteExtraData: {},
-        overwriteInitial: true,
-        layoutTemplates: defaultLayoutTemplates,
-        previewTemplates: defaultPreviewTemplates,
-        allowedPreviewTypes: null,
-        allowedPreviewMimeTypes: null,
-        allowedFileTypes: null,
-        allowedFileExtensions: null,
-        defaultPreviewContent: null,
-        customLayoutTags: {},
-        customPreviewTags: {},
-        previewSettings: defaultPreviewSettings,
-        fileTypeSettings: defaultFileTypeSettings,
-        previewFileIcon: '<i class="glyphicon glyphicon-file"></i>',
-        previewFileIconClass: 'file-icon-4x',
-        previewFileIconSettings: {},
-        previewFileExtSettings: {},
-        buttonLabelClass: 'hidden-xs',
-        browseIcon: '<i class="glyphicon glyphicon-folder-open"></i>&nbsp;',
-        browseClass: 'btn btn-primary',
-        removeIcon: '<i class="glyphicon glyphicon-trash"></i>',
-        removeClass: 'btn btn-default',
-        cancelIcon: '<i class="glyphicon glyphicon-ban-circle"></i>',
-        cancelClass: 'btn btn-default',
-        uploadIcon: '<i class="glyphicon glyphicon-upload"></i>',
-        uploadClass: 'btn btn-default',
-        uploadUrl: null,
-        uploadAsync: true,
-        uploadExtraData: {},
-        minImageWidth: null,
-        minImageHeight: null,
-        maxImageWidth: null,
-        maxImageHeight: null,
-        resizeImage: false,
-        resizePreference: 'width',
-        resizeQuality: 0.92,
-        resizeDefaultImageType: 'image/jpeg',
-        maxFileSize: 0,
-        maxFilePreviewSize: 25600, // 25 MB
-        minFileCount: 0,
-        maxFileCount: 0,
-        validateInitialCount: false,
-        msgValidationErrorClass: 'text-danger',
-        msgValidationErrorIcon: '<i class="glyphicon glyphicon-exclamation-sign"></i> ',
-        msgErrorClass: 'file-error-message',
-        progressThumbClass: "progress-bar progress-bar-success progress-bar-striped active",
-        progressClass: "progress-bar progress-bar-success progress-bar-striped active",
-        progressCompleteClass: "progress-bar progress-bar-success",
-        progressErrorClass: "progress-bar progress-bar-danger",
-        previewFileType: 'image',
-        zoomIndicator: '<i class="glyphicon glyphicon-zoom-in"></i>',
-        elCaptionContainer: null,
-        elCaptionText: null,
-        elPreviewContainer: null,
-        elPreviewImage: null,
-        elPreviewStatus: null,
-        elErrorContainer: null,
-        errorCloseButton: '<span class="close kv-error-close">&times;</span>',
-        slugCallback: null,
-        dropZoneEnabled: true,
-        dropZoneTitleClass: 'file-drop-zone-title',
-        fileActionSettings: {},
-        otherActionButtons: '',
-        textEncoding: 'UTF-8',
-        ajaxSettings: {},
-        ajaxDeleteSettings: {},
-        showAjaxErrorDetails: true
-    };
-
-    $.fn.fileinputLocales.en = {
-        fileSingle: 'file',
-        filePlural: 'files',
-        browseLabel: 'Browse &hellip;',
-        removeLabel: 'Remove',
-        removeTitle: 'Clear selected files',
-        cancelLabel: 'Cancel',
-        cancelTitle: 'Abort ongoing upload',
-        uploadLabel: 'Upload',
-        uploadTitle: 'Upload selected files',
-        msgNo: 'No',
-        msgCancelled: 'Cancelled',
-        msgZoomTitle: 'View details',
-        msgZoomModalHeading: 'Detailed Preview',
-        msgSizeTooLarge: 'File "{name}" (<b>{size} KB</b>) exceeds maximum allowed upload size of <b>{maxSize} KB</b>.',
-        msgFilesTooLess: 'You must select at least <b>{n}</b> {files} to upload.',
-        msgFilesTooMany: 'Number of files selected for upload <b>({n})</b> exceeds maximum allowed limit of <b>{m}</b>.',
-        msgFileNotFound: 'File "{name}" not found!',
-        msgFileSecured: 'Security restrictions prevent reading the file "{name}".',
-        msgFileNotReadable: 'File "{name}" is not readable.',
-        msgFilePreviewAborted: 'File preview aborted for "{name}".',
-        msgFilePreviewError: 'An error occurred while reading the file "{name}".',
-        msgInvalidFileType: 'Invalid type for file "{name}". Only "{types}" files are supported.',
-        msgInvalidFileExtension: 'Invalid extension for file "{name}". Only "{extensions}" files are supported.',
-        msgUploadAborted: 'The file upload was aborted',
-        msgValidationError: 'Validation Error',
-        msgLoading: 'Loading file {index} of {files} &hellip;',
-        msgProgress: 'Loading file {index} of {files} - {name} - {percent}% completed.',
-        msgSelected: '{n} {files} selected',
-        msgFoldersNotAllowed: 'Drag & drop files only! {n} folder(s) dropped were skipped.',
-        msgImageWidthSmall: 'Width of image file "{name}" must be at least {size} px.',
-        msgImageHeightSmall: 'Height of image file "{name}" must be at least {size} px.',
-        msgImageWidthLarge: 'Width of image file "{name}" cannot exceed {size} px.',
-        msgImageHeightLarge: 'Height of image file "{name}" cannot exceed {size} px.',
-        msgImageResizeError: 'Could not get the image dimensions to resize.',
-        msgImageResizeException: 'Error while resizing the image.<pre>{errors}</pre>',
-        dropZoneTitle: 'Drag & drop files here &hellip;'
-    };
-
-    $.fn.fileinput.Constructor = FileInput;
-
-    /**
-     * Convert automatically file inputs with class 'file' into a bootstrap fileinput control.
-     */
-    $(document).ready(function () {
-        var $input = $('input.file[type=file]');
-        if ($input.length) {
-            $input.fileinput();
-        }
-    });
-}));
+/*!
+ * bootstrap-fileinput v4.3.2
+ * http://plugins.krajee.com/file-input
+ *
+ * Author: Kartik Visweswaran
+ * Copyright: 2014 - 2016, Kartik Visweswaran, Krajee.com
+ *
+ * Licensed under the BSD 3-Clause
+ * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md
+ */
+(function (factory) {
+    "use strict";
+    if (typeof define === 'function' && define.amd) { // jshint ignore:line
+        // AMD. Register as an anonymous module.
+        define(['jquery'], factory); // jshint ignore:line
+    } else { // noinspection JSUnresolvedVariable
+        if (typeof module === 'object' && module.exports) { // jshint ignore:line
+            // Node/CommonJS
+            // noinspection JSUnresolvedVariable
+            module.exports = factory(require('jquery')); // jshint ignore:line
+        } else {
+            // Browser globals
+            factory(window.jQuery);
+        }
+    }
+}(function ($) {
+    "use strict";
+
+    $.fn.fileinputLocales = {};
+    $.fn.fileinputThemes = {};
+
+    var NAMESPACE, MODAL_ID, STYLE_SETTING, OBJECT_PARAMS, DEFAULT_PREVIEW, objUrl, compare, isIE, isEdge, handler,
+        previewCache, getNum, hasFileAPISupport, hasDragDropSupport, hasFileUploadSupport, addCss, tMain1, tMain2,
+        tPreview, tFileIcon, tClose, tCaption, tBtnDefault, tBtnLink, tBtnBrowse, tModalMain, tModal, tProgress,
+        tFooter, tActions, tActionDelete, tActionUpload, tActionZoom, tActionDrag, tTagBef, tTagBef1, tTagBef2, tTagAft,
+        tGeneric, tHtml, tImage, tText, tVideo, tAudio, tFlash, tObject, tPdf, tOther, defaultFileActionSettings,
+        defaultLayoutTemplates, defaultPreviewTemplates, defaultPreviewZoomSettings, defaultPreviewTypes, getElement,
+        defaultPreviewSettings, defaultFileTypeSettings, isEmpty, isArray, ifSet, uniqId, htmlEncode, replaceTags,
+        cleanMemory, findFileName, checkFullScreen, toggleFullScreen, moveArray, FileInput;
+
+    NAMESPACE = '.fileinput';
+    MODAL_ID = 'kvFileinputModal';
+    STYLE_SETTING = 'style="width:{width};height:{height};"';
+    OBJECT_PARAMS = '<param name="controller" value="true" />\n' +
+        '<param name="allowFullScreen" value="true" />\n' +
+        '<param name="allowScriptAccess" value="always" />\n' +
+        '<param name="autoPlay" value="false" />\n' +
+        '<param name="autoStart" value="false" />\n' +
+        '<param name="quality" value="high" />\n';
+    DEFAULT_PREVIEW = '<div class="file-preview-other">\n' +
+        '<span class="{previewFileIconClass}">{previewFileIcon}</span>\n' +
+        '</div>';
+    //noinspection JSUnresolvedVariable
+    objUrl = window.URL || window.webkitURL;
+    compare = function (input, str, exact) {
+        return input !== undefined && (exact ? input === str : input.match(str));
+    };
+    isIE = function (ver) {
+        // check for IE versions < 11
+        if (navigator.appName !== 'Microsoft Internet Explorer') {
+            return false;
+        }
+        if (ver === 10) {
+            return new RegExp('msie\\s' + ver, 'i').test(navigator.userAgent);
+        }
+        var div = document.createElement("div"), status;
+        div.innerHTML = "<!--[if IE " + ver + "]> <i></i> <![endif]-->";
+        status = div.getElementsByTagName("i").length;
+        document.body.appendChild(div);
+        div.parentNode.removeChild(div);
+        return status;
+    };
+    isEdge = function () {
+        return new RegExp('Edge\/[0-9]+', 'i').test(navigator.userAgent);
+    };
+    handler = function ($el, event, callback, skipNS) {
+        var ev = skipNS ? event : event.split(' ').join(NAMESPACE + ' ') + NAMESPACE;
+        $el.off(ev).on(ev, callback);
+    };
+    previewCache = {
+        data: {},
+        init: function (obj) {
+            var content = obj.initialPreview, id = obj.id;
+            if (content.length > 0 && !isArray(content)) {
+                content = content.split(obj.initialPreviewDelimiter);
+            }
+            previewCache.data[id] = {
+                content: content,
+                config: obj.initialPreviewConfig,
+                tags: obj.initialPreviewThumbTags,
+                delimiter: obj.initialPreviewDelimiter,
+                previewFileType: obj.initialPreviewFileType,
+                previewAsData: obj.initialPreviewAsData,
+                template: obj.previewGenericTemplate,
+                showZoom: obj.fileActionSettings.showZoom,
+                showDrag: obj.fileActionSettings.showDrag,
+                parseTemplate: function (cat, data, fname, ftype, pId, footer, ind) {
+                    var frameClass = ' file-preview-initial';
+                    return obj._generatePreviewTemplate(cat, data, fname, ftype, pId, false, frameClass, footer, ind);
+                },
+                msg: function (n) {
+                    return obj._getMsgSelected(n);
+                },
+                initId: obj.previewInitId,
+                footer: obj._getLayoutTemplate('footer').replace(/\{progress}/g, obj._renderThumbProgress()),
+                isDelete: obj.initialPreviewShowDelete,
+                caption: obj.initialCaption,
+                actions: function (showUpload, showDelete, showZoom, showDrag, disabled, url, key) {
+                    return obj._renderFileActions(showUpload, showDelete, showZoom, showDrag, disabled, url, key, true);
+                }
+            };
+        },
+        fetch: function (id) {
+            return previewCache.data[id].content.filter(function (n) {
+                return n !== null;
+            });
+        },
+        count: function (id, all) {
+            return !!previewCache.data[id] && !!previewCache.data[id].content ?
+                (all ? previewCache.data[id].content.length : previewCache.fetch(id).length) : 0;
+        },
+        get: function (id, i, isDisabled) {
+            var ind = 'init_' + i, data = previewCache.data[id], config = data.config[i], content = data.content[i],
+                previewId = data.initId + '-' + ind, out, $tmp, frameClass = ' file-preview-initial', cat, cap, ftr,
+                ftype, asData = ifSet('previewAsData', config, data.previewAsData);
+            isDisabled = isDisabled === undefined ? true : isDisabled;
+            /** @namespace config.frameAttr */
+            /** @namespace config.frameClass */
+            /** @namespace config.filetype */
+            if (!content) {
+                return '';
+            }
+            if (config && config.frameClass) {
+                frameClass += ' ' + config.frameClass;
+            }
+            if (asData) {
+                cat = data.previewAsData ? ifSet('type', config, data.previewFileType || 'generic') : 'generic';
+                cap = ifSet('caption', config);
+                ftr = previewCache.footer(id, i, isDisabled);
+                ftype = ifSet('filetype', config, cat);
+                out = data.parseTemplate(cat, content, cap, ftype, previewId, ftr, ind);
+            } else {
+                out = data.template
+                    .replace(/\{previewId}/g, previewId)
+                    .replace(/\{frameClass}/g, frameClass)
+                    .replace(/\{fileindex}/g, ind)
+                    .replace(/\{content}/g, data.content[i])
+                    .replace(/\{template}/g, ifSet('type', config, data.previewFileType))
+                    .replace(/\{footer}/g, previewCache.footer(id, i, isDisabled));
+            }
+            if (data.tags.length && data.tags[i]) {
+                out = replaceTags(out, data.tags[i]);
+            }
+            if (!isEmpty(config) && !isEmpty(config.frameAttr)) {
+                $tmp = $(document.createElement('div')).html(out);
+                $tmp.find('.file-preview-initial').attr(config.frameAttr);
+                out = $tmp.html();
+                $tmp.remove();
+            }
+            return out;
+        },
+        add: function (id, content, config, tags, append) {
+            var data = $.extend(true, {}, previewCache.data[id]), index;
+            if (!isArray(content)) {
+                content = content.split(data.delimiter);
+            }
+            if (append) {
+                index = data.content.push(content) - 1;
+                data.config[index] = config;
+                data.tags[index] = tags;
+            } else {
+                index = content.length - 1;
+                data.content = content;
+                data.config = config;
+                data.tags = tags;
+            }
+            previewCache.data[id] = data;
+            return index;
+        },
+        set: function (id, content, config, tags, append) {
+            var data = $.extend(true, {}, previewCache.data[id]), i, chk;
+            if (!content || !content.length) {
+                return;
+            }
+            if (!isArray(content)) {
+                content = content.split(data.delimiter);
+            }
+            chk = content.filter(function (n) {
+                return n !== null;
+            });
+            if (!chk.length) {
+                return;
+            }
+            if (data.content === undefined) {
+                data.content = [];
+            }
+            if (data.config === undefined) {
+                data.config = [];
+            }
+            if (data.tags === undefined) {
+                data.tags = [];
+            }
+            if (append) {
+                for (i = 0; i < content.length; i++) {
+                    if (content[i]) {
+                        data.content.push(content[i]);
+                    }
+                }
+                for (i = 0; i < config.length; i++) {
+                    if (config[i]) {
+                        data.config.push(config[i]);
+                    }
+                }
+                for (i = 0; i < tags.length; i++) {
+                    if (tags[i]) {
+                        data.tags.push(tags[i]);
+                    }
+                }
+            } else {
+                data.content = content;
+                data.config = config;
+                data.tags = tags;
+            }
+            previewCache.data[id] = data;
+        },
+        unset: function (id, index) {
+            var chk = previewCache.count(id);
+            if (!chk) {
+                return;
+            }
+            if (chk === 1) {
+                previewCache.data[id].content = [];
+                previewCache.data[id].config = [];
+                previewCache.data[id].tags = [];
+                return;
+            }
+            previewCache.data[id].content[index] = null;
+            previewCache.data[id].config[index] = null;
+            previewCache.data[id].tags[index] = null;
+        },
+        out: function (id) {
+            var html = '', data = previewCache.data[id], caption, len = previewCache.count(id, true);
+            if (len === 0) {
+                return {content: '', caption: ''};
+            }
+            for (var i = 0; i < len; i++) {
+                html += previewCache.get(id, i);
+            }
+            caption = data.msg(previewCache.count(id));
+            return {content: '<div class="file-initial-thumbs">' + html + '</div>', caption: caption};
+        },
+        footer: function (id, i, isDisabled) {
+            var data = previewCache.data[id];
+            isDisabled = isDisabled === undefined ? true : isDisabled;
+            if (data.config.length === 0 || isEmpty(data.config[i])) {
+                return '';
+            }
+            var config = data.config[i], caption = ifSet('caption', config), width = ifSet('width', config, 'auto'),
+                url = ifSet('url', config, false), key = ifSet('key', config, null),
+                showDel = ifSet('showDelete', config, true), showZoom = ifSet('showZoom', config, data.showZoom),
+                showDrag = ifSet('showDrag', config, data.showDrag), disabled = (url === false) && isDisabled,
+                actions = data.isDelete ? data.actions(false, showDel, showZoom, showDrag, disabled, url, key) : '',
+                footer = data.footer.replace(/\{actions}/g, actions);
+            return footer.replace(/\{caption}/g, caption)
+                .replace(/\{width}/g, width)
+                .replace(/\{indicator}/g, '')
+                .replace(/\{indicatorTitle}/g, '');
+        }
+    };
+    getNum = function (num, def) {
+        def = def || 0;
+        if (typeof num === "number") {
+            return num;
+        }
+        if (typeof num === "string") {
+            num = parseFloat(num);
+        }
+        return isNaN(num) ? def : num;
+    };
+    hasFileAPISupport = function () {
+        return !!(window.File && window.FileReader);
+    };
+    hasDragDropSupport = function () {
+        var div = document.createElement('div');
+        /** @namespace div.draggable */
+        /** @namespace div.ondragstart */
+        /** @namespace div.ondrop */
+        return !isIE(9) && !isEdge() && // Fix for MS Edge drag & drop support bug
+            (div.draggable !== undefined || (div.ondragstart !== undefined && div.ondrop !== undefined));
+    };
+    hasFileUploadSupport = function () {
+        return hasFileAPISupport() && window.FormData;
+    };
+    addCss = function ($el, css) {
+        $el.removeClass(css).addClass(css);
+    };
+    defaultFileActionSettings = {
+        showRemove: true,
+        showUpload: true,
+        showZoom: true,
+        showDrag: true,
+        removeIcon: '<i class="glyphicon glyphicon-trash text-danger"></i>',
+        removeClass: 'btn btn-xs btn-default',
+        removeTitle: 'Remove file',
+        uploadIcon: '<i class="glyphicon glyphicon-upload text-info"></i>',
+        uploadClass: 'btn btn-xs btn-default',
+        uploadTitle: 'Upload file',
+        zoomIcon: '<i class="glyphicon glyphicon-zoom-in"></i>',
+        zoomClass: 'btn btn-xs btn-default',
+        zoomTitle: 'View Details',
+        dragIcon: '<i class="glyphicon glyphicon-menu-hamburger"></i>',
+        dragClass: 'text-info',
+        dragTitle: 'Move / Rearrange',
+        dragSettings: {},
+        indicatorNew: '<i class="glyphicon glyphicon-hand-down text-warning"></i>',
+        indicatorSuccess: '<i class="glyphicon glyphicon-ok-sign text-success"></i>',
+        indicatorError: '<i class="glyphicon glyphicon-exclamation-sign text-danger"></i>',
+        indicatorLoading: '<i class="glyphicon glyphicon-hand-up text-muted"></i>',
+        indicatorNewTitle: 'Not uploaded yet',
+        indicatorSuccessTitle: 'Uploaded',
+        indicatorErrorTitle: 'Upload Error',
+        indicatorLoadingTitle: 'Uploading ...'
+    };
+    tMain1 = '{preview}\n' +
+        '<div class="kv-upload-progress hide"></div>\n' +
+        '<div class="input-group {class}">\n' +
+        '   {caption}\n' +
+        '   <div class="input-group-btn">\n' +
+        '       {remove}\n' +
+        '       {cancel}\n' +
+        '       {upload}\n' +
+        '       {browse}\n' +
+        '   </div>\n' +
+        '</div>';
+    tMain2 = '{preview}\n<div class="kv-upload-progress hide"></div>\n{remove}\n{cancel}\n{upload}\n{browse}\n';
+    tPreview = '<div class="file-preview {class}">\n' +
+        '    {close}' +
+        '    <div class="{dropClass}">\n' +
+        '    <div class="file-preview-thumbnails">\n' +
+        '    </div>\n' +
+        '    <div class="clearfix"></div>' +
+        '    <div class="file-preview-status text-center text-success"></div>\n' +
+        '    <div class="kv-fileinput-error"></div>\n' +
+        '    </div>\n' +
+        '</div>';
+    tClose = '<div class="close fileinput-remove">&times;</div>\n';
+    tFileIcon = '<i class="glyphicon glyphicon-file kv-caption-icon"></i>';
+    tCaption = '<div tabindex="500" class="form-control file-caption {class}">\n' +
+        '   <div class="file-caption-name"></div>\n' +
+        '</div>\n';
+    //noinspection HtmlUnknownAttribute
+    tBtnDefault = '<button type="{type}" tabindex="500" title="{title}" class="{css}" {status}>{icon} {label}</button>';
+    tBtnLink = '<a href="{href}" tabindex="500" title="{title}" class="{css}" {status}>{icon} {label}</a>';
+    tBtnBrowse = '<div tabindex="500" class="{css}" {status}>{icon} {label}</div>';
+    tModalMain = '<div id="' + MODAL_ID + '" class="file-zoom-dialog modal fade" tabindex="-1" aria-labelledby="' +
+        MODAL_ID + 'Label"></div>';
+    tModal = '<div class="modal-dialog modal-lg" role="document">\n' +
+        '  <div class="modal-content">\n' +
+        '    <div class="modal-header">\n' +
+        '      <div class="kv-zoom-actions pull-right">{toggleheader}{fullscreen}{borderless}{close}</div>\n' +
+        '      <h3 class="modal-title">{heading} <small><span class="kv-zoom-title"></span></small></h3>\n' +
+        '    </div>\n' +
+        '    <div class="modal-body">\n' +
+        '      <div class="floating-buttons"></div>\n' +
+        '      <div class="kv-zoom-body file-zoom-content"></div>\n' + '{prev} {next}\n' +
+        '    </div>\n' +
+        '  </div>\n' +
+        '</div>\n';
+    tProgress = '<div class="progress">\n' +
+        '    <div class="{class}" role="progressbar"' +
+        ' aria-valuenow="{percent}" aria-valuemin="0" aria-valuemax="100" style="width:{percent}%;">\n' +
+        '        {percent}%\n' +
+        '     </div>\n' +
+        '</div>';
+    tFooter = '<div class="file-thumbnail-footer">\n' +
+        '    <div class="file-footer-caption" title="{caption}">{caption}</div>\n' +
+        '    {progress} {actions}\n' +
+        '</div>';
+    tActions = '<div class="file-actions">\n' +
+        '    <div class="file-footer-buttons">\n' +
+        '        {upload} {delete} {zoom} {other}' +
+        '    </div>\n' +
+        '    {drag}\n' +
+        '    <div class="file-upload-indicator" title="{indicatorTitle}">{indicator}</div>\n' +
+        '    <div class="clearfix"></div>\n' +
+        '</div>';
+    tActionDelete = '<button type="button" class="kv-file-remove {removeClass}" ' +
+        'title="{removeTitle}" {dataUrl}{dataKey}>{removeIcon}</button>\n';
+    tActionUpload = '<button type="button" class="kv-file-upload {uploadClass}" title="{uploadTitle}">' +
+        '{uploadIcon}</button>';
+    tActionZoom = '<button type="button" class="kv-file-zoom {zoomClass}" title="{zoomTitle}">{zoomIcon}</button>';
+    tActionDrag = '<span class="file-drag-handle {dragClass}" title="{dragTitle}">{dragIcon}</span>';
+    tTagBef = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}"' +
+        ' data-template="{template}" draggable';
+    tTagBef1 = tTagBef + '><div class="kv-file-content">\n';
+    tTagBef2 = tTagBef + ' title="{caption}" ' + STYLE_SETTING + '><div class="kv-file-content">\n';
+    tTagAft = '</div>{footer}\n</div>\n';
+    tGeneric = '{content}\n';
+    tHtml = '<div class="kv-preview-data file-preview-html" title="{caption}" ' + STYLE_SETTING + '>{data}</div>\n';
+    tImage = '<img src="{data}" class="kv-preview-data file-preview-image" title="{caption}" alt="{caption}" ' +
+        STYLE_SETTING + '>\n';
+    tText = '<textarea class="kv-preview-data file-preview-text" title="{caption}" readonly ' + STYLE_SETTING +
+        '>{data}</textarea>\n';
+    tVideo = '<video class="kv-preview-data" width="{width}" height="{height}" controls>\n' +
+        '<source src="{data}" type="{type}">\n' + DEFAULT_PREVIEW + '\n</video>\n';
+    tAudio = '<audio class="kv-preview-data" controls>\n<source src="' + '{data}' + '" type="{type}">\n' +
+        DEFAULT_PREVIEW + '\n</audio>\n';
+    tFlash = '<object class="kv-preview-data file-object" type="application/x-shockwave-flash" ' +
+        'width="{width}" height="{height}" data="{data}">\n' + OBJECT_PARAMS + ' ' + DEFAULT_PREVIEW + '\n</object>\n';
+    tObject = '<object class="kv-preview-data file-object" data="{data}" type="{type}" width="{width}" height="{height}">\n' +
+        '<param name="movie" value="{caption}" />\n' + OBJECT_PARAMS + ' ' + DEFAULT_PREVIEW + '\n</object>\n';
+    tPdf = '<embed class="kv-preview-data" src="{data}" width="{width}" height="{height}" type="application/pdf">\n';
+    tOther = '<div class="kv-preview-data file-preview-other-frame">\n' + DEFAULT_PREVIEW + '\n</div>\n';
+    defaultLayoutTemplates = {
+        main1: tMain1,
+        main2: tMain2,
+        preview: tPreview,
+        close: tClose,
+        fileIcon: tFileIcon,
+        caption: tCaption,
+        modalMain: tModalMain,
+        modal: tModal,
+        progress: tProgress,
+        footer: tFooter,
+        actions: tActions,
+        actionDelete: tActionDelete,
+        actionUpload: tActionUpload,
+        actionZoom: tActionZoom,
+        actionDrag: tActionDrag,
+        btnDefault: tBtnDefault,
+        btnLink: tBtnLink,
+        btnBrowse: tBtnBrowse
+    };
+    defaultPreviewTemplates = {
+        generic: tTagBef1 + tGeneric + tTagAft,
+        html: tTagBef1 + tHtml + tTagAft,
+        image: tTagBef1 + tImage + tTagAft,
+        text: tTagBef1 + tText + tTagAft,
+        video: tTagBef2 + tVideo + tTagAft,
+        audio: tTagBef2 + tAudio + tTagAft,
+        flash: tTagBef2 + tFlash + tTagAft,
+        object: tTagBef2 + tObject + tTagAft,
+        pdf: tTagBef2 + tPdf + tTagAft,
+        other: tTagBef2 + tOther + tTagAft
+    };
+    defaultPreviewTypes = ['image', 'html', 'text', 'video', 'audio', 'flash', 'object'];
+    defaultPreviewSettings = {
+        image: {width: "auto", height: "160px"},
+        html: {width: "213px", height: "160px"},
+        text: {width: "213px", height: "160px"},
+        video: {width: "213px", height: "160px"},
+        audio: {width: "213px", height: "80px"},
+        flash: {width: "213px", height: "160px"},
+        object: {width: "160px", height: "160px"},
+        pdf: {width: "160px", height: "160px"},
+        other: {width: "160px", height: "160px"}
+    };
+    defaultPreviewZoomSettings = {
+        image: {width: "auto", height: "100%"},
+        html: {width: "100%", height: "100%", 'min-height': "480px"},
+        text: {width: "100%", height: "100%", 'min-height': "480px"},
+        video: {width: "auto", height: "100%", 'max-width': "100%"},
+        audio: {width: "100%", height: "30px"},
+        flash: {width: "auto", height: "480px"},
+        object: {width: "auto", height: "480px"},
+        pdf: {width: "100%", height: "100%", 'min-height': "480px"},
+        other: {width: "auto", height: "100%", 'min-height': "480px"}
+    };
+    defaultFileTypeSettings = {
+        image: function (vType, vName) {
+            return compare(vType, 'image.*') || compare(vName, /\.(gif|png|jpe?g)$/i);
+        },
+        html: function (vType, vName) {
+            return compare(vType, 'text/html') || compare(vName, /\.(htm|html)$/i);
+        },
+        text: function (vType, vName) {
+            return compare(vType, 'text.*') || compare(vType, /\.(xml|javascript)$/i) ||
+                compare(vName, /\.(txt|md|csv|nfo|ini|json|php|js|css)$/i);
+        },
+        video: function (vType, vName) {
+            return compare(vType, 'video.*') && (compare(vType, /(ogg|mp4|mp?g|webm|3gp)$/i) ||
+                compare(vName, /\.(og?|mp4|webm|mp?g|3gp)$/i));
+        },
+        audio: function (vType, vName) {
+            return compare(vType, 'audio.*') && (compare(vType, /(ogg|mp3|mp?g|wav)$/i) ||
+                compare(vName, /\.(og?|mp3|mp?g|wav)$/i));
+        },
+        flash: function (vType, vName) {
+            return compare(vType, 'application/x-shockwave-flash', true) || compare(vName, /\.(swf)$/i);
+        },
+        pdf: function (vType, vName) {
+            return compare(vType, 'application/pdf', true) || compare(vName, /\.(pdf)$/i);
+        },
+        object: function () {
+            return true;
+        },
+        other: function () {
+            return true;
+        }
+    };
+    isEmpty = function (value, trim) {
+        return value === undefined || value === null || value.length === 0 || (trim && $.trim(value) === '');
+    };
+    isArray = function (a) {
+        return Array.isArray(a) || Object.prototype.toString.call(a) === '[object Array]';
+    };
+    ifSet = function (needle, haystack, def) {
+        def = def || '';
+        return (typeof haystack === 'object' && needle in haystack) ? haystack[needle] : def;
+    };
+    getElement = function (options, param, value) {
+        return (isEmpty(options) || isEmpty(options[param])) ? value : $(options[param]);
+    };
+    uniqId = function () {
+        return Math.round(new Date().getTime() + (Math.random() * 100));
+    };
+    htmlEncode = function (str) {
+        return str.replace(/&/g, '&amp;')
+            .replace(/</g, '&lt;')
+            .replace(/>/g, '&gt;')
+            .replace(/"/g, '&quot;')
+            .replace(/'/g, '&apos;');
+    };
+    replaceTags = function (str, tags) {
+        var out = str;
+        if (!tags) {
+            return out;
+        }
+        $.each(tags, function (key, value) {
+            if (typeof value === "function") {
+                value = value();
+            }
+            out = out.split(key).join(value);
+        });
+        return out;
+    };
+    cleanMemory = function ($thumb) {
+        var data = $thumb.is('img') ? $thumb.attr('src') : $thumb.find('source').attr('src');
+        /** @namespace objUrl.revokeObjectURL */
+        objUrl.revokeObjectURL(data);
+    };
+    findFileName = function (filePath) {
+        var sepIndex = filePath.lastIndexOf('/');
+        if (sepIndex === -1) {
+            sepIndex = filePath.lastIndexOf('\\');
+        }
+        return filePath.split(filePath.substring(sepIndex, sepIndex + 1)).pop();
+    };
+    checkFullScreen = function () {
+        //noinspection JSUnresolvedVariable
+        return document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement ||
+            document.msFullscreenElement;
+    };
+    toggleFullScreen = function (maximize) {
+        if (maximize && !checkFullScreen()) {
+            /** @namespace document.documentElement.requestFullscreen */
+            /** @namespace document.documentElement.msRequestFullscreen */
+            /** @namespace document.documentElement.mozRequestFullScreen */
+            /** @namespace document.documentElement.webkitRequestFullscreen */
+            /** @namespace Element.ALLOW_KEYBOARD_INPUT */
+            if (document.documentElement.requestFullscreen) {
+                document.documentElement.requestFullscreen();
+            } else if (document.documentElement.msRequestFullscreen) {
+                document.documentElement.msRequestFullscreen();
+            } else if (document.documentElement.mozRequestFullScreen) {
+                document.documentElement.mozRequestFullScreen();
+            } else if (document.documentElement.webkitRequestFullscreen) {
+                document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
+            }
+        } else {
+            /** @namespace document.exitFullscreen */
+            /** @namespace document.msExitFullscreen */
+            /** @namespace document.mozCancelFullScreen */
+            /** @namespace document.webkitExitFullscreen */
+            if (document.exitFullscreen) {
+                document.exitFullscreen();
+            } else if (document.msExitFullscreen) {
+                document.msExitFullscreen();
+            } else if (document.mozCancelFullScreen) {
+                document.mozCancelFullScreen();
+            } else if (document.webkitExitFullscreen) {
+                document.webkitExitFullscreen();
+            }
+        }
+    };
+    moveArray = function (arr, oldIndex, newIndex) {
+        if (newIndex >= arr.length) {
+            var k = newIndex - arr.length;
+            while ((k--) + 1) {
+                arr.push(undefined);
+            }
+        }
+        arr.splice(newIndex, 0, arr.splice(oldIndex, 1)[0]);
+        return arr;
+    };
+    FileInput = function (element, options) {
+        var self = this;
+        self.$element = $(element);
+        if (!self._validate()) {
+            return;
+        }
+        self.isPreviewable = hasFileAPISupport();
+        self.isIE9 = isIE(9);
+        self.isIE10 = isIE(10);
+        if (self.isPreviewable || self.isIE9) {
+            self._init(options);
+            self._listen();
+        } else {
+            self.$element.removeClass('file-loading');
+        }
+    };
+    FileInput.prototype = {
+        constructor: FileInput,
+        _init: function (options) {
+            var self = this, $el = self.$element, t;
+            $.each(options, function (key, value) {
+                switch (key) {
+                    case 'minFileCount':
+                    case 'maxFileCount':
+                    case 'maxFileSize':
+                        self[key] = getNum(value);
+                        break;
+                    default:
+                        self[key] = value;
+                        break;
+                }
+            });
+            if (isEmpty(self.allowedPreviewTypes)) {
+                self.allowedPreviewTypes = defaultPreviewTypes;
+            }
+            self.fileInputCleared = false;
+            self.fileBatchCompleted = true;
+            if (!self.isPreviewable) {
+                self.showPreview = false;
+            }
+            self.uploadFileAttr = !isEmpty($el.attr('name')) ? $el.attr('name') : 'file_data';
+            self.reader = null;
+            self.formdata = {};
+            self.clearStack();
+            self.uploadCount = 0;
+            self.uploadStatus = {};
+            self.uploadLog = [];
+            self.uploadAsyncCount = 0;
+            self.loadedImages = [];
+            self.totalImagesCount = 0;
+            self.ajaxRequests = [];
+            self.isError = false;
+            self.ajaxAborted = false;
+            self.cancelling = false;
+            t = self._getLayoutTemplate('progress');
+            self.progressTemplate = t.replace('{class}', self.progressClass);
+            self.progressCompleteTemplate = t.replace('{class}', self.progressCompleteClass);
+            self.progressErrorTemplate = t.replace('{class}', self.progressErrorClass);
+            self.dropZoneEnabled = hasDragDropSupport() && self.dropZoneEnabled;
+            self.isDisabled = self.$element.attr('disabled') || self.$element.attr('readonly');
+            self.isUploadable = hasFileUploadSupport() && !isEmpty(self.uploadUrl);
+            self.slug = typeof options.slugCallback === "function" ? options.slugCallback : self._slugDefault;
+            self.mainTemplate = self.showCaption ? self._getLayoutTemplate('main1') : self._getLayoutTemplate('main2');
+            self.captionTemplate = self._getLayoutTemplate('caption');
+            self.previewGenericTemplate = self._getPreviewTemplate('generic');
+            if (self.resizeImage && (self.maxImageWidth || self.maxImageHeight)) {
+                self.imageCanvas = document.createElement('canvas');
+                self.imageCanvasContext = self.imageCanvas.getContext('2d');
+            }
+            if (isEmpty(self.$element.attr('id'))) {
+                self.$element.attr('id', uniqId());
+            }
+            if (self.$container === undefined) {
+                self.$container = self._createContainer();
+            } else {
+                self._refreshContainer();
+            }
+            self.$dropZone = self.$container.find('.file-drop-zone');
+            self.$progress = self.$container.find('.kv-upload-progress');
+            self.$btnUpload = self.$container.find('.fileinput-upload');
+            self.$captionContainer = getElement(options, 'elCaptionContainer', self.$container.find('.file-caption'));
+            self.$caption = getElement(options, 'elCaptionText', self.$container.find('.file-caption-name'));
+            self.$previewContainer = getElement(options, 'elPreviewContainer', self.$container.find('.file-preview'));
+            self.$preview = getElement(options, 'elPreviewImage', self.$container.find('.file-preview-thumbnails'));
+            self.$previewStatus = getElement(options, 'elPreviewStatus', self.$container.find('.file-preview-status'));
+            self.$errorContainer = getElement(options, 'elErrorContainer',
+                self.$previewContainer.find('.kv-fileinput-error'));
+            if (!isEmpty(self.msgErrorClass)) {
+                addCss(self.$errorContainer, self.msgErrorClass);
+            }
+            self.$errorContainer.hide();
+            self.fileActionSettings = $.extend(true, defaultFileActionSettings, options.fileActionSettings);
+            self.previewInitId = "preview-" + uniqId();
+            self.id = self.$element.attr('id');
+            previewCache.init(self);
+            self._initPreview(true);
+            self._initPreviewActions();
+            self.options = options;
+            self._setFileDropZoneTitle();
+            self.$element.removeClass('file-loading');
+            if (self.$element.attr('disabled')) {
+                self.disable();
+            }
+            self._initZoom();
+        },
+        _validate: function () {
+            var self = this, $exception;
+            if (self.$element.attr('type') === 'file') {
+                return true;
+            }
+            $exception = '<div class="help-block alert alert-warning">' +
+                '<h4>Invalid Input Type</h4>' +
+                'You must set an input <code>type = file</code> for <b>bootstrap-fileinput</b> plugin to initialize.' +
+                '</div>';
+            self.$element.after($exception);
+            return false;
+        },
+        _errorsExist: function () {
+            var self = this, $err;
+            if (self.$errorContainer.find('li').length) {
+                return true;
+            }
+            $err = $(document.createElement('div')).html(self.$errorContainer.html());
+            $err.find('span.kv-error-close').remove();
+            $err.find('ul').remove();
+            return $.trim($err.text()).length ? true : false;
+        },
+        _errorHandler: function (evt, caption) {
+            var self = this, err = evt.target.error;
+            /** @namespace err.NOT_FOUND_ERR */
+            /** @namespace err.SECURITY_ERR */
+            /** @namespace err.NOT_READABLE_ERR */
+            if (err.code === err.NOT_FOUND_ERR) {
+                self._showError(self.msgFileNotFound.replace('{name}', caption));
+            } else if (err.code === err.SECURITY_ERR) {
+                self._showError(self.msgFileSecured.replace('{name}', caption));
+            } else if (err.code === err.NOT_READABLE_ERR) {
+                self._showError(self.msgFileNotReadable.replace('{name}', caption));
+            } else if (err.code === err.ABORT_ERR) {
+                self._showError(self.msgFilePreviewAborted.replace('{name}', caption));
+            } else {
+                self._showError(self.msgFilePreviewError.replace('{name}', caption));
+            }
+        },
+        _addError: function (msg) {
+            var self = this, $error = self.$errorContainer;
+            if (msg && $error.length) {
+                $error.html(self.errorCloseButton + msg);
+                handler($error.find('.kv-error-close'), 'click', function () {
+                    $error.fadeOut('slow');
+                });
+            }
+        },
+        _resetErrors: function (fade) {
+            var self = this, $error = self.$errorContainer;
+            self.isError = false;
+            self.$container.removeClass('has-error');
+            $error.html('');
+            if (fade) {
+                $error.fadeOut('slow');
+            } else {
+                $error.hide();
+            }
+        },
+        _showFolderError: function (folders) {
+            var self = this, $error = self.$errorContainer, msg;
+            if (!folders) {
+                return;
+            }
+            msg = self.msgFoldersNotAllowed.replace(/\{n}/g, folders);
+            self._addError(msg);
+            addCss(self.$container, 'has-error');
+            $error.fadeIn(800);
+            self._raise('filefoldererror', [folders, msg]);
+        },
+        _showUploadError: function (msg, params, event) {
+            var self = this, $error = self.$errorContainer, ev = event || 'fileuploaderror', e = params && params.id ?
+            '<li data-file-id="' + params.id + '">' + msg + '</li>' : '<li>' + msg + '</li>';
+            if ($error.find('ul').length === 0) {
+                self._addError('<ul>' + e + '</ul>');
+            } else {
+                $error.find('ul').append(e);
+            }
+            $error.fadeIn(800);
+            self._raise(ev, [params, msg]);
+            self.$container.removeClass('file-input-new');
+            addCss(self.$container, 'has-error');
+            return true;
+        },
+        _showError: function (msg, params, event) {
+            var self = this, $error = self.$errorContainer, ev = event || 'fileerror';
+            params = params || {};
+            params.reader = self.reader;
+            self._addError(msg);
+            $error.fadeIn(800);
+            self._raise(ev, [params, msg]);
+            if (!self.isUploadable) {
+                self._clearFileInput();
+            }
+            self.$container.removeClass('file-input-new');
+            addCss(self.$container, 'has-error');
+            self.$btnUpload.attr('disabled', true);
+            return true;
+        },
+        _noFilesError: function (params) {
+            var self = this, label = self.minFileCount > 1 ? self.filePlural : self.fileSingle,
+                msg = self.msgFilesTooLess.replace('{n}', self.minFileCount).replace('{files}', label),
+                $error = self.$errorContainer;
+            self._addError(msg);
+            self.isError = true;
+            self._updateFileDetails(0);
+            $error.fadeIn(800);
+            self._raise('fileerror', [params, msg]);
+            self._clearFileInput();
+            addCss(self.$container, 'has-error');
+        },
+        _parseError: function (jqXHR, errorThrown, fileName) {
+            /** @namespace jqXHR.responseJSON */
+            var self = this, errMsg = $.trim(errorThrown + ''),
+                dot = errMsg.slice(-1) === '.' ? '' : '.',
+                text = jqXHR.responseJSON !== undefined && jqXHR.responseJSON.error !== undefined ?
+                    jqXHR.responseJSON.error : jqXHR.responseText;
+            if (self.cancelling && self.msgUploadAborted) {
+                errMsg = self.msgUploadAborted;
+            }
+            if (self.showAjaxErrorDetails && text) {
+                text = $.trim(text.replace(/\n\s*\n/g, '\n'));
+                text = text.length > 0 ? '<pre>' + text + '</pre>' : '';
+                errMsg += dot + text;
+            } else {
+                errMsg += dot;
+            }
+            self.cancelling = false;
+            return fileName ? '<b>' + fileName + ': </b>' + errMsg : errMsg;
+        },
+        _parseFileType: function (file) {
+            var self = this, isValid, vType, cat, i;
+            for (i = 0; i < defaultPreviewTypes.length; i += 1) {
+                cat = defaultPreviewTypes[i];
+                isValid = ifSet(cat, self.fileTypeSettings, defaultFileTypeSettings[cat]);
+                vType = isValid(file.type, file.name) ? cat : '';
+                if (!isEmpty(vType)) {
+                    return vType;
+                }
+            }
+            return 'other';
+        },
+        _parseFilePreviewIcon: function (content, fname) {
+            var self = this, ext, icn = self.previewFileIcon;
+            if (fname && fname.indexOf('.') > -1) {
+                ext = fname.split('.').pop();
+                if (self.previewFileIconSettings && self.previewFileIconSettings[ext]) {
+                    icn = self.previewFileIconSettings[ext];
+                }
+                if (self.previewFileExtSettings) {
+                    $.each(self.previewFileExtSettings, function (key, func) {
+                        if (self.previewFileIconSettings[key] && func(ext)) {
+                            icn = self.previewFileIconSettings[key];
+                        }
+                    });
+                }
+            }
+            if (content.indexOf('{previewFileIcon}') > -1) {
+                return content.replace(/\{previewFileIconClass}/g, self.previewFileIconClass).replace(
+                    /\{previewFileIcon}/g, icn);
+            }
+            return content;
+        },
+        _raise: function (event, params) {
+            var self = this, e = $.Event(event);
+            if (params !== undefined) {
+                self.$element.trigger(e, params);
+            } else {
+                self.$element.trigger(e);
+            }
+            if (e.isDefaultPrevented()) {
+                return false;
+            }
+            if (!e.result) {
+                return e.result;
+            }
+            switch (event) {
+                // ignore these events
+                case 'filebatchuploadcomplete':
+                case 'filebatchuploadsuccess':
+                case 'fileuploaded':
+                case 'fileclear':
+                case 'filecleared':
+                case 'filereset':
+                case 'fileerror':
+                case 'filefoldererror':
+                case 'fileuploaderror':
+                case 'filebatchuploaderror':
+                case 'filedeleteerror':
+                case 'filecustomerror':
+                case 'filesuccessremove':
+                    break;
+                // receive data response via `filecustomerror` event`
+                default:
+                    self.ajaxAborted = e.result;
+                    break;
+            }
+            return true;
+        },
+        _listenFullScreen: function (isFullScreen) {
+            var self = this, $modal = self.$modal, $btnFull, $btnBord;
+            if (!$modal || !$modal.length) {
+                return;
+            }
+            $btnFull = $modal && $modal.find('.btn-fullscreen');
+            $btnBord = $modal && $modal.find('.btn-borderless');
+            if (!$btnFull.length || !$btnBord.length) {
+                return;
+            }
+            $btnFull.removeClass('active').attr('aria-pressed', 'false');
+            $btnBord.removeClass('active').attr('aria-pressed', 'false');
+            if (isFullScreen) {
+                $btnFull.addClass('active').attr('aria-pressed', 'true');
+            } else {
+                $btnBord.addClass('active').attr('aria-pressed', 'true');
+            }
+            if ($modal.hasClass('file-zoom-fullscreen')) {
+                self._maximizeZoomDialog();
+            } else {
+                if (isFullScreen) {
+                    self._maximizeZoomDialog();
+                } else {
+                    $btnBord.removeClass('active').attr('aria-pressed', 'false');
+                }
+            }
+        },
+        _listen: function () {
+            var self = this, $el = self.$element, $form = $el.closest('form'), $cont = self.$container;
+            handler($el, 'change', $.proxy(self._change, self));
+            handler(self.$btnFile, 'click', $.proxy(self._browse, self));
+            handler($form, 'reset', $.proxy(self.reset, self));
+            handler($cont.find('.fileinput-remove:not([disabled])'), 'click', $.proxy(self.clear, self));
+            handler($cont.find('.fileinput-cancel'), 'click', $.proxy(self.cancel, self));
+            self._initDragDrop();
+            if (!self.isUploadable) {
+                handler($form, 'submit', $.proxy(self._submitForm, self));
+            }
+            handler(self.$container.find('.fileinput-upload'), 'click', $.proxy(self._uploadClick, self));
+            handler($(window), 'resize', function () {
+                self._listenFullScreen(screen.width === window.innerWidth && screen.height === window.innerHeight);
+            });
+            handler($(document), 'webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange',
+                function () {
+                    self._listenFullScreen(checkFullScreen());
+                });
+
+        },
+        _initDragDrop: function () {
+            var self = this, $zone = self.$dropZone;
+            if (self.isUploadable && self.dropZoneEnabled && self.showPreview) {
+                handler($zone, 'dragenter dragover', $.proxy(self._zoneDragEnter, self));
+                handler($zone, 'dragleave', $.proxy(self._zoneDragLeave, self));
+                handler($zone, 'drop', $.proxy(self._zoneDrop, self));
+                handler($(document), 'dragenter dragover drop', self._zoneDragDropInit);
+            }
+        },
+        _zoneDragDropInit: function (e) {
+            e.stopPropagation();
+            e.preventDefault();
+        },
+        _zoneDragEnter: function (e) {
+            var self = this, hasFiles = $.inArray('Files', e.originalEvent.dataTransfer.types) > -1;
+            self._zoneDragDropInit(e);
+            if (self.isDisabled || !hasFiles) {
+                e.originalEvent.dataTransfer.effectAllowed = 'none';
+                e.originalEvent.dataTransfer.dropEffect = 'none';
+                return;
+            }
+            addCss(self.$dropZone, 'file-highlighted');
+        },
+        _zoneDragLeave: function (e) {
+            var self = this;
+            self._zoneDragDropInit(e);
+            if (self.isDisabled) {
+                return;
+            }
+            self.$dropZone.removeClass('file-highlighted');
+        },
+        _zoneDrop: function (e) {
+            var self = this;
+            e.preventDefault();
+            /** @namespace e.originalEvent.dataTransfer */
+            if (self.isDisabled || isEmpty(e.originalEvent.dataTransfer.files)) {
+                return;
+            }
+            self._change(e, 'dragdrop');
+            self.$dropZone.removeClass('file-highlighted');
+        },
+        _uploadClick: function (e) {
+            var self = this, $btn = self.$container.find('.fileinput-upload'), $form,
+                isEnabled = !$btn.hasClass('disabled') && isEmpty($btn.attr('disabled'));
+            if (e && e.isDefaultPrevented()) {
+                return;
+            }
+            if (!self.isUploadable) {
+                if (isEnabled && $btn.attr('type') !== 'submit') {
+                    $form = $btn.closest('form');
+                    // downgrade to normal form submit if possible
+                    if ($form.length) {
+                        $form.trigger('submit');
+                    }
+                    e.preventDefault();
+                }
+                return;
+            }
+            e.preventDefault();
+            if (isEnabled) {
+                self.upload();
+            }
+        },
+        _submitForm: function () {
+            var self = this, $el = self.$element, files = $el.get(0).files;
+            if (files && self.minFileCount > 0 && self._getFileCount(files.length) < self.minFileCount) {
+                self._noFilesError({});
+                return false;
+            }
+            return !self._abort({});
+        },
+        _clearPreview: function () {
+            var self = this, $thumbs = !self.showUploadedThumbs ? self.$preview.find('.file-preview-frame') :
+                self.$preview.find('.file-preview-frame:not(.file-preview-success)');
+            $thumbs.remove();
+            if (!self.$preview.find('.file-preview-frame').length || !self.showPreview) {
+                self._resetUpload();
+            }
+            self._validateDefaultPreview();
+        },
+        _initSortable: function () {
+            var self = this, $preview = self.$preview, $el, settings;
+            if (!window.Sortable) {
+                return;
+            }
+            $el = $preview.find('.file-initial-thumbs');
+            settings = {
+                handle: '.drag-handle-init',
+                dataIdAttr: 'data-preview-id',
+                onSort: function (e) {
+                    var oldIndex = e.oldIndex, newIndex = e.newIndex;
+                    self.initialPreview = moveArray(self.initialPreview, oldIndex, newIndex);
+                    self.initialPreviewConfig = moveArray(self.initialPreviewConfig, oldIndex, newIndex);
+                    previewCache.init(self);
+                    self._raise('filesorted', {
+                        isInit: true,
+                        previewId: $(e.item).attr('id'),
+                        'oldIndex': oldIndex,
+                        'newIndex': newIndex,
+                        stack: self.initialPreviewConfig
+                    });
+                }
+            };
+            if ($el.data('sortable')) {
+                $el.sortable('destroy');
+            }
+            $.extend(true, settings, self.fileActionSettings.dragSettings);
+            $el.sortable(settings);
+        },
+        _initPreview: function (isInit) {
+            var self = this, cap = self.initialCaption || '', out;
+            if (!previewCache.count(self.id)) {
+                self._clearPreview();
+                if (isInit) {
+                    self._setCaption(cap);
+                } else {
+                    self._initCaption();
+                }
+                return;
+            }
+            out = previewCache.out(self.id);
+            cap = isInit && self.initialCaption ? self.initialCaption : out.caption;
+            self.$preview.html(out.content);
+            self._setCaption(cap);
+            self._initSortable();
+            if (!isEmpty(out.content)) {
+                self.$container.removeClass('file-input-new');
+            }
+        },
+        _getZoomButton: function (type) {
+            var self = this, label = self.previewZoomButtonIcons[type], css = self.previewZoomButtonClasses[type],
+                title = ' title="' + (self.previewZoomButtonTitles[type] || '') + '" ',
+                params = title + (type === 'close' ? ' data-dismiss="modal" aria-hidden="true"' : '');
+            if (type === 'fullscreen' || type === 'borderless' || type === 'toggleheader') {
+                params += ' data-toggle="button" aria-pressed="false" autocomplete="off"';
+            }
+            return '<button type="button" class="' + css + ' btn-' + type + '"' + params + '>' + label + '</button>';
+        },
+        _getModalContent: function () {
+            var self = this;
+            return self._getLayoutTemplate('modal')
+                .replace(/\{heading}/g, self.msgZoomModalHeading)
+                .replace(/\{prev}/g, self._getZoomButton('prev'))
+                .replace(/\{next}/g, self._getZoomButton('next'))
+                .replace(/\{toggleheader}/g, self._getZoomButton('toggleheader'))
+                .replace(/\{fullscreen}/g, self._getZoomButton('fullscreen'))
+                .replace(/\{borderless}/g, self._getZoomButton('borderless'))
+                .replace(/\{close}/g, self._getZoomButton('close'));
+        },
+        _listenModalEvent: function (event) {
+            var self = this, $modal = self.$modal, getParams = function (e) {
+                return {
+                    sourceEvent: e,
+                    previewId: $modal.data('previewId'),
+                    modal: $modal
+                };
+            };
+            $modal.on(event + '.bs.modal', function (e) {
+                var $btnFull = $modal.find('.btn-fullscreen'), $btnBord = $modal.find('.btn-borderless');
+                self._raise('filezoom' + event, getParams(e));
+                if (event === 'shown') {
+                    $btnBord.removeClass('active').attr('aria-pressed', 'false');
+                    $btnFull.removeClass('active').attr('aria-pressed', 'false');
+                    if ($modal.hasClass('file-zoom-fullscreen')) {
+                        self._maximizeZoomDialog();
+                        if (checkFullScreen()) {
+                            $btnFull.addClass('active').attr('aria-pressed', 'true');
+                        } else {
+                            $btnBord.addClass('active').attr('aria-pressed', 'true');
+                        }
+                    }
+                }
+            });
+        },
+        _initZoom: function () {
+            var self = this, $dialog, modalMain = self._getLayoutTemplate('modalMain'), modalId = '#' + MODAL_ID;
+            self.$modal = $(modalId);
+            if (!self.$modal || !self.$modal.length) {
+                $dialog = $(document.createElement('div')).html(modalMain).insertAfter(self.$container);
+                self.$modal = $('#' + MODAL_ID).insertBefore($dialog);
+                $dialog.remove();
+            }
+            self.$modal.html(self._getModalContent());
+            self._listenModalEvent('show');
+            self._listenModalEvent('shown');
+            self._listenModalEvent('hide');
+            self._listenModalEvent('hidden');
+            self._listenModalEvent('loaded');
+        },
+        _initZoomButtons: function () {
+            var self = this, previewId = self.$modal.data('previewId') || '', $first, $last,
+                frames = self.$preview.find('.file-preview-frame').toArray(), len = frames.length,
+                $prev = self.$modal.find('.btn-prev'), $next = self.$modal.find('.btn-next');
+
+            if (!len) {
+                return;
+            }
+            $first = $(frames[0]);
+            $last = $(frames[len - 1]);
+            $prev.removeAttr('disabled');
+            $next.removeAttr('disabled');
+            if ($first.length && $first.attr('id') === previewId) {
+                $prev.attr('disabled', true);
+            }
+            if ($last.length && $last.attr('id') === previewId) {
+                $next.attr('disabled', true);
+            }
+        },
+        _maximizeZoomDialog: function () {
+            var self = this, $modal = self.$modal, $head = $modal.find('.modal-header:visible'),
+                $foot = $modal.find('.modal-footer:visible'), $body = $modal.find('.modal-body'),
+                h = $(window).height(), diff = 0;
+            $modal.addClass('file-zoom-fullscreen');
+            if ($head && $head.length) {
+                h -= $head.outerHeight(true);
+            }
+            if ($foot && $foot.length) {
+                h -= $foot.outerHeight(true);
+            }
+            if ($body && $body.length) {
+                diff = $body.outerHeight(true) - $body.height();
+                h -= diff;
+            }
+            $modal.find('.kv-zoom-body').height(h);
+        },
+        _resizeZoomDialog: function (fullScreen) {
+            var self = this, $modal = self.$modal, $btnFull = $modal.find('.btn-fullscreen'),
+                $btnBord = $modal.find('.btn-borderless');
+            if ($modal.hasClass('file-zoom-fullscreen')) {
+                toggleFullScreen(false);
+                if (!fullScreen) {
+                    if (!$btnFull.hasClass('active')) {
+                        $modal.removeClass('file-zoom-fullscreen');
+                        self.$modal.find('.kv-zoom-body').css('height', self.zoomModalHeight);
+                    } else {
+                        $btnFull.removeClass('active').attr('aria-pressed', 'false');
+                    }
+                } else {
+                    if (!$btnFull.hasClass('active')) {
+                        $modal.removeClass('file-zoom-fullscreen');
+                        self._resizeZoomDialog(true);
+                        if ($btnBord.hasClass('active')) {
+                            $btnBord.removeClass('active').attr('aria-pressed', 'false');
+                        }
+                    }
+                }
+            } else {
+                if (!fullScreen) {
+                    self._maximizeZoomDialog();
+                    return;
+                }
+                toggleFullScreen(true);
+            }
+            $modal.focus();
+        },
+        _setZoomContent: function ($preview, animate) {
+            var self = this, $content, tmplt, body, title, $body, $dataEl, config, previewId = $preview.attr('id'),
+                $modal = self.$modal, $prev = $modal.find('.btn-prev'), $next = $modal.find('.btn-next'), $tmp,
+                $btnFull = $modal.find('.btn-fullscreen'), $btnBord = $modal.find('.btn-borderless'),
+                $btnTogh = $modal.find('.btn-toggleheader');
+            tmplt = $preview.data('template') || 'generic';
+            $content = $preview.find('.kv-file-content');
+            body = $content.length ? $content.html() : '';
+            title = $preview.find('.file-footer-caption').text() || '';
+            $modal.find('.kv-zoom-title').html(title);
+            $body = $modal.find('.kv-zoom-body');
+            if (animate) {
+                $tmp = $body.clone().insertAfter($body);
+                $body.html(body).hide();
+                $tmp.fadeOut('fast', function () {
+                    $body.fadeIn('fast');
+                    $tmp.remove();
+                });
+            } else {
+                $body.html(body);
+            }
+            config = self.previewZoomSettings[tmplt];
+            if (config) {
+                $dataEl = $body.find('.kv-preview-data');
+                addCss($dataEl, 'file-zoom-detail');
+                $.each(config, function (key, value) {
+                    $dataEl.css(key, value);
+                    if (($dataEl.attr('width') && key === 'width') || ($dataEl.attr('height') && key === 'height')) {
+                        $dataEl.removeAttr(key);
+                    }
+                });
+            }
+            $modal.data('previewId', previewId);
+            handler($prev, 'click', function () {
+                self._zoomSlideShow('prev', previewId);
+            });
+            handler($next, 'click', function () {
+                self._zoomSlideShow('next', previewId);
+            });
+            handler($btnFull, 'click', function () {
+                self._resizeZoomDialog(true);
+            });
+            handler($btnBord, 'click', function () {
+                self._resizeZoomDialog(false);
+            });
+            handler($btnTogh, 'click', function () {
+                var $header = $modal.find('.modal-header'), $floatBar = $modal.find('.modal-body .floating-buttons'),
+                    ht, $actions = $header.find('.kv-zoom-actions'), resize = function (height) {
+                        var $body = self.$modal.find('.kv-zoom-body'), h = self.zoomModalHeight;
+                        if ($modal.hasClass('file-zoom-fullscreen')) {
+                            h = $body.outerHeight(true);
+                            if (!height) {
+                                h = h - $header.outerHeight(true);
+                            }
+                        }
+                        $body.css('height', height ? h + height : h);
+                    };
+                if ($header.is(':visible')) {
+                    ht = $header.outerHeight(true);
+                    $header.slideUp('slow', function () {
+                        $actions.find('.btn').appendTo($floatBar);
+                        resize(ht);
+                    });
+                } else {
+                    $floatBar.find('.btn').appendTo($actions);
+                    $header.slideDown('slow', function () {
+                        resize();
+                    });
+                }
+                $modal.focus();
+            });
+            handler($modal, 'keydown', function (e) {
+                var key = e.which || e.keyCode;
+                if (key === 37 && !$prev.attr('disabled')) {
+                    self._zoomSlideShow('prev', previewId);
+                }
+                if (key === 39 && !$next.attr('disabled')) {
+                    self._zoomSlideShow('next', previewId);
+                }
+            });
+        },
+        _zoomPreview: function ($btn) {
+            var self = this, $preview;
+            if (!$btn.length) {
+                throw 'Cannot zoom to detailed preview!';
+            }
+            self.$modal.html(self._getModalContent());
+            $preview = $btn.closest('.file-preview-frame');
+            self._setZoomContent($preview);
+            self.$modal.modal('show');
+            self._initZoomButtons();
+        },
+        _zoomSlideShow: function (dir, previewId) {
+            var self = this, $btn = self.$modal.find('.kv-zoom-actions .btn-' + dir), $targFrame, i,
+                frames = self.$preview.find('.file-preview-frame').toArray(), len = frames.length, out;
+            if ($btn.attr('disabled')) {
+                return;
+            }
+            for (i = 0; i < len; i++) {
+                if ($(frames[i]).attr('id') === previewId) {
+                    out = dir === 'prev' ? i - 1 : i + 1;
+                    break;
+                }
+            }
+            if (out < 0 || out >= len || !frames[out]) {
+                return;
+            }
+            $targFrame = $(frames[out]);
+            if ($targFrame.length) {
+                self._setZoomContent($targFrame, true);
+            }
+            self._initZoomButtons();
+            self._raise('filezoom' + dir, {'previewId': previewId, modal: self.$modal});
+        },
+        _initZoomButton: function () {
+            var self = this;
+            self.$preview.find('.kv-file-zoom').each(function () {
+                var $el = $(this);
+                handler($el, 'click', function () {
+                    self._zoomPreview($el);
+                });
+            });
+        },
+        _initPreviewActions: function () {
+            var self = this, deleteExtraData = self.deleteExtraData || {},
+                resetProgress = function () {
+                    var hasFiles = self.isUploadable ? previewCache.count(self.id) : self.$element.get(0).files.length;
+                    if (self.$preview.find('.kv-file-remove').length === 0 && !hasFiles) {
+                        self.reset();
+                        self.initialCaption = '';
+                    }
+                };
+            self._initZoomButton();
+            self.$preview.find('.kv-file-remove').each(function () {
+                var $el = $(this), vUrl = $el.data('url') || self.deleteUrl, vKey = $el.data('key');
+                if (isEmpty(vUrl) || vKey === undefined) {
+                    return;
+                }
+                var $frame = $el.closest('.file-preview-frame'), cache = previewCache.data[self.id],
+                    settings, params, index = $frame.data('fileindex'), config, extraData;
+                index = parseInt(index.replace('init_', ''));
+                config = isEmpty(cache.config) && isEmpty(cache.config[index]) ? null : cache.config[index];
+                extraData = isEmpty(config) || isEmpty(config.extra) ? deleteExtraData : config.extra;
+                if (typeof extraData === "function") {
+                    extraData = extraData();
+                }
+                params = {id: $el.attr('id'), key: vKey, extra: extraData};
+                settings = $.extend(true, {}, {
+                    url: vUrl,
+                    type: 'POST',
+                    dataType: 'json',
+                    data: $.extend(true, {}, {key: vKey}, extraData),
+                    beforeSend: function (jqXHR) {
+                        self.ajaxAborted = false;
+                        self._raise('filepredelete', [vKey, jqXHR, extraData]);
+                        if (self.ajaxAborted) {
+                            jqXHR.abort();
+                        } else {
+                            addCss($frame, 'file-uploading');
+                            addCss($el, 'disabled');
+                        }
+                    },
+                    success: function (data, textStatus, jqXHR) {
+                        var n, cap;
+                        if (isEmpty(data) || isEmpty(data.error)) {
+                            previewCache.unset(self.id, index);
+                            n = previewCache.count(self.id);
+                            cap = n > 0 ? self._getMsgSelected(n) : '';
+                            self._raise('filedeleted', [vKey, jqXHR, extraData]);
+                            self._setCaption(cap);
+                        } else {
+                            params.jqXHR = jqXHR;
+                            params.response = data;
+                            self._showError(data.error, params, 'filedeleteerror');
+                            $frame.removeClass('file-uploading');
+                            $el.removeClass('disabled');
+                            resetProgress();
+                            return;
+                        }
+                        $frame.removeClass('file-uploading').addClass('file-deleted');
+                        $frame.fadeOut('slow', function () {
+                            self._clearObjects($frame);
+                            $frame.remove();
+                            resetProgress();
+                            if (!n && self.getFileStack().length === 0) {
+                                self._setCaption('');
+                                self.reset();
+                            }
+                        });
+                    },
+                    error: function (jqXHR, textStatus, errorThrown) {
+                        var errMsg = self._parseError(jqXHR, errorThrown);
+                        params.jqXHR = jqXHR;
+                        params.response = {};
+                        self._showError(errMsg, params, 'filedeleteerror');
+                        $frame.removeClass('file-uploading');
+                        resetProgress();
+                    }
+                }, self.ajaxDeleteSettings);
+                handler($el, 'click', function () {
+                    if (!self._validateMinCount()) {
+                        return false;
+                    }
+                    $.ajax(settings);
+                });
+            });
+        },
+        _clearObjects: function ($el) {
+            $el.find('video audio').each(function () {
+                this.pause();
+                $(this).remove();
+            });
+            $el.find('img object div').each(function () {
+                $(this).remove();
+            });
+        },
+        _clearFileInput: function () {
+            var self = this, $el = self.$element, $srcFrm, $tmpFrm, $tmpEl;
+            if (isEmpty($el.val())) {
+                return;
+            }
+            // Fix for IE ver < 11, that does not clear file inputs. Requires a sequence of steps to prevent IE
+            // crashing but still allow clearing of the file input.
+            if (self.isIE9 || self.isIE10) {
+                $srcFrm = $el.closest('form');
+                $tmpFrm = $(document.createElement('form'));
+                $tmpEl = $(document.createElement('div'));
+                $el.before($tmpEl);
+                if ($srcFrm.length) {
+                    $srcFrm.after($tmpFrm);
+                } else {
+                    $tmpEl.after($tmpFrm);
+                }
+                $tmpFrm.append($el).trigger('reset');
+                $tmpEl.before($el).remove();
+                $tmpFrm.remove();
+            } else { // normal input clear behavior for other sane browsers
+                $el.val('');
+            }
+            self.fileInputCleared = true;
+        },
+        _resetUpload: function () {
+            var self = this;
+            self.uploadCache = {content: [], config: [], tags: [], append: true};
+            self.uploadCount = 0;
+            self.uploadStatus = {};
+            self.uploadLog = [];
+            self.uploadAsyncCount = 0;
+            self.loadedImages = [];
+            self.totalImagesCount = 0;
+            self.$btnUpload.removeAttr('disabled');
+            self._setProgress(0);
+            addCss(self.$progress, 'hide');
+            self._resetErrors(false);
+            self.ajaxAborted = false;
+            self.ajaxRequests = [];
+            self._resetCanvas();
+        },
+        _resetCanvas: function () {
+            var self = this;
+            if (self.canvas && self.imageCanvasContext) {
+                self.imageCanvasContext.clearRect(0, 0, self.canvas.width, self.canvas.height);
+            }
+        },
+        _hasInitialPreview: function () {
+            var self = this;
+            return !self.overwriteInitial && previewCache.count(self.id);
+        },
+        _resetPreview: function () {
+            var self = this, out, cap;
+            if (previewCache.count(self.id)) {
+                out = previewCache.out(self.id);
+                self.$preview.html(out.content);
+                cap = self.initialCaption ? self.initialCaption : out.caption;
+                self._setCaption(cap);
+            } else {
+                self._clearPreview();
+                self._initCaption();
+            }
+            if (self.showPreview) {
+                self._initZoom();
+                self._initSortable();
+            }
+        },
+        _clearDefaultPreview: function () {
+            var self = this;
+            self.$preview.find('.file-default-preview').remove();
+        },
+        _validateDefaultPreview: function () {
+            var self = this;
+            if (!self.showPreview || isEmpty(self.defaultPreviewContent)) {
+                return;
+            }
+            self.$preview.html('<div class="file-default-preview">' + self.defaultPreviewContent + '</div>');
+            self.$container.removeClass('file-input-new');
+        },
+        _resetPreviewThumbs: function (isAjax) {
+            var self = this, out;
+            if (isAjax) {
+                self._clearPreview();
+                self.clearStack();
+                return;
+            }
+            if (self._hasInitialPreview()) {
+                out = previewCache.out(self.id);
+                self.$preview.html(out.content);
+                self._setCaption(out.caption);
+                self._initPreviewActions();
+            } else {
+                self._clearPreview();
+            }
+        },
+        _getLayoutTemplate: function (t) {
+            var self = this,
+                template = ifSet(t, self.layoutTemplates, defaultLayoutTemplates[t]);
+            if (isEmpty(self.customLayoutTags)) {
+                return template;
+            }
+            return replaceTags(template, self.customLayoutTags);
+        },
+        _getPreviewTemplate: function (t) {
+            var self = this,
+                template = ifSet(t, self.previewTemplates, defaultPreviewTemplates[t]);
+            if (isEmpty(self.customPreviewTags)) {
+                return template;
+            }
+            return replaceTags(template, self.customPreviewTags);
+        },
+        _getOutData: function (jqXHR, responseData, filesData) {
+            var self = this;
+            jqXHR = jqXHR || {};
+            responseData = responseData || {};
+            filesData = filesData || self.filestack.slice(0) || {};
+            return {
+                form: self.formdata,
+                files: filesData,
+                filenames: self.filenames,
+                extra: self._getExtraData(),
+                response: responseData,
+                reader: self.reader,
+                jqXHR: jqXHR
+            };
+        },
+        _getMsgSelected: function (n) {
+            var self = this, strFiles = n === 1 ? self.fileSingle : self.filePlural;
+            return self.msgSelected.replace('{n}', n).replace('{files}', strFiles);
+        },
+        _getThumbs: function (css) {
+            css = css || '';
+            return this.$preview.find('.file-preview-frame:not(.file-preview-initial)' + css);
+        },
+        _getExtraData: function (previewId, index) {
+            var self = this, data = self.uploadExtraData;
+            if (typeof self.uploadExtraData === "function") {
+                data = self.uploadExtraData(previewId, index);
+            }
+            return data;
+        },
+        _initXhr: function (xhrobj, previewId, fileCount) {
+            var self = this;
+            if (xhrobj.upload) {
+                xhrobj.upload.addEventListener('progress', function (event) {
+                    var pct = 0, position = event.loaded || event.position, total = event.total;
+                    /** @namespace event.lengthComputable */
+                    if (event.lengthComputable) {
+                        pct = Math.ceil(position / total * 100);
+                    }
+                    if (previewId) {
+                        self._setAsyncUploadStatus(previewId, pct, fileCount);
+                    } else {
+                        self._setProgress(Math.ceil(pct));
+                    }
+                }, false);
+            }
+            return xhrobj;
+        },
+        _ajaxSubmit: function (fnBefore, fnSuccess, fnComplete, fnError, previewId, index) {
+            var self = this, settings;
+            self._raise('filepreajax', [previewId, index]);
+            self._uploadExtra(previewId, index);
+            settings = $.extend(true, {}, {
+                xhr: function () {
+                    var xhrobj = $.ajaxSettings.xhr();
+                    return self._initXhr(xhrobj, previewId, self.getFileStack().length);
+                },
+                url: self.uploadUrl,
+                type: 'POST',
+                dataType: 'json',
+                data: self.formdata,
+                cache: false,
+                processData: false,
+                contentType: false,
+                beforeSend: fnBefore,
+                success: fnSuccess,
+                complete: fnComplete,
+                error: fnError
+            }, self.ajaxSettings);
+            self.ajaxRequests.push($.ajax(settings));
+        },
+        _initUploadSuccess: function (out, $thumb, allFiles) {
+            var self = this, append, data, index, $newThumb, content, config, tags, i;
+            if (!self.showPreview || typeof out !== 'object' || $.isEmptyObject(out)) {
+                return;
+            }
+            if (out.initialPreview !== undefined && out.initialPreview.length > 0) {
+                self.hasInitData = true;
+                content = out.initialPreview || [];
+                config = out.initialPreviewConfig || [];
+                tags = out.initialPreviewThumbTags || [];
+                append = out.append === undefined || out.append ? true : false;
+                if (content.length > 0 && !isArray(content)) {
+                    content = content.split(self.initialPreviewDelimiter);
+                }
+                self.overwriteInitial = false;
+                self.initialPreview.concat(content);
+                self.initialPreviewThumbTags.concat(tags);
+                self.initialPreviewConfig.concat(config);
+                if ($thumb !== undefined) {
+                    if (!allFiles) {
+                        index = previewCache.add(self.id, content, config[0], tags[0], append);
+                        data = previewCache.get(self.id, index, false);
+                        $newThumb = $(data).hide();
+                        $thumb.after($newThumb).fadeOut('slow', function () {
+                            $newThumb.fadeIn('slow').css('display:inline-block');
+                            self._initPreviewActions();
+                            self._clearFileInput();
+                            $thumb.remove();
+                        });
+                    } else {
+                        i = $thumb.attr('data-fileindex');
+                        self.uploadCache.content[i] = content[0];
+                        self.uploadCache.config[i] = config[0];
+                        self.uploadCache.tags[i] = tags[0];
+                        self.uploadCache.append = append;
+                    }
+                } else {
+                    previewCache.set(self.id, content, config, tags, append);
+                    self._initPreview();
+                    self._initPreviewActions();
+                }
+            }
+        },
+        _initSuccessThumbs: function () {
+            var self = this;
+            if (!self.showPreview) {
+                return;
+            }
+            self._getThumbs('.file-preview-success').each(function () {
+                var $thumb = $(this), $remove = $thumb.find('.kv-file-remove');
+                $remove.removeAttr('disabled');
+                handler($remove, 'click', function () {
+                    var out = self._raise('filesuccessremove', [$thumb.attr('id'), $thumb.data('fileindex')]);
+                    cleanMemory($thumb);
+                    if (out === false) {
+                        return;
+                    }
+                    $thumb.fadeOut('slow', function () {
+                        $thumb.remove();
+                        if (!self.$preview.find('.file-preview-frame').length) {
+                            self.reset();
+                        }
+                    });
+                });
+            });
+        },
+        _checkAsyncComplete: function () {
+            var self = this, previewId, i;
+            for (i = 0; i < self.filestack.length; i++) {
+                if (self.filestack[i]) {
+                    previewId = self.previewInitId + "-" + i;
+                    if ($.inArray(previewId, self.uploadLog) === -1) {
+                        return false;
+                    }
+                }
+            }
+            return (self.uploadAsyncCount === self.uploadLog.length);
+        },
+        _uploadExtra: function (previewId, index) {
+            var self = this, data = self._getExtraData(previewId, index);
+            if (data.length === 0) {
+                return;
+            }
+            $.each(data, function (key, value) {
+                self.formdata.append(key, value);
+            });
+        },
+        _uploadSingle: function (i, files, allFiles) {
+            var self = this, total = self.getFileStack().length, formdata = new FormData(), outData,
+                previewId = self.previewInitId + "-" + i, $thumb, chkComplete, $btnUpload, $btnDelete,
+                hasPostData = self.filestack.length > 0 || !$.isEmptyObject(self.uploadExtraData),
+                fnBefore, fnSuccess, fnComplete, fnError, updateUploadLog, params = {id: previewId, index: i};
+            self.formdata = formdata;
+            if (self.showPreview) {
+                $thumb = $('#' + previewId + ':not(.file-preview-initial)');
+                $btnUpload = $thumb.find('.kv-file-upload');
+                $btnDelete = $thumb.find('.kv-file-remove');
+                $('#' + previewId).find('.file-thumb-progress').removeClass('hide');
+            }
+            if (total === 0 || !hasPostData || ($btnUpload && $btnUpload.hasClass('disabled')) || self._abort(params)) {
+                return;
+            }
+            updateUploadLog = function (i, previewId) {
+                self.updateStack(i, undefined);
+                self.uploadLog.push(previewId);
+                if (self._checkAsyncComplete()) {
+                    self.fileBatchCompleted = true;
+                }
+            };
+            chkComplete = function () {
+                var u = self.uploadCache;
+                if (!self.fileBatchCompleted) {
+                    return;
+                }
+                setTimeout(function () {
+                    if (self.showPreview) {
+                        previewCache.set(self.id, u.content, u.config, u.tags, u.append);
+                        if (self.hasInitData) {
+                            self._initPreview();
+                            self._initPreviewActions();
+                        }
+                    }
+                    self.unlock();
+                    self._clearFileInput();
+                    self._raise('filebatchuploadcomplete', [self.filestack, self._getExtraData()]);
+                    self.uploadCount = 0;
+                    self.uploadStatus = {};
+                    self.uploadLog = [];
+                    self._setProgress(100);
+                }, 100);
+            };
+            fnBefore = function (jqXHR) {
+                outData = self._getOutData(jqXHR);
+                self.fileBatchCompleted = false;
+                if (self.showPreview) {
+                    if (!$thumb.hasClass('file-preview-success')) {
+                        self._setThumbStatus($thumb, 'Loading');
+                        addCss($thumb, 'file-uploading');
+                    }
+                    $btnUpload.attr('disabled', true);
+                    $btnDelete.attr('disabled', true);
+                }
+                if (!allFiles) {
+                    self.lock();
+                }
+                self._raise('filepreupload', [outData, previewId, i]);
+                $.extend(true, params, outData);
+                if (self._abort(params)) {
+                    jqXHR.abort();
+                    self._setProgressCancelled();
+                }
+            };
+            fnSuccess = function (data, textStatus, jqXHR) {
+                outData = self._getOutData(jqXHR, data);
+                $.extend(true, params, outData);
+                setTimeout(function () {
+                    if (isEmpty(data) || isEmpty(data.error)) {
+                        if (self.showPreview) {
+                            self._setThumbStatus($thumb, 'Success');
+                            $btnUpload.hide();
+                            self._initUploadSuccess(data, $thumb, allFiles);
+                        }
+                        self._raise('fileuploaded', [outData, previewId, i]);
+                        if (!allFiles) {
+                            self.updateStack(i, undefined);
+                        } else {
+                            updateUploadLog(i, previewId);
+                        }
+                    } else {
+                        self._showUploadError(data.error, params);
+                        self._setPreviewError($thumb, i);
+                        if (allFiles) {
+                            updateUploadLog(i, previewId);
+                        }
+                    }
+                }, 100);
+            };
+            fnComplete = function () {
+                setTimeout(function () {
+                    if (self.showPreview) {
+                        $btnUpload.removeAttr('disabled');
+                        $btnDelete.removeAttr('disabled');
+                        $thumb.removeClass('file-uploading');
+                    }
+                    if (!allFiles) {
+                        self.unlock(false);
+                        self._clearFileInput();
+                    } else {
+                        chkComplete();
+                    }
+                    self._initSuccessThumbs();
+                }, 100);
+            };
+            fnError = function (jqXHR, textStatus, errorThrown) {
+                var errMsg = self._parseError(jqXHR, errorThrown, (allFiles ? files[i].name : null));
+                setTimeout(function () {
+                    if (allFiles) {
+                        updateUploadLog(i, previewId);
+                    }
+                    self.uploadStatus[previewId] = 100;
+                    self._setPreviewError($thumb, i);
+                    $.extend(true, params, self._getOutData(jqXHR));
+                    self._showUploadError(errMsg, params);
+                }, 100);
+            };
+            formdata.append(self.uploadFileAttr, files[i], self.filenames[i]);
+            formdata.append('file_id', i);
+            self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, previewId, i);
+        },
+        _uploadBatch: function () {
+            var self = this, files = self.filestack, total = files.length, params = {}, fnBefore, fnSuccess, fnError,
+                fnComplete, hasPostData = self.filestack.length > 0 || !$.isEmptyObject(self.uploadExtraData),
+                setAllUploaded;
+            self.formdata = new FormData();
+            if (total === 0 || !hasPostData || self._abort(params)) {
+                return;
+            }
+            setAllUploaded = function () {
+                $.each(files, function (key) {
+                    self.updateStack(key, undefined);
+                });
+                self._clearFileInput();
+            };
+            fnBefore = function (jqXHR) {
+                self.lock();
+                var outData = self._getOutData(jqXHR);
+                if (self.showPreview) {
+                    self._getThumbs().each(function () {
+                        var $thumb = $(this), $btnUpload = $thumb.find('.kv-file-upload'),
+                            $btnDelete = $thumb.find('.kv-file-remove');
+                        if (!$thumb.hasClass('file-preview-success')) {
+                            self._setThumbStatus($thumb, 'Loading');
+                            addCss($thumb, 'file-uploading');
+                        }
+                        $btnUpload.attr('disabled', true);
+                        $btnDelete.attr('disabled', true);
+                    });
+                }
+                self._raise('filebatchpreupload', [outData]);
+                if (self._abort(outData)) {
+                    jqXHR.abort();
+                    self._setProgressCancelled();
+                }
+            };
+            fnSuccess = function (data, textStatus, jqXHR) {
+                /** @namespace data.errorkeys */
+                var outData = self._getOutData(jqXHR, data), $thumbs = self._getThumbs(), key = 0,
+                    keys = isEmpty(data) || isEmpty(data.errorkeys) ? [] : data.errorkeys;
+                if (isEmpty(data) || isEmpty(data.error)) {
+                    self._raise('filebatchuploadsuccess', [outData]);
+                    setAllUploaded();
+                    if (self.showPreview) {
+                        $thumbs.each(function () {
+                            var $thumb = $(this), $btnUpload = $thumb.find('.kv-file-upload');
+                            $thumb.find('.kv-file-upload').hide();
+                            self._setThumbStatus($thumb, 'Success');
+                            $thumb.removeClass('file-uploading');
+                            $btnUpload.removeAttr('disabled');
+                        });
+                        self._initUploadSuccess(data);
+                    } else {
+                        self.reset();
+                    }
+                } else {
+                    if (self.showPreview) {
+                        $thumbs.each(function () {
+                            var $thumb = $(this), $btnDelete = $thumb.find('.kv-file-remove'),
+                                $btnUpload = $thumb.find('.kv-file-upload');
+                            $thumb.removeClass('file-uploading');
+                            $btnUpload.removeAttr('disabled');
+                            $btnDelete.removeAttr('disabled');
+                            if (keys.length === 0) {
+                                self._setPreviewError($thumb);
+                                return;
+                            }
+                            if ($.inArray(key, keys) !== -1) {
+                                self._setPreviewError($thumb);
+                            } else {
+                                $thumb.find('.kv-file-upload').hide();
+                                self._setThumbStatus($thumb, 'Success');
+                                self.updateStack(key, undefined);
+                            }
+                            key++;
+                        });
+                        self._initUploadSuccess(data);
+                    }
+                    self._showUploadError(data.error, outData, 'filebatchuploaderror');
+                }
+            };
+            fnComplete = function () {
+                self._setProgress(100);
+                self.unlock();
+                self._initSuccessThumbs();
+                self._clearFileInput();
+                self._raise('filebatchuploadcomplete', [self.filestack, self._getExtraData()]);
+            };
+            fnError = function (jqXHR, textStatus, errorThrown) {
+                var outData = self._getOutData(jqXHR), errMsg = self._parseError(jqXHR, errorThrown);
+                self._showUploadError(errMsg, outData, 'filebatchuploaderror');
+                self.uploadFileCount = total - 1;
+                if (!self.showPreview) {
+                    return;
+                }
+                self._getThumbs().each(function () {
+                    var $thumb = $(this), key = $thumb.attr('data-fileindex');
+                    $thumb.removeClass('file-uploading');
+                    if (self.filestack[key] !== undefined) {
+                        self._setPreviewError($thumb);
+                    }
+                });
+                self._getThumbs().removeClass('file-uploading');
+                self._getThumbs(' .kv-file-upload').removeAttr('disabled');
+                self._getThumbs(' .kv-file-delete').removeAttr('disabled');
+            };
+            $.each(files, function (key, data) {
+                if (!isEmpty(files[key])) {
+                    self.formdata.append(self.uploadFileAttr, data, self.filenames[key]);
+                }
+            });
+            self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError);
+        },
+        _uploadExtraOnly: function () {
+            var self = this, params = {}, fnBefore, fnSuccess, fnComplete, fnError;
+            self.formdata = new FormData();
+            if (self._abort(params)) {
+                return;
+            }
+            fnBefore = function (jqXHR) {
+                self.lock();
+                var outData = self._getOutData(jqXHR);
+                self._raise('filebatchpreupload', [outData]);
+                self._setProgress(50);
+                params.data = outData;
+                params.xhr = jqXHR;
+                if (self._abort(params)) {
+                    jqXHR.abort();
+                    self._setProgressCancelled();
+                }
+            };
+            fnSuccess = function (data, textStatus, jqXHR) {
+                var outData = self._getOutData(jqXHR, data);
+                if (isEmpty(data) || isEmpty(data.error)) {
+                    self._raise('filebatchuploadsuccess', [outData]);
+                    self._clearFileInput();
+                    self._initUploadSuccess(data);
+                } else {
+                    self._showUploadError(data.error, outData, 'filebatchuploaderror');
+                }
+            };
+            fnComplete = function () {
+                self._setProgress(100);
+                self.unlock();
+                self._clearFileInput();
+                self._raise('filebatchuploadcomplete', [self.filestack, self._getExtraData()]);
+            };
+            fnError = function (jqXHR, textStatus, errorThrown) {
+                var outData = self._getOutData(jqXHR), errMsg = self._parseError(jqXHR, errorThrown);
+                params.data = outData;
+                self._showUploadError(errMsg, outData, 'filebatchuploaderror');
+            };
+            self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError);
+        },
+        _initFileActions: function () {
+            var self = this;
+            if (!self.showPreview) {
+                return;
+            }
+            self._initZoomButton();
+            self.$preview.find('.kv-file-remove').each(function () {
+                var $el = $(this), $frame = $el.closest('.file-preview-frame'), hasError,
+                    id = $frame.attr('id'), ind = $frame.attr('data-fileindex'), n, cap, status;
+                handler($el, 'click', function () {
+                    status = self._raise('filepreremove', [id, ind]);
+                    if (status === false || !self._validateMinCount()) {
+                        return false;
+                    }
+                    hasError = $frame.hasClass('file-preview-error');
+                    cleanMemory($frame);
+                    $frame.fadeOut('slow', function () {
+                        self.updateStack(ind, undefined);
+                        self._clearObjects($frame);
+                        $frame.remove();
+                        if (id && hasError) {
+                            self.$errorContainer.find('li[data-file-id="' + id + '"]').fadeOut('fast', function () {
+                                $(this).remove();
+                                if (!self._errorsExist()) {
+                                    self._resetErrors();
+                                }
+                            });
+                        }
+                        var filestack = self.getFileStack(true), len = filestack.length, chk = previewCache.count(
+                            self.id),
+                            hasThumb = self.showPreview && self.$preview.find('.file-preview-frame').length;
+                        self._clearFileInput();
+                        if (len === 0 && chk === 0 && !hasThumb) {
+                            self.reset();
+                        } else {
+                            n = chk + len;
+                            cap = n > 1 ? self._getMsgSelected(n) : (filestack[0] ? self._getFileNames()[0] : '');
+                            self._setCaption(cap);
+                        }
+                        self._raise('fileremoved', [id, ind]);
+                    });
+                });
+            });
+            self.$preview.find('.kv-file-upload').each(function () {
+                var $el = $(this);
+                handler($el, 'click', function () {
+                    var $frame = $el.closest('.file-preview-frame'),
+                        ind = $frame.attr('data-fileindex');
+                    if (!$frame.hasClass('file-preview-error')) {
+                        self._uploadSingle(ind, self.filestack, false);
+                    }
+                });
+            });
+        },
+        _hideFileIcon: function () {
+            if (this.overwriteInitial) {
+                this.$captionContainer.find('.kv-caption-icon').hide();
+            }
+        },
+        _showFileIcon: function () {
+            this.$captionContainer.find('.kv-caption-icon').show();
+        },
+        _generatePreviewTemplate: function (cat, data, fname, ftype, previewId, isError, frameClass, foot, ind) {
+            var self = this, tmplt = self._getPreviewTemplate(cat), content, sText, css = frameClass || '',
+                config = ifSet(cat, self.previewSettings, defaultPreviewSettings[cat]),
+                caption = self.slug(fname), footer = foot || self._renderFileFooter(caption, config.width);
+            ind = ind || previewId.slice(previewId.lastIndexOf('-') + 1);
+            tmplt = self._parseFilePreviewIcon(tmplt, fname.split('.').pop());
+            if (isError) {
+                footer += '<div class="file-other-error" title="' + self.fileActionSettings.indicatorErrorTitle +
+                    '">' + self.fileActionSettings.indicatorError + '</div>';
+            }
+            if (cat === 'text' || cat === 'html') {
+                sText = cat === 'text' ? htmlEncode(data) : data;
+                content = tmplt.replace(/\{previewId}/g, previewId).replace(/\{caption}/g, caption)
+                    .replace(/\{width}/g, config.width).replace(/\{height}/g, config.height)
+                    .replace(/\{frameClass}/g, css).replace(/\{cat}/g, ftype)
+                    .replace(/\{footer}/g, footer).replace(/\{fileindex}/g, ind)
+                    .replace(/\{data}/g, sText).replace(/\{template}/g, cat);
+            } else {
+                content = tmplt.replace(/\{previewId}/g, previewId).replace(/\{caption}/g, caption)
+                    .replace(/\{frameClass}/g, css).replace(/\{type}/g, ftype).replace(/\{fileindex}/g, ind)
+                    .replace(/\{width}/g, config.width).replace(/\{height}/g, config.height)
+                    .replace(/\{footer}/g, footer).replace(/\{data}/g, data).replace(/\{template}/g, cat);
+            }
+            return content;
+        },
+        _previewDefault: function (file, previewId, isDisabled) {
+            var self = this, $preview = self.$preview, $previewLive = $preview.find('.file-live-thumbs');
+            if (!self.showPreview) {
+                return;
+            }
+            var fname = file ? file.name : '', ftype = file ? file.type : '', content,
+                isError = isDisabled === true && !self.isUploadable, data = objUrl.createObjectURL(file);
+            self._clearDefaultPreview();
+            content = self._generatePreviewTemplate('other', data, fname, ftype, previewId, isError);
+            if (!$previewLive.length) {
+                $previewLive = $(document.createElement('div')).addClass('file-live-thumbs').appendTo($preview);
+            }
+            $previewLive.append("\n" + content);
+            if (isDisabled === true && self.isUploadable) {
+                self._setThumbStatus($('#' + previewId), 'Error');
+            }
+        },
+        _previewFile: function (i, file, theFile, previewId, data) {
+            if (!this.showPreview) {
+                return;
+            }
+            var self = this, cat = self._parseFileType(file), fname = file ? file.name : '', caption = self.slug(fname),
+                types = self.allowedPreviewTypes, mimes = self.allowedPreviewMimeTypes, $preview = self.$preview,
+                chkTypes = types && types.indexOf(cat) >= 0, $previewLive = $preview.find('.file-live-thumbs'),
+                iData = (cat === 'text' || cat === 'html' || cat === 'image') ? theFile.target.result : data, content,
+                chkMimes = mimes && mimes.indexOf(file.type) !== -1;
+            if (!$previewLive.length) {
+                $previewLive = $(document.createElement('div')).addClass('file-live-thumbs').appendTo($preview);
+            }
+            /** @namespace window.DOMPurify */
+            if (cat === 'html' && self.purifyHtml && window.DOMPurify) {
+                iData = window.DOMPurify.sanitize(iData);
+            }
+            if (chkTypes || chkMimes) {
+                content = self._generatePreviewTemplate(cat, iData, fname, file.type, previewId);
+                self._clearDefaultPreview();
+                $previewLive.append("\n" + content);
+                self._validateImage(i, previewId, caption, file.type);
+            } else {
+                self._previewDefault(file, previewId);
+            }
+            self._initSortable();
+        },
+        _slugDefault: function (text) {
+            return isEmpty(text) ? '' : String(text).replace(/[\-\[\]\/\{}:;#%=\(\)\*\+\?\\\^\$\|<>&"']/g, '_');
+        },
+        _readFiles: function (files) {
+            this.reader = new FileReader();
+            var self = this, $el = self.$element, $preview = self.$preview, reader = self.reader,
+                $container = self.$previewContainer, $status = self.$previewStatus, msgLoading = self.msgLoading,
+                msgProgress = self.msgProgress, previewInitId = self.previewInitId, numFiles = files.length,
+                settings = self.fileTypeSettings, ctr = self.filestack.length, readFile,
+                maxPreviewSize = self.maxFilePreviewSize && parseFloat(self.maxFilePreviewSize),
+                canPreview = $preview.length && (!maxPreviewSize || isNaN(maxPreviewSize)),
+                throwError = function (msg, file, previewId, index) {
+                    var p1 = $.extend(true, {}, self._getOutData({}, {}, files), {id: previewId, index: index}),
+                        p2 = {id: previewId, index: index, file: file, files: files};
+                    self._previewDefault(file, previewId, true);
+                    if (self.isUploadable) {
+                        self.addToStack(undefined);
+                    }
+                    setTimeout(function () {
+                        readFile(index + 1);
+                    }, 100);
+                    self._initFileActions();
+                    if (self.removeFromPreviewOnError) {
+                        $('#' + previewId).remove();
+                    }
+                    return self.isUploadable ? self._showUploadError(msg, p1) : self._showError(msg, p2);
+                };
+
+            self.loadedImages = [];
+            self.totalImagesCount = 0;
+
+            $.each(files, function (key, file) {
+                var func = self.fileTypeSettings.image || defaultFileTypeSettings.image;
+                if (func && func(file.type)) {
+                    self.totalImagesCount++;
+                }
+            });
+            readFile = function (i) {
+                if (isEmpty($el.attr('multiple'))) {
+                    numFiles = 1;
+                }
+                if (i >= numFiles) {
+                    if (self.isUploadable && self.filestack.length > 0) {
+                        self._raise('filebatchselected', [self.getFileStack()]);
+                    } else {
+                        self._raise('filebatchselected', [files]);
+                    }
+                    $container.removeClass('file-thumb-loading');
+                    $status.html('');
+                    return;
+                }
+                var node = ctr + i, previewId = previewInitId + "-" + node, isText, isImage, file = files[i],
+                    caption = self.slug(file.name), fileSize = (file.size || 0) / 1000, checkFile, fileExtExpr = '',
+                    previewData = objUrl.createObjectURL(file), fileCount = 0, j, msg, typ, chk,
+                    fileTypes = self.allowedFileTypes, strTypes = isEmpty(fileTypes) ? '' : fileTypes.join(', '),
+                    fileExt = self.allowedFileExtensions, strExt = isEmpty(fileExt) ? '' : fileExt.join(', ');
+                if (!isEmpty(fileExt)) {
+                    fileExtExpr = new RegExp('\\.(' + fileExt.join('|') + ')$', 'i');
+                }
+                fileSize = fileSize.toFixed(2);
+                if (self.maxFileSize > 0 && fileSize > self.maxFileSize) {
+                    msg = self.msgSizeTooLarge.replace('{name}', caption)
+                        .replace('{size}', fileSize)
+                        .replace('{maxSize}', self.maxFileSize);
+                    self.isError = throwError(msg, file, previewId, i);
+                    return;
+                }
+                if (!isEmpty(fileTypes) && isArray(fileTypes)) {
+                    for (j = 0; j < fileTypes.length; j += 1) {
+                        typ = fileTypes[j];
+                        checkFile = settings[typ];
+                        chk = (checkFile !== undefined && checkFile(file.type, caption));
+                        fileCount += isEmpty(chk) ? 0 : chk.length;
+                    }
+                    if (fileCount === 0) {
+                        msg = self.msgInvalidFileType.replace('{name}', caption).replace('{types}', strTypes);
+                        self.isError = throwError(msg, file, previewId, i);
+                        return;
+                    }
+                }
+                if (fileCount === 0 && !isEmpty(fileExt) && isArray(fileExt) && !isEmpty(fileExtExpr)) {
+                    chk = compare(caption, fileExtExpr);
+                    fileCount += isEmpty(chk) ? 0 : chk.length;
+                    if (fileCount === 0) {
+                        msg = self.msgInvalidFileExtension.replace('{name}', caption).replace('{extensions}',
+                            strExt);
+                        self.isError = throwError(msg, file, previewId, i);
+                        return;
+                    }
+                }
+                if (!self.showPreview) {
+                    self.addToStack(file);
+                    setTimeout(function () {
+                        readFile(i + 1);
+                    }, 100);
+                    self._raise('fileloaded', [file, previewId, i, reader]);
+                    return;
+                }
+                if (!canPreview && fileSize > maxPreviewSize) {
+                    $container.addClass('file-thumb-loading');
+                    self._previewDefault(file, previewId);
+                    self._initFileActions();
+                    self._updateFileDetails(numFiles);
+                    readFile(i + 1);
+                    return;
+                }
+                if ($preview.length && FileReader !== undefined) {
+                    $status.html(msgLoading.replace('{index}', i + 1).replace('{files}', numFiles));
+                    $container.addClass('file-thumb-loading');
+                    reader.onerror = function (evt) {
+                        self._errorHandler(evt, caption);
+                    };
+                    reader.onload = function (theFile) {
+                        self._previewFile(i, file, theFile, previewId, previewData);
+                        self._initFileActions();
+                    };
+                    reader.onloadend = function () {
+                        msg = msgProgress.replace('{index}', i + 1).replace('{files}', numFiles)
+                            .replace('{percent}', 50).replace('{name}', caption);
+                        setTimeout(function () {
+                            $status.html(msg);
+                            self._updateFileDetails(numFiles);
+                            readFile(i + 1);
+                        }, 100);
+                        self._raise('fileloaded', [file, previewId, i, reader]);
+                    };
+                    reader.onprogress = function (data) {
+                        if (data.lengthComputable) {
+                            var fact = (data.loaded / data.total) * 100, progress = Math.ceil(fact);
+                            msg = msgProgress.replace('{index}', i + 1).replace('{files}', numFiles)
+                                .replace('{percent}', progress).replace('{name}', caption);
+                            setTimeout(function () {
+                                $status.html(msg);
+                            }, 100);
+                        }
+                    };
+                    isText = ifSet('text', settings, defaultFileTypeSettings.text);
+                    isImage = ifSet('image', settings, defaultFileTypeSettings.image);
+
+                    if (isText(file.type, caption)) {
+                        reader.readAsText(file, self.textEncoding);
+                    } else {
+                        if (isImage(file.type, caption)) {
+                            reader.readAsDataURL(file);
+                        } else {
+                            reader.readAsArrayBuffer(file);
+                        }
+                    }
+                } else {
+                    self._previewDefault(file, previewId);
+                    setTimeout(function () {
+                        readFile(i + 1);
+                        self._updateFileDetails(numFiles);
+                    }, 100);
+                    self._raise('fileloaded', [file, previewId, i, reader]);
+                }
+                self.addToStack(file);
+            };
+
+            readFile(0);
+            self._updateFileDetails(numFiles, false);
+        },
+        _updateFileDetails: function (numFiles) {
+            var self = this, $el = self.$element, fileStack = self.getFileStack(),
+                name = (isIE(9) && findFileName($el.val())) ||
+                    ($el[0].files[0] && $el[0].files[0].name) || (fileStack.length && fileStack[0].name) || '',
+                label = self.slug(name), n = self.isUploadable ? fileStack.length : numFiles,
+                nFiles = previewCache.count(self.id) + n, log = n > 1 ? self._getMsgSelected(nFiles) : label;
+            if (self.isError) {
+                self.$previewContainer.removeClass('file-thumb-loading');
+                self.$previewStatus.html('');
+                self.$captionContainer.find('.kv-caption-icon').hide();
+            } else {
+                self._showFileIcon();
+            }
+            self._setCaption(log, self.isError);
+            self.$container.removeClass('file-input-new file-input-ajax-new');
+            if (arguments.length === 1) {
+                self._raise('fileselect', [numFiles, label]);
+            }
+            if (previewCache.count(self.id)) {
+                self._initPreviewActions();
+            }
+        },
+        _setThumbStatus: function ($thumb, status) {
+            var self = this;
+            if (!self.showPreview) {
+                return;
+            }
+            var icon = 'indicator' + status, msg = icon + 'Title',
+                css = 'file-preview-' + status.toLowerCase(),
+                $indicator = $thumb.find('.file-upload-indicator'),
+                config = self.fileActionSettings;
+            $thumb.removeClass('file-preview-success file-preview-error file-preview-loading');
+            if (status === 'Error') {
+                $thumb.find('.kv-file-upload').attr('disabled', true);
+            }
+            if (status === 'Success') {
+                $thumb.find('.file-drag-handle').remove();
+                $indicator.css('margin-left', 0);
+            }
+            $indicator.html(config[icon]);
+            $indicator.attr('title', config[msg]);
+            $thumb.addClass(css);
+        },
+        _setProgressCancelled: function () {
+            var self = this;
+            self._setProgress(100, self.$progress, self.msgCancelled);
+        },
+        _setProgress: function (p, $el, error) {
+            var self = this, pct = Math.min(p, 100), template = pct < 100 ? self.progressTemplate :
+                (error ? self.progressErrorTemplate : self.progressCompleteTemplate);
+            $el = $el || self.$progress;
+            if (!isEmpty(template)) {
+                $el.html(template.replace(/\{percent}/g, pct));
+                if (error) {
+                    $el.find('[role="progressbar"]').html(error);
+                }
+            }
+        },
+        _setFileDropZoneTitle: function () {
+            var self = this, $zone = self.$container.find('.file-drop-zone');
+            $zone.find('.' + self.dropZoneTitleClass).remove();
+            if (!self.isUploadable || !self.showPreview || $zone.length === 0 || self.getFileStack().length > 0 || !self.dropZoneEnabled) {
+                return;
+            }
+            if ($zone.find('.file-preview-frame').length === 0 && isEmpty(self.defaultPreviewContent)) {
+                $zone.prepend('<div class="' + self.dropZoneTitleClass + '">' + self.dropZoneTitle + '</div>');
+            }
+            self.$container.removeClass('file-input-new');
+            addCss(self.$container, 'file-input-ajax-new');
+        },
+        _setAsyncUploadStatus: function (previewId, pct, total) {
+            var self = this, sum = 0;
+            self._setProgress(pct, $('#' + previewId).find('.file-thumb-progress'));
+            self.uploadStatus[previewId] = pct;
+            $.each(self.uploadStatus, function (key, value) {
+                sum += value;
+            });
+            self._setProgress(Math.ceil(sum / total));
+
+        },
+        _validateMinCount: function () {
+            var self = this, len = self.isUploadable ? self.getFileStack().length : self.$element.get(0).files.length;
+            if (self.validateInitialCount && self.minFileCount > 0 && self._getFileCount(
+                    len - 1) < self.minFileCount) {
+                self._noFilesError({});
+                return false;
+            }
+            return true;
+        },
+        _getFileCount: function (fileCount) {
+            var self = this, addCount = 0;
+            if (self.validateInitialCount && !self.overwriteInitial) {
+                addCount = previewCache.count(self.id);
+                fileCount += addCount;
+            }
+            return fileCount;
+        },
+        _getFileName: function (file) {
+            return file && file.name ? this.slug(file.name) : undefined;
+        },
+        _getFileNames: function (skipNull) {
+            var self = this;
+            return self.filenames.filter(function (n) {
+                return (skipNull ? n !== undefined : n !== undefined && n !== null);
+            });
+        },
+        _setPreviewError: function ($thumb, i, val) {
+            var self = this;
+            if (i) {
+                self.updateStack(i, val);
+            }
+            if (self.removeFromPreviewOnError) {
+                $thumb.remove();
+            } else {
+                self._setThumbStatus($thumb, 'Error');
+            }
+        },
+        _checkDimensions: function (i, chk, $img, $thumb, fname, type, params) {
+            var self = this, msg, dim, tag = chk === 'Small' ? 'min' : 'max', limit = self[tag + 'Image' + type],
+                $imgEl, isValid;
+            if (isEmpty(limit) || !$img.length) {
+                return;
+            }
+            $imgEl = $img[0];
+            dim = (type === 'Width') ? $imgEl.naturalWidth || $imgEl.width : $imgEl.naturalHeight || $imgEl.height;
+            isValid = chk === 'Small' ? dim >= limit : dim <= limit;
+            if (isValid) {
+                return;
+            }
+            msg = self['msgImage' + type + chk].replace('{name}', fname).replace('{size}', limit);
+            self._showUploadError(msg, params);
+            self._setPreviewError($thumb, i, null);
+        },
+        _validateImage: function (i, previewId, fname, ftype) {
+            var self = this, $preview = self.$preview, params, w1, w2,
+                $thumb = $preview.find("#" + previewId), $img = $thumb.find('img');
+            fname = fname || 'Untitled';
+            if (!$img.length) {
+                return;
+            }
+            handler($img, 'load', function () {
+                w1 = $thumb.width();
+                w2 = $preview.width();
+                if (w1 > w2) {
+                    $img.css('width', '100%');
+                    $thumb.css('width', '97%');
+                }
+                params = {ind: i, id: previewId};
+                self._checkDimensions(i, 'Small', $img, $thumb, fname, 'Width', params);
+                self._checkDimensions(i, 'Small', $img, $thumb, fname, 'Height', params);
+                if (!self.resizeImage) {
+                    self._checkDimensions(i, 'Large', $img, $thumb, fname, 'Width', params);
+                    self._checkDimensions(i, 'Large', $img, $thumb, fname, 'Height', params);
+                }
+                self._raise('fileimageloaded', [previewId]);
+                self.loadedImages.push({ind: i, img: $img, thumb: $thumb, pid: previewId, typ: ftype});
+                self._validateAllImages();
+                objUrl.revokeObjectURL($img.attr('src'));
+            });
+        },
+        _validateAllImages: function () {
+            var self = this, i, config, $img, $thumb, pid, ind, params = {}, errFunc;
+            if (self.loadedImages.length !== self.totalImagesCount) {
+                return;
+            }
+            self._raise('fileimagesloaded');
+            if (!self.resizeImage) {
+                return;
+            }
+            errFunc = self.isUploadable ? self._showUploadError : self._showError;
+            for (i = 0; i < self.loadedImages.length; i++) {
+                config = self.loadedImages[i];
+                $img = config.img;
+                $thumb = config.thumb;
+                pid = config.pid;
+                ind = config.ind;
+                params = {id: pid, 'index': ind};
+                if (!self._getResizedImage($img[0], config.typ, pid, ind)) {
+                    errFunc(self.msgImageResizeError, params, 'fileimageresizeerror');
+                    self._setPreviewError($thumb, ind);
+                }
+            }
+            self._raise('fileimagesresized');
+        },
+        _getResizedImage: function (image, type, pid, ind) {
+            var self = this, width = image.naturalWidth, height = image.naturalHeight, ratio = 1,
+                maxWidth = self.maxImageWidth || width, maxHeight = self.maxImageHeight || height,
+                isValidImage = (width && height), chkWidth, chkHeight,
+                canvas = self.imageCanvas, context = self.imageCanvasContext;
+            if (!isValidImage) {
+                return false;
+            }
+            if (width === maxWidth && height === maxHeight) {
+                return true;
+            }
+            type = type || self.resizeDefaultImageType;
+            chkWidth = width > maxWidth;
+            chkHeight = height > maxHeight;
+            if (self.resizePreference === 'width') {
+                ratio = chkWidth ? maxWidth / width : (chkHeight ? maxHeight / height : 1);
+            } else {
+                ratio = chkHeight ? maxHeight / height : (chkWidth ? maxWidth / width : 1);
+            }
+            self._resetCanvas();
+            width *= ratio;
+            height *= ratio;
+            canvas.width = width;
+            canvas.height = height;
+            try {
+                context.drawImage(image, 0, 0, width, height);
+                canvas.toBlob(function (blob) {
+                    self._raise('fileimageresized', [pid, ind]);
+                    self.filestack[ind] = blob;
+                }, type, self.resizeQuality);
+                return true;
+            }
+            catch (err) {
+                return false;
+            }
+        },
+        _initBrowse: function ($container) {
+            var self = this;
+            self.$btnFile = $container.find('.btn-file');
+            self.$btnFile.append(self.$element);
+        },
+        _initCaption: function () {
+            var self = this, cap = self.initialCaption || '';
+            if (self.overwriteInitial || isEmpty(cap)) {
+                self.$caption.html('');
+                return false;
+            }
+            self._setCaption(cap);
+            return true;
+        },
+        _setCaption: function (content, isError) {
+            var self = this, title, out, n, cap, stack = self.getFileStack();
+            if (!self.$caption.length) {
+                return;
+            }
+            if (isError) {
+                title = $('<div>' + self.msgValidationError + '</div>').text();
+                n = stack.length;
+                if (n) {
+                    cap = n === 1 && stack[0] ? self._getFileNames()[0] : self._getMsgSelected(n);
+                } else {
+                    cap = self._getMsgSelected(self.msgNo);
+                }
+                out = '<span class="' + self.msgValidationErrorClass + '">' + self.msgValidationErrorIcon +
+                    (isEmpty(content) ? cap : content) + '</span>';
+            } else {
+                if (isEmpty(content)) {
+                    return;
+                }
+                title = $('<div>' + content + '</div>').text();
+                out = self._getLayoutTemplate('fileIcon') + title;
+            }
+            self.$caption.html(out);
+            self.$caption.attr('title', title);
+            self.$captionContainer.find('.file-caption-ellipsis').attr('title', title);
+        },
+        _createContainer: function () {
+            var self = this, $container = $(document.createElement("div"))
+                .attr({"class": 'file-input file-input-new'})
+                .html(self._renderMain());
+            self.$element.before($container);
+            self._initBrowse($container);
+            if (self.theme) {
+                $container.addClass('theme-' + self.theme);
+            }
+            return $container;
+        },
+        _refreshContainer: function () {
+            var self = this, $container = self.$container;
+            $container.before(self.$element);
+            $container.html(self._renderMain());
+            self._initBrowse($container);
+        },
+        _renderMain: function () {
+            var self = this, dropCss = (self.isUploadable && self.dropZoneEnabled) ? ' file-drop-zone' : 'file-drop-disabled',
+                close = !self.showClose ? '' : self._getLayoutTemplate('close'),
+                preview = !self.showPreview ? '' : self._getLayoutTemplate('preview')
+                    .replace(/\{class}/g, self.previewClass)
+                    .replace(/\{dropClass}/g, dropCss),
+                css = self.isDisabled ? self.captionClass + ' file-caption-disabled' : self.captionClass,
+                caption = self.captionTemplate.replace(/\{class}/g, css + ' kv-fileinput-caption');
+            return self.mainTemplate.replace(/\{class}/g, self.mainClass)
+                .replace(/\{preview}/g, preview)
+                .replace(/\{close}/g, close)
+                .replace(/\{caption}/g, caption)
+                .replace(/\{upload}/g, self._renderButton('upload'))
+                .replace(/\{remove}/g, self._renderButton('remove'))
+                .replace(/\{cancel}/g, self._renderButton('cancel'))
+                .replace(/\{browse}/g, self._renderButton('browse'));
+        },
+        _renderButton: function (type) {
+            var self = this, tmplt = self._getLayoutTemplate('btnDefault'), css = self[type + 'Class'],
+                title = self[type + 'Title'], icon = self[type + 'Icon'], label = self[type + 'Label'],
+                status = self.isDisabled ? ' disabled' : '', btnType = 'button';
+            switch (type) {
+                case 'remove':
+                    if (!self.showRemove) {
+                        return '';
+                    }
+                    break;
+                case 'cancel':
+                    if (!self.showCancel) {
+                        return '';
+                    }
+                    css += ' hide';
+                    break;
+                case 'upload':
+                    if (!self.showUpload) {
+                        return '';
+                    }
+                    if (self.isUploadable && !self.isDisabled) {
+                        tmplt = self._getLayoutTemplate('btnLink').replace('{href}', self.uploadUrl);
+                    } else {
+                        btnType = 'submit';
+                    }
+                    break;
+                case 'browse':
+                    tmplt = self._getLayoutTemplate('btnBrowse');
+                    break;
+                default:
+                    return '';
+            }
+            css += type === 'browse' ? ' btn-file' : ' fileinput-' + type + ' fileinput-' + type + '-button';
+            if (!isEmpty(label)) {
+                label = ' <span class="' + self.buttonLabelClass + '">' + label + '</span>';
+            }
+            return tmplt.replace('{type}', btnType).replace('{css}', css).replace('{title}', title)
+                .replace('{status}', status).replace( '{icon}', icon).replace( '{label}', label);
+        },
+        _renderThumbProgress: function () {
+            return '<div class="file-thumb-progress hide">' + this.progressTemplate.replace(/\{percent}/g,
+                    '0') + '</div>';
+        },
+        _renderFileFooter: function (caption, width) {
+            var self = this, config = self.fileActionSettings, footer, rem = config.showRemove, drg = config.showDrag,
+                upl = config.showUpload, zoom = config.showZoom, out, template = self._getLayoutTemplate('footer');
+            if (self.isUploadable) {
+                out = template.replace(/\{actions}/g, self._renderFileActions(rem, upl, zoom, drg, false, false, false))
+                    .replace(/\{caption}/g, caption)
+                    .replace(/\{width}/g, width)
+                    .replace(/\{progress}/g, self._renderThumbProgress())
+                    .replace(/\{indicator}/g, config.indicatorNew)
+                    .replace(/\{indicatorTitle}/g, config.indicatorNewTitle);
+            } else {
+                out = template.replace(/\{actions}/g,
+                    self._renderFileActions(false, false, zoom, drg, false, false, false))
+                    .replace(/\{caption}/g, caption)
+                    .replace(/\{progress}/g, '')
+                    .replace(/\{width}/g, width)
+                    .replace(/\{indicator}/g, config.indicatorNew)
+                    .replace(/\{indicatorTitle}/g, config.indicatorNewTitle);
+            }
+            out = replaceTags(out, self.previewThumbTags);
+            return out;
+        },
+        _renderFileActions: function (showUpload, showDelete, showZoom, showDrag, disabled, url, key, isInit) {
+            if (!showUpload && !showDelete && !showZoom && !showDrag) {
+                return '';
+            }
+            var self = this,
+                vUrl = url === false ? '' : ' data-url="' + url + '"',
+                vKey = key === false ? '' : ' data-key="' + key + '"',
+                btnDelete = '', btnUpload = '', btnZoom = '', btnDrag = '', css,
+                template = self._getLayoutTemplate('actions'), config = self.fileActionSettings,
+                otherButtons = self.otherActionButtons.replace(/\{dataKey}/g, vKey),
+                removeClass = disabled ? config.removeClass + ' disabled' : config.removeClass;
+            if (showDelete) {
+                btnDelete = self._getLayoutTemplate('actionDelete')
+                    .replace(/\{removeClass}/g, removeClass)
+                    .replace(/\{removeIcon}/g, config.removeIcon)
+                    .replace(/\{removeTitle}/g, config.removeTitle)
+                    .replace(/\{dataUrl}/g, vUrl)
+                    .replace(/\{dataKey}/g, vKey);
+            }
+            if (showUpload) {
+                btnUpload = self._getLayoutTemplate('actionUpload')
+                    .replace(/\{uploadClass}/g, config.uploadClass)
+                    .replace(/\{uploadIcon}/g, config.uploadIcon)
+                    .replace(/\{uploadTitle}/g, config.uploadTitle);
+            }
+            if (showZoom) {
+                btnZoom = self._getLayoutTemplate('actionZoom')
+                    .replace(/\{zoomClass}/g, config.zoomClass)
+                    .replace(/\{zoomIcon}/g, config.zoomIcon)
+                    .replace(/\{zoomTitle}/g, config.zoomTitle);
+            }
+            if (showDrag && isInit) {
+                css = 'drag-handle-init ' + config.dragClass;
+                btnDrag = self._getLayoutTemplate('actionDrag').replace(/\{dragClass}/g, css)
+                    .replace(/\{dragTitle}/g, config.dragTitle)
+                    .replace(/\{dragIcon}/g, config.dragIcon);
+            }
+            return template.replace(/\{delete}/g, btnDelete)
+                .replace(/\{upload}/g, btnUpload)
+                .replace(/\{zoom}/g, btnZoom)
+                .replace(/\{drag}/g, btnDrag)
+                .replace(/\{other}/g, otherButtons);
+        },
+        _browse: function (e) {
+            var self = this;
+            self._raise('filebrowse');
+            if (e && e.isDefaultPrevented()) {
+                return;
+            }
+            if (self.isError && !self.isUploadable) {
+                self.clear();
+            }
+            self.$captionContainer.focus();
+        },
+        _change: function (e) {
+            var self = this, $el = self.$element;
+            if (!self.isUploadable && isEmpty($el.val()) && self.fileInputCleared) { // IE 11 fix
+                self.fileInputCleared = false;
+                return;
+            }
+            self.fileInputCleared = false;
+            var tfiles, msg, total, isDragDrop = arguments.length > 1, isAjaxUpload = self.isUploadable, i = 0, f, n, len,
+                files = isDragDrop ? e.originalEvent.dataTransfer.files : $el.get(0).files, ctr = self.filestack.length,
+                isSingleUpload = isEmpty($el.attr('multiple')), flagSingle = (isSingleUpload && ctr > 0), folders = 0,
+                throwError = function (mesg, file, previewId, index) {
+                    var p1 = $.extend(true, {}, self._getOutData({}, {}, files), {id: previewId, index: index}),
+                        p2 = {id: previewId, index: index, file: file, files: files};
+                    return self.isUploadable ? self._showUploadError(mesg, p1) : self._showError(mesg, p2);
+                };
+            self.reader = null;
+            self._resetUpload();
+            self._hideFileIcon();
+            if (self.isUploadable) {
+                self.$container.find('.file-drop-zone .' + self.dropZoneTitleClass).remove();
+            }
+            if (isDragDrop) {
+                tfiles = [];
+                while (files[i]) {
+                    f = files[i];
+                    if (!f.type && f.size % 4096 === 0) {
+                        folders++;
+                    } else {
+                        tfiles.push(f);
+                    }
+                    i++;
+                }
+            } else {
+                if (e.target.files === undefined) {
+                    tfiles = e.target && e.target.value ? [
+                        {name: e.target.value.replace(/^.+\\/, '')}
+                    ] : [];
+                } else {
+                    tfiles = e.target.files;
+                }
+            }
+            if (isEmpty(tfiles) || tfiles.length === 0) {
+                if (!isAjaxUpload) {
+                    self.clear();
+                }
+                self._showFolderError(folders);
+                self._raise('fileselectnone');
+                return;
+            }
+            self._resetErrors();
+            len = tfiles.length;
+            total = self._getFileCount(self.isUploadable ? (self.getFileStack().length + len) : len);
+            if (self.maxFileCount > 0 && total > self.maxFileCount) {
+                if (!self.autoReplace || len > self.maxFileCount) {
+                    n = (self.autoReplace && len > self.maxFileCount) ? len : total;
+                    msg = self.msgFilesTooMany.replace('{m}', self.maxFileCount).replace('{n}', n);
+                    self.isError = throwError(msg, null, null, null);
+                    self.$captionContainer.find('.kv-caption-icon').hide();
+                    self._setCaption('', true);
+                    self.$container.removeClass('file-input-new file-input-ajax-new');
+                    return;
+                }
+                if (total > self.maxFileCount) {
+                    self._resetPreviewThumbs(isAjaxUpload);
+                }
+            } else {
+                if (!isAjaxUpload || flagSingle) {
+                    self._resetPreviewThumbs(false);
+                    if (flagSingle) {
+                        self.clearStack();
+                    }
+                } else {
+                    if (isAjaxUpload && ctr === 0 && (!previewCache.count(self.id) || self.overwriteInitial)) {
+                        self._resetPreviewThumbs(true);
+                    }
+                }
+            }
+            if (self.isPreviewable) {
+                self._readFiles(tfiles);
+            } else {
+                self._updateFileDetails(1);
+            }
+            self._showFolderError(folders);
+        },
+        _abort: function (params) {
+            var self = this, data;
+            if (self.ajaxAborted && typeof self.ajaxAborted === "object" && self.ajaxAborted.message !== undefined) {
+                data = $.extend(true, {}, self._getOutData(), params);
+                data.abortData = self.ajaxAborted.data || {};
+                data.abortMessage = self.ajaxAborted.message;
+                self.cancel();
+                self._setProgress(100, self.$progress, self.msgCancelled);
+                self._showUploadError(self.ajaxAborted.message, data, 'filecustomerror');
+                return true;
+            }
+            return false;
+        },
+        _resetFileStack: function () {
+            var self = this, i = 0, newstack = [], newnames = [];
+            self._getThumbs().each(function () {
+                var $thumb = $(this), ind = $thumb.attr('data-fileindex'),
+                    file = self.filestack[ind];
+                if (ind === -1) {
+                    return;
+                }
+                if (file !== undefined) {
+                    newstack[i] = file;
+                    newnames[i] = self._getFileName(file);
+                    $thumb.attr({
+                        'id': self.previewInitId + '-' + i,
+                        'data-fileindex': i
+                    });
+                    i++;
+                } else {
+                    $thumb.attr({
+                        'id': 'uploaded-' + uniqId(),
+                        'data-fileindex': '-1'
+                    });
+                }
+            });
+            self.filestack = newstack;
+            self.filenames = newnames;
+        },
+        clearStack: function () {
+            var self = this;
+            self.filestack = [];
+            self.filenames = [];
+            return self.$element;
+        },
+        updateStack: function (i, file) {
+            var self = this;
+            self.filestack[i] = file;
+            self.filenames[i] = self._getFileName(file);
+            return self.$element;
+        },
+        addToStack: function (file) {
+            var self = this;
+            self.filestack.push(file);
+            self.filenames.push(self._getFileName(file));
+            return self.$element;
+        },
+        getFileStack: function (skipNull) {
+            var self = this;
+            return self.filestack.filter(function (n) {
+                return (skipNull ? n !== undefined : n !== undefined && n !== null);
+            });
+        },
+        lock: function () {
+            var self = this;
+            self._resetErrors();
+            self.disable();
+            if (self.showRemove) {
+                addCss(self.$container.find('.fileinput-remove'), 'hide');
+            }
+            if (self.showCancel) {
+                self.$container.find('.fileinput-cancel').removeClass('hide');
+            }
+            self._raise('filelock', [self.filestack, self._getExtraData()]);
+            return self.$element;
+        },
+        unlock: function (reset) {
+            var self = this;
+            if (reset === undefined) {
+                reset = true;
+            }
+            self.enable();
+            if (self.showCancel) {
+                addCss(self.$container.find('.fileinput-cancel'), 'hide');
+            }
+            if (self.showRemove) {
+                self.$container.find('.fileinput-remove').removeClass('hide');
+            }
+            if (reset) {
+                self._resetFileStack();
+            }
+            self._raise('fileunlock', [self.filestack, self._getExtraData()]);
+            return self.$element;
+        },
+        cancel: function () {
+            var self = this, xhr = self.ajaxRequests, len = xhr.length, i;
+            if (len > 0) {
+                for (i = 0; i < len; i += 1) {
+                    self.cancelling = true;
+                    xhr[i].abort();
+                }
+            }
+            self._setProgressCancelled();
+            self._getThumbs().each(function () {
+                var $thumb = $(this), ind = $thumb.attr('data-fileindex');
+                $thumb.removeClass('file-uploading');
+                if (self.filestack[ind] !== undefined) {
+                    $thumb.find('.kv-file-upload').removeClass('disabled').removeAttr('disabled');
+                    $thumb.find('.kv-file-remove').removeClass('disabled').removeAttr('disabled');
+                }
+                self.unlock();
+            });
+            return self.$element;
+        },
+        clear: function () {
+            var self = this, cap;
+            self.$btnUpload.removeAttr('disabled');
+            self._getThumbs().find('video,audio,img').each(function () {
+                cleanMemory($(this));
+            });
+            self._resetUpload();
+            self.clearStack();
+            self._clearFileInput();
+            self._resetErrors(true);
+            self._raise('fileclear');
+            if (self._hasInitialPreview()) {
+                self._showFileIcon();
+                self._resetPreview();
+                self._initPreviewActions();
+                self.$container.removeClass('file-input-new');
+            } else {
+                self._getThumbs().each(function () {
+                    self._clearObjects($(this));
+                });
+                if (self.isUploadable) {
+                    previewCache.data[self.id] = {};
+                }
+                self.$preview.html('');
+                cap = (!self.overwriteInitial && self.initialCaption.length > 0) ? self.initialCaption : '';
+                self._setCaption(cap);
+                self.$caption.attr('title', '');
+                addCss(self.$container, 'file-input-new');
+                self._validateDefaultPreview();
+            }
+            if (self.$container.find('.file-preview-frame').length === 0) {
+                if (!self._initCaption()) {
+                    self.$captionContainer.find('.kv-caption-icon').hide();
+                }
+            }
+            self._hideFileIcon();
+            self._raise('filecleared');
+            self.$captionContainer.focus();
+            self._setFileDropZoneTitle();
+            return self.$element;
+        },
+        reset: function () {
+            var self = this;
+            self._resetPreview();
+            self.$container.find('.fileinput-filename').text('');
+            self._raise('filereset');
+            addCss(self.$container, 'file-input-new');
+            if (self.$preview.find('.file-preview-frame').length || self.isUploadable && self.dropZoneEnabled) {
+                self.$container.removeClass('file-input-new');
+            }
+            self._setFileDropZoneTitle();
+            self.clearStack();
+            self.formdata = {};
+            return self.$element;
+        },
+        disable: function () {
+            var self = this;
+            self.isDisabled = true;
+            self._raise('filedisabled');
+            self.$element.attr('disabled', 'disabled');
+            self.$container.find(".kv-fileinput-caption").addClass("file-caption-disabled");
+            self.$container.find(".btn-file, .fileinput-remove, .fileinput-upload, .file-preview-frame button").attr(
+                "disabled",
+                true);
+            self._initDragDrop();
+            return self.$element;
+        },
+        enable: function () {
+            var self = this;
+            self.isDisabled = false;
+            self._raise('fileenabled');
+            self.$element.removeAttr('disabled');
+            self.$container.find(".kv-fileinput-caption").removeClass("file-caption-disabled");
+            self.$container.find(
+                ".btn-file, .fileinput-remove, .fileinput-upload, .file-preview-frame button").removeAttr("disabled");
+            self._initDragDrop();
+            return self.$element;
+        },
+        upload: function () {
+            var self = this, totLen = self.getFileStack().length, params = {},
+                i, outData, len, hasExtraData = !$.isEmptyObject(self._getExtraData());
+            if (self.minFileCount > 0 && self._getFileCount(totLen) < self.minFileCount) {
+                self._noFilesError(params);
+                return;
+            }
+            if (!self.isUploadable || self.isDisabled || (totLen === 0 && !hasExtraData)) {
+                return;
+            }
+            self._resetUpload();
+            self.$progress.removeClass('hide');
+            self.uploadCount = 0;
+            self.uploadStatus = {};
+            self.uploadLog = [];
+            self.lock();
+            self._setProgress(2);
+            if (totLen === 0 && hasExtraData) {
+                self._uploadExtraOnly();
+                return;
+            }
+            len = self.filestack.length;
+            self.hasInitData = false;
+            if (self.uploadAsync) {
+                outData = self._getOutData();
+                self._raise('filebatchpreupload', [outData]);
+                self.fileBatchCompleted = false;
+                self.uploadCache = {content: [], config: [], tags: [], append: true};
+                self.uploadAsyncCount = self.getFileStack().length;
+                for (i = 0; i < len; i++) {
+                    self.uploadCache.content[i] = null;
+                    self.uploadCache.config[i] = null;
+                    self.uploadCache.tags[i] = null;
+                }
+                for (i = 0; i < len; i++) {
+                    if (self.filestack[i] !== undefined) {
+                        self._uploadSingle(i, self.filestack, true);
+                    }
+                }
+                return;
+            }
+            self._uploadBatch();
+            return self.$element;
+        },
+        destroy: function () {
+            var self = this, $cont = self.$container;
+            $cont.find('.file-drop-zone').off();
+            self.$element.insertBefore($cont).off(NAMESPACE).removeData();
+            $cont.off().remove();
+            return self.$element;
+        },
+        refresh: function (options) {
+            var self = this, $el = self.$element;
+            options = options ? $.extend(true, {}, self.options, options) : self.options;
+            self.destroy();
+            $el.fileinput(options);
+            if ($el.val()) {
+                $el.trigger('change.fileinput');
+            }
+            return $el;
+        }
+    };
+
+    $.fn.fileinput = function (option) {
+        if (!hasFileAPISupport() && !isIE(9)) {
+            return;
+        }
+        var args = Array.apply(null, arguments), retvals = [];
+        args.shift();
+        this.each(function () {
+            var self = $(this), data = self.data('fileinput'), options = typeof option === 'object' && option,
+                theme = options.theme || self.data('theme'), l = {}, t = {},
+                lang = options.language || self.data('language') || 'en', opts;
+            if (!data) {
+                if (theme) {
+                    t = $.fn.fileinputThemes[theme] || {};
+                }
+                if (lang !== 'en' && !isEmpty($.fn.fileinputLocales[lang])) {
+                    l = $.fn.fileinputLocales[lang] || {};
+                }
+                opts = $.extend(true, {}, $.fn.fileinput.defaults, t, $.fn.fileinputLocales.en, l, options,
+                    self.data());
+                data = new FileInput(this, opts);
+                self.data('fileinput', data);
+            }
+
+            if (typeof option === 'string') {
+                retvals.push(data[option].apply(data, args));
+            }
+        });
+        switch (retvals.length) {
+            case 0:
+                return this;
+            case 1:
+                return retvals[0];
+            default:
+                return retvals;
+        }
+    };
+
+    $.fn.fileinput.defaults = {
+        language: 'en',
+        showCaption: true,
+        showPreview: true,
+        showRemove: true,
+        showUpload: true,
+        showCancel: true,
+        showClose: true,
+        showUploadedThumbs: true,
+        autoReplace: false,
+        previewClass: '',
+        captionClass: '',
+        mainClass: '',
+        mainTemplate: null,
+        purifyHtml: true,
+        initialCaption: '',
+        initialPreview: [],
+        initialPreviewDelimiter: '*$$*',
+        initialPreviewAsData: false,
+        initialPreviewFileType: 'image',
+        initialPreviewConfig: [],
+        initialPreviewThumbTags: [],
+        previewThumbTags: {},
+        initialPreviewShowDelete: true,
+        removeFromPreviewOnError: false,
+        deleteUrl: '',
+        deleteExtraData: {},
+        overwriteInitial: true,
+        layoutTemplates: defaultLayoutTemplates,
+        previewTemplates: defaultPreviewTemplates,
+        previewZoomSettings: defaultPreviewZoomSettings,
+        previewZoomButtonIcons: {
+            prev: '<i class="glyphicon glyphicon-triangle-left"></i>',
+            next: '<i class="glyphicon glyphicon-triangle-right"></i>',
+            toggleheader: '<i class="glyphicon glyphicon-resize-vertical"></i>',
+            fullscreen: '<i class="glyphicon glyphicon-fullscreen"></i>',
+            borderless: '<i class="glyphicon glyphicon-resize-full"></i>',
+            close: '<i class="glyphicon glyphicon-remove"></i>'
+        },
+        previewZoomButtonClasses: {
+            prev: 'btn btn-navigate',
+            next: 'btn btn-navigate',
+            toggleheader: 'btn btn-default btn-header-toggle',
+            fullscreen: 'btn btn-default',
+            borderless: 'btn btn-default',
+            close: 'btn btn-default'
+        },
+        allowedPreviewTypes: null,
+        allowedPreviewMimeTypes: null,
+        allowedFileTypes: null,
+        allowedFileExtensions: null,
+        defaultPreviewContent: null,
+        customLayoutTags: {},
+        customPreviewTags: {},
+        previewSettings: defaultPreviewSettings,
+        fileTypeSettings: defaultFileTypeSettings,
+        previewFileIcon: '<i class="glyphicon glyphicon-file"></i>',
+        previewFileIconClass: 'file-icon-4x',
+        previewFileIconSettings: {},
+        previewFileExtSettings: {},
+        buttonLabelClass: 'hidden-xs',
+        browseIcon: '<i class="glyphicon glyphicon-folder-open"></i>&nbsp;',
+        browseClass: 'btn btn-primary',
+        removeIcon: '<i class="glyphicon glyphicon-trash"></i>',
+        removeClass: 'btn btn-default',
+        cancelIcon: '<i class="glyphicon glyphicon-ban-circle"></i>',
+        cancelClass: 'btn btn-default',
+        uploadIcon: '<i class="glyphicon glyphicon-upload"></i>',
+        uploadClass: 'btn btn-default',
+        uploadUrl: null,
+        uploadAsync: true,
+        uploadExtraData: {},
+        zoomModalHeight: 480,
+        minImageWidth: null,
+        minImageHeight: null,
+        maxImageWidth: null,
+        maxImageHeight: null,
+        resizeImage: false,
+        resizePreference: 'width',
+        resizeQuality: 0.92,
+        resizeDefaultImageType: 'image/jpeg',
+        maxFileSize: 0,
+        maxFilePreviewSize: 25600, // 25 MB
+        minFileCount: 0,
+        maxFileCount: 0,
+        validateInitialCount: false,
+        msgValidationErrorClass: 'text-danger',
+        msgValidationErrorIcon: '<i class="glyphicon glyphicon-exclamation-sign"></i> ',
+        msgErrorClass: 'file-error-message',
+        progressThumbClass: "progress-bar progress-bar-success progress-bar-striped active",
+        progressClass: "progress-bar progress-bar-success progress-bar-striped active",
+        progressCompleteClass: "progress-bar progress-bar-success",
+        progressErrorClass: "progress-bar progress-bar-danger",
+        previewFileType: 'image',
+        elCaptionContainer: null,
+        elCaptionText: null,
+        elPreviewContainer: null,
+        elPreviewImage: null,
+        elPreviewStatus: null,
+        elErrorContainer: null,
+        errorCloseButton: '<span class="close kv-error-close">&times;</span>',
+        slugCallback: null,
+        dropZoneEnabled: true,
+        dropZoneTitleClass: 'file-drop-zone-title',
+        fileActionSettings: {},
+        otherActionButtons: '',
+        textEncoding: 'UTF-8',
+        ajaxSettings: {},
+        ajaxDeleteSettings: {},
+        showAjaxErrorDetails: true
+    };
+
+    $.fn.fileinputLocales.en = {
+        fileSingle: 'file',
+        filePlural: 'files',
+        browseLabel: 'Browse &hellip;',
+        removeLabel: 'Remove',
+        removeTitle: 'Clear selected files',
+        cancelLabel: 'Cancel',
+        cancelTitle: 'Abort ongoing upload',
+        uploadLabel: 'Upload',
+        uploadTitle: 'Upload selected files',
+        msgNo: 'No',
+        msgCancelled: 'Cancelled',
+        msgZoomModalHeading: 'Detailed Preview',
+        msgSizeTooLarge: 'File "{name}" (<b>{size} KB</b>) exceeds maximum allowed upload size of <b>{maxSize} KB</b>.',
+        msgFilesTooLess: 'You must select at least <b>{n}</b> {files} to upload.',
+        msgFilesTooMany: 'Number of files selected for upload <b>({n})</b> exceeds maximum allowed limit of <b>{m}</b>.',
+        msgFileNotFound: 'File "{name}" not found!',
+        msgFileSecured: 'Security restrictions prevent reading the file "{name}".',
+        msgFileNotReadable: 'File "{name}" is not readable.',
+        msgFilePreviewAborted: 'File preview aborted for "{name}".',
+        msgFilePreviewError: 'An error occurred while reading the file "{name}".',
+        msgInvalidFileType: 'Invalid type for file "{name}". Only "{types}" files are supported.',
+        msgInvalidFileExtension: 'Invalid extension for file "{name}". Only "{extensions}" files are supported.',
+        msgUploadAborted: 'The file upload was aborted',
+        msgValidationError: 'Validation Error',
+        msgLoading: 'Loading file {index} of {files} &hellip;',
+        msgProgress: 'Loading file {index} of {files} - {name} - {percent}% completed.',
+        msgSelected: '{n} {files} selected',
+        msgFoldersNotAllowed: 'Drag & drop files only! {n} folder(s) dropped were skipped.',
+        msgImageWidthSmall: 'Width of image file "{name}" must be at least {size} px.',
+        msgImageHeightSmall: 'Height of image file "{name}" must be at least {size} px.',
+        msgImageWidthLarge: 'Width of image file "{name}" cannot exceed {size} px.',
+        msgImageHeightLarge: 'Height of image file "{name}" cannot exceed {size} px.',
+        msgImageResizeError: 'Could not get the image dimensions to resize.',
+        msgImageResizeException: 'Error while resizing the image.<pre>{errors}</pre>',
+        dropZoneTitle: 'Drag & drop files here &hellip;',
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
+
+    $.fn.fileinput.Constructor = FileInput;
+
+    /**
+     * Convert automatically file inputs with class 'file' into a bootstrap fileinput control.
+     */
+    $(document).ready(function () {
+        var $input = $('input.file[type=file]');
+        if ($input.length) {
+            $input.fileinput();
+        }
+    });
+}));

File diff suppressed because it is too large
+ 0 - 15
js/fileinput.min.js


+ 68 - 59
js/fileinput_locale_LANG.js → js/locales/fileinput_locale_LANG.js

@@ -1,60 +1,69 @@
-/*!
- * FileInput <_LANG_> Translations
- *
- * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
- * any HTML markup tags in the messages must not be converted or translated.
- *
- * @see http://github.com/kartik-v/bootstrap-fileinput
- *
- * NOTE: this file must be saved in UTF-8 encoding.
- */
-(function ($) {
-    "use strict";
-
-    $.fn.fileinputLocales['_LANG_'] = {
-        fileSingle: 'file',
-        filePlural: 'files',
-        browseLabel: 'Browse &hellip;',
-        removeLabel: 'Remove',
-        removeTitle: 'Clear selected files',
-        cancelLabel: 'Cancel',
-        cancelTitle: 'Abort ongoing upload',
-        uploadLabel: 'Upload',
-        uploadTitle: 'Upload selected files',
-        msgNo: 'No',
-        msgCancelled: 'Cancelled',
-        msgZoomTitle: 'View details',
-        msgZoomModalHeading: 'Detailed Preview',
-        msgSizeTooLarge: 'File "{name}" (<b>{size} KB</b>) exceeds maximum allowed upload size of <b>{maxSize} KB</b>.',
-        msgFilesTooLess: 'You must select at least <b>{n}</b> {files} to upload.',
-        msgFilesTooMany: 'Number of files selected for upload <b>({n})</b> exceeds maximum allowed limit of <b>{m}</b>.',
-        msgFileNotFound: 'File "{name}" not found!',
-        msgFileSecured: 'Security restrictions prevent reading the file "{name}".',
-        msgFileNotReadable: 'File "{name}" is not readable.',
-        msgFilePreviewAborted: 'File preview aborted for "{name}".',
-        msgFilePreviewError: 'An error occurred while reading the file "{name}".',
-        msgInvalidFileType: 'Invalid type for file "{name}". Only "{types}" files are supported.',
-        msgInvalidFileExtension: 'Invalid extension for file "{name}". Only "{extensions}" files are supported.',
-        msgUploadAborted: 'The file upload was aborted',
-        msgValidationError: 'Validation Error',
-        msgLoading: 'Loading file {index} of {files} &hellip;',
-        msgProgress: 'Loading file {index} of {files} - {name} - {percent}% completed.',
-        msgSelected: '{n} {files} selected',
-        msgFoldersNotAllowed: 'Drag & drop files only! Skipped {n} dropped folder(s).',
-        msgImageWidthSmall: 'Width of image file "{name}" must be at least {size} px.',
-        msgImageHeightSmall: 'Height of image file "{name}" must be at least {size} px.',
-        msgImageWidthLarge: 'Width of image file "{name}" cannot exceed {size} px.',
-        msgImageHeightLarge: 'Height of image file "{name}" cannot exceed {size} px.',
-        msgImageResizeError: 'Could not get the image dimensions to resize.',
-        msgImageResizeException: 'Error while resizing the image.<pre>{errors}</pre>',
-        dropZoneTitle: 'Drag & drop files here &hellip;',
-        fileActionSettings: {
-            removeTitle: 'Remove file',
-            uploadTitle: 'Upload file',
-            indicatorNewTitle: 'Not uploaded yet',
-            indicatorSuccessTitle: 'Uploaded',
-            indicatorErrorTitle: 'Upload Error',
-            indicatorLoadingTitle: 'Uploading ...'
-        }
-    };
+/*!
+ * FileInput <_LANG_> Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputLocales['_LANG_'] = {
+        fileSingle: 'file',
+        filePlural: 'files',
+        browseLabel: 'Browse &hellip;',
+        removeLabel: 'Remove',
+        removeTitle: 'Clear selected files',
+        cancelLabel: 'Cancel',
+        cancelTitle: 'Abort ongoing upload',
+        uploadLabel: 'Upload',
+        uploadTitle: 'Upload selected files',
+        msgNo: 'No',
+        msgCancelled: 'Cancelled',
+        msgZoomModalHeading: 'Detailed Preview',
+        msgSizeTooLarge: 'File "{name}" (<b>{size} KB</b>) exceeds maximum allowed upload size of <b>{maxSize} KB</b>.',
+        msgFilesTooLess: 'You must select at least <b>{n}</b> {files} to upload.',
+        msgFilesTooMany: 'Number of files selected for upload <b>({n})</b> exceeds maximum allowed limit of <b>{m}</b>.',
+        msgFileNotFound: 'File "{name}" not found!',
+        msgFileSecured: 'Security restrictions prevent reading the file "{name}".',
+        msgFileNotReadable: 'File "{name}" is not readable.',
+        msgFilePreviewAborted: 'File preview aborted for "{name}".',
+        msgFilePreviewError: 'An error occurred while reading the file "{name}".',
+        msgInvalidFileType: 'Invalid type for file "{name}". Only "{types}" files are supported.',
+        msgInvalidFileExtension: 'Invalid extension for file "{name}". Only "{extensions}" files are supported.',
+        msgUploadAborted: 'The file upload was aborted',
+        msgValidationError: 'Validation Error',
+        msgLoading: 'Loading file {index} of {files} &hellip;',
+        msgProgress: 'Loading file {index} of {files} - {name} - {percent}% completed.',
+        msgSelected: '{n} {files} selected',
+        msgFoldersNotAllowed: 'Drag & drop files only! Skipped {n} dropped folder(s).',
+        msgImageWidthSmall: 'Width of image file "{name}" must be at least {size} px.',
+        msgImageHeightSmall: 'Height of image file "{name}" must be at least {size} px.',
+        msgImageWidthLarge: 'Width of image file "{name}" cannot exceed {size} px.',
+        msgImageHeightLarge: 'Height of image file "{name}" cannot exceed {size} px.',
+        msgImageResizeError: 'Could not get the image dimensions to resize.',
+        msgImageResizeException: 'Error while resizing the image.<pre>{errors}</pre>',
+        dropZoneTitle: 'Drag & drop files here &hellip;',
+        fileActionSettings: {
+            removeTitle: 'Remove file',
+            uploadTitle: 'Upload file',
+            zoomTitle: 'View details',
+            dragTitle: 'Move / Rearrange',
+            indicatorNewTitle: 'Not uploaded yet',
+            indicatorSuccessTitle: 'Uploaded',
+            indicatorErrorTitle: 'Upload Error',
+            indicatorLoadingTitle: 'Uploading ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
 })(window.jQuery);

+ 10 - 1
js/fileinput_locale_ar.js → js/locales/fileinput_locale_ar.js

@@ -24,7 +24,6 @@
         uploadTitle: 'رفع الملفات المختارة',
         msgNo: 'لا',
         msgCancelled: 'ألغيت',
-        msgZoomTitle: 'مشاهدة التفاصيل',
         msgZoomModalHeading: 'معاينة تفصيلية',
         msgSizeTooLarge: 'الملف "{name}" (<b>{size} ك.ب</b>) تعدى الحد الأقصى المسموح للرفع <b>{maxSize} ك.ب</b>.',
         msgFilesTooLess: 'يجب عليك اختيار <b>{n}</b> {files} على الأقل للرفع.',
@@ -52,10 +51,20 @@
         fileActionSettings: {
             removeTitle: 'إزالة الملف',
             uploadTitle: 'رفع الملف',
+            zoomTitle: 'مشاهدة التفاصيل',
+            dragTitle: 'Move / Rearrange',
             indicatorNewTitle: 'لم يتم الرفع بعد',
             indicatorSuccessTitle: 'تم الرفع',
             indicatorErrorTitle: 'خطأ بالرفع',
             indicatorLoadingTitle: 'جارٍ الرفع ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
         }
     };
 })(window.jQuery);

+ 69 - 60
js/fileinput_locale_bg.js → js/locales/fileinput_locale_bg.js

@@ -1,60 +1,69 @@
-/*!
- * FileInput Bulgarian Translations
- *
- * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
- * any HTML markup tags in the messages must not be converted or translated.
- *
- * @see http://github.com/kartik-v/bootstrap-fileinput
- *
- * NOTE: this file must be saved in UTF-8 encoding.
- */
-(function ($) {
-    "use strict";
-
-    $.fn.fileinputLocales['bg'] = {
-        fileSingle: 'файл',
-        filePlural: 'файла',
-        browseLabel: 'Избери &hellip;',
-        removeLabel: 'Премахни',
-        removeTitle: 'Изчисти избраните',
-        cancelLabel: 'Откажи',
-        cancelTitle: 'Откажи качването',
-        uploadLabel: 'Качи',
-        uploadTitle: 'Качи избраните файлове',
-        msgNo: 'Не',
-        msgCancelled: 'Отменен',
-        msgZoomTitle: 'Вижте детайли',
-        msgZoomModalHeading: 'Детайлен преглед',
-        msgSizeTooLarge: 'Файла "{name}" (<b>{size} KB</b>) надвишава максималните разрешени <b>{maxSize} KB</b>.',
-        msgFilesTooLess: 'Трябва да изберете поне <b>{n}</b> {files} файла.',
-        msgFilesTooMany: 'Броя файлове избрани за качване <b>({n})</b> надвишава ограниченито от максимум <b>{m}</b>.',
-        msgFileNotFound: 'Файлът "{name}" не може да бъде намерен!',
-        msgFileSecured: 'От съображения за сигурност не може да прочетем файла "{name}".',
-        msgFileNotReadable: 'Файлът "{name}" не е четим.',
-        msgFilePreviewAborted: 'Прегледа на файла е прекратен за "{name}".',
-        msgFilePreviewError: 'Грешка при опит за четене на файла "{name}".',
-        msgInvalidFileType: 'Невалиден тип на файла "{name}". Разрешени са само "{types}".',
-        msgInvalidFileExtension: 'Невалидно разрешение на "{name}". Разрешени са само "{extensions}".',
-        msgUploadAborted: 'Качите файла, бе прекратена',
-        msgValidationError: 'утвърждаване грешка',
-        msgLoading: 'Зареждане на файл {index} от общо {files} &hellip;',
-        msgProgress: 'Зареждане на файл {index} от общо {files} - {name} - {percent}% завършени.',
-        msgSelected: '{n} {files} избрани',
-        msgFoldersNotAllowed: 'Само пуснати файлове! Пропуснати {n} пуснати папки.',
-        msgImageWidthSmall: 'Широчината на изображението "{name}" трябва да е поне {size} px.',
-        msgImageHeightSmall: 'Височината на изображението "{name}" трябва да е поне {size} px.',
-        msgImageWidthLarge: 'Широчината на изображението "{name}" не може да е по-голяма от {size} px.',
-        msgImageHeightLarge: 'Височината на изображението "{name}" нее може да е по-голяма от {size} px.',
-        msgImageResizeError: 'Не може да размерите на изображението, за да промените размера.',
-        msgImageResizeException: 'Грешка при промяна на размера на изображението.<pre>{errors}</pre>',
-        dropZoneTitle: 'Пуснете файловете тук &hellip;',
-        fileActionSettings: {
-            removeTitle: 'Махни файл',
-            uploadTitle: 'Качване на файл',
-            indicatorNewTitle: 'Все още не е качил',
-            indicatorSuccessTitle: 'Качено',
-            indicatorErrorTitle: 'Качи Error',
-            indicatorLoadingTitle: 'Качва се ...'
-        }
-    };
-})(window.jQuery);
+/*!
+ * FileInput Bulgarian Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputLocales['bg'] = {
+        fileSingle: 'файл',
+        filePlural: 'файла',
+        browseLabel: 'Избери &hellip;',
+        removeLabel: 'Премахни',
+        removeTitle: 'Изчисти избраните',
+        cancelLabel: 'Откажи',
+        cancelTitle: 'Откажи качването',
+        uploadLabel: 'Качи',
+        uploadTitle: 'Качи избраните файлове',
+        msgNo: 'Не',
+        msgCancelled: 'Отменен',
+        msgZoomModalHeading: 'Детайлен преглед',
+        msgSizeTooLarge: 'Файла "{name}" (<b>{size} KB</b>) надвишава максималните разрешени <b>{maxSize} KB</b>.',
+        msgFilesTooLess: 'Трябва да изберете поне <b>{n}</b> {files} файла.',
+        msgFilesTooMany: 'Броя файлове избрани за качване <b>({n})</b> надвишава ограниченито от максимум <b>{m}</b>.',
+        msgFileNotFound: 'Файлът "{name}" не може да бъде намерен!',
+        msgFileSecured: 'От съображения за сигурност не може да прочетем файла "{name}".',
+        msgFileNotReadable: 'Файлът "{name}" не е четим.',
+        msgFilePreviewAborted: 'Прегледа на файла е прекратен за "{name}".',
+        msgFilePreviewError: 'Грешка при опит за четене на файла "{name}".',
+        msgInvalidFileType: 'Невалиден тип на файла "{name}". Разрешени са само "{types}".',
+        msgInvalidFileExtension: 'Невалидно разрешение на "{name}". Разрешени са само "{extensions}".',
+        msgUploadAborted: 'Качите файла, бе прекратена',
+        msgValidationError: 'утвърждаване грешка',
+        msgLoading: 'Зареждане на файл {index} от общо {files} &hellip;',
+        msgProgress: 'Зареждане на файл {index} от общо {files} - {name} - {percent}% завършени.',
+        msgSelected: '{n} {files} избрани',
+        msgFoldersNotAllowed: 'Само пуснати файлове! Пропуснати {n} пуснати папки.',
+        msgImageWidthSmall: 'Широчината на изображението "{name}" трябва да е поне {size} px.',
+        msgImageHeightSmall: 'Височината на изображението "{name}" трябва да е поне {size} px.',
+        msgImageWidthLarge: 'Широчината на изображението "{name}" не може да е по-голяма от {size} px.',
+        msgImageHeightLarge: 'Височината на изображението "{name}" нее може да е по-голяма от {size} px.',
+        msgImageResizeError: 'Не може да размерите на изображението, за да промените размера.',
+        msgImageResizeException: 'Грешка при промяна на размера на изображението.<pre>{errors}</pre>',
+        dropZoneTitle: 'Пуснете файловете тук &hellip;',
+        fileActionSettings: {
+            removeTitle: 'Махни файл',
+            uploadTitle: 'Качване на файл',
+            zoomTitle: 'Вижте детайли',
+            dragTitle: 'Move / Rearrange',
+            indicatorNewTitle: 'Все още не е качил',
+            indicatorSuccessTitle: 'Качено',
+            indicatorErrorTitle: 'Качи Error',
+            indicatorLoadingTitle: 'Качва се ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
+})(window.jQuery);

+ 10 - 1
js/fileinput_locale_ca.js → js/locales/fileinput_locale_ca.js

@@ -23,7 +23,6 @@
         uploadTitle: 'Pujar arxius seleccionats',
         msgNo: 'No',
         msgCancelled: 'cancel·lat',
-        msgZoomTitle: 'Veure detalls',
         msgZoomModalHeading: 'Vista prèvia detallada',
         msgSizeTooLarge: 'Arxiu "{name}" (<b>{size} KB</b>) excedeix la mida màxima permès de <b>{maxSize} KB</b>.',
         msgFilesTooLess: 'Heu de seleccionar almenys <b>{n}</b> {files} a carregar.',
@@ -51,10 +50,20 @@
         fileActionSettings: {
             removeTitle: 'Eliminar arxiu',
             uploadTitle: 'Pujar arxiu',
+            zoomTitle: 'Veure detalls',
+            dragTitle: 'Move / Rearrange',
             indicatorNewTitle: 'No pujat encara',
             indicatorSuccessTitle: 'Subido',
             indicatorErrorTitle: 'Pujar Error',
             indicatorLoadingTitle: 'Pujant ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
         }
     };
 })(window.jQuery);

+ 10 - 1
js/fileinput_locale_cr.js → js/locales/fileinput_locale_cr.js

@@ -24,7 +24,6 @@
         uploadTitle: 'Otpremi označene datoteke',
         msgNo: 'Ne',
         msgCancelled: 'Otkazan',
-        msgZoomTitle: 'Pregledavati pojedinosti',
         msgZoomModalHeading: 'Detaljni pregled',
         msgSizeTooLarge: 'Datoteka "{name}" (<b>{size} KB</b>) prekoračuje maksimalnu dozvoljenu veličinu datoteke od <b>{maxSize} KB</b>.',
         msgFilesTooLess: 'Morate odabrati najmanje <b>{n}</b> {files} za otpremanje.',
@@ -52,10 +51,20 @@
         fileActionSettings: {
             removeTitle: 'Uklonite datoteku',
             uploadTitle: 'Postavi datoteku',
+            zoomTitle: 'Pregledavati pojedinosti',
+            dragTitle: 'Move / Rearrange',
             indicatorNewTitle: 'Još nije učitao',
             indicatorSuccessTitle: 'Preneseno',
             indicatorErrorTitle: 'Postavi Greška',
             indicatorLoadingTitle: 'Prijenos ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
         }
     };
 })(window.jQuery);

+ 68 - 59
js/fileinput_locale_cz.js → js/locales/fileinput_locale_cz.js

@@ -1,60 +1,69 @@
-/*!
- * FileInput Czech Translations
- *
- * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
- * any HTML markup tags in the messages must not be converted or translated.
- *
- * @see http://github.com/kartik-v/bootstrap-fileinput
- *
- * NOTE: this file must be saved in UTF-8 encoding.
- */
-(function ($) {
-    "use strict";
-
-    $.fn.fileinputLocales['cz'] = {
-        fileSingle: 'soubor',
-        filePlural: 'soubory',
-        browseLabel: 'Vybrat &hellip;',
-        removeLabel: 'Odstranit',
-        removeTitle: 'Vyčistit vybrané soubory',
-        cancelLabel: 'Storno',
-        cancelTitle: 'Přerušit  nahrávání',
-        uploadLabel: 'Nahrát',
-        uploadTitle: 'Nahrát vybrané soubory',
-        msgNo: 'Ne',
-        msgCancelled: 'Zrušeno',
-        msgZoomTitle: 'zobrazit podrobnosti',
-        msgZoomModalHeading: 'Detailní náhled',
-        msgSizeTooLarge: 'Soubor "{name}" (<b>{size} KB</b>): překročení - maximální povolená velikost <b>{maxSize} KB</b>.',
-        msgFilesTooLess: 'Musíte vybrat nejméně <b>{n}</b> {files} pro nahrání.',
-        msgFilesTooMany: 'Počet vybraných souborů pro nahrání <b>({n})</b>: překročení - maximální povolený limit <b>{m}</b>.',
-        msgFileNotFound: 'Soubor "{name}" nebyl nalezen!',
-        msgFileSecured: 'Zabezpečení souboru znemožnilo číst soubor "{name}".',
-        msgFileNotReadable: 'Soubor "{name}" není čitelný.',
-        msgFilePreviewAborted: 'Náhled souboru byl přerušen pro "{name}".',
-        msgFilePreviewError: 'Nastala chyba při načtení souboru "{name}".',
-        msgInvalidFileType: 'Neplatný typ souboru "{name}". Pouze "{types}" souborů jsou podporovány.',
-        msgInvalidFileExtension: 'Neplatná extenze souboru "{name}". Pouze "{extensions}" souborů jsou podporovány.',
-        msgUploadAborted: 'Soubor nahrávání byl přerušen',
-        msgValidationError: 'Chyba ověření',
-        msgLoading: 'Nahrávání souboru {index} z {files} &hellip;',
-        msgProgress: 'Nahrávání souboru {index} z {files} - {name} - {percent}% dokončeno.',
-        msgSelected: '{n} {files} vybrano',
-        msgFoldersNotAllowed: 'Táhni a pusť pouze soubory! Vynechané {n} pustěné složk(y).',
-        msgImageWidthSmall: 'Šířka image soubor "{name}", musí být alespoň {size} px.',
-        msgImageHeightSmall: 'Výška image soubor "{name}", musí být alespoň {size} px.',
-        msgImageWidthLarge: 'Šířka obrazového souboru "{name}" nelze překročit {size} px.',
-        msgImageHeightLarge: 'Výška obrazového souboru "{name}" nelze překročit {size} px.',
-        msgImageResizeError: 'Nelze získat rozměry obrázku změnit velikost.',
-        msgImageResizeException: 'Chyba při změně velikosti obrázku.<pre>{errors}</pre>',
-        dropZoneTitle: 'Táhni a pusť soubory sem &hellip;',
-        fileActionSettings: {
-            removeTitle: 'Odstranit soubor',
-            uploadTitle: 'nahrát soubor',
-            indicatorNewTitle: 'Ještě nenahrál',
-            indicatorSuccessTitle: 'Nahraný',
-            indicatorErrorTitle: 'Nahrát Chyba',
-            indicatorLoadingTitle: 'Nahrávání ...'
-        }
-    };
+/*!
+ * FileInput Czech Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputLocales['cz'] = {
+        fileSingle: 'soubor',
+        filePlural: 'soubory',
+        browseLabel: 'Vybrat &hellip;',
+        removeLabel: 'Odstranit',
+        removeTitle: 'Vyčistit vybrané soubory',
+        cancelLabel: 'Storno',
+        cancelTitle: 'Přerušit  nahrávání',
+        uploadLabel: 'Nahrát',
+        uploadTitle: 'Nahrát vybrané soubory',
+        msgNo: 'Ne',
+        msgCancelled: 'Zrušeno',
+        msgZoomModalHeading: 'Detailní náhled',
+        msgSizeTooLarge: 'Soubor "{name}" (<b>{size} KB</b>): překročení - maximální povolená velikost <b>{maxSize} KB</b>.',
+        msgFilesTooLess: 'Musíte vybrat nejméně <b>{n}</b> {files} pro nahrání.',
+        msgFilesTooMany: 'Počet vybraných souborů pro nahrání <b>({n})</b>: překročení - maximální povolený limit <b>{m}</b>.',
+        msgFileNotFound: 'Soubor "{name}" nebyl nalezen!',
+        msgFileSecured: 'Zabezpečení souboru znemožnilo číst soubor "{name}".',
+        msgFileNotReadable: 'Soubor "{name}" není čitelný.',
+        msgFilePreviewAborted: 'Náhled souboru byl přerušen pro "{name}".',
+        msgFilePreviewError: 'Nastala chyba při načtení souboru "{name}".',
+        msgInvalidFileType: 'Neplatný typ souboru "{name}". Pouze "{types}" souborů jsou podporovány.',
+        msgInvalidFileExtension: 'Neplatná extenze souboru "{name}". Pouze "{extensions}" souborů jsou podporovány.',
+        msgUploadAborted: 'Soubor nahrávání byl přerušen',
+        msgValidationError: 'Chyba ověření',
+        msgLoading: 'Nahrávání souboru {index} z {files} &hellip;',
+        msgProgress: 'Nahrávání souboru {index} z {files} - {name} - {percent}% dokončeno.',
+        msgSelected: '{n} {files} vybrano',
+        msgFoldersNotAllowed: 'Táhni a pusť pouze soubory! Vynechané {n} pustěné složk(y).',
+        msgImageWidthSmall: 'Šířka image soubor "{name}", musí být alespoň {size} px.',
+        msgImageHeightSmall: 'Výška image soubor "{name}", musí být alespoň {size} px.',
+        msgImageWidthLarge: 'Šířka obrazového souboru "{name}" nelze překročit {size} px.',
+        msgImageHeightLarge: 'Výška obrazového souboru "{name}" nelze překročit {size} px.',
+        msgImageResizeError: 'Nelze získat rozměry obrázku změnit velikost.',
+        msgImageResizeException: 'Chyba při změně velikosti obrázku.<pre>{errors}</pre>',
+        dropZoneTitle: 'Táhni a pusť soubory sem &hellip;',
+        fileActionSettings: {
+            removeTitle: 'Odstranit soubor',
+            uploadTitle: 'nahrát soubor',
+            zoomTitle: 'zobrazit podrobnosti',
+            dragTitle: 'Move / Rearrange',
+            indicatorNewTitle: 'Ještě nenahrál',
+            indicatorSuccessTitle: 'Nahraný',
+            indicatorErrorTitle: 'Nahrát Chyba',
+            indicatorLoadingTitle: 'Nahrávání ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
 })(window.jQuery);

+ 68 - 59
js/fileinput_locale_da.js → js/locales/fileinput_locale_da.js

@@ -1,60 +1,69 @@
-/*!
- * FileInput Danish Translations
- *
- * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
- * any HTML markup tags in the messages must not be converted or translated.
- *
- * @see http://github.com/kartik-v/bootstrap-fileinput
- *
- * NOTE: this file must be saved in UTF-8 encoding.
- */
-(function ($) {
-    "use strict";
-    
-    $.fn.fileinputLocales['da'] = {
-        fileSingle: 'fil',
-        filePlural: 'filer',
-        browseLabel: 'Browse &hellip;',
-        removeLabel: 'Fjern',
-        removeTitle: 'Fjern valgte filer',
-        cancelLabel: 'Fortryd',
-        cancelTitle: 'Afbryd nuv&aelig;rende upload',
-        uploadLabel: 'Upload',
-        uploadTitle: 'Upload valgte filer',
-        msgNo: 'Ingen',
-        msgCancelled: 'aflyst',
-        msgZoomTitle: 'Se detaljer',
-        msgZoomModalHeading: 'Detaljeret visning',
-        msgSizeTooLarge: 'Fil "{name}" (<b>{size} KB</b>) er st&oslash;rre end de tilladte <b>{maxSize} KB</b>.',
-        msgFilesTooLess: 'Du skal mindst v&aelig;lge <b>{n}</b> {files} til upload.',
-        msgFilesTooMany: '<b>({n})</b> filer valgt til upload, men maks. <b>{m}</b> er tilladt.',
-        msgFileNotFound: 'Filen "{name}" blev ikke fundet!',
-        msgFileSecured: 'Sikkerhedsrestriktioner forhindrer l&aelig;sning af "{name}".',
-        msgFileNotReadable: 'Filen "{name}" kan ikke indl&aelig;ses.',
-        msgFilePreviewAborted: 'Filpreview annulleret for "{name}".',
-        msgFilePreviewError: 'Der skete en fejl under l&aelig;sningen af filen "{name}".',
-        msgInvalidFileType: 'Ukendt type for filen "{name}". Kun "{types}" kan bruges.',
-        msgInvalidFileExtension: 'Ukendt filtype for filen "{name}". Kun "{extensions}" filer kan bruges.',
-        msgUploadAborted: 'Filupload annulleret',
-        msgValidationError: 'Validering Fejl',
-        msgLoading: 'Henter fil {index} af {files} &hellip;',
-        msgProgress: 'Henter fil {index} af {files} - {name} - {percent}% f&aelig;rdiggjort.',
-        msgSelected: '{n} {files} valgt',
-        msgFoldersNotAllowed: 'Drag & drop kun filer! {n} mappe(r) sprunget over.',
-        msgImageWidthSmall: 'Bredden af billedet "{name}" skal v&aelig;re p&aring; mindst {size} px.',
-        msgImageHeightSmall: 'H&oslash;jden af billedet "{name}" skal v&aelig;re p&aring; mindst {size} px.',
-        msgImageWidthLarge: 'Bredden af billedet "{name}" m&aring; ikke v&aelig;re over {size} px.',
-        msgImageHeightLarge: 'H&oslash;jden af billedet "{name}" m&aring; ikke v&aelig;re over {size} px.',
-        msgImageResizeError: 'Kunne ikke få billedets dimensioner for at ændre størrelsen.',
-        msgImageResizeException: 'Fejl ved at ændre størrelsen på billedet.<pre>{errors}</pre>',
-        dropZoneTitle: 'Drag & drop filer her &hellip;',
-        fileActionSettings: {
-            removeTitle: 'Fjern fil',
-            uploadTitle: 'Upload fil',
-            indicatorNewTitle: 'Ikke uploadet endnu',
-            indicatorSuccessTitle: 'Uploadet',
-            indicatorErrorTitle: 'Upload fejl',
-            indicatorLoadingTitle: 'Uploader ...'
-        }
-    };
+/*!
+ * FileInput Danish Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+    "use strict";
+    
+    $.fn.fileinputLocales['da'] = {
+        fileSingle: 'fil',
+        filePlural: 'filer',
+        browseLabel: 'Browse &hellip;',
+        removeLabel: 'Fjern',
+        removeTitle: 'Fjern valgte filer',
+        cancelLabel: 'Fortryd',
+        cancelTitle: 'Afbryd nuv&aelig;rende upload',
+        uploadLabel: 'Upload',
+        uploadTitle: 'Upload valgte filer',
+        msgNo: 'Ingen',
+        msgCancelled: 'aflyst',
+        msgZoomModalHeading: 'Detaljeret visning',
+        msgSizeTooLarge: 'Fil "{name}" (<b>{size} KB</b>) er st&oslash;rre end de tilladte <b>{maxSize} KB</b>.',
+        msgFilesTooLess: 'Du skal mindst v&aelig;lge <b>{n}</b> {files} til upload.',
+        msgFilesTooMany: '<b>({n})</b> filer valgt til upload, men maks. <b>{m}</b> er tilladt.',
+        msgFileNotFound: 'Filen "{name}" blev ikke fundet!',
+        msgFileSecured: 'Sikkerhedsrestriktioner forhindrer l&aelig;sning af "{name}".',
+        msgFileNotReadable: 'Filen "{name}" kan ikke indl&aelig;ses.',
+        msgFilePreviewAborted: 'Filpreview annulleret for "{name}".',
+        msgFilePreviewError: 'Der skete en fejl under l&aelig;sningen af filen "{name}".',
+        msgInvalidFileType: 'Ukendt type for filen "{name}". Kun "{types}" kan bruges.',
+        msgInvalidFileExtension: 'Ukendt filtype for filen "{name}". Kun "{extensions}" filer kan bruges.',
+        msgUploadAborted: 'Filupload annulleret',
+        msgValidationError: 'Validering Fejl',
+        msgLoading: 'Henter fil {index} af {files} &hellip;',
+        msgProgress: 'Henter fil {index} af {files} - {name} - {percent}% f&aelig;rdiggjort.',
+        msgSelected: '{n} {files} valgt',
+        msgFoldersNotAllowed: 'Drag & drop kun filer! {n} mappe(r) sprunget over.',
+        msgImageWidthSmall: 'Bredden af billedet "{name}" skal v&aelig;re p&aring; mindst {size} px.',
+        msgImageHeightSmall: 'H&oslash;jden af billedet "{name}" skal v&aelig;re p&aring; mindst {size} px.',
+        msgImageWidthLarge: 'Bredden af billedet "{name}" m&aring; ikke v&aelig;re over {size} px.',
+        msgImageHeightLarge: 'H&oslash;jden af billedet "{name}" m&aring; ikke v&aelig;re over {size} px.',
+        msgImageResizeError: 'Kunne ikke få billedets dimensioner for at ændre størrelsen.',
+        msgImageResizeException: 'Fejl ved at ændre størrelsen på billedet.<pre>{errors}</pre>',
+        dropZoneTitle: 'Drag & drop filer her &hellip;',
+        fileActionSettings: {
+            removeTitle: 'Fjern fil',
+            uploadTitle: 'Upload fil',
+            zoomTitle: 'Se detaljer',
+            dragTitle: 'Move / Rearrange',
+            indicatorNewTitle: 'Ikke uploadet endnu',
+            indicatorSuccessTitle: 'Uploadet',
+            indicatorErrorTitle: 'Upload fejl',
+            indicatorLoadingTitle: 'Uploader ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
 })(window.jQuery);

+ 67 - 58
js/fileinput_locale_de.js → js/locales/fileinput_locale_de.js

@@ -1,58 +1,67 @@
-/*!
- * FileInput German Translations
- *
- * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
- * any HTML markup tags in the messages must not be converted or translated.
- *
- * @see http://github.com/kartik-v/bootstrap-fileinput
- */
-(function ($) {
-    "use strict";
-
-    $.fn.fileinputLocales['de'] = {
-        fileSingle: 'Datei',
-        filePlural: 'Dateien',
-        browseLabel: 'Auswählen &hellip;',
-        removeLabel: 'Löschen',
-        removeTitle: 'Ausgewählte löschen',
-        cancelLabel: 'Laden',
-        cancelTitle: 'Hochladen abbrechen',
-        uploadLabel: 'Hochladen',
-        uploadTitle: 'Hochladen der ausgewählten Dateien',
-        msgNo: 'Keine',
-        msgCancelled: 'Abgebrochen',
-        msgZoomTitle: 'Details anzeigen',
-        msgZoomModalHeading: 'ausführliche Vorschau',
-        msgSizeTooLarge: 'Datei "{name}" (<b>{size} KB</b>) überschreitet maximal zulässige Upload-Größe von <b>{maxSize} KB</b>.',
-        msgFilesTooLess: 'Sie müssen mindestens <b>{n}</b> {files} zum Hochladen auswählen.',
-        msgFilesTooMany: 'Anzahl der Dateien für den Upload ausgewählt <b>({n})</b> überschreitet maximal zulässige Grenze von <b>{m}</b> Stück.',
-        msgFileNotFound: 'Datei "{name}" wurde nicht gefunden!',
-        msgFileSecured: 'Sicherheitseinstellungen verhindern das Lesen der Datei "{name}".',
-        msgFileNotReadable: 'Die Datei "{name}" ist nicht lesbar.',
-        msgFilePreviewAborted: 'Dateivorschau abgebrochen für "{name}".',
-        msgFilePreviewError: 'Beim Lesen der Datei "{name}" ein Fehler aufgetreten.',
-        msgInvalidFileType: 'Ungültiger Typ für Datei "{name}". Nur Dateien der Typen "{types}" werden unterstützt.',
-        msgInvalidFileExtension: 'Ungültige Erweiterung für Datei "{name}". Nur Dateien mit der Endung "{extensions}" werden unterstützt.',
-        msgUploadAborted: 'Der Datei-Upload wurde abgebrochen',
-        msgValidationError: 'Validierungs fehler',
-        msgLoading: 'Lade Datei {index} von {files} hoch&hellip;',
-        msgProgress: 'Datei {index} von {files} - {name} - zu {percent}% fertiggestellt.',
-        msgSelected: '{n} {files} ausgewählt',
-        msgFoldersNotAllowed: 'Drag & Drop funktioniert nur bei Dateien! {n} Ordner übersprungen.',
-        msgImageWidthSmall: 'Breite der Bilddatei "{name}" muss mindestens {size} px betragen.',
-        msgImageHeightSmall: 'Höhe der Bilddatei "{name}" muss mindestens {size} px betragen.',
-        msgImageWidthLarge: 'Breite der Bilddatei "{name}" nicht überschreiten {size} px.',
-        msgImageHeightLarge: 'Höhe der Bilddatei "{name}" nicht überschreiten {size} px.',
-        msgImageResizeError: 'Konnte nicht die Bildabmessungen zu ändern.',
-        msgImageResizeException: 'Fehler beim Ändern der Größe des Bildes.<pre>{errors}</pre>',
-        dropZoneTitle: 'Dateien hierher ziehen &hellip;',
-        fileActionSettings: {
-            removeTitle: 'Datei entfernen',
-            uploadTitle: 'Datei hochladen',
-            indicatorNewTitle: 'Noch nicht hochgeladen',
-            indicatorSuccessTitle: 'Hochgeladen',
-            indicatorErrorTitle: 'Upload Fehler',
-            indicatorLoadingTitle: 'Hochladen ...'
-        }
-    };
-})(window.jQuery);
+/*!
+ * FileInput German Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputLocales['de'] = {
+        fileSingle: 'Datei',
+        filePlural: 'Dateien',
+        browseLabel: 'Auswählen &hellip;',
+        removeLabel: 'Löschen',
+        removeTitle: 'Ausgewählte löschen',
+        cancelLabel: 'Laden',
+        cancelTitle: 'Hochladen abbrechen',
+        uploadLabel: 'Hochladen',
+        uploadTitle: 'Hochladen der ausgewählten Dateien',
+        msgNo: 'Keine',
+        msgCancelled: 'Abgebrochen',
+        msgZoomModalHeading: 'ausführliche Vorschau',
+        msgSizeTooLarge: 'Datei "{name}" (<b>{size} KB</b>) überschreitet maximal zulässige Upload-Größe von <b>{maxSize} KB</b>.',
+        msgFilesTooLess: 'Sie müssen mindestens <b>{n}</b> {files} zum Hochladen auswählen.',
+        msgFilesTooMany: 'Anzahl der Dateien für den Upload ausgewählt <b>({n})</b> überschreitet maximal zulässige Grenze von <b>{m}</b> Stück.',
+        msgFileNotFound: 'Datei "{name}" wurde nicht gefunden!',
+        msgFileSecured: 'Sicherheitseinstellungen verhindern das Lesen der Datei "{name}".',
+        msgFileNotReadable: 'Die Datei "{name}" ist nicht lesbar.',
+        msgFilePreviewAborted: 'Dateivorschau abgebrochen für "{name}".',
+        msgFilePreviewError: 'Beim Lesen der Datei "{name}" ein Fehler aufgetreten.',
+        msgInvalidFileType: 'Ungültiger Typ für Datei "{name}". Nur Dateien der Typen "{types}" werden unterstützt.',
+        msgInvalidFileExtension: 'Ungültige Erweiterung für Datei "{name}". Nur Dateien mit der Endung "{extensions}" werden unterstützt.',
+        msgUploadAborted: 'Der Datei-Upload wurde abgebrochen',
+        msgValidationError: 'Validierungs fehler',
+        msgLoading: 'Lade Datei {index} von {files} hoch&hellip;',
+        msgProgress: 'Datei {index} von {files} - {name} - zu {percent}% fertiggestellt.',
+        msgSelected: '{n} {files} ausgewählt',
+        msgFoldersNotAllowed: 'Drag & Drop funktioniert nur bei Dateien! {n} Ordner übersprungen.',
+        msgImageWidthSmall: 'Breite der Bilddatei "{name}" muss mindestens {size} px betragen.',
+        msgImageHeightSmall: 'Höhe der Bilddatei "{name}" muss mindestens {size} px betragen.',
+        msgImageWidthLarge: 'Breite der Bilddatei "{name}" nicht überschreiten {size} px.',
+        msgImageHeightLarge: 'Höhe der Bilddatei "{name}" nicht überschreiten {size} px.',
+        msgImageResizeError: 'Konnte nicht die Bildabmessungen zu ändern.',
+        msgImageResizeException: 'Fehler beim Ändern der Größe des Bildes.<pre>{errors}</pre>',
+        dropZoneTitle: 'Dateien hierher ziehen &hellip;',
+        fileActionSettings: {
+            removeTitle: 'Datei entfernen',
+            uploadTitle: 'Datei hochladen',
+            zoomTitle: 'Details anzeigen',
+            dragTitle: 'Move / Rearrange',
+            indicatorNewTitle: 'Noch nicht hochgeladen',
+            indicatorSuccessTitle: 'Hochgeladen',
+            indicatorErrorTitle: 'Upload Fehler',
+            indicatorLoadingTitle: 'Hochladen ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
+})(window.jQuery);

+ 68 - 59
js/fileinput_locale_el.js → js/locales/fileinput_locale_el.js

@@ -1,60 +1,69 @@
-/*!
- * FileInput Greek Translations
- *
- * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
- * any HTML markup tags in the messages must not be converted or translated.
- *
- * @see http://github.com/kartik-v/bootstrap-fileinput
- *
- * NOTE: this file must be saved in UTF-8 encoding.
- */
-(function ($) {
-    "use strict";
-
-    $.fn.fileinputLocales['el'] = {
-        fileSingle: 'Αρχείο',
-        filePlural: 'Αρχεία',
-        browseLabel: 'Αναζήτηση &hellip;',
-        removeLabel: 'Ακύρωση',
-        removeTitle: 'Εκκαθάριση αρχείων',
-        cancelLabel: 'Ακύρωση',
-        cancelTitle: 'Ακύρωση μεταφόρτωσης',
-        uploadLabel: 'Μεταφόρτωση',
-        uploadTitle: 'Μεταφόρτωση επιλεγμένων αρχείων',
-        msgNo: 'Όχι',
-        msgCancelled: 'Ακυρώθηκε',
-        msgZoomTitle: 'Δείτε λεπτομέρειες',
-        msgZoomModalHeading: 'λεπτομερής Προεπισκόπηση',
-        msgSizeTooLarge: 'Το αρχείο "{name}" (<b>{size} KB</b>) υπερβαίνει το μέγιστο επιτρεπόμενο μέγεθος μεταφόρτωσης <b>{maxSize} KB</b>.',
-        msgFilesTooLess: 'Πρέπει να επιλέξετε τουλάχιστον <b>{n}</b> {files} για να ξεκινήσει η μεταφόρτωση.',
-        msgFilesTooMany: 'Ο αριθμός των αρχείων που έχουν επιλεγεί για μεταφόρτωση <b>({n})</b> υπερβαίνει το μέγιστο επιτρεπόμενο αριθμό <b>{m}</b>.',
-        msgFileNotFound: 'Το αρχείο με όνομα "{name}" δεν βρέθηκε!',
-        msgFileSecured: 'Περιορισμοί ασφαλείας εμπόδισαν την ανάγνωση του αρχείου"{name}".',
-        msgFileNotReadable: 'Το αρχείο με όνομα "{name}" δεν είναι αναγνώσιμο.',
-        msgFilePreviewAborted: 'Η προεπισκόπηση του αρχείου ακυρώθηκε για "{name}".',
-        msgFilePreviewError: 'Παρουσιάστηκε σφάλμα κατά την ανάγνωση του αρχείου "{name}".',
-        msgInvalidFileType: 'Μη έγκυρος τύπος αρχείου "{name}". Οι τύποι αρχείων που υποστηρίζονται είναι : "{types}".',
-        msgInvalidFileExtension: 'Μη έγκυρη επέκταση αρχείου "{name}". Οι επεκτάσεις που υποστηρίζονται είναι:  "{extensions}" .',
-        msgUploadAborted: 'Το ανέβασμα των αρχείων ματαιώθηκε',
-        msgValidationError: 'Σπικύρωση σφάλματος',
-        msgLoading: 'Φόρτωση αρχείου {index} από {files} &hellip;',
-        msgProgress: 'Φόρτωση αρχείου {index} απο {files} - {name} - {percent}% ολοκληρώθηκε.',
-        msgSelected: '{n} {files} επιλέχθηκαν',
-        msgFoldersNotAllowed: 'Μπορείτε να σύρετε μόνο αρχεία! Παραβλέφθηκαν {n} φάκελος(οι).',
-        msgImageWidthSmall: 'Πλάτος του αρχείου εικόνας "{name}" πρέπει να είναι τουλάχιστον {size} px.',
-        msgImageHeightSmall: 'Ύψος του αρχείου εικόνας "{name}" πρέπει να είναι τουλάχιστον {size} px.',
-        msgImageWidthLarge: 'Πλάτος του αρχείου εικόνας "{name}" δεν μπορεί να υπερβαίνει το {size} px.',
-        msgImageHeightLarge: 'Ύψος του αρχείου εικόνας "{name}" δεν μπορεί να υπερβαίνει το {size} px.',
-        msgImageResizeError: 'Δεν θα μπορούσε να πάρει τις διαστάσεις της εικόνας για να αλλάξετε το μέγεθος.',
-        msgImageResizeException: 'Σφάλμα κατά την αλλαγή μεγέθους της εικόνας.<pre>{errors}</pre>',
-        dropZoneTitle: 'Σύρετε τα αρχεία εδώ &hellip;',
-        fileActionSettings: {
-            removeTitle: 'Αφαιρέστε το αρχείο',
-            uploadTitle: 'Ανεβάστε το αρχείο',
-            indicatorNewTitle: 'Δεν ανεβάσει ακόμα',
-            indicatorSuccessTitle: 'Προστέθηκε',
-            indicatorErrorTitle: 'Ανέβασμα Σφάλμα',
-            indicatorLoadingTitle: 'Μεταφόρτωση ...'
-        }
-    };
+/*!
+ * FileInput Greek Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputLocales['el'] = {
+        fileSingle: 'Αρχείο',
+        filePlural: 'Αρχεία',
+        browseLabel: 'Αναζήτηση &hellip;',
+        removeLabel: 'Ακύρωση',
+        removeTitle: 'Εκκαθάριση αρχείων',
+        cancelLabel: 'Ακύρωση',
+        cancelTitle: 'Ακύρωση μεταφόρτωσης',
+        uploadLabel: 'Μεταφόρτωση',
+        uploadTitle: 'Μεταφόρτωση επιλεγμένων αρχείων',
+        msgNo: 'Όχι',
+        msgCancelled: 'Ακυρώθηκε',
+        msgZoomModalHeading: 'λεπτομερής Προεπισκόπηση',
+        msgSizeTooLarge: 'Το αρχείο "{name}" (<b>{size} KB</b>) υπερβαίνει το μέγιστο επιτρεπόμενο μέγεθος μεταφόρτωσης <b>{maxSize} KB</b>.',
+        msgFilesTooLess: 'Πρέπει να επιλέξετε τουλάχιστον <b>{n}</b> {files} για να ξεκινήσει η μεταφόρτωση.',
+        msgFilesTooMany: 'Ο αριθμός των αρχείων που έχουν επιλεγεί για μεταφόρτωση <b>({n})</b> υπερβαίνει το μέγιστο επιτρεπόμενο αριθμό <b>{m}</b>.',
+        msgFileNotFound: 'Το αρχείο με όνομα "{name}" δεν βρέθηκε!',
+        msgFileSecured: 'Περιορισμοί ασφαλείας εμπόδισαν την ανάγνωση του αρχείου"{name}".',
+        msgFileNotReadable: 'Το αρχείο με όνομα "{name}" δεν είναι αναγνώσιμο.',
+        msgFilePreviewAborted: 'Η προεπισκόπηση του αρχείου ακυρώθηκε για "{name}".',
+        msgFilePreviewError: 'Παρουσιάστηκε σφάλμα κατά την ανάγνωση του αρχείου "{name}".',
+        msgInvalidFileType: 'Μη έγκυρος τύπος αρχείου "{name}". Οι τύποι αρχείων που υποστηρίζονται είναι : "{types}".',
+        msgInvalidFileExtension: 'Μη έγκυρη επέκταση αρχείου "{name}". Οι επεκτάσεις που υποστηρίζονται είναι:  "{extensions}" .',
+        msgUploadAborted: 'Το ανέβασμα των αρχείων ματαιώθηκε',
+        msgValidationError: 'Σπικύρωση σφάλματος',
+        msgLoading: 'Φόρτωση αρχείου {index} από {files} &hellip;',
+        msgProgress: 'Φόρτωση αρχείου {index} απο {files} - {name} - {percent}% ολοκληρώθηκε.',
+        msgSelected: '{n} {files} επιλέχθηκαν',
+        msgFoldersNotAllowed: 'Μπορείτε να σύρετε μόνο αρχεία! Παραβλέφθηκαν {n} φάκελος(οι).',
+        msgImageWidthSmall: 'Πλάτος του αρχείου εικόνας "{name}" πρέπει να είναι τουλάχιστον {size} px.',
+        msgImageHeightSmall: 'Ύψος του αρχείου εικόνας "{name}" πρέπει να είναι τουλάχιστον {size} px.',
+        msgImageWidthLarge: 'Πλάτος του αρχείου εικόνας "{name}" δεν μπορεί να υπερβαίνει το {size} px.',
+        msgImageHeightLarge: 'Ύψος του αρχείου εικόνας "{name}" δεν μπορεί να υπερβαίνει το {size} px.',
+        msgImageResizeError: 'Δεν θα μπορούσε να πάρει τις διαστάσεις της εικόνας για να αλλάξετε το μέγεθος.',
+        msgImageResizeException: 'Σφάλμα κατά την αλλαγή μεγέθους της εικόνας.<pre>{errors}</pre>',
+        dropZoneTitle: 'Σύρετε τα αρχεία εδώ &hellip;',
+        fileActionSettings: {
+            removeTitle: 'Αφαιρέστε το αρχείο',
+            uploadTitle: 'Ανεβάστε το αρχείο',
+            zoomTitle: 'Δείτε λεπτομέρειες',
+            dragTitle: 'Move / Rearrange',
+            indicatorNewTitle: 'Δεν ανεβάσει ακόμα',
+            indicatorSuccessTitle: 'Προστέθηκε',
+            indicatorErrorTitle: 'Ανέβασμα Σφάλμα',
+            indicatorLoadingTitle: 'Μεταφόρτωση ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
 })(window.jQuery);

+ 69 - 60
js/fileinput_locale_es.js → js/locales/fileinput_locale_es.js

@@ -1,60 +1,69 @@
-/*!
- * FileInput Spanish Translations
- *
- * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
- * any HTML markup tags in the messages must not be converted or translated.
- *
- * @see http://github.com/kartik-v/bootstrap-fileinput
- *
- * NOTE: this file must be saved in UTF-8 encoding.
- */
-(function ($) {
-    "use strict";
-
-    $.fn.fileinputLocales['es'] = {
-        fileSingle: 'archivo',
-        filePlural: 'archivos',
-        browseLabel: 'Examinar &hellip;',
-        removeLabel: 'Quitar',
-        removeTitle: 'Quitar archivos seleccionados',
-        cancelLabel: 'Cancelar',
-        cancelTitle: 'Abortar la subida en curso',
-        uploadLabel: 'Subir archivo',
-        uploadTitle: 'Subir archivos seleccionados',
-        msgNo: 'No',
-        msgCancelled: 'Cancelado',
-        msgZoomTitle: 'Ver detalles',
-        msgZoomModalHeading: 'Vista previa detallada',
-        msgSizeTooLarge: 'Archivo "{name}" (<b>{size} KB</b>) excede el tamaño máximo permitido de <b>{maxSize} KB</b>.',
-        msgFilesTooLess: 'Debe seleccionar al menos <b>{n}</b> {files} a cargar.',
-        msgFilesTooMany: 'El número de archivos seleccionados a cargar <b>({n})</b> excede el límite máximo permitido de <b>{m}</b>.',
-        msgFileNotFound: 'Archivo "{name}" no encontrado.',
-        msgFileSecured: 'No es posible acceder al archivo "{name}" porque estará siendo usado por otra aplicación o no tengamos permisos de lectura.',
-        msgFileNotReadable: 'No es posible acceder al archivo "{name}".',
-        msgFilePreviewAborted: 'Previsualización del archivo "{name}" cancelada.',
-        msgFilePreviewError: 'Ocurrió un error mientras se leía el archivo "{name}".',
-        msgInvalidFileType: 'Tipo de archivo no válido para "{name}". Sólo archivos "{types}" son permitidos.',
-        msgInvalidFileExtension: 'Extensión de archivo no válido para "{name}". Sólo archivos "{extensions}" son permitidos.',
-        msgUploadAborted: 'La carga de archivos se ha cancelado',
-        msgValidationError: 'Error de validacion',
-        msgLoading: 'Subiendo archivo {index} de {files} &hellip;',
-        msgProgress: 'Subiendo archivo {index} de {files} - {name} - {percent}% completado.',
-        msgSelected: '{n} {files} seleccionado(s)',
-        msgFoldersNotAllowed: 'Arrastre y suelte únicamente archivos. Omitida(s) {n} carpeta(s).',
-        msgImageWidthSmall: 'El ancho de la imagen "{name}" debe ser al menos {size} px.',
-        msgImageHeightSmall: 'La altura de la imagen "{name}" debe ser al menos {size} px.',
-        msgImageWidthLarge: 'El ancho de la imagen "{name}" no puede exceder de {size} px.',
-        msgImageHeightLarge: 'La altura de la imagen "{name}" no puede exceder de {size} px.',
-        msgImageResizeError: 'No se pudo obtener las dimensiones de imagen para cambiar el tamaño.',
-        msgImageResizeException: 'Error al cambiar el tamaño de la imagen.<pre>{errors}</pre>',
-        dropZoneTitle: 'Arrastre y suelte aquí los archivos &hellip;',
-        fileActionSettings: {
-            removeTitle: 'Eliminar archivo',
-            uploadTitle: 'Subir archivo',
-            indicatorNewTitle: 'No subido todavía',
-            indicatorSuccessTitle: 'Subido',
-            indicatorErrorTitle: 'Subir Error',
-            indicatorLoadingTitle: 'Subiendo ...'
-        }
-    };
-})(window.jQuery);
+/*!
+ * FileInput Spanish Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputLocales['es'] = {
+        fileSingle: 'archivo',
+        filePlural: 'archivos',
+        browseLabel: 'Examinar &hellip;',
+        removeLabel: 'Quitar',
+        removeTitle: 'Quitar archivos seleccionados',
+        cancelLabel: 'Cancelar',
+        cancelTitle: 'Abortar la subida en curso',
+        uploadLabel: 'Subir archivo',
+        uploadTitle: 'Subir archivos seleccionados',
+        msgNo: 'No',
+        msgCancelled: 'Cancelado',
+        msgZoomModalHeading: 'Vista previa detallada',
+        msgSizeTooLarge: 'Archivo "{name}" (<b>{size} KB</b>) excede el tamaño máximo permitido de <b>{maxSize} KB</b>.',
+        msgFilesTooLess: 'Debe seleccionar al menos <b>{n}</b> {files} a cargar.',
+        msgFilesTooMany: 'El número de archivos seleccionados a cargar <b>({n})</b> excede el límite máximo permitido de <b>{m}</b>.',
+        msgFileNotFound: 'Archivo "{name}" no encontrado.',
+        msgFileSecured: 'No es posible acceder al archivo "{name}" porque estará siendo usado por otra aplicación o no tengamos permisos de lectura.',
+        msgFileNotReadable: 'No es posible acceder al archivo "{name}".',
+        msgFilePreviewAborted: 'Previsualización del archivo "{name}" cancelada.',
+        msgFilePreviewError: 'Ocurrió un error mientras se leía el archivo "{name}".',
+        msgInvalidFileType: 'Tipo de archivo no válido para "{name}". Sólo archivos "{types}" son permitidos.',
+        msgInvalidFileExtension: 'Extensión de archivo no válido para "{name}". Sólo archivos "{extensions}" son permitidos.',
+        msgUploadAborted: 'La carga de archivos se ha cancelado',
+        msgValidationError: 'Error de validacion',
+        msgLoading: 'Subiendo archivo {index} de {files} &hellip;',
+        msgProgress: 'Subiendo archivo {index} de {files} - {name} - {percent}% completado.',
+        msgSelected: '{n} {files} seleccionado(s)',
+        msgFoldersNotAllowed: 'Arrastre y suelte únicamente archivos. Omitida(s) {n} carpeta(s).',
+        msgImageWidthSmall: 'El ancho de la imagen "{name}" debe ser al menos {size} px.',
+        msgImageHeightSmall: 'La altura de la imagen "{name}" debe ser al menos {size} px.',
+        msgImageWidthLarge: 'El ancho de la imagen "{name}" no puede exceder de {size} px.',
+        msgImageHeightLarge: 'La altura de la imagen "{name}" no puede exceder de {size} px.',
+        msgImageResizeError: 'No se pudo obtener las dimensiones de imagen para cambiar el tamaño.',
+        msgImageResizeException: 'Error al cambiar el tamaño de la imagen.<pre>{errors}</pre>',
+        dropZoneTitle: 'Arrastre y suelte aquí los archivos &hellip;',
+        fileActionSettings: {
+            removeTitle: 'Eliminar archivo',
+            uploadTitle: 'Subir archivo',
+            zoomTitle: 'Ver detalles',
+            dragTitle: 'Move / Rearrange',
+            indicatorNewTitle: 'No subido todavía',
+            indicatorSuccessTitle: 'Subido',
+            indicatorErrorTitle: 'Subir Error',
+            indicatorLoadingTitle: 'Subiendo ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
+})(window.jQuery);

+ 70 - 61
js/fileinput_locale_fa.js → js/locales/fileinput_locale_fa.js

@@ -1,61 +1,70 @@
-/*!
- * FileInput Persian Translations
- *
- * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
- * any HTML markup tags in the messages must not be converted or translated.
- *
- * @see http://github.com/kartik-v/bootstrap-fileinput
- * @author Milad Nekofar <[email protected]>
- *
- * NOTE: this file must be saved in UTF-8 encoding.
- */
-(function ($) {
-    "use strict";
-
-    $.fn.fileinputLocales['fa'] = {
-        fileSingle: 'فایل',
-        filePlural: 'فایل',
-        browseLabel: 'مرور &hellip;',
-        removeLabel: 'حذف',
-        removeTitle: 'پاکسازی فایل‌های انتخاب شده',
-        cancelLabel: 'لغو',
-        cancelTitle: 'لغو بارگزاری جاری',
-        uploadLabel: 'بارگذاری',
-        uploadTitle: 'بارگذاری فایل‌های انتخاب شده',
-        msgNo: 'خیر',
-        msgCancelled: 'لغو',
-        msgZoomTitle: 'دیدن جزئیات',
-        msgZoomModalHeading: 'پیشنمایش مفصل',
-        msgSizeTooLarge: 'فایل "{name}" (<b>{size} کیلوبایت</b>) از حداکثر مجاز <b>{maxSize} کیلوبایت</b>.',
-        msgFilesTooLess: 'شما باید حداقل <b>{n}</b> {files} فایل برای بارگذاری انتخاب کنید.',
-        msgFilesTooMany: 'تعداد فایل‌های انتخاب شده برای بارگذاری <b>({n})</b> از حداکثر مجاز عبور کرده است <b>{m}</b>.',
-        msgFileNotFound: 'فایل "{name}" یافت نشد!',
-        msgFileSecured: 'محدودیت های امنیتی مانع خواندن فایل "{name}" است.',
-        msgFileNotReadable: 'فایل "{name}" قابل نوشتن نیست.',
-        msgFilePreviewAborted: 'پیشنمایش فایل "{name}". شکست خورد',
-        msgFilePreviewError: 'در هنگام خواندن فایل "{name}" خطایی رخ داد.',
-        msgInvalidFileType: 'نوع فایل "{name}" معتبر نیست. فقط "{types}" پشیبانی می‌شود.',
-        msgInvalidFileExtension: 'پسوند فایل "{name}" معتبر نیست. فقط "{extensions}" پشتیبانی می‌شود.',
-        msgUploadAborted: 'The file upload was aborted',
-        msgValidationError: 'خطای اعتبار سنجی',
-        msgLoading: 'بارگیری فایل {index} از {files} &hellip;',
-        msgProgress: 'بارگیری فایل {index} از {files} - {name} - {percent}% تمام شد.',
-        msgSelected: '{n} {files} انتخاب شده',
-        msgFoldersNotAllowed: 'فقط فایل‌ها را بکشید و رها کنید! {n} پوشه نادیده گرفته شد.',
-        msgImageWidthSmall: 'عرض فایل تصویر "{name}" باید حداقل {size} پیکسل باشد.',
-        msgImageHeightSmall: 'ارتفاع فایل تصویر "{name}" باید حداقل {size} پیکسل باشد.',
-        msgImageWidthLarge: 'عرض فایل تصویر "{name}" نمیتواند از {size} پیکسل بیشتر باشد.',
-        msgImageHeightLarge: 'ارتفاع فایل تصویر "{name}" نمی‌تواند از {size} پیکسل بیشتر باشد.',
-        msgImageResizeError: 'یافت نشد ابعاد تصویر را برای تغییر اندازه.',
-        msgImageResizeException: 'خطا در هنگام تغییر اندازه تصویر.<pre>{errors}</pre>',
-        dropZoneTitle: 'فایل‌ها را بکشید و در اینجا رها کنید &hellip;',
-        fileActionSettings: {
-            removeTitle: 'حذف فایل',
-            uploadTitle: 'آپلود فایل',
-            indicatorNewTitle: 'آپلود نشده است',
-            indicatorSuccessTitle: 'آپلود شده',
-            indicatorErrorTitle: 'بارگذاری خطا',
-            indicatorLoadingTitle: 'آپلود ...'
-        }
-    };
-})(window.jQuery);
+/*!
+ * FileInput Persian Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ * @author Milad Nekofar <[email protected]>
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputLocales['fa'] = {
+        fileSingle: 'فایل',
+        filePlural: 'فایل',
+        browseLabel: 'مرور &hellip;',
+        removeLabel: 'حذف',
+        removeTitle: 'پاکسازی فایل‌های انتخاب شده',
+        cancelLabel: 'لغو',
+        cancelTitle: 'لغو بارگزاری جاری',
+        uploadLabel: 'بارگذاری',
+        uploadTitle: 'بارگذاری فایل‌های انتخاب شده',
+        msgNo: 'No',
+        msgCancelled: 'Cancelled',
+        msgZoomModalHeading: 'Detailed Preview',
+        msgSizeTooLarge: 'فایل "{name}" (<b>{size} کیلوبایت</b>) از حداکثر مجاز <b>{maxSize} کیلوبایت</b>.',
+        msgFilesTooLess: 'شما باید حداقل <b>{n}</b> {files} فایل برای بارگذاری انتخاب کنید.',
+        msgFilesTooMany: 'تعداد فایل‌های انتخاب شده برای بارگذاری <b>({n})</b> از حداکثر مجاز عبور کرده است <b>{m}</b>.',
+        msgFileNotFound: 'فایل "{name}" یافت نشد!',
+        msgFileSecured: 'محدودیت های امنیتی مانع خواندن فایل "{name}" است.',
+        msgFileNotReadable: 'فایل "{name}" قابل نوشتن نیست.',
+        msgFilePreviewAborted: 'پیشنمایش فایل "{name}". شکست خورد',
+        msgFilePreviewError: 'در هنگام خواندن فایل "{name}" خطایی رخ داد.',
+        msgInvalidFileType: 'نوع فایل "{name}" معتبر نیست. فقط "{types}" پشیبانی می‌شود.',
+        msgInvalidFileExtension: 'پسوند فایل "{name}" معتبر نیست. فقط "{extensions}" پشتیبانی می‌شود.',
+        msgUploadAborted: 'The file upload was aborted',
+        msgValidationError: 'خطای اعتبار سنجی',
+        msgLoading: 'بارگیری فایل {index} از {files} &hellip;',
+        msgProgress: 'بارگیری فایل {index} از {files} - {name} - {percent}% تمام شد.',
+        msgSelected: '{n} {files} انتخاب شده',
+        msgFoldersNotAllowed: 'فقط فایل‌ها را بکشید و رها کنید! {n} پوشه نادیده گرفته شد.',
+        msgImageWidthSmall: 'عرض فایل تصویر "{name}" باید حداقل {size} پیکسل باشد.',
+        msgImageHeightSmall: 'ارتفاع فایل تصویر "{name}" باید حداقل {size} پیکسل باشد.',
+        msgImageWidthLarge: 'عرض فایل تصویر "{name}" نمیتواند از {size} پیکسل بیشتر باشد.',
+        msgImageHeightLarge: 'ارتفاع فایل تصویر "{name}" نمی‌تواند از {size} پیکسل بیشتر باشد.',
+        msgImageResizeError: 'یافت نشد ابعاد تصویر را برای تغییر اندازه.',
+        msgImageResizeException: 'خطا در هنگام تغییر اندازه تصویر.<pre>{errors}</pre>',
+        dropZoneTitle: 'فایل‌ها را بکشید و در اینجا رها کنید &hellip;',
+        fileActionSettings: {
+            removeTitle: 'حذف فایل',
+            uploadTitle: 'آپلود فایل',
+            zoomTitle: 'دیدن جزئیات',
+            dragTitle: 'Move / Rearrange',
+            indicatorNewTitle: 'آپلود نشده است',
+            indicatorSuccessTitle: 'آپلود شده',
+            indicatorErrorTitle: 'بارگذاری خطا',
+            indicatorLoadingTitle: 'آپلود ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
+})(window.jQuery);

+ 60 - 42
js/fileinput_locale_fi.js → js/locales/fileinput_locale_fi.js

@@ -1,43 +1,61 @@
-/*!
- * FileInput Finnish Translations
- *
- * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
- * any HTML markup tags in the messages must not be converted or translated.
- *
- * @see http://github.com/kartik-v/bootstrap-fileinput
- *
- * NOTE: this file must be saved in UTF-8 encoding.
- */
-(function ($) {
-    "use strict";
-
-    $.fn.fileinputLocales.fi = {
-        fileSingle: 'tiedosto',
-        filePlural: 'tiedostot',
-        browseLabel: 'Selaa &hellip;',
-        removeLabel: 'Poista',
-        removeTitle: 'Tyhj&auml;nn&auml; valitut tiedostot',
-        cancelLabel: 'Peruuta',
-        cancelTitle: 'Peruuta lataus',
-        uploadLabel: 'Lataa',
-        uploadTitle: 'Lataa valitut tiedostot',
-        msgSizeTooLarge: 'Tiedosto "{name}" (<b>{size} Kt</b>) ylitt&auml;&auml; suurimman sallitun tiedoston koon, joka on <b>{maxSize} Kt</b>. Yrit&auml; uudelleen!',
-        msgFilesTooLess: 'V&auml;hint&auml;&auml;n <b>{n}</b> {files} tiedostoa on valittava ladattavaksi. Ole hyv&auml; ja yrit&auml; uudelleen!',
-        msgFilesTooMany: 'Valittujen tiedostojen lukum&auml;&auml;r&auml; <b>({n})</b> ylitt&auml;&auml; suurimman sallitun m&auml;&auml;r&auml;n <b>{m}</b>. Ole hyv&auml; ja yrit&auml; uudelleen!',
-        msgFileNotFound: 'Tiedostoa "{name}" ei l&ouml;ydy!',
-        msgFileSecured: 'Tietoturvarajoitukset est&auml;v&auml;t tiedoston "{name}" lukemisen.',
-        msgFileNotReadable: 'Tiedosto "{name}" ei ole luettavissa.',
-        msgFilePreviewAborted: 'Tiedoston "{name}" esikatselu keskeytetty.',
-        msgFilePreviewError: 'Virhe on tapahtunut luettaessa tiedostoa "{name}".',
-        msgInvalidFileType: 'Tiedosto "{name}" on v&auml;&auml;r&auml;n tyyppinen. Ainoastaan tiedostot tyyppi&auml; "{types}" ovat tuettuja.',
-        msgInvalidFileExtension: 'Tiedoston "{name}" tarkenne on ep&auml;kelpo. Ainoastaan tarkenteet "{extensions}" ovat tuettuja.',
-        msgValidationError: 'Tiedoston latausvirhe',
-        msgLoading: 'Ladataan tiedostoa {index} / {files} &hellip;',
-        msgProgress: 'Ladataan tiedostoa {index} / {files} - {name} - {percent}% valmistunut.',
-        msgSelected: '{n} tiedostoa valittu',
-        msgFoldersNotAllowed: 'Raahaa ja pudota ainoastaan tiedostoja! Ohitettu {n} raahattua kansiota.',
-        dropZoneTitle: 'Raahaa ja pudota tiedostot t&auml;h&auml;n &hellip;'
-    };
-
-    $.extend($.fn.fileinput.defaults, $.fn.fileinputLocales.fi);
+/*!
+ * FileInput Finnish Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputLocales.fi = {
+        fileSingle: 'tiedosto',
+        filePlural: 'tiedostot',
+        browseLabel: 'Selaa &hellip;',
+        removeLabel: 'Poista',
+        removeTitle: 'Tyhj&auml;nn&auml; valitut tiedostot',
+        cancelLabel: 'Peruuta',
+        cancelTitle: 'Peruuta lataus',
+        uploadLabel: 'Lataa',
+        uploadTitle: 'Lataa valitut tiedostot',
+        msgSizeTooLarge: 'Tiedosto "{name}" (<b>{size} Kt</b>) ylitt&auml;&auml; suurimman sallitun tiedoston koon, joka on <b>{maxSize} Kt</b>. Yrit&auml; uudelleen!',
+        msgFilesTooLess: 'V&auml;hint&auml;&auml;n <b>{n}</b> {files} tiedostoa on valittava ladattavaksi. Ole hyv&auml; ja yrit&auml; uudelleen!',
+        msgFilesTooMany: 'Valittujen tiedostojen lukum&auml;&auml;r&auml; <b>({n})</b> ylitt&auml;&auml; suurimman sallitun m&auml;&auml;r&auml;n <b>{m}</b>. Ole hyv&auml; ja yrit&auml; uudelleen!',
+        msgFileNotFound: 'Tiedostoa "{name}" ei l&ouml;ydy!',
+        msgFileSecured: 'Tietoturvarajoitukset est&auml;v&auml;t tiedoston "{name}" lukemisen.',
+        msgFileNotReadable: 'Tiedosto "{name}" ei ole luettavissa.',
+        msgFilePreviewAborted: 'Tiedoston "{name}" esikatselu keskeytetty.',
+        msgFilePreviewError: 'Virhe on tapahtunut luettaessa tiedostoa "{name}".',
+        msgInvalidFileType: 'Tiedosto "{name}" on v&auml;&auml;r&auml;n tyyppinen. Ainoastaan tiedostot tyyppi&auml; "{types}" ovat tuettuja.',
+        msgInvalidFileExtension: 'Tiedoston "{name}" tarkenne on ep&auml;kelpo. Ainoastaan tarkenteet "{extensions}" ovat tuettuja.',
+        msgValidationError: 'Tiedoston latausvirhe',
+        msgLoading: 'Ladataan tiedostoa {index} / {files} &hellip;',
+        msgProgress: 'Ladataan tiedostoa {index} / {files} - {name} - {percent}% valmistunut.',
+        msgSelected: '{n} tiedostoa valittu',
+        msgFoldersNotAllowed: 'Raahaa ja pudota ainoastaan tiedostoja! Ohitettu {n} raahattua kansiota.',
+        dropZoneTitle: 'Raahaa ja pudota tiedostot t&auml;h&auml;n &hellip;',
+        fileActionSettings: {
+            removeTitle: 'Remove file',
+            uploadTitle: 'Upload file',
+            zoomTitle: 'View Details',
+            dragTitle: 'Move / Rearrange',
+            indicatorNewTitle: 'Not uploaded yet',
+            indicatorSuccessTitle: 'Uploaded',
+            indicatorErrorTitle: 'Upload Error',
+            indicatorLoadingTitle: 'Uploading ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
+
+    $.extend($.fn.fileinput.defaults, $.fn.fileinputLocales.fi);
 })(window.jQuery);

+ 68 - 59
js/fileinput_locale_fr.js → js/locales/fileinput_locale_fr.js

@@ -1,60 +1,69 @@
-/*!
- * FileInput French Translations
- *
- * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
- * any HTML markup tags in the messages must not be converted or translated.
- *
- * @see http://github.com/kartik-v/bootstrap-fileinput
- *
- * NOTE: this file must be saved in UTF-8 encoding.
- */
-(function ($) {
-    "use strict";
-
-    $.fn.fileinputLocales['fr'] = {
-        fileSingle: 'fichier',
-        filePlural: 'fichiers',
-        browseLabel: 'Parcourir&hellip;',
-        removeLabel: 'Retirer',
-        removeTitle: 'Retirer les fichiers sélectionnés',
-        cancelLabel: 'Annuler',
-        cancelTitle: "Annuler l'envoi en cours",
-        uploadLabel: 'Transférer',
-        uploadTitle: 'Transférer les fichiers sélectionnés',
-        msgNo: 'Non',
-        msgCancelled: 'Annulé',
-        msgZoomTitle: 'Voir les détails',
-        msgZoomModalHeading: 'Aperçu détaillé',
-        msgSizeTooLarge: 'Le fichier "{name}" (<b>{size} Ko</b>) dépasse la taille maximale autorisée qui est de <b>{maxSize} Ko</b>.',
-        msgFilesTooLess: 'Vous devez sélectionner au moins <b>{n}</b> {files} à transmettre.',
-        msgFilesTooMany: 'Le nombre de fichier sélectionné <b>({n})</b> dépasse la quantité maximale autorisée qui est de <b>{m}</b>.',
-        msgFileNotFound: 'Le fichier "{name}" est introuvable !',
-        msgFileSecured: "Des restrictions de sécurité vous empêchent d'accéder au fichier \"{name}\".",
-        msgFileNotReadable: 'Le fichier "{name}" est illisble.',
-        msgFilePreviewAborted: 'Prévisualisation du fichier "{name}" annulée.',
-        msgFilePreviewError: 'Une erreur est survenue lors de la lecture du fichier "{name}".',
-        msgInvalidFileType: 'Type de document invalide pour "{name}". Seulement les documents de type "{types}" sont autorisés.',
-        msgInvalidFileExtension: 'Extension invalide pour le fichier "{name}". Seules les extensions "{extensions}" sont autorisées.',
-        msgUploadAborted: 'Le téléchargement du fichier a été interrompu',
-        msgValidationError: 'Erreur de validation',
-        msgLoading: 'Transmission du fichier {index} sur {files}&hellip;',
-        msgProgress: 'Transmission du fichier {index} sur {files} - {name} - {percent}% faits.',
-        msgSelected: '{n} {files} sélectionné(s)',
-        msgFoldersNotAllowed: 'Glissez et déposez uniquement des fichiers ! {n} répertoire(s) exclu(s).',
-        msgImageWidthSmall: 'Largeur de fichier image "{name}" doit être d\'au moins {size} px.',
-        msgImageHeightSmall: 'Hauteur de fichier image "{name}" doit être d\'au moins {size} px.',
-        msgImageWidthLarge: 'Largeur de fichier image "{name}" ne peut pas dépasser {size} px.',
-        msgImageHeightLarge: 'Hauteur de fichier image "{name}" ne peut pas dépasser {size} px.',
-        msgImageResizeError: "Impossible d'obtenir les dimensions de l'image à redimensionner.",
-        msgImageResizeException: "Erreur lors du redimensionnement de l'image.<pre>{errors}</pre>",
-        dropZoneTitle: 'Glissez et déposez les fichiers ici&hellip;',
-        fileActionSettings: {
-            removeTitle: 'Supprimer le fichier',
-            uploadTitle: 'Télécharger un fichier',
-            indicatorNewTitle: 'Pas encore téléchargé',
-            indicatorSuccessTitle: 'Posté',
-            indicatorErrorTitle: 'Ajouter erreur',
-            indicatorLoadingTitle: 'ajout ...'
-        }
-    };
+/*!
+ * FileInput French Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputLocales['fr'] = {
+        fileSingle: 'fichier',
+        filePlural: 'fichiers',
+        browseLabel: 'Parcourir&hellip;',
+        removeLabel: 'Retirer',
+        removeTitle: 'Retirer les fichiers sélectionnés',
+        cancelLabel: 'Annuler',
+        cancelTitle: "Annuler l'envoi en cours",
+        uploadLabel: 'Transférer',
+        uploadTitle: 'Transférer les fichiers sélectionnés',
+        msgNo: 'Non',
+        msgCancelled: 'Annulé',
+        msgZoomModalHeading: 'Aperçu détaillé',
+        msgSizeTooLarge: 'Le fichier "{name}" (<b>{size} Ko</b>) dépasse la taille maximale autorisée qui est de <b>{maxSize} Ko</b>.',
+        msgFilesTooLess: 'Vous devez sélectionner au moins <b>{n}</b> {files} à transmettre.',
+        msgFilesTooMany: 'Le nombre de fichier sélectionné <b>({n})</b> dépasse la quantité maximale autorisée qui est de <b>{m}</b>.',
+        msgFileNotFound: 'Le fichier "{name}" est introuvable !',
+        msgFileSecured: "Des restrictions de sécurité vous empêchent d'accéder au fichier \"{name}\".",
+        msgFileNotReadable: 'Le fichier "{name}" est illisble.',
+        msgFilePreviewAborted: 'Prévisualisation du fichier "{name}" annulée.',
+        msgFilePreviewError: 'Une erreur est survenue lors de la lecture du fichier "{name}".',
+        msgInvalidFileType: 'Type de document invalide pour "{name}". Seulement les documents de type "{types}" sont autorisés.',
+        msgInvalidFileExtension: 'Extension invalide pour le fichier "{name}". Seules les extensions "{extensions}" sont autorisées.',
+        msgUploadAborted: 'Le téléchargement du fichier a été interrompu',
+        msgValidationError: 'Erreur de validation',
+        msgLoading: 'Transmission du fichier {index} sur {files}&hellip;',
+        msgProgress: 'Transmission du fichier {index} sur {files} - {name} - {percent}% faits.',
+        msgSelected: '{n} {files} sélectionné(s)',
+        msgFoldersNotAllowed: 'Glissez et déposez uniquement des fichiers ! {n} répertoire(s) exclu(s).',
+        msgImageWidthSmall: 'Largeur de fichier image "{name}" doit être d\'au moins {size} px.',
+        msgImageHeightSmall: 'Hauteur de fichier image "{name}" doit être d\'au moins {size} px.',
+        msgImageWidthLarge: 'Largeur de fichier image "{name}" ne peut pas dépasser {size} px.',
+        msgImageHeightLarge: 'Hauteur de fichier image "{name}" ne peut pas dépasser {size} px.',
+        msgImageResizeError: "Impossible d'obtenir les dimensions de l'image à redimensionner.",
+        msgImageResizeException: "Erreur lors du redimensionnement de l'image.<pre>{errors}</pre>",
+        dropZoneTitle: 'Glissez et déposez les fichiers ici&hellip;',
+        fileActionSettings: {
+            removeTitle: 'Supprimer le fichier',
+            uploadTitle: 'Télécharger un fichier',
+            zoomTitle: 'Voir les détails',
+            dragTitle: 'Move / Rearrange',
+            indicatorNewTitle: 'Pas encore téléchargé',
+            indicatorSuccessTitle: 'Posté',
+            indicatorErrorTitle: 'Ajouter erreur',
+            indicatorLoadingTitle: 'ajout ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
 })(window.jQuery);

+ 69 - 60
js/fileinput_locale_hu.js → js/locales/fileinput_locale_hu.js

@@ -1,60 +1,69 @@
-/*!
- * FileInput Hungarian Translations
- *
- * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
- * any HTML markup tags in the messages must not be converted or translated.
- *
- * @see http://github.com/kartik-v/bootstrap-fileinput
- *
- * NOTE: this file must be saved in UTF-8 encoding.
- */
-(function ($) {
-    "use strict";
-
-    $.fn.fileinputLocales['hu'] = {
-        fileSingle: 'fájl',
-        filePlural: 'fájl',
-        browseLabel: 'Böngész &hellip;',
-        removeLabel: 'Eltávolít',
-        removeTitle: 'Kijelölt fájlok törlése',
-        cancelLabel: 'Mégse',
-        cancelTitle: 'Feltöltés megszakítása',
-        uploadLabel: 'Feltöltés',
-        uploadTitle: 'Kijelölt fájlok feltöltése',
-        msgNo: 'No',
-        msgCancelled: 'Cancelled',
-        msgZoomTitle: 'Részletek megtekintése',
-        msgZoomModalHeading: 'Részletes Preview',
-        msgSizeTooLarge: '"{name}" fájl (<b>{size} KB</b>) mérete nagyobb a megengedettnél <b>{maxSize} KB</b>.',
-        msgFilesTooLess: 'Legalább <b>{n}</b> {files} ki kell választania a feltöltéshez.',
-        msgFilesTooMany: 'A feltölteni kívánt fájlok száma <b>({n})</b> elérte a megengedett maximumot <b>{m}</b>.',
-        msgFileNotFound: '"{name}" fájl nem található!',
-        msgFileSecured: 'Biztonsági beállítások nem engedik olvasni a fájlt "{name}".',
-        msgFileNotReadable: '"{name}" fájl nem olvasható',
-        msgFilePreviewAborted: '"{name}" fájl feltöltése megszakítva.',
-        msgFilePreviewError: 'Hiba lépett fel a "{name}" fájl olvasása közben.',
-        msgInvalidFileType: 'Nem megengedett fájl "{name}". Csak a "{types}" fájl típusok támogatottak.',
-        msgInvalidFileExtension: 'Nem megengedett kiterjesztés / fájltípus "{name}". Csak a "{extensions}" kiterjesztés(ek) / fájltípus(ok) támogatottak.',
-        msgUploadAborted: 'A fájl feltöltés megszakítva',
-        msgValidationError: 'Érvényesítés hiba',
-        msgLoading: '{index} / {files} töltése &hellip;',
-        msgProgress: 'Feltöltés: {index} / {files} - {name} - {percent}% kész.',
-        msgSelected: '{n} {files} kiválasztva.',
-        msgFoldersNotAllowed: 'Csak fájlokat húzzon ide! Kihagyva {n} könyvtár.',
-        msgImageWidthSmall: 'Szélessége image file "{name}" legalább {size} px.',
-        msgImageHeightSmall: 'Magassága image file "{name}" legalább {size} px.',
-        msgImageWidthLarge: 'Szélessége image file "{name}" nem haladhatja meg a {size} px.',
-        msgImageHeightLarge: 'Magassága image file "{name}" nem haladhatja meg a {size} px.',
-        msgImageResizeError: 'Nem lehet megszerezni a kép méretei átméretezni.',
-        msgImageResizeException: 'Hiba történt a méretezés.<pre>{errors}</pre>',
-        dropZoneTitle: 'Fájlok húzása ide &hellip;',
-        fileActionSettings: {
-            removeTitle: 'A fájl eltávolítása',
-            uploadTitle: 'fájl feltöltése',
-            indicatorNewTitle: 'Nem feltöltve',
-            indicatorSuccessTitle: 'Feltöltött',
-            indicatorErrorTitle: 'Feltöltés Error',
-            indicatorLoadingTitle: 'Feltöltése ...'
-        }
-    };
-})(window.jQuery);
+/*!
+ * FileInput Hungarian Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputLocales['hu'] = {
+        fileSingle: 'fájl',
+        filePlural: 'fájl',
+        browseLabel: 'Böngész &hellip;',
+        removeLabel: 'Eltávolít',
+        removeTitle: 'Kijelölt fájlok törlése',
+        cancelLabel: 'Mégse',
+        cancelTitle: 'Feltöltés megszakítása',
+        uploadLabel: 'Feltöltés',
+        uploadTitle: 'Kijelölt fájlok feltöltése',
+        msgNo: 'No',
+        msgCancelled: 'Cancelled',
+        msgZoomModalHeading: 'Részletes Preview',
+        msgSizeTooLarge: '"{name}" fájl (<b>{size} KB</b>) mérete nagyobb a megengedettnél <b>{maxSize} KB</b>.',
+        msgFilesTooLess: 'Legalább <b>{n}</b> {files} ki kell választania a feltöltéshez.',
+        msgFilesTooMany: 'A feltölteni kívánt fájlok száma <b>({n})</b> elérte a megengedett maximumot <b>{m}</b>.',
+        msgFileNotFound: '"{name}" fájl nem található!',
+        msgFileSecured: 'Biztonsági beállítások nem engedik olvasni a fájlt "{name}".',
+        msgFileNotReadable: '"{name}" fájl nem olvasható',
+        msgFilePreviewAborted: '"{name}" fájl feltöltése megszakítva.',
+        msgFilePreviewError: 'Hiba lépett fel a "{name}" fájl olvasása közben.',
+        msgInvalidFileType: 'Nem megengedett fájl "{name}". Csak a "{types}" fájl típusok támogatottak.',
+        msgInvalidFileExtension: 'Nem megengedett kiterjesztés / fájltípus "{name}". Csak a "{extensions}" kiterjesztés(ek) / fájltípus(ok) támogatottak.',
+        msgUploadAborted: 'A fájl feltöltés megszakítva',
+        msgValidationError: 'Érvényesítés hiba',
+        msgLoading: '{index} / {files} töltése &hellip;',
+        msgProgress: 'Feltöltés: {index} / {files} - {name} - {percent}% kész.',
+        msgSelected: '{n} {files} kiválasztva.',
+        msgFoldersNotAllowed: 'Csak fájlokat húzzon ide! Kihagyva {n} könyvtár.',
+        msgImageWidthSmall: 'Szélessége image file "{name}" legalább {size} px.',
+        msgImageHeightSmall: 'Magassága image file "{name}" legalább {size} px.',
+        msgImageWidthLarge: 'Szélessége image file "{name}" nem haladhatja meg a {size} px.',
+        msgImageHeightLarge: 'Magassága image file "{name}" nem haladhatja meg a {size} px.',
+        msgImageResizeError: 'Nem lehet megszerezni a kép méretei átméretezni.',
+        msgImageResizeException: 'Hiba történt a méretezés.<pre>{errors}</pre>',
+        dropZoneTitle: 'Fájlok húzása ide &hellip;',
+        fileActionSettings: {
+            removeTitle: 'A fájl eltávolítása',
+            uploadTitle: 'fájl feltöltése',
+            zoomTitle: 'Részletek megtekintése',
+            dragTitle: 'Move / Rearrange',
+            indicatorNewTitle: 'Nem feltöltve',
+            indicatorSuccessTitle: 'Feltöltött',
+            indicatorErrorTitle: 'Feltöltés Error',
+            indicatorLoadingTitle: 'Feltöltése ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
+})(window.jQuery);

+ 10 - 1
js/fileinput_locale_id.js → js/locales/fileinput_locale_id.js

@@ -24,7 +24,6 @@
         uploadTitle: 'Unggah berkas terpilih',
         msgNo: 'Tidak',
         msgCancelled: 'Dibatalkan',
-        msgZoomTitle: 'Tampilkan Rincian',
         msgZoomModalHeading: 'Pratinjau terperinci',
         msgSizeTooLarge: 'Berkas "{name}" (<b>{size} KB</b>) melebihi ukuran upload maksimal yaitu <b>{maxSize} KB</b>.',
         msgFilesTooLess: 'Anda harus memilih setidaknya <b>{n}</b> {files} untuk diunggah.',
@@ -52,10 +51,20 @@
         fileActionSettings: {
             removeTitle: 'Hapus berkas',
             uploadTitle: 'Unggah berkas',
+            zoomTitle: 'Tampilkan Rincian',
+            dragTitle: 'Move / Rearrange',
             indicatorNewTitle: 'Belum diunggah',
             indicatorSuccessTitle: 'Sudah diunggah',
             indicatorErrorTitle: 'Kesalahan pengunggahan',
             indicatorLoadingTitle: 'Mengunggah ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
         }
     };
 })(window.jQuery);

+ 70 - 61
js/fileinput_locale_it.js → js/locales/fileinput_locale_it.js

@@ -1,62 +1,71 @@
-/*!
- * FileInput Italian Translation
- * 
- * Author: Lorenzo Milesi <[email protected]>
- *
- * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
- * any HTML markup tags in the messages must not be converted or translated.
- *
- * @see http://github.com/kartik-v/bootstrap-fileinput
- *
- * NOTE: this file must be saved in UTF-8 encoding.
- */
-(function ($) {
-    "use strict";
-
-    $.fn.fileinputLocales['it'] = {
-        fileSingle: 'file',
-        filePlural: 'file',
-        browseLabel: 'Sfoglia&hellip;',
-        removeLabel: 'Rimuovi',
-        removeTitle: 'Rimuovi i file selezionati',
-        cancelLabel: 'Annulla',
-        cancelTitle: 'Annulla i caricamenti in corso',
-        uploadLabel: 'Carica',
-        uploadTitle: 'Carica i file selezionati',
-        msgNo: 'No',
-        msgCancelled: 'Annullato',
-        msgZoomTitle: 'Guarda i dettagli',
-        msgZoomModalHeading: 'Anteprima dettagliata',
-        msgSizeTooLarge: 'Il file "{name}" (<b>{size} KB</b>) eccede la dimensione massima di caricamento di <b>{maxSize} KB</b>.',
-        msgFilesTooLess: 'Devi selezionare almeno <b>{n}</b> {files} da caricare.',
-        msgFilesTooMany: 'Il numero di file selezionati per il caricamento <b>({n})</b> eccede il numero massimo di file accettati <b>{m}</b>.',
-        msgFileNotFound: 'File "{name}" non trovato!',
-        msgFileSecured: 'Restrizioni di sicurezza impediscono la lettura del file "{name}".',
-        msgFileNotReadable: 'Il file "{name}" non \xE8 leggibile.',
-        msgFilePreviewAborted: 'Generazione anteprima per "{name}" annullata.',
-        msgFilePreviewError: 'Errore durante la lettura del file "{name}".',
-        msgInvalidFileType: 'Tipo non valido per il file "{name}". Sono ammessi solo file di tipo "{types}".',
-        msgInvalidFileExtension: 'Estensione non valida per il file "{name}". Sono ammessi solo file con estensione "{extensions}".',
-        msgUploadAborted: 'Il caricamento del file è stata interrotta',
-        msgValidationError: 'Errore di convalida',
-        msgLoading: 'Caricamento file {index} di {files}&hellip;',
-        msgProgress: 'Caricamento file {index} di {files} - {name} - {percent}% completato.',
-        msgSelected: '{n} {files} selezionati',
-        msgFoldersNotAllowed: 'Trascina solo file! Ignorata/e {n} cartella/e.',
-        msgImageWidthSmall: 'Larghezza di file immagine "{name}" deve essere di almeno {size} px.',
-        msgImageHeightSmall: 'Altezza di file immagine "{name}" deve essere di almeno {size} px.',
-        msgImageWidthLarge: 'Larghezza di file immagine "{name}" non può superare {size} px.',
-        msgImageHeightLarge: 'Altezza di file immagine "{name}" non può superare {size} px.',
-        msgImageResizeError: "Impossibile ottenere le dimensioni dell'immagine per ridimensionare.",
-        msgImageResizeException: "Errore durante il ridimensionamento dell'immagine.<pre>{errors}</pre>",
-        dropZoneTitle: 'Trascina i file qui&hellip;',
-        fileActionSettings: {
-            removeTitle: 'Rimuovere il file',
-            uploadTitle: 'Caricare un file',
-            indicatorNewTitle: 'Non ancora caricato',
-            indicatorSuccessTitle: 'Caricati',
-            indicatorErrorTitle: 'Carica Errore',
-            indicatorLoadingTitle: 'Caricamento ...'
-        }
-    };
+/*!
+ * FileInput Italian Translation
+ * 
+ * Author: Lorenzo Milesi <[email protected]>
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputLocales['it'] = {
+        fileSingle: 'file',
+        filePlural: 'file',
+        browseLabel: 'Sfoglia&hellip;',
+        removeLabel: 'Rimuovi',
+        removeTitle: 'Rimuovi i file selezionati',
+        cancelLabel: 'Annulla',
+        cancelTitle: 'Annulla i caricamenti in corso',
+        uploadLabel: 'Carica',
+        uploadTitle: 'Carica i file selezionati',
+        msgNo: 'No',
+        msgCancelled: 'Annullato',
+        msgZoomModalHeading: 'Anteprima dettagliata',
+        msgSizeTooLarge: 'Il file "{name}" (<b>{size} KB</b>) eccede la dimensione massima di caricamento di <b>{maxSize} KB</b>.',
+        msgFilesTooLess: 'Devi selezionare almeno <b>{n}</b> {files} da caricare.',
+        msgFilesTooMany: 'Il numero di file selezionati per il caricamento <b>({n})</b> eccede il numero massimo di file accettati <b>{m}</b>.',
+        msgFileNotFound: 'File "{name}" non trovato!',
+        msgFileSecured: 'Restrizioni di sicurezza impediscono la lettura del file "{name}".',
+        msgFileNotReadable: 'Il file "{name}" non \xE8 leggibile.',
+        msgFilePreviewAborted: 'Generazione anteprima per "{name}" annullata.',
+        msgFilePreviewError: 'Errore durante la lettura del file "{name}".',
+        msgInvalidFileType: 'Tipo non valido per il file "{name}". Sono ammessi solo file di tipo "{types}".',
+        msgInvalidFileExtension: 'Estensione non valida per il file "{name}". Sono ammessi solo file con estensione "{extensions}".',
+        msgUploadAborted: 'Il caricamento del file è stata interrotta',
+        msgValidationError: 'Errore di convalida',
+        msgLoading: 'Caricamento file {index} di {files}&hellip;',
+        msgProgress: 'Caricamento file {index} di {files} - {name} - {percent}% completato.',
+        msgSelected: '{n} {files} selezionati',
+        msgFoldersNotAllowed: 'Trascina solo file! Ignorata/e {n} cartella/e.',
+        msgImageWidthSmall: 'Larghezza di file immagine "{name}" deve essere di almeno {size} px.',
+        msgImageHeightSmall: 'Altezza di file immagine "{name}" deve essere di almeno {size} px.',
+        msgImageWidthLarge: 'Larghezza di file immagine "{name}" non può superare {size} px.',
+        msgImageHeightLarge: 'Altezza di file immagine "{name}" non può superare {size} px.',
+        msgImageResizeError: "Impossibile ottenere le dimensioni dell'immagine per ridimensionare.",
+        msgImageResizeException: "Errore durante il ridimensionamento dell'immagine.<pre>{errors}</pre>",
+        dropZoneTitle: 'Trascina i file qui&hellip;',
+        fileActionSettings: {
+            removeTitle: 'Rimuovere il file',
+            uploadTitle: 'Caricare un file',
+            zoomTitle: 'Guarda i dettagli',
+            dragTitle: 'Move / Rearrange',
+            indicatorNewTitle: 'Non ancora caricato',
+            indicatorSuccessTitle: 'Caricati',
+            indicatorErrorTitle: 'Carica Errore',
+            indicatorLoadingTitle: 'Caricamento ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
 })(window.jQuery);

+ 10 - 1
js/fileinput_locale_ja.js → js/locales/fileinput_locale_ja.js

@@ -30,7 +30,6 @@
         uploadTitle: '選択したファイルをアップロード',
         msgNo: 'いいえ',
         msgCancelled: 'キャンセル',
-        msgZoomTitle: 'プレビュー',
         msgZoomModalHeading: 'ファイル詳細',
         msgSizeTooLarge: 'ファイル"{name}" (<b>{size} KB</b>)はアップロード可能な上限容量<b>{maxSize} KB</b>を超えています',
         msgFilesTooLess: '最低<b>{n}</b>個の{files}を選択してください',
@@ -61,10 +60,20 @@
         fileActionSettings: {
             removeTitle: 'ファイルを削除',
             uploadTitle: 'ファイルをアップロード',
+            zoomTitle: 'プレビュー',
+            dragTitle: 'Move / Rearrange',
             indicatorNewTitle: 'まだアップロードされていません',
             indicatorSuccessTitle: 'アップロード済み',
             indicatorErrorTitle: 'アップロード失敗',
             indicatorLoadingTitle: 'アップロード中...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
         }
     };
 })(window.jQuery);

+ 69 - 60
js/fileinput_locale_nl.js → js/locales/fileinput_locale_nl.js

@@ -1,60 +1,69 @@
-/*!
- * FileInput Dutch Translations
- *
- * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
- * any HTML markup tags in the messages must not be converted or translated.
- *
- * @see http://github.com/kartik-v/bootstrap-fileinput
- *
- * NOTE: this file must be saved in UTF-8 encoding.
- */
-(function ($) {
-    "use strict";
-
-    $.fn.fileinputLocales['nl'] = {
-        fileSingle: 'bestand',
-        filePlural: 'bestanden',
-        browseLabel: 'Zoek &hellip;',
-        removeLabel: 'Verwijder',
-        removeTitle: 'Verwijder geselecteerde bestanden',
-        cancelLabel: 'Annuleren',
-        cancelTitle: 'Annuleer upload',
-        uploadLabel: 'Upload',
-        uploadTitle: 'Upload geselecteerde bestanden',
-        msgNo: 'Nee',
-        msgCancelled: 'Geannuleerd',
-        msgZoomTitle: 'Bekijk details',
-        msgZoomModalHeading: 'Gedetailleerd voorbeeld',
-        msgSizeTooLarge: 'Bestand "{name}" (<b>{size} KB</b>) is groter dan de toegestane <b>{maxSize} KB</b>.',
-        msgFilesTooLess: 'U moet minstens <b>{n}</b> {files} selecteren om te uploaden.',
-        msgFilesTooMany: 'Aantal geselecteerde bestanden <b>({n})</b> is meer dan de toegestane <b>{m}</b>.',
-        msgFileNotFound: 'Bestand "{name}" niet gevonden!',
-        msgFileSecured: 'Bestand kan niet gelezen worden in verband met beveiligings redenen "{name}".',
-        msgFileNotReadable: 'Bestand "{name}" is niet leesbaar.',
-        msgFilePreviewAborted: 'Bestand weergaven geannuleerd voor "{name}".',
-        msgFilePreviewError: 'Er is een fout opgetreden met het lezen van "{name}".',
-        msgInvalidFileType: 'Geen geldig bestand "{name}". Alleen "{types}" zijn toegestaan.',
-        msgInvalidFileExtension: 'Geen geldige extensie "{name}". Alleen "{extensions}" zijn toegestaan.',
-        msgUploadAborted: 'Het uploaden van bestanden is afgebroken',
-        msgValidationError: 'Bevestiging fout',
-        msgLoading: 'Bestanden laden {index} van de {files} &hellip;',
-        msgProgress: 'Bestanden laden {index} van de {files} - {name} - {percent}% compleet.',
-        msgSelected: '{n} {files} geselecteerd',
-        msgFoldersNotAllowed: 'Drag & drop alleen bestanden! {n} overgeslagen map(pen).',
-        msgImageWidthSmall: 'Breedte van het foto-bestand "{name}" moet minstens {size} px zijn.',
-        msgImageHeightSmall: 'Hoogte van het foto-bestand "{name}" moet minstens {size} px zijn.',
-        msgImageWidthLarge: 'Breedte van het foto-bestand "{name}" kan niet hoger zijn dan {size} px.',
-        msgImageHeightLarge: 'Hoogte van het foto bestand "{name}" kan niet hoger zijn dan {size} px.',
-        msgImageResizeError: 'Kon de foto afmetingen niet lezen om te verkleinen.',
-        msgImageResizeException: 'Fout bij het verkleinen van de foto.<pre>{errors}</pre>',
-        dropZoneTitle: 'Drag & drop bestanden hier &hellip;',
-        fileActionSettings: {
-            removeTitle: 'Verwijder bestand',
-            uploadTitle: 'bestand uploaden',
-            indicatorNewTitle: 'Nog niet geupload',
-            indicatorSuccessTitle: 'geupload',
-            indicatorErrorTitle: 'fout uploaden',
-            indicatorLoadingTitle: 'uploaden ...'
-        }
-    };
-})(window.jQuery);
+/*!
+ * FileInput Dutch Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputLocales['nl'] = {
+        fileSingle: 'bestand',
+        filePlural: 'bestanden',
+        browseLabel: 'Zoek &hellip;',
+        removeLabel: 'Verwijder',
+        removeTitle: 'Verwijder geselecteerde bestanden',
+        cancelLabel: 'Annuleren',
+        cancelTitle: 'Annuleer upload',
+        uploadLabel: 'Upload',
+        uploadTitle: 'Upload geselecteerde bestanden',
+        msgNo: 'Nee',
+        msgCancelled: 'Geannuleerd',
+        msgZoomModalHeading: 'Gedetailleerd voorbeeld',
+        msgSizeTooLarge: 'Bestand "{name}" (<b>{size} KB</b>) is groter dan de toegestane <b>{maxSize} KB</b>.',
+        msgFilesTooLess: 'U moet minstens <b>{n}</b> {files} selecteren om te uploaden.',
+        msgFilesTooMany: 'Aantal geselecteerde bestanden <b>({n})</b> is meer dan de toegestane <b>{m}</b>.',
+        msgFileNotFound: 'Bestand "{name}" niet gevonden!',
+        msgFileSecured: 'Bestand kan niet gelezen worden in verband met beveiligings redenen "{name}".',
+        msgFileNotReadable: 'Bestand "{name}" is niet leesbaar.',
+        msgFilePreviewAborted: 'Bestand weergaven geannuleerd voor "{name}".',
+        msgFilePreviewError: 'Er is een fout opgetreden met het lezen van "{name}".',
+        msgInvalidFileType: 'Geen geldig bestand "{name}". Alleen "{types}" zijn toegestaan.',
+        msgInvalidFileExtension: 'Geen geldige extensie "{name}". Alleen "{extensions}" zijn toegestaan.',
+        msgUploadAborted: 'Het uploaden van bestanden is afgebroken',
+        msgValidationError: 'Bevestiging fout',
+        msgLoading: 'Bestanden laden {index} van de {files} &hellip;',
+        msgProgress: 'Bestanden laden {index} van de {files} - {name} - {percent}% compleet.',
+        msgSelected: '{n} {files} geselecteerd',
+        msgFoldersNotAllowed: 'Drag & drop alleen bestanden! {n} overgeslagen map(pen).',
+        msgImageWidthSmall: 'Breedte van het foto-bestand "{name}" moet minstens {size} px zijn.',
+        msgImageHeightSmall: 'Hoogte van het foto-bestand "{name}" moet minstens {size} px zijn.',
+        msgImageWidthLarge: 'Breedte van het foto-bestand "{name}" kan niet hoger zijn dan {size} px.',
+        msgImageHeightLarge: 'Hoogte van het foto bestand "{name}" kan niet hoger zijn dan {size} px.',
+        msgImageResizeError: 'Kon de foto afmetingen niet lezen om te verkleinen.',
+        msgImageResizeException: 'Fout bij het verkleinen van de foto.<pre>{errors}</pre>',
+        dropZoneTitle: 'Drag & drop bestanden hier &hellip;',
+        fileActionSettings: {
+            removeTitle: 'Verwijder bestand',
+            uploadTitle: 'bestand uploaden',
+            zoomTitle: 'Bekijk details',
+            dragTitle: 'Move / Rearrange',
+            indicatorNewTitle: 'Nog niet geupload',
+            indicatorSuccessTitle: 'geupload',
+            indicatorErrorTitle: 'fout uploaden',
+            indicatorLoadingTitle: 'uploaden ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
+})(window.jQuery);

+ 68 - 59
js/fileinput_locale_pl.js → js/locales/fileinput_locale_pl.js

@@ -1,60 +1,69 @@
-/*!
- * FileInput Polish Translations
- *
- * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
- * any HTML markup tags in the messages must not be converted or translated.
- *
- * @see http://github.com/kartik-v/bootstrap-fileinput
- *
- * NOTE: this file must be saved in UTF-8 encoding.
- */
-(function ($) {
-    "use strict";
-
-    $.fn.fileinputLocales['pl'] = {
-        fileSingle: 'plik',
-        filePlural: 'pliki',
-        browseLabel: 'Przeglądaj &hellip;',
-        removeLabel: 'Usuń',
-        removeTitle: 'Usuń zaznaczone pliki',
-        cancelLabel: 'Przerwij',
-        cancelTitle: 'Anuluj wysyłanie',
-        uploadLabel: 'Wgraj',
-        uploadTitle: 'Wgraj zaznaczone pliki',
-        msgNo: 'Nie',
-        msgCancelled: 'Odwołany',
-        msgZoomTitle: 'Pokaż szczegóły',
-        msgZoomModalHeading: 'Szczegółowe Podgląd',
-        msgSizeTooLarge: 'Plik o nazwie "{name}" (<b>{size} KB</b>) przekroczył maksymalną dopuszczalną wielkość pliku wynoszącą <b>{maxSize} KB</b>.',
-        msgFilesTooLess: 'Musisz wybrać przynajmniej <b>{n}</b> {files} do wgrania.',
-        msgFilesTooMany: 'Liczba plików wybranych do wgrania w liczbie <b>({n})</b>, przekracza maksymalny dozwolony limit wynoszący <b>{m}</b>.',
-        msgFileNotFound: 'Plik "{name}" nie istnieje!',
-        msgFileSecured: 'Ustawienia zabezpieczeń uniemożliwiają odczyt pliku "{name}".',
-        msgFileNotReadable: 'Plik "{name}" nie jest plikiem do odczytu.',
-        msgFilePreviewAborted: 'Podgląd pliku "{name}" został przerwany.',
-        msgFilePreviewError: 'Wystąpił błąd w czasie odczytu pliku "{name}".',
-        msgInvalidFileType: 'Nieznny typ pliku "{name}". Tylko następujące rodzaje plików "{types}", są obsługiwane.',
-        msgInvalidFileExtension: 'Złe rozszerzenie dla pliku "{name}". Tylko następujące rozszerzenia plików "{extensions}", są obsługiwane.',
-        msgUploadAborted: 'Plik przesyłanie zostało przerwane',
-        msgValidationError: 'Błąd walidacji',
-        msgLoading: 'Wczytywanie pliku {index} z {files} &hellip;',
-        msgProgress: 'Wczytywanie pliku {index} z {files} - {name} - {percent}% zakończone.',
-        msgSelected: '{n} {files} zaznaczonych',
-        msgFoldersNotAllowed: 'Metodą przeciągnij i upuść, można przenosić tylko pliki. Pominięto {n} katalogów.',
-        msgImageWidthSmall: 'Szerokość pliku obrazu "{name}" musi być co najmniej {size} px.',
-        msgImageHeightSmall: 'Wysokość pliku obrazu "{name}" musi być co najmniej {size} px.',
-        msgImageWidthLarge: 'Szerokość pliku obrazu "{name}" nie może przekraczać {size} px.',
-        msgImageHeightLarge: 'Wysokość pliku obrazu "{name}" nie może przekraczać {size} px.',
-        msgImageResizeError: 'Nie udało się uzyskać wymiary obrazu, aby zmienić rozmiar.',
-        msgImageResizeException: 'Błąd podczas zmiany rozmiaru obrazu.<pre>{errors}</pre>',
-        dropZoneTitle: 'Przeciągnij i upuść pliki tu &hellip;',
-        fileActionSettings: {
-            removeTitle: 'Usuń plik',
-            uploadTitle: 'przesyłanie pliku',
-            indicatorNewTitle: 'Jeszcze nie przesłanych',
-            indicatorSuccessTitle: 'Dodane',
-            indicatorErrorTitle: 'Prześlij błąd',
-            indicatorLoadingTitle: 'Zamieszczanie ...'
-        }
-    };
+/*!
+ * FileInput Polish Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputLocales['pl'] = {
+        fileSingle: 'plik',
+        filePlural: 'pliki',
+        browseLabel: 'Przeglądaj &hellip;',
+        removeLabel: 'Usuń',
+        removeTitle: 'Usuń zaznaczone pliki',
+        cancelLabel: 'Przerwij',
+        cancelTitle: 'Anuluj wysyłanie',
+        uploadLabel: 'Wgraj',
+        uploadTitle: 'Wgraj zaznaczone pliki',
+        msgNo: 'Nie',
+        msgCancelled: 'Odwołany',
+        msgZoomModalHeading: 'Szczegółowe Podgląd',
+        msgSizeTooLarge: 'Plik o nazwie "{name}" (<b>{size} KB</b>) przekroczył maksymalną dopuszczalną wielkość pliku wynoszącą <b>{maxSize} KB</b>.',
+        msgFilesTooLess: 'Musisz wybrać przynajmniej <b>{n}</b> {files} do wgrania.',
+        msgFilesTooMany: 'Liczba plików wybranych do wgrania w liczbie <b>({n})</b>, przekracza maksymalny dozwolony limit wynoszący <b>{m}</b>.',
+        msgFileNotFound: 'Plik "{name}" nie istnieje!',
+        msgFileSecured: 'Ustawienia zabezpieczeń uniemożliwiają odczyt pliku "{name}".',
+        msgFileNotReadable: 'Plik "{name}" nie jest plikiem do odczytu.',
+        msgFilePreviewAborted: 'Podgląd pliku "{name}" został przerwany.',
+        msgFilePreviewError: 'Wystąpił błąd w czasie odczytu pliku "{name}".',
+        msgInvalidFileType: 'Nieznny typ pliku "{name}". Tylko następujące rodzaje plików "{types}", są obsługiwane.',
+        msgInvalidFileExtension: 'Złe rozszerzenie dla pliku "{name}". Tylko następujące rozszerzenia plików "{extensions}", są obsługiwane.',
+        msgUploadAborted: 'Plik przesyłanie zostało przerwane',
+        msgValidationError: 'Błąd walidacji',
+        msgLoading: 'Wczytywanie pliku {index} z {files} &hellip;',
+        msgProgress: 'Wczytywanie pliku {index} z {files} - {name} - {percent}% zakończone.',
+        msgSelected: '{n} {files} zaznaczonych',
+        msgFoldersNotAllowed: 'Metodą przeciągnij i upuść, można przenosić tylko pliki. Pominięto {n} katalogów.',
+        msgImageWidthSmall: 'Szerokość pliku obrazu "{name}" musi być co najmniej {size} px.',
+        msgImageHeightSmall: 'Wysokość pliku obrazu "{name}" musi być co najmniej {size} px.',
+        msgImageWidthLarge: 'Szerokość pliku obrazu "{name}" nie może przekraczać {size} px.',
+        msgImageHeightLarge: 'Wysokość pliku obrazu "{name}" nie może przekraczać {size} px.',
+        msgImageResizeError: 'Nie udało się uzyskać wymiary obrazu, aby zmienić rozmiar.',
+        msgImageResizeException: 'Błąd podczas zmiany rozmiaru obrazu.<pre>{errors}</pre>',
+        dropZoneTitle: 'Przeciągnij i upuść pliki tu &hellip;',
+        fileActionSettings: {
+            removeTitle: 'Usuń plik',
+            uploadTitle: 'przesyłanie pliku',
+            zoomTitle: 'Pokaż szczegóły',
+            dragTitle: 'Move / Rearrange',
+            indicatorNewTitle: 'Jeszcze nie przesłanych',
+            indicatorSuccessTitle: 'Dodane',
+            indicatorErrorTitle: 'Prześlij błąd',
+            indicatorLoadingTitle: 'Zamieszczanie ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
 })(window.jQuery);

+ 68 - 59
js/fileinput_locale_pt-BR.js → js/locales/fileinput_locale_pt-BR.js

@@ -1,60 +1,69 @@
-/*!
- * FileInput Brazillian Portuguese Translations
- *
- * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
- * any HTML markup tags in the messages must not be converted or translated.
- *
- * @see http://github.com/kartik-v/bootstrap-fileinput
- *
- * NOTE: this file must be saved in UTF-8 encoding.
- */
-(function ($) {
-    "use strict";
-
-    $.fn.fileinputLocales['pt-BR'] = {
-        fileSingle: 'arquivo',
-        filePlural: 'arquivos',
-        browseLabel: 'Procurar&hellip;',
-        removeLabel: 'Remover',
-        removeTitle: 'Remover arquivos selecionados',
-        cancelLabel: 'Cancelar',
-        cancelTitle: 'Interromper envio em andamento',
-        uploadLabel: 'Enviar',
-        uploadTitle: 'Enviar arquivos selecionados',
-        msgNo: 'Não',
-        msgCancelled: 'Cancelado',
-        msgZoomTitle: 'Ver detalhes',
-        msgZoomModalHeading: 'Pré-visualização detalhada',
-        msgSizeTooLarge: 'O arquivo "{name}" (<b>{size} KB</b>) excede o tamanho máximo permitido de <b>{maxSize} KB</b>.',
-        msgFilesTooLess: 'Você deve selecionar pelo menos <b>{n}</b> {files} para enviar.',
-        msgFilesTooMany: 'O número de arquivos selecionados para o envio <b>({n})</b> excede o limite máximo permitido de <b>{m}</b>.',
-        msgFileNotFound: 'O arquivo "{name}" não foi encontrado!',
-        msgFileSecured: 'Restrições de segurança impedem a leitura do arquivo "{name}".',
-        msgFileNotReadable: 'O arquivo "{name}" não pode ser lido.',
-        msgFilePreviewAborted: 'A pré-visualização do arquivo "{name}" foi interrompida.',
-        msgFilePreviewError: 'Ocorreu um erro ao ler o arquivo "{name}".',
-        msgInvalidFileType: 'Tipo inválido para o arquivo "{name}". Apenas arquivos "{types}" são permitidos.',
-        msgInvalidFileExtension: 'Extensão inválida para o arquivo "{name}". Apenas arquivos "{extensions}" são permitidos.',
-        msgUploadAborted: 'O upload do arquivo foi abortada',
-        msgValidationError: 'Erro de validação',
-        msgLoading: 'Enviando arquivo {index} de {files}&hellip;',
-        msgProgress: 'Enviando arquivo {index} de {files} - {name} - {percent}% completo.',
-        msgSelected: '{n} {files} selecionado(s)',
-        msgFoldersNotAllowed: 'Arraste e solte apenas arquivos! {n} soltar pasta(s) ignoradas.',
-        msgImageWidthSmall: 'Largura do arquivo de imagem "{name}" deve ser pelo menos {size} px.',
-        msgImageHeightSmall: 'Altura do arquivo de imagem "{name}" deve ser pelo menos {size} px.',
-        msgImageWidthLarge: 'Largura do arquivo de imagem "{name}" não pode exceder {size} px.',
-        msgImageHeightLarge: 'Altura do arquivo de imagem "{name}" não pode exceder {size} px.',
-        msgImageResizeError: 'Could not get the image dimensions to resize.',
-        msgImageResizeException: 'Erro ao redimensionar a imagem.<pre>{errors}</pre>',
-        dropZoneTitle: 'Arraste e solte os arquivos aqui&hellip;',
-        fileActionSettings: {
-            removeTitle: 'Remover arquivo',
-            uploadTitle: 'Carregar arquivo',
-            indicatorNewTitle: 'Ainda não carregou',
-            indicatorSuccessTitle: 'Carregado',
-            indicatorErrorTitle: 'Carregar Erro',
-            indicatorLoadingTitle: 'A carregar ...'
-        }
-    };
+/*!
+ * FileInput Brazillian Portuguese Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputLocales['pt-BR'] = {
+        fileSingle: 'arquivo',
+        filePlural: 'arquivos',
+        browseLabel: 'Procurar&hellip;',
+        removeLabel: 'Remover',
+        removeTitle: 'Remover arquivos selecionados',
+        cancelLabel: 'Cancelar',
+        cancelTitle: 'Interromper envio em andamento',
+        uploadLabel: 'Enviar',
+        uploadTitle: 'Enviar arquivos selecionados',
+        msgNo: 'Não',
+        msgCancelled: 'Cancelado',
+        msgZoomModalHeading: 'Pré-visualização detalhada',
+        msgSizeTooLarge: 'O arquivo "{name}" (<b>{size} KB</b>) excede o tamanho máximo permitido de <b>{maxSize} KB</b>.',
+        msgFilesTooLess: 'Você deve selecionar pelo menos <b>{n}</b> {files} para enviar.',
+        msgFilesTooMany: 'O número de arquivos selecionados para o envio <b>({n})</b> excede o limite máximo permitido de <b>{m}</b>.',
+        msgFileNotFound: 'O arquivo "{name}" não foi encontrado!',
+        msgFileSecured: 'Restrições de segurança impedem a leitura do arquivo "{name}".',
+        msgFileNotReadable: 'O arquivo "{name}" não pode ser lido.',
+        msgFilePreviewAborted: 'A pré-visualização do arquivo "{name}" foi interrompida.',
+        msgFilePreviewError: 'Ocorreu um erro ao ler o arquivo "{name}".',
+        msgInvalidFileType: 'Tipo inválido para o arquivo "{name}". Apenas arquivos "{types}" são permitidos.',
+        msgInvalidFileExtension: 'Extensão inválida para o arquivo "{name}". Apenas arquivos "{extensions}" são permitidos.',
+        msgUploadAborted: 'O upload do arquivo foi abortada',
+        msgValidationError: 'Erro de validação',
+        msgLoading: 'Enviando arquivo {index} de {files}&hellip;',
+        msgProgress: 'Enviando arquivo {index} de {files} - {name} - {percent}% completo.',
+        msgSelected: '{n} {files} selecionado(s)',
+        msgFoldersNotAllowed: 'Arraste e solte apenas arquivos! {n} soltar pasta(s) ignoradas.',
+        msgImageWidthSmall: 'Largura do arquivo de imagem "{name}" deve ser pelo menos {size} px.',
+        msgImageHeightSmall: 'Altura do arquivo de imagem "{name}" deve ser pelo menos {size} px.',
+        msgImageWidthLarge: 'Largura do arquivo de imagem "{name}" não pode exceder {size} px.',
+        msgImageHeightLarge: 'Altura do arquivo de imagem "{name}" não pode exceder {size} px.',
+        msgImageResizeError: 'Could not get the image dimensions to resize.',
+        msgImageResizeException: 'Erro ao redimensionar a imagem.<pre>{errors}</pre>',
+        dropZoneTitle: 'Arraste e solte os arquivos aqui&hellip;',
+        fileActionSettings: {
+            removeTitle: 'Remover arquivo',
+            uploadTitle: 'Carregar arquivo',
+            zoomTitle: 'Ver detalhes',
+            dragTitle: 'Move / Rearrange',
+            indicatorNewTitle: 'Ainda não carregou',
+            indicatorSuccessTitle: 'Carregado',
+            indicatorErrorTitle: 'Carregar Erro',
+            indicatorLoadingTitle: 'A carregar ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
 })(window.jQuery);

+ 68 - 59
js/fileinput_locale_pt.js → js/locales/fileinput_locale_pt.js

@@ -1,60 +1,69 @@
-/*!
- * FileInput Portuguese Translations
- *
- * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
- * any HTML markup tags in the messages must not be converted or translated.
- *
- * @see http://github.com/kartik-v/bootstrap-fileinput
- *
- * NOTE: this file must be saved in UTF-8 encoding.
- */
-(function ($) {
-    "use strict";
-
-    $.fn.fileinputLocales['pt'] = {
-        fileSingle: 'ficheiro',
-        filePlural: 'ficheiros',
-        browseLabel: 'Procurar &hellip;',
-        removeLabel: 'Remover',
-        removeTitle: 'Remover ficheiros seleccionados',
-        cancelLabel: 'Cancelar',
-        cancelTitle: 'Abortar carregamento ',
-        uploadLabel: 'Carregar',
-        uploadTitle: 'Carregar ficheiros seleccionados',
-        msgNo: 'Não',
-        msgCancelled: 'Cancelado',
-        msgZoomTitle: 'Ver detalhes',
-        msgZoomModalHeading: 'Pré-visualização detalhada',
-        msgSizeTooLarge: 'Ficheiro "{name}" (<b>{size} KB</b>) excede o tamanho máximo permido de <b>{maxSize} KB</b>.',
-        msgFilesTooLess: 'Deve seleccionar pelo menos <b>{n}</b> {files} para fazer upload.',
-        msgFilesTooMany: 'Número máximo de ficheiros seleccionados <b>({n})</b> excede o limite máximo de <b>{m}</b>.',
-        msgFileNotFound: 'Ficheiro "{name}" não encontrado!',
-        msgFileSecured: 'Restrições de segurança preventem a leitura do ficheiro "{name}".',
-        msgFileNotReadable: 'Ficheiro "{name}" não pode ser lido.',
-        msgFilePreviewAborted: 'Pré-visualização abortado para o ficheiro "{name}".',
-        msgFilePreviewError: 'Ocorreu um erro ao ler o ficheiro "{name}".',
-        msgInvalidFileType: 'Tipo inválido para o ficheiro "{name}". Apenas ficheiros "{types}" são suportados.',
-        msgInvalidFileExtension: 'Extensão inválida para o ficheiro "{name}". Apenas ficheiros "{extensions}" são suportados.',
-        msgUploadAborted: 'O upload do arquivo foi abortada',
-        msgValidationError: 'Erro de validação',
-        msgLoading: 'A carregar ficheiro {index} de {files} &hellip;',
-        msgProgress: 'A carregar ficheiro {index} de {files} - {name} - {percent}% completo.',
-        msgSelected: '{n} {files} seleccionados',
-        msgFoldersNotAllowed: 'Arrastar e largar ficheiros apenas! {n} pasta(s) ignoradas.',
-        msgImageWidthSmall: 'Largura do arquivo de imagem "{name}" deve ser pelo menos {size} px.',
-        msgImageHeightSmall: 'Altura do arquivo de imagem "{name}" deve ser pelo menos {size} px.',
-        msgImageWidthLarge: 'Largura do arquivo de imagem "{name}" não pode exceder {size} px.',
-        msgImageHeightLarge: 'Altura do arquivo de imagem "{name}" não pode exceder {size} px.',
-        msgImageResizeError: 'Could not get the image dimensions to resize.',
-        msgImageResizeException: 'Erro ao redimensionar a imagem.<pre>{errors}</pre>',
-        dropZoneTitle: 'Arrastar e largar ficheiros aqui &hellip;',
-        fileActionSettings: {
-            removeTitle: 'Remover arquivo',
-            uploadTitle: 'Carregar arquivo',
-            indicatorNewTitle: 'Ainda não carregou',
-            indicatorSuccessTitle: 'Carregado',
-            indicatorErrorTitle: 'Carregar Erro',
-            indicatorLoadingTitle: 'A carregar ...'
-        }
-    };
+/*!
+ * FileInput Portuguese Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputLocales['pt'] = {
+        fileSingle: 'ficheiro',
+        filePlural: 'ficheiros',
+        browseLabel: 'Procurar &hellip;',
+        removeLabel: 'Remover',
+        removeTitle: 'Remover ficheiros seleccionados',
+        cancelLabel: 'Cancelar',
+        cancelTitle: 'Abortar carregamento ',
+        uploadLabel: 'Carregar',
+        uploadTitle: 'Carregar ficheiros seleccionados',
+        msgNo: 'Não',
+        msgCancelled: 'Cancelado',
+        msgZoomModalHeading: 'Pré-visualização detalhada',
+        msgSizeTooLarge: 'Ficheiro "{name}" (<b>{size} KB</b>) excede o tamanho máximo permido de <b>{maxSize} KB</b>.',
+        msgFilesTooLess: 'Deve seleccionar pelo menos <b>{n}</b> {files} para fazer upload.',
+        msgFilesTooMany: 'Número máximo de ficheiros seleccionados <b>({n})</b> excede o limite máximo de <b>{m}</b>.',
+        msgFileNotFound: 'Ficheiro "{name}" não encontrado!',
+        msgFileSecured: 'Restrições de segurança preventem a leitura do ficheiro "{name}".',
+        msgFileNotReadable: 'Ficheiro "{name}" não pode ser lido.',
+        msgFilePreviewAborted: 'Pré-visualização abortado para o ficheiro "{name}".',
+        msgFilePreviewError: 'Ocorreu um erro ao ler o ficheiro "{name}".',
+        msgInvalidFileType: 'Tipo inválido para o ficheiro "{name}". Apenas ficheiros "{types}" são suportados.',
+        msgInvalidFileExtension: 'Extensão inválida para o ficheiro "{name}". Apenas ficheiros "{extensions}" são suportados.',
+        msgUploadAborted: 'O upload do arquivo foi abortada',
+        msgValidationError: 'Erro de validação',
+        msgLoading: 'A carregar ficheiro {index} de {files} &hellip;',
+        msgProgress: 'A carregar ficheiro {index} de {files} - {name} - {percent}% completo.',
+        msgSelected: '{n} {files} seleccionados',
+        msgFoldersNotAllowed: 'Arrastar e largar ficheiros apenas! {n} pasta(s) ignoradas.',
+        msgImageWidthSmall: 'Largura do arquivo de imagem "{name}" deve ser pelo menos {size} px.',
+        msgImageHeightSmall: 'Altura do arquivo de imagem "{name}" deve ser pelo menos {size} px.',
+        msgImageWidthLarge: 'Largura do arquivo de imagem "{name}" não pode exceder {size} px.',
+        msgImageHeightLarge: 'Altura do arquivo de imagem "{name}" não pode exceder {size} px.',
+        msgImageResizeError: 'Could not get the image dimensions to resize.',
+        msgImageResizeException: 'Erro ao redimensionar a imagem.<pre>{errors}</pre>',
+        dropZoneTitle: 'Arrastar e largar ficheiros aqui &hellip;',
+        fileActionSettings: {
+            removeTitle: 'Remover arquivo',
+            uploadTitle: 'Carregar arquivo',
+            zoomTitle: 'Ver detalhes',
+            dragTitle: 'Move / Rearrange',
+            indicatorNewTitle: 'Ainda não carregou',
+            indicatorSuccessTitle: 'Carregado',
+            indicatorErrorTitle: 'Carregar Erro',
+            indicatorLoadingTitle: 'A carregar ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
 })(window.jQuery);

+ 70 - 61
js/fileinput_locale_ro.js → js/locales/fileinput_locale_ro.js

@@ -1,61 +1,70 @@
-/*!
- * FileInput Romanian Translations
- *
- * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
- * any HTML markup tags in the messages must not be converted or translated.
- *
- * @see http://github.com/kartik-v/bootstrap-fileinput
- * @author Ciprian Voicu <[email protected]>
- *
- * NOTE: this file must be saved in UTF-8 encoding.
- */
-(function ($) {
-    "use strict";
-
-    $.fn.fileinputLocales['ro'] = {
-        fileSingle: 'fișier',
-        filePlural: 'fișiere',
-        browseLabel: 'Răsfoiește &hellip;',
-        removeLabel: 'Șterge',
-        removeTitle: 'Curăță fișierele selectate',
-        cancelLabel: 'Renunță',
-        cancelTitle: 'Anulează încărcarea curentă',
-        uploadLabel: 'Încarcă',
-        uploadTitle: 'Încarcă fișierele selectate',
-        msgNo: 'Nu',
-        msgCancelled: 'Anulat',
-        msgZoomTitle: 'Vezi detalii',
-        msgZoomModalHeading: 'Previzualizare detaliată',
-        msgSizeTooLarge: 'Fișierul "{name}" (<b>{size} KB</b>) depășește limita maximă de încărcare de <b>{maxSize} KB</b>.',
-        msgFilesTooLess: 'Trebuie să selectezi cel puțin <b>{n}</b> {files} pentru a încărca.',
-        msgFilesTooMany: 'Numărul fișierelor pentru încărcare <b>({n})</b> depășește limita maximă de <b>{m}</b>.',
-        msgFileNotFound: 'Fișierul "{name}" nu a fost găsit!',
-        msgFileSecured: 'Restricții de securitate previn citirea fișierului "{name}".',
-        msgFileNotReadable: 'Fișierul "{name}" nu se poate citi.',
-        msgFilePreviewAborted: 'Fișierului "{name}" nu poate fi previzualizat.',
-        msgFilePreviewError: 'A intervenit o eroare în încercarea de citire a fișierului "{name}".',
-        msgInvalidFileType: 'Tip de fișier incorect pentru "{name}". Sunt suportate doar fișiere de tipurile "{types}".',
-        msgInvalidFileExtension: 'Extensie incorectă pentru "{name}". Sunt suportate doar extensiile "{extensions}".',
-        msgUploadAborted: 'Fișierul Încărcarea a fost întrerupt',
-        msgValidationError: 'Eroare de validare',
-        msgLoading: 'Se încarcă fișierul {index} din {files} &hellip;',
-        msgProgress: 'Se încarcă fișierul {index} din {files} - {name} - {percent}% încărcat.',
-        msgSelected: '{n} {files} încărcate',
-        msgFoldersNotAllowed: 'Se poate doar trăgând fișierele! Se renunță la {n} dosar(e).',
-        msgImageWidthSmall: 'Lățimea de fișier de imagine "{name}" trebuie să fie de cel puțin {size px.',
-        msgImageHeightSmall: 'Înălțimea fișier imagine "{name}" trebuie să fie de cel puțin {size} px.',
-        msgImageWidthLarge: 'Lățimea de fișier de imagine "{name}" nu poate depăși {size} px.',
-        msgImageHeightLarge: 'Înălțimea fișier imagine "{name}" nu poate depăși {size} px.',
-        msgImageResizeError: 'Nu a putut obține dimensiunile imaginii pentru a redimensiona.',
-        msgImageResizeException: 'Eroare la redimensionarea imaginii.<pre>{errors}</pre>',
-        dropZoneTitle: 'Trage fișierele aici &hellip;',
-        fileActionSettings: {
-            removeTitle: 'Scoateți fișier',
-            uploadTitle: 'Incarca fisier',
-            indicatorNewTitle: 'Nu a încărcat încă',
-            indicatorSuccessTitle: 'încărcat',
-            indicatorErrorTitle: 'Încărcați eroare',
-            indicatorLoadingTitle: 'Se încarcă ...'
-        }
-    };
-})(window.jQuery);
+/*!
+ * FileInput Romanian Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ * @author Ciprian Voicu <[email protected]>
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputLocales['ro'] = {
+        fileSingle: 'fișier',
+        filePlural: 'fișiere',
+        browseLabel: 'Răsfoiește &hellip;',
+        removeLabel: 'Șterge',
+        removeTitle: 'Curăță fișierele selectate',
+        cancelLabel: 'Renunță',
+        cancelTitle: 'Anulează încărcarea curentă',
+        uploadLabel: 'Încarcă',
+        uploadTitle: 'Încarcă fișierele selectate',
+        msgNo: 'Nu',
+        msgCancelled: 'Anulat',
+        msgZoomModalHeading: 'Previzualizare detaliată',
+        msgSizeTooLarge: 'Fișierul "{name}" (<b>{size} KB</b>) depășește limita maximă de încărcare de <b>{maxSize} KB</b>.',
+        msgFilesTooLess: 'Trebuie să selectezi cel puțin <b>{n}</b> {files} pentru a încărca.',
+        msgFilesTooMany: 'Numărul fișierelor pentru încărcare <b>({n})</b> depășește limita maximă de <b>{m}</b>.',
+        msgFileNotFound: 'Fișierul "{name}" nu a fost găsit!',
+        msgFileSecured: 'Restricții de securitate previn citirea fișierului "{name}".',
+        msgFileNotReadable: 'Fișierul "{name}" nu se poate citi.',
+        msgFilePreviewAborted: 'Fișierului "{name}" nu poate fi previzualizat.',
+        msgFilePreviewError: 'A intervenit o eroare în încercarea de citire a fișierului "{name}".',
+        msgInvalidFileType: 'Tip de fișier incorect pentru "{name}". Sunt suportate doar fișiere de tipurile "{types}".',
+        msgInvalidFileExtension: 'Extensie incorectă pentru "{name}". Sunt suportate doar extensiile "{extensions}".',
+        msgUploadAborted: 'Fișierul Încărcarea a fost întrerupt',
+        msgValidationError: 'Eroare de validare',
+        msgLoading: 'Se încarcă fișierul {index} din {files} &hellip;',
+        msgProgress: 'Se încarcă fișierul {index} din {files} - {name} - {percent}% încărcat.',
+        msgSelected: '{n} {files} încărcate',
+        msgFoldersNotAllowed: 'Se poate doar trăgând fișierele! Se renunță la {n} dosar(e).',
+        msgImageWidthSmall: 'Lățimea de fișier de imagine "{name}" trebuie să fie de cel puțin {size px.',
+        msgImageHeightSmall: 'Înălțimea fișier imagine "{name}" trebuie să fie de cel puțin {size} px.',
+        msgImageWidthLarge: 'Lățimea de fișier de imagine "{name}" nu poate depăși {size} px.',
+        msgImageHeightLarge: 'Înălțimea fișier imagine "{name}" nu poate depăși {size} px.',
+        msgImageResizeError: 'Nu a putut obține dimensiunile imaginii pentru a redimensiona.',
+        msgImageResizeException: 'Eroare la redimensionarea imaginii.<pre>{errors}</pre>',
+        dropZoneTitle: 'Trage fișierele aici &hellip;',
+        fileActionSettings: {
+            removeTitle: 'Scoateți fișier',
+            uploadTitle: 'Incarca fisier',
+            zoomTitle: 'Vezi detalii',
+            dragTitle: 'Move / Rearrange',
+            indicatorNewTitle: 'Nu a încărcat încă',
+            indicatorSuccessTitle: 'încărcat',
+            indicatorErrorTitle: 'Încărcați eroare',
+            indicatorLoadingTitle: 'Se încarcă ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
+})(window.jQuery);

+ 70 - 61
js/fileinput_locale_ru.js → js/locales/fileinput_locale_ru.js

@@ -1,61 +1,70 @@
-/*!
- * FileInput Russian Translations
- *
- * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
- * any HTML markup tags in the messages must not be converted or translated.
- *
- * @see http://github.com/kartik-v/bootstrap-fileinput
- * @author CyanoFresh <[email protected]>
- *
- * NOTE: this file must be saved in UTF-8 encoding.
- */
-(function ($) {
-    "use strict";
-
-    $.fn.fileinputLocales['ru'] = {
-        fileSingle: 'файл',
-        filePlural: 'файлы',
-        browseLabel: 'Выбрать &hellip;',
-        removeLabel: 'Удалить',
-        removeTitle: 'Очистить выбранные файлы',
-        cancelLabel: 'Отмена',
-        cancelTitle: 'Отменить текущую загрузку',
-        uploadLabel: 'Загрузить',
-        uploadTitle: 'Загрузить выбранные файлы',
-        msgNo: 'нет',
-        msgCancelled: 'Отменено',
-        msgZoomTitle: 'посмотреть детали',
-        msgZoomModalHeading: 'Подробное превью',
-        msgSizeTooLarge: 'Файл "{name}" (<b>{size} KB</b>) превышает максимальный размер <b>{maxSize} KB</b>.',
-        msgFilesTooLess: 'Вы должны выбрать как минимум <b>{n}</b> {files} для загрузки.',
-        msgFilesTooMany: 'Количество выбранных файлов <b>({n})</b> превышает максимально допустимое количество <b>{m}</b>.',
-        msgFileNotFound: 'Файл "{name}" не найден!',
-        msgFileSecured: 'Ограничения безопасности запрещают читать файл "{name}".',
-        msgFileNotReadable: 'Файл "{name}" невозможно прочитать.',
-        msgFilePreviewAborted: 'Предпросмотр отменен для файла "{name}".',
-        msgFilePreviewError: 'Произошла ошибка при чтении файла "{name}".',
-        msgInvalidFileType: 'Запрещенный тип файла для "{name}". Только "{types}" разрешены.',
-        msgInvalidFileExtension: 'Запрещенное расширение для файла "{name}". Только "{extensions}" разрешены.',
-        msgUploadAborted: 'Выгрузка файла прервана',
-        msgValidationError: 'Ошибка проверки',
-        msgLoading: 'Загрузка файла {index} из {files} &hellip;',
-        msgProgress: 'Загрузка файла {index} из {files} - {name} - {percent}% завершено.',
-        msgSelected: 'Выбрано файлов: {n}',
-        msgFoldersNotAllowed: 'Разрешено перетаскивание только файлов! Пропущено {n} папок.',
-        msgImageWidthSmall: 'Ширина изображения {name} должна быть не меньше {size} px.',
-        msgImageHeightSmall: 'Высота изображения {name} должна быть не меньше {size} px.',
-        msgImageWidthLarge: 'Ширина изображения "{name}" не может превышать {size} px.',
-        msgImageHeightLarge: 'Высота изображения "{name}" не может превышать {size} px.',
-        msgImageResizeError: 'Не удалось получить размеры изображения, чтобы изменить размер.',
-        msgImageResizeException: 'Ошибка при изменении размера изображения.<pre>{errors}</pre>',
-        dropZoneTitle: 'Перетащите файлы сюда &hellip;',
-        fileActionSettings: {
-            removeTitle: 'Удалить файл',
-            uploadTitle: 'Загрузить файл',
-            indicatorNewTitle: 'Еще не загружен',
-            indicatorSuccessTitle: 'Загружен',
-            indicatorErrorTitle: 'Ошибка загрузки',
-            indicatorLoadingTitle: 'Загрузка ...'
-        }
-    };
-})(window.jQuery);
+/*!
+ * FileInput Russian Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ * @author CyanoFresh <[email protected]>
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputLocales['ru'] = {
+        fileSingle: 'файл',
+        filePlural: 'файлы',
+        browseLabel: 'Выбрать &hellip;',
+        removeLabel: 'Удалить',
+        removeTitle: 'Очистить выбранные файлы',
+        cancelLabel: 'Отмена',
+        cancelTitle: 'Отменить текущую загрузку',
+        uploadLabel: 'Загрузить',
+        uploadTitle: 'Загрузить выбранные файлы',
+        msgNo: 'нет',
+        msgCancelled: 'Отменено',
+        msgZoomModalHeading: 'Подробное превью',
+        msgSizeTooLarge: 'Файл "{name}" (<b>{size} KB</b>) превышает максимальный размер <b>{maxSize} KB</b>.',
+        msgFilesTooLess: 'Вы должны выбрать как минимум <b>{n}</b> {files} для загрузки.',
+        msgFilesTooMany: 'Количество выбранных файлов <b>({n})</b> превышает максимально допустимое количество <b>{m}</b>.',
+        msgFileNotFound: 'Файл "{name}" не найден!',
+        msgFileSecured: 'Ограничения безопасности запрещают читать файл "{name}".',
+        msgFileNotReadable: 'Файл "{name}" невозможно прочитать.',
+        msgFilePreviewAborted: 'Предпросмотр отменен для файла "{name}".',
+        msgFilePreviewError: 'Произошла ошибка при чтении файла "{name}".',
+        msgInvalidFileType: 'Запрещенный тип файла для "{name}". Только "{types}" разрешены.',
+        msgInvalidFileExtension: 'Запрещенное расширение для файла "{name}". Только "{extensions}" разрешены.',
+        msgUploadAborted: 'Выгрузка файла прервана',
+        msgValidationError: 'Ошибка проверки',
+        msgLoading: 'Загрузка файла {index} из {files} &hellip;',
+        msgProgress: 'Загрузка файла {index} из {files} - {name} - {percent}% завершено.',
+        msgSelected: 'Выбрано файлов: {n}',
+        msgFoldersNotAllowed: 'Разрешено перетаскивание только файлов! Пропущено {n} папок.',
+        msgImageWidthSmall: 'Ширина изображения {name} должна быть не меньше {size} px.',
+        msgImageHeightSmall: 'Высота изображения {name} должна быть не меньше {size} px.',
+        msgImageWidthLarge: 'Ширина изображения "{name}" не может превышать {size} px.',
+        msgImageHeightLarge: 'Высота изображения "{name}" не может превышать {size} px.',
+        msgImageResizeError: 'Не удалось получить размеры изображения, чтобы изменить размер.',
+        msgImageResizeException: 'Ошибка при изменении размера изображения.<pre>{errors}</pre>',
+        dropZoneTitle: 'Перетащите файлы сюда &hellip;',
+        fileActionSettings: {
+            removeTitle: 'Удалить файл',
+            uploadTitle: 'Загрузить файл',
+            zoomTitle: 'посмотреть детали',
+            dragTitle: 'Move / Rearrange',
+            indicatorNewTitle: 'Еще не загружен',
+            indicatorSuccessTitle: 'Загружен',
+            indicatorErrorTitle: 'Ошибка загрузки',
+            indicatorLoadingTitle: 'Загрузка ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
+})(window.jQuery);

+ 68 - 59
js/fileinput_locale_sk.js → js/locales/fileinput_locale_sk.js

@@ -1,60 +1,69 @@
-/*!
- * FileInput Slovakian Translations
- *
- * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
- * any HTML markup tags in the messages must not be converted or translated.
- *
- * @see http://github.com/kartik-v/bootstrap-fileinput
- *
- * NOTE: this file must be saved in UTF-8 encoding.
- */
-(function ($) {
-    "use strict";
-
-    $.fn.fileinputLocales['sk'] = {
-        fileSingle: 'súbor',
-        filePlural: 'súbory',
-        browseLabel: 'Vybrať &hellip;',
-        removeLabel: 'Odstrániť',
-        removeTitle: 'Vyčistiť vybraté súbory',
-        cancelLabel: 'Storno',
-        cancelTitle: 'Prerušiť  nahrávanie',
-        uploadLabel: 'Nahrať',
-        uploadTitle: 'Nahrať vybraté súbory',
-        msgNo: 'Nie',
-        msgCancelled: 'Zrušené',
-        msgZoomTitle: 'Zobraziť podrobnosti',
-        msgZoomModalHeading: 'Detailný náhľad',
-        msgSizeTooLarge: 'Súbor "{name}" (<b>{size} KB</b>): prekročenie - maximálna povolená veľkosť <b>{maxSize} KB</b>.',
-        msgFilesTooLess: 'Musíte vybrať najmenej <b>{n}</b> {files} pre nahranie.',
-        msgFilesTooMany: 'Počet vybratých súborov pre nahranie <b>({n})</b>: prekročenie - maximálny povolený limit <b>{m}</b>.',
-        msgFileNotFound: 'Súbor "{name}" nebol nájdený!',
-        msgFileSecured: 'Zabezpečenie súboru znemožnilo čítať súbor "{name}".',
-        msgFileNotReadable: 'Súbor "{name}" nie je čitateľný.',
-        msgFilePreviewAborted: 'Náhľad súboru bol prerušený pre "{name}".',
-        msgFilePreviewError: 'Nastala chyba pri načítaní súboru "{name}".',
-        msgInvalidFileType: 'Neplatný typ súboru "{name}". Iba "{types}" súborov sú podporované.',
-        msgInvalidFileExtension: 'Neplatná extenzia súboru "{name}". Iba "{extensions}" súborov sú podporované.',
-        msgUploadAborted: 'Súbor nahrávania bol prerušený',
-        msgValidationError: 'Chyba overenia',
-        msgLoading: 'Nahrávanie súboru {index} z {files} &hellip;',
-        msgProgress: 'Nahrávanie súboru {index} z {files} - {name} - {percent}% dokončené.',
-        msgSelected: '{n} {files} vybraté',
-        msgFoldersNotAllowed: 'Tiahni a pusť iba súbory! Vynechané {n} pustené prečinok(y).',
-        msgImageWidthSmall: 'Šírka image súboru "{name}", musí byť minimálne {size} px.',
-        msgImageHeightSmall: 'Výška image súboru "{name}", musí byť minimálne {size} px.',
-        msgImageWidthLarge: 'Šírka image súboru "{name}" nemôže presiahnuť {size} px.',
-        msgImageHeightLarge: 'Výška súboru obrazu "{name}" nesmie presiahnuť {size} px.',
-        msgImageResizeError: 'Nemožno získať rozmery obrázku zmeniť veľkosť.',
-        msgImageResizeException: 'Chyba pri zmene veľkosti obrázka.<pre>{errors}</pre>',
-        dropZoneTitle: 'Tiahni a pusť súbory tu &hellip;',
-        fileActionSettings: {
-            removeTitle: 'odstrániť súbor',
-            uploadTitle: 'nahrať súbor',
-            indicatorNewTitle: 'Ešte nenahral',
-            indicatorSuccessTitle: 'nahral',
-            indicatorErrorTitle: 'nahrať Chyba',
-            indicatorLoadingTitle: 'nahrávanie ...'
-        }
-    };
+/*!
+ * FileInput Slovakian Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputLocales['sk'] = {
+        fileSingle: 'súbor',
+        filePlural: 'súbory',
+        browseLabel: 'Vybrať &hellip;',
+        removeLabel: 'Odstrániť',
+        removeTitle: 'Vyčistiť vybraté súbory',
+        cancelLabel: 'Storno',
+        cancelTitle: 'Prerušiť  nahrávanie',
+        uploadLabel: 'Nahrať',
+        uploadTitle: 'Nahrať vybraté súbory',
+        msgNo: 'Nie',
+        msgCancelled: 'Zrušené',
+        msgZoomModalHeading: 'Detailný náhľad',
+        msgSizeTooLarge: 'Súbor "{name}" (<b>{size} KB</b>): prekročenie - maximálna povolená veľkosť <b>{maxSize} KB</b>.',
+        msgFilesTooLess: 'Musíte vybrať najmenej <b>{n}</b> {files} pre nahranie.',
+        msgFilesTooMany: 'Počet vybratých súborov pre nahranie <b>({n})</b>: prekročenie - maximálny povolený limit <b>{m}</b>.',
+        msgFileNotFound: 'Súbor "{name}" nebol nájdený!',
+        msgFileSecured: 'Zabezpečenie súboru znemožnilo čítať súbor "{name}".',
+        msgFileNotReadable: 'Súbor "{name}" nie je čitateľný.',
+        msgFilePreviewAborted: 'Náhľad súboru bol prerušený pre "{name}".',
+        msgFilePreviewError: 'Nastala chyba pri načítaní súboru "{name}".',
+        msgInvalidFileType: 'Neplatný typ súboru "{name}". Iba "{types}" súborov sú podporované.',
+        msgInvalidFileExtension: 'Neplatná extenzia súboru "{name}". Iba "{extensions}" súborov sú podporované.',
+        msgUploadAborted: 'Súbor nahrávania bol prerušený',
+        msgValidationError: 'Chyba overenia',
+        msgLoading: 'Nahrávanie súboru {index} z {files} &hellip;',
+        msgProgress: 'Nahrávanie súboru {index} z {files} - {name} - {percent}% dokončené.',
+        msgSelected: '{n} {files} vybraté',
+        msgFoldersNotAllowed: 'Tiahni a pusť iba súbory! Vynechané {n} pustené prečinok(y).',
+        msgImageWidthSmall: 'Šírka image súboru "{name}", musí byť minimálne {size} px.',
+        msgImageHeightSmall: 'Výška image súboru "{name}", musí byť minimálne {size} px.',
+        msgImageWidthLarge: 'Šírka image súboru "{name}" nemôže presiahnuť {size} px.',
+        msgImageHeightLarge: 'Výška súboru obrazu "{name}" nesmie presiahnuť {size} px.',
+        msgImageResizeError: 'Nemožno získať rozmery obrázku zmeniť veľkosť.',
+        msgImageResizeException: 'Chyba pri zmene veľkosti obrázka.<pre>{errors}</pre>',
+        dropZoneTitle: 'Tiahni a pusť súbory tu &hellip;',
+        fileActionSettings: {
+            removeTitle: 'odstrániť súbor',
+            uploadTitle: 'nahrať súbor',
+            zoomTitle: 'Zobraziť podrobnosti',
+            dragTitle: 'Move / Rearrange',
+            indicatorNewTitle: 'Ešte nenahral',
+            indicatorSuccessTitle: 'nahral',
+            indicatorErrorTitle: 'nahrať Chyba',
+            indicatorLoadingTitle: 'nahrávanie ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
 })(window.jQuery);

+ 68 - 59
js/fileinput_locale_th.js → js/locales/fileinput_locale_th.js

@@ -1,60 +1,69 @@
-/*!
- * FileInput Thai Translations
- *
- * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
- * any HTML markup tags in the messages must not be converted or translated.
- *
- * @see http://github.com/kartik-v/bootstrap-fileinput
- *
- * NOTE: this file must be saved in UTF-8 encoding.
- */
-(function ($) {
-    "use strict";
-
-    $.fn.fileinputLocales['th'] = {
-        fileSingle: 'ไฟล์',
-        filePlural: 'ไฟล์',
-        browseLabel: 'เลือกดู &hellip;',
-        removeLabel: 'ลบทิ้ง',
-        removeTitle: 'ลบไฟล์ที่เลือกทิ้ง',
-        cancelLabel: 'ยกเลิก',
-        cancelTitle: 'ยกเลิกการอัพโหลด',
-        uploadLabel: 'อัพโหลด',
-        uploadTitle: 'อัพโหลดไฟล์ที่เลือก',
-        msgNo: 'ไม่',
-        msgCancelled: 'ยกเลิก',
-        msgZoomTitle: 'ดูรายละเอียด',
-        msgZoomModalHeading: 'ตัวอย่างละเอียด',
-        msgSizeTooLarge: 'ไฟล์ "{name}" (<b>{size} KB</b>) มีขนาดเกินที่ระบบอนุญาตที่ <b>{maxSize} KB</b>, กรุณาลองใหม่อีกครั้ง!',
-        msgFilesTooLess: 'คุณต้องเลือกไฟล์จำนวนอย่างน้อย <b>{n}</b> {files} เพื่ออัพโหลด, กรุณาลองใหม่อีกครั้ง!',
-        msgFilesTooMany: 'ไฟล์ที่คุณเลือกมีจำนวน <b>({n})</b> ซึ่งเกินกว่าที่ระบบอนุญาตที่ <b>{m}</b>, กรุณาลองใหม่อีกครั้ง!',
-        msgFileNotFound: 'ไม่พบไฟล์ "{name}" !',
-        msgFileSecured: 'ระบบความปลอดภัยไม่อนุญาตให้อ่านไฟล์ "{name}".',
-        msgFileNotReadable: 'ไม่สามารถอ่านไฟล์ "{name}" ได้',
-        msgFilePreviewAborted: 'ไฟล์ "{name}" ไม่อนุญาตให้ดูตัวอย่าง',
-        msgFilePreviewError: 'พบปัญหาในการดูตัวอย่างไฟล์ "{name}".',
-        msgInvalidFileType: 'ไฟล์ "{name}" เป็นประเภทไฟล์ที่ไม่ถูกต้อง, อนุญาตเฉพาะไฟล์ประเภท "{types}"',
-        msgInvalidFileExtension: 'ไฟล์ "{name}" เป็น extension ที่ไมถูกต้อง, อนุญาตเฉพาะไฟล์ extension "{extensions}"',
-        msgUploadAborted: 'อัปโหลดไฟล์ถูกยกเลิก',
-        msgValidationError: 'ข้อผิดพลาดในการตรวจสอบ',
-        msgLoading: 'กำลังโหลดไฟล์ {index} จาก {files} &hellip;',
-        msgProgress: 'กำลังโหลดไฟล์ {index} จาก {files} - {name} - {percent}%',
-        msgSelected: '{n} {files} ถูกเลือก',
-        msgFoldersNotAllowed: 'Drag & drop เฉพาะไฟล์เท่านั้น! ข้าม dropped folder จำนวน {n}',
-        msgImageWidthSmall: 'ความกว้างของภาพไฟล์ "{name}" ต้องมีอย่างน้อย {size} px.',
-        msgImageHeightSmall: 'ความสูงของภาพไฟล์ "{name}" ต้องมีอย่างน้อย {size} px.',
-        msgImageWidthLarge: 'ความกว้างของภาพไฟล์ "{name}" ไม่เกิน {size} พิกเซล.',
-        msgImageHeightLarge: 'ความสูงของไฟล์ภาพ "{name}" ไม่เกิน {size} พิกเซล.',
-        msgImageResizeError: 'ไม่สามารถรับขนาดภาพเพื่อปรับขนาด',
-        msgImageResizeException: 'ข้อผิดพลาดขณะปรับขนาดภาพ<pre>{errors}</pre>',
-        dropZoneTitle: 'Drag & drop ไฟล์ตรงนี้ &hellip;',
-        fileActionSettings: {
-            removeTitle: 'ลบไฟล์',
-            uploadTitle: 'อัปโหลดไฟล์',
-            indicatorNewTitle: 'ยังไม่ได้อัปโหลด',
-            indicatorSuccessTitle: 'อัพโหลด',
-            indicatorErrorTitle: 'อัปโหลดข้อผิดพลาด',
-            indicatorLoadingTitle: 'อัพโหลด ...'
-        }
-    };
+/*!
+ * FileInput Thai Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputLocales['th'] = {
+        fileSingle: 'ไฟล์',
+        filePlural: 'ไฟล์',
+        browseLabel: 'เลือกดู &hellip;',
+        removeLabel: 'ลบทิ้ง',
+        removeTitle: 'ลบไฟล์ที่เลือกทิ้ง',
+        cancelLabel: 'ยกเลิก',
+        cancelTitle: 'ยกเลิกการอัพโหลด',
+        uploadLabel: 'อัพโหลด',
+        uploadTitle: 'อัพโหลดไฟล์ที่เลือก',
+        msgNo: 'ไม่',
+        msgCancelled: 'ยกเลิก',
+        msgZoomModalHeading: 'ตัวอย่างละเอียด',
+        msgSizeTooLarge: 'ไฟล์ "{name}" (<b>{size} KB</b>) มีขนาดเกินที่ระบบอนุญาตที่ <b>{maxSize} KB</b>, กรุณาลองใหม่อีกครั้ง!',
+        msgFilesTooLess: 'คุณต้องเลือกไฟล์จำนวนอย่างน้อย <b>{n}</b> {files} เพื่ออัพโหลด, กรุณาลองใหม่อีกครั้ง!',
+        msgFilesTooMany: 'ไฟล์ที่คุณเลือกมีจำนวน <b>({n})</b> ซึ่งเกินกว่าที่ระบบอนุญาตที่ <b>{m}</b>, กรุณาลองใหม่อีกครั้ง!',
+        msgFileNotFound: 'ไม่พบไฟล์ "{name}" !',
+        msgFileSecured: 'ระบบความปลอดภัยไม่อนุญาตให้อ่านไฟล์ "{name}".',
+        msgFileNotReadable: 'ไม่สามารถอ่านไฟล์ "{name}" ได้',
+        msgFilePreviewAborted: 'ไฟล์ "{name}" ไม่อนุญาตให้ดูตัวอย่าง',
+        msgFilePreviewError: 'พบปัญหาในการดูตัวอย่างไฟล์ "{name}".',
+        msgInvalidFileType: 'ไฟล์ "{name}" เป็นประเภทไฟล์ที่ไม่ถูกต้อง, อนุญาตเฉพาะไฟล์ประเภท "{types}"',
+        msgInvalidFileExtension: 'ไฟล์ "{name}" เป็น extension ที่ไมถูกต้อง, อนุญาตเฉพาะไฟล์ extension "{extensions}"',
+        msgUploadAborted: 'อัปโหลดไฟล์ถูกยกเลิก',
+        msgValidationError: 'ข้อผิดพลาดในการตรวจสอบ',
+        msgLoading: 'กำลังโหลดไฟล์ {index} จาก {files} &hellip;',
+        msgProgress: 'กำลังโหลดไฟล์ {index} จาก {files} - {name} - {percent}%',
+        msgSelected: '{n} {files} ถูกเลือก',
+        msgFoldersNotAllowed: 'Drag & drop เฉพาะไฟล์เท่านั้น! ข้าม dropped folder จำนวน {n}',
+        msgImageWidthSmall: 'ความกว้างของภาพไฟล์ "{name}" ต้องมีอย่างน้อย {size} px.',
+        msgImageHeightSmall: 'ความสูงของภาพไฟล์ "{name}" ต้องมีอย่างน้อย {size} px.',
+        msgImageWidthLarge: 'ความกว้างของภาพไฟล์ "{name}" ไม่เกิน {size} พิกเซล.',
+        msgImageHeightLarge: 'ความสูงของไฟล์ภาพ "{name}" ไม่เกิน {size} พิกเซล.',
+        msgImageResizeError: 'ไม่สามารถรับขนาดภาพเพื่อปรับขนาด',
+        msgImageResizeException: 'ข้อผิดพลาดขณะปรับขนาดภาพ<pre>{errors}</pre>',
+        dropZoneTitle: 'Drag & drop ไฟล์ตรงนี้ &hellip;',
+        fileActionSettings: {
+            removeTitle: 'ลบไฟล์',
+            uploadTitle: 'อัปโหลดไฟล์',
+            zoomTitle: 'ดูรายละเอียด',
+            dragTitle: 'Move / Rearrange',
+            indicatorNewTitle: 'ยังไม่ได้อัปโหลด',
+            indicatorSuccessTitle: 'อัพโหลด',
+            indicatorErrorTitle: 'อัปโหลดข้อผิดพลาด',
+            indicatorLoadingTitle: 'อัพโหลด ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
 })(window.jQuery);

+ 69 - 60
js/fileinput_locale_tr.js → js/locales/fileinput_locale_tr.js

@@ -1,60 +1,69 @@
-/*!
- * FileInput Turkish Translations
- *
- * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
- * any HTML markup tags in the messages must not be converted or translated.
- *
- * @see http://github.com/kartik-v/bootstrap-fileinput
- *
- * NOTE: this file must be saved in UTF-8 encoding.
- */
-(function ($) {
-    "use strict";
-
-    $.fn.fileinputLocales['tr'] = {
-        fileSingle: 'dosya',
-        filePlural: 'dosyalar',
-        browseLabel: 'Gözat &hellip;',
-        removeLabel: 'Sil',
-        removeTitle: 'Seçilen dosyaları sil',
-        cancelLabel: 'İptal',
-        cancelTitle: 'Devam eden yüklemeyi iptal et',
-        uploadLabel: 'Yükle',
-        uploadTitle: 'Seçilen dosyaları yükle',
-        msgNo: 'Hayır',
-        msgCancelled: 'Iptal edildi',
-        msgZoomTitle: 'Ayrıntıları görüntüle',
-        msgZoomModalHeading: 'Detaylı Önizleme',
-        msgSizeTooLarge: '"{name}" dosyasının boyutu (<b>{size} KB</b>) izin verilen azami dosya boyutu olan <b>{maxSize} KB</b>\'tan büyük.',
-        msgFilesTooLess: 'Yüklemek için en az <b>{n}</b> {files} dosya seçmelisiniz.',
-        msgFilesTooMany: 'Yüklemek için seçtiğiniz dosya sayısı <b>({n})</b> azami limitin <b>({m})</b> altında olmalıdır.',
-        msgFileNotFound: '"{name}" dosyası bulunamadı!',
-        msgFileSecured: 'Güvenlik kısıtlamaları "{name}" dosyasının okunmasını engelliyor.',
-        msgFileNotReadable: '"{name}" dosyası okunabilir değil.',
-        msgFilePreviewAborted: '"{name}" dosyası için önizleme iptal edildi.',
-        msgFilePreviewError: '"{name}" dosyası okunurken bir hata oluştu.',
-        msgInvalidFileType: '"{name}" dosyasının türü geçerli değil. Yalnızca "{types}" türünde dosyalara izin veriliyor.',
-        msgInvalidFileExtension: '"{name}" dosyasının uzantısı geçersiz. Yalnızca "{extensions}" uzantılı dosyalara izin veriliyor.',
-        msgUploadAborted: 'Dosya yükleme iptal edildi',
-        msgValidationError: 'Doğrulama Hatası',
-        msgLoading: 'Dosya yükleniyor {index} / {files} &hellip;',
-        msgProgress: 'Dosya yükleniyor {index} / {files} - {name} - %{percent} tamamlandı.',
-        msgSelected: '{n} {files} seçildi',
-        msgFoldersNotAllowed: 'Yalnızca dosyaları sürükleyip bırakabilirsiniz! {n} dizin(ler) göz ardı edildi.',
-        msgImageWidthSmall: '"{name}" adlı görüntü dosyasının genişliği en az {size} piksel olmalıdır.',
-        msgImageHeightSmall: '"{name}" adlı görüntü dosyasının yüksekliği en az {size} piksel olmalıdır.',
-        msgImageWidthLarge: '"{name}" adlı görüntü dosyasının genişliği {size} pikseli geçemez.',
-        msgImageHeightLarge: '"{name}" adlı görüntü dosyasının yüksekliği {size} pikseli geçemez.',
-        msgImageResizeError: 'Görüntü boyutlarını yeniden boyutlandırmak için alınamadı.',
-        msgImageResizeException: 'Görsel boyutlandırma sırasında hata.<pre>{errors}</pre>',
-        dropZoneTitle: 'Dosyaları buraya sürükleyip bırakın &hellip;',
-        fileActionSettings: {
-            removeTitle: 'Dosyayı kaldır',
-            uploadTitle: 'Dosyayı yükle',
-            indicatorNewTitle: 'Henüz yüklenemedi',
-            indicatorSuccessTitle: 'Yüklendi',
-            indicatorErrorTitle: 'Yükleme Hatası',
-            indicatorLoadingTitle: 'Yükleniyor ...'
-        }
-    };
-})(window.jQuery);
+/*!
+ * FileInput Turkish Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputLocales['tr'] = {
+        fileSingle: 'dosya',
+        filePlural: 'dosyalar',
+        browseLabel: 'Gözat &hellip;',
+        removeLabel: 'Sil',
+        removeTitle: 'Seçilen dosyaları sil',
+        cancelLabel: 'İptal',
+        cancelTitle: 'Devam eden yüklemeyi iptal et',
+        uploadLabel: 'Yükle',
+        uploadTitle: 'Seçilen dosyaları yükle',
+        msgNo: 'Hayır',
+        msgCancelled: 'Iptal edildi',
+        msgZoomModalHeading: 'Detaylı Önizleme',
+        msgSizeTooLarge: '"{name}" dosyasının boyutu (<b>{size} KB</b>) izin verilen azami dosya boyutu olan <b>{maxSize} KB</b>\'tan büyük.',
+        msgFilesTooLess: 'Yüklemek için en az <b>{n}</b> {files} dosya seçmelisiniz.',
+        msgFilesTooMany: 'Yüklemek için seçtiğiniz dosya sayısı <b>({n})</b> azami limitin <b>({m})</b> altında olmalıdır.',
+        msgFileNotFound: '"{name}" dosyası bulunamadı!',
+        msgFileSecured: 'Güvenlik kısıtlamaları "{name}" dosyasının okunmasını engelliyor.',
+        msgFileNotReadable: '"{name}" dosyası okunabilir değil.',
+        msgFilePreviewAborted: '"{name}" dosyası için önizleme iptal edildi.',
+        msgFilePreviewError: '"{name}" dosyası okunurken bir hata oluştu.',
+        msgInvalidFileType: '"{name}" dosyasının türü geçerli değil. Yalnızca "{types}" türünde dosyalara izin veriliyor.',
+        msgInvalidFileExtension: '"{name}" dosyasının uzantısı geçersiz. Yalnızca "{extensions}" uzantılı dosyalara izin veriliyor.',
+        msgUploadAborted: 'Dosya yükleme iptal edildi',
+        msgValidationError: 'Doğrulama Hatası',
+        msgLoading: 'Dosya yükleniyor {index} / {files} &hellip;',
+        msgProgress: 'Dosya yükleniyor {index} / {files} - {name} - %{percent} tamamlandı.',
+        msgSelected: '{n} {files} seçildi',
+        msgFoldersNotAllowed: 'Yalnızca dosyaları sürükleyip bırakabilirsiniz! {n} dizin(ler) göz ardı edildi.',
+        msgImageWidthSmall: '"{name}" adlı görüntü dosyasının genişliği en az {size} piksel olmalıdır.',
+        msgImageHeightSmall: '"{name}" adlı görüntü dosyasının yüksekliği en az {size} piksel olmalıdır.',
+        msgImageWidthLarge: '"{name}" adlı görüntü dosyasının genişliği {size} pikseli geçemez.',
+        msgImageHeightLarge: '"{name}" adlı görüntü dosyasının yüksekliği {size} pikseli geçemez.',
+        msgImageResizeError: 'Görüntü boyutlarını yeniden boyutlandırmak için alınamadı.',
+        msgImageResizeException: 'Görsel boyutlandırma sırasında hata.<pre>{errors}</pre>',
+        dropZoneTitle: 'Dosyaları buraya sürükleyip bırakın &hellip;',
+        fileActionSettings: {
+            removeTitle: 'Dosyayı kaldır',
+            uploadTitle: 'Dosyayı yükle',
+            zoomTitle: 'Ayrıntıları görüntüle',
+            dragTitle: 'Move / Rearrange',
+            indicatorNewTitle: 'Henüz yüklenemedi',
+            indicatorSuccessTitle: 'Yüklendi',
+            indicatorErrorTitle: 'Yükleme Hatası',
+            indicatorLoadingTitle: 'Yükleniyor ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
+})(window.jQuery);

+ 70 - 61
js/fileinput_locale_uk.js → js/locales/fileinput_locale_uk.js

@@ -1,61 +1,70 @@
-/*!
- * FileInput Ukrainian Translations
- *
- * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
- * any HTML markup tags in the messages must not be converted or translated.
- *
- * @see http://github.com/kartik-v/bootstrap-fileinput
- * @author CyanoFresh <[email protected]>
- *
- * NOTE: this file must be saved in UTF-8 encoding.
- */
-(function ($) {
-    "use strict";
-
-    $.fn.fileinputLocales['uk'] = {
-        fileSingle: 'файл',
-        filePlural: 'файли',
-        browseLabel: 'Вибрати &hellip;',
-        removeLabel: 'Видалити',
-        removeTitle: 'Видалити вибрані файли',
-        cancelLabel: 'Скасувати',
-        cancelTitle: 'Скасувати поточну загрузку',
-        uploadLabel: 'Загрузити',
-        uploadTitle: 'Загрузити вибрані файли',
-        msgNo: 'Немає',
-        msgCancelled: 'Cкасовано',
-        msgZoomTitle: 'Подивитися деталі',
-        msgZoomModalHeading: 'Детальний превью',
-        msgSizeTooLarge: 'Файл "{name}" (<b>{size} KB</b>) перевищує максимальний розмір <b>{maxSize} KB</b>.',
-        msgFilesTooLess: 'Ви повинні вибрати як мінімум <b>{n}</b> {files} для загрузки.',
-        msgFilesTooMany: 'Кількість вибраних файлів <b>({n})</b> перевищує максимально допустиму кількість <b>{m}</b>.',
-        msgFileNotFound: 'Файл "{name}" не знайдено!',
-        msgFileSecured: 'Обмеження безпеки перешкоджають читанню файла "{name}".',
-        msgFileNotReadable: 'Файл "{name}" неможливо прочитати.',
-        msgFilePreviewAborted: 'Перегляд скасований для файла "{name}".',
-        msgFilePreviewError: 'Сталася помилка під час читання файла "{name}".',
-        msgInvalidFileType: 'Заборонений тип файла для "{name}". Тільки "{types}" дозволені.',
-        msgInvalidFileExtension: 'Заборонене розширення для файла "{name}". Тільки "{extensions}" дозволені.',
-        msgUploadAborted: 'Вивантаження файлу перервана',
-        msgValidationError: 'Помилка перевірки',
-        msgLoading: 'Загрузка файла {index} із {files} &hellip;',
-        msgProgress: 'Загрузка файла {index} із {files} - {name} - {percent}% завершено.',
-        msgSelected: '{n} {files} вибрано',
-        msgFoldersNotAllowed: 'Дозволено перетягувати тільки файли! Пропущено {n} папок.',
-        msgImageWidthSmall: 'Ширина зображення "{name}" повинна бути не менше {size} px.',
-        msgImageHeightSmall: 'Висота зображення "{name}" повинна бути не менше {size} px.',
-        msgImageWidthLarge: 'Ширина зображення "{name}" не може перевищувати {size} px.',
-        msgImageHeightLarge: 'Висота зображення "{name}" не може перевищувати {size} px.',
-        msgImageResizeError: 'Не вдалося розміри зображення, щоб змінити розмір.',
-        msgImageResizeException: 'Помилка при зміні розміру зображення.<pre>{errors}</pre>',
-        dropZoneTitle: 'Перетягніть файли сюди &hellip;',
-        fileActionSettings: {
-            removeTitle: 'Видалити файл',
-            uploadTitle: 'Загрузити файл',
-            indicatorNewTitle: 'Ще не загружено',
-            indicatorSuccessTitle: 'Загружено',
-            indicatorErrorTitle: 'Помилка при загрузці',
-            indicatorLoadingTitle: 'Загрузка ...'
-        }
-    };
-})(window.jQuery);
+/*!
+ * FileInput Ukrainian Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ * @author CyanoFresh <[email protected]>
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputLocales['uk'] = {
+        fileSingle: 'файл',
+        filePlural: 'файли',
+        browseLabel: 'Вибрати &hellip;',
+        removeLabel: 'Видалити',
+        removeTitle: 'Видалити вибрані файли',
+        cancelLabel: 'Скасувати',
+        cancelTitle: 'Скасувати поточну загрузку',
+        uploadLabel: 'Загрузити',
+        uploadTitle: 'Загрузити вибрані файли',
+        msgNo: 'Немає',
+        msgCancelled: 'Cкасовано',
+        msgZoomModalHeading: 'Детальний превью',
+        msgSizeTooLarge: 'Файл "{name}" (<b>{size} KB</b>) перевищує максимальний розмір <b>{maxSize} KB</b>.',
+        msgFilesTooLess: 'Ви повинні вибрати як мінімум <b>{n}</b> {files} для загрузки.',
+        msgFilesTooMany: 'Кількість вибраних файлів <b>({n})</b> перевищує максимально допустиму кількість <b>{m}</b>.',
+        msgFileNotFound: 'Файл "{name}" не знайдено!',
+        msgFileSecured: 'Обмеження безпеки перешкоджають читанню файла "{name}".',
+        msgFileNotReadable: 'Файл "{name}" неможливо прочитати.',
+        msgFilePreviewAborted: 'Перегляд скасований для файла "{name}".',
+        msgFilePreviewError: 'Сталася помилка під час читання файла "{name}".',
+        msgInvalidFileType: 'Заборонений тип файла для "{name}". Тільки "{types}" дозволені.',
+        msgInvalidFileExtension: 'Заборонене розширення для файла "{name}". Тільки "{extensions}" дозволені.',
+        msgUploadAborted: 'Вивантаження файлу перервана',
+        msgValidationError: 'Помилка перевірки',
+        msgLoading: 'Загрузка файла {index} із {files} &hellip;',
+        msgProgress: 'Загрузка файла {index} із {files} - {name} - {percent}% завершено.',
+        msgSelected: '{n} {files} вибрано',
+        msgFoldersNotAllowed: 'Дозволено перетягувати тільки файли! Пропущено {n} папок.',
+        msgImageWidthSmall: 'Ширина зображення "{name}" повинна бути не менше {size} px.',
+        msgImageHeightSmall: 'Висота зображення "{name}" повинна бути не менше {size} px.',
+        msgImageWidthLarge: 'Ширина зображення "{name}" не може перевищувати {size} px.',
+        msgImageHeightLarge: 'Висота зображення "{name}" не може перевищувати {size} px.',
+        msgImageResizeError: 'Не вдалося розміри зображення, щоб змінити розмір.',
+        msgImageResizeException: 'Помилка при зміні розміру зображення.<pre>{errors}</pre>',
+        dropZoneTitle: 'Перетягніть файли сюди &hellip;',
+        fileActionSettings: {
+            removeTitle: 'Видалити файл',
+            uploadTitle: 'Загрузити файл',
+            zoomTitle: 'Подивитися деталі',
+            dragTitle: 'Move / Rearrange',
+            indicatorNewTitle: 'Ще не загружено',
+            indicatorSuccessTitle: 'Загружено',
+            indicatorErrorTitle: 'Помилка при загрузці',
+            indicatorLoadingTitle: 'Загрузка ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
+})(window.jQuery);

+ 11 - 1
js/fileinput_locale_zh-TW.js → js/locales/fileinput_locale_zh-TW.js

@@ -24,7 +24,7 @@
         uploadTitle: '上傳選取檔案',
         msgNo: '沒有',
         msgCancelled: '取消',
-        msgZoomTitle: '詳細資料',
+        zoomTitle: '詳細資料',
         msgZoomModalHeading: '內容預覽',
         msgSizeTooLarge: '檔案 "{name}" (<b>{size} KB</b>) 大小超過上限 <b>{maxSize} KB</b>.',
         msgFilesTooLess: '最少必須選擇 <b>{n}</b> {files} 來上傳. ',
@@ -52,10 +52,20 @@
         fileActionSettings: {
             removeTitle: '刪除檔案',
             uploadTitle: '上傳檔案',
+            zoomTitle: '詳細資料',
+            dragTitle: 'Move / Rearrange',
             indicatorNewTitle: '尚未上傳',
             indicatorSuccessTitle: '上傳成功',
             indicatorErrorTitle: '上傳失敗',
             indicatorLoadingTitle: '上傳中 ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
         }
     };
 })(window.jQuery);

+ 69 - 60
js/fileinput_locale_zh.js → js/locales/fileinput_locale_zh.js

@@ -1,61 +1,70 @@
-/*!
- * FileInput Chinese Translations
- *
- * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
- * any HTML markup tags in the messages must not be converted or translated.
- *
- * @see http://github.com/kartik-v/bootstrap-fileinput
- * @author kangqf <[email protected]>
- *
- * NOTE: this file must be saved in UTF-8 encoding.
- */
-(function ($) {
-    "use strict";
-
-    $.fn.fileinputLocales['zh'] = {
-        fileSingle: '文件',
-        filePlural: '多个文件',
-        browseLabel: '选择 &hellip;',
-        removeLabel: '移除',
-        removeTitle: '清除选中文件',
-        cancelLabel: '取消',
-        cancelTitle: '取消进行中的上传',
-        uploadLabel: '上传',
-        uploadTitle: '上传选中文件',
-        msgNo: '没有',
-        msgCancelled: '取消',
-        msgZoomTitle: '查看详情',
-        msgZoomModalHeading: '详细预览',
-        msgSizeTooLarge: '文件 "{name}" (<b>{size} KB</b>) 超过了允许大小 <b>{maxSize} KB</b>.',
-        msgFilesTooLess: '你必须选择最少 <b>{n}</b> {files} 来上传. ',
-        msgFilesTooMany: '选择的上传文件个数 <b>({n})</b> 超出最大文件的限制个数 <b>{m}</b>.',
-        msgFileNotFound: '文件 "{name}" 未找到!',
-        msgFileSecured: '安全限制,为了防止读取文件 "{name}".',
-        msgFileNotReadable: '文件 "{name}" 不可读.',
-        msgFilePreviewAborted: '取消 "{name}" 的预览.',
-        msgFilePreviewError: '读取 "{name}" 时出现了一个错误.',
-        msgInvalidFileType: '不正确的类型 "{name}". 只支持 "{types}" 类型的文件.',
-        msgInvalidFileExtension: '不正确的文件扩展名 "{name}". 只支持 "{extensions}" 的文件扩展名.',
-        msgUploadAborted: '该文件上传被中止',
-        msgValidationError: '验证错误',
-        msgLoading: '加载第 {index} 文件 共 {files} &hellip;',
-        msgProgress: '加载第 {index} 文件 共 {files} - {name} - {percent}% 完成.',
-        msgSelected: '{n} {files} 选中',
-        msgFoldersNotAllowed: '只支持拖拽文件! 跳过 {n} 拖拽的文件夹.',
-        msgImageWidthSmall: '宽度的图像文件的"{name}"的必须是至少{size}像素.',
-        msgImageHeightSmall: '图像文件的"{name}"的高度必须至少为{size}像素.',
-        msgImageWidthLarge: '宽度的图像文件"{name}"不能超过{size}像素.',
-        msgImageHeightLarge: '图像文件"{name}"的高度不能超过{size}像素.',
-        msgImageResizeError: '无法获取的图像尺寸调整。',
-        msgImageResizeException: '错误而调整图像大小。<pre>{errors}</pre>',
-        dropZoneTitle: '拖拽文件到这里 &hellip;',
-        fileActionSettings: {
-            removeTitle: '删除文件',
-            uploadTitle: '上传文件',
-            indicatorNewTitle: '没有上传',
-            indicatorSuccessTitle: '上传',
-            indicatorErrorTitle: '上传错误',
-            indicatorLoadingTitle: '上传 ...'
-        }
-    };
+/*!
+ * FileInput Chinese Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ * @author kangqf <[email protected]>
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputLocales['zh'] = {
+        fileSingle: '文件',
+        filePlural: '多个文件',
+        browseLabel: '选择 &hellip;',
+        removeLabel: '移除',
+        removeTitle: '清除选中文件',
+        cancelLabel: '取消',
+        cancelTitle: '取消进行中的上传',
+        uploadLabel: '上传',
+        uploadTitle: '上传选中文件',
+        msgNo: '没有',
+        msgCancelled: '取消',
+        msgZoomModalHeading: '详细预览',
+        msgSizeTooLarge: '文件 "{name}" (<b>{size} KB</b>) 超过了允许大小 <b>{maxSize} KB</b>.',
+        msgFilesTooLess: '你必须选择最少 <b>{n}</b> {files} 来上传. ',
+        msgFilesTooMany: '选择的上传文件个数 <b>({n})</b> 超出最大文件的限制个数 <b>{m}</b>.',
+        msgFileNotFound: '文件 "{name}" 未找到!',
+        msgFileSecured: '安全限制,为了防止读取文件 "{name}".',
+        msgFileNotReadable: '文件 "{name}" 不可读.',
+        msgFilePreviewAborted: '取消 "{name}" 的预览.',
+        msgFilePreviewError: '读取 "{name}" 时出现了一个错误.',
+        msgInvalidFileType: '不正确的类型 "{name}". 只支持 "{types}" 类型的文件.',
+        msgInvalidFileExtension: '不正确的文件扩展名 "{name}". 只支持 "{extensions}" 的文件扩展名.',
+        msgUploadAborted: '该文件上传被中止',
+        msgValidationError: '验证错误',
+        msgLoading: '加载第 {index} 文件 共 {files} &hellip;',
+        msgProgress: '加载第 {index} 文件 共 {files} - {name} - {percent}% 完成.',
+        msgSelected: '{n} {files} 选中',
+        msgFoldersNotAllowed: '只支持拖拽文件! 跳过 {n} 拖拽的文件夹.',
+        msgImageWidthSmall: '宽度的图像文件的"{name}"的必须是至少{size}像素.',
+        msgImageHeightSmall: '图像文件的"{name}"的高度必须至少为{size}像素.',
+        msgImageWidthLarge: '宽度的图像文件"{name}"不能超过{size}像素.',
+        msgImageHeightLarge: '图像文件"{name}"的高度不能超过{size}像素.',
+        msgImageResizeError: '无法获取的图像尺寸调整。',
+        msgImageResizeException: '错误而调整图像大小。<pre>{errors}</pre>',
+        dropZoneTitle: '拖拽文件到这里 &hellip;',
+        fileActionSettings: {
+            removeTitle: '删除文件',
+            uploadTitle: '上传文件',
+            zoomTitle: '查看详情',
+            dragTitle: 'Move / Rearrange',
+            indicatorNewTitle: '没有上传',
+            indicatorSuccessTitle: '上传',
+            indicatorErrorTitle: '上传错误',
+            indicatorLoadingTitle: '上传 ...'
+        },
+        previewZoomButtonTitles: {
+            prev: 'View previous file',
+            next: 'View next file',
+            toggleheader: 'Toggle header',
+            fullscreen: 'Toggle full screen',
+            borderless: 'Toggle borderless mode',
+            close: 'Close detailed preview'
+        }
+    };
 })(window.jQuery);

+ 812 - 0
js/plugins/purify.js

@@ -0,0 +1,812 @@
+;(function(factory) {
+    'use strict';
+    /* global window: false, define: false, module: false */
+    var root = typeof window === 'undefined' ? null : window;
+
+    if (typeof define === 'function' && define.amd) {
+        define(function(){ return factory(root); });
+    } else if (typeof module !== 'undefined') {
+        module.exports = factory(root);
+    } else {
+        root.DOMPurify = factory(root);
+    }
+}(function factory(window) {
+    'use strict';
+
+    var DOMPurify = function(window) {
+        return factory(window);
+    };
+
+    /**
+     * Version label, exposed for easier checks
+     * if DOMPurify is up to date or not
+     */
+    DOMPurify.version = '0.7.4';
+
+    if (!window || !window.document || window.document.nodeType !== 9) {
+        // not running in a browser, provide a factory function
+        // so that you can pass your own Window
+        DOMPurify.isSupported = false;
+        return DOMPurify;
+    }
+
+    var document = window.document;
+    var originalDocument = document;
+    var DocumentFragment = window.DocumentFragment;
+    var HTMLTemplateElement = window.HTMLTemplateElement;
+    var NodeFilter = window.NodeFilter;
+    var NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap;
+    var Text = window.Text;
+    var Comment = window.Comment;
+    var DOMParser = window.DOMParser;
+
+    // As per issue #47, the web-components registry is inherited by a
+    // new document created via createHTMLDocument. As per the spec
+    // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
+    // a new empty registry is used when creating a template contents owner
+    // document, so we use that as our parent document to ensure nothing
+    // is inherited.
+    if (typeof HTMLTemplateElement === 'function') {
+        var template = document.createElement('template');
+        if (template.content && template.content.ownerDocument) {
+            document = template.content.ownerDocument;
+        }
+    }
+    var implementation = document.implementation;
+    var createNodeIterator = document.createNodeIterator;
+    var getElementsByTagName = document.getElementsByTagName;
+    var createDocumentFragment = document.createDocumentFragment;
+    var importNode = originalDocument.importNode;
+
+    var hooks = {};
+
+    /**
+     * Expose whether this browser supports running the full DOMPurify.
+     */
+    DOMPurify.isSupported =
+        typeof implementation.createHTMLDocument !== 'undefined' &&
+        document.documentMode !== 9;
+
+    /* Add properties to a lookup table */
+    var _addToSet = function(set, array) {
+        var l = array.length;
+        while (l--) {
+            if (typeof array[l] === 'string') {
+                array[l] = array[l].toLowerCase();
+            }
+            set[array[l]] = true;
+        }
+        return set;
+    };
+
+    /* Shallow clone an object */
+    var _cloneObj = function(object) {
+        var newObject = {};
+        var property;
+        for (property in object) {
+            if (object.hasOwnProperty(property)) {
+                newObject[property] = object[property];
+            }
+        }
+        return newObject;
+    };
+
+    /**
+     * We consider the elements and attributes below to be safe. Ideally
+     * don't add any new ones but feel free to remove unwanted ones.
+     */
+
+    /* allowed element names */
+    var ALLOWED_TAGS = null;
+    var DEFAULT_ALLOWED_TAGS = _addToSet({}, [
+
+        // HTML
+        'a','abbr','acronym','address','area','article','aside','audio','b',
+        'bdi','bdo','big','blink','blockquote','body','br','button','canvas',
+        'caption','center','cite','code','col','colgroup','content','data',
+        'datalist','dd','decorator','del','details','dfn','dir','div','dl','dt',
+        'element','em','fieldset','figcaption','figure','font','footer','form',
+        'h1','h2','h3','h4','h5','h6','head','header','hgroup','hr','html','i',
+        'img','input','ins','kbd','label','legend','li','main','map','mark',
+        'marquee','menu','menuitem','meter','nav','nobr','ol','optgroup',
+        'option','output','p','pre','progress','q','rp','rt','ruby','s','samp',
+        'section','select','shadow','small','source','spacer','span','strike',
+        'strong','style','sub','summary','sup','table','tbody','td','template',
+        'textarea','tfoot','th','thead','time','tr','track','tt','u','ul','var',
+        'video','wbr',
+
+        // SVG
+        'svg','altglyph','altglyphdef','altglyphitem','animatecolor',
+        'animatemotion','animatetransform','circle','clippath','defs','desc',
+        'ellipse','filter','font','g','glyph','glyphref','hkern','image','line',
+        'lineargradient','marker','mask','metadata','mpath','path','pattern',
+        'polygon','polyline','radialgradient','rect','stop','switch','symbol',
+        'text','textpath','title','tref','tspan','view','vkern',
+
+        // SVG Filters
+        'feBlend','feColorMatrix','feComponentTransfer','feComposite',
+        'feConvolveMatrix','feDiffuseLighting','feDisplacementMap',
+        'feFlood','feFuncA','feFuncB','feFuncG','feFuncR','feGaussianBlur',
+        'feMerge','feMergeNode','feMorphology','feOffset',
+        'feSpecularLighting','feTile','feTurbulence',
+
+        //MathML
+        'math','menclose','merror','mfenced','mfrac','mglyph','mi','mlabeledtr',
+        'mmuliscripts','mn','mo','mover','mpadded','mphantom','mroot','mrow',
+        'ms','mpspace','msqrt','mystyle','msub','msup','msubsup','mtable','mtd',
+        'mtext','mtr','munder','munderover',
+
+        //Text
+        '#text'
+    ]);
+
+    /* Allowed attribute names */
+    var ALLOWED_ATTR = null;
+    var DEFAULT_ALLOWED_ATTR = _addToSet({}, [
+
+        // HTML
+        'accept','action','align','alt','autocomplete','background','bgcolor',
+        'border','cellpadding','cellspacing','checked','cite','class','clear','color',
+        'cols','colspan','coords','datetime','default','dir','disabled',
+        'download','enctype','face','for','headers','height','hidden','high','href',
+        'hreflang','id','ismap','label','lang','list','loop', 'low','max',
+        'maxlength','media','method','min','multiple','name','noshade','novalidate',
+        'nowrap','open','optimum','pattern','placeholder','poster','preload','pubdate',
+        'radiogroup','readonly','rel','required','rev','reversed','rows',
+        'rowspan','spellcheck','scope','selected','shape','size','span',
+        'srclang','start','src','step','style','summary','tabindex','title',
+        'type','usemap','valign','value','width','xmlns',
+
+        // SVG
+        'accent-height','accumulate','additivive','alignment-baseline',
+        'ascent','attributename','attributetype','azimuth','basefrequency',
+        'baseline-shift','begin','bias','by','clip','clip-path','clip-rule',
+        'color','color-interpolation','color-interpolation-filters','color-profile',
+        'color-rendering','cx','cy','d','dx','dy','diffuseconstant','direction',
+        'display','divisor','dur','edgemode','elevation','end','fill','fill-opacity',
+        'fill-rule','filter','flood-color','flood-opacity','font-family','font-size',
+        'font-size-adjust','font-stretch','font-style','font-variant','font-weight',
+        'fx', 'fy','g1','g2','glyph-name','glyphref','gradientunits','gradienttransform',
+        'image-rendering','in','in2','k','k1','k2','k3','k4','kerning','keypoints',
+        'keysplines','keytimes','lengthadjust','letter-spacing','kernelmatrix',
+        'kernelunitlength','lighting-color','local','marker-end','marker-mid',
+        'marker-start','markerheight','markerunits','markerwidth','maskcontentunits',
+        'maskunits','max','mask','mode','min','numoctaves','offset','operator',
+        'opacity','order','orient','orientation','origin','overflow','paint-order',
+        'path','pathlength','patterncontentunits','patterntransform','patternunits',
+        'points','preservealpha','r','rx','ry','radius','refx','refy','repeatcount',
+        'repeatdur','restart','result','rotate','scale','seed','shape-rendering',
+        'specularconstant','specularexponent','spreadmethod','stddeviation','stitchtiles',
+        'stop-color','stop-opacity','stroke-dasharray','stroke-dashoffset','stroke-linecap',
+        'stroke-linejoin','stroke-miterlimit','stroke-opacity','stroke','stroke-width',
+        'surfacescale','targetx','targety','transform','text-anchor','text-decoration',
+        'text-rendering','textlength','u1','u2','unicode','values','viewbox',
+        'visibility','vert-adv-y','vert-origin-x','vert-origin-y','word-spacing',
+        'wrap','writing-mode','xchannelselector','ychannelselector','x','x1','x2',
+        'y','y1','y2','z','zoomandpan',
+
+        // MathML
+        'accent','accentunder','bevelled','close','columnsalign','columnlines',
+        'columnspan','denomalign','depth','display','displaystyle','fence',
+        'frame','largeop','length','linethickness','lspace','lquote',
+        'mathbackground','mathcolor','mathsize','mathvariant','maxsize',
+        'minsize','movablelimits','notation','numalign','open','rowalign',
+        'rowlines','rowspacing','rowspan','rspace','rquote','scriptlevel',
+        'scriptminsize','scriptsizemultiplier','selection','separator',
+        'separators','stretchy','subscriptshift','supscriptshift','symmetric',
+        'voffset',
+
+        // XML
+        'xlink:href','xml:id','xlink:title','xml:space','xmlns:xlink'
+    ]);
+
+    /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
+    var FORBID_TAGS = null;
+
+    /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
+    var FORBID_ATTR = null;
+
+    /* Decide if custom data attributes are okay */
+    var ALLOW_DATA_ATTR = true;
+
+    /* Decide if unknown protocols are okay */
+    var ALLOW_UNKNOWN_PROTOCOLS = false;
+
+    /* Output should be safe for jQuery's $() factory? */
+    var SAFE_FOR_JQUERY = false;
+
+    /* Output should be safe for common template engines.
+     * This means, DOMPurify removes data attributes, mustaches and ERB
+     */
+    var SAFE_FOR_TEMPLATES = false;
+
+    /* Specify template detection regex for SAFE_FOR_TEMPLATES mode */
+    var MUSTACHE_EXPR = /\{\{[\s\S]*|[\s\S]*\}\}/gm;
+    var ERB_EXPR = /<%[\s\S]*|[\s\S]*%>/gm;
+
+    /* Decide if document with <html>... should be returned */
+    var WHOLE_DOCUMENT = false;
+
+    /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html string.
+     * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
+     */
+    var RETURN_DOM = false;
+
+    /* Decide if a DOM `DocumentFragment` should be returned, instead of a html string */
+    var RETURN_DOM_FRAGMENT = false;
+
+    /* If `RETURN_DOM` or `RETURN_DOM_FRAGMENT` is enabled, decide if the returned DOM
+     * `Node` is imported into the current `Document`. If this flag is not enabled the
+     * `Node` will belong (its ownerDocument) to a fresh `HTMLDocument`, created by
+     * DOMPurify. */
+    var RETURN_DOM_IMPORT = false;
+
+    /* Output should be free from DOM clobbering attacks? */
+    var SANITIZE_DOM = true;
+
+    /* Keep element content when removing element? */
+    var KEEP_CONTENT = true;
+
+    /* Tags to ignore content of when KEEP_CONTENT is true */
+    var FORBID_CONTENTS = _addToSet({}, [
+        'audio', 'head', 'math', 'script', 'style', 'svg', 'video'
+    ]);
+
+    /* Tags that are safe for data: URIs */
+    var DATA_URI_TAGS = _addToSet({}, [
+        'audio', 'video', 'img', 'source'
+    ]);
+
+    /* Attributes safe for values like "javascript:" */
+    var URI_SAFE_ATTRIBUTES = _addToSet({}, [
+        'alt','class','for','id','label','name','pattern','placeholder',
+        'summary','title','value','style','xmlns'
+    ]);
+
+    /* Keep a reference to config to pass to hooks */
+    var CONFIG = null;
+
+    /* Ideally, do not touch anything below this line */
+    /* ______________________________________________ */
+
+    var formElement = document.createElement('form');
+
+    /**
+     * _parseConfig
+     *
+     * @param  optional config literal
+     */
+    var _parseConfig = function(cfg) {
+        /* Shield configuration object from tampering */
+        if (typeof cfg !== 'object') {
+            cfg = {};
+        }
+
+        /* Set configuration parameters */
+        ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ?
+            _addToSet({}, cfg.ALLOWED_TAGS) : DEFAULT_ALLOWED_TAGS;
+        ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ?
+            _addToSet({}, cfg.ALLOWED_ATTR) : DEFAULT_ALLOWED_ATTR;
+        FORBID_TAGS = 'FORBID_TAGS' in cfg ?
+            _addToSet({}, cfg.FORBID_TAGS) : {};
+        FORBID_ATTR = 'FORBID_ATTR' in cfg ?
+            _addToSet({}, cfg.FORBID_ATTR) : {};
+        ALLOW_DATA_ATTR     = cfg.ALLOW_DATA_ATTR     !== false; // Default true
+        ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
+        SAFE_FOR_JQUERY     = cfg.SAFE_FOR_JQUERY     ||  false; // Default false
+        SAFE_FOR_TEMPLATES  = cfg.SAFE_FOR_TEMPLATES  ||  false; // Default false
+        WHOLE_DOCUMENT      = cfg.WHOLE_DOCUMENT      ||  false; // Default false
+        RETURN_DOM          = cfg.RETURN_DOM          ||  false; // Default false
+        RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT ||  false; // Default false
+        RETURN_DOM_IMPORT   = cfg.RETURN_DOM_IMPORT   ||  false; // Default false
+        SANITIZE_DOM        = cfg.SANITIZE_DOM        !== false; // Default true
+        KEEP_CONTENT        = cfg.KEEP_CONTENT        !== false; // Default true
+
+        if (SAFE_FOR_TEMPLATES) {
+            ALLOW_DATA_ATTR = false;
+        }
+
+        if (RETURN_DOM_FRAGMENT) {
+            RETURN_DOM = true;
+        }
+
+        /* Merge configuration parameters */
+        if (cfg.ADD_TAGS) {
+            if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
+                ALLOWED_TAGS = _cloneObj(ALLOWED_TAGS);
+            }
+            _addToSet(ALLOWED_TAGS, cfg.ADD_TAGS);
+        }
+        if (cfg.ADD_ATTR) {
+            if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
+                ALLOWED_ATTR = _cloneObj(ALLOWED_ATTR);
+            }
+            _addToSet(ALLOWED_ATTR, cfg.ADD_ATTR);
+        }
+
+        /* Add #text in case KEEP_CONTENT is set to true */
+        if (KEEP_CONTENT) { ALLOWED_TAGS['#text'] = true; }
+
+        // Prevent further manipulation of configuration.
+        // Not available in IE8, Safari 5, etc.
+        if (Object && 'freeze' in Object) { Object.freeze(cfg); }
+
+        CONFIG = cfg;
+    };
+
+   /**
+     * _forceRemove
+     *
+     * @param  a DOM node
+     */
+    var _forceRemove = function(node) {
+        try {
+            node.parentNode.removeChild(node);
+        } catch (e) {
+            node.outerHTML = '';
+        }
+    };
+
+   /**
+     * _initDocument
+     *
+     * @param  a string of dirty markup
+     * @return a DOM, filled with the dirty markup
+     */
+    var _initDocument = function(dirty) {
+        /* Create a HTML document using DOMParser */
+        var doc, body;
+        try {
+            doc = new DOMParser().parseFromString(dirty, 'text/html');
+        } catch (e) {}
+
+        /* Some browsers throw, some browsers return null for the code above
+           DOMParser with text/html support is only in very recent browsers. */
+        if (!doc) {
+            doc = implementation.createHTMLDocument('');
+            body = doc.body;
+            body.parentNode.removeChild(body.parentNode.firstElementChild);
+            body.outerHTML = dirty;
+        }
+
+        /* Work on whole document or just its body */
+        if (typeof doc.getElementsByTagName === 'function') {
+            return doc.getElementsByTagName(
+                WHOLE_DOCUMENT ? 'html' : 'body')[0];
+        }
+        return getElementsByTagName.call(doc,
+            WHOLE_DOCUMENT ? 'html' : 'body')[0];
+    };
+
+    /**
+     * _createIterator
+     *
+     * @param  document/fragment to create iterator for
+     * @return iterator instance
+     */
+    var _createIterator = function(root) {
+        return createNodeIterator.call(root.ownerDocument || root,
+            root,
+            NodeFilter.SHOW_ELEMENT
+            | NodeFilter.SHOW_COMMENT
+            | NodeFilter.SHOW_TEXT,
+            function() { return NodeFilter.FILTER_ACCEPT; },
+            false
+        );
+    };
+
+    /**
+     * _isClobbered
+     *
+     * @param  element to check for clobbering attacks
+     * @return true if clobbered, false if safe
+     */
+    var _isClobbered = function(elm) {
+        if (elm instanceof Text || elm instanceof Comment) {
+            return false;
+        }
+        if (  typeof elm.nodeName !== 'string'
+           || typeof elm.textContent !== 'string'
+           || typeof elm.removeChild !== 'function'
+           || !(elm.attributes instanceof NamedNodeMap)
+           || typeof elm.removeAttribute !== 'function'
+           || typeof elm.setAttribute !== 'function'
+        ) {
+            return true;
+        }
+        return false;
+    };
+
+    /**
+     * _sanitizeElements
+     *
+     * @protect nodeName
+     * @protect textContent
+     * @protect removeChild
+     *
+     * @param   node to check for permission to exist
+     * @return  true if node was killed, false if left alive
+     */
+    var _sanitizeElements = function(currentNode) {
+        var tagName, content;
+        /* Execute a hook if present */
+        _executeHook('beforeSanitizeElements', currentNode, null);
+
+        /* Check if element is clobbered or can clobber */
+        if (_isClobbered(currentNode)) {
+            _forceRemove(currentNode);
+            return true;
+        }
+
+        /* Now let's check the element's type and name */
+        tagName = currentNode.nodeName.toLowerCase();
+
+        /* Execute a hook if present */
+        _executeHook('uponSanitizeElement', currentNode, {
+            tagName: tagName
+        });
+
+        /* Remove element if anything forbids its presence */
+        if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
+            /* Keep content except for black-listed elements */
+            if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]
+                    && typeof currentNode.insertAdjacentHTML === 'function') {
+                try {
+                    currentNode.insertAdjacentHTML('AfterEnd', currentNode.innerHTML);
+                } catch (e) {}
+            }
+            _forceRemove(currentNode);
+            return true;
+        }
+
+        /* Convert markup to cover jQuery behavior */
+        if (SAFE_FOR_JQUERY && !currentNode.firstElementChild &&
+                (!currentNode.content || !currentNode.content.firstElementChild)) {
+            currentNode.innerHTML = currentNode.textContent.replace(/</g, '&lt;');
+        }
+
+        /* Sanitize element content to be template-safe */
+        if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) {
+            /* Get the element's text content */
+            content = currentNode.textContent;
+            content = content.replace(MUSTACHE_EXPR, ' ');
+            content = content.replace(ERB_EXPR, ' ');
+            currentNode.textContent = content;
+        }
+
+        /* Execute a hook if present */
+        _executeHook('afterSanitizeElements', currentNode, null);
+
+        return false;
+    };
+
+    var DATA_ATTR = /^data-[\w.\u00B7-\uFFFF-]/;
+    var IS_ALLOWED_URI = /^(?:(?:(?:f|ht)tps?|mailto|tel):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i;
+    var IS_SCRIPT_OR_DATA = /^(?:\w+script|data):/i;
+    /* This needs to be extensive thanks to Webkit/Blink's behavior */
+    var ATTR_WHITESPACE = /[\x00-\x20\xA0\u1680\u180E\u2000-\u2029\u205f\u3000]/g;
+
+    /**
+     * _sanitizeAttributes
+     *
+     * @protect attributes
+     * @protect nodeName
+     * @protect removeAttribute
+     * @protect setAttribute
+     *
+     * @param   node to sanitize
+     * @return  void
+     */
+    var _sanitizeAttributes = function(currentNode) {
+        var attr, name, value, lcName, idAttr, attributes, hookEvent, l;
+        /* Execute a hook if present */
+        _executeHook('beforeSanitizeAttributes', currentNode, null);
+
+        attributes = currentNode.attributes;
+
+        /* Check if we have attributes; if not we might have a text node */
+        if (!attributes) { return; }
+
+        hookEvent = {
+            attrName: '',
+            attrValue: '',
+            keepAttr: true
+        };
+        l = attributes.length;
+
+        /* Go backwards over all attributes; safely remove bad ones */
+        while (l--) {
+            attr = attributes[l];
+            name = attr.name;
+            value = attr.value;
+            lcName = name.toLowerCase();
+
+            /* Execute a hook if present */
+            hookEvent.attrName = lcName;
+            hookEvent.attrValue = value;
+            hookEvent.keepAttr = true;
+            _executeHook('uponSanitizeAttribute', currentNode, hookEvent );
+            value = hookEvent.attrValue;
+
+            /* Remove attribute */
+            // Safari (iOS + Mac), last tested v8.0.5, crashes if you try to
+            // remove a "name" attribute from an <img> tag that has an "id"
+            // attribute at the time.
+            if (lcName === 'name'  &&
+                    currentNode.nodeName === 'IMG' && attributes.id) {
+                idAttr = attributes.id;
+                attributes = Array.prototype.slice.apply(attributes);
+                currentNode.removeAttribute('id');
+                currentNode.removeAttribute(name);
+                if (attributes.indexOf(idAttr) > l) {
+                    currentNode.setAttribute('id', idAttr.value);
+                }
+            } else {
+                // This avoids a crash in Safari v9.0 with double-ids.
+                // The trick is to first set the id to be empty and then to
+                // remove the attriubute
+                if (name === 'id') {
+                    currentNode.setAttribute(name, '');
+                }
+                currentNode.removeAttribute(name);
+            }
+
+            /* Did the hooks approve of the attribute? */
+            if (!hookEvent.keepAttr) {
+                continue;
+            }
+
+            /* Make sure attribute cannot clobber */
+            if (SANITIZE_DOM &&
+                    (lcName === 'id' || lcName === 'name') &&
+                    (value in window || value in document || value in formElement)) {
+                continue;
+            }
+
+            /* Sanitize attribute content to be template-safe */
+            if (SAFE_FOR_TEMPLATES) {
+                value = value.replace(MUSTACHE_EXPR, ' ');
+                value = value.replace(ERB_EXPR, ' ');
+            }
+
+            if (
+                /* Check the name is permitted */
+                (ALLOWED_ATTR[lcName] && !FORBID_ATTR[lcName] && (
+                  /* Check no script, data or unknown possibly unsafe URI
+                     unless we know URI values are safe for that attribute */
+                  URI_SAFE_ATTRIBUTES[lcName] ||
+                  IS_ALLOWED_URI.test(value.replace(ATTR_WHITESPACE,'')) ||
+                  /* Keep image data URIs alive if src is allowed */
+                  (lcName === 'src' && value.indexOf('data:') === 0 &&
+                   DATA_URI_TAGS[currentNode.nodeName.toLowerCase()])
+                )) ||
+                /* Allow potentially valid data-* attributes:
+                 * At least one character after "-" (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
+                 * XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
+                 * We don't need to check the value; it's always URI safe.
+                 */
+                 (ALLOW_DATA_ATTR && DATA_ATTR.test(lcName)) ||
+                 /* Allow unknown protocols:
+                  * This provides support for links that are handled by protocol handlers which may be unknown
+                  * ahead of time, e.g. fb:, spotify:
+                  */
+                 (ALLOW_UNKNOWN_PROTOCOLS && !IS_SCRIPT_OR_DATA.test(value.replace(ATTR_WHITESPACE,'')))
+            ) {
+                /* Handle invalid data-* attribute set by try-catching it */
+                try {
+                    currentNode.setAttribute(name, value);
+                } catch (e) {}
+            }
+        }
+
+        /* Execute a hook if present */
+        _executeHook('afterSanitizeAttributes', currentNode, null);
+    };
+
+    /**
+     * _sanitizeShadowDOM
+     *
+     * @param  fragment to iterate over recursively
+     * @return void
+     */
+    var _sanitizeShadowDOM = function(fragment) {
+        var shadowNode;
+        var shadowIterator = _createIterator(fragment);
+
+        /* Execute a hook if present */
+        _executeHook('beforeSanitizeShadowDOM', fragment, null);
+
+        while ( (shadowNode = shadowIterator.nextNode()) ) {
+            /* Execute a hook if present */
+            _executeHook('uponSanitizeShadowNode', shadowNode, null);
+
+            /* Sanitize tags and elements */
+            if (_sanitizeElements(shadowNode)) {
+                continue;
+            }
+
+            /* Deep shadow DOM detected */
+            if (shadowNode.content instanceof DocumentFragment) {
+                _sanitizeShadowDOM(shadowNode.content);
+            }
+
+            /* Check attributes, sanitize if necessary */
+            _sanitizeAttributes(shadowNode);
+        }
+
+        /* Execute a hook if present */
+        _executeHook('afterSanitizeShadowDOM', fragment, null);
+    };
+
+    /**
+     * _executeHook
+     * Execute user configurable hooks
+     *
+     * @param  {String} entryPoint  Name of the hook's entry point
+     * @param  {Node} currentNode
+     */
+    var _executeHook = function(entryPoint, currentNode, data) {
+        if (!hooks[entryPoint]) { return; }
+
+        hooks[entryPoint].forEach(function(hook) {
+            hook.call(DOMPurify, currentNode, data, CONFIG);
+        });
+    };
+
+    /**
+     * sanitize
+     * Public method providing core sanitation functionality
+     *
+     * @param {String} dirty string
+     * @param {Object} configuration object
+     */
+    DOMPurify.sanitize = function(dirty, cfg) {
+        var body, currentNode, oldNode, nodeIterator, returnNode;
+        /* Make sure we have a string to sanitize.
+           DO NOT return early, as this will return the wrong type if
+           the user has requested a DOM object rather than a string */
+        if (!dirty) {
+            dirty = '';
+        }
+
+        /* Stringify, in case dirty is an object */
+        if (typeof dirty !== 'string') {
+            if (typeof dirty.toString !== 'function') {
+                throw new TypeError('toString is not a function');
+            } else {
+                dirty = dirty.toString();
+            }
+        }
+
+        /* Check we can run. Otherwise fall back or ignore */
+        if (!DOMPurify.isSupported) {
+            if (typeof window.toStaticHTML === 'object'
+                || typeof window.toStaticHTML === 'function') {
+                return window.toStaticHTML(dirty);
+            }
+            return dirty;
+        }
+
+        /* Assign config vars */
+        _parseConfig(cfg);
+
+        /* Exit directly if we have nothing to do */
+        if (!RETURN_DOM && !WHOLE_DOCUMENT && dirty.indexOf('<') === -1) {
+            return dirty;
+        }
+
+        /* Initialize the document to work on */
+        body = _initDocument(dirty);
+
+        /* Check we have a DOM node from the data */
+        if (!body) {
+            return RETURN_DOM ? null : '';
+        }
+
+        /* Get node iterator */
+        nodeIterator = _createIterator(body);
+
+        /* Now start iterating over the created document */
+        while ( (currentNode = nodeIterator.nextNode()) ) {
+
+            /* Fix IE's strange behavior with manipulated textNodes #89 */
+            if (currentNode.nodeType === 3 && currentNode === oldNode) {
+                continue;
+            }
+
+            /* Sanitize tags and elements */
+            if (_sanitizeElements(currentNode)) {
+                continue;
+            }
+
+            /* Shadow DOM detected, sanitize it */
+            if (currentNode.content instanceof DocumentFragment) {
+                _sanitizeShadowDOM(currentNode.content);
+            }
+
+            /* Check attributes, sanitize if necessary */
+            _sanitizeAttributes(currentNode);
+
+            oldNode = currentNode;
+        }
+
+        /* Return sanitized string or DOM */
+        if (RETURN_DOM) {
+
+            if (RETURN_DOM_FRAGMENT) {
+                returnNode = createDocumentFragment.call(body.ownerDocument);
+
+                while (body.firstChild) {
+                    returnNode.appendChild(body.firstChild);
+                }
+            } else {
+                returnNode = body;
+            }
+
+            if (RETURN_DOM_IMPORT) {
+                /* adoptNode() is not used because internal state is not reset
+                   (e.g. the past names map of a HTMLFormElement), this is safe
+                   in theory but we would rather not risk another attack vector.
+                   The state that is cloned by importNode() is explicitly defined
+                   by the specs. */
+                returnNode = importNode.call(originalDocument, returnNode, true);
+            }
+
+            return returnNode;
+        }
+
+        return WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
+    };
+
+    /**
+     * addHook
+     * Public method to add DOMPurify hooks
+     *
+     * @param {String} entryPoint
+     * @param {Function} hookFunction
+     */
+    DOMPurify.addHook = function(entryPoint, hookFunction) {
+        if (typeof hookFunction !== 'function') { return; }
+        hooks[entryPoint] = hooks[entryPoint] || [];
+        hooks[entryPoint].push(hookFunction);
+    };
+
+    /**
+     * removeHook
+     * Public method to remove a DOMPurify hook at a given entryPoint
+     * (pops it from the stack of hooks if more are present)
+     *
+     * @param {String} entryPoint
+     * @return void
+     */
+    DOMPurify.removeHook = function(entryPoint) {
+        if (hooks[entryPoint]) {
+            hooks[entryPoint].pop();
+        }
+    };
+
+    /**
+     * removeHooks
+     * Public method to remove all DOMPurify hooks at a given entryPoint
+     *
+     * @param  {String} entryPoint
+     * @return void
+     */
+    DOMPurify.removeHooks = function(entryPoint) {
+        if (hooks[entryPoint]) {
+            hooks[entryPoint] = [];
+        }
+    };
+
+    /**
+     * removeAllHooks
+     * Public method to remove all DOMPurify hooks
+     *
+     * @return void
+     */
+    DOMPurify.removeAllHooks = function() {
+        hooks = [];
+    };
+
+    return DOMPurify;
+}));

File diff suppressed because it is too large
+ 0 - 0
js/plugins/purify.min.js


+ 1330 - 0
js/plugins/sortable.js

@@ -0,0 +1,1330 @@
+/**!
+ * Sortable
+ * @author	RubaXa   <[email protected]>
+ * @license MIT
+ */
+(function (factory) {
+	"use strict";
+
+	if (typeof define === "function" && define.amd) {
+		define(factory);
+	}
+	else if (typeof module != "undefined" && typeof module.exports != "undefined") {
+		module.exports = factory();
+	}
+	else if (typeof Package !== "undefined") {
+		Sortable = factory();  // export for Meteor.js
+	}
+	else {
+		/* jshint sub:true */
+		window["Sortable"] = factory();
+	}
+})(function () {
+	"use strict";
+	
+	if (typeof window === "undefined" || typeof window.document == "undefined") {
+		return function() {
+			throw new Error( "Sortable.js requires a window with a document" );
+		}
+	}
+
+	var dragEl,
+		parentEl,
+		ghostEl,
+		cloneEl,
+		rootEl,
+		nextEl,
+
+		scrollEl,
+		scrollParentEl,
+
+		lastEl,
+		lastCSS,
+		lastParentCSS,
+
+		oldIndex,
+		newIndex,
+
+		activeGroup,
+		autoScroll = {},
+
+		tapEvt,
+		touchEvt,
+
+		moved,
+
+		/** @const */
+		RSPACE = /\s+/g,
+
+		expando = 'Sortable' + (new Date).getTime(),
+
+		win = window,
+		document = win.document,
+		parseInt = win.parseInt,
+
+		supportDraggable = !!('draggable' in document.createElement('div')),
+		supportCssPointerEvents = (function (el) {
+			el = document.createElement('x');
+			el.style.cssText = 'pointer-events:auto';
+			return el.style.pointerEvents === 'auto';
+		})(),
+
+		_silent = false,
+
+		abs = Math.abs,
+		slice = [].slice,
+
+		touchDragOverListeners = [],
+
+		_autoScroll = _throttle(function (/**Event*/evt, /**Object*/options, /**HTMLElement*/rootEl) {
+			// Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521
+			if (rootEl && options.scroll) {
+				var el,
+					rect,
+					sens = options.scrollSensitivity,
+					speed = options.scrollSpeed,
+
+					x = evt.clientX,
+					y = evt.clientY,
+
+					winWidth = window.innerWidth,
+					winHeight = window.innerHeight,
+
+					vx,
+					vy
+				;
+
+				// Delect scrollEl
+				if (scrollParentEl !== rootEl) {
+					scrollEl = options.scroll;
+					scrollParentEl = rootEl;
+
+					if (scrollEl === true) {
+						scrollEl = rootEl;
+
+						do {
+							if ((scrollEl.offsetWidth < scrollEl.scrollWidth) ||
+								(scrollEl.offsetHeight < scrollEl.scrollHeight)
+							) {
+								break;
+							}
+							/* jshint boss:true */
+						} while (scrollEl = scrollEl.parentNode);
+					}
+				}
+
+				if (scrollEl) {
+					el = scrollEl;
+					rect = scrollEl.getBoundingClientRect();
+					vx = (abs(rect.right - x) <= sens) - (abs(rect.left - x) <= sens);
+					vy = (abs(rect.bottom - y) <= sens) - (abs(rect.top - y) <= sens);
+				}
+
+
+				if (!(vx || vy)) {
+					vx = (winWidth - x <= sens) - (x <= sens);
+					vy = (winHeight - y <= sens) - (y <= sens);
+
+					/* jshint expr:true */
+					(vx || vy) && (el = win);
+				}
+
+
+				if (autoScroll.vx !== vx || autoScroll.vy !== vy || autoScroll.el !== el) {
+					autoScroll.el = el;
+					autoScroll.vx = vx;
+					autoScroll.vy = vy;
+
+					clearInterval(autoScroll.pid);
+
+					if (el) {
+						autoScroll.pid = setInterval(function () {
+							if (el === win) {
+								win.scrollTo(win.pageXOffset + vx * speed, win.pageYOffset + vy * speed);
+							} else {
+								vy && (el.scrollTop += vy * speed);
+								vx && (el.scrollLeft += vx * speed);
+							}
+						}, 24);
+					}
+				}
+			}
+		}, 30),
+
+		_prepareGroup = function (options) {
+			var group = options.group;
+
+			if (!group || typeof group != 'object') {
+				group = options.group = {name: group};
+			}
+
+			['pull', 'put'].forEach(function (key) {
+				if (!(key in group)) {
+					group[key] = true;
+				}
+			});
+
+			options.groups = ' ' + group.name + (group.put.join ? ' ' + group.put.join(' ') : '') + ' ';
+		}
+	;
+
+
+
+	/**
+	 * @class  Sortable
+	 * @param  {HTMLElement}  el
+	 * @param  {Object}       [options]
+	 */
+	function Sortable(el, options) {
+		if (!(el && el.nodeType && el.nodeType === 1)) {
+			throw 'Sortable: `el` must be HTMLElement, and not ' + {}.toString.call(el);
+		}
+
+		this.el = el; // root element
+		this.options = options = _extend({}, options);
+
+
+		// Export instance
+		el[expando] = this;
+
+
+		// Default options
+		var defaults = {
+			group: Math.random(),
+			sort: true,
+			disabled: false,
+			store: null,
+			handle: null,
+			scroll: true,
+			scrollSensitivity: 30,
+			scrollSpeed: 10,
+			draggable: /[uo]l/i.test(el.nodeName) ? 'li' : '>*',
+			ghostClass: 'sortable-ghost',
+			chosenClass: 'sortable-chosen',
+			ignore: 'a, img',
+			filter: null,
+			animation: 0,
+			setData: function (dataTransfer, dragEl) {
+				dataTransfer.setData('Text', dragEl.textContent);
+			},
+			dropBubble: false,
+			dragoverBubble: false,
+			dataIdAttr: 'data-id',
+			delay: 0,
+			forceFallback: false,
+			fallbackClass: 'sortable-fallback',
+			fallbackOnBody: false
+		};
+
+
+		// Set default options
+		for (var name in defaults) {
+			!(name in options) && (options[name] = defaults[name]);
+		}
+
+		_prepareGroup(options);
+
+		// Bind all private methods
+		for (var fn in this) {
+			if (fn.charAt(0) === '_') {
+				this[fn] = this[fn].bind(this);
+			}
+		}
+
+		// Setup drag mode
+		this.nativeDraggable = options.forceFallback ? false : supportDraggable;
+
+		// Bind events
+		_on(el, 'mousedown', this._onTapStart);
+		_on(el, 'touchstart', this._onTapStart);
+
+		if (this.nativeDraggable) {
+			_on(el, 'dragover', this);
+			_on(el, 'dragenter', this);
+		}
+
+		touchDragOverListeners.push(this._onDragOver);
+
+		// Restore sorting
+		options.store && this.sort(options.store.get(this));
+	}
+
+
+	Sortable.prototype = /** @lends Sortable.prototype */ {
+		constructor: Sortable,
+
+		_onTapStart: function (/** Event|TouchEvent */evt) {
+			var _this = this,
+				el = this.el,
+				options = this.options,
+				type = evt.type,
+				touch = evt.touches && evt.touches[0],
+				target = (touch || evt).target,
+				originalTarget = target,
+				filter = options.filter;
+
+
+			if (type === 'mousedown' && evt.button !== 0 || options.disabled) {
+				return; // only left button or enabled
+			}
+
+			target = _closest(target, options.draggable, el);
+
+			if (!target) {
+				return;
+			}
+
+			// get the index of the dragged element within its parent
+			oldIndex = _index(target, options.draggable);
+
+			// Check filter
+			if (typeof filter === 'function') {
+				if (filter.call(this, evt, target, this)) {
+					_dispatchEvent(_this, originalTarget, 'filter', target, el, oldIndex);
+					evt.preventDefault();
+					return; // cancel dnd
+				}
+			}
+			else if (filter) {
+				filter = filter.split(',').some(function (criteria) {
+					criteria = _closest(originalTarget, criteria.trim(), el);
+
+					if (criteria) {
+						_dispatchEvent(_this, criteria, 'filter', target, el, oldIndex);
+						return true;
+					}
+				});
+
+				if (filter) {
+					evt.preventDefault();
+					return; // cancel dnd
+				}
+			}
+
+
+			if (options.handle && !_closest(originalTarget, options.handle, el)) {
+				return;
+			}
+
+
+			// Prepare `dragstart`
+			this._prepareDragStart(evt, touch, target);
+		},
+
+		_prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target) {
+			var _this = this,
+				el = _this.el,
+				options = _this.options,
+				ownerDocument = el.ownerDocument,
+				dragStartFn;
+
+			if (target && !dragEl && (target.parentNode === el)) {
+				tapEvt = evt;
+
+				rootEl = el;
+				dragEl = target;
+				parentEl = dragEl.parentNode;
+				nextEl = dragEl.nextSibling;
+				activeGroup = options.group;
+
+				dragStartFn = function () {
+					// Delayed drag has been triggered
+					// we can re-enable the events: touchmove/mousemove
+					_this._disableDelayedDrag();
+
+					// Make the element draggable
+					dragEl.draggable = true;
+
+					// Chosen item
+					_toggleClass(dragEl, _this.options.chosenClass, true);
+
+					// Bind the events: dragstart/dragend
+					_this._triggerDragStart(touch);
+				};
+
+				// Disable "draggable"
+				options.ignore.split(',').forEach(function (criteria) {
+					_find(dragEl, criteria.trim(), _disableDraggable);
+				});
+
+				_on(ownerDocument, 'mouseup', _this._onDrop);
+				_on(ownerDocument, 'touchend', _this._onDrop);
+				_on(ownerDocument, 'touchcancel', _this._onDrop);
+
+				if (options.delay) {
+					// If the user moves the pointer or let go the click or touch
+					// before the delay has been reached:
+					// disable the delayed drag
+					_on(ownerDocument, 'mouseup', _this._disableDelayedDrag);
+					_on(ownerDocument, 'touchend', _this._disableDelayedDrag);
+					_on(ownerDocument, 'touchcancel', _this._disableDelayedDrag);
+					_on(ownerDocument, 'mousemove', _this._disableDelayedDrag);
+					_on(ownerDocument, 'touchmove', _this._disableDelayedDrag);
+
+					_this._dragStartTimer = setTimeout(dragStartFn, options.delay);
+				} else {
+					dragStartFn();
+				}
+			}
+		},
+
+		_disableDelayedDrag: function () {
+			var ownerDocument = this.el.ownerDocument;
+
+			clearTimeout(this._dragStartTimer);
+			_off(ownerDocument, 'mouseup', this._disableDelayedDrag);
+			_off(ownerDocument, 'touchend', this._disableDelayedDrag);
+			_off(ownerDocument, 'touchcancel', this._disableDelayedDrag);
+			_off(ownerDocument, 'mousemove', this._disableDelayedDrag);
+			_off(ownerDocument, 'touchmove', this._disableDelayedDrag);
+		},
+
+		_triggerDragStart: function (/** Touch */touch) {
+			if (touch) {
+				// Touch device support
+				tapEvt = {
+					target: dragEl,
+					clientX: touch.clientX,
+					clientY: touch.clientY
+				};
+
+				this._onDragStart(tapEvt, 'touch');
+			}
+			else if (!this.nativeDraggable) {
+				this._onDragStart(tapEvt, true);
+			}
+			else {
+				_on(dragEl, 'dragend', this);
+				_on(rootEl, 'dragstart', this._onDragStart);
+			}
+
+			try {
+				if (document.selection) {
+					document.selection.empty();
+				} else {
+					window.getSelection().removeAllRanges();
+				}
+			} catch (err) {
+			}
+		},
+
+		_dragStarted: function () {
+			if (rootEl && dragEl) {
+				// Apply effect
+				_toggleClass(dragEl, this.options.ghostClass, true);
+
+				Sortable.active = this;
+
+				// Drag start event
+				_dispatchEvent(this, rootEl, 'start', dragEl, rootEl, oldIndex);
+			}
+		},
+
+		_emulateDragOver: function () {
+			if (touchEvt) {
+				if (this._lastX === touchEvt.clientX && this._lastY === touchEvt.clientY) {
+					return;
+				}
+
+				this._lastX = touchEvt.clientX;
+				this._lastY = touchEvt.clientY;
+
+				if (!supportCssPointerEvents) {
+					_css(ghostEl, 'display', 'none');
+				}
+
+				var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY),
+					parent = target,
+					groupName = ' ' + this.options.group.name + '',
+					i = touchDragOverListeners.length;
+
+				if (parent) {
+					do {
+						if (parent[expando] && parent[expando].options.groups.indexOf(groupName) > -1) {
+							while (i--) {
+								touchDragOverListeners[i]({
+									clientX: touchEvt.clientX,
+									clientY: touchEvt.clientY,
+									target: target,
+									rootEl: parent
+								});
+							}
+
+							break;
+						}
+
+						target = parent; // store last element
+					}
+					/* jshint boss:true */
+					while (parent = parent.parentNode);
+				}
+
+				if (!supportCssPointerEvents) {
+					_css(ghostEl, 'display', '');
+				}
+			}
+		},
+
+
+		_onTouchMove: function (/**TouchEvent*/evt) {
+			if (tapEvt) {
+				// only set the status to dragging, when we are actually dragging
+				if (!Sortable.active) {
+					this._dragStarted();
+				}
+
+				// as well as creating the ghost element on the document body
+				this._appendGhost();
+
+				var touch = evt.touches ? evt.touches[0] : evt,
+					dx = touch.clientX - tapEvt.clientX,
+					dy = touch.clientY - tapEvt.clientY,
+					translate3d = evt.touches ? 'translate3d(' + dx + 'px,' + dy + 'px,0)' : 'translate(' + dx + 'px,' + dy + 'px)';
+
+				moved = true;
+				touchEvt = touch;
+
+				_css(ghostEl, 'webkitTransform', translate3d);
+				_css(ghostEl, 'mozTransform', translate3d);
+				_css(ghostEl, 'msTransform', translate3d);
+				_css(ghostEl, 'transform', translate3d);
+
+				evt.preventDefault();
+			}
+		},
+
+		_appendGhost: function () {
+			if (!ghostEl) {
+				var rect = dragEl.getBoundingClientRect(),
+					css = _css(dragEl),
+					options = this.options,
+					ghostRect;
+
+				ghostEl = dragEl.cloneNode(true);
+
+				_toggleClass(ghostEl, options.ghostClass, false);
+				_toggleClass(ghostEl, options.fallbackClass, true);
+
+				_css(ghostEl, 'top', rect.top - parseInt(css.marginTop, 10));
+				_css(ghostEl, 'left', rect.left - parseInt(css.marginLeft, 10));
+				_css(ghostEl, 'width', rect.width);
+				_css(ghostEl, 'height', rect.height);
+				_css(ghostEl, 'opacity', '0.8');
+				_css(ghostEl, 'position', 'fixed');
+				_css(ghostEl, 'zIndex', '100000');
+				_css(ghostEl, 'pointerEvents', 'none');
+
+				options.fallbackOnBody && document.body.appendChild(ghostEl) || rootEl.appendChild(ghostEl);
+
+				// Fixing dimensions.
+				ghostRect = ghostEl.getBoundingClientRect();
+				_css(ghostEl, 'width', rect.width * 2 - ghostRect.width);
+				_css(ghostEl, 'height', rect.height * 2 - ghostRect.height);
+			}
+		},
+
+		_onDragStart: function (/**Event*/evt, /**boolean*/useFallback) {
+			var dataTransfer = evt.dataTransfer,
+				options = this.options;
+
+			this._offUpEvents();
+
+			if (activeGroup.pull == 'clone') {
+				cloneEl = dragEl.cloneNode(true);
+				_css(cloneEl, 'display', 'none');
+				rootEl.insertBefore(cloneEl, dragEl);
+			}
+
+			if (useFallback) {
+
+				if (useFallback === 'touch') {
+					// Bind touch events
+					_on(document, 'touchmove', this._onTouchMove);
+					_on(document, 'touchend', this._onDrop);
+					_on(document, 'touchcancel', this._onDrop);
+				} else {
+					// Old brwoser
+					_on(document, 'mousemove', this._onTouchMove);
+					_on(document, 'mouseup', this._onDrop);
+				}
+
+				this._loopId = setInterval(this._emulateDragOver, 50);
+			}
+			else {
+				if (dataTransfer) {
+					dataTransfer.effectAllowed = 'move';
+					options.setData && options.setData.call(this, dataTransfer, dragEl);
+				}
+
+				_on(document, 'drop', this);
+				setTimeout(this._dragStarted, 0);
+			}
+		},
+
+		_onDragOver: function (/**Event*/evt) {
+			var el = this.el,
+				target,
+				dragRect,
+				revert,
+				options = this.options,
+				group = options.group,
+				groupPut = group.put,
+				isOwner = (activeGroup === group),
+				canSort = options.sort;
+
+			if (evt.preventDefault !== void 0) {
+				evt.preventDefault();
+				!options.dragoverBubble && evt.stopPropagation();
+			}
+
+			moved = true;
+
+			if (activeGroup && !options.disabled &&
+				(isOwner
+					? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list
+					: activeGroup.pull && groupPut && (
+						(activeGroup.name === group.name) || // by Name
+						(groupPut.indexOf && ~groupPut.indexOf(activeGroup.name)) // by Array
+					)
+				) &&
+				(evt.rootEl === void 0 || evt.rootEl === this.el) // touch fallback
+			) {
+				// Smart auto-scrolling
+				_autoScroll(evt, options, this.el);
+
+				if (_silent) {
+					return;
+				}
+
+				target = _closest(evt.target, options.draggable, el);
+				dragRect = dragEl.getBoundingClientRect();
+
+				if (revert) {
+					_cloneHide(true);
+
+					if (cloneEl || nextEl) {
+						rootEl.insertBefore(dragEl, cloneEl || nextEl);
+					}
+					else if (!canSort) {
+						rootEl.appendChild(dragEl);
+					}
+
+					return;
+				}
+
+
+				if ((el.children.length === 0) || (el.children[0] === ghostEl) ||
+					(el === evt.target) && (target = _ghostIsLast(el, evt))
+				) {
+
+					if (target) {
+						if (target.animated) {
+							return;
+						}
+
+						targetRect = target.getBoundingClientRect();
+					}
+
+					_cloneHide(isOwner);
+
+					if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect) !== false) {
+						if (!dragEl.contains(el)) {
+							el.appendChild(dragEl);
+							parentEl = el; // actualization
+						}
+
+						this._animate(dragRect, dragEl);
+						target && this._animate(targetRect, target);
+					}
+				}
+				else if (target && !target.animated && target !== dragEl && (target.parentNode[expando] !== void 0)) {
+					if (lastEl !== target) {
+						lastEl = target;
+						lastCSS = _css(target);
+						lastParentCSS = _css(target.parentNode);
+					}
+
+
+					var targetRect = target.getBoundingClientRect(),
+						width = targetRect.right - targetRect.left,
+						height = targetRect.bottom - targetRect.top,
+						floating = /left|right|inline/.test(lastCSS.cssFloat + lastCSS.display)
+							|| (lastParentCSS.display == 'flex' && lastParentCSS['flex-direction'].indexOf('row') === 0),
+						isWide = (target.offsetWidth > dragEl.offsetWidth),
+						isLong = (target.offsetHeight > dragEl.offsetHeight),
+						halfway = (floating ? (evt.clientX - targetRect.left) / width : (evt.clientY - targetRect.top) / height) > 0.5,
+						nextSibling = target.nextElementSibling,
+						moveVector = _onMove(rootEl, el, dragEl, dragRect, target, targetRect),
+						after
+					;
+
+					if (moveVector !== false) {
+						_silent = true;
+						setTimeout(_unsilent, 30);
+
+						_cloneHide(isOwner);
+
+						if (moveVector === 1 || moveVector === -1) {
+							after = (moveVector === 1);
+						}
+						else if (floating) {
+							var elTop = dragEl.offsetTop,
+								tgTop = target.offsetTop;
+
+							if (elTop === tgTop) {
+								after = (target.previousElementSibling === dragEl) && !isWide || halfway && isWide;
+							} else {
+								after = tgTop > elTop;
+							}
+						} else {
+							after = (nextSibling !== dragEl) && !isLong || halfway && isLong;
+						}
+
+						if (!dragEl.contains(el)) {
+							if (after && !nextSibling) {
+								el.appendChild(dragEl);
+							} else {
+								target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
+							}
+						}
+
+						parentEl = dragEl.parentNode; // actualization
+
+						this._animate(dragRect, dragEl);
+						this._animate(targetRect, target);
+					}
+				}
+			}
+		},
+
+		_animate: function (prevRect, target) {
+			var ms = this.options.animation;
+
+			if (ms) {
+				var currentRect = target.getBoundingClientRect();
+
+				_css(target, 'transition', 'none');
+				_css(target, 'transform', 'translate3d('
+					+ (prevRect.left - currentRect.left) + 'px,'
+					+ (prevRect.top - currentRect.top) + 'px,0)'
+				);
+
+				target.offsetWidth; // repaint
+
+				_css(target, 'transition', 'all ' + ms + 'ms');
+				_css(target, 'transform', 'translate3d(0,0,0)');
+
+				clearTimeout(target.animated);
+				target.animated = setTimeout(function () {
+					_css(target, 'transition', '');
+					_css(target, 'transform', '');
+					target.animated = false;
+				}, ms);
+			}
+		},
+
+		_offUpEvents: function () {
+			var ownerDocument = this.el.ownerDocument;
+
+			_off(document, 'touchmove', this._onTouchMove);
+			_off(ownerDocument, 'mouseup', this._onDrop);
+			_off(ownerDocument, 'touchend', this._onDrop);
+			_off(ownerDocument, 'touchcancel', this._onDrop);
+		},
+
+		_onDrop: function (/**Event*/evt) {
+			var el = this.el,
+				options = this.options;
+
+			clearInterval(this._loopId);
+			clearInterval(autoScroll.pid);
+			clearTimeout(this._dragStartTimer);
+
+			// Unbind events
+			_off(document, 'mousemove', this._onTouchMove);
+
+			if (this.nativeDraggable) {
+				_off(document, 'drop', this);
+				_off(el, 'dragstart', this._onDragStart);
+			}
+
+			this._offUpEvents();
+
+			if (evt) {
+				if (moved) {
+					evt.preventDefault();
+					!options.dropBubble && evt.stopPropagation();
+				}
+
+				ghostEl && ghostEl.parentNode.removeChild(ghostEl);
+
+				if (dragEl) {
+					if (this.nativeDraggable) {
+						_off(dragEl, 'dragend', this);
+					}
+
+					_disableDraggable(dragEl);
+
+					// Remove class's
+					_toggleClass(dragEl, this.options.ghostClass, false);
+					_toggleClass(dragEl, this.options.chosenClass, false);
+
+					if (rootEl !== parentEl) {
+						newIndex = _index(dragEl, options.draggable);
+
+						if (newIndex >= 0) {
+							// drag from one list and drop into another
+							_dispatchEvent(null, parentEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
+							_dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
+
+							// Add event
+							_dispatchEvent(null, parentEl, 'add', dragEl, rootEl, oldIndex, newIndex);
+
+							// Remove event
+							_dispatchEvent(this, rootEl, 'remove', dragEl, rootEl, oldIndex, newIndex);
+						}
+					}
+					else {
+						// Remove clone
+						cloneEl && cloneEl.parentNode.removeChild(cloneEl);
+
+						if (dragEl.nextSibling !== nextEl) {
+							// Get the index of the dragged element within its parent
+							newIndex = _index(dragEl, options.draggable);
+
+							if (newIndex >= 0) {
+								// drag & drop within the same list
+								_dispatchEvent(this, rootEl, 'update', dragEl, rootEl, oldIndex, newIndex);
+								_dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
+							}
+						}
+					}
+
+					if (Sortable.active) {
+						if (newIndex === null || newIndex === -1) {
+							newIndex = oldIndex;
+						}
+
+						_dispatchEvent(this, rootEl, 'end', dragEl, rootEl, oldIndex, newIndex);
+
+						// Save sorting
+						this.save();
+					}
+				}
+
+			}
+			this._nulling();
+		},
+
+		_nulling: function() {
+			// Nulling
+			rootEl =
+			dragEl =
+			parentEl =
+			ghostEl =
+			nextEl =
+			cloneEl =
+
+			scrollEl =
+			scrollParentEl =
+
+			tapEvt =
+			touchEvt =
+
+			moved =
+			newIndex =
+
+			lastEl =
+			lastCSS =
+
+			activeGroup =
+			Sortable.active = null;
+		},
+
+		handleEvent: function (/**Event*/evt) {
+			var type = evt.type;
+
+			if (type === 'dragover' || type === 'dragenter') {
+				if (dragEl) {
+					this._onDragOver(evt);
+					_globalDragOver(evt);
+				}
+			}
+			else if (type === 'drop' || type === 'dragend') {
+				this._onDrop(evt);
+			}
+		},
+
+
+		/**
+		 * Serializes the item into an array of string.
+		 * @returns {String[]}
+		 */
+		toArray: function () {
+			var order = [],
+				el,
+				children = this.el.children,
+				i = 0,
+				n = children.length,
+				options = this.options;
+
+			for (; i < n; i++) {
+				el = children[i];
+				if (_closest(el, options.draggable, this.el)) {
+					order.push(el.getAttribute(options.dataIdAttr) || _generateId(el));
+				}
+			}
+
+			return order;
+		},
+
+
+		/**
+		 * Sorts the elements according to the array.
+		 * @param  {String[]}  order  order of the items
+		 */
+		sort: function (order) {
+			var items = {}, rootEl = this.el;
+
+			this.toArray().forEach(function (id, i) {
+				var el = rootEl.children[i];
+
+				if (_closest(el, this.options.draggable, rootEl)) {
+					items[id] = el;
+				}
+			}, this);
+
+			order.forEach(function (id) {
+				if (items[id]) {
+					rootEl.removeChild(items[id]);
+					rootEl.appendChild(items[id]);
+				}
+			});
+		},
+
+
+		/**
+		 * Save the current sorting
+		 */
+		save: function () {
+			var store = this.options.store;
+			store && store.set(this);
+		},
+
+
+		/**
+		 * For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree.
+		 * @param   {HTMLElement}  el
+		 * @param   {String}       [selector]  default: `options.draggable`
+		 * @returns {HTMLElement|null}
+		 */
+		closest: function (el, selector) {
+			return _closest(el, selector || this.options.draggable, this.el);
+		},
+
+
+		/**
+		 * Set/get option
+		 * @param   {string} name
+		 * @param   {*}      [value]
+		 * @returns {*}
+		 */
+		option: function (name, value) {
+			var options = this.options;
+
+			if (value === void 0) {
+				return options[name];
+			} else {
+				options[name] = value;
+
+				if (name === 'group') {
+					_prepareGroup(options);
+				}
+			}
+		},
+
+
+		/**
+		 * Destroy
+		 */
+		destroy: function () {
+			var el = this.el;
+
+			el[expando] = null;
+
+			_off(el, 'mousedown', this._onTapStart);
+			_off(el, 'touchstart', this._onTapStart);
+
+			if (this.nativeDraggable) {
+				_off(el, 'dragover', this);
+				_off(el, 'dragenter', this);
+			}
+
+			// Remove draggable attributes
+			Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) {
+				el.removeAttribute('draggable');
+			});
+
+			touchDragOverListeners.splice(touchDragOverListeners.indexOf(this._onDragOver), 1);
+
+			this._onDrop();
+
+			this.el = el = null;
+		}
+	};
+
+
+	function _cloneHide(state) {
+		if (cloneEl && (cloneEl.state !== state)) {
+			_css(cloneEl, 'display', state ? 'none' : '');
+			!state && cloneEl.state && rootEl.insertBefore(cloneEl, dragEl);
+			cloneEl.state = state;
+		}
+	}
+
+
+	function _closest(/**HTMLElement*/el, /**String*/selector, /**HTMLElement*/ctx) {
+		if (el) {
+			ctx = ctx || document;
+
+			do {
+				if (
+					(selector === '>*' && el.parentNode === ctx)
+					|| _matches(el, selector)
+				) {
+					return el;
+				}
+			}
+			while (el !== ctx && (el = el.parentNode));
+		}
+
+		return null;
+	}
+
+
+	function _globalDragOver(/**Event*/evt) {
+		if (evt.dataTransfer) {
+			evt.dataTransfer.dropEffect = 'move';
+		}
+		evt.preventDefault();
+	}
+
+
+	function _on(el, event, fn) {
+		el.addEventListener(event, fn, false);
+	}
+
+
+	function _off(el, event, fn) {
+		el.removeEventListener(event, fn, false);
+	}
+
+
+	function _toggleClass(el, name, state) {
+		if (el) {
+			if (el.classList) {
+				el.classList[state ? 'add' : 'remove'](name);
+			}
+			else {
+				var className = (' ' + el.className + ' ').replace(RSPACE, ' ').replace(' ' + name + ' ', ' ');
+				el.className = (className + (state ? ' ' + name : '')).replace(RSPACE, ' ');
+			}
+		}
+	}
+
+
+	function _css(el, prop, val) {
+		var style = el && el.style;
+
+		if (style) {
+			if (val === void 0) {
+				if (document.defaultView && document.defaultView.getComputedStyle) {
+					val = document.defaultView.getComputedStyle(el, '');
+				}
+				else if (el.currentStyle) {
+					val = el.currentStyle;
+				}
+
+				return prop === void 0 ? val : val[prop];
+			}
+			else {
+				if (!(prop in style)) {
+					prop = '-webkit-' + prop;
+				}
+
+				style[prop] = val + (typeof val === 'string' ? '' : 'px');
+			}
+		}
+	}
+
+
+	function _find(ctx, tagName, iterator) {
+		if (ctx) {
+			var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length;
+
+			if (iterator) {
+				for (; i < n; i++) {
+					iterator(list[i], i);
+				}
+			}
+
+			return list;
+		}
+
+		return [];
+	}
+
+
+
+	function _dispatchEvent(sortable, rootEl, name, targetEl, fromEl, startIndex, newIndex) {
+		var evt = document.createEvent('Event'),
+			options = (sortable || rootEl[expando]).options,
+			onName = 'on' + name.charAt(0).toUpperCase() + name.substr(1);
+
+		evt.initEvent(name, true, true);
+
+		evt.to = rootEl;
+		evt.from = fromEl || rootEl;
+		evt.item = targetEl || rootEl;
+		evt.clone = cloneEl;
+
+		evt.oldIndex = startIndex;
+		evt.newIndex = newIndex;
+
+		rootEl.dispatchEvent(evt);
+
+		if (options[onName]) {
+			options[onName].call(sortable, evt);
+		}
+	}
+
+
+	function _onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect) {
+		var evt,
+			sortable = fromEl[expando],
+			onMoveFn = sortable.options.onMove,
+			retVal;
+
+		evt = document.createEvent('Event');
+		evt.initEvent('move', true, true);
+
+		evt.to = toEl;
+		evt.from = fromEl;
+		evt.dragged = dragEl;
+		evt.draggedRect = dragRect;
+		evt.related = targetEl || toEl;
+		evt.relatedRect = targetRect || toEl.getBoundingClientRect();
+
+		fromEl.dispatchEvent(evt);
+
+		if (onMoveFn) {
+			retVal = onMoveFn.call(sortable, evt);
+		}
+
+		return retVal;
+	}
+
+
+	function _disableDraggable(el) {
+		el.draggable = false;
+	}
+
+
+	function _unsilent() {
+		_silent = false;
+	}
+
+
+	/** @returns {HTMLElement|false} */
+	function _ghostIsLast(el, evt) {
+		var lastEl = el.lastElementChild,
+				rect = lastEl.getBoundingClientRect();
+
+		return ((evt.clientY - (rect.top + rect.height) > 5) || (evt.clientX - (rect.right + rect.width) > 5)) && lastEl; // min delta
+	}
+
+
+	/**
+	 * Generate id
+	 * @param   {HTMLElement} el
+	 * @returns {String}
+	 * @private
+	 */
+	function _generateId(el) {
+		var str = el.tagName + el.className + el.src + el.href + el.textContent,
+			i = str.length,
+			sum = 0;
+
+		while (i--) {
+			sum += str.charCodeAt(i);
+		}
+
+		return sum.toString(36);
+	}
+
+	/**
+	 * Returns the index of an element within its parent for a selected set of
+	 * elements
+	 * @param  {HTMLElement} el
+	 * @param  {selector} selector
+	 * @return {number}
+	 */
+	function _index(el, selector) {
+		var index = 0;
+
+		if (!el || !el.parentNode) {
+			return -1;
+		}
+
+		while (el && (el = el.previousElementSibling)) {
+			if (el.nodeName.toUpperCase() !== 'TEMPLATE'
+					&& _matches(el, selector)) {
+				index++;
+			}
+		}
+
+		return index;
+	}
+
+	function _matches(/**HTMLElement*/el, /**String*/selector) {
+		if (el) {
+			selector = selector.split('.');
+
+			var tag = selector.shift().toUpperCase(),
+				re = new RegExp('\\s(' + selector.join('|') + ')(?=\\s)', 'g');
+
+			return (
+				(tag === '' || el.nodeName.toUpperCase() == tag) &&
+				(!selector.length || ((' ' + el.className + ' ').match(re) || []).length == selector.length)
+			);
+		}
+
+		return false;
+	}
+
+	function _throttle(callback, ms) {
+		var args, _this;
+
+		return function () {
+			if (args === void 0) {
+				args = arguments;
+				_this = this;
+
+				setTimeout(function () {
+					if (args.length === 1) {
+						callback.call(_this, args[0]);
+					} else {
+						callback.apply(_this, args);
+					}
+
+					args = void 0;
+				}, ms);
+			}
+		};
+	}
+
+	function _extend(dst, src) {
+		if (dst && src) {
+			for (var key in src) {
+				if (src.hasOwnProperty(key)) {
+					dst[key] = src[key];
+				}
+			}
+		}
+
+		return dst;
+	}
+
+
+	// Export utils
+	Sortable.utils = {
+		on: _on,
+		off: _off,
+		css: _css,
+		find: _find,
+		is: function (el, selector) {
+			return !!_closest(el, selector, el);
+		},
+		extend: _extend,
+		throttle: _throttle,
+		closest: _closest,
+		toggleClass: _toggleClass,
+		index: _index
+	};
+
+
+	/**
+	 * Create sortable instance
+	 * @param {HTMLElement}  el
+	 * @param {Object}      [options]
+	 */
+	Sortable.create = function (el, options) {
+		return new Sortable(el, options);
+	};
+
+
+	// Export
+	Sortable.version = '1.4.2';
+	return Sortable;
+});
+/**
+ * jQuery plugin for Sortable
+ * @author	RubaXa   <[email protected]>
+ * @license MIT
+ */
+(function (factory) {
+	"use strict";
+
+	if (typeof define === "function" && define.amd) {
+		define(["jquery"], factory);
+	}
+	else {
+		/* jshint sub:true */
+		factory(jQuery);
+	}
+})(function ($) {
+	"use strict";
+
+
+	/* CODE */
+
+
+	/**
+	 * jQuery plugin for Sortable
+	 * @param   {Object|String} options
+	 * @param   {..*}           [args]
+	 * @returns {jQuery|*}
+	 */
+	$.fn.sortable = function (options) {
+		var retVal,
+			args = arguments;
+
+		this.each(function () {
+			var $el = $(this),
+				sortable = $el.data('sortable');
+
+			if (!sortable && (options instanceof Object || !options)) {
+				sortable = new Sortable(this, options);
+				$el.data('sortable', sortable);
+			}
+
+			if (sortable) {
+				if (options === 'widget') {
+					return sortable;
+				}
+				else if (options === 'destroy') {
+					sortable.destroy();
+					$el.removeData('sortable');
+				}
+				else if (typeof sortable[options] === 'function') {
+					retVal = sortable[options].apply(sortable, [].slice.call(args, 1));
+				}
+				else if (options in sortable.options) {
+					retVal = sortable.option.apply(sortable, args);
+				}
+			}
+		});
+
+		return (retVal === void 0) ? this : retVal;
+	};
+});

File diff suppressed because it is too large
+ 2 - 0
js/plugins/sortable.min.js


+ 45 - 0
js/themes/theme-fa.js

@@ -0,0 +1,45 @@
+/*!
+ * bootstrap-fileinput v4.3.2
+ * http://plugins.krajee.com/file-input
+ *
+ * Font Awesome icon theme configuration for bootstrap-fileinput. Requires font awesome assets to be loaded.
+ *
+ * Author: Kartik Visweswaran
+ * Copyright: 2014 - 2016, Kartik Visweswaran, Krajee.com
+ *
+ * Licensed under the BSD 3-Clause
+ * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputThemes.fa = {
+        fileActionSettings: {
+            removeIcon: '<i class="fa fa-trash text-danger"></i>',
+            uploadIcon: '<i class="fa fa-upload text-info"></i>',
+            zoomIcon: '<i class="fa fa-search-plus"></i>',
+            dragIcon: '<i class="fa fa-bars"></i>',
+            indicatorNew: '<i class="fa fa-hand-o-down text-warning"></i>',
+            indicatorSuccess: '<i class="fa fa-check-circle text-success"></i>',
+            indicatorError: '<i class="fa fa-exclamation-circle text-danger"></i>',
+            indicatorLoading: '<i class="fa fa-hand-o-up text-muted"></i>'
+        },
+        layoutTemplates: {
+            fileIcon: '<i class="fa fa-file kv-caption-icon"></i> '
+        },
+        previewZoomButtonIcons: {
+            prev: '<i class="fa fa-caret-left fa-lg"></i>',
+            next: '<i class="fa fa-caret-right fa-lg"></i>',
+            toggleheader: '<i class="fa fa-arrows-v"></i>',
+            fullscreen: '<i class="fa fa-arrows-alt"></i>',
+            borderless: '<i class="fa fa-external-link"></i>',
+            close: '<i class="fa fa-remove"></i>'
+        },
+        previewFileIcon: '<i class="fa fa-file"></i>',
+        browseIcon: '<i class="fa fa-folder-open"></i>',
+        removeIcon: '<i class="fa fa-trash"></i>',
+        cancelIcon: '<i class="fa fa-ban"></i>',
+        uploadIcon: '<i class="fa fa-upload"></i>',
+        msgValidationErrorIcon: '<i class="fa fa-exclamation-circle"></i> '
+    };
+})(window.jQuery);

+ 45 - 0
js/themes/theme-gly.js

@@ -0,0 +1,45 @@
+/*!
+ * bootstrap-fileinput v4.3.2
+ * http://plugins.krajee.com/file-input
+ *
+ * Glyphicon (default) theme configuration for bootstrap-fileinput.
+ *
+ * Author: Kartik Visweswaran
+ * Copyright: 2014 - 2016, Kartik Visweswaran, Krajee.com
+ *
+ * Licensed under the BSD 3-Clause
+ * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md
+ */
+(function ($) {
+    "use strict";
+
+    $.fn.fileinputThemes.gly = {
+        fileActionSettings: {
+            removeIcon: '<i class="glyphicon glyphicon-trash text-danger"></i>',
+            uploadIcon: '<i class="glyphicon glyphicon-upload text-info"></i>',
+            zoomIcon: '<i class="glyphicon glyphicon-zoom-in"></i>',
+            dragIcon: '<i class="glyphicon glyphicon-menu-hamburger"></i>',
+            indicatorNew: '<i class="glyphicon glyphicon-hand-down text-warning"></i>',
+            indicatorSuccess: '<i class="glyphicon glyphicon-ok-sign text-success"></i>',
+            indicatorError: '<i class="glyphicon glyphicon-exclamation-sign text-danger"></i>',
+            indicatorLoading: '<i class="glyphicon glyphicon-hand-up text-muted"></i>'
+        },
+        layoutTemplates: {
+            fileIcon: '<i class="glyphicon glyphicon-file kv-caption-icon"></i>'
+        },
+        previewZoomButtonIcons: {
+            prev: '<i class="glyphicon glyphicon-triangle-left"></i>',
+            next: '<i class="glyphicon glyphicon-triangle-right"></i>',
+            toggleheader: '<i class="glyphicon glyphicon-resize-vertical"></i>',
+            fullscreen: '<i class="glyphicon glyphicon-fullscreen"></i>',
+            borderless: '<i class="glyphicon glyphicon-resize-full"></i>',
+            close: '<i class="glyphicon glyphicon-remove"></i>'
+        },
+        previewFileIcon: '<i class="glyphicon glyphicon-file"></i>',
+        browseIcon: '<i class="glyphicon glyphicon-folder-open"></i>&nbsp;',
+        removeIcon: '<i class="glyphicon glyphicon-trash"></i>',
+        cancelIcon: '<i class="glyphicon glyphicon-ban-circle"></i>',
+        uploadIcon: '<i class="glyphicon glyphicon-upload"></i>',
+        msgValidationErrorIcon: '<i class="glyphicon glyphicon-exclamation-sign"></i> '
+    };
+})(window.jQuery);

+ 1 - 1
nuget/Package.nuspec

@@ -15,7 +15,7 @@
     <copyright>Copyright 2014 - 2016</copyright>
     <tags>bootstrap bootstrap-fileinput</tags>
     <dependencies>
-      <dependency id="bootstrap" version="3.0.0" />
+      <dependency id="bootstrap" version="3.3.0" />
     </dependencies>
   </metadata>
 </package>

+ 1 - 1
package.json

@@ -7,7 +7,7 @@
     ],
     "description": "An enhanced HTML 5 file input for Bootstrap 3.x with file preview, multiple selection, ajax uploads, and more features.",
     "repository" : {
-        "type": "git",
+        "type": "git", 
         "url": "https://github.com/kartik-v/bootstrap-fileinput.git"
     },
     "bugs": {

Some files were not shown because too many files changed in this diff