4388 lines
113 KiB
JavaScript
4388 lines
113 KiB
JavaScript
/**
|
|
* Plupload - multi-runtime File Uploader
|
|
* v3.1.2
|
|
*
|
|
* Copyright 2018, Ephox
|
|
* Released under AGPLv3 License.
|
|
*
|
|
* License: http://www.plupload.com/license
|
|
* Contributing: http://www.plupload.com/contributing
|
|
*
|
|
* Date: 2018-02-20
|
|
*/
|
|
;(function (global, factory) {
|
|
var extract = function() {
|
|
var ctx = {};
|
|
factory.apply(ctx, arguments);
|
|
return ctx.plupload;
|
|
};
|
|
|
|
if (typeof define === "function" && define.amd) {
|
|
define("plupload", ['./moxie'], extract);
|
|
} else if (typeof module === "object" && module.exports) {
|
|
module.exports = extract(require('./moxie'));
|
|
} else {
|
|
global.plupload = extract(global.moxie);
|
|
}
|
|
}(this || window, function(moxie) {
|
|
/**
|
|
* Compiled inline version. (Library mode)
|
|
*/
|
|
|
|
/*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */
|
|
/*globals $code */
|
|
|
|
(function(exports, undefined) {
|
|
"use strict";
|
|
|
|
var modules = {};
|
|
|
|
function require(ids, callback) {
|
|
var module, defs = [];
|
|
|
|
for (var i = 0; i < ids.length; ++i) {
|
|
module = modules[ids[i]] || resolve(ids[i]);
|
|
if (!module) {
|
|
throw 'module definition dependecy not found: ' + ids[i];
|
|
}
|
|
|
|
defs.push(module);
|
|
}
|
|
|
|
callback.apply(null, defs);
|
|
}
|
|
|
|
function define(id, dependencies, definition) {
|
|
if (typeof id !== 'string') {
|
|
throw 'invalid module definition, module id must be defined and be a string';
|
|
}
|
|
|
|
if (dependencies === undefined) {
|
|
throw 'invalid module definition, dependencies must be specified';
|
|
}
|
|
|
|
if (definition === undefined) {
|
|
throw 'invalid module definition, definition function must be specified';
|
|
}
|
|
|
|
require(dependencies, function() {
|
|
modules[id] = definition.apply(null, arguments);
|
|
});
|
|
}
|
|
|
|
function defined(id) {
|
|
return !!modules[id];
|
|
}
|
|
|
|
function resolve(id) {
|
|
var target = exports;
|
|
var fragments = id.split(/[.\/]/);
|
|
|
|
for (var fi = 0; fi < fragments.length; ++fi) {
|
|
if (!target[fragments[fi]]) {
|
|
return;
|
|
}
|
|
|
|
target = target[fragments[fi]];
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
function expose(ids) {
|
|
for (var i = 0; i < ids.length; i++) {
|
|
var target = exports;
|
|
var id = ids[i];
|
|
var fragments = id.split(/[.\/]/);
|
|
|
|
for (var fi = 0; fi < fragments.length - 1; ++fi) {
|
|
if (target[fragments[fi]] === undefined) {
|
|
target[fragments[fi]] = {};
|
|
}
|
|
|
|
target = target[fragments[fi]];
|
|
}
|
|
|
|
target[fragments[fragments.length - 1]] = modules[id];
|
|
}
|
|
}
|
|
|
|
// Included from: src/plupload.js
|
|
|
|
/**
|
|
* plupload.js
|
|
*
|
|
* Copyright 2017, Ephox
|
|
* Released under AGPLv3 License.
|
|
*
|
|
* License: http://www.plupload.com/license
|
|
* Contributing: http://www.plupload.com/contributing
|
|
*/
|
|
|
|
/**
|
|
Namespace for all Plupload related classes, methods and properties.
|
|
|
|
@class plupload
|
|
@public
|
|
@static
|
|
*/
|
|
define('plupload', [], function() {
|
|
|
|
var o = moxie;
|
|
var u = o.core.utils;
|
|
|
|
// redifine event dispatcher for Flash/Silverlight runtimes
|
|
u.Env.global_event_dispatcher = 'plupload.EventTarget.instance.dispatchEvent';
|
|
|
|
|
|
return {
|
|
/**
|
|
* Plupload version will be replaced on build.
|
|
*
|
|
* @property VERSION
|
|
* @static
|
|
* @final
|
|
*/
|
|
VERSION: '3.1.2',
|
|
|
|
/**
|
|
* The state of the queue before it has started and after it has finished
|
|
*
|
|
* @property STOPPED
|
|
* @static
|
|
* @final
|
|
*/
|
|
STOPPED: 1,
|
|
|
|
/**
|
|
* Upload process is running
|
|
*
|
|
* @property STARTED
|
|
* @static
|
|
* @final
|
|
*/
|
|
STARTED: 2,
|
|
|
|
/**
|
|
File is queued for upload
|
|
|
|
@property QUEUED
|
|
@static
|
|
@final
|
|
*/
|
|
QUEUED: 1,
|
|
|
|
/**
|
|
File is being uploaded
|
|
|
|
@property UPLOADING
|
|
@static
|
|
@final
|
|
*/
|
|
UPLOADING: 2,
|
|
|
|
/**
|
|
File has failed to be uploaded
|
|
|
|
@property FAILED
|
|
@static
|
|
@final
|
|
*/
|
|
FAILED: 4,
|
|
|
|
/**
|
|
File has been uploaded successfully
|
|
|
|
@property DONE
|
|
@static
|
|
@final
|
|
*/
|
|
DONE: 5,
|
|
|
|
// Error constants used by the Error event
|
|
|
|
/**
|
|
* Generic error for example if an exception is thrown inside Silverlight.
|
|
*
|
|
* @property GENERIC_ERROR
|
|
* @static
|
|
* @final
|
|
*/
|
|
GENERIC_ERROR: -100,
|
|
|
|
/**
|
|
* HTTP transport error. For example if the server produces a HTTP status other than 200.
|
|
*
|
|
* @property HTTP_ERROR
|
|
* @static
|
|
* @final
|
|
*/
|
|
HTTP_ERROR: -200,
|
|
|
|
/**
|
|
* Generic I/O error. For example if it wasn't possible to open the file stream on local machine.
|
|
*
|
|
* @property IO_ERROR
|
|
* @static
|
|
* @final
|
|
*/
|
|
IO_ERROR: -300,
|
|
|
|
/**
|
|
* @property SECURITY_ERROR
|
|
* @static
|
|
* @final
|
|
*/
|
|
SECURITY_ERROR: -400,
|
|
|
|
/**
|
|
* Initialization error. Will be triggered if no runtime was initialized.
|
|
*
|
|
* @property INIT_ERROR
|
|
* @static
|
|
* @final
|
|
*/
|
|
INIT_ERROR: -500,
|
|
|
|
/**
|
|
* File size error. If the user selects a file that is too large it will be blocked and an error of this type will be triggered.
|
|
*
|
|
* @property FILE_SIZE_ERROR
|
|
* @static
|
|
* @final
|
|
*/
|
|
FILE_SIZE_ERROR: -600,
|
|
|
|
/**
|
|
* File extension error. If the user selects a file that isn't valid according to the filters setting.
|
|
*
|
|
* @property FILE_EXTENSION_ERROR
|
|
* @static
|
|
* @final
|
|
*/
|
|
FILE_EXTENSION_ERROR: -601,
|
|
|
|
/**
|
|
* Duplicate file error. If prevent_duplicates is set to true and user selects the same file again.
|
|
*
|
|
* @property FILE_DUPLICATE_ERROR
|
|
* @static
|
|
* @final
|
|
*/
|
|
FILE_DUPLICATE_ERROR: -602,
|
|
|
|
/**
|
|
* Runtime will try to detect if image is proper one. Otherwise will throw this error.
|
|
*
|
|
* @property IMAGE_FORMAT_ERROR
|
|
* @static
|
|
* @final
|
|
*/
|
|
IMAGE_FORMAT_ERROR: -700,
|
|
|
|
/**
|
|
* While working on files runtime may run out of memory and will throw this error.
|
|
*
|
|
* @since 2.1.2
|
|
* @property MEMORY_ERROR
|
|
* @static
|
|
* @final
|
|
*/
|
|
MEMORY_ERROR: -701,
|
|
|
|
/**
|
|
* Each runtime has an upper limit on a dimension of the image it can handle. If bigger, will throw this error.
|
|
*
|
|
* @property IMAGE_DIMENSIONS_ERROR
|
|
* @static
|
|
* @final
|
|
*/
|
|
IMAGE_DIMENSIONS_ERROR: -702,
|
|
|
|
|
|
/**
|
|
Invalid option error. Will be thrown if user tries to alter the option that cannot be changed without
|
|
uploader reinitialisation.
|
|
|
|
@property OPTION_ERROR
|
|
@static
|
|
@final
|
|
*/
|
|
OPTION_ERROR: -800,
|
|
|
|
/**
|
|
* Expose whole moxie (#1469).
|
|
*
|
|
* @property moxie
|
|
* @type Object
|
|
* @final
|
|
*/
|
|
moxie: o,
|
|
|
|
/**
|
|
* In some cases sniffing is the only way around :(
|
|
*/
|
|
ua: u.Env,
|
|
|
|
/**
|
|
* Gets the true type of the built-in object (better version of typeof).
|
|
* @credits Angus Croll (http://javascriptweblog.wordpress.com/)
|
|
*
|
|
* @method typeOf
|
|
* @static
|
|
* @param {Object} o Object to check.
|
|
* @return {String} Object [[Class]]
|
|
*/
|
|
typeOf: u.Basic.typeOf,
|
|
|
|
clone: u.Basic.clone,
|
|
|
|
inherit: u.Basic.inherit,
|
|
|
|
|
|
/**
|
|
* Extends the specified object with another object.
|
|
*
|
|
* @method extend
|
|
* @static
|
|
* @param {Object} target Object to extend.
|
|
* @param {Object..} obj Multiple objects to extend with.
|
|
* @return {Object} Same as target, the extended object.
|
|
*/
|
|
extend: u.Basic.extend,
|
|
|
|
|
|
extendImmutable: u.Basic.extendImmutable,
|
|
|
|
/**
|
|
Extends the specified object with another object(s), but only if the property exists in the target.
|
|
|
|
@method extendIf
|
|
@static
|
|
@param {Object} target Object to extend.
|
|
@param {Object} [obj]* Multiple objects to extend with.
|
|
@return {Object} Same as target, the extended object.
|
|
*/
|
|
extendIf: u.Basic.extendIf,
|
|
|
|
/**
|
|
Recieve an array of functions (usually async) to call in sequence, each function
|
|
receives a callback as first argument that it should call, when it completes. Finally,
|
|
after everything is complete, main callback is called. Passing truthy value to the
|
|
callback as a first argument will interrupt the sequence and invoke main callback
|
|
immediately.
|
|
|
|
@method inSeries
|
|
@static
|
|
@param {Array} queue Array of functions to call in sequence
|
|
@param {Function} cb Main callback that is called in the end, or in case of error
|
|
*/
|
|
inSeries: u.Basic.inSeries,
|
|
|
|
/**
|
|
Recieve an array of functions (usually async) to call in parallel, each function
|
|
receives a callback as first argument that it should call, when it completes. After
|
|
everything is complete, main callback is called. Passing truthy value to the
|
|
callback as a first argument will interrupt the process and invoke main callback
|
|
immediately.
|
|
|
|
@method inParallel
|
|
@static
|
|
@param {Array} queue Array of functions to call in sequence
|
|
@param {Function} cb Main callback that is called in the end, or in case of erro
|
|
*/
|
|
inParallel: u.Basic.inParallel,
|
|
|
|
/**
|
|
* Generates an unique ID. This is 99.99% unique since it takes the current time and 5 random numbers.
|
|
* The only way a user would be able to get the same ID is if the two persons at the same exact millisecond manages
|
|
* to get 5 the same random numbers between 0-65535 it also uses a counter so each call will be guaranteed to be page unique.
|
|
* It's more probable for the earth to be hit with an asteriod. You can also if you want to be 100% sure set the plupload.guidPrefix property
|
|
* to an user unique key.
|
|
*
|
|
* @method guid
|
|
* @static
|
|
* @return {String} Virtually unique id.
|
|
*/
|
|
guid: u.Basic.guid,
|
|
|
|
/**
|
|
* Get array of DOM Elements by their ids.
|
|
*
|
|
* @method get
|
|
* @param {String} id Identifier of the DOM Element
|
|
* @return {Array}
|
|
*/
|
|
getAll: function get(ids) {
|
|
var els = [],
|
|
el;
|
|
|
|
if (u.Basic.typeOf(ids) !== 'array') {
|
|
ids = [ids];
|
|
}
|
|
|
|
var i = ids.length;
|
|
while (i--) {
|
|
el = u.Dom.get(ids[i]);
|
|
if (el) {
|
|
els.push(el);
|
|
}
|
|
}
|
|
|
|
return els.length ? els : null;
|
|
},
|
|
|
|
/**
|
|
Get DOM element by id
|
|
|
|
@method get
|
|
@param {String} id Identifier of the DOM Element
|
|
@return {Node}
|
|
*/
|
|
get: u.Dom.get,
|
|
|
|
/**
|
|
* Executes the callback function for each item in array/object. If you return false in the
|
|
* callback it will break the loop.
|
|
*
|
|
* @method each
|
|
* @static
|
|
* @param {Object} obj Object to iterate.
|
|
* @param {function} callback Callback function to execute for each item.
|
|
*/
|
|
each: u.Basic.each,
|
|
|
|
/**
|
|
* Returns the absolute x, y position of an Element. The position will be returned in a object with x, y fields.
|
|
*
|
|
* @method getPos
|
|
* @static
|
|
* @param {Element} node HTML element or element id to get x, y position from.
|
|
* @param {Element} root Optional root element to stop calculations at.
|
|
* @return {object} Absolute position of the specified element object with x, y fields.
|
|
*/
|
|
getPos: u.Dom.getPos,
|
|
|
|
/**
|
|
* Returns the size of the specified node in pixels.
|
|
*
|
|
* @method getSize
|
|
* @static
|
|
* @param {Node} node Node to get the size of.
|
|
* @return {Object} Object with a w and h property.
|
|
*/
|
|
getSize: u.Dom.getSize,
|
|
|
|
/**
|
|
* Encodes the specified string.
|
|
*
|
|
* @method xmlEncode
|
|
* @static
|
|
* @param {String} s String to encode.
|
|
* @return {String} Encoded string.
|
|
*/
|
|
xmlEncode: function(str) {
|
|
var xmlEncodeChars = {
|
|
'<': 'lt',
|
|
'>': 'gt',
|
|
'&': 'amp',
|
|
'"': 'quot',
|
|
'\'': '#39'
|
|
},
|
|
xmlEncodeRegExp = /[<>&\"\']/g;
|
|
|
|
return str ? ('' + str).replace(xmlEncodeRegExp, function(chr) {
|
|
return xmlEncodeChars[chr] ? '&' + xmlEncodeChars[chr] + ';' : chr;
|
|
}) : str;
|
|
},
|
|
|
|
/**
|
|
* Forces anything into an array.
|
|
*
|
|
* @method toArray
|
|
* @static
|
|
* @param {Object} obj Object with length field.
|
|
* @return {Array} Array object containing all items.
|
|
*/
|
|
toArray: u.Basic.toArray,
|
|
|
|
/**
|
|
* Find an element in array and return its index if present, otherwise return -1.
|
|
*
|
|
* @method inArray
|
|
* @static
|
|
* @param {mixed} needle Element to find
|
|
* @param {Array} array
|
|
* @return {Int} Index of the element, or -1 if not found
|
|
*/
|
|
inArray: u.Basic.inArray,
|
|
|
|
/**
|
|
* Extends the language pack object with new items.
|
|
*
|
|
* @method addI18n
|
|
* @static
|
|
* @param {Object} pack Language pack items to add.
|
|
* @return {Object} Extended language pack object.
|
|
*/
|
|
addI18n: o.core.I18n.addI18n,
|
|
|
|
/**
|
|
* Translates the specified string by checking for the english string in the language pack lookup.
|
|
*
|
|
* @method translate
|
|
* @static
|
|
* @param {String} str String to look for.
|
|
* @return {String} Translated string or the input string if it wasn't found.
|
|
*/
|
|
translate: o.core.I18n.translate,
|
|
|
|
/**
|
|
* Pseudo sprintf implementation - simple way to replace tokens with specified values.
|
|
*
|
|
* @param {String} str String with tokens
|
|
* @return {String} String with replaced tokens
|
|
*/
|
|
sprintf: u.Basic.sprintf,
|
|
|
|
/**
|
|
* Checks if object is empty.
|
|
*
|
|
* @method isEmptyObj
|
|
* @static
|
|
* @param {Object} obj Object to check.
|
|
* @return {Boolean}
|
|
*/
|
|
isEmptyObj: u.Basic.isEmptyObj,
|
|
|
|
/**
|
|
* Checks if specified DOM element has specified class.
|
|
*
|
|
* @method hasClass
|
|
* @static
|
|
* @param {Object} obj DOM element like object to add handler to.
|
|
* @param {String} name Class name
|
|
*/
|
|
hasClass: u.Dom.hasClass,
|
|
|
|
/**
|
|
* Adds specified className to specified DOM element.
|
|
*
|
|
* @method addClass
|
|
* @static
|
|
* @param {Object} obj DOM element like object to add handler to.
|
|
* @param {String} name Class name
|
|
*/
|
|
addClass: u.Dom.addClass,
|
|
|
|
/**
|
|
* Removes specified className from specified DOM element.
|
|
*
|
|
* @method removeClass
|
|
* @static
|
|
* @param {Object} obj DOM element like object to add handler to.
|
|
* @param {String} name Class name
|
|
*/
|
|
removeClass: u.Dom.removeClass,
|
|
|
|
/**
|
|
* Returns a given computed style of a DOM element.
|
|
*
|
|
* @method getStyle
|
|
* @static
|
|
* @param {Object} obj DOM element like object.
|
|
* @param {String} name Style you want to get from the DOM element
|
|
*/
|
|
getStyle: u.Dom.getStyle,
|
|
|
|
/**
|
|
* Adds an event handler to the specified object and store reference to the handler
|
|
* in objects internal Plupload registry (@see removeEvent).
|
|
*
|
|
* @method addEvent
|
|
* @static
|
|
* @param {Object} obj DOM element like object to add handler to.
|
|
* @param {String} name Name to add event listener to.
|
|
* @param {Function} callback Function to call when event occurs.
|
|
* @param {String} (optional) key that might be used to add specifity to the event record.
|
|
*/
|
|
addEvent: u.Events.addEvent,
|
|
|
|
/**
|
|
* Remove event handler from the specified object. If third argument (callback)
|
|
* is not specified remove all events with the specified name.
|
|
*
|
|
* @method removeEvent
|
|
* @static
|
|
* @param {Object} obj DOM element to remove event listener(s) from.
|
|
* @param {String} name Name of event listener to remove.
|
|
* @param {Function|String} (optional) might be a callback or unique key to match.
|
|
*/
|
|
removeEvent: u.Events.removeEvent,
|
|
|
|
/**
|
|
* Remove all kind of events from the specified object
|
|
*
|
|
* @method removeAllEvents
|
|
* @static
|
|
* @param {Object} obj DOM element to remove event listeners from.
|
|
* @param {String} (optional) unique key to match, when removing events.
|
|
*/
|
|
removeAllEvents: u.Events.removeAllEvents,
|
|
|
|
/**
|
|
* Cleans the specified name from national characters (diacritics). The result will be a name with only a-z, 0-9 and _.
|
|
*
|
|
* @method cleanName
|
|
* @static
|
|
* @param {String} s String to clean up.
|
|
* @return {String} Cleaned string.
|
|
*/
|
|
cleanName: function(name) {
|
|
var i, lookup;
|
|
|
|
// Replace diacritics
|
|
lookup = [
|
|
/[\300-\306]/g, 'A', /[\340-\346]/g, 'a',
|
|
/\307/g, 'C', /\347/g, 'c',
|
|
/[\310-\313]/g, 'E', /[\350-\353]/g, 'e',
|
|
/[\314-\317]/g, 'I', /[\354-\357]/g, 'i',
|
|
/\321/g, 'N', /\361/g, 'n',
|
|
/[\322-\330]/g, 'O', /[\362-\370]/g, 'o',
|
|
/[\331-\334]/g, 'U', /[\371-\374]/g, 'u'
|
|
];
|
|
|
|
for (i = 0; i < lookup.length; i += 2) {
|
|
name = name.replace(lookup[i], lookup[i + 1]);
|
|
}
|
|
|
|
// Replace whitespace
|
|
name = name.replace(/\s+/g, '_');
|
|
|
|
// Remove anything else
|
|
name = name.replace(/[^a-z0-9_\-\.]+/gi, '');
|
|
|
|
return name;
|
|
},
|
|
|
|
/**
|
|
* Builds a full url out of a base URL and an object with items to append as query string items.
|
|
*
|
|
* @method buildUrl
|
|
* @static
|
|
* @param {String} url Base URL to append query string items to.
|
|
* @param {Object} items Name/value object to serialize as a querystring.
|
|
* @return {String} String with url + serialized query string items.
|
|
*/
|
|
buildUrl: function(url, items) {
|
|
var query = '';
|
|
|
|
u.Basic.each(items, function(value, name) {
|
|
query += (query ? '&' : '') + encodeURIComponent(name) + '=' + encodeURIComponent(value);
|
|
});
|
|
|
|
if (query) {
|
|
url += (url.indexOf('?') > 0 ? '&' : '?') + query;
|
|
}
|
|
|
|
return url;
|
|
},
|
|
|
|
/**
|
|
* Formats the specified number as a size string for example 1024 becomes 1 KB.
|
|
*
|
|
* @method formatSize
|
|
* @static
|
|
* @param {Number} size Size to format as string.
|
|
* @return {String} Formatted size string.
|
|
*/
|
|
formatSize: function(size) {
|
|
var self = this;
|
|
|
|
function round(num, precision) {
|
|
return Math.round(num * Math.pow(10, precision)) / Math.pow(10, precision);
|
|
}
|
|
|
|
size = parseInt(size, 10);
|
|
if (isNaN(size)) {
|
|
return self.translate('N/A');
|
|
}
|
|
|
|
var boundary = Math.pow(1024, 4);
|
|
|
|
// TB
|
|
if (size > boundary) {
|
|
return round(size / boundary, 1) + " " + self.translate('tb');
|
|
}
|
|
|
|
// GB
|
|
if (size > (boundary /= 1024)) {
|
|
return round(size / boundary, 1) + " " + self.translate('gb');
|
|
}
|
|
|
|
// MB
|
|
if (size > (boundary /= 1024)) {
|
|
return round(size / boundary, 1) + " " + self.translate('mb');
|
|
}
|
|
|
|
// KB
|
|
if (size > 1024) {
|
|
return Math.round(size / 1024) + " " + self.translate('kb');
|
|
}
|
|
|
|
return size + " " + self.translate('b');
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
mimes2extList: moxie.core.utils.Mime.mimes2extList,
|
|
|
|
/**
|
|
Resolve url - among other things will turn relative url to absolute
|
|
|
|
@method resolveUrl
|
|
@static
|
|
@param {String|Object} url Either absolute or relative, or a result of parseUrl call
|
|
@return {String} Resolved, absolute url
|
|
*/
|
|
resolveUrl: u.Url.resolveUrl,
|
|
|
|
/**
|
|
* Parses the specified size string into a byte value. For example 10kb becomes 10240.
|
|
*
|
|
* @method parseSize
|
|
* @static
|
|
* @param {String|Number} size String to parse or number to just pass through.
|
|
* @return {Number} Size in bytes.
|
|
*/
|
|
parseSize: u.Basic.parseSizeStr,
|
|
|
|
delay: u.Basic.delay,
|
|
|
|
|
|
/**
|
|
Parent object for all event dispatching components and objects
|
|
|
|
@class plupload.EventTarget
|
|
@private
|
|
@constructor
|
|
*/
|
|
EventTarget: moxie.core.EventTarget,
|
|
|
|
/**
|
|
Common set of methods and properties for every runtime instance
|
|
|
|
@class plupload.Runtime
|
|
@private
|
|
|
|
@param {Object} options
|
|
@param {String} type Sanitized name of the runtime
|
|
@param {Object} [caps] Set of capabilities that differentiate specified runtime
|
|
@param {Object} [modeCaps] Set of capabilities that do require specific operational mode
|
|
@param {String} [preferredMode='browser'] Preferred operational mode to choose if no required capabilities were requested
|
|
*/
|
|
Runtime: moxie.runtime.Runtime,
|
|
|
|
/**
|
|
Provides a convenient way to create cross-browser file-picker. Generates file selection dialog on click,
|
|
converts selected files to _File_ objects, to be used in conjunction with _Image_, preloaded in memory
|
|
with _FileReader_ or uploaded to a server through _XMLHttpRequest_.
|
|
|
|
@class plupload.FileInput
|
|
@private
|
|
@constructor
|
|
@extends EventTarget
|
|
@uses RuntimeClient
|
|
@param {Object|String|DOMElement} options If options is string or node, argument is considered as _browse\_button_.
|
|
@param {String|DOMElement} options.browse_button DOM Element to turn into file picker.
|
|
@param {Array} [options.accept] Array of mime types to accept. By default accepts all.
|
|
@param {String} [options.file='file'] Name of the file field (not the filename).
|
|
@param {Boolean} [options.multiple=false] Enable selection of multiple files.
|
|
@param {Boolean} [options.directory=false] Turn file input into the folder input (cannot be both at the same time).
|
|
@param {String|DOMElement} [options.container] DOM Element to use as a container for file-picker. Defaults to parentNode
|
|
for _browse\_button_.
|
|
@param {Object|String} [options.required_caps] Set of required capabilities, that chosen runtime must support.
|
|
*/
|
|
FileInput: moxie.file.FileInput,
|
|
|
|
/**
|
|
Utility for preloading o.Blob/o.File objects in memory. By design closely follows [W3C FileReader](http://www.w3.org/TR/FileAPI/#dfn-filereader)
|
|
interface. Where possible uses native FileReader, where - not falls back to shims.
|
|
|
|
@class plupload.FileReader
|
|
@private
|
|
@constructor
|
|
@extends EventTarget
|
|
@uses RuntimeClient
|
|
*/
|
|
FileReader: moxie.file.FileReader
|
|
};
|
|
|
|
});
|
|
|
|
// Included from: src/core/Collection.js
|
|
|
|
/**
|
|
* Collection.js
|
|
*
|
|
* Copyright 2017, Ephox
|
|
* Released under AGPLv3 License.
|
|
*
|
|
* License: http://www.plupload.com/license
|
|
* Contributing: http://www.plupload.com/contributing
|
|
*/
|
|
|
|
|
|
/**
|
|
Helper collection class - in a way a mix of object and array
|
|
|
|
@contsructor
|
|
@class plupload.core.Collection
|
|
@private
|
|
*/
|
|
define('plupload/core/Collection', [
|
|
'plupload'
|
|
], function(Basic) {
|
|
|
|
var Collection = function() {
|
|
var _registry = {};
|
|
var _length = 0;
|
|
var _last;
|
|
|
|
|
|
plupload.extend(this, {
|
|
|
|
count: function() {
|
|
return _length;
|
|
},
|
|
|
|
hasKey: function(key) {
|
|
return _registry.hasOwnProperty(key)
|
|
},
|
|
|
|
|
|
get: function(key) {
|
|
return _registry[key];
|
|
},
|
|
|
|
|
|
first: function() {
|
|
for (var key in _registry) {
|
|
return _registry[key];
|
|
}
|
|
},
|
|
|
|
|
|
last: function() {
|
|
return _last;
|
|
},
|
|
|
|
|
|
toObject: function() {
|
|
return _registry;
|
|
},
|
|
|
|
|
|
add: function(key, obj) {
|
|
var self = this;
|
|
|
|
if (typeof(key) === 'object' && !obj) {
|
|
return plupload.each(key, function(obj, key) {
|
|
self.add(key, obj);
|
|
});
|
|
}
|
|
|
|
if (_registry.hasOwnProperty(key)) {
|
|
return self.update.apply(self, arguments);
|
|
}
|
|
|
|
_registry[key] = _last = obj;
|
|
_length++;
|
|
},
|
|
|
|
|
|
remove: function(key) {
|
|
if (this.hasKey(key)) {
|
|
var last = _registry[key];
|
|
|
|
delete _registry[key];
|
|
_length--;
|
|
|
|
// renew ref to the last added item if necessary
|
|
if (_last === last) {
|
|
_last = findLast();
|
|
}
|
|
}
|
|
},
|
|
|
|
|
|
extract: function(key) {
|
|
var item = this.get(key);
|
|
this.remove(key);
|
|
return item;
|
|
},
|
|
|
|
|
|
shift: function() {
|
|
var self = this,
|
|
first, key;
|
|
|
|
for (key in _registry) {
|
|
first = _registry[key];
|
|
self.remove(key);
|
|
return first;
|
|
}
|
|
},
|
|
|
|
|
|
update: function(key, obj) {
|
|
_registry[key] = obj;
|
|
},
|
|
|
|
|
|
each: function(cb) {
|
|
plupload.each(_registry, cb);
|
|
},
|
|
|
|
|
|
combineWith: function() {
|
|
var newCol = new Collection();
|
|
|
|
newCol.add(_registry);
|
|
|
|
plupload.each(arguments, function(col) {
|
|
if (col instanceof Collection) {
|
|
newCol.add(col.toObject());
|
|
}
|
|
});
|
|
return newCol;
|
|
},
|
|
|
|
|
|
clear: function() {
|
|
_registry = {};
|
|
_last = null;
|
|
_length = 0;
|
|
}
|
|
});
|
|
|
|
|
|
function findLast() {
|
|
var key;
|
|
for (key in _registry) {}
|
|
return _registry[key];
|
|
}
|
|
|
|
};
|
|
|
|
return Collection;
|
|
});
|
|
|
|
// Included from: src/core/ArrCollection.js
|
|
|
|
/**
|
|
* ArrCollection.js
|
|
*
|
|
* Copyright 2017, Ephox
|
|
* Released under AGPLv3 License.
|
|
*
|
|
* License: http://www.plupload.com/license
|
|
* Contributing: http://www.plupload.com/contributing
|
|
*/
|
|
|
|
|
|
/**
|
|
@contsructor
|
|
@class plupload.core.ArrCollection
|
|
@private
|
|
*/
|
|
define('plupload/core/ArrCollection', [
|
|
'plupload'
|
|
], function(plupload) {
|
|
|
|
var ArrCollection = function() {
|
|
var _registry = [];
|
|
|
|
plupload.extend(this, {
|
|
|
|
count: function() {
|
|
return _registry.length;
|
|
},
|
|
|
|
hasKey: function(key) {
|
|
return this.getIdx(key) > -1;
|
|
},
|
|
|
|
|
|
get: function(key) {
|
|
var idx = this.getIdx(key);
|
|
return idx > -1 ? _registry[idx] : null;
|
|
},
|
|
|
|
getIdx: function(key) {
|
|
for (var i = 0, length = _registry.length; i < length; i++) {
|
|
if (_registry[i].uid === key) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
},
|
|
|
|
getByIdx: function(idx) {
|
|
return _registry[idx]
|
|
},
|
|
|
|
first: function() {
|
|
return _registry[0];
|
|
},
|
|
|
|
last: function() {
|
|
return _registry[_registry.length - 1];
|
|
},
|
|
|
|
add: function(obj) {
|
|
obj = arguments[1] || obj; // make it compatible with Collection.add()
|
|
|
|
var idx = this.getIdx(obj.uid);
|
|
if (idx > -1) {
|
|
_registry[idx] = obj;
|
|
return idx;
|
|
}
|
|
|
|
_registry.push(obj);
|
|
return _registry.length - 1;
|
|
},
|
|
|
|
remove: function(key) {
|
|
return !!this.extract(key);
|
|
},
|
|
|
|
splice: function(start, length) {
|
|
start = plupload.typeOf(start) === 'undefinded' ? 0 : Math.max(start, 0);
|
|
length = plupload.typeOf(length) !== 'undefinded' && start + length < _registry.length ? length : _registry.length - start;
|
|
|
|
return _registry.splice(start, length);
|
|
},
|
|
|
|
extract: function(key) {
|
|
var idx = this.getIdx(key);
|
|
if (idx > -1) {
|
|
return _registry.splice(idx, 1);
|
|
}
|
|
return null;
|
|
},
|
|
|
|
shift: function() {
|
|
return _registry.shift();
|
|
},
|
|
|
|
update: function(key, obj) {
|
|
var idx = this.getIdx(key);
|
|
if (idx > -1) {
|
|
_registry[idx] = obj;
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
|
|
each: function(cb) {
|
|
plupload.each(_registry, cb);
|
|
},
|
|
|
|
combineWith: function() {
|
|
return Array.prototype.concat.apply(this.toArray(), arguments);
|
|
},
|
|
|
|
sort: function(cb) {
|
|
_registry.sort(cb || function(a, b) {
|
|
return a.priority - b.priority;
|
|
});
|
|
},
|
|
|
|
clear: function() {
|
|
_registry = [];
|
|
},
|
|
|
|
toObject: function() {
|
|
var obj = {};
|
|
for (var i = 0, length = _registry.length; i < length; i++) {
|
|
obj[_registry[i].uid] = _registry[i];
|
|
}
|
|
return obj;
|
|
},
|
|
|
|
toArray: function() {
|
|
return Array.prototype.slice.call(_registry);
|
|
}
|
|
});
|
|
};
|
|
|
|
return ArrCollection;
|
|
});
|
|
|
|
// Included from: src/core/Optionable.js
|
|
|
|
/**
|
|
* Optionable.js
|
|
*
|
|
* Copyright 2017, Ephox
|
|
* Released under AGPLv3 License.
|
|
*
|
|
* License: http://www.plupload.com/license
|
|
* Contributing: http://www.plupload.com/contributing
|
|
*/
|
|
|
|
|
|
/**
|
|
@contsructor
|
|
@class plupload.core.Optionable
|
|
@private
|
|
@since 3.0
|
|
*/
|
|
define('plupload/core/Optionable', [
|
|
'plupload'
|
|
], function(plupload) {
|
|
var EventTarget = moxie.core.EventTarget;
|
|
|
|
var dispatches = [
|
|
/**
|
|
* Dispatched when option is being changed.
|
|
*
|
|
* @event OptionChanged
|
|
* @param {Object} event
|
|
* @param {String} name Name of the option being changed
|
|
* @param {Mixed} value
|
|
* @param {Mixed} oldValue
|
|
*/
|
|
'OptionChanged'
|
|
];
|
|
|
|
return (function(Parent) {
|
|
|
|
/**
|
|
* @class Optionable
|
|
* @constructor
|
|
* @extends EventTarget
|
|
*/
|
|
function Optionable() {
|
|
Parent.apply(this, arguments);
|
|
|
|
this._options = {};
|
|
}
|
|
|
|
plupload.inherit(Optionable, Parent);
|
|
|
|
plupload.extend(Optionable.prototype, {
|
|
/**
|
|
* Set the value for the specified option(s).
|
|
*
|
|
* @method setOption
|
|
* @since 2.1
|
|
* @param {String|Object} option Name of the option to change or the set of key/value pairs
|
|
* @param {Mixed} [value] Value for the option (is ignored, if first argument is object)
|
|
* @param {Boolean} [mustBeDefined] if truthy, any option that is not in defaults will be ignored
|
|
*/
|
|
setOption: function(option, value, mustBeDefined) {
|
|
var self = this;
|
|
var oldValue;
|
|
|
|
if (typeof(option) === 'object') {
|
|
mustBeDefined = value;
|
|
plupload.each(option, function(value, option) {
|
|
self.setOption(option, value, mustBeDefined);
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (mustBeDefined && !self._options.hasOwnProperty(option)) {
|
|
return;
|
|
}
|
|
|
|
oldValue = plupload.clone(self._options[option]);
|
|
|
|
//! basically if an option is of type object extend it rather than replace
|
|
if (plupload.typeOf(value) === 'object' && plupload.typeOf(self._options[option]) === 'object') {
|
|
// having some options as objects was a bad idea, prefixes is the way
|
|
plupload.extend(self._options[option], value);
|
|
} else {
|
|
self._options[option] = value;
|
|
}
|
|
|
|
self.trigger('OptionChanged', option, value, oldValue);
|
|
},
|
|
|
|
/**
|
|
* Get the value for the specified option or the whole configuration, if not specified.
|
|
*
|
|
* @method getOption
|
|
* @since 2.1
|
|
* @param {String} [option] Name of the option to get
|
|
* @return {Mixed} Value for the option or the whole set
|
|
*/
|
|
getOption: function(option) {
|
|
if (!option) {
|
|
return this._options;
|
|
}
|
|
|
|
var value = this._options[option];
|
|
if (plupload.inArray(plupload.typeOf(value), ['array', 'object']) > -1) {
|
|
return plupload.extendImmutable({}, value);
|
|
} else {
|
|
return value;
|
|
}
|
|
},
|
|
|
|
|
|
/**
|
|
* Set many options as once.
|
|
*
|
|
* @method setOptions
|
|
* @param {Object} options
|
|
* @param {Boolean} [mustBeDefined] if truthy, any option that is not in defaults will be ignored
|
|
*/
|
|
setOptions: function(options, mustBeDefined) {
|
|
if (typeof(options) !== 'object') {
|
|
return;
|
|
}
|
|
this.setOption(options, mustBeDefined);
|
|
},
|
|
|
|
|
|
/**
|
|
Gets all options.
|
|
|
|
@method getOptions
|
|
@return {Object}
|
|
*/
|
|
getOptions: function() {
|
|
return this.getOption();
|
|
}
|
|
});
|
|
|
|
return Optionable;
|
|
|
|
}(EventTarget));
|
|
|
|
});
|
|
|
|
// Included from: src/core/Queueable.js
|
|
|
|
/**
|
|
* Queueable.js
|
|
*
|
|
* Copyright 2017, Ephox
|
|
* Released under AGPLv3 License.se.
|
|
*
|
|
* License: http://www.plupload.com/license
|
|
* Contributing: http://www.plupload.com/contributing
|
|
*/
|
|
|
|
|
|
/**
|
|
Every queue item must have properties, implement methods and fire events defined in this class
|
|
|
|
@contsructor
|
|
@class plupload.core.Queueable
|
|
@private
|
|
@decorator
|
|
@extends EventTarget
|
|
*/
|
|
define('plupload/core/Queueable', [
|
|
'plupload',
|
|
'plupload/core/Optionable'
|
|
], function(plupload, Optionable) {
|
|
|
|
var dispatches = [
|
|
/**
|
|
* Dispatched every time the state of queue changes
|
|
*
|
|
* @event statechanged
|
|
* @param {Object} event
|
|
* @param {Number} state New state
|
|
* @param {Number} prevState Previous state
|
|
*/
|
|
'statechanged',
|
|
|
|
|
|
/**
|
|
* Dispatched when the item is put on pending list
|
|
*
|
|
* @event queued
|
|
* @param {Object} event
|
|
*/
|
|
'queued',
|
|
|
|
|
|
/**
|
|
* Dispatched as soon as activity starts
|
|
*
|
|
* @event started
|
|
* @param {Object} event
|
|
*/
|
|
'started',
|
|
|
|
|
|
'paused',
|
|
|
|
|
|
'resumed',
|
|
|
|
|
|
'stopped',
|
|
|
|
|
|
/**
|
|
* Dispatched as the activity progresses
|
|
*
|
|
* @event
|
|
* @param {Object} event
|
|
* @param {Number} event.percent
|
|
* @param {Number} [event.processed]
|
|
* @param {Number} [event.total]
|
|
*/
|
|
'progress',
|
|
|
|
|
|
'failed',
|
|
|
|
|
|
'done',
|
|
|
|
|
|
'processed',
|
|
|
|
'destroy'
|
|
];
|
|
|
|
|
|
return (function(Parent) {
|
|
|
|
function Queueable() {
|
|
Parent.apply(this, arguments);
|
|
|
|
/**
|
|
Unique identifier
|
|
@property uid
|
|
@type {String}
|
|
*/
|
|
this.uid = plupload.guid();
|
|
|
|
this.state = Queueable.IDLE;
|
|
|
|
this.processed = 0;
|
|
|
|
this.total = 0;
|
|
|
|
this.percent = 0;
|
|
|
|
this.retries = 0;
|
|
|
|
/**
|
|
* Can be 0-Infinity - item with higher priority will have well... higher priority
|
|
* @property [priority=0]
|
|
* @type {Number}
|
|
*/
|
|
this.priority = 0;
|
|
|
|
this.startedTimestamp = 0;
|
|
|
|
/**
|
|
* Set when item becomes Queueable.DONE or Queueable.FAILED.
|
|
* Used to calculate proper processedPerSec for the queue stats.
|
|
* @property processedTimestamp
|
|
* @type {Number}
|
|
*/
|
|
this.processedTimestamp = 0;
|
|
|
|
if (MXI_DEBUG) {
|
|
this.bind('StateChanged', function(e, state, oldState) {
|
|
var self = this;
|
|
|
|
var stateToString = function(code) {
|
|
switch (code) {
|
|
case Queueable.IDLE:
|
|
return 'IDLE';
|
|
|
|
case Queueable.PROCESSING:
|
|
return 'PROCESSING';
|
|
|
|
case Queueable.PAUSED:
|
|
return 'PAUSED';
|
|
|
|
case Queueable.RESUMED:
|
|
return 'RESUMED';
|
|
|
|
case Queueable.DONE:
|
|
return 'DONE';
|
|
|
|
case Queueable.FAILED:
|
|
return 'FAILED';
|
|
|
|
case Queueable.DESTROYED:
|
|
return 'DESTROYED';
|
|
}
|
|
};
|
|
|
|
var indent = function() {
|
|
switch (self.ctorName) {
|
|
case 'File':
|
|
return "\t".repeat(2);
|
|
|
|
case 'QueueUpload':
|
|
case 'QueueResize':
|
|
return "\t";
|
|
|
|
case 'FileUploader':
|
|
return "\t".repeat(3);
|
|
|
|
case 'ChunkUploader':
|
|
return "\t".repeat(4);
|
|
|
|
default:
|
|
return "\t";
|
|
}
|
|
};
|
|
|
|
plupload.ua.log("StateChanged:" + indent() + self.ctorName + '::' + self.uid + ' (' + stateToString(oldState) + ' to ' + stateToString(state) + ')');
|
|
}, 999);
|
|
}
|
|
}
|
|
|
|
Queueable.IDLE = 1;
|
|
Queueable.PROCESSING = 2;
|
|
Queueable.PAUSED = 6;
|
|
Queueable.RESUMED = 7;
|
|
Queueable.DONE = 5;
|
|
Queueable.FAILED = 4;
|
|
Queueable.DESTROYED = 8;
|
|
|
|
plupload.inherit(Queueable, Parent);
|
|
|
|
plupload.extend(Queueable.prototype, {
|
|
|
|
start: function() {
|
|
var prevState = this.state;
|
|
|
|
if (this.state === Queueable.PROCESSING) {
|
|
return false;
|
|
}
|
|
|
|
if (!this.startedTimestamp) {
|
|
this.startedTimestamp = +new Date();
|
|
}
|
|
|
|
this.state = Queueable.PROCESSING;
|
|
this.trigger('statechanged', this.state, prevState);
|
|
this.trigger('started');
|
|
|
|
return true;
|
|
},
|
|
|
|
|
|
pause: function() {
|
|
var prevState = this.state;
|
|
|
|
if (plupload.inArray(this.state, [Queueable.IDLE, Queueable.RESUMED, Queueable.PROCESSING]) === -1) {
|
|
return false;
|
|
}
|
|
|
|
this.processed = this.percent = 0; // by default reset all progress
|
|
this.loaded = this.processed; // for backward compatibility
|
|
|
|
this.state = Queueable.PAUSED;
|
|
this.trigger('statechanged', this.state, prevState);
|
|
this.trigger('paused');
|
|
return true;
|
|
},
|
|
|
|
|
|
resume: function() {
|
|
var prevState = this.state;
|
|
|
|
if (this.state !== Queueable.PAUSED && this.state !== Queueable.RESUMED) {
|
|
return false;
|
|
}
|
|
|
|
this.state = Queueable.RESUMED;
|
|
this.trigger('statechanged', this.state, prevState);
|
|
this.trigger('resumed');
|
|
return true;
|
|
},
|
|
|
|
|
|
stop: function() {
|
|
var prevState = this.state;
|
|
|
|
if (this.state === Queueable.IDLE) {
|
|
return false;
|
|
}
|
|
|
|
this.processed = this.percent = 0;
|
|
this.loaded = this.processed; // for backward compatibility
|
|
|
|
this.startedTimestamp = 0;
|
|
|
|
this.state = Queueable.IDLE;
|
|
this.trigger('statechanged', this.state, prevState);
|
|
this.trigger('stopped');
|
|
return true;
|
|
},
|
|
|
|
|
|
done: function(result) {
|
|
var prevState = this.state;
|
|
|
|
if (this.state === Queueable.DONE) {
|
|
return false;
|
|
}
|
|
|
|
this.processed = this.total;
|
|
this.loaded = this.processed; // for backward compatibility
|
|
this.percent = 100;
|
|
|
|
this.processedTimestamp = +new Date();
|
|
|
|
this.state = Queueable.DONE;
|
|
this.trigger('statechanged', this.state, prevState);
|
|
this.trigger('done', result);
|
|
this.trigger('processed');
|
|
return true;
|
|
},
|
|
|
|
|
|
failed: function(result) {
|
|
var prevState = this.state;
|
|
|
|
if (this.state === Queueable.FAILED) {
|
|
return false;
|
|
}
|
|
|
|
this.processed = this.percent = 0; // reset the progress
|
|
this.loaded = this.processed; // for backward compatibility
|
|
|
|
this.processedTimestamp = +new Date();
|
|
|
|
this.state = Queueable.FAILED;
|
|
this.trigger('statechanged', this.state, prevState);
|
|
this.trigger('failed', result);
|
|
this.trigger('processed');
|
|
return true;
|
|
},
|
|
|
|
|
|
progress: function(processed, total) {
|
|
if (total) {
|
|
this.total = total; // is this even required?
|
|
}
|
|
|
|
this.processed = Math.min(processed, this.total);
|
|
this.loaded = this.processed; // for backward compatibility
|
|
this.percent = Math.ceil(this.processed / this.total * 100);
|
|
|
|
this.trigger({
|
|
type: 'progress',
|
|
loaded: this.processed,
|
|
total: this.total
|
|
});
|
|
},
|
|
|
|
|
|
destroy: function() {
|
|
var prevState = this.state;
|
|
|
|
if (this.state === Queueable.DESTROYED) {
|
|
return false; // already destroyed
|
|
}
|
|
|
|
this.state = Queueable.DESTROYED;
|
|
this.trigger('statechanged', this.state, prevState);
|
|
this.trigger('destroy');
|
|
this.unbindAll();
|
|
return true;
|
|
}
|
|
|
|
});
|
|
|
|
return Queueable;
|
|
|
|
}(Optionable));
|
|
});
|
|
|
|
// Included from: src/core/Stats.js
|
|
|
|
/**
|
|
@class plupload.core.Stats
|
|
@constructor
|
|
@private
|
|
*/
|
|
define('plupload/core/Stats', [], function() {
|
|
|
|
return function() {
|
|
var self = this;
|
|
|
|
/**
|
|
* Total queue file size.
|
|
*
|
|
* @property size
|
|
* @deprecated use total
|
|
* @type Number
|
|
*/
|
|
self.size = 0;
|
|
|
|
/**
|
|
* Total size of the queue in units.
|
|
*
|
|
* @property total
|
|
* @since 3.0
|
|
* @type Number
|
|
*/
|
|
self.total = 0;
|
|
|
|
/**
|
|
* Total bytes uploaded.
|
|
*
|
|
* @property loaded
|
|
* @type Number
|
|
*/
|
|
self.loaded = 0;
|
|
|
|
|
|
/**
|
|
* Number of files uploaded successfully.
|
|
*
|
|
* @property uploaded
|
|
* @deprecated use done
|
|
* @type Number
|
|
*/
|
|
self.uploaded = 0;
|
|
|
|
/**
|
|
* Number of items processed successfully.
|
|
*
|
|
* @property done
|
|
* @since 3.0
|
|
* @type Number
|
|
*/
|
|
self.done = 0;
|
|
|
|
/**
|
|
* Number of failed items.
|
|
*
|
|
* @property failed
|
|
* @type Number
|
|
*/
|
|
self.failed = 0;
|
|
|
|
/**
|
|
* Number of items yet to be processed.
|
|
*
|
|
* @property queued
|
|
* @type Number
|
|
*/
|
|
self.queued = 0;
|
|
|
|
/**
|
|
* Number of items currently paused.
|
|
*
|
|
* @property paused
|
|
* @type Number
|
|
*/
|
|
self.paused = 0;
|
|
|
|
/**
|
|
* Number of items being processed.
|
|
*
|
|
* @property processing
|
|
* @type Number
|
|
*/
|
|
self.processing = 0;
|
|
|
|
|
|
/**
|
|
* Number of items being paused.
|
|
*
|
|
* @property paused
|
|
* @type Number
|
|
*/
|
|
self.paused = 0;
|
|
|
|
/**
|
|
* Percent of processed units.
|
|
*
|
|
* @property percent
|
|
* @type Number
|
|
*/
|
|
self.percent = 0;
|
|
|
|
/**
|
|
* Bytes processed per second.
|
|
*
|
|
* @property bytesPerSec
|
|
* @deprecated use processedPerSec
|
|
* @type Number
|
|
*/
|
|
self.bytesPerSec = 0;
|
|
|
|
/**
|
|
* Units processed per second.
|
|
*
|
|
* @property processedPerSec
|
|
* @since 3.0
|
|
* @type Number
|
|
*/
|
|
self.processedPerSec = 0;
|
|
|
|
/**
|
|
* Resets the progress to its initial values.
|
|
*
|
|
* @method reset
|
|
*/
|
|
self.reset = function() {
|
|
self.size = // deprecated
|
|
self.total =
|
|
self.loaded = // deprecated
|
|
self.processed =
|
|
self.uploaded = // deprecated
|
|
self.done =
|
|
self.failed =
|
|
self.queued =
|
|
self.processing =
|
|
self.paused =
|
|
self.percent =
|
|
self.bytesPerSec = // deprecated
|
|
self.processedPerSec = 0;
|
|
};
|
|
};
|
|
});
|
|
|
|
// Included from: src/core/Queue.js
|
|
|
|
/**
|
|
* Queue.js
|
|
*
|
|
* Copyright 2017, Ephox
|
|
* Released under AGPLv3 License.
|
|
*
|
|
* License: http://www.plupload.com/license
|
|
* Contributing: http://www.plupload.com/contributing
|
|
*/
|
|
|
|
|
|
/**
|
|
@contsructor
|
|
@class plupload.core.Queue
|
|
@private
|
|
*/
|
|
define('plupload/core/Queue', [
|
|
'plupload',
|
|
'plupload/core/ArrCollection',
|
|
'plupload/core/Queueable',
|
|
'plupload/core/Stats'
|
|
], function(plupload, ArrCollection, Queueable, Stats) {
|
|
|
|
var dispatches = [
|
|
/**
|
|
* Dispatched as soon as activity starts
|
|
*
|
|
* @event started
|
|
* @param {Object} event
|
|
*/
|
|
'Started',
|
|
|
|
|
|
/**
|
|
* Dispatched as activity progresses
|
|
*
|
|
* @event progress
|
|
* @param {Object} event
|
|
* @param {Number} processed
|
|
* @param {Number} total
|
|
* @param {plupload.core.Stats} stats
|
|
*/
|
|
'Progress',
|
|
|
|
/**
|
|
* Dispatched when activity is paused
|
|
*
|
|
* @event paused
|
|
* @param {Object} event
|
|
*/
|
|
'Paused',
|
|
|
|
/**
|
|
* Dispatched when there's no more items in processing
|
|
*
|
|
* @event done
|
|
* @param {Object} event
|
|
*/
|
|
'Done',
|
|
|
|
/**
|
|
* Dispatched as soon as activity ends
|
|
*
|
|
* @event stopped
|
|
* @param {Object} event
|
|
*/
|
|
'Stopped',
|
|
|
|
/**
|
|
* Dispatched when queue is destroyed
|
|
*
|
|
* @event destroy
|
|
* @param {Object} event
|
|
*/
|
|
'Destroy'
|
|
];
|
|
|
|
/**
|
|
* @class Queue
|
|
* @constructor
|
|
* @extends EventTarget
|
|
*/
|
|
return (function(Parent) {
|
|
plupload.inherit(Queue, Parent);
|
|
|
|
|
|
function Queue(options) {
|
|
Parent.apply(this, arguments);
|
|
|
|
/**
|
|
@property _queue
|
|
@type {Collection}
|
|
@private
|
|
*/
|
|
this._queue = new ArrCollection();
|
|
|
|
|
|
/**
|
|
@property stats
|
|
@type {Stats}
|
|
@readOnly
|
|
*/
|
|
this.stats = new Stats();
|
|
|
|
|
|
this._options = plupload.extend({}, this._options, {
|
|
max_slots: 1,
|
|
max_retries: 0,
|
|
auto_start: false,
|
|
finish_active: false
|
|
}, options);
|
|
}
|
|
|
|
plupload.extend(Queue.prototype, {
|
|
|
|
/**
|
|
* Returns number of items in the queue
|
|
*
|
|
* @method count
|
|
* @returns {Number}
|
|
*/
|
|
count: function() {
|
|
return this._queue.count();
|
|
},
|
|
|
|
/**
|
|
* Start the queue
|
|
*
|
|
* @method start
|
|
*/
|
|
start: function() {
|
|
if (!Queue.parent.start.call(this)) {
|
|
return false;
|
|
}
|
|
return processNext.call(this);
|
|
},
|
|
|
|
|
|
pause: function() {
|
|
if (!Queue.parent.pause.call(this)) {
|
|
return false;
|
|
}
|
|
|
|
this.forEachItem(function(item) {
|
|
item.pause();
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Stop the queue. If `finish_active=true` the queue will wait until active items are done, before
|
|
* stopping.
|
|
*
|
|
* @method stop
|
|
*/
|
|
stop: function() {
|
|
if (!Queue.parent.stop.call(this) || this.getOption('finish_active')) {
|
|
return false;
|
|
}
|
|
|
|
if (this.isActive()) {
|
|
this.forEachItem(function(item) {
|
|
item.stop();
|
|
});
|
|
}
|
|
},
|
|
|
|
|
|
forEachItem: function(cb) {
|
|
this._queue.each(cb);
|
|
},
|
|
|
|
|
|
getItem: function(uid) {
|
|
return this._queue.get(uid);
|
|
},
|
|
|
|
|
|
/**
|
|
* Add instance of Queueable to the queue. If `auto_start=true` queue will start as well.
|
|
*
|
|
* @method addItem
|
|
* @param {Queueable} item
|
|
*/
|
|
addItem: function(item) {
|
|
var self = this;
|
|
|
|
item.bind('Started', function() {
|
|
if (self.calcStats()) {
|
|
plupload.delay.call(self, processNext);
|
|
}
|
|
});
|
|
|
|
item.bind('Resumed',function() {
|
|
self.start();
|
|
});
|
|
|
|
item.bind('Paused', function() {
|
|
if (self.calcStats()) {
|
|
plupload.delay.call(self, function() {
|
|
if (!processNext.call(self) && !self.stats.processing) {
|
|
self.pause();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
item.bind('Processed Stopped', function() {
|
|
if (self.calcStats()) {
|
|
plupload.delay.call(self, function() {
|
|
if (!processNext.call(self) && !this.isStopped() && !this.isActive()) {
|
|
self.stop();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
item.bind('Progress', function() {
|
|
if (self.calcStats()) {
|
|
self.trigger('Progress', self.stats.processed, self.stats.total, self.stats);
|
|
}
|
|
});
|
|
|
|
item.bind('Failed', function() {
|
|
if (self.getOption('max_retries') && this.retries < self.getOption('max_retries')) {
|
|
this.stop();
|
|
this.retries++;
|
|
}
|
|
});
|
|
|
|
this._queue.add(item.uid, item);
|
|
this.calcStats();
|
|
item.trigger('Queued');
|
|
|
|
if (self.getOption('auto_start') || self.state === Queueable.PAUSED) {
|
|
plupload.delay.call(this, this.start);
|
|
}
|
|
},
|
|
|
|
|
|
/**
|
|
* Extracts item from the queue by its uid and returns it.
|
|
*
|
|
* @method extractItem
|
|
* @param {String} uid
|
|
* @return {Queueable} Item that was removed
|
|
*/
|
|
extractItem: function(uid) {
|
|
var item = this._queue.get(uid);
|
|
if (item) {
|
|
this.stopItem(item.uid);
|
|
this._queue.remove(uid);
|
|
this.calcStats();
|
|
}
|
|
return item;
|
|
},
|
|
|
|
/**
|
|
* Removes item from the queue and destroys it
|
|
*
|
|
* @method removeItem
|
|
* @param {String} uid
|
|
* @returns {Boolean} Result of the operation
|
|
*/
|
|
removeItem: function(uid) {
|
|
var item = this.extractItem(uid);
|
|
if (item) {
|
|
item.destroy();
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
|
|
|
|
stopItem: function(uid) {
|
|
var item = this._queue.get(uid);
|
|
if (item) {
|
|
return item.stop();
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
|
|
pauseItem: function(uid) {
|
|
var item = this._queue.get(uid);
|
|
if (item) {
|
|
return item.pause();
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
|
|
resumeItem: function(uid) {
|
|
var item = this._queue.get(uid);
|
|
if (item) {
|
|
plupload.delay.call(this, function() {
|
|
this.start(); // start() will know if it needs to restart the queue
|
|
});
|
|
return item.resume();
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
|
|
splice: function(start, length) {
|
|
return this._queue.splice(start, length);
|
|
},
|
|
|
|
|
|
isActive: function() {
|
|
return this.stats && (this.stats.processing || this.stats.paused);
|
|
},
|
|
|
|
isStopped: function() {
|
|
return this.state === Queueable.IDLE || this.state === Queueable.DESTROYED;
|
|
},
|
|
|
|
|
|
countSpareSlots: function() {
|
|
return Math.max(this.getOption('max_slots') - this.stats.processing, 0);
|
|
},
|
|
|
|
|
|
toArray: function() {
|
|
return this._queue.toArray();
|
|
},
|
|
|
|
|
|
clear: function() {
|
|
var self = this;
|
|
|
|
if (self.state !== Queueable.IDLE) {
|
|
// stop the active queue first
|
|
self.bindOnce('Stopped', function() {
|
|
self.clear();
|
|
});
|
|
return self.stop();
|
|
} else {
|
|
self._queue.clear();
|
|
self.stats.reset();
|
|
}
|
|
},
|
|
|
|
|
|
calcStats: function() {
|
|
var self = this;
|
|
var stats = self.stats;
|
|
var processed = 0;
|
|
var processedDuringThisSession = 0;
|
|
|
|
if (!stats) {
|
|
return false; // maybe queue is destroyed
|
|
}
|
|
|
|
stats.reset();
|
|
|
|
self.forEachItem(function(item) {
|
|
switch (item.state) {
|
|
case Queueable.DONE:
|
|
stats.done++;
|
|
stats.uploaded = stats.done; // for backward compatibility
|
|
break;
|
|
|
|
case Queueable.FAILED:
|
|
stats.failed++;
|
|
break;
|
|
|
|
case Queueable.PROCESSING:
|
|
stats.processing++;
|
|
break;
|
|
|
|
case Queueable.PAUSED:
|
|
stats.paused++;
|
|
break;
|
|
|
|
default:
|
|
stats.queued++;
|
|
}
|
|
|
|
processed += item.processed;
|
|
|
|
if (!item.processedTimestamp || item.processedTimestamp > self.startedTimestamp) {
|
|
processedDuringThisSession += processed;
|
|
}
|
|
|
|
stats.processedPerSec = Math.ceil(processedDuringThisSession / ((+new Date() - self.startedTimestamp || 1) / 1000.0));
|
|
|
|
stats.processed = processed;
|
|
stats.total += item.total;
|
|
if (stats.total) {
|
|
stats.percent = Math.ceil(stats.processed / stats.total * 100);
|
|
}
|
|
});
|
|
|
|
// enable properties inherited from Queueable
|
|
|
|
/* TODO: this is good but it currently conflicts with deprecated total property in Uploader
|
|
self.processed = stats.processed;
|
|
self.total = stats.total;
|
|
*/
|
|
self.percent = stats.percent;
|
|
|
|
// for backward compatibility
|
|
stats.loaded = stats.processed;
|
|
stats.size = stats.total;
|
|
stats.bytesPerSec = stats.processedPerSec;
|
|
|
|
return true;
|
|
},
|
|
|
|
|
|
destroy: function() {
|
|
var self = this;
|
|
|
|
if (self.state === Queueable.DESTROYED) {
|
|
return false; // already destroyed
|
|
}
|
|
|
|
if (self.state !== Queueable.IDLE) {
|
|
// stop the active queue first
|
|
self.bindOnce('Stopped', function() {
|
|
plupload.delay.call(self, self.destroy);
|
|
});
|
|
return self.stop();
|
|
} else {
|
|
self.clear();
|
|
Queue.parent.destroy.call(this);
|
|
self._queue = self.stats = null;
|
|
}
|
|
return true;
|
|
}
|
|
});
|
|
|
|
|
|
/**
|
|
* Returns another Queueable.IDLE or Queueable.RESUMED item, or null.
|
|
*/
|
|
function getNextIdleItem() {
|
|
var nextItem;
|
|
this.forEachItem(function(item) {
|
|
if (item.state === Queueable.IDLE || item.state === Queueable.RESUMED) {
|
|
nextItem = item;
|
|
return false;
|
|
}
|
|
});
|
|
return nextItem ? nextItem : null;
|
|
}
|
|
|
|
|
|
function processNext() {
|
|
var item;
|
|
|
|
if (this.state !== Queueable.PROCESSING && this.state !== Queueable.PAUSED) {
|
|
return false;
|
|
}
|
|
|
|
if (this.stats.processing < this.getOption('max_slots')) {
|
|
item = getNextIdleItem.call(this);
|
|
if (item) {
|
|
if (item.trigger('beforestart')) {
|
|
item.setOptions(this.getOptions());
|
|
return item.start();
|
|
} else {
|
|
item.pause();
|
|
// we need to call it sync, otherwise another thread may pick up the same file, while it is processed in beforestart handler
|
|
processNext.call(this);
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return Queue;
|
|
|
|
}(Queueable));
|
|
});
|
|
|
|
// Included from: src/QueueUpload.js
|
|
|
|
/**
|
|
* QueueUpload.js
|
|
*
|
|
* Copyright 2017, Ephox
|
|
* Released under AGPLv3 License.
|
|
*
|
|
* License: http://www.plupload.com/license
|
|
* Contributing: http://www.plupload.com/contributing
|
|
*/
|
|
|
|
|
|
/**
|
|
@class plupload.QueueUpload
|
|
@extends plupload.core.Queue
|
|
@constructor
|
|
@private
|
|
@final
|
|
@since 3.0
|
|
@param {Object} options
|
|
*/
|
|
define('plupload/QueueUpload', [
|
|
'plupload',
|
|
'plupload/core/Queue'
|
|
], function(plupload, Queue) {
|
|
|
|
return (function(Parent) {
|
|
plupload.inherit(QueueUpload, Parent);
|
|
|
|
function QueueUpload(options) {
|
|
|
|
Queue.call(this, {
|
|
max_slots: 1,
|
|
max_retries: 0,
|
|
auto_start: false,
|
|
finish_active: false,
|
|
url: false,
|
|
chunk_size: 0,
|
|
multipart: true,
|
|
http_method: 'POST',
|
|
params: {},
|
|
headers: false,
|
|
file_data_name: 'file',
|
|
send_file_name: true,
|
|
stop_on_fail: true
|
|
});
|
|
|
|
this.setOption = function(option, value) {
|
|
if (typeof(option) !== 'object') {
|
|
if (option == 'max_upload_slots') {
|
|
option = 'max_slots';
|
|
}
|
|
}
|
|
QueueUpload.prototype.setOption.call(this, option, value, true);
|
|
};
|
|
|
|
this.setOptions(options);
|
|
}
|
|
|
|
return QueueUpload;
|
|
}(Queue));
|
|
});
|
|
|
|
// Included from: src/QueueResize.js
|
|
|
|
/**
|
|
* QueueResize.js
|
|
*
|
|
* Copyright 2017, Ephox
|
|
* Released under AGPLv3 License.
|
|
*
|
|
* License: http://www.plupload.com/license
|
|
* Contributing: http://www.plupload.com/contributing
|
|
*/
|
|
|
|
|
|
/**
|
|
@class plupload.QueueResize
|
|
@extends plupload.core.Queue
|
|
@constructor
|
|
@private
|
|
@final
|
|
@since 3.0
|
|
@param {Object} options
|
|
*/
|
|
define('plupload/QueueResize', [
|
|
'plupload',
|
|
'plupload/core/Queue'
|
|
], function(plupload, Queue) {
|
|
|
|
return (function(Parent) {
|
|
plupload.inherit(QueueResize, Parent);
|
|
|
|
function QueueResize(options) {
|
|
|
|
Queue.call(this, {
|
|
max_slots: 1,
|
|
max_retries: 0,
|
|
auto_start: false,
|
|
finish_active: false,
|
|
resize: {}
|
|
});
|
|
|
|
this.setOption = function(option, value) {
|
|
if (typeof(option) !== 'object') {
|
|
if (option == 'max_resize_slots') {
|
|
option = 'max_slots';
|
|
}
|
|
}
|
|
QueueResize.prototype.setOption.call(this, option, value, true);
|
|
};
|
|
|
|
|
|
this.setOptions(options);
|
|
}
|
|
|
|
|
|
return QueueResize;
|
|
}(Queue));
|
|
});
|
|
|
|
// Included from: src/ChunkUploader.js
|
|
|
|
/**
|
|
* ChunkUploader.js
|
|
*
|
|
* Copyright 2017, Ephox
|
|
* Released under AGPLv3 License.
|
|
*
|
|
* License: http://www.plupload.com/license
|
|
* Contributing: http://www.plupload.com/contributing
|
|
*/
|
|
|
|
/**
|
|
* @class plupload.ChunkUploader
|
|
* @extends plupload.core.Queueable
|
|
* @constructor
|
|
* @private
|
|
* @final
|
|
* @constructor
|
|
*/
|
|
define('plupload/ChunkUploader', [
|
|
'plupload',
|
|
'plupload/core/Collection',
|
|
'plupload/core/Queueable'
|
|
], function(plupload, Collection, Queueable) {
|
|
var XMLHttpRequest = moxie.xhr.XMLHttpRequest;
|
|
var FormData = moxie.xhr.FormData;
|
|
|
|
function ChunkUploader(blob) {
|
|
var _xhr;
|
|
|
|
Queueable.call(this);
|
|
|
|
this._options = {
|
|
file_data_name: 'file',
|
|
headers: false,
|
|
http_method: 'POST',
|
|
multipart: true,
|
|
params: {},
|
|
send_file_name: true,
|
|
url: false
|
|
};
|
|
|
|
plupload.extend(this, {
|
|
|
|
start: function() {
|
|
var self = this;
|
|
var url;
|
|
var formData;
|
|
var prevState = this.state;
|
|
var options = self._options;
|
|
|
|
if (this.state === Queueable.PROCESSING) {
|
|
return false;
|
|
}
|
|
|
|
if (!this.startedTimestamp) {
|
|
this.startedTimestamp = +new Date();
|
|
}
|
|
|
|
this.state = Queueable.PROCESSING;
|
|
this.trigger('statechanged', this.state, prevState);
|
|
|
|
_xhr = new XMLHttpRequest();
|
|
|
|
if (_xhr.upload) {
|
|
_xhr.upload.onprogress = function(e) {
|
|
self.progress(e.loaded, e.total);
|
|
};
|
|
}
|
|
|
|
_xhr.onload = function() {
|
|
var result = {
|
|
response: this.responseText,
|
|
status: this.status,
|
|
responseHeaders: this.getAllResponseHeaders()
|
|
};
|
|
|
|
if (this.status < 200 || this.status >= 400) { // assume error
|
|
return self.failed(result);
|
|
}
|
|
|
|
self.done(result);
|
|
};
|
|
|
|
_xhr.onerror = function() {
|
|
self.failed(); // TODO: reason here
|
|
};
|
|
|
|
_xhr.onloadend = function() {
|
|
// we do not need _xhr anymore, so destroy it
|
|
setTimeout(function() { // we detach to sustain reference until all handlers are done
|
|
if (_xhr) {
|
|
_xhr.destroy();
|
|
_xhr = null;
|
|
}
|
|
}, 1);
|
|
};
|
|
|
|
try {
|
|
url = options.multipart ? options.url : buildUrl(options.url, options.params);
|
|
_xhr.open(options.http_method, url, true);
|
|
|
|
|
|
// headers must be set after request is already opened, otherwise INVALID_STATE_ERR exception will raise
|
|
if (!plupload.isEmptyObj(options.headers)) {
|
|
plupload.each(options.headers, function(val, key) {
|
|
_xhr.setRequestHeader(key, val);
|
|
});
|
|
}
|
|
|
|
|
|
if (options.multipart) {
|
|
formData = new FormData();
|
|
|
|
if (!plupload.isEmptyObj(options.params)) {
|
|
plupload.each(options.params, function(val, key) {
|
|
formData.append(key, val);
|
|
});
|
|
}
|
|
|
|
formData.append(options.file_data_name, blob);
|
|
|
|
_xhr.send(formData);
|
|
} else { // if no multipart, send as binary stream
|
|
if (plupload.isEmptyObj(options.headers) || !_xhr.hasRequestHeader('content-type')) {
|
|
_xhr.setRequestHeader('content-type', 'application/octet-stream'); // binary stream header
|
|
}
|
|
|
|
_xhr.send(blob);
|
|
}
|
|
|
|
this.trigger('started');
|
|
} catch(ex) {
|
|
self.failed();
|
|
}
|
|
},
|
|
|
|
|
|
stop: function() {
|
|
if (_xhr) {
|
|
_xhr.abort();
|
|
_xhr.destroy();
|
|
_xhr = null;
|
|
}
|
|
ChunkUploader.prototype.stop.call(this);
|
|
},
|
|
|
|
setOption: function(option, value) {
|
|
ChunkUploader.prototype.setOption.call(this, option, value, true);
|
|
},
|
|
|
|
setOptions: function(options) {
|
|
ChunkUploader.prototype.setOption.call(this, options, true);
|
|
},
|
|
|
|
destroy: function() {
|
|
this.stop();
|
|
ChunkUploader.prototype.destroy.call(this);
|
|
}
|
|
});
|
|
|
|
|
|
/**
|
|
* Builds a full url out of a base URL and an object with items to append as query string items.
|
|
*
|
|
* @method buildUrl
|
|
* @private
|
|
* @param {String} url Base URL to append query string items to.
|
|
* @param {Object} items Name/value object to serialize as a querystring.
|
|
* @return {String} String with url + serialized query string items.
|
|
*/
|
|
function buildUrl(url, items) {
|
|
var query = '';
|
|
|
|
plupload.each(items, function(value, name) {
|
|
query += (query ? '&' : '') + encodeURIComponent(name) + '=' + encodeURIComponent(value);
|
|
});
|
|
|
|
if (query) {
|
|
url += (url.indexOf('?') > 0 ? '&' : '?') + query;
|
|
}
|
|
|
|
return url;
|
|
}
|
|
|
|
}
|
|
|
|
plupload.inherit(ChunkUploader, Queueable);
|
|
|
|
return ChunkUploader;
|
|
});
|
|
|
|
// Included from: src/FileUploader.js
|
|
|
|
/**
|
|
* FileUploader.js
|
|
*
|
|
* Copyright 2017, Ephox
|
|
* Released under AGPLv3 License.se.
|
|
*
|
|
* License: http://www.plupload.com/license
|
|
* Contributing: http://www.plupload.com/contributing
|
|
*/
|
|
|
|
/**
|
|
* @class plupload.FileUploader
|
|
* @extends plupload.core.Queueable
|
|
* @constructor
|
|
* @since 3.0
|
|
* @final
|
|
*/
|
|
define('plupload/FileUploader', [
|
|
'plupload',
|
|
'plupload/core/Collection',
|
|
'plupload/core/Queueable',
|
|
'plupload/ChunkUploader'
|
|
], function(plupload, Collection, Queueable, ChunkUploader) {
|
|
|
|
|
|
function FileUploader(file, queue) {
|
|
var _chunks = new Collection();
|
|
var _totalChunks = 1;
|
|
|
|
Queueable.call(this);
|
|
|
|
this._options = {
|
|
chunk_size: 0,
|
|
params: {},
|
|
send_file_name: true,
|
|
stop_on_fail: true
|
|
};
|
|
|
|
plupload.extend(this, {
|
|
/**
|
|
When send_file_name is set to true, will be sent with the request as `name` param.
|
|
Can be used on server-side to override original file name.
|
|
|
|
@property name
|
|
@type {String}
|
|
*/
|
|
name: file.name,
|
|
|
|
|
|
start: function() {
|
|
var self = this;
|
|
var prevState = this.state;
|
|
var up;
|
|
|
|
if (this.state === Queueable.PROCESSING) {
|
|
return false;
|
|
}
|
|
|
|
if (!this.startedTimestamp) {
|
|
this.startedTimestamp = +new Date();
|
|
}
|
|
|
|
this.state = Queueable.PROCESSING;
|
|
this.trigger('statechanged', this.state, prevState);
|
|
|
|
// send additional 'name' parameter only if required or explicitly requested
|
|
if (self._options.send_file_name) {
|
|
self._options.params.name = self.target_name || self.name;
|
|
}
|
|
|
|
if (self._options.chunk_size) {
|
|
_totalChunks = Math.ceil(file.size / self._options.chunk_size);
|
|
self.uploadChunk(false, true);
|
|
} else {
|
|
up = new ChunkUploader(file);
|
|
|
|
up.bind('progress', function(e) {
|
|
self.progress(e.loaded, e.total);
|
|
});
|
|
|
|
up.bind('done', function(e, result) {
|
|
self.done(result);
|
|
});
|
|
|
|
up.bind('failed', function(e, result) {
|
|
self.failed(result);
|
|
});
|
|
|
|
up.setOptions(self._options);
|
|
|
|
queue.addItem(up);
|
|
}
|
|
|
|
this.trigger('started');
|
|
},
|
|
|
|
|
|
uploadChunk: function(seq, dontStop) {
|
|
var self = this;
|
|
var chunkSize = this.getOption('chunk_size');
|
|
var up;
|
|
var chunk = {};
|
|
var _options;
|
|
|
|
chunk.seq = parseInt(seq, 10) || getNextChunk();
|
|
chunk.start = chunk.seq * chunkSize;
|
|
chunk.end = Math.min(chunk.start + chunkSize, file.size);
|
|
chunk.total = file.size;
|
|
|
|
// do not proceed for weird chunks
|
|
if (chunk.start < 0 || chunk.start >= file.size) {
|
|
return false;
|
|
}
|
|
|
|
_options = plupload.extendImmutable({}, this.getOptions(), {
|
|
params: {
|
|
chunk: chunk.seq,
|
|
chunks: _totalChunks
|
|
}
|
|
});
|
|
|
|
up = new ChunkUploader(file.slice(chunk.start, chunk.end, file.type));
|
|
|
|
up.bind('progress', function(e) {
|
|
self.progress(calcProcessed() + e.loaded, file.size);
|
|
});
|
|
|
|
up.bind('failed', function(e, result) {
|
|
_chunks.add(chunk.seq, plupload.extend({
|
|
state: Queueable.FAILED
|
|
}, chunk));
|
|
|
|
self.trigger('chunkuploadfailed', plupload.extendImmutable({}, chunk, result));
|
|
|
|
if (_options.stop_on_fail) {
|
|
self.failed(result);
|
|
}
|
|
});
|
|
|
|
up.bind('done', function(e, result) {
|
|
_chunks.add(chunk.seq, plupload.extend({
|
|
state: Queueable.DONE
|
|
}, chunk));
|
|
|
|
self.trigger('chunkuploaded', plupload.extendImmutable({}, chunk, result));
|
|
|
|
if (calcProcessed() >= file.size) {
|
|
self.progress(file.size, file.size);
|
|
self.done(result); // obviously we are done
|
|
} else if (dontStop) {
|
|
plupload.delay(function() {
|
|
self.uploadChunk(getNextChunk(), dontStop);
|
|
});
|
|
}
|
|
});
|
|
|
|
up.bind('processed', function() {
|
|
this.destroy();
|
|
});
|
|
|
|
up.setOptions(_options);
|
|
|
|
_chunks.add(chunk.seq, plupload.extend({
|
|
state: Queueable.PROCESSING
|
|
}, chunk));
|
|
|
|
queue.addItem(up);
|
|
|
|
// enqueue even more chunks if slots available
|
|
if (dontStop && queue.countSpareSlots()) {
|
|
self.uploadChunk(getNextChunk(), dontStop);
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
destroy: function() {
|
|
FileUploader.prototype.destroy.call(this);
|
|
_chunks.clear();
|
|
}
|
|
});
|
|
|
|
|
|
function calcProcessed() {
|
|
var processed = 0;
|
|
|
|
_chunks.each(function(item) {
|
|
if (item.state === Queueable.DONE) {
|
|
processed += (item.end - item.start);
|
|
}
|
|
});
|
|
|
|
return processed;
|
|
}
|
|
|
|
|
|
function getNextChunk() {
|
|
var i = 0;
|
|
while (i < _totalChunks && _chunks.hasKey(i)) {
|
|
i++;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
plupload.inherit(FileUploader, Queueable);
|
|
|
|
return FileUploader;
|
|
});
|
|
|
|
// Included from: src/ImageResizer.js
|
|
|
|
/**
|
|
* ImageResizer.js
|
|
*
|
|
* Copyright 2017, Ephox
|
|
* Released under AGPLv3 License.
|
|
*
|
|
* License: http://www.plupload.com/license
|
|
* Contributing: http://www.plupload.com/contributing
|
|
*/
|
|
|
|
/**
|
|
@class plupload.ImageResizer
|
|
@extends plupload.core.Queueable
|
|
@constructor
|
|
@private
|
|
@final
|
|
@since 3.0
|
|
@param {plupload.File} fileRef
|
|
*/
|
|
define("plupload/ImageResizer", [
|
|
'plupload',
|
|
'plupload/core/Queueable'
|
|
], function(plupload, Queueable) {
|
|
var mxiImage = moxie.image.Image;
|
|
|
|
function ImageResizer(fileRef) {
|
|
|
|
Queueable.call(this);
|
|
|
|
this._options = {
|
|
type: 'image/jpeg',
|
|
quality: 90,
|
|
crop: false,
|
|
fit: true,
|
|
preserveHeaders: true,
|
|
resample: 'default',
|
|
multipass: true
|
|
};
|
|
|
|
this.setOption = function(option, value) {
|
|
if (typeof(option) !== 'object' && !this._options.hasOwnProperty(option)) {
|
|
return;
|
|
}
|
|
ImageResizer.prototype.setOption.apply(this, arguments);
|
|
};
|
|
|
|
|
|
this.start = function(options) {
|
|
var self = this;
|
|
var img;
|
|
|
|
if (options) {
|
|
this.setOptions(options.resize);
|
|
}
|
|
|
|
img = new mxiImage();
|
|
|
|
img.bind('load', function() {
|
|
this.resize(self.getOptions());
|
|
});
|
|
|
|
img.bind('resize', function() {
|
|
self.done(this.getAsBlob(self.getOption('type'), self.getOption('quality')));
|
|
this.destroy();
|
|
});
|
|
|
|
img.bind('error', function() {
|
|
self.failed();
|
|
this.destroy();
|
|
});
|
|
|
|
img.load(fileRef, self.getOption('runtimeOptions'));
|
|
};
|
|
}
|
|
|
|
plupload.inherit(ImageResizer, Queueable);
|
|
|
|
// ImageResizer is only included for builds with Image manipulation support, so we add plupload.Image here manually
|
|
plupload.Image = mxiImage;
|
|
|
|
return ImageResizer;
|
|
});
|
|
|
|
// Included from: src/File.js
|
|
|
|
/**
|
|
* File.js
|
|
*
|
|
* Copyright 2017, Ephox
|
|
* Released under AGPLv3 License.se.
|
|
*
|
|
* License: http://www.plupload.com/license
|
|
* Contributing: http://www.plupload.com/contributing
|
|
*/
|
|
|
|
/**
|
|
* @class plupload.File
|
|
* @extends plupload.core.Queueable
|
|
* @constructor
|
|
* @since 3.0
|
|
* @final
|
|
*/
|
|
define('plupload/File', [
|
|
'plupload',
|
|
'plupload/core/Queueable',
|
|
'plupload/FileUploader',
|
|
'plupload/ImageResizer'
|
|
], function(plupload, Queueable, FileUploader, ImageResizer) {
|
|
|
|
function File(file, queueUpload, queueResize) {
|
|
Queueable.call(this);
|
|
|
|
|
|
plupload.extend(this, {
|
|
/**
|
|
* For backward compatibility
|
|
*
|
|
* @property id
|
|
* @type {String}
|
|
* @deprecated
|
|
*/
|
|
id: this.uid,
|
|
|
|
|
|
/**
|
|
When send_file_name is set to true, will be sent with the request as `name` param.
|
|
Can be used on server-side to override original file name.
|
|
|
|
@property name
|
|
@type {String}
|
|
*/
|
|
name: file.name,
|
|
|
|
/**
|
|
@property target_name
|
|
@type {String}
|
|
@deprecated use name
|
|
*/
|
|
target_name: null,
|
|
|
|
/**
|
|
* File type, `e.g image/jpeg`
|
|
*
|
|
* @property type
|
|
* @type String
|
|
*/
|
|
type: file.type,
|
|
|
|
/**
|
|
* File size in bytes (may change after client-side manupilation).
|
|
*
|
|
* @property size
|
|
* @type Number
|
|
*/
|
|
size: file.size,
|
|
|
|
/**
|
|
* Original file size in bytes.
|
|
*
|
|
* @property origSize
|
|
* @type Number
|
|
*/
|
|
origSize: file.size,
|
|
|
|
start: function() {
|
|
var prevState = this.state;
|
|
|
|
if (this.state === Queueable.PROCESSING) {
|
|
return false;
|
|
}
|
|
|
|
this.state = Queueable.PROCESSING;
|
|
this.trigger('statechanged', this.state, prevState);
|
|
this.trigger('started');
|
|
|
|
if (!plupload.isEmptyObj(this._options.resize) && isImage(this.type) && runtimeCan(file, 'send_binary_string')) {
|
|
this.resizeAndUpload();
|
|
} else {
|
|
this.upload();
|
|
}
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
* Get the file for which this File is responsible
|
|
*
|
|
* @method getSource
|
|
* @returns {moxie.file.File}
|
|
*/
|
|
getSource: function() {
|
|
return file;
|
|
},
|
|
|
|
/**
|
|
* Returns file representation of the current runtime. For HTML5 runtime
|
|
* this is going to be native browser File object
|
|
* (for backward compatibility)
|
|
*
|
|
* @method getNative
|
|
* @deprecated
|
|
* @returns {File|Blob|Object}
|
|
*/
|
|
getNative: function() {
|
|
return this.getFile().getSource();
|
|
},
|
|
|
|
|
|
resizeAndUpload: function() {
|
|
var self = this;
|
|
var opts = self.getOptions();
|
|
var rszr = new ImageResizer(file);
|
|
|
|
rszr.bind('progress', function(e) {
|
|
self.progress(e.loaded, e.total);
|
|
});
|
|
|
|
rszr.bind('done', function(e, file) {
|
|
file = file;
|
|
self.upload();
|
|
});
|
|
|
|
rszr.bind('failed', function() {
|
|
self.upload();
|
|
});
|
|
|
|
rszr.setOption('runtimeOptions', {
|
|
runtime_order: opts.runtimes,
|
|
required_caps: opts.required_features,
|
|
preferred_caps: opts.preferred_caps,
|
|
swf_url: opts.flash_swf_url,
|
|
xap_url: opts.silverlight_xap_url
|
|
});
|
|
|
|
queueResize.addItem(rszr);
|
|
},
|
|
|
|
|
|
upload: function() {
|
|
var self = this;
|
|
var up = new FileUploader(file, queueUpload);
|
|
|
|
up.bind('paused', function() {
|
|
self.pause();
|
|
});
|
|
|
|
up.bind('resumed', function() {
|
|
this.start();
|
|
});
|
|
|
|
up.bind('started', function() {
|
|
self.trigger('startupload');
|
|
});
|
|
|
|
up.bind('progress', function(e) {
|
|
self.progress(e.loaded, e.total);
|
|
});
|
|
|
|
up.bind('done', function(e, result) {
|
|
self.done(result);
|
|
});
|
|
|
|
up.bind('failed', function(e, result) {
|
|
self.failed(result);
|
|
});
|
|
|
|
up.setOptions(self.getOptions());
|
|
|
|
up.start();
|
|
},
|
|
|
|
|
|
|
|
destroy: function() {
|
|
File.prototype.destroy.call(this);
|
|
file = null;
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
function isImage(type) {
|
|
return plupload.inArray(type, ['image/jpeg', 'image/png']) > -1;
|
|
}
|
|
|
|
|
|
function runtimeCan(blob, cap) {
|
|
if (blob.ruid) {
|
|
var info = plupload.Runtime.getInfo(blob.ruid);
|
|
if (info) {
|
|
return info.can(cap);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
plupload.inherit(File, Queueable);
|
|
|
|
return File;
|
|
});
|
|
|
|
// Included from: src/Uploader.js
|
|
|
|
/**
|
|
* Uploader.js
|
|
*
|
|
* Copyright 2017, Ephox
|
|
* Released under AGPLv3 License.
|
|
*
|
|
* License: http://www.plupload.com/license
|
|
* Contributing: http://www.plupload.com/contributing
|
|
*/
|
|
|
|
|
|
/**
|
|
@class plupload.Uploader
|
|
@extends plupload.core.Queue
|
|
@constructor
|
|
@public
|
|
@final
|
|
|
|
@param {Object} settings For detailed information about each option check documentation.
|
|
@param {String|DOMElement} settings.browse_button id of the DOM element or DOM element itself to use as file dialog trigger.
|
|
@param {Number|String} [settings.chunk_size=0] Chunk size in bytes to slice the file into. Shorcuts with b, kb, mb, gb, tb suffixes also supported. `e.g. 204800 or "204800b" or "200kb"`. By default - disabled.
|
|
@param {Boolean} [settings.send_chunk_number=true] Whether to send chunks and chunk numbers, or total and offset bytes.
|
|
@param {String|DOMElement} [settings.container] id of the DOM element or DOM element itself that will be used to wrap uploader structures. Defaults to immediate parent of the `browse_button` element.
|
|
@param {String|DOMElement} [settings.drop_element] id of the DOM element or DOM element itself to use as a drop zone for Drag-n-Drop.
|
|
@param {String} [settings.file_data_name="file"] Name for the file field in Multipart formated message.
|
|
@param {Object} [settings.filters={}] Set of file type filters.
|
|
@param {Array} [settings.filters.mime_types=[]] List of file types to accept, each one defined by title and list of extensions. `e.g. {title : "Image files", extensions : "jpg,jpeg,gif,png"}`. Dispatches `plupload.FILE_EXTENSION_ERROR`
|
|
@param {String|Number} [settings.filters.max_file_size=0] Maximum file size that the user can pick, in bytes. Optionally supports b, kb, mb, gb, tb suffixes. `e.g. "10mb" or "1gb"`. By default - not set. Dispatches `plupload.FILE_SIZE_ERROR`.
|
|
@param {Boolean} [settings.filters.prevent_duplicates=false] Do not let duplicates into the queue. Dispatches `plupload.FILE_DUPLICATE_ERROR`.
|
|
@param {String} [settings.flash_swf_url] URL of the Flash swf.
|
|
@param {Object} [settings.headers] Custom headers to send with the upload. Hash of name/value pairs.
|
|
@param {String} [settings.http_method="POST"] HTTP method to use during upload (only PUT or POST allowed).
|
|
@param {Number} [settings.max_retries=0] How many times to retry the chunk or file, before triggering Error event.
|
|
@param {Boolean} [settings.multipart=true] Whether to send file and additional parameters as Multipart formated message.
|
|
@param {Boolean} [settings.multi_selection=true] Enable ability to select multiple files at once in file dialog.
|
|
@param {Object} [settings.params] Hash of key/value pairs to send with every file upload.
|
|
@param {String|Object} [settings.required_features] Either comma-separated list or hash of required features that chosen runtime should absolutely possess.
|
|
@param {Object} [settings.resize] Enable resizing of images on client-side. Applies to `image/jpeg` and `image/png` only. `e.g. {width : 200, height : 200, quality : 90, crop: true}`
|
|
@param {Number} settings.resize.width Resulting width
|
|
@param {Number} [settings.resize.height=width] Resulting height (optional, if not supplied will default to width)
|
|
@param {String} [settings.resize.type='image/jpeg'] MIME type of the resulting image
|
|
@param {Number} [settings.resize.quality=90] In the case of JPEG, controls the quality of resulting image
|
|
@param {Boolean} [settings.resize.crop='cc'] If not falsy, image will be cropped, by default from center
|
|
@param {Boolean} [settings.resize.fit=true] In case of crop whether to upscale the image to fit the exact dimensions
|
|
@param {Boolean} [settings.resize.preserveHeaders=true] Whether to preserve meta headers (on JPEGs after resize)
|
|
@param {String} [settings.resize.resample='default'] Resampling algorithm to use during resize
|
|
@param {Boolean} [settings.resize.multipass=true] Whether to scale the image in steps (results in better quality)
|
|
@param {String} [settings.runtimes="html5,flash,silverlight,html4"] Comma separated list of runtimes, that Plupload will try in turn, moving to the next if previous fails.
|
|
@param {Boolean} [settings.send_file_name=true] Whether to send file name as additional argument - 'name' (required for chunked uploads and some other cases where file name cannot be sent via normal ways).
|
|
@param {String} [settings.silverlight_xap_url] URL of the Silverlight xap.
|
|
@param {Boolean} [settings.unique_names=false] If true will generate unique filenames for uploaded files.
|
|
@param {String} settings.url URL of the server-side upload handler.
|
|
*/
|
|
|
|
/**
|
|
Fires when the current RunTime has been initialized.
|
|
|
|
@event Init
|
|
@param {plupload.Uploader} uploader Uploader instance sending the event.
|
|
*/
|
|
|
|
/**
|
|
Fires after the init event incase you need to perform actions there.
|
|
|
|
@event PostInit
|
|
@param {plupload.Uploader} uploader Uploader instance sending the event.
|
|
*/
|
|
|
|
/**
|
|
Fires when the option is changed in via uploader.setOption().
|
|
|
|
@event OptionChanged
|
|
@since 2.1
|
|
@param {plupload.Uploader} uploader Uploader instance sending the event.
|
|
@param {String} name Name of the option that was changed
|
|
@param {Mixed} value New value for the specified option
|
|
@param {Mixed} oldValue Previous value of the option
|
|
*/
|
|
|
|
/**
|
|
Fires when the silverlight/flash or other shim needs to move.
|
|
|
|
@event Refresh
|
|
@param {plupload.Uploader} uploader Uploader instance sending the event.
|
|
*/
|
|
|
|
/**
|
|
Fires when the overall state is being changed for the upload queue.
|
|
|
|
@event StateChanged
|
|
@param {plupload.Uploader} uploader Uploader instance sending the event.
|
|
*/
|
|
|
|
/**
|
|
Fires when browse_button is clicked and browse dialog shows.
|
|
|
|
@event Browse
|
|
@since 2.1.2
|
|
@param {plupload.Uploader} uploader Uploader instance sending the event.
|
|
*/
|
|
|
|
/**
|
|
Fires for every filtered file before it is added to the queue.
|
|
|
|
@event FileFiltered
|
|
@since 2.1
|
|
@param {plupload.Uploader} uploader Uploader instance sending the event.
|
|
@param {plupload.File} file Another file that has to be added to the queue.
|
|
*/
|
|
|
|
/**
|
|
Fires when the file queue is changed. In other words when files are added/removed to the files array of the uploader instance.
|
|
|
|
@event QueueChanged
|
|
@param {plupload.Uploader} uploader Uploader instance sending the event.
|
|
*/
|
|
|
|
/**
|
|
Fires after files were filtered and added to the queue.
|
|
|
|
@event FilesAdded
|
|
@param {plupload.Uploader} uploader Uploader instance sending the event.
|
|
@param {Array} files Array of FileUploader objects that were added to the queue by user.
|
|
*/
|
|
|
|
/**
|
|
Fires when file is removed from the queue.
|
|
|
|
@event FilesRemoved
|
|
@param {plupload.Uploader} uploader Uploader instance sending the event.
|
|
@param {Array} files Array of files that got removed.
|
|
*/
|
|
|
|
/**
|
|
Fires just before a file is uploaded. Can be used to cancel upload of the current file
|
|
by returning false from the handler.
|
|
|
|
@event BeforeUpload
|
|
@param {plupload.Uploader} uploader Uploader instance sending the event.
|
|
@param {plupload.File} file File to be uploaded.
|
|
*/
|
|
|
|
/**
|
|
Fires when a file is to be uploaded by the runtime.
|
|
|
|
@event UploadFile
|
|
@param {plupload.Uploader} uploader Uploader instance sending the event.
|
|
@param {plupload.File} file File to be uploaded.
|
|
*/
|
|
|
|
/**
|
|
Fires while a file is being uploaded. Use this event to update the current file upload progress.
|
|
|
|
@event UploadProgress
|
|
@param {plupload.Uploader} uploader Uploader instance sending the event.
|
|
@param {plupload.File} file File that is currently being uploaded.
|
|
*/
|
|
|
|
/**
|
|
Fires when file chunk is uploaded.
|
|
|
|
@event ChunkUploaded
|
|
@param {plupload.Uploader} uploader Uploader instance sending the event.
|
|
@param {plupload.File} file File that the chunk was uploaded for.
|
|
@param {Object} result Object with response properties.
|
|
@param {Number} result.offset The amount of bytes the server has received so far, including this chunk.
|
|
@param {Number} result.total The size of the file.
|
|
@param {String} result.response The response body sent by the server.
|
|
@param {Number} result.status The HTTP status code sent by the server.
|
|
@param {String} result.responseHeaders All the response headers as a single string.
|
|
*/
|
|
|
|
/**
|
|
Fires when a file is successfully uploaded.
|
|
|
|
@event FileUploaded
|
|
@param {plupload.Uploader} uploader Uploader instance sending the event.
|
|
@param {plupload.File} file File that was uploaded.
|
|
@param {Object} result Object with response properties.
|
|
@param {String} result.response The response body sent by the server.
|
|
@param {Number} result.status The HTTP status code sent by the server.
|
|
@param {String} result.responseHeaders All the response headers as a single string.
|
|
*/
|
|
|
|
/**
|
|
Fires when all files in a queue are uploaded
|
|
|
|
@event UploadComplete
|
|
@param {plupload.Uploader} uploader Uploader instance sending the event.
|
|
*/
|
|
|
|
|
|
/**
|
|
Fires whenever upload is aborted for some reason
|
|
|
|
@event CancelUpload
|
|
@param {plupload.Uploader} uploader Uploader instance sending the event.
|
|
*/
|
|
|
|
/**
|
|
Fires when a error occurs.
|
|
|
|
@event Error
|
|
@param {plupload.Uploader} uploader Uploader instance sending the event.
|
|
@param {Object} error Contains code, message and sometimes file and other details.
|
|
@param {Number} error.code The plupload error code.
|
|
@param {String} error.message Description of the error (uses i18n).
|
|
*/
|
|
|
|
/**
|
|
Fires when destroy method is called.
|
|
|
|
@event Destroy
|
|
@param {plupload.Uploader} uploader Uploader instance sending the event.
|
|
*/
|
|
define('plupload/Uploader', [
|
|
'plupload',
|
|
'plupload/core/Collection',
|
|
'plupload/core/Queue',
|
|
'plupload/QueueUpload',
|
|
'plupload/QueueResize',
|
|
'plupload/File'
|
|
], function(plupload, Collection, Queue, QueueUpload, QueueResize, PluploadFile) {
|
|
|
|
var fileFilters = {};
|
|
var undef;
|
|
|
|
|
|
function Uploader(options) {
|
|
var _fileInputs = [];
|
|
var _fileDrops = [];
|
|
var _queueUpload, _queueResize;
|
|
var _initialized = false;
|
|
var _disabled = false;
|
|
|
|
var _options = normalizeOptions(plupload.extend({
|
|
backward_compatibility: true,
|
|
chunk_size: 0,
|
|
file_data_name: 'file',
|
|
filters: {
|
|
mime_types: '*',
|
|
prevent_duplicates: false,
|
|
max_file_size: 0
|
|
},
|
|
flash_swf_url: 'js/Moxie.swf',
|
|
// @since 2.3
|
|
http_method: 'POST',
|
|
// headers: false, // Plupload had a required feature with the same name, comment it to avoid confusion
|
|
max_resize_slots: 1,
|
|
max_retries: 0,
|
|
max_upload_slots: 1,
|
|
multipart: true,
|
|
multipart_params: {}, // deprecated, use - params,
|
|
multi_selection: true,
|
|
// @since 3
|
|
params: {},
|
|
resize: false,
|
|
runtimes: plupload.Runtime.order,
|
|
send_chunk_number: true, // whether to send chunks and chunk numbers, instead of total and offset bytes
|
|
send_file_name: true,
|
|
silverlight_xap_url: 'js/Moxie.xap',
|
|
|
|
// during normalization, these should be processed last
|
|
required_features: false,
|
|
preferred_caps: false
|
|
}, options));
|
|
|
|
Queue.call(this);
|
|
|
|
|
|
// Add public methods
|
|
plupload.extend(this, {
|
|
|
|
_options: _options,
|
|
|
|
/**
|
|
* Unique id for the Uploader instance.
|
|
*
|
|
* @property id
|
|
* @type String
|
|
*/
|
|
id: this.uid,
|
|
|
|
/**
|
|
* Current state of the total uploading progress. This one can either be plupload.STARTED or plupload.STOPPED.
|
|
* These states are controlled by the stop/start methods. The default value is STOPPED.
|
|
*
|
|
* @property state
|
|
* @type Number
|
|
*/
|
|
state: plupload.STOPPED,
|
|
|
|
/**
|
|
* Map of features that are available for the uploader runtime. Features will be filled
|
|
* before the init event is called, these features can then be used to alter the UI for the end user.
|
|
* Some of the current features that might be in this map is: dragdrop, chunks, jpgresize, pngresize.
|
|
*
|
|
* @property features
|
|
* @type Object
|
|
* @deprecated
|
|
*/
|
|
features: {},
|
|
|
|
/**
|
|
* Object with name/value settings.
|
|
*
|
|
* @property settings
|
|
* @type Object
|
|
* @deprecated Use `getOption()/setOption()`
|
|
*/
|
|
settings : _options,
|
|
|
|
/**
|
|
* Current runtime name
|
|
*
|
|
* @property runtime
|
|
* @type String
|
|
* @deprecated There might be multiple runtimes per uploader
|
|
*/
|
|
runtime: null,
|
|
|
|
/**
|
|
* Current upload queue, an array of File instances
|
|
*
|
|
* @property files
|
|
* @deprecated use forEachItem(callback) to cycle over the items in the queue
|
|
* @type Array
|
|
*/
|
|
files: [],
|
|
|
|
/**
|
|
* Total progess information. How many files has been uploaded, total percent etc.
|
|
*
|
|
* @property total
|
|
* @deprecated use stats
|
|
*/
|
|
total: this.stats,
|
|
|
|
/**
|
|
* Initializes the Uploader instance and adds internal event listeners.
|
|
*
|
|
* @method init
|
|
*/
|
|
init: function() {
|
|
var self = this, preinitOpt, err;
|
|
|
|
preinitOpt = self.getOption('preinit');
|
|
if (typeof(preinitOpt) == "function") {
|
|
preinitOpt(self);
|
|
} else {
|
|
plupload.each(preinitOpt, function(func, name) {
|
|
self.bind(name, func);
|
|
});
|
|
}
|
|
|
|
bindEventListeners.call(self);
|
|
|
|
// Check for required options
|
|
plupload.each(['container', 'browse_button', 'drop_element'], function(el) {
|
|
if (self.getOption(el) === null) {
|
|
err = {
|
|
code: plupload.INIT_ERROR,
|
|
message: plupload.sprintf(plupload.translate("%s specified, but cannot be found."), el)
|
|
}
|
|
return false;
|
|
}
|
|
});
|
|
|
|
if (err) {
|
|
return self.trigger('Error', err);
|
|
}
|
|
|
|
|
|
if (!self.getOption('browse_button') && !self.getOption('drop_element')) {
|
|
return self.trigger('Error', {
|
|
code: plupload.INIT_ERROR,
|
|
message: plupload.translate("You must specify either browse_button or drop_element.")
|
|
});
|
|
}
|
|
|
|
|
|
initControls.call(self, function(initialized) {
|
|
var runtime;
|
|
var initOpt = self.getOption('init');
|
|
var queueOpts = plupload.extendImmutable({}, self.getOption(), { auto_start: true });
|
|
|
|
if (typeof(initOpt) == "function") {
|
|
initOpt(self);
|
|
} else {
|
|
plupload.each(initOpt, function(func, name) {
|
|
self.bind(name, func);
|
|
});
|
|
}
|
|
|
|
if (initialized) {
|
|
_initialized = true;
|
|
runtime = plupload.Runtime.getInfo(getRUID());
|
|
|
|
_queueUpload = new QueueUpload(queueOpts);
|
|
_queueResize = new QueueResize(queueOpts);
|
|
|
|
self.trigger('Init', {
|
|
ruid: runtime.uid,
|
|
runtime: self.runtime = runtime.type
|
|
});
|
|
|
|
self.trigger('PostInit');
|
|
} else {
|
|
self.trigger('Error', {
|
|
code: plupload.INIT_ERROR,
|
|
message: plupload.translate('Init error.')
|
|
});
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Set the value for the specified option(s).
|
|
*
|
|
* @method setOption
|
|
* @since 2.1
|
|
* @param {String|Object} option Name of the option to change or the set of key/value pairs
|
|
* @param {Mixed} [value] Value for the option (is ignored, if first argument is object)
|
|
*/
|
|
setOption: function(option, value) {
|
|
if (_initialized) {
|
|
// following options cannot be changed after initialization
|
|
if (plupload.inArray(option, [
|
|
'container',
|
|
'browse_button',
|
|
'drop_element',
|
|
'runtimes',
|
|
'multi_selection',
|
|
'flash_swf_url',
|
|
'silverlight_xap_url'
|
|
]) > -1) {
|
|
return this.trigger('Error', {
|
|
code: plupload.OPTION_ERROR,
|
|
message: plupload.sprintf(plupload.translate("%s option cannot be changed.")),
|
|
option: option
|
|
});
|
|
}
|
|
}
|
|
|
|
if (typeof(option) !== 'object') {
|
|
value = normalizeOption(option, value, this._options);
|
|
|
|
// queues will take in only appropriate options
|
|
if (_queueUpload) {
|
|
_queueUpload.setOption(option, value);
|
|
}
|
|
if (_queueResize) {
|
|
_queueResize.setOption(option, value);
|
|
}
|
|
}
|
|
|
|
Uploader.prototype.setOption.call(this, option, value);
|
|
},
|
|
|
|
/**
|
|
* Refreshes the upload instance by dispatching out a refresh event to all runtimes.
|
|
* This would for example reposition flash/silverlight shims on the page.
|
|
*
|
|
* @method refresh
|
|
*/
|
|
refresh: function() {
|
|
if (_fileInputs.length) {
|
|
plupload.each(_fileInputs, function(fileInput) {
|
|
fileInput.trigger('Refresh');
|
|
});
|
|
}
|
|
|
|
if (_fileDrops.length) {
|
|
plupload.each(_fileDrops, function(fileDrops) {
|
|
fileDrops.trigger('Refresh');
|
|
});
|
|
}
|
|
|
|
this.trigger('Refresh');
|
|
},
|
|
|
|
/**
|
|
* Stops the upload of the queued files.
|
|
*
|
|
* @method stop
|
|
*/
|
|
stop: function() {
|
|
if (Uploader.prototype.stop.call(this) && this.state != plupload.STOPPED) {
|
|
this.trigger('CancelUpload');
|
|
}
|
|
},
|
|
|
|
|
|
/**
|
|
* Disables/enables browse button on request.
|
|
*
|
|
* @method disableBrowse
|
|
* @param {Boolean} disable Whether to disable or enable (default: true)
|
|
*/
|
|
disableBrowse: function() {
|
|
_disabled = arguments[0] !== undef ? arguments[0] : true;
|
|
|
|
if (_fileInputs.length) {
|
|
plupload.each(_fileInputs, function(fileInput) {
|
|
fileInput.disable(_disabled);
|
|
});
|
|
}
|
|
|
|
this.trigger('DisableBrowse', _disabled);
|
|
},
|
|
|
|
/**
|
|
* Returns the specified FileUploader object by id
|
|
*
|
|
* @method getFile
|
|
* @deprecated use getItem()
|
|
* @param {String} id FileUploader id to look for
|
|
* @return {plupload.FileUploader}
|
|
*/
|
|
getFile: function(id) {
|
|
return this.getItem(id);
|
|
},
|
|
|
|
/**
|
|
* Adds file to the queue programmatically. Can be native file, instance of Plupload.File,
|
|
* instance of mOxie.File, input[type="file"] element, or array of these. Fires FilesAdded,
|
|
* if any files were added to the queue. Otherwise nothing happens.
|
|
*
|
|
* @method addFile
|
|
* @since 2.0
|
|
* @param {plupload.File|mOxie.File|File|Node|Array} file File or files to add to the queue.
|
|
* @param {String} [fileName] If specified, will be used as a name for the file
|
|
*/
|
|
addFile: function(file, fileName) {
|
|
var self = this;
|
|
var queue = [];
|
|
var ruid; // spare runtime uid, for those files that do not have their own
|
|
var filesAdded = []; // here we track the files that got filtered and are added to the queue
|
|
|
|
|
|
function bindListeners(fileUp) {
|
|
fileUp.bind('beforestart', function(e) {
|
|
return self.trigger('BeforeUpload', e.target);
|
|
});
|
|
|
|
fileUp.bind('startupload', function() {
|
|
self.trigger('UploadFile', this);
|
|
});
|
|
|
|
fileUp.bind('progress', function() {
|
|
self.trigger('UploadProgress', this);
|
|
});
|
|
|
|
fileUp.bind('done', function(e, args) {
|
|
self.trigger('FileUploaded', this, args);
|
|
});
|
|
|
|
fileUp.bind('failed', function(e, err) {
|
|
self.trigger('Error', plupload.extend({
|
|
code: plupload.HTTP_ERROR,
|
|
message: plupload.translate('HTTP Error.'),
|
|
file: this
|
|
}, err));
|
|
});
|
|
}
|
|
|
|
|
|
function filterFile(file, cb) {
|
|
var queue = [];
|
|
plupload.each(self.getOption('filters'), function(rule, name) {
|
|
if (fileFilters[name]) {
|
|
queue.push(function(cb) {
|
|
fileFilters[name].call(self, rule, file, function(res) {
|
|
cb(!res);
|
|
});
|
|
});
|
|
}
|
|
});
|
|
plupload.inParallel(queue, cb);
|
|
}
|
|
|
|
/**
|
|
* @method resolveFile
|
|
* @private
|
|
* @param {mxiFile|mxiBlob|FileUploader|File|Blob|input[type="file"]} file
|
|
*/
|
|
function resolveFile(file) {
|
|
var type = plupload.typeOf(file);
|
|
|
|
// mxiFile (final step for other conditional branches)
|
|
if (file instanceof moxie.file.File) {
|
|
if (!file.ruid && !file.isDetached()) {
|
|
if (!ruid) { // weird case
|
|
return false;
|
|
}
|
|
file.ruid = ruid;
|
|
file.connectRuntime(ruid);
|
|
}
|
|
|
|
queue.push(function(cb) {
|
|
// run through the internal and user-defined filters, if any
|
|
filterFile(file, function(err) {
|
|
var fileUp;
|
|
|
|
if (!err) {
|
|
fileUp = new PluploadFile(file, _queueUpload, _queueResize);
|
|
|
|
if (fileName) {
|
|
fileUp.name = fileName;
|
|
}
|
|
|
|
bindListeners(fileUp);
|
|
|
|
self.addItem(fileUp); // make files available for the filters by updating the main queue directly
|
|
filesAdded.push(fileUp);
|
|
self.trigger("FileFiltered", fileUp);
|
|
}
|
|
|
|
plupload.delay(cb); // do not build up recursions or eventually we might hit the limits
|
|
});
|
|
});
|
|
}
|
|
// mxiBlob
|
|
else if (file instanceof moxie.file.Blob) {
|
|
resolveFile(file.getSource());
|
|
file.destroy();
|
|
}
|
|
// native File or blob
|
|
else if (plupload.inArray(type, ['file', 'blob']) !== -1) {
|
|
resolveFile(new moxie.file.File(null, file));
|
|
}
|
|
// input[type="file"]
|
|
else if (type === 'node' && plupload.typeOf(file.files) === 'filelist') {
|
|
// if we are dealing with input[type="file"]
|
|
plupload.each(file.files, resolveFile);
|
|
}
|
|
// mixed array of any supported types (see above)
|
|
else if (type === 'array') {
|
|
fileName = null; // should never happen, but unset anyway to avoid funny situations
|
|
plupload.each(file, resolveFile);
|
|
}
|
|
}
|
|
|
|
ruid = getRUID();
|
|
|
|
resolveFile(file);
|
|
|
|
if (queue.length) {
|
|
plupload.inParallel(queue, function() {
|
|
// if any files left after filtration, trigger FilesAdded
|
|
if (filesAdded.length) {
|
|
self.trigger("FilesAdded", filesAdded);
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Removes a specific item from the queue
|
|
*
|
|
* @method removeFile
|
|
* @param {plupload.FileUploader|String} file
|
|
*/
|
|
removeFile: function(file) {
|
|
var item = this.extractItem(typeof(file) === 'string' ? file : file.uid);
|
|
if (item) {
|
|
this.trigger("FilesRemoved", [item]);
|
|
item.destroy();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Removes part of the queue and returns removed files.
|
|
* Triggers FilesRemoved and consequently QueueChanged events.
|
|
*
|
|
* @method splice
|
|
* @param {Number} [start=0] Start index to remove from
|
|
* @param {Number} [length] Length of items to remove
|
|
*/
|
|
splice: function() {
|
|
var i = 0;
|
|
var shouldRestart = plupload.STARTED == this.state;
|
|
|
|
var removed = Queue.prototype.splice.apply(this, arguments);
|
|
if (removed.length) {
|
|
this.trigger("FilesRemoved", removed);
|
|
|
|
if (shouldRestart) {
|
|
this.stop();
|
|
}
|
|
|
|
for (i = 0; i < removed.length; i++) {
|
|
removed[i].destroy();
|
|
}
|
|
|
|
if (shouldRestart) {
|
|
this.start();
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
Dispatches the specified event name and its arguments to all listeners.
|
|
|
|
@method trigger
|
|
@param {String} name Event name to fire.
|
|
@param {Object..} Multiple arguments to pass along to the listener functions.
|
|
*/
|
|
|
|
// override the parent method to match Plupload-like event logic
|
|
dispatchEvent: function(type) {
|
|
var list, args, result;
|
|
|
|
type = type.toLowerCase();
|
|
|
|
list = this.hasEventListener(type);
|
|
|
|
if (list) {
|
|
// sort event list by priority
|
|
list.sort(function(a, b) {
|
|
return b.priority - a.priority;
|
|
});
|
|
|
|
// first argument should be current plupload.Uploader instance
|
|
args = [].slice.call(arguments);
|
|
args.shift();
|
|
args.unshift(this);
|
|
|
|
for (var i = 0; i < list.length; i++) {
|
|
// Fire event, break chain if false is returned
|
|
if (list[i].fn.apply(list[i].scope, args) === false) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
Check whether uploader has any listeners to the specified event.
|
|
|
|
@method hasEventListener
|
|
@param {String} name Event name to check for.
|
|
*/
|
|
|
|
|
|
/**
|
|
Adds an event listener by name.
|
|
|
|
@method bind
|
|
@param {String} name Event name to listen for.
|
|
@param {function} fn Function to call ones the event gets fired.
|
|
@param {Object} [scope] Optional scope to execute the specified function in.
|
|
@param {Number} [priority=0] Priority of the event handler - handlers with higher priorities will be called first
|
|
*/
|
|
bind: function(name, fn, scope, priority) {
|
|
// adapt moxie EventTarget style to Plupload-like
|
|
plupload.Uploader.prototype.bind.call(this, name, fn, priority, scope);
|
|
}
|
|
|
|
/**
|
|
Removes the specified event listener.
|
|
|
|
@method unbind
|
|
@param {String} name Name of event to remove.
|
|
@param {function} fn Function to remove from listener.
|
|
*/
|
|
|
|
/**
|
|
Removes all event listeners.
|
|
|
|
@method unbindAll
|
|
*/
|
|
});
|
|
|
|
|
|
// keep alive deprecated properties
|
|
if (_options.backward_compatibility) {
|
|
this.bind('FilesAdded FilesRemoved', function (up) {
|
|
up.files = up.toArray();
|
|
}, this, 999);
|
|
|
|
this.bind('OptionChanged', function (up, name, value) {
|
|
up.settings[name] = typeof(value) == 'object' ? plupload.extend({}, value) : value;
|
|
}, this, 999);
|
|
}
|
|
|
|
|
|
function getRUID() {
|
|
var ctrl = _fileInputs[0] || _fileDrops[0];
|
|
if (ctrl) {
|
|
return ctrl.getRuntime().uid;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
function bindEventListeners() {
|
|
this.bind('FilesAdded FilesRemoved', function(up) {
|
|
up.trigger('QueueChanged');
|
|
up.refresh();
|
|
}, this, 999);
|
|
|
|
this.bind('BeforeUpload', onBeforeUpload);
|
|
|
|
this.bind('Stopped', function(up) {
|
|
up.trigger('UploadComplete');
|
|
});
|
|
|
|
this.bind('Error', onError);
|
|
|
|
this.bind('Destroy', onDestroy);
|
|
}
|
|
|
|
|
|
function initControls(cb) {
|
|
var self = this;
|
|
var initialized = 0;
|
|
var queue = [];
|
|
|
|
// common settings
|
|
var options = {
|
|
runtime_order: self.getOption('runtimes'),
|
|
required_caps: self.getOption('required_features'),
|
|
preferred_caps: self.getOption('preferred_caps'),
|
|
swf_url: self.getOption('flash_swf_url'),
|
|
xap_url: self.getOption('silverlight_xap_url')
|
|
};
|
|
|
|
// add runtime specific options if any
|
|
plupload.each(self.getOption('runtimes').split(/\s*,\s*/), function(runtime) {
|
|
if (self.getOption(runtime)) {
|
|
options[runtime] = self.getOption(runtime);
|
|
}
|
|
});
|
|
|
|
// initialize file pickers - there can be many
|
|
if (self.getOption('browse_button')) {
|
|
plupload.each(self.getOption('browse_button'), function(el) {
|
|
queue.push(function(cb) {
|
|
var fileInput = new moxie.file.FileInput(plupload.extend({}, options, {
|
|
accept: self.getOption('filters').mime_types,
|
|
name: self.getOption('file_data_name'),
|
|
multiple: self.getOption('multi_selection'),
|
|
container: self.getOption('container'),
|
|
browse_button: el
|
|
}));
|
|
|
|
fileInput.onready = function() {
|
|
var info = plupload.Runtime.getInfo(this.ruid);
|
|
|
|
// for backward compatibility
|
|
plupload.extend(self.features, {
|
|
chunks: info.can('slice_blob'),
|
|
multipart: info.can('send_multipart'),
|
|
multi_selection: info.can('select_multiple')
|
|
});
|
|
|
|
initialized++;
|
|
_fileInputs.push(this);
|
|
cb();
|
|
};
|
|
|
|
fileInput.onchange = function() {
|
|
self.addFile(this.files);
|
|
};
|
|
|
|
fileInput.bind('mouseenter mouseleave mousedown mouseup', function(e) {
|
|
if (!_disabled) {
|
|
if (self.getOption('browse_button_hover')) {
|
|
if ('mouseenter' === e.type) {
|
|
plupload.addClass(el, self.getOption('browse_button_hover'));
|
|
} else if ('mouseleave' === e.type) {
|
|
plupload.removeClass(el, self.getOption('browse_button_hover'));
|
|
}
|
|
}
|
|
|
|
if (self.getOption('browse_button_active')) {
|
|
if ('mousedown' === e.type) {
|
|
plupload.addClass(el, self.getOption('browse_button_active'));
|
|
} else if ('mouseup' === e.type) {
|
|
plupload.removeClass(el, self.getOption('browse_button_active'));
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
fileInput.bind('mousedown', function() {
|
|
self.trigger('Browse');
|
|
});
|
|
|
|
fileInput.bind('error runtimeerror', function() {
|
|
fileInput = null;
|
|
cb();
|
|
});
|
|
|
|
fileInput.init();
|
|
});
|
|
});
|
|
}
|
|
|
|
// initialize drop zones
|
|
if (self.getOption('drop_element')) {
|
|
plupload.each(self.getOption('drop_element'), function(el) {
|
|
queue.push(function(cb) {
|
|
var fileDrop = new moxie.file.FileDrop(plupload.extend({}, options, {
|
|
drop_zone: el
|
|
}));
|
|
|
|
fileDrop.onready = function() {
|
|
var info = plupload.Runtime.getInfo(this.ruid);
|
|
|
|
// for backward compatibility
|
|
plupload.extend(self.features, {
|
|
chunks: info.can('slice_blob'),
|
|
multipart: info.can('send_multipart'),
|
|
dragdrop: info.can('drag_and_drop')
|
|
});
|
|
|
|
initialized++;
|
|
_fileDrops.push(this);
|
|
cb();
|
|
};
|
|
|
|
fileDrop.ondrop = function() {
|
|
self.addFile(this.files);
|
|
};
|
|
|
|
fileDrop.bind('error runtimeerror', function() {
|
|
fileDrop = null;
|
|
cb();
|
|
});
|
|
|
|
fileDrop.init();
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
plupload.inParallel(queue, function() {
|
|
if (typeof(cb) === 'function') {
|
|
cb(initialized);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
// Internal event handlers
|
|
function onBeforeUpload(up, file) {
|
|
// Generate unique target filenames
|
|
if (up.getOption('unique_names')) {
|
|
var matches = file.name.match(/\.([^.]+)$/),
|
|
ext = "part";
|
|
if (matches) {
|
|
ext = matches[1];
|
|
}
|
|
file.target_name = file.id + '.' + ext;
|
|
}
|
|
}
|
|
|
|
|
|
function onError(up, err) {
|
|
if (err.code === plupload.INIT_ERROR) {
|
|
up.destroy();
|
|
}
|
|
else if (err.code === plupload.HTTP_ERROR && up.state == plupload.STARTED) {
|
|
up.trigger('CancelUpload');
|
|
}
|
|
}
|
|
|
|
|
|
function onDestroy(up) {
|
|
up.forEachItem(function(file) {
|
|
file.destroy();
|
|
});
|
|
|
|
if (_fileInputs.length) {
|
|
plupload.each(_fileInputs, function(fileInput) {
|
|
fileInput.destroy();
|
|
});
|
|
_fileInputs = [];
|
|
}
|
|
|
|
if (_fileDrops.length) {
|
|
plupload.each(_fileDrops, function(fileDrop) {
|
|
fileDrop.destroy();
|
|
});
|
|
_fileDrops = [];
|
|
}
|
|
|
|
_initialized = false;
|
|
|
|
if (_queueUpload) {
|
|
_queueUpload.destroy();
|
|
}
|
|
|
|
if (_queueResize) {
|
|
_queueResize.destroy();
|
|
}
|
|
|
|
_options = _queueUpload = _queueResize = null; // purge these exclusively
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// convert plupload features to caps acceptable by mOxie
|
|
function normalizeCaps(settings) {
|
|
var features = settings.required_features,
|
|
caps = {};
|
|
|
|
function resolve(feature, value, strict) {
|
|
// Feature notation is deprecated, use caps (this thing here is required for backward compatibility)
|
|
var map = {
|
|
chunks: 'slice_blob',
|
|
jpgresize: 'send_binary_string',
|
|
pngresize: 'send_binary_string',
|
|
progress: 'report_upload_progress',
|
|
multi_selection: 'select_multiple',
|
|
dragdrop: 'drag_and_drop',
|
|
drop_element: 'drag_and_drop',
|
|
headers: 'send_custom_headers',
|
|
urlstream_upload: 'send_binary_string',
|
|
canSendBinary: 'send_binary',
|
|
triggerDialog: 'summon_file_dialog'
|
|
};
|
|
|
|
if (map[feature]) {
|
|
caps[map[feature]] = value;
|
|
} else if (!strict) {
|
|
caps[feature] = value;
|
|
}
|
|
}
|
|
|
|
if (typeof(features) === 'string') {
|
|
plupload.each(features.split(/\s*,\s*/), function(feature) {
|
|
resolve(feature, true);
|
|
});
|
|
} else if (typeof(features) === 'object') {
|
|
plupload.each(features, function(value, feature) {
|
|
resolve(feature, value);
|
|
});
|
|
} else if (features === true) {
|
|
// check settings for required features
|
|
if (settings.chunk_size && settings.chunk_size > 0) {
|
|
caps.slice_blob = true;
|
|
}
|
|
|
|
if (!plupload.isEmptyObj(settings.resize) || settings.multipart === false) {
|
|
caps.send_binary_string = true;
|
|
}
|
|
|
|
if (settings.http_method) {
|
|
caps.use_http_method = settings.http_method;
|
|
}
|
|
|
|
plupload.each(settings, function(value, feature) {
|
|
resolve(feature, !!value, true); // strict check
|
|
});
|
|
}
|
|
|
|
return caps;
|
|
}
|
|
|
|
function normalizeOptions(options) {
|
|
plupload.each(options, function(value, option) {
|
|
options[option] = normalizeOption(option, value, options);
|
|
});
|
|
return options;
|
|
}
|
|
|
|
/**
|
|
Normalize an option.
|
|
|
|
@method normalizeOption
|
|
@private
|
|
|
|
@param {String} option Name of the option to normalize
|
|
@param {Mixed} value
|
|
@param {Object} options The whole set of options, that might be modified during normalization (see max_file_size or unique_names)!
|
|
*/
|
|
function normalizeOption(option, value, options) {
|
|
switch (option) {
|
|
|
|
case 'chunk_size':
|
|
if (value = plupload.parseSize(value)) {
|
|
options.send_file_name = true;
|
|
}
|
|
break;
|
|
|
|
case 'headers':
|
|
var headers = {};
|
|
if (typeof(value) === 'object') {
|
|
plupload.each(value, function(value, key) {
|
|
headers[key.toLowerCase()] = value;
|
|
});
|
|
}
|
|
return headers;
|
|
|
|
case 'http_method':
|
|
return value.toUpperCase() === 'PUT' ? 'PUT' : 'POST';
|
|
|
|
|
|
case 'filters':
|
|
if (plupload.typeOf(value) === 'array') { // for backward compatibility
|
|
value = {
|
|
mime_types: value
|
|
};
|
|
}
|
|
|
|
// if file format filters are being updated, regenerate the matching expressions
|
|
if (value.mime_types) {
|
|
if (plupload.typeOf(value.mime_types) === 'string') {
|
|
value.mime_types = plupload.mimes2extList(value.mime_types);
|
|
}
|
|
|
|
// generate and cache regular expression for filtering file extensions
|
|
options.re_ext_filter = (function(filters) {
|
|
var extensionsRegExp = [];
|
|
|
|
plupload.each(filters, function(filter) {
|
|
plupload.each(filter.extensions.split(/,/), function(ext) {
|
|
if (/^\s*\*\s*$/.test(ext)) {
|
|
extensionsRegExp.push('\\.*');
|
|
} else {
|
|
extensionsRegExp.push('\\.' + ext.replace(new RegExp('[' + ('/^$.*+?|()[]{}\\'.replace(/./g, '\\$&')) + ']', 'g'), '\\$&'));
|
|
}
|
|
});
|
|
});
|
|
|
|
return new RegExp('(' + extensionsRegExp.join('|') + ')$', 'i');
|
|
}(value.mime_types));
|
|
}
|
|
|
|
return value;
|
|
|
|
case 'max_file_size':
|
|
if (options && !options.filters) {
|
|
options.filters = {};
|
|
}
|
|
options.filters.max_file_size = value;
|
|
break;
|
|
|
|
case 'multipart':
|
|
if (!value) {
|
|
options.send_file_name = true;
|
|
}
|
|
break;
|
|
|
|
case 'multipart_params':
|
|
options.params = options.multipart_params = value;
|
|
break;
|
|
|
|
case 'resize':
|
|
if (value) {
|
|
return plupload.extend({
|
|
preserve_headers: true,
|
|
crop: false
|
|
}, value);
|
|
}
|
|
return false;
|
|
|
|
case 'prevent_duplicates':
|
|
if (options && !options.filters) {
|
|
options.filters = {};
|
|
}
|
|
options.filters.prevent_duplicates = !!value;
|
|
break;
|
|
|
|
case 'unique_names':
|
|
if (value) {
|
|
options.send_file_name = true;
|
|
}
|
|
break;
|
|
|
|
case 'required_features':
|
|
// Normalize the list of required capabilities
|
|
return normalizeCaps(plupload.extend({}, options));
|
|
|
|
case 'preferred_caps':
|
|
// Come up with the list of capabilities that can affect default mode in a multi-mode runtimes
|
|
return normalizeCaps(plupload.extend({}, options, {
|
|
required_features: true
|
|
}));
|
|
|
|
// options that require reinitialisation
|
|
case 'container':
|
|
case 'browse_button':
|
|
case 'drop_element':
|
|
return 'container' === option ? plupload.get(value) : plupload.getAll(value);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
/**
|
|
* Registers a filter that will be executed for each file added to the queue.
|
|
* If callback returns false, file will not be added.
|
|
*
|
|
* Callback receives two arguments: a value for the filter as it was specified in settings.filters
|
|
* and a file to be filtered. Callback is executed in the context of uploader instance.
|
|
*
|
|
* @method addFileFilter
|
|
* @static
|
|
* @param {String} name Name of the filter by which it can be referenced in settings.filters
|
|
* @param {String} cb Callback - the actual routine that every added file must pass
|
|
*/
|
|
function addFileFilter(name, cb) {
|
|
fileFilters[name] = cb;
|
|
}
|
|
|
|
|
|
/**
|
|
* A way to predict what runtime will be choosen in the current environment with the
|
|
* specified settings.
|
|
*
|
|
* @method predictRuntime
|
|
* @static
|
|
* @param {Object|String} config Plupload settings to check
|
|
* @param {String} [runtimes] Comma-separated list of runtimes to check against
|
|
* @return {String} Type of compatible runtime
|
|
*/
|
|
function predictRuntime(config, runtimes) {
|
|
var up, runtime;
|
|
|
|
up = new Uploader(config);
|
|
runtime = plupload.Runtime.thatCan(up.getOption('required_features'), runtimes || config.runtimes);
|
|
up.destroy();
|
|
return runtime;
|
|
}
|
|
|
|
|
|
addFileFilter('mime_types', function(filters, file, cb) {
|
|
if (filters.length && !this.getOption('re_ext_filter').test(file.name)) {
|
|
this.trigger('Error', {
|
|
code: plupload.FILE_EXTENSION_ERROR,
|
|
message: plupload.translate('File extension error.'),
|
|
file: file
|
|
});
|
|
cb(false);
|
|
} else {
|
|
cb(true);
|
|
}
|
|
});
|
|
|
|
|
|
addFileFilter('max_file_size', function(maxSize, file, cb) {
|
|
var undef;
|
|
|
|
maxSize = plupload.parseSize(maxSize);
|
|
|
|
// Invalid file size
|
|
if (file.size !== undef && maxSize && file.size > maxSize) {
|
|
this.trigger('Error', {
|
|
code: plupload.FILE_SIZE_ERROR,
|
|
message: plupload.translate('File size error.'),
|
|
file: file
|
|
});
|
|
cb(false);
|
|
} else {
|
|
cb(true);
|
|
}
|
|
});
|
|
|
|
|
|
addFileFilter('prevent_duplicates', function(value, file, cb) {
|
|
var self = this;
|
|
if (value) {
|
|
this.forEachItem(function(item) {
|
|
// Compare by name and size (size might be 0 or undefined, but still equivalent for both)
|
|
if (file.name === item.name && file.size === item.size) {
|
|
self.trigger('Error', {
|
|
code: plupload.FILE_DUPLICATE_ERROR,
|
|
message: plupload.translate('Duplicate file error.'),
|
|
file: file
|
|
});
|
|
cb(false);
|
|
return;
|
|
}
|
|
});
|
|
}
|
|
cb(true);
|
|
});
|
|
|
|
|
|
addFileFilter('prevent_empty', function(value, file, cb) {
|
|
if (value && !file.size && file.size !== undef) {
|
|
this.trigger('Error', {
|
|
code : plupload.FILE_SIZE_ERROR,
|
|
message : plupload.translate('File size error.'),
|
|
file : file
|
|
});
|
|
cb(false);
|
|
} else {
|
|
cb(true);
|
|
}
|
|
});
|
|
|
|
|
|
Uploader.addFileFilter = addFileFilter;
|
|
|
|
plupload.inherit(Uploader, Queue);
|
|
|
|
// for backward compatibility
|
|
plupload.addFileFilter = addFileFilter;
|
|
plupload.predictRuntime = predictRuntime;
|
|
|
|
return Uploader;
|
|
});
|
|
|
|
expose(["plupload","plupload/core/Collection","plupload/core/ArrCollection","plupload/core/Optionable","plupload/core/Queueable","plupload/core/Stats","plupload/core/Queue","plupload/QueueUpload","plupload/QueueResize","plupload/ChunkUploader","plupload/FileUploader","plupload/ImageResizer","plupload/File","plupload/Uploader"]);
|
|
})(this);
|
|
})); |