diff --git a/app/assets/javascripts/discourse/app/components/image-uploader.js b/app/assets/javascripts/discourse/app/components/image-uploader.js deleted file mode 100644 index 9852497bca5..00000000000 --- a/app/assets/javascripts/discourse/app/components/image-uploader.js +++ /dev/null @@ -1,135 +0,0 @@ -import Component from "@ember/component"; -import deprecated from "discourse-common/lib/deprecated"; -import UploadMixin from "discourse/mixins/upload"; -import { ajax } from "discourse/lib/ajax"; -import discourseComputed from "discourse-common/utils/decorators"; -import { getURLWithCDN } from "discourse-common/lib/get-url"; -import { isEmpty } from "@ember/utils"; -import lightbox from "discourse/lib/lightbox"; -import { next } from "@ember/runloop"; -import { popupAjaxError } from "discourse/lib/ajax-error"; - -export default Component.extend(UploadMixin, { - classNames: ["image-uploader"], - loadingLightbox: false, - - init() { - this._super(...arguments); - // TODO (martin) (2022-01-22) Remove this component. - deprecated( - "image-uploader will be removed in a future version, use uppy-image-uploader instead (the API is the same)" - ); - this._applyLightbox(); - }, - - willDestroyElement() { - this._super(...arguments); - const elem = $("a.lightbox"); - if (elem && typeof elem.magnificPopup === "function") { - $("a.lightbox").magnificPopup("close"); - } - }, - - @discourseComputed("imageUrl", "placeholderUrl") - showingPlaceholder(imageUrl, placeholderUrl) { - return !imageUrl && placeholderUrl; - }, - - @discourseComputed("placeholderUrl") - placeholderStyle(url) { - if (isEmpty(url)) { - return "".htmlSafe(); - } - return `background-image: url(${url})`.htmlSafe(); - }, - - @discourseComputed("imageUrl") - imageCDNURL(url) { - if (isEmpty(url)) { - return "".htmlSafe(); - } - - return getURLWithCDN(url); - }, - - @discourseComputed("imageCDNURL") - backgroundStyle(url) { - return `background-image: url(${url})`.htmlSafe(); - }, - - @discourseComputed("imageUrl") - imageBaseName(imageUrl) { - if (isEmpty(imageUrl)) { - return; - } - return imageUrl.split("/").slice(-1)[0]; - }, - - validateUploadedFilesOptions() { - return { imagesOnly: true }; - }, - - uploadDone(upload) { - this.setProperties({ - imageUrl: upload.url, - imageId: upload.id, - imageFilesize: upload.human_filesize, - imageFilename: upload.original_filename, - imageWidth: upload.width, - imageHeight: upload.height, - }); - - this._applyLightbox(); - - if (this.onUploadDone) { - this.onUploadDone(upload); - } - }, - - _openLightbox() { - next(() => - $(this.element.querySelector("a.lightbox")).magnificPopup("open") - ); - }, - - _applyLightbox() { - if (this.imageUrl) { - next(() => lightbox(this.element, this.siteSettings)); - } - }, - - actions: { - toggleLightbox() { - if (this.imageFilename) { - this._openLightbox(); - } else { - this.set("loadingLightbox", true); - - ajax(`/uploads/lookup-metadata`, { - type: "POST", - data: { url: this.imageUrl }, - }) - .then((json) => { - this.setProperties({ - imageFilename: json.original_filename, - imageFilesize: json.human_filesize, - imageWidth: json.width, - imageHeight: json.height, - }); - - this._openLightbox(); - this.set("loadingLightbox", false); - }) - .catch(popupAjaxError); - } - }, - - trash() { - this.setProperties({ imageUrl: null, imageId: null }); - - if (this.onUploadDeleted) { - this.onUploadDeleted(); - } - }, - }, -}); diff --git a/app/assets/javascripts/discourse/app/mixins/upload.js b/app/assets/javascripts/discourse/app/mixins/upload.js deleted file mode 100644 index 5647471e66f..00000000000 --- a/app/assets/javascripts/discourse/app/mixins/upload.js +++ /dev/null @@ -1,134 +0,0 @@ -import { - displayErrorForUpload, - validateUploadedFiles, -} from "discourse/lib/uploads"; -import I18n from "I18n"; -import Mixin from "@ember/object/mixin"; -import bootbox from "bootbox"; -import { deepMerge } from "discourse-common/lib/object"; -import getUrl from "discourse-common/lib/get-url"; -import { on } from "@ember/object/evented"; -import { warn } from "@ember/debug"; - -export default Mixin.create({ - uploading: false, - uploadProgress: 0, - - uploadDone() { - warn("You should implement `uploadDone`", { - id: "discourse.upload.missing-upload-done", - }); - }, - - validateUploadedFilesOptions() { - return {}; - }, - - calculateUploadUrl() { - return ( - getUrl(this.getWithDefault("uploadUrl", "/uploads")) + - ".json?client_id=" + - (this.messageBus && this.messageBus.clientId) + - this.uploadUrlParams - ); - }, - - uploadUrlParams: "", - - uploadOptions() { - return {}; - }, - - _initialize: on("didInsertElement", function () { - const $upload = $(this.element); - const reset = () => { - this.setProperties({ uploading: false, uploadProgress: 0 }); - document.getElementsByClassName("hidden-upload-field")[0].value = ""; - }; - const maxFiles = this.getWithDefault( - "maxFiles", - this.siteSettings.simultaneous_uploads - ); - - $upload.on("fileuploaddone", (e, data) => { - let upload = data.result; - this.uploadDone(upload); - reset(); - }); - - $upload.fileupload( - deepMerge( - { - url: this.calculateUploadUrl(), - dataType: "json", - replaceFileInput: false, - dropZone: $upload, - pasteZone: $upload, - }, - this.uploadOptions() - ) - ); - - $upload.on("fileuploaddrop", (e, data) => { - if (maxFiles > 0 && data.files.length > maxFiles) { - bootbox.alert( - I18n.t("post.errors.too_many_dragged_and_dropped_files", { - count: maxFiles, - }) - ); - return false; - } else { - return true; - } - }); - - $upload.on("fileuploadsubmit", (e, data) => { - const opts = deepMerge( - { - bypassNewUserRestriction: true, - user: this.currentUser, - siteSettings: this.siteSettings, - }, - this.validateUploadedFilesOptions() - ); - const isValid = validateUploadedFiles(data.files, opts); - const type = this.type; - let form = type ? { type } : {}; - - if (this.data) { - form = Object.assign(form, this.data); - } - - data.formData = form; - this.setProperties({ uploadProgress: 0, uploading: isValid }); - - return isValid; - }); - - $upload.on("fileuploadprogressall", (e, data) => { - if (this.isDestroying || this.isDestroyed) { - return; - } - - const progress = parseInt((data.loaded / data.total) * 100, 10); - this.set("uploadProgress", progress); - }); - - $upload.on("fileuploadfail", (e, data) => { - displayErrorForUpload(data, this.siteSettings, data.files[0].name); - reset(); - }); - }), - - _destroy: on("willDestroyElement", function () { - this.messageBus && this.messageBus.unsubscribe("/uploads/" + this.type); - - const $upload = $(this.element); - try { - $upload.fileupload("destroy"); - } catch (e) { - /* wasn't initialized yet */ - } - $upload.off(); - }), -}); diff --git a/app/assets/javascripts/discourse/app/templates/components/image-uploader.hbs b/app/assets/javascripts/discourse/app/templates/components/image-uploader.hbs deleted file mode 100644 index 04096a287ad..00000000000 --- a/app/assets/javascripts/discourse/app/templates/components/image-uploader.hbs +++ /dev/null @@ -1,45 +0,0 @@ -
- {{#if showingPlaceholder}} -
- {{/if}} -
- - - {{#if imageUrl}} - {{d-button - action=(action "trash") - class="btn-danger pad-left no-text" - icon="far-trash-alt" - type="button" - }} - - {{d-button - icon="discourse-expand" - title="expand" - type="button" - class="image-uploader-lightbox-btn no-text" - action=(action "toggleLightbox") - disabled=loadingLightbox - }} - {{/if}} - - {{i18n "upload_selector.uploading"}} {{uploadProgress}}% -
- - {{#if imageUrl}} - - -
- - {{imageWidth}}x{{imageHeight}} {{imageFilesize}} - -
-
- {{/if}} -
diff --git a/app/assets/javascripts/discourse/ember-cli-build.js b/app/assets/javascripts/discourse/ember-cli-build.js index a643bc1ddf0..d7bd432bdb3 100644 --- a/app/assets/javascripts/discourse/ember-cli-build.js +++ b/app/assets/javascripts/discourse/ember-cli-build.js @@ -42,8 +42,6 @@ module.exports = function (defaults) { app.import(vendorJs + "bootbox.js"); app.import(vendorJs + "bootstrap-modal.js"); app.import(vendorJs + "jquery.ui.widget.js"); - app.import(vendorJs + "jquery.fileupload.js"); - app.import(vendorJs + "jquery.fileupload-process.js"); app.import(vendorJs + "caret_position.js"); app.import("node_modules/ember-source/dist/ember-template-compiler.js", { type: "test", diff --git a/app/assets/javascripts/discourse/tests/integration/components/image-uploader-test.js b/app/assets/javascripts/discourse/tests/integration/components/image-uploader-test.js deleted file mode 100644 index 1ae8c058f09..00000000000 --- a/app/assets/javascripts/discourse/tests/integration/components/image-uploader-test.js +++ /dev/null @@ -1,97 +0,0 @@ -import componentTest, { - setupRenderingTest, -} from "discourse/tests/helpers/component-test"; -import { - count, - discourseModule, - exists, -} from "discourse/tests/helpers/qunit-helpers"; -import { click } from "@ember/test-helpers"; -import hbs from "htmlbars-inline-precompile"; - -discourseModule("Integration | Component | image-uploader", function (hooks) { - setupRenderingTest(hooks); - - componentTest("with image", { - template: hbs` - {{image-uploader imageUrl='/images/avatar.png' placeholderUrl='/not/used.png'}} - `, - - async test(assert) { - assert.strictEqual( - count(".d-icon-far-image"), - 1, - "it displays the upload icon" - ); - - assert.strictEqual( - count(".d-icon-far-trash-alt"), - 1, - "it displays the trash icon" - ); - - assert.ok( - !exists(".placeholder-overlay"), - "it does not display the placeholder image" - ); - - await click(".image-uploader-lightbox-btn"); - - assert.strictEqual( - document.querySelectorAll(".mfp-container").length, - 1, - "it displays the image lightbox" - ); - }, - }); - - componentTest("without image", { - template: hbs`{{image-uploader}}`, - - test(assert) { - assert.strictEqual( - count(".d-icon-far-image"), - 1, - "it displays the upload icon" - ); - - assert.ok( - !exists(".d-icon-far-trash-alt"), - "it does not display trash icon" - ); - - assert.ok( - !exists(".image-uploader-lightbox-btn"), - "it does not display the button to open image lightbox" - ); - }, - }); - - componentTest("with placeholder", { - template: hbs`{{image-uploader placeholderUrl='/images/avatar.png'}}`, - - test(assert) { - assert.strictEqual( - count(".d-icon-far-image"), - 1, - "it displays the upload icon" - ); - - assert.ok( - !exists(".d-icon-far-trash-alt"), - "it does not display trash icon" - ); - - assert.ok( - !exists(".image-uploader-lightbox-btn"), - "it does not display the button to open image lightbox" - ); - - assert.strictEqual( - count(".placeholder-overlay"), - 1, - "it displays the placeholder image" - ); - }, - }); -}); diff --git a/app/assets/javascripts/discourse/tests/theme_qunit_vendor.js b/app/assets/javascripts/discourse/tests/theme_qunit_vendor.js index d6088b6ca87..9eb492b917d 100644 --- a/app/assets/javascripts/discourse/tests/theme_qunit_vendor.js +++ b/app/assets/javascripts/discourse/tests/theme_qunit_vendor.js @@ -18,9 +18,6 @@ //= require popper.js //= require bootstrap-modal.js //= require caret_position -//= require jquery.fileupload.js -//= require jquery.iframe-transport.js -//= require jquery.fileupload-process.js //= require jquery.sortable.js //= require lodash.js //= require itsatrap.js diff --git a/app/assets/javascripts/vendor.js b/app/assets/javascripts/vendor.js index 764c99d1182..fb49d750d58 100644 --- a/app/assets/javascripts/vendor.js +++ b/app/assets/javascripts/vendor.js @@ -11,9 +11,6 @@ //= require popper.js //= require bootstrap-modal.js //= require caret_position -//= require jquery.fileupload.js -//= require jquery.iframe-transport.js -//= require jquery.fileupload-process.js //= require jquery.sortable.js //= require lodash.js //= require itsatrap.js diff --git a/app/assets/javascripts/wizard-vendor.js b/app/assets/javascripts/wizard-vendor.js index 1818614a197..0a72163b2cc 100644 --- a/app/assets/javascripts/wizard-vendor.js +++ b/app/assets/javascripts/wizard-vendor.js @@ -1,7 +1,6 @@ //= require ember_jquery //= require template_include.js //= require jquery.ui.widget.js -//= require jquery.fileupload.js //= require uppy.js //= require bootstrap-modal.js //= require bootbox.js diff --git a/lib/tasks/javascript.rake b/lib/tasks/javascript.rake index d1ecd861e72..cb0aca9bcde 100644 --- a/lib/tasks/javascript.rake +++ b/lib/tasks/javascript.rake @@ -103,14 +103,6 @@ def dependencies }, { source: '@highlightjs/cdn-assets/.', destination: 'highlightjs' - }, { - source: 'blueimp-file-upload/js/jquery.fileupload.js', - }, { - source: 'blueimp-file-upload/js/jquery.iframe-transport.js', - }, { - source: 'blueimp-file-upload/js/jquery.fileupload-process.js', - }, { - source: 'blueimp-file-upload/js/vendor/jquery.ui.widget.js', }, { source: 'jquery/dist/jquery.js' }, { diff --git a/vendor/assets/javascripts/jquery.fileupload-process.js b/vendor/assets/javascripts/jquery.fileupload-process.js deleted file mode 100644 index 29de8309a50..00000000000 --- a/vendor/assets/javascripts/jquery.fileupload-process.js +++ /dev/null @@ -1,169 +0,0 @@ -/* - * jQuery File Upload Processing Plugin - * https://github.com/blueimp/jQuery-File-Upload - * - * Copyright 2012, Sebastian Tschan - * https://blueimp.net - * - * Licensed under the MIT license: - * https://opensource.org/licenses/MIT - */ - -/* global define, require */ - -(function (factory) { - 'use strict'; - if (typeof define === 'function' && define.amd) { - // Register as an anonymous AMD module: - define(['jquery', './jquery.fileupload'], factory); - } else if (typeof exports === 'object') { - // Node/CommonJS: - factory(require('jquery'), require('./jquery.fileupload')); - } else { - // Browser globals: - factory(window.jQuery); - } -})(function ($) { - 'use strict'; - - var originalAdd = $.blueimp.fileupload.prototype.options.add; - - // The File Upload Processing plugin extends the fileupload widget - // with file processing functionality: - $.widget('blueimp.fileupload', $.blueimp.fileupload, { - options: { - // The list of processing actions: - processQueue: [ - /* - { - action: 'log', - type: 'debug' - } - */ - ], - add: function (e, data) { - var $this = $(this); - data.process(function () { - return $this.fileupload('process', data); - }); - originalAdd.call(this, e, data); - } - }, - - processActions: { - /* - log: function (data, options) { - console[options.type]( - 'Processing "' + data.files[data.index].name + '"' - ); - } - */ - }, - - _processFile: function (data, originalData) { - var that = this, - // eslint-disable-next-line new-cap - dfd = $.Deferred().resolveWith(that, [data]), - chain = dfd.promise(); - this._trigger('process', null, data); - $.each(data.processQueue, function (i, settings) { - var func = function (data) { - if (originalData.errorThrown) { - // eslint-disable-next-line new-cap - return $.Deferred().rejectWith(that, [originalData]).promise(); - } - return that.processActions[settings.action].call( - that, - data, - settings - ); - }; - chain = chain.then(func, settings.always && func); - }); - chain - .done(function () { - that._trigger('processdone', null, data); - that._trigger('processalways', null, data); - }) - .fail(function () { - that._trigger('processfail', null, data); - that._trigger('processalways', null, data); - }); - return chain; - }, - - // Replaces the settings of each processQueue item that - // are strings starting with an "@", using the remaining - // substring as key for the option map, - // e.g. "@autoUpload" is replaced with options.autoUpload: - _transformProcessQueue: function (options) { - var processQueue = []; - $.each(options.processQueue, function () { - var settings = {}, - action = this.action, - prefix = this.prefix === true ? action : this.prefix; - $.each(this, function (key, value) { - if ($.type(value) === 'string' && value.charAt(0) === '@') { - settings[key] = - options[ - value.slice(1) || - (prefix - ? prefix + key.charAt(0).toUpperCase() + key.slice(1) - : key) - ]; - } else { - settings[key] = value; - } - }); - processQueue.push(settings); - }); - options.processQueue = processQueue; - }, - - // Returns the number of files currently in the processsing queue: - processing: function () { - return this._processing; - }, - - // Processes the files given as files property of the data parameter, - // returns a Promise object that allows to bind callbacks: - process: function (data) { - var that = this, - options = $.extend({}, this.options, data); - if (options.processQueue && options.processQueue.length) { - this._transformProcessQueue(options); - if (this._processing === 0) { - this._trigger('processstart'); - } - $.each(data.files, function (index) { - var opts = index ? $.extend({}, options) : options, - func = function () { - if (data.errorThrown) { - // eslint-disable-next-line new-cap - return $.Deferred().rejectWith(that, [data]).promise(); - } - return that._processFile(opts, data); - }; - opts.index = index; - that._processing += 1; - that._processingQueue = that._processingQueue - .then(func, func) - .always(function () { - that._processing -= 1; - if (that._processing === 0) { - that._trigger('processstop'); - } - }); - }); - } - return this._processingQueue; - }, - - _create: function () { - this._super(); - this._processing = 0; - // eslint-disable-next-line new-cap - this._processingQueue = $.Deferred().resolveWith(this).promise(); - } - }); -}); diff --git a/vendor/assets/javascripts/jquery.fileupload.js b/vendor/assets/javascripts/jquery.fileupload.js deleted file mode 100644 index f75cf2b10aa..00000000000 --- a/vendor/assets/javascripts/jquery.fileupload.js +++ /dev/null @@ -1,1597 +0,0 @@ -/* - * jQuery File Upload Plugin - * https://github.com/blueimp/jQuery-File-Upload - * - * Copyright 2010, Sebastian Tschan - * https://blueimp.net - * - * Licensed under the MIT license: - * https://opensource.org/licenses/MIT - */ - -/* global define, require */ -/* eslint-disable new-cap */ - -(function (factory) { - 'use strict'; - if (typeof define === 'function' && define.amd) { - // Register as an anonymous AMD module: - define(['jquery', 'jquery-ui/ui/widget'], factory); - } else if (typeof exports === 'object') { - // Node/CommonJS: - factory(require('jquery'), require('./vendor/jquery.ui.widget')); - } else { - // Browser globals: - factory(window.jQuery); - } -})(function ($) { - 'use strict'; - - // Detect file input support, based on - // https://viljamis.com/2012/file-upload-support-on-mobile/ - $.support.fileInput = !( - new RegExp( - // Handle devices which give false positives for the feature detection: - '(Android (1\\.[0156]|2\\.[01]))' + - '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' + - '|(w(eb)?OSBrowser)|(webOS)' + - '|(Kindle/(1\\.0|2\\.[05]|3\\.0))' - ).test(window.navigator.userAgent) || - // Feature detection for all other devices: - $('').prop('disabled') - ); - - // The FileReader API is not actually used, but works as feature detection, - // as some Safari versions (5?) support XHR file uploads via the FormData API, - // but not non-multipart XHR file uploads. - // window.XMLHttpRequestUpload is not available on IE10, so we check for - // window.ProgressEvent instead to detect XHR2 file upload capability: - $.support.xhrFileUpload = !!(window.ProgressEvent && window.FileReader); - $.support.xhrFormDataFileUpload = !!window.FormData; - - // Detect support for Blob slicing (required for chunked uploads): - $.support.blobSlice = - window.Blob && - (Blob.prototype.slice || - Blob.prototype.webkitSlice || - Blob.prototype.mozSlice); - - /** - * Helper function to create drag handlers for dragover/dragenter/dragleave - * - * @param {string} type Event type - * @returns {Function} Drag handler - */ - function getDragHandler(type) { - var isDragOver = type === 'dragover'; - return function (e) { - e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer; - var dataTransfer = e.dataTransfer; - if ( - dataTransfer && - $.inArray('Files', dataTransfer.types) !== -1 && - this._trigger(type, $.Event(type, { delegatedEvent: e })) !== false - ) { - e.preventDefault(); - if (isDragOver) { - dataTransfer.dropEffect = 'copy'; - } - } - }; - } - - // The fileupload widget listens for change events on file input fields defined - // via fileInput setting and paste or drop events of the given dropZone. - // In addition to the default jQuery Widget methods, the fileupload widget - // exposes the "add" and "send" methods, to add or directly send files using - // the fileupload API. - // By default, files added via file input selection, paste, drag & drop or - // "add" method are uploaded immediately, but it is possible to override - // the "add" callback option to queue file uploads. - $.widget('blueimp.fileupload', { - options: { - // The drop target element(s), by the default the complete document. - // Set to null to disable drag & drop support: - dropZone: $(document), - // The paste target element(s), by the default undefined. - // Set to a DOM node or jQuery object to enable file pasting: - pasteZone: undefined, - // The file input field(s), that are listened to for change events. - // If undefined, it is set to the file input fields inside - // of the widget element on plugin initialization. - // Set to null to disable the change listener. - fileInput: undefined, - // By default, the file input field is replaced with a clone after - // each input field change event. This is required for iframe transport - // queues and allows change events to be fired for the same file - // selection, but can be disabled by setting the following option to false: - replaceFileInput: true, - // The parameter name for the file form data (the request argument name). - // If undefined or empty, the name property of the file input field is - // used, or "files[]" if the file input name property is also empty, - // can be a string or an array of strings: - paramName: undefined, - // By default, each file of a selection is uploaded using an individual - // request for XHR type uploads. Set to false to upload file - // selections in one request each: - singleFileUploads: true, - // To limit the number of files uploaded with one XHR request, - // set the following option to an integer greater than 0: - limitMultiFileUploads: undefined, - // The following option limits the number of files uploaded with one - // XHR request to keep the request size under or equal to the defined - // limit in bytes: - limitMultiFileUploadSize: undefined, - // Multipart file uploads add a number of bytes to each uploaded file, - // therefore the following option adds an overhead for each file used - // in the limitMultiFileUploadSize configuration: - limitMultiFileUploadSizeOverhead: 512, - // Set the following option to true to issue all file upload requests - // in a sequential order: - sequentialUploads: false, - // To limit the number of concurrent uploads, - // set the following option to an integer greater than 0: - limitConcurrentUploads: undefined, - // Set the following option to true to force iframe transport uploads: - forceIframeTransport: false, - // Set the following option to the location of a redirect url on the - // origin server, for cross-domain iframe transport uploads: - redirect: undefined, - // The parameter name for the redirect url, sent as part of the form - // data and set to 'redirect' if this option is empty: - redirectParamName: undefined, - // Set the following option to the location of a postMessage window, - // to enable postMessage transport uploads: - postMessage: undefined, - // By default, XHR file uploads are sent as multipart/form-data. - // The iframe transport is always using multipart/form-data. - // Set to false to enable non-multipart XHR uploads: - multipart: true, - // To upload large files in smaller chunks, set the following option - // to a preferred maximum chunk size. If set to 0, null or undefined, - // or the browser does not support the required Blob API, files will - // be uploaded as a whole. - maxChunkSize: undefined, - // When a non-multipart upload or a chunked multipart upload has been - // aborted, this option can be used to resume the upload by setting - // it to the size of the already uploaded bytes. This option is most - // useful when modifying the options object inside of the "add" or - // "send" callbacks, as the options are cloned for each file upload. - uploadedBytes: undefined, - // By default, failed (abort or error) file uploads are removed from the - // global progress calculation. Set the following option to false to - // prevent recalculating the global progress data: - recalculateProgress: true, - // Interval in milliseconds to calculate and trigger progress events: - progressInterval: 100, - // Interval in milliseconds to calculate progress bitrate: - bitrateInterval: 500, - // By default, uploads are started automatically when adding files: - autoUpload: true, - // By default, duplicate file names are expected to be handled on - // the server-side. If this is not possible (e.g. when uploading - // files directly to Amazon S3), the following option can be set to - // an empty object or an object mapping existing filenames, e.g.: - // { "image.jpg": true, "image (1).jpg": true } - // If it is set, all files will be uploaded with unique filenames, - // adding increasing number suffixes if necessary, e.g.: - // "image (2).jpg" - uniqueFilenames: undefined, - - // Error and info messages: - messages: { - uploadedBytes: 'Uploaded bytes exceed file size' - }, - - // Translation function, gets the message key to be translated - // and an object with context specific data as arguments: - i18n: function (message, context) { - // eslint-disable-next-line no-param-reassign - message = this.messages[message] || message.toString(); - if (context) { - $.each(context, function (key, value) { - // eslint-disable-next-line no-param-reassign - message = message.replace('{' + key + '}', value); - }); - } - return message; - }, - - // Additional form data to be sent along with the file uploads can be set - // using this option, which accepts an array of objects with name and - // value properties, a function returning such an array, a FormData - // object (for XHR file uploads), or a simple object. - // The form of the first fileInput is given as parameter to the function: - formData: function (form) { - return form.serializeArray(); - }, - - // The add callback is invoked as soon as files are added to the fileupload - // widget (via file input selection, drag & drop, paste or add API call). - // If the singleFileUploads option is enabled, this callback will be - // called once for each file in the selection for XHR file uploads, else - // once for each file selection. - // - // The upload starts when the submit method is invoked on the data parameter. - // The data object contains a files property holding the added files - // and allows you to override plugin options as well as define ajax settings. - // - // Listeners for this callback can also be bound the following way: - // .on('fileuploadadd', func); - // - // data.submit() returns a Promise object and allows to attach additional - // handlers using jQuery's Deferred callbacks: - // data.submit().done(func).fail(func).always(func); - add: function (e, data) { - if (e.isDefaultPrevented()) { - return false; - } - if ( - data.autoUpload || - (data.autoUpload !== false && - $(this).fileupload('option', 'autoUpload')) - ) { - data.process().done(function () { - data.submit(); - }); - } - }, - - // Other callbacks: - - // Callback for the submit event of each file upload: - // submit: function (e, data) {}, // .on('fileuploadsubmit', func); - - // Callback for the start of each file upload request: - // send: function (e, data) {}, // .on('fileuploadsend', func); - - // Callback for successful uploads: - // done: function (e, data) {}, // .on('fileuploaddone', func); - - // Callback for failed (abort or error) uploads: - // fail: function (e, data) {}, // .on('fileuploadfail', func); - - // Callback for completed (success, abort or error) requests: - // always: function (e, data) {}, // .on('fileuploadalways', func); - - // Callback for upload progress events: - // progress: function (e, data) {}, // .on('fileuploadprogress', func); - - // Callback for global upload progress events: - // progressall: function (e, data) {}, // .on('fileuploadprogressall', func); - - // Callback for uploads start, equivalent to the global ajaxStart event: - // start: function (e) {}, // .on('fileuploadstart', func); - - // Callback for uploads stop, equivalent to the global ajaxStop event: - // stop: function (e) {}, // .on('fileuploadstop', func); - - // Callback for change events of the fileInput(s): - // change: function (e, data) {}, // .on('fileuploadchange', func); - - // Callback for paste events to the pasteZone(s): - // paste: function (e, data) {}, // .on('fileuploadpaste', func); - - // Callback for drop events of the dropZone(s): - // drop: function (e, data) {}, // .on('fileuploaddrop', func); - - // Callback for dragover events of the dropZone(s): - // dragover: function (e) {}, // .on('fileuploaddragover', func); - - // Callback before the start of each chunk upload request (before form data initialization): - // chunkbeforesend: function (e, data) {}, // .on('fileuploadchunkbeforesend', func); - - // Callback for the start of each chunk upload request: - // chunksend: function (e, data) {}, // .on('fileuploadchunksend', func); - - // Callback for successful chunk uploads: - // chunkdone: function (e, data) {}, // .on('fileuploadchunkdone', func); - - // Callback for failed (abort or error) chunk uploads: - // chunkfail: function (e, data) {}, // .on('fileuploadchunkfail', func); - - // Callback for completed (success, abort or error) chunk upload requests: - // chunkalways: function (e, data) {}, // .on('fileuploadchunkalways', func); - - // The plugin options are used as settings object for the ajax calls. - // The following are jQuery ajax settings required for the file uploads: - processData: false, - contentType: false, - cache: false, - timeout: 0 - }, - - // A list of options that require reinitializing event listeners and/or - // special initialization code: - _specialOptions: [ - 'fileInput', - 'dropZone', - 'pasteZone', - 'multipart', - 'forceIframeTransport' - ], - - _blobSlice: - $.support.blobSlice && - function () { - var slice = this.slice || this.webkitSlice || this.mozSlice; - return slice.apply(this, arguments); - }, - - _BitrateTimer: function () { - this.timestamp = Date.now ? Date.now() : new Date().getTime(); - this.loaded = 0; - this.bitrate = 0; - this.getBitrate = function (now, loaded, interval) { - var timeDiff = now - this.timestamp; - if (!this.bitrate || !interval || timeDiff > interval) { - this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8; - this.loaded = loaded; - this.timestamp = now; - } - return this.bitrate; - }; - }, - - _isXHRUpload: function (options) { - return ( - !options.forceIframeTransport && - ((!options.multipart && $.support.xhrFileUpload) || - $.support.xhrFormDataFileUpload) - ); - }, - - _getFormData: function (options) { - var formData; - if ($.type(options.formData) === 'function') { - return options.formData(options.form); - } - if ($.isArray(options.formData)) { - return options.formData; - } - if ($.type(options.formData) === 'object') { - formData = []; - $.each(options.formData, function (name, value) { - formData.push({ name: name, value: value }); - }); - return formData; - } - return []; - }, - - _getTotal: function (files) { - var total = 0; - $.each(files, function (index, file) { - total += file.size || 1; - }); - return total; - }, - - _initProgressObject: function (obj) { - var progress = { - loaded: 0, - total: 0, - bitrate: 0 - }; - if (obj._progress) { - $.extend(obj._progress, progress); - } else { - obj._progress = progress; - } - }, - - _initResponseObject: function (obj) { - var prop; - if (obj._response) { - for (prop in obj._response) { - if (Object.prototype.hasOwnProperty.call(obj._response, prop)) { - delete obj._response[prop]; - } - } - } else { - obj._response = {}; - } - }, - - _onProgress: function (e, data) { - if (e.lengthComputable) { - var now = Date.now ? Date.now() : new Date().getTime(), - loaded; - if ( - data._time && - data.progressInterval && - now - data._time < data.progressInterval && - e.loaded !== e.total - ) { - return; - } - data._time = now; - loaded = - Math.floor( - (e.loaded / e.total) * (data.chunkSize || data._progress.total) - ) + (data.uploadedBytes || 0); - // Add the difference from the previously loaded state - // to the global loaded counter: - this._progress.loaded += loaded - data._progress.loaded; - this._progress.bitrate = this._bitrateTimer.getBitrate( - now, - this._progress.loaded, - data.bitrateInterval - ); - data._progress.loaded = data.loaded = loaded; - data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate( - now, - loaded, - data.bitrateInterval - ); - // Trigger a custom progress event with a total data property set - // to the file size(s) of the current upload and a loaded data - // property calculated accordingly: - this._trigger( - 'progress', - $.Event('progress', { delegatedEvent: e }), - data - ); - // Trigger a global progress event for all current file uploads, - // including ajax calls queued for sequential file uploads: - this._trigger( - 'progressall', - $.Event('progressall', { delegatedEvent: e }), - this._progress - ); - } - }, - - _initProgressListener: function (options) { - var that = this, - xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr(); - // Accesss to the native XHR object is required to add event listeners - // for the upload progress event: - if (xhr.upload) { - $(xhr.upload).on('progress', function (e) { - var oe = e.originalEvent; - // Make sure the progress event properties get copied over: - e.lengthComputable = oe.lengthComputable; - e.loaded = oe.loaded; - e.total = oe.total; - that._onProgress(e, options); - }); - options.xhr = function () { - return xhr; - }; - } - }, - - _deinitProgressListener: function (options) { - var xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr(); - if (xhr.upload) { - $(xhr.upload).off('progress'); - } - }, - - _isInstanceOf: function (type, obj) { - // Cross-frame instanceof check - return Object.prototype.toString.call(obj) === '[object ' + type + ']'; - }, - - _getUniqueFilename: function (name, map) { - // eslint-disable-next-line no-param-reassign - name = String(name); - if (map[name]) { - // eslint-disable-next-line no-param-reassign - name = name.replace(/(?: \(([\d]+)\))?(\.[^.]+)?$/, function ( - _, - p1, - p2 - ) { - var index = p1 ? Number(p1) + 1 : 1; - var ext = p2 || ''; - return ' (' + index + ')' + ext; - }); - return this._getUniqueFilename(name, map); - } - map[name] = true; - return name; - }, - - _initXHRData: function (options) { - var that = this, - formData, - file = options.files[0], - // Ignore non-multipart setting if not supported: - multipart = options.multipart || !$.support.xhrFileUpload, - paramName = - $.type(options.paramName) === 'array' - ? options.paramName[0] - : options.paramName; - options.headers = $.extend({}, options.headers); - if (options.contentRange) { - options.headers['Content-Range'] = options.contentRange; - } - if (!multipart || options.blob || !this._isInstanceOf('File', file)) { - options.headers['Content-Disposition'] = - 'attachment; filename="' + - encodeURI(file.uploadName || file.name) + - '"'; - } - if (!multipart) { - options.contentType = file.type || 'application/octet-stream'; - options.data = options.blob || file; - } else if ($.support.xhrFormDataFileUpload) { - if (options.postMessage) { - // window.postMessage does not allow sending FormData - // objects, so we just add the File/Blob objects to - // the formData array and let the postMessage window - // create the FormData object out of this array: - formData = this._getFormData(options); - if (options.blob) { - formData.push({ - name: paramName, - value: options.blob - }); - } else { - $.each(options.files, function (index, file) { - formData.push({ - name: - ($.type(options.paramName) === 'array' && - options.paramName[index]) || - paramName, - value: file - }); - }); - } - } else { - if (that._isInstanceOf('FormData', options.formData)) { - formData = options.formData; - } else { - formData = new FormData(); - $.each(this._getFormData(options), function (index, field) { - formData.append(field.name, field.value); - }); - } - if (options.blob) { - formData.append( - paramName, - options.blob, - file.uploadName || file.name - ); - } else { - $.each(options.files, function (index, file) { - // This check allows the tests to run with - // dummy objects: - if ( - that._isInstanceOf('File', file) || - that._isInstanceOf('Blob', file) - ) { - var fileName = file.uploadName || file.name; - if (options.uniqueFilenames) { - fileName = that._getUniqueFilename( - fileName, - options.uniqueFilenames - ); - } - formData.append( - ($.type(options.paramName) === 'array' && - options.paramName[index]) || - paramName, - file, - fileName - ); - } - }); - } - } - options.data = formData; - } - // Blob reference is not needed anymore, free memory: - options.blob = null; - }, - - _initIframeSettings: function (options) { - var targetHost = $('').prop('href', options.url).prop('host'); - // Setting the dataType to iframe enables the iframe transport: - options.dataType = 'iframe ' + (options.dataType || ''); - // The iframe transport accepts a serialized array as form data: - options.formData = this._getFormData(options); - // Add redirect url to form data on cross-domain uploads: - if (options.redirect && targetHost && targetHost !== location.host) { - options.formData.push({ - name: options.redirectParamName || 'redirect', - value: options.redirect - }); - } - }, - - _initDataSettings: function (options) { - if (this._isXHRUpload(options)) { - if (!this._chunkedUpload(options, true)) { - if (!options.data) { - this._initXHRData(options); - } - this._initProgressListener(options); - } - if (options.postMessage) { - // Setting the dataType to postmessage enables the - // postMessage transport: - options.dataType = 'postmessage ' + (options.dataType || ''); - } - } else { - this._initIframeSettings(options); - } - }, - - _getParamName: function (options) { - var fileInput = $(options.fileInput), - paramName = options.paramName; - if (!paramName) { - paramName = []; - fileInput.each(function () { - var input = $(this), - name = input.prop('name') || 'files[]', - i = (input.prop('files') || [1]).length; - while (i) { - paramName.push(name); - i -= 1; - } - }); - if (!paramName.length) { - paramName = [fileInput.prop('name') || 'files[]']; - } - } else if (!$.isArray(paramName)) { - paramName = [paramName]; - } - return paramName; - }, - - _initFormSettings: function (options) { - // Retrieve missing options from the input field and the - // associated form, if available: - if (!options.form || !options.form.length) { - options.form = $(options.fileInput.prop('form')); - // If the given file input doesn't have an associated form, - // use the default widget file input's form: - if (!options.form.length) { - options.form = $(this.options.fileInput.prop('form')); - } - } - options.paramName = this._getParamName(options); - if (!options.url) { - options.url = options.form.prop('action') || location.href; - } - // The HTTP request method must be "POST" or "PUT": - options.type = ( - options.type || - ($.type(options.form.prop('method')) === 'string' && - options.form.prop('method')) || - '' - ).toUpperCase(); - if ( - options.type !== 'POST' && - options.type !== 'PUT' && - options.type !== 'PATCH' - ) { - options.type = 'POST'; - } - if (!options.formAcceptCharset) { - options.formAcceptCharset = options.form.attr('accept-charset'); - } - }, - - _getAJAXSettings: function (data) { - var options = $.extend({}, this.options, data); - this._initFormSettings(options); - this._initDataSettings(options); - return options; - }, - - // jQuery 1.6 doesn't provide .state(), - // while jQuery 1.8+ removed .isRejected() and .isResolved(): - _getDeferredState: function (deferred) { - if (deferred.state) { - return deferred.state(); - } - if (deferred.isResolved()) { - return 'resolved'; - } - if (deferred.isRejected()) { - return 'rejected'; - } - return 'pending'; - }, - - // Maps jqXHR callbacks to the equivalent - // methods of the given Promise object: - _enhancePromise: function (promise) { - promise.success = promise.done; - promise.error = promise.fail; - promise.complete = promise.always; - return promise; - }, - - // Creates and returns a Promise object enhanced with - // the jqXHR methods abort, success, error and complete: - _getXHRPromise: function (resolveOrReject, context, args) { - var dfd = $.Deferred(), - promise = dfd.promise(); - // eslint-disable-next-line no-param-reassign - context = context || this.options.context || promise; - if (resolveOrReject === true) { - dfd.resolveWith(context, args); - } else if (resolveOrReject === false) { - dfd.rejectWith(context, args); - } - promise.abort = dfd.promise; - return this._enhancePromise(promise); - }, - - // Adds convenience methods to the data callback argument: - _addConvenienceMethods: function (e, data) { - var that = this, - getPromise = function (args) { - return $.Deferred().resolveWith(that, args).promise(); - }; - data.process = function (resolveFunc, rejectFunc) { - if (resolveFunc || rejectFunc) { - data._processQueue = this._processQueue = ( - this._processQueue || getPromise([this]) - ) - .then(function () { - if (data.errorThrown) { - return $.Deferred().rejectWith(that, [data]).promise(); - } - return getPromise(arguments); - }) - .then(resolveFunc, rejectFunc); - } - return this._processQueue || getPromise([this]); - }; - data.submit = function () { - if (this.state() !== 'pending') { - data.jqXHR = this.jqXHR = - that._trigger( - 'submit', - $.Event('submit', { delegatedEvent: e }), - this - ) !== false && that._onSend(e, this); - } - return this.jqXHR || that._getXHRPromise(); - }; - data.abort = function () { - if (this.jqXHR) { - return this.jqXHR.abort(); - } - this.errorThrown = 'abort'; - that._trigger('fail', null, this); - return that._getXHRPromise(false); - }; - data.state = function () { - if (this.jqXHR) { - return that._getDeferredState(this.jqXHR); - } - if (this._processQueue) { - return that._getDeferredState(this._processQueue); - } - }; - data.processing = function () { - return ( - !this.jqXHR && - this._processQueue && - that._getDeferredState(this._processQueue) === 'pending' - ); - }; - data.progress = function () { - return this._progress; - }; - data.response = function () { - return this._response; - }; - }, - - // Parses the Range header from the server response - // and returns the uploaded bytes: - _getUploadedBytes: function (jqXHR) { - var range = jqXHR.getResponseHeader('Range'), - parts = range && range.split('-'), - upperBytesPos = parts && parts.length > 1 && parseInt(parts[1], 10); - return upperBytesPos && upperBytesPos + 1; - }, - - // Uploads a file in multiple, sequential requests - // by splitting the file up in multiple blob chunks. - // If the second parameter is true, only tests if the file - // should be uploaded in chunks, but does not invoke any - // upload requests: - _chunkedUpload: function (options, testOnly) { - options.uploadedBytes = options.uploadedBytes || 0; - var that = this, - file = options.files[0], - fs = file.size, - ub = options.uploadedBytes, - mcs = options.maxChunkSize || fs, - slice = this._blobSlice, - dfd = $.Deferred(), - promise = dfd.promise(), - jqXHR, - upload; - if ( - !( - this._isXHRUpload(options) && - slice && - (ub || ($.type(mcs) === 'function' ? mcs(options) : mcs) < fs) - ) || - options.data - ) { - return false; - } - if (testOnly) { - return true; - } - if (ub >= fs) { - file.error = options.i18n('uploadedBytes'); - return this._getXHRPromise(false, options.context, [ - null, - 'error', - file.error - ]); - } - // The chunk upload method: - upload = function () { - // Clone the options object for each chunk upload: - var o = $.extend({}, options), - currentLoaded = o._progress.loaded; - o.blob = slice.call( - file, - ub, - ub + ($.type(mcs) === 'function' ? mcs(o) : mcs), - file.type - ); - // Store the current chunk size, as the blob itself - // will be dereferenced after data processing: - o.chunkSize = o.blob.size; - // Expose the chunk bytes position range: - o.contentRange = - 'bytes ' + ub + '-' + (ub + o.chunkSize - 1) + '/' + fs; - // Trigger chunkbeforesend to allow form data to be updated for this chunk - that._trigger('chunkbeforesend', null, o); - // Process the upload data (the blob and potential form data): - that._initXHRData(o); - // Add progress listeners for this chunk upload: - that._initProgressListener(o); - jqXHR = ( - (that._trigger('chunksend', null, o) !== false && $.ajax(o)) || - that._getXHRPromise(false, o.context) - ) - .done(function (result, textStatus, jqXHR) { - ub = that._getUploadedBytes(jqXHR) || ub + o.chunkSize; - // Create a progress event if no final progress event - // with loaded equaling total has been triggered - // for this chunk: - if (currentLoaded + o.chunkSize - o._progress.loaded) { - that._onProgress( - $.Event('progress', { - lengthComputable: true, - loaded: ub - o.uploadedBytes, - total: ub - o.uploadedBytes - }), - o - ); - } - options.uploadedBytes = o.uploadedBytes = ub; - o.result = result; - o.textStatus = textStatus; - o.jqXHR = jqXHR; - that._trigger('chunkdone', null, o); - that._trigger('chunkalways', null, o); - if (ub < fs) { - // File upload not yet complete, - // continue with the next chunk: - upload(); - } else { - dfd.resolveWith(o.context, [result, textStatus, jqXHR]); - } - }) - .fail(function (jqXHR, textStatus, errorThrown) { - o.jqXHR = jqXHR; - o.textStatus = textStatus; - o.errorThrown = errorThrown; - that._trigger('chunkfail', null, o); - that._trigger('chunkalways', null, o); - dfd.rejectWith(o.context, [jqXHR, textStatus, errorThrown]); - }) - .always(function () { - that._deinitProgressListener(o); - }); - }; - this._enhancePromise(promise); - promise.abort = function () { - return jqXHR.abort(); - }; - upload(); - return promise; - }, - - _beforeSend: function (e, data) { - if (this._active === 0) { - // the start callback is triggered when an upload starts - // and no other uploads are currently running, - // equivalent to the global ajaxStart event: - this._trigger('start'); - // Set timer for global bitrate progress calculation: - this._bitrateTimer = new this._BitrateTimer(); - // Reset the global progress values: - this._progress.loaded = this._progress.total = 0; - this._progress.bitrate = 0; - } - // Make sure the container objects for the .response() and - // .progress() methods on the data object are available - // and reset to their initial state: - this._initResponseObject(data); - this._initProgressObject(data); - data._progress.loaded = data.loaded = data.uploadedBytes || 0; - data._progress.total = data.total = this._getTotal(data.files) || 1; - data._progress.bitrate = data.bitrate = 0; - this._active += 1; - // Initialize the global progress values: - this._progress.loaded += data.loaded; - this._progress.total += data.total; - }, - - _onDone: function (result, textStatus, jqXHR, options) { - var total = options._progress.total, - response = options._response; - if (options._progress.loaded < total) { - // Create a progress event if no final progress event - // with loaded equaling total has been triggered: - this._onProgress( - $.Event('progress', { - lengthComputable: true, - loaded: total, - total: total - }), - options - ); - } - response.result = options.result = result; - response.textStatus = options.textStatus = textStatus; - response.jqXHR = options.jqXHR = jqXHR; - this._trigger('done', null, options); - }, - - _onFail: function (jqXHR, textStatus, errorThrown, options) { - var response = options._response; - if (options.recalculateProgress) { - // Remove the failed (error or abort) file upload from - // the global progress calculation: - this._progress.loaded -= options._progress.loaded; - this._progress.total -= options._progress.total; - } - response.jqXHR = options.jqXHR = jqXHR; - response.textStatus = options.textStatus = textStatus; - response.errorThrown = options.errorThrown = errorThrown; - this._trigger('fail', null, options); - }, - - _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) { - // jqXHRorResult, textStatus and jqXHRorError are added to the - // options object via done and fail callbacks - this._trigger('always', null, options); - }, - - _onSend: function (e, data) { - if (!data.submit) { - this._addConvenienceMethods(e, data); - } - var that = this, - jqXHR, - aborted, - slot, - pipe, - options = that._getAJAXSettings(data), - send = function () { - that._sending += 1; - // Set timer for bitrate progress calculation: - options._bitrateTimer = new that._BitrateTimer(); - jqXHR = - jqXHR || - ( - ((aborted || - that._trigger( - 'send', - $.Event('send', { delegatedEvent: e }), - options - ) === false) && - that._getXHRPromise(false, options.context, aborted)) || - that._chunkedUpload(options) || - $.ajax(options) - ) - .done(function (result, textStatus, jqXHR) { - that._onDone(result, textStatus, jqXHR, options); - }) - .fail(function (jqXHR, textStatus, errorThrown) { - that._onFail(jqXHR, textStatus, errorThrown, options); - }) - .always(function (jqXHRorResult, textStatus, jqXHRorError) { - that._deinitProgressListener(options); - that._onAlways( - jqXHRorResult, - textStatus, - jqXHRorError, - options - ); - that._sending -= 1; - that._active -= 1; - if ( - options.limitConcurrentUploads && - options.limitConcurrentUploads > that._sending - ) { - // Start the next queued upload, - // that has not been aborted: - var nextSlot = that._slots.shift(); - while (nextSlot) { - if (that._getDeferredState(nextSlot) === 'pending') { - nextSlot.resolve(); - break; - } - nextSlot = that._slots.shift(); - } - } - if (that._active === 0) { - // The stop callback is triggered when all uploads have - // been completed, equivalent to the global ajaxStop event: - that._trigger('stop'); - } - }); - return jqXHR; - }; - this._beforeSend(e, options); - if ( - this.options.sequentialUploads || - (this.options.limitConcurrentUploads && - this.options.limitConcurrentUploads <= this._sending) - ) { - if (this.options.limitConcurrentUploads > 1) { - slot = $.Deferred(); - this._slots.push(slot); - pipe = slot.then(send); - } else { - this._sequence = this._sequence.then(send, send); - pipe = this._sequence; - } - // Return the piped Promise object, enhanced with an abort method, - // which is delegated to the jqXHR object of the current upload, - // and jqXHR callbacks mapped to the equivalent Promise methods: - pipe.abort = function () { - aborted = [undefined, 'abort', 'abort']; - if (!jqXHR) { - if (slot) { - slot.rejectWith(options.context, aborted); - } - return send(); - } - return jqXHR.abort(); - }; - return this._enhancePromise(pipe); - } - return send(); - }, - - _onAdd: function (e, data) { - var that = this, - result = true, - options = $.extend({}, this.options, data), - files = data.files, - filesLength = files.length, - limit = options.limitMultiFileUploads, - limitSize = options.limitMultiFileUploadSize, - overhead = options.limitMultiFileUploadSizeOverhead, - batchSize = 0, - paramName = this._getParamName(options), - paramNameSet, - paramNameSlice, - fileSet, - i, - j = 0; - if (!filesLength) { - return false; - } - if (limitSize && files[0].size === undefined) { - limitSize = undefined; - } - if ( - !(options.singleFileUploads || limit || limitSize) || - !this._isXHRUpload(options) - ) { - fileSet = [files]; - paramNameSet = [paramName]; - } else if (!(options.singleFileUploads || limitSize) && limit) { - fileSet = []; - paramNameSet = []; - for (i = 0; i < filesLength; i += limit) { - fileSet.push(files.slice(i, i + limit)); - paramNameSlice = paramName.slice(i, i + limit); - if (!paramNameSlice.length) { - paramNameSlice = paramName; - } - paramNameSet.push(paramNameSlice); - } - } else if (!options.singleFileUploads && limitSize) { - fileSet = []; - paramNameSet = []; - for (i = 0; i < filesLength; i = i + 1) { - batchSize += files[i].size + overhead; - if ( - i + 1 === filesLength || - batchSize + files[i + 1].size + overhead > limitSize || - (limit && i + 1 - j >= limit) - ) { - fileSet.push(files.slice(j, i + 1)); - paramNameSlice = paramName.slice(j, i + 1); - if (!paramNameSlice.length) { - paramNameSlice = paramName; - } - paramNameSet.push(paramNameSlice); - j = i + 1; - batchSize = 0; - } - } - } else { - paramNameSet = paramName; - } - data.originalFiles = files; - $.each(fileSet || files, function (index, element) { - var newData = $.extend({}, data); - newData.files = fileSet ? element : [element]; - newData.paramName = paramNameSet[index]; - that._initResponseObject(newData); - that._initProgressObject(newData); - that._addConvenienceMethods(e, newData); - result = that._trigger( - 'add', - $.Event('add', { delegatedEvent: e }), - newData - ); - return result; - }); - return result; - }, - - _replaceFileInput: function (data) { - var input = data.fileInput, - inputClone = input.clone(true), - restoreFocus = input.is(document.activeElement); - // Add a reference for the new cloned file input to the data argument: - data.fileInputClone = inputClone; - $('
').append(inputClone)[0].reset(); - // Detaching allows to insert the fileInput on another form - // without loosing the file input value: - input.after(inputClone).detach(); - // If the fileInput had focus before it was detached, - // restore focus to the inputClone. - if (restoreFocus) { - inputClone.focus(); - } - // Avoid memory leaks with the detached file input: - $.cleanData(input.off('remove')); - // Replace the original file input element in the fileInput - // elements set with the clone, which has been copied including - // event handlers: - this.options.fileInput = this.options.fileInput.map(function (i, el) { - if (el === input[0]) { - return inputClone[0]; - } - return el; - }); - // If the widget has been initialized on the file input itself, - // override this.element with the file input clone: - if (input[0] === this.element[0]) { - this.element = inputClone; - } - }, - - _handleFileTreeEntry: function (entry, path) { - var that = this, - dfd = $.Deferred(), - entries = [], - dirReader, - errorHandler = function (e) { - if (e && !e.entry) { - e.entry = entry; - } - // Since $.when returns immediately if one - // Deferred is rejected, we use resolve instead. - // This allows valid files and invalid items - // to be returned together in one set: - dfd.resolve([e]); - }, - successHandler = function (entries) { - that - ._handleFileTreeEntries(entries, path + entry.name + '/') - .done(function (files) { - dfd.resolve(files); - }) - .fail(errorHandler); - }, - readEntries = function () { - dirReader.readEntries(function (results) { - if (!results.length) { - successHandler(entries); - } else { - entries = entries.concat(results); - readEntries(); - } - }, errorHandler); - }; - // eslint-disable-next-line no-param-reassign - path = path || ''; - if (entry.isFile) { - if (entry._file) { - // Workaround for Chrome bug #149735 - entry._file.relativePath = path; - dfd.resolve(entry._file); - } else { - entry.file(function (file) { - file.relativePath = path; - dfd.resolve(file); - }, errorHandler); - } - } else if (entry.isDirectory) { - dirReader = entry.createReader(); - readEntries(); - } else { - // Return an empty list for file system items - // other than files or directories: - dfd.resolve([]); - } - return dfd.promise(); - }, - - _handleFileTreeEntries: function (entries, path) { - var that = this; - return $.when - .apply( - $, - $.map(entries, function (entry) { - return that._handleFileTreeEntry(entry, path); - }) - ) - .then(function () { - return Array.prototype.concat.apply([], arguments); - }); - }, - - _getDroppedFiles: function (dataTransfer) { - // eslint-disable-next-line no-param-reassign - dataTransfer = dataTransfer || {}; - var items = dataTransfer.items; - if ( - items && - items.length && - (items[0].webkitGetAsEntry || items[0].getAsEntry) - ) { - return this._handleFileTreeEntries( - $.map(items, function (item) { - var entry; - if (item.webkitGetAsEntry) { - entry = item.webkitGetAsEntry(); - if (entry) { - // Workaround for Chrome bug #149735: - entry._file = item.getAsFile(); - } - return entry; - } - return item.getAsEntry(); - }) - ); - } - return $.Deferred().resolve($.makeArray(dataTransfer.files)).promise(); - }, - - _getSingleFileInputFiles: function (fileInput) { - // eslint-disable-next-line no-param-reassign - fileInput = $(fileInput); - var entries = - fileInput.prop('webkitEntries') || fileInput.prop('entries'), - files, - value; - if (entries && entries.length) { - return this._handleFileTreeEntries(entries); - } - files = $.makeArray(fileInput.prop('files')); - if (!files.length) { - value = fileInput.prop('value'); - if (!value) { - return $.Deferred().resolve([]).promise(); - } - // If the files property is not available, the browser does not - // support the File API and we add a pseudo File object with - // the input value as name with path information removed: - files = [{ name: value.replace(/^.*\\/, '') }]; - } else if (files[0].name === undefined && files[0].fileName) { - // File normalization for Safari 4 and Firefox 3: - $.each(files, function (index, file) { - file.name = file.fileName; - file.size = file.fileSize; - }); - } - return $.Deferred().resolve(files).promise(); - }, - - _getFileInputFiles: function (fileInput) { - if (!(fileInput instanceof $) || fileInput.length === 1) { - return this._getSingleFileInputFiles(fileInput); - } - return $.when - .apply($, $.map(fileInput, this._getSingleFileInputFiles)) - .then(function () { - return Array.prototype.concat.apply([], arguments); - }); - }, - - _onChange: function (e) { - var that = this, - data = { - fileInput: $(e.target), - form: $(e.target.form) - }; - this._getFileInputFiles(data.fileInput).always(function (files) { - data.files = files; - if (that.options.replaceFileInput) { - that._replaceFileInput(data); - } - if ( - that._trigger( - 'change', - $.Event('change', { delegatedEvent: e }), - data - ) !== false - ) { - that._onAdd(e, data); - } - }); - }, - - _onPaste: function (e) { - var items = - e.originalEvent && - e.originalEvent.clipboardData && - e.originalEvent.clipboardData.items, - data = { files: [] }; - if (items && items.length) { - $.each(items, function (index, item) { - var file = item.getAsFile && item.getAsFile(); - if (file) { - data.files.push(file); - } - }); - if ( - this._trigger( - 'paste', - $.Event('paste', { delegatedEvent: e }), - data - ) !== false - ) { - this._onAdd(e, data); - } - } - }, - - _onDrop: function (e) { - e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer; - var that = this, - dataTransfer = e.dataTransfer, - data = {}; - if (dataTransfer && dataTransfer.files && dataTransfer.files.length) { - e.preventDefault(); - this._getDroppedFiles(dataTransfer).always(function (files) { - data.files = files; - if ( - that._trigger( - 'drop', - $.Event('drop', { delegatedEvent: e }), - data - ) !== false - ) { - that._onAdd(e, data); - } - }); - } - }, - - _onDragOver: getDragHandler('dragover'), - - _onDragEnter: getDragHandler('dragenter'), - - _onDragLeave: getDragHandler('dragleave'), - - _initEventHandlers: function () { - if (this._isXHRUpload(this.options)) { - this._on(this.options.dropZone, { - dragover: this._onDragOver, - drop: this._onDrop, - // event.preventDefault() on dragenter is required for IE10+: - dragenter: this._onDragEnter, - // dragleave is not required, but added for completeness: - dragleave: this._onDragLeave - }); - this._on(this.options.pasteZone, { - paste: this._onPaste - }); - } - if ($.support.fileInput) { - this._on(this.options.fileInput, { - change: this._onChange - }); - } - }, - - _destroyEventHandlers: function () { - this._off(this.options.dropZone, 'dragenter dragleave dragover drop'); - this._off(this.options.pasteZone, 'paste'); - this._off(this.options.fileInput, 'change'); - }, - - _destroy: function () { - this._destroyEventHandlers(); - }, - - _setOption: function (key, value) { - var reinit = $.inArray(key, this._specialOptions) !== -1; - if (reinit) { - this._destroyEventHandlers(); - } - this._super(key, value); - if (reinit) { - this._initSpecialOptions(); - this._initEventHandlers(); - } - }, - - _initSpecialOptions: function () { - var options = this.options; - if (options.fileInput === undefined) { - options.fileInput = this.element.is('input[type="file"]') - ? this.element - : this.element.find('input[type="file"]'); - } else if (!(options.fileInput instanceof $)) { - options.fileInput = $(options.fileInput); - } - if (!(options.dropZone instanceof $)) { - options.dropZone = $(options.dropZone); - } - if (!(options.pasteZone instanceof $)) { - options.pasteZone = $(options.pasteZone); - } - }, - - _getRegExp: function (str) { - var parts = str.split('/'), - modifiers = parts.pop(); - parts.shift(); - return new RegExp(parts.join('/'), modifiers); - }, - - _isRegExpOption: function (key, value) { - return ( - key !== 'url' && - $.type(value) === 'string' && - /^\/.*\/[igm]{0,3}$/.test(value) - ); - }, - - _initDataAttributes: function () { - var that = this, - options = this.options, - data = this.element.data(); - // Initialize options set via HTML5 data-attributes: - $.each(this.element[0].attributes, function (index, attr) { - var key = attr.name.toLowerCase(), - value; - if (/^data-/.test(key)) { - // Convert hyphen-ated key to camelCase: - key = key.slice(5).replace(/-[a-z]/g, function (str) { - return str.charAt(1).toUpperCase(); - }); - value = data[key]; - if (that._isRegExpOption(key, value)) { - value = that._getRegExp(value); - } - options[key] = value; - } - }); - }, - - _create: function () { - this._initDataAttributes(); - this._initSpecialOptions(); - this._slots = []; - this._sequence = this._getXHRPromise(true); - this._sending = this._active = 0; - this._initProgressObject(this); - this._initEventHandlers(); - }, - - // This method is exposed to the widget API and allows to query - // the number of active uploads: - active: function () { - return this._active; - }, - - // This method is exposed to the widget API and allows to query - // the widget upload progress. - // It returns an object with loaded, total and bitrate properties - // for the running uploads: - progress: function () { - return this._progress; - }, - - // This method is exposed to the widget API and allows adding files - // using the fileupload API. The data parameter accepts an object which - // must have a files property and can contain additional options: - // .fileupload('add', {files: filesList}); - add: function (data) { - var that = this; - if (!data || this.options.disabled) { - return; - } - if (data.fileInput && !data.files) { - this._getFileInputFiles(data.fileInput).always(function (files) { - data.files = files; - that._onAdd(null, data); - }); - } else { - data.files = $.makeArray(data.files); - this._onAdd(null, data); - } - }, - - // This method is exposed to the widget API and allows sending files - // using the fileupload API. The data parameter accepts an object which - // must have a files or fileInput property and can contain additional options: - // .fileupload('send', {files: filesList}); - // The method returns a Promise object for the file upload call. - send: function (data) { - if (data && !this.options.disabled) { - if (data.fileInput && !data.files) { - var that = this, - dfd = $.Deferred(), - promise = dfd.promise(), - jqXHR, - aborted; - promise.abort = function () { - aborted = true; - if (jqXHR) { - return jqXHR.abort(); - } - dfd.reject(null, 'abort', 'abort'); - return promise; - }; - this._getFileInputFiles(data.fileInput).always(function (files) { - if (aborted) { - return; - } - if (!files.length) { - dfd.reject(); - return; - } - data.files = files; - jqXHR = that._onSend(null, data); - jqXHR.then( - function (result, textStatus, jqXHR) { - dfd.resolve(result, textStatus, jqXHR); - }, - function (jqXHR, textStatus, errorThrown) { - dfd.reject(jqXHR, textStatus, errorThrown); - } - ); - }); - return this._enhancePromise(promise); - } - data.files = $.makeArray(data.files); - if (data.files.length) { - return this._onSend(null, data); - } - } - return this._getXHRPromise(false, data && data.context); - } - }); -});