/*!
* elFinder - file manager for web
* Version 2.1.57 (2020-06-05)
* http://elfinder.org
*
* Copyright 2009-2020, Studio 42
* Licensed under a 3-clauses BSD license
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery','jquery-ui'], factory);
} else if (typeof exports !== 'undefined') {
// CommonJS
var $, ui;
try {
$ = require('jquery');
ui = require('jquery-ui');
} catch (e) {}
module.exports = factory($, ui);
} else {
// Browser globals (Note: root is window)
factory(root.jQuery, root.jQuery.ui, true);
}
}(this, function($, _ui, toGlobal) {
toGlobal = toGlobal || false;
/*
* File: /js/elFinder.js
*/
/**
* @class elFinder - file manager for web
*
* @author Dmitry (dio) Levashov
**/
var elFinder = function(elm, opts, bootCallback) {
//this.time('load');
var self = this,
/**
* Objects array of jQuery.Deferred that calls before elFinder boot up
*
* @type Array
*/
dfrdsBeforeBootup = [],
/**
* Plugin name to check for conflicts with bootstrap etc
*
* @type Array
**/
conflictChecks = ['button', 'tooltip'],
/**
* Node on which elfinder creating
*
* @type jQuery
**/
node = $(elm),
/**
* Object of events originally registered in this node
*
* @type Object
*/
prevEvents = $.extend(true, {}, $._data(node.get(0), 'events')),
/**
* Store node contents.
*
* @see this.destroy
* @type jQuery
**/
prevContent = $('
').append(node.contents()).attr('class', node.attr('class') || '').attr('style', node.attr('style') || ''),
/**
* Instance ID. Required to get/set cookie
*
* @type String
**/
id = node.attr('id') || node.attr('id', 'elfauto' + $('.elfinder').length).attr('id'),
/**
* Events namespace
*
* @type String
**/
namespace = 'elfinder-' + id,
/**
* Mousedown event
*
* @type String
**/
mousedown = 'mousedown.'+namespace,
/**
* Keydown event
*
* @type String
**/
keydown = 'keydown.'+namespace,
/**
* Keypress event
*
* @type String
**/
keypress = 'keypress.'+namespace,
/**
* Keypup event
*
* @type String
**/
keyup = 'keyup.'+namespace,
/**
* Is shortcuts/commands enabled
*
* @type Boolean
**/
enabled = false,
/**
* Store enabled value before ajax request
*
* @type Boolean
**/
prevEnabled = false,
/**
* List of build-in events which mapped into methods with same names
*
* @type Array
**/
events = ['enable', 'disable', 'load', 'open', 'reload', 'select', 'add', 'remove', 'change', 'dblclick', 'getfile', 'lockfiles', 'unlockfiles', 'selectfiles', 'unselectfiles', 'dragstart', 'dragstop', 'search', 'searchend', 'viewchange'],
/**
* Rules to validate data from backend
*
* @type Object
**/
rules = {},
/**
* Current working directory hash
*
* @type String
**/
cwd = '',
/**
* Current working directory options default
*
* @type Object
**/
cwdOptionsDefault = {
path : '',
url : '',
tmbUrl : '',
disabled : [],
separator : '/',
archives : [],
extract : [],
copyOverwrite : true,
uploadOverwrite : true,
uploadMaxSize : 0,
jpgQuality : 100,
tmbCrop : false,
tmbReqCustomData : false,
tmb : false // old API
},
/**
* Current working directory options
*
* @type Object
**/
cwdOptions = {},
/**
* Files/dirs cache
*
* @type Object
**/
files = {},
/**
* Hidden Files/dirs cache
*
* @type Object
**/
hiddenFiles = {},
/**
* Files/dirs hash cache of each dirs
*
* @type Object
**/
ownFiles = {},
/**
* Selected files hashes
*
* @type Array
**/
selected = [],
/**
* Events listeners
*
* @type Object
**/
listeners = {},
/**
* Shortcuts
*
* @type Object
**/
shortcuts = {},
/**
* Buffer for copied files
*
* @type Array
**/
clipboard = [],
/**
* Copied/cuted files hashes
* Prevent from remove its from cache.
* Required for dispaly correct files names in error messages
*
* @type Object
**/
remember = {},
/**
* Queue for 'open' requests
*
* @type Array
**/
queue = [],
/**
* Queue for only cwd requests e.g. `tmb`
*
* @type Array
**/
cwdQueue = [],
/**
* Commands prototype
*
* @type Object
**/
base = new self.command(self),
/**
* elFinder node width
*
* @type String
* @default "auto"
**/
width = 'auto',
/**
* elFinder node height
* Number: pixcel or String: Number + "%"
*
* @type Number | String
* @default 400
**/
height = 400,
/**
* Base node object or selector
* Element which is the reference of the height percentage
*
* @type Object|String
* @default null | $(window) (if height is percentage)
**/
heightBase = null,
/**
* MIME type list(Associative array) handled as a text file
*
* @type Object|null
*/
textMimes = null,
/**
* elfinder path for sound played on remove
* @type String
* @default ./sounds/
**/
soundPath = 'sounds/',
/**
* JSON.stringify of previous fm.sorters
* @type String
*/
prevSorterStr = '',
/**
* Map table of file extention to MIME-Type
* @type Object
*/
extToMimeTable,
/**
* Disabled page unload function
* @type Boolean
*/
diableUnloadCheck = false,
beeper = $(document.createElement('audio')).hide().appendTo('body')[0],
syncInterval,
autoSyncStop = 0,
uiCmdMapPrev = '',
gcJobRes = null,
open = function(data) {
// NOTES: Do not touch data object
var volumeid, contextmenu, emptyDirs = {}, stayDirs = {},
rmClass, hashes, calc, gc, collapsed, prevcwd, sorterStr, diff;
if (self.api >= 2.1) {
// support volume driver option `uiCmdMap`
self.commandMap = (data.options.uiCmdMap && Object.keys(data.options.uiCmdMap).length)? data.options.uiCmdMap : {};
if (uiCmdMapPrev !== JSON.stringify(self.commandMap)) {
uiCmdMapPrev = JSON.stringify(self.commandMap);
}
} else {
self.options.sync = 0;
}
if (data.init) {
// init - reset cache
files = {};
ownFiles = {};
} else {
// remove only files from prev cwd
// and collapsed directory (included 100+ directories) to empty for perfomance tune in DnD
prevcwd = cwd;
rmClass = 'elfinder-subtree-loaded ' + self.res('class', 'navexpand');
collapsed = self.res('class', 'navcollapse');
hashes = Object.keys(files);
calc = function(i) {
if (!files[i]) {
return true;
}
var isDir = (files[i].mime === 'directory'),
phash = files[i].phash,
pnav;
if (
(!isDir
|| emptyDirs[phash]
|| (!stayDirs[phash]
&& self.navHash2Elm(files[i].hash).is(':hidden')
&& self.navHash2Elm(phash).next('.elfinder-navbar-subtree').children().length > 100
)
)
&& (isDir || phash !== cwd)
&& ! remember[i]
) {
if (isDir && !emptyDirs[phash]) {
emptyDirs[phash] = true;
self.navHash2Elm(phash)
.removeClass(rmClass)
.next('.elfinder-navbar-subtree').empty();
}
deleteCache(files[i]);
} else if (isDir) {
stayDirs[phash] = true;
}
};
gc = function() {
if (hashes.length) {
gcJobRes && gcJobRes._abort();
gcJobRes = self.asyncJob(calc, hashes, {
interval : 20,
numPerOnce : 100
}).done(function() {
var hd = self.storage('hide') || {items: {}};
if (Object.keys(hiddenFiles).length) {
$.each(hiddenFiles, function(h) {
if (!hd.items[h]) {
delete hiddenFiles[h];
}
});
}
});
}
};
self.trigger('filesgc').one('filesgc', function() {
hashes = [];
});
self.one('opendone', function() {
if (prevcwd !== cwd) {
if (! node.data('lazycnt')) {
gc();
} else {
self.one('lazydone', gc);
}
}
});
}
self.sorters = {};
cwd = data.cwd.hash;
cache(data.files);
if (!files[cwd]) {
cache([data.cwd]);
} else {
diff = self.diff([data.cwd], true);
if (diff.changed.length) {
cache(diff.changed, 'change');
self.change({changed: diff.changed});
}
}
data.changed && data.changed.length && cache(data.changed, 'change');
// trigger event 'sorterupdate'
sorterStr = JSON.stringify(self.sorters);
if (prevSorterStr !== sorterStr) {
self.trigger('sorterupdate');
prevSorterStr = sorterStr;
}
self.lastDir(cwd);
self.autoSync();
},
/**
* Store info about files/dirs in "files" object.
*
* @param Array files
* @param String data type
* @return void
**/
cache = function(data, type) {
var type = type || 'files',
keeps = ['sizeInfo', 'encoding'],
defsorter = { name: true, perm: true, date: true, size: true, kind: true },
sorterChk = !self.sorters._checked && (type === 'files'),
l = data.length,
setSorter = function(file) {
var f = file || {},
sorters = [];
$.each(self.sortRules, function(key) {
if (defsorter[key] || typeof f[key] !== 'undefined' || (key === 'mode' && typeof f.perm !== 'undefined')) {
sorters.push(key);
}
});
self.sorters = self.arrayFlip(sorters, true);
self.sorters._checked = true;
},
changedParents = {},
hideData = self.storage('hide') || {},
hides = hideData.items || {},
f, i, i1, keepProp, parents, hidden;
for (i = 0; i < l; i++) {
f = Object.assign({}, data[i]);
hidden = (!hideData.show && hides[f.hash])? true : false;
if (f.name && f.hash && f.mime) {
if (!hidden) {
if (sorterChk && f.phash === cwd) {
setSorter(f);
sorterChk = false;
}
if (f.phash && (type === 'add' || (type === 'change' && (!files[f.hash] || f.size !== files[f.hash])))) {
if (parents = self.parents(f.phash)) {
$.each(parents, function() {
changedParents[this] = true;
});
}
}
}
if (files[f.hash]) {
for (i1 =0; i1 < keeps.length; i1++) {
if(files[f.hash][keeps[i1]] && ! f[keeps[i1]]) {
f[keeps[i1]] = files[f.hash][keeps[i1]];
}
}
if (f.sizeInfo && !f.size) {
f.size = f.sizeInfo.size;
}
deleteCache(files[f.hash], true);
}
if (hides[f.hash]) {
hiddenFiles[f.hash] = f;
}
if (hidden) {
l--;
data.splice(i--, 1);
} else {
files[f.hash] = f;
if (f.mime === 'directory' && !ownFiles[f.hash]) {
ownFiles[f.hash] = {};
}
if (f.phash) {
if (!ownFiles[f.phash]) {
ownFiles[f.phash] = {};
}
ownFiles[f.phash][f.hash] = true;
}
}
}
}
// delete sizeInfo cache
$.each(Object.keys(changedParents), function() {
var target = files[this];
if (target && target.sizeInfo) {
delete target.sizeInfo;
}
});
// for empty folder
sorterChk && setSorter();
},
/**
* Delete file object from files caches
*
* @param Array removed hashes
* @return void
*/
remove = function(removed) {
var l = removed.length,
roots = {},
rm = function(hash) {
var file = files[hash], i;
if (file) {
if (file.mime === 'directory') {
if (roots[hash]) {
delete self.roots[roots[hash]];
}
// restore stats of deleted root parent directory
$.each(self.leafRoots, function(phash, roots) {
var idx, pdir;
if ((idx = $.inArray(hash, roots))!== -1) {
if (roots.length === 1) {
if ((pdir = Object.assign({}, files[phash])) && pdir._realStats) {
$.each(pdir._realStats, function(k, v) {
pdir[k] = v;
});
remove(files[phash]._realStats);
self.change({ changed: [pdir] });
}
delete self.leafRoots[phash];
} else {
self.leafRoots[phash].splice(idx, 1);
}
}
});
if (self.searchStatus.state < 2) {
$.each(files, function(h, f) {
f.phash == hash && rm(h);
});
}
}
if (file.phash) {
if (parents = self.parents(file.phash)) {
$.each(parents, function() {
changedParents[this] = true;
});
}
}
deleteCache(files[hash]);
}
},
changedParents = {},
parents;
$.each(self.roots, function(k, v) {
roots[v] = k;
});
while (l--) {
rm(removed[l]);
}
// delete sizeInfo cache
$.each(Object.keys(changedParents), function() {
var target = files[this];
if (target && target.sizeInfo) {
delete target.sizeInfo;
}
});
},
/**
* Update file object in files caches
*
* @param Array changed file objects
* @return void
* @deprecated should be use `cache(updatesArrayData, 'change');`
*/
change = function(changed) {
$.each(changed, function(i, file) {
var hash = file.hash;
if (files[hash]) {
$.each(Object.keys(files[hash]), function(i, v){
if (typeof file[v] === 'undefined') {
delete files[hash][v];
}
});
}
files[hash] = files[hash] ? Object.assign(files[hash], file) : file;
});
},
/**
* Delete cache data of files, ownFiles and self.optionsByHashes
*
* @param Object file
* @param Boolean update
* @return void
*/
deleteCache = function(file, update) {
var hash = file.hash,
phash = file.phash;
if (phash && ownFiles[phash]) {
delete ownFiles[phash][hash];
}
if (!update) {
ownFiles[hash] && delete ownFiles[hash];
self.optionsByHashes[hash] && delete self.optionsByHashes[hash];
}
delete files[hash];
},
/**
* Maximum number of concurrent connections on request
*
* @type Number
*/
requestMaxConn,
/**
* Current number of connections
*
* @type Number
*/
requestCnt = 0,
/**
* Queue waiting for connection
*
* @type Array
*/
requestQueue = [],
/**
* Current open command instance
*
* @type Object
*/
currentOpenCmd = null,
/**
* Exec shortcut
*
* @param jQuery.Event keydown/keypress event
* @return void
*/
execShortcut = function(e) {
var code = e.keyCode,
ctrlKey = !!(e.ctrlKey || e.metaKey),
isMousedown = e.type === 'mousedown',
ddm;
!isMousedown && (self.keyState.keyCode = code);
self.keyState.ctrlKey = ctrlKey;
self.keyState.shiftKey = e.shiftKey;
self.keyState.metaKey = e.metaKey;
self.keyState.altKey = e.altKey;
if (isMousedown) {
return;
} else if (e.type === 'keyup') {
self.keyState.keyCode = null;
return;
}
if (enabled) {
$.each(shortcuts, function(i, shortcut) {
if (shortcut.type == e.type
&& shortcut.keyCode == code
&& shortcut.shiftKey == e.shiftKey
&& shortcut.ctrlKey == ctrlKey
&& shortcut.altKey == e.altKey) {
e.preventDefault();
e.stopPropagation();
shortcut.callback(e, self);
self.debug('shortcut-exec', i+' : '+shortcut.description);
}
});
// prevent tab out of elfinder
if (code == $.ui.keyCode.TAB && !$(e.target).is(':input')) {
e.preventDefault();
}
// cancel any actions by [Esc] key
if (e.type === 'keydown' && code == $.ui.keyCode.ESCAPE) {
// copy or cut
if (! node.find('.ui-widget:visible').length) {
self.clipboard().length && self.clipboard([]);
}
// dragging
if ($.ui.ddmanager) {
ddm = $.ui.ddmanager.current;
ddm && ddm.helper && ddm.cancel();
}
// button menus
self.toHide(node.find('.ui-widget.elfinder-button-menu.elfinder-frontmost:visible'));
// trigger keydownEsc
self.trigger('keydownEsc', e);
}
}
},
date = new Date(),
utc,
i18n,
inFrame = (window.parent !== window),
parentIframe = (function() {
var pifm, ifms;
if (inFrame) {
try {
ifms = $('iframe', window.parent.document);
if (ifms.length) {
$.each(ifms, function(i, ifm) {
if (ifm.contentWindow === window) {
pifm = $(ifm);
return false;
}
});
}
} catch(e) {}
}
return pifm;
})(),
/**
* elFinder boot up function
*
* @type Function
*/
bootUp,
/**
* Original function of XMLHttpRequest.prototype.send
*
* @type Function
*/
savedXhrSend;
// opts must be an object
if (!opts) {
opts = {};
}
// set UA.Angle, UA.Rotated for mobile devices
if (self.UA.Mobile) {
$(window).on('orientationchange.'+namespace, function() {
var a = ((screen && screen.orientation && screen.orientation.angle) || window.orientation || 0) + 0;
if (a === -90) {
a = 270;
}
self.UA.Angle = a;
self.UA.Rotated = a % 180 === 0? false : true;
}).trigger('orientationchange.'+namespace);
}
// check opt.bootCallback
if (opts.bootCallback && typeof opts.bootCallback === 'function') {
(function() {
var func = bootCallback,
opFunc = opts.bootCallback;
bootCallback = function(fm, extraObj) {
func && typeof func === 'function' && func.call(this, fm, extraObj);
opFunc.call(this, fm, extraObj);
};
})();
}
delete opts.bootCallback;
/**
* Protocol version
*
* @type String
**/
this.api = null;
/**
* elFinder use new api
*
* @type Boolean
**/
this.newAPI = false;
/**
* elFinder use old api
*
* @type Boolean
**/
this.oldAPI = false;
/**
* Net drivers names
*
* @type Array
**/
this.netDrivers = [];
/**
* Base URL of elfFinder library starting from Manager HTML
*
* @type String
*/
this.baseUrl = '';
/**
* Base URL of i18n js files
* baseUrl + "js/i18n/" when empty value
*
* @type String
*/
this.i18nBaseUrl = '';
/**
* Is elFinder CSS loaded
*
* @type Boolean
*/
this.cssloaded = false;
/**
* Current theme object
*
* @type Object|Null
*/
this.theme = null;
this.mimesCanMakeEmpty = {};
/**
* Callback function at boot up that option specified at elFinder starting
*
* @type Function
*/
this.bootCallback;
/**
* Callback function at reload(restart) elFinder
*
* @type Function
*/
this.reloadCallback;
/**
* ID. Required to create unique cookie name
*
* @type String
**/
this.id = id;
/**
* Method to store/fetch data
*
* @type Function
**/
this.storage = (function() {
try {
if ('localStorage' in window && window.localStorage !== null) {
if (self.UA.Safari) {
// check for Mac/iOS safari private browsing mode
window.localStorage.setItem('elfstoragecheck', 1);
window.localStorage.removeItem('elfstoragecheck');
}
return self.localStorage;
} else {
return self.cookie;
}
} catch (e) {
return self.cookie;
}
})();
/**
* Set pause page unload check function or Get state
*
* @param Boolean state To set state
* @param Boolean keep Keep disabled
* @return Boolean|void
*/
this.pauseUnloadCheck = function(state, keep) {
if (typeof state === 'undefined') {
return diableUnloadCheck;
} else {
diableUnloadCheck = !!state;
if (state && !keep) {
requestAnimationFrame(function() {
diableUnloadCheck = false;
});
}
}
};
/**
* Configuration options
*
* @type Object
**/
//this.options = $.extend(true, {}, this._options, opts);
this.options = Object.assign({}, this._options);
// for old type configuration
if (opts.uiOptions) {
if (opts.uiOptions.toolbar && Array.isArray(opts.uiOptions.toolbar)) {
if ($.isPlainObject(opts.uiOptions.toolbar[opts.uiOptions.toolbar.length - 1])) {
self.options.uiOptions.toolbarExtra = Object.assign(self.options.uiOptions.toolbarExtra || {}, opts.uiOptions.toolbar.pop());
}
}
}
// Overwrite if opts value is an array
(function() {
var arrOv = function(obj, base) {
if ($.isPlainObject(obj)) {
$.each(obj, function(k, v) {
if ($.isPlainObject(v)) {
if (!base[k]) {
base[k] = {};
}
arrOv(v, base[k]);
} else {
base[k] = v;
}
});
}
};
arrOv(opts, self.options);
})();
// join toolbarExtra to toolbar
this.options.uiOptions.toolbar.push(this.options.uiOptions.toolbarExtra);
delete this.options.uiOptions.toolbarExtra;
/**
* Arrays that has to unbind events
*
* @type Object
*/
this.toUnbindEvents = {};
/**
* Attach listener to events
* To bind to multiply events at once, separate events names by space
*
* @param String event(s) name(s)
* @param Object event handler or {done: handler}
* @param Boolean priority first
* @return elFinder
*/
this.bind = function(event, callback, priorityFirst) {
var i, len;
if (callback && (typeof callback === 'function' || typeof callback.done === 'function')) {
event = ('' + event).toLowerCase().replace(/^\s+|\s+$/g, '').split(/\s+/);
len = event.length;
for (i = 0; i < len; i++) {
if (listeners[event[i]] === void(0)) {
listeners[event[i]] = [];
}
listeners[event[i]][priorityFirst? 'unshift' : 'push'](callback);
}
}
return this;
};
/**
* Remove event listener if exists
* To un-bind to multiply events at once, separate events names by space
*
* @param String event(s) name(s)
* @param Function callback
* @return elFinder
*/
this.unbind = function(event, callback) {
var i, len, l, ci;
event = ('' + event).toLowerCase().split(/\s+/);
len = event.length;
for (i = 0; i < len; i++) {
if (l = listeners[event[i]]) {
ci = $.inArray(callback, l);
ci > -1 && l.splice(ci, 1);
}
}
callback = null;
return this;
};
/**
* Fire event - send notification to all event listeners
* In the callback `this` becames an event object
*
* @param String event type
* @param Object data to send across event
* @param Boolean allow modify data (call by reference of data) default: true
* @return elFinder
*/
this.trigger = function(evType, data, allowModify) {
var type = evType.toLowerCase(),
isopen = (type === 'open'),
dataIsObj = (typeof data === 'object'),
handlers = listeners[type] || [],
dones = [],
i, l, jst, event;
this.debug('event-'+type, data);
if (! dataIsObj || typeof allowModify === 'undefined') {
allowModify = true;
}
if (l = handlers.length) {
event = $.Event(type);
if (data) {
data._getEvent = function() {
return event;
};
}
if (allowModify) {
event.data = data;
}
for (i = 0; i < l; i++) {
if (! handlers[i]) {
// probably un-binded this handler
continue;
}
// handler is $.Deferred(), call all functions upon completion
if (handlers[i].done) {
dones.push(handlers[i].done);
continue;
}
// set `event.data` only callback has argument
if (handlers[i].length) {
if (!allowModify) {
// to avoid data modifications. remember about "sharing" passing arguments in js :)
if (typeof jst === 'undefined') {
try {
jst = JSON.stringify(data);
} catch(e) {
jst = false;
}
}
event.data = jst? JSON.parse(jst) : data;
}
}
try {
if (handlers[i].call(event, event, this) === false || event.isDefaultPrevented()) {
this.debug('event-stoped', event.type);
break;
}
} catch (ex) {
window.console && window.console.log && window.console.log(ex);
}
}
// call done functions
if (l = dones.length) {
for (i = 0; i < l; i++) {
try {
if (dones[i].call(event, event, this) === false || event.isDefaultPrevented()) {
this.debug('event-stoped', event.type + '(done)');
break;
}
} catch (ex) {
window.console && window.console.log && window.console.log(ex);
}
}
}
if (this.toUnbindEvents[type] && this.toUnbindEvents[type].length) {
$.each(this.toUnbindEvents[type], function(i, v) {
self.unbind(v.type, v.callback);
});
delete this.toUnbindEvents[type];
}
}
return this;
};
/**
* Get event listeners
*
* @param String event type
* @return Array listed event functions
*/
this.getListeners = function(event) {
return event? listeners[event.toLowerCase()] : listeners;
};
// set fm.baseUrl
this.baseUrl = (function() {
var myTag, base, baseUrl;
if (self.options.baseUrl) {
return self.options.baseUrl;
} else {
baseUrl = '';
myTag = null;
$('head > script').each(function() {
if (this.src && this.src.match(/js\/elfinder(?:-[a-z0-9_-]+)?\.(?:min|full)\.js$/i)) {
myTag = $(this);
return false;
}
});
if (myTag) {
baseUrl = myTag.attr('src').replace(/js\/[^\/]+$/, '');
if (! baseUrl.match(/^(https?\/\/|\/)/)) {
// check tag
if (base = $('head > base[href]').attr('href')) {
baseUrl = base.replace(/\/$/, '') + '/' + baseUrl;
}
}
}
if (baseUrl !== '') {
self.options.baseUrl = baseUrl;
} else {
if (! self.options.baseUrl) {
self.options.baseUrl = './';
}
baseUrl = self.options.baseUrl;
}
return baseUrl;
}
})();
this.i18nBaseUrl = (this.options.i18nBaseUrl || this.baseUrl + 'js/i18n').replace(/\/$/, '') + '/';
this.options.maxErrorDialogs = Math.max(1, parseInt(this.options.maxErrorDialogs || 5));
// set dispInlineRegex
cwdOptionsDefault.dispInlineRegex = this.options.dispInlineRegex;
// auto load required CSS
if (this.options.cssAutoLoad) {
(function() {
var baseUrl = self.baseUrl,
myCss = $('head > link[href$="css/elfinder.min.css"],link[href$="css/elfinder.full.css"]:first').length,
rmTag = function() {
if (node.data('cssautoloadHide')) {
node.data('cssautoloadHide').remove();
node.removeData('cssautoloadHide');
}
},
loaded = function() {
if (!self.cssloaded) {
rmTag();
self.cssloaded = true;
self.trigger('cssloaded');
}
};
if (! myCss) {
// to request CSS auto loading
self.cssloaded = null;
}
// additional CSS files
if (Array.isArray(self.options.cssAutoLoad)) {
if (!self.options.themes.default) {
// set as default theme
self.options.themes = Object.assign({
'default' : {
'name': 'default',
'cssurls': self.options.cssAutoLoad
}
}, self.options.themes);
if (!self.options.theme) {
self.options.theme = 'default';
}
} else {
if (self.cssloaded === true) {
self.loadCss(self.options.cssAutoLoad);
} else {
self.bind('cssloaded', function() {
self.loadCss(self.options.cssAutoLoad);
});
}
}
}
// try to load main css
if (self.cssloaded === null) {
// hide elFinder node while css loading
node.addClass('elfinder')
.data('cssautoloadHide', $(''));
$('head').append(node.data('cssautoloadHide'));
// set default theme
if (!self.options.themes.default) {
self.options.themes = Object.assign({
'default' : {
'name': 'default',
'cssurls': 'css/theme.css',
'author': 'elFinder Project',
'license': '3-clauses BSD'
}
}, self.options.themes);
if (!self.options.theme) {
self.options.theme = 'default';
}
}
// Delay 'visibility' check it required for browsers such as Safari
requestAnimationFrame(function() {
if (node.css('visibility') === 'hidden') {
// load CSS
self.loadCss([baseUrl+'css/elfinder.min.css'], {
dfd: $.Deferred().done(function() {
loaded();
}).fail(function() {
rmTag();
if (!self.cssloaded) {
self.cssloaded = false;
self.bind('init', function() {
if (!self.cssloaded) {
self.error(['errRead', 'CSS (elfinder.min)']);
}
});
}
})
});
} else {
loaded();
}
});
}
})();
}
// load theme if exists
(function() {
var theme,
themes = self.options.themes,
ids = Object.keys(themes || {});
if (ids.length) {
theme = self.storage('theme') || self.options.theme;
if (!themes[theme]) {
theme = ids[0];
}
if (self.cssloaded) {
self.changeTheme(theme);
} else {
self.bind('cssloaded', function() {
self.changeTheme(theme);
});
}
}
})();
/**
* Volume option to set the properties of the root Stat
*
* @type Object
*/
this.optionProperties = {
icon: void(0),
csscls: void(0),
tmbUrl: void(0),
uiCmdMap: {},
netkey: void(0),
disabled: []
};
if (! inFrame && ! this.options.enableAlways && $('body').children().length === 2) { // only node and beeper
this.options.enableAlways = true;
}
// make options.debug
if (this.options.debug === true) {
this.options.debug = 'all';
} else if (Array.isArray(this.options.debug)) {
(function() {
var d = {};
$.each(self.options.debug, function() {
d[this] = true;
});
self.options.debug = d;
})();
} else {
this.options.debug = false;
}
/**
* Original functions evacuated by conflict check
*
* @type Object
*/
this.noConflicts = {};
/**
* Check and save conflicts with bootstrap etc
*
* @type Function
*/
this.noConflict = function() {
$.each(conflictChecks, function(i, p) {
if ($.fn[p] && typeof $.fn[p].noConflict === 'function') {
self.noConflicts[p] = $.fn[p].noConflict();
}
});
};
// do check conflict
this.noConflict();
/**
* Is elFinder over CORS
*
* @type Boolean
**/
this.isCORS = false;
// configure for CORS
(function(){
if (typeof self.options.cors !== 'undefined' && self.options.cors !== null) {
self.isCORS = self.options.cors? true : false;
} else {
var parseUrl = document.createElement('a'),
parseUploadUrl,
selfProtocol = window.location.protocol,
portReg = function(protocol) {
protocol = (!protocol || protocol === ':')? selfProtocol : protocol;
return protocol === 'https:'? /\:443$/ : /\:80$/;
},
selfHost = window.location.host.replace(portReg(selfProtocol), '');
parseUrl.href = opts.url;
if (opts.urlUpload && (opts.urlUpload !== opts.url)) {
parseUploadUrl = document.createElement('a');
parseUploadUrl.href = opts.urlUpload;
}
if (selfHost !== parseUrl.host.replace(portReg(parseUrl.protocol), '')
|| (parseUrl.protocol !== ':'&& parseUrl.protocol !== '' && (selfProtocol !== parseUrl.protocol))
|| (parseUploadUrl &&
(selfHost !== parseUploadUrl.host.replace(portReg(parseUploadUrl.protocol), '')
|| (parseUploadUrl.protocol !== ':' && parseUploadUrl.protocol !== '' && (selfProtocol !== parseUploadUrl.protocol))
)
)
) {
self.isCORS = true;
}
}
if (self.isCORS) {
if (!$.isPlainObject(self.options.customHeaders)) {
self.options.customHeaders = {};
}
if (!$.isPlainObject(self.options.xhrFields)) {
self.options.xhrFields = {};
}
self.options.requestType = 'post';
self.options.customHeaders['X-Requested-With'] = 'XMLHttpRequest';
self.options.xhrFields['withCredentials'] = true;
}
})();
/**
* Ajax request type
*
* @type String
* @default "get"
**/
this.requestType = /^(get|post)$/i.test(this.options.requestType) ? this.options.requestType.toLowerCase() : 'get';
// set `requestMaxConn` by option
requestMaxConn = Math.max(parseInt(this.options.requestMaxConn), 1);
/**
* Custom data that given as options
*
* @type Object
* @default {}
*/
this.optsCustomData = $.isPlainObject(this.options.customData) ? this.options.customData : {};
/**
* Any data to send across every ajax request
*
* @type Object
* @default {}
**/
this.customData = Object.assign({}, this.optsCustomData);
/**
* Previous custom data from connector
*
* @type Object|null
*/
this.prevCustomData = null;
/**
* Any custom headers to send across every ajax request
*
* @type Object
* @default {}
*/
this.customHeaders = $.isPlainObject(this.options.customHeaders) ? this.options.customHeaders : {};
/**
* Any custom xhrFields to send across every ajax request
*
* @type Object
* @default {}
*/
this.xhrFields = $.isPlainObject(this.options.xhrFields) ? this.options.xhrFields : {};
/**
* Replace XMLHttpRequest.prototype.send to extended function for 3rd party libs XHR request etc.
*
* @type Function
*/
this.replaceXhrSend = function() {
if (! savedXhrSend) {
savedXhrSend = XMLHttpRequest.prototype.send;
}
XMLHttpRequest.prototype.send = function() {
var xhr = this;
// set request headers
if (self.customHeaders) {
$.each(self.customHeaders, function(key) {
xhr.setRequestHeader(key, this);
});
}
// set xhrFields
if (self.xhrFields) {
$.each(self.xhrFields, function(key) {
if (key in xhr) {
xhr[key] = this;
}
});
}
return savedXhrSend.apply(this, arguments);
};
};
/**
* Restore saved original XMLHttpRequest.prototype.send
*
* @type Function
*/
this.restoreXhrSend = function() {
savedXhrSend && (XMLHttpRequest.prototype.send = savedXhrSend);
};
/**
* command names for into queue for only cwd requests
* these commands aborts before `open` request
*
* @type Array
* @default ['tmb', 'parents']
*/
this.abortCmdsOnOpen = this.options.abortCmdsOnOpen || ['tmb', 'parents'];
/**
* ui.nav id prefix
*
* @type String
*/
this.navPrefix = 'nav' + (elFinder.prototype.uniqueid? elFinder.prototype.uniqueid : '') + '-';
/**
* ui.cwd id prefix
*
* @type String
*/
this.cwdPrefix = elFinder.prototype.uniqueid? ('cwd' + elFinder.prototype.uniqueid + '-') : '';
// Increment elFinder.prototype.uniqueid
++elFinder.prototype.uniqueid;
/**
* URL to upload files
*
* @type String
**/
this.uploadURL = opts.urlUpload || opts.url;
/**
* Events namespace
*
* @type String
**/
this.namespace = namespace;
/**
* Today timestamp
*
* @type Number
**/
this.today = (new Date(date.getFullYear(), date.getMonth(), date.getDate())).getTime()/1000;
/**
* Yesterday timestamp
*
* @type Number
**/
this.yesterday = this.today - 86400;
utc = this.options.UTCDate ? 'UTC' : '';
this.getHours = 'get'+utc+'Hours';
this.getMinutes = 'get'+utc+'Minutes';
this.getSeconds = 'get'+utc+'Seconds';
this.getDate = 'get'+utc+'Date';
this.getDay = 'get'+utc+'Day';
this.getMonth = 'get'+utc+'Month';
this.getFullYear = 'get'+utc+'FullYear';
/**
* elFinder node z-index (auto detect on elFinder load)
*
* @type null | Number
**/
this.zIndex;
/**
* Current search status
*
* @type Object
*/
this.searchStatus = {
state : 0, // 0: search ended, 1: search started, 2: in search result
query : '',
target : '',
mime : '',
mixed : false, // in multi volumes search: false or Array that target volume ids
ininc : false // in incremental search
};
/**
* Interface language
*
* @type String
* @default "en"
**/
this.lang = this.storage('lang') || this.options.lang;
if (this.lang === 'jp') {
this.lang = this.options.lang = 'ja';
}
this.viewType = this.storage('view') || this.options.defaultView || 'icons';
this.sortType = this.storage('sortType') || this.options.sortType || 'name';
this.sortOrder = this.storage('sortOrder') || this.options.sortOrder || 'asc';
this.sortStickFolders = this.storage('sortStickFolders');
if (this.sortStickFolders === null) {
this.sortStickFolders = !!this.options.sortStickFolders;
} else {
this.sortStickFolders = !!this.sortStickFolders;
}
this.sortAlsoTreeview = this.storage('sortAlsoTreeview');
if (this.sortAlsoTreeview === null || this.options.sortAlsoTreeview === null) {
this.sortAlsoTreeview = !!this.options.sortAlsoTreeview;
} else {
this.sortAlsoTreeview = !!this.sortAlsoTreeview;
}
this.sortRules = $.extend(true, {}, this._sortRules, this.options.sortRules);
$.each(this.sortRules, function(name, method) {
if (typeof method != 'function') {
delete self.sortRules[name];
}
});
this.compare = $.proxy(this.compare, this);
/**
* Delay in ms before open notification dialog
*
* @type Number
* @default 500
**/
this.notifyDelay = this.options.notifyDelay > 0 ? parseInt(this.options.notifyDelay) : 500;
/**
* Dragging UI Helper object
*
* @type jQuery | null
**/
this.draggingUiHelper = null;
/**
* Base droppable options
*
* @type Object
**/
this.droppable = {
greedy : true,
tolerance : 'pointer',
accept : '.elfinder-cwd-file-wrapper,.elfinder-navbar-dir,.elfinder-cwd-file,.elfinder-cwd-filename',
hoverClass : this.res('class', 'adroppable'),
classes : { // Deprecated hoverClass jQueryUI>=1.12.0
'ui-droppable-hover': this.res('class', 'adroppable')
},
autoDisable: true, // elFinder original, see jquery.elfinder.js
drop : function(e, ui) {
var dst = $(this),
targets = $.grep(ui.helper.data('files')||[], function(h) { return h? true : false; }),
result = [],
dups = [],
faults = [],
isCopy = ui.helper.hasClass('elfinder-drag-helper-plus'),
c = 'class',
cnt, hash, i, h;
if (typeof e.button === 'undefined' || ui.helper.data('namespace') !== namespace || ! self.insideWorkzone(e.pageX, e.pageY)) {
return false;
}
if (dst.hasClass(self.res(c, 'cwdfile'))) {
hash = self.cwdId2Hash(dst.attr('id'));
} else if (dst.hasClass(self.res(c, 'navdir'))) {
hash = self.navId2Hash(dst.attr('id'));
} else {
hash = cwd;
}
cnt = targets.length;
while (cnt--) {
h = targets[cnt];
// ignore drop into itself or in own location
if (h != hash && files[h].phash != hash) {
result.push(h);
} else {
((isCopy && h !== hash && files[hash].write)? dups : faults).push(h);
}
}
if (faults.length) {
return false;
}
ui.helper.data('droped', true);
if (dups.length) {
ui.helper.hide();
self.exec('duplicate', dups, {_userAction: true});
}
if (result.length) {
ui.helper.hide();
self.clipboard(result, !isCopy);
self.exec('paste', hash, {_userAction: true}, hash).always(function(){
self.clipboard([]);
self.trigger('unlockfiles', {files : targets});
});
self.trigger('drop', {files : targets});
}
}
};
/**
* Return true if filemanager is active
*
* @return Boolean
**/
this.enabled = function() {
return enabled && this.visible();
};
/**
* Return true if filemanager is visible
*
* @return Boolean
**/
this.visible = function() {
return node[0].elfinder && node.is(':visible');
};
/**
* Return file is root?
*
* @param Object target file object
* @return Boolean
*/
this.isRoot = function(file) {
return (file.isroot || ! file.phash)? true : false;
};
/**
* Return root dir hash for current working directory
*
* @param String target hash
* @param Boolean include fake parent (optional)
* @return String
*/
this.root = function(hash, fake) {
hash = hash || cwd;
var dir, i;
if (! fake) {
$.each(self.roots, function(id, rhash) {
if (hash.indexOf(id) === 0) {
dir = rhash;
return false;
}
});
if (dir) {
return dir;
}
}
dir = files[hash];
while (dir && dir.phash && (fake || ! dir.isroot)) {
dir = files[dir.phash];
}
if (dir) {
return dir.hash;
}
while (i in files && files.hasOwnProperty(i)) {
dir = files[i];
if (dir.mime === 'directory' && !dir.phash && dir.read) {
return dir.hash;
}
}
return '';
};
/**
* Return current working directory info
*
* @return Object
*/
this.cwd = function() {
return files[cwd] || {};
};
/**
* Return required cwd option
*
* @param String option name
* @param String target hash (optional)
* @return mixed
*/
this.option = function(name, target) {
var res, item;
target = target || cwd;
if (self.optionsByHashes[target] && typeof self.optionsByHashes[target][name] !== 'undefined') {
return self.optionsByHashes[target][name];
}
if (self.hasVolOptions && cwd !== target && (!(item = self.file(target)) || item.phash !== cwd)) {
res = '';
$.each(self.volOptions, function(id, opt) {
if (target.indexOf(id) === 0) {
res = opt[name] || '';
return false;
}
});
return res;
} else {
return cwdOptions[name] || '';
}
};
/**
* Return disabled commands by each folder
*
* @param Array target hashes
* @return Array
*/
this.getDisabledCmds = function(targets, flip) {
var disabled = {'hidden': true};
if (! Array.isArray(targets)) {
targets = [ targets ];
}
$.each(targets, function(i, h) {
var disCmds = self.option('disabledFlip', h);
if (disCmds) {
Object.assign(disabled, disCmds);
}
});
return flip? disabled : Object.keys(disabled);
};
/**
* Return file data from current dir or tree by it's hash
*
* @param String file hash
* @return Object
*/
this.file = function(hash, alsoHidden) {
return hash? (files[hash] || (alsoHidden? hiddenFiles[hash] : void(0))) : void(0);
};
/**
* Return all cached files
*
* @param String parent hash
* @return Object
*/
this.files = function(phash) {
var items = {};
if (phash) {
if (!ownFiles[phash]) {
return {};
}
$.each(ownFiles[phash], function(h) {
if (files[h]) {
items[h] = files[h];
} else {
delete ownFiles[phash][h];
}
});
return Object.assign({}, items);
}
return Object.assign({}, files);
};
/**
* Return list of file parents hashes include file hash
*
* @param String file hash
* @return Array
*/
this.parents = function(hash) {
var parents = [],
dir;
while (hash && (dir = this.file(hash))) {
parents.unshift(dir.hash);
hash = dir.phash;
}
return parents;
};
this.path2array = function(hash, i18) {
var file,
path = [];
while (hash) {
if ((file = files[hash]) && file.hash) {
path.unshift(i18 && file.i18 ? file.i18 : file.name);
hash = file.isroot? null : file.phash;
} else {
path = [];
break;
}
}
return path;
};
/**
* Return file path or Get path async with jQuery.Deferred
*
* @param Object file
* @param Boolean i18
* @param Object asyncOpt
* @return String|jQuery.Deferred
*/
this.path = function(hash, i18, asyncOpt) {
var path = files[hash] && files[hash].path
? files[hash].path
: this.path2array(hash, i18).join(cwdOptions.separator);
if (! asyncOpt || ! files[hash]) {
return path;
} else {
asyncOpt = Object.assign({notify: {type : 'parents', cnt : 1, hideCnt : true}}, asyncOpt);
var dfd = $.Deferred(),
notify = asyncOpt.notify,
noreq = false,
req = function() {
self.request({
data : {cmd : 'parents', target : files[hash].phash},
notify : notify,
preventFail : true
})
.done(done)
.fail(function() {
dfd.reject();
});
},
done = function() {
self.one('parentsdone', function() {
path = self.path(hash, i18);
if (path === '' && noreq) {
//retry with request
noreq = false;
req();
} else {
if (notify) {
clearTimeout(ntftm);
notify.cnt = -(parseInt(notify.cnt || 0));
self.notify(notify);
}
dfd.resolve(path);
}
});
},
ntftm;
if (path) {
return dfd.resolve(path);
} else {
if (self.ui['tree']) {
// try as no request
if (notify) {
ntftm = setTimeout(function() {
self.notify(notify);
}, self.notifyDelay);
}
noreq = true;
done(true);
} else {
req();
}
return dfd;
}
}
};
/**
* Return file url if set
*
* @param String file hash
* @param Object Options
* @return String|Object of jQuery Deferred
*/
this.url = function(hash, o) {
var file = files[hash],
opts = o || {},
async = opts.async || false,
temp = opts.temporary || false,
onetm = (opts.onetime && self.option('onetimeUrl', hash)) || false,
absurl = opts.absurl || false,
dfrd = (async || onetm)? $.Deferred() : null,
filter = function(url) {
if (url && absurl) {
url = self.convAbsUrl(url);
}
return url;
},
getUrl = function(url) {
if (url) {
return filter(url);
}
if (file.url) {
return filter(file.url);
}
if (typeof baseUrl === 'undefined') {
baseUrl = getBaseUrl();
}
if (baseUrl) {
return filter(baseUrl + $.map(self.path2array(hash), function(n) { return encodeURIComponent(n); }).slice(1).join('/'));
}
var params = Object.assign({}, self.customData, {
cmd: 'file',
target: file.hash
});
if (self.oldAPI) {
params.cmd = 'open';
params.current = file.phash;
}
return filter(self.options.url + (self.options.url.indexOf('?') === -1 ? '?' : '&') + $.param(params, true));
},
getBaseUrl = function() {
return self.option('url', (!self.isRoot(file) && file.phash) || file.hash);
},
baseUrl, res;
if (!file || !file.read) {
return async? dfrd.resolve('') : '';
}
if (onetm && (!file.url || file.url == '1') && !(baseUrl = getBaseUrl())) {
async = true;
this.request({
data : { cmd : 'url', target : hash, options : { onetime: 1 } },
preventDefault : true,
options: {async: async},
notify: {type : 'file', cnt : 1, hideCnt : true},
progressBar: opts.progressBar
}).done(function(data) {
dfrd.resolve(filter(data.url || ''));
}).fail(function() {
dfrd.resolve('');
});
} else {
if (file.url == '1' || (temp && !file.url && !(baseUrl = getBaseUrl()))) {
this.request({
data : { cmd : 'url', target : hash, options : { temporary: temp? 1 : 0 } },
preventDefault : true,
options: {async: async},
notify: async? {type : temp? 'file' : 'url', cnt : 1, hideCnt : true} : {},
progressBar: opts.progressBar
})
.done(function(data) {
file.url = data.url || '';
})
.fail(function() {
file.url = '';
})
.always(function() {
var url;
if (file.url && temp) {
url = file.url;
file.url = '1'; // restore
}
if (async) {
dfrd.resolve(getUrl(url));
} else {
return getUrl(url);
}
});
} else {
if (async) {
dfrd.resolve(getUrl());
} else {
return getUrl();
}
}
}
if (async) {
return dfrd;
}
};
/**
* Return file url for the extarnal service
*
* @param String hash The hash
* @param Object options The options
* @return Object jQuery Deferred
*/
this.forExternalUrl = function(hash, options) {
var onetime = self.option('onetimeUrl', hash),
opts = {
async: true,
absurl: true
};
opts[onetime? 'onetime' : 'temporary'] = true;
return self.url(hash, Object.assign({}, options, opts));
};
/**
* Return file url for open in elFinder
*
* @param String file hash
* @param Boolean for download link
* @param Object requestOpts The request options
* @return String
*/
this.openUrl = function(hash, download, callback, requestOpts) {
var file = files[hash],
url = '',
onetimeSize = (requestOpts || {}).onetimeSize || (5 * 1024 * 1024);
if (!file || !file.read) {
return '';
}
if (!download || download === 'sameorigin') {
if (file.url) {
if (file.url != 1) {
url = file.url;
}
} else if (cwdOptions.url && file.hash.indexOf(self.cwd().volumeid) === 0) {
url = cwdOptions.url + $.map(this.path2array(hash), function(n) { return encodeURIComponent(n); }).slice(1).join('/');
}
if (!download || this.isSameOrigin(url)) {
if (url) {
url += (url.match(/\?/)? '&' : '?') + '_'.repeat((url.match(/[\?&](_+)t=/g) || ['&t=']).sort().shift().match(/[\?&](_*)t=/)[1].length + 1) + 't=' + (file.ts || parseInt(+new Date()/1000));
if (callback) {
callback(url);
return;
} else {
return url;
}
}
}
}
if (callback && this.hasParrotHeaders()) {
if (!requestOpts) {
requestOpts = {};
} else {
delete requestOpts.onetimeSize;
}
if (!requestOpts.onetime && !requestOpts.temporary && file.size > onetimeSize) {
if (file.mime.match(/^video|audio/)) {
requestOpts.temporary = true;
} else {
requestOpts.onetime = true;
}
}
if (requestOpts.onetime || requestOpts.temporary) {
return this.url(file.hash, Object.assign({
async: true
}, requestOpts)).done(function(url) {
callback(url);
}).fail(function() {
callback('');
});
} else {
return this.getContents(hash, 'blob', requestOpts).done(function(blob){
url = (window.URL || window.webkitURL).createObjectURL(blob);
callback(url);
}).fail(function() {
callback('');
});
}
} else {
url = this.options.url;
url = url + (url.indexOf('?') === -1 ? '?' : '&')
+ (this.oldAPI ? 'cmd=open¤t='+file.phash : 'cmd=file')
+ '&target=' + file.hash
+ '&_t=' + (file.ts || parseInt(+new Date()/1000));
if (download === true) {
url += '&download=1';
}
$.each(this.customData, function(key, val) {
url += '&' + encodeURIComponent(key) + '=' + encodeURIComponent(val);
});
if (callback) {
callback(url);
return;
} else {
return url;
}
}
};
/**
* Return thumbnail url
*
* @param Object file object
* @return String
*/
this.tmb = function(file) {
var tmbUrl, tmbCrop,
cls = 'elfinder-cwd-bgurl',
url = '',
cData = {},
n = 0;
if ($.isPlainObject(file)) {
if (self.searchStatus.state && file.hash.indexOf(self.cwd().volumeid) !== 0) {
tmbUrl = self.option('tmbUrl', file.hash);
tmbCrop = self.option('tmbCrop', file.hash);
} else {
tmbUrl = cwdOptions.tmbUrl;
tmbCrop = cwdOptions.tmbCrop;
}
if (tmbCrop) {
cls += ' elfinder-cwd-bgurl-crop';
}
if (tmbUrl === 'self' && file.mime.indexOf('image/') === 0) {
url = self.openUrl(file.hash);
cls += ' elfinder-cwd-bgself';
} else if ((self.oldAPI || tmbUrl) && file && file.tmb && file.tmb != 1) {
url = tmbUrl + file.tmb;
} else if (self.newAPI && file && file.tmb && file.tmb != 1) {
url = file.tmb;
}
if (url) {
if (tmbUrl !== 'self') {
if (file.ts) {
cData._t = file.ts;
}
if (cwdOptions.tmbReqCustomData && Object.keys(this.customData).length) {
cData = Object.assign(cData, this.customData);
}
if (Object.keys(cData).length) {
url += (url.match(/\?/) ? '&' : '?');
$.each(cData, function (key, val) {
url += ((n++ === 0)? '' : '&') + encodeURIComponent(key) + '=' + encodeURIComponent(val);
});
}
}
return { url: url, className: cls };
}
}
return false;
};
/**
* Return selected files hashes
*
* @return Array
**/
this.selected = function() {
return selected.slice(0);
};
/**
* Return selected files info
*
* @return Array
*/
this.selectedFiles = function() {
return $.map(selected, function(hash) { return files[hash] ? Object.assign({}, files[hash]) : null; });
};
/**
* Return true if file with required name existsin required folder
*
* @param String file name
* @param String parent folder hash
* @return Boolean
*/
this.fileByName = function(name, phash) {
var hash;
for (hash in files) {
if (files.hasOwnProperty(hash) && files[hash].phash == phash && files[hash].name == name) {
return files[hash];
}
}
};
/**
* Valid data for required command based on rules
*
* @param String command name
* @param Object cammand's data
* @return Boolean
*/
this.validResponse = function(cmd, data) {
return data.error || this.rules[this.rules[cmd] ? cmd : 'defaults'](data);
};
/**
* Return bytes from ini formated size
*
* @param String ini formated size
* @return Integer
*/
this.returnBytes = function(val) {
var last;
if (isNaN(val)) {
if (! val) {
val = '';
}
// for ex. 1mb, 1KB
val = val.replace(/b$/i, '');
last = val.charAt(val.length - 1).toLowerCase();
val = val.replace(/[tgmk]$/i, '');
if (last == 't') {
val = val * 1024 * 1024 * 1024 * 1024;
} else if (last == 'g') {
val = val * 1024 * 1024 * 1024;
} else if (last == 'm') {
val = val * 1024 * 1024;
} else if (last == 'k') {
val = val * 1024;
}
val = isNaN(val)? 0 : parseInt(val);
} else {
val = parseInt(val);
if (val < 1) val = 0;
}
return val;
};
/**
* Process ajax request.
* Fired events :
* @todo
* @example
* @todo
* @return $.Deferred
*/
this.request = function(opts) {
var self = this,
o = this.options,
dfrd = $.Deferred(),
// request ID
reqId = (+ new Date()).toString(16) + Math.floor(1000 * Math.random()).toString(16),
// request data
data = Object.assign({}, self.customData, {mimes : o.onlyMimes}, opts.data || opts),
// command name
cmd = data.cmd,
// request type is binary
isBinary = (opts.options || {}).dataType === 'binary',
// current cmd is "open"
isOpen = (!opts.asNotOpen && cmd === 'open'),
// call default fail callback (display error dialog) ?
deffail = !(isBinary || opts.preventDefault || opts.preventFail),
// call default success callback ?
defdone = !(isBinary || opts.preventDefault || opts.preventDone),
// current progress of receive data
prog = opts.progressVal || 20,
// timer of fake progress
progTm = null,
// whether the notification dialog is currently displayed
hasNotify= false,
// options for notify dialog
notify = !opts.progressBar? (opts.notify? Object.assign({progress: prog * opts.notify.cnt}, opts.notify) : {}) : {},
// make cancel button
cancel = !!opts.cancel,
// do not normalize data - return as is
raw = isBinary || !!opts.raw,
// sync files on request fail
syncOnFail = opts.syncOnFail,
// use lazy()
lazy = !!opts.lazy,
// prepare function before done()
prepare = opts.prepare,
// navigate option object when cmd done
navigate = opts.navigate,
// open notify dialog timeout
timeout,
// use browser cache
useCache = (opts.options || {}).cache,
// request options
options = Object.assign({
url : o.url,
async : true,
type : this.requestType,
dataType : 'json',
cache : (self.api >= 2.1029), // api >= 2.1029 has unique request ID
data : data,
headers : this.customHeaders,
xhrFields: this.xhrFields,
progress : function(e) {
var p = e.loaded / e.total * 100;
progTm && clearTimeout(progTm);
if (opts.progressBar) {
try {
opts.progressBar.width(p + '%');
} catch(e) {}
} else {
if (hasNotify && notify.type) {
p = p * notify.cnt;
if (prog < p) {
self.notify({
type: notify.type,
progress: p - prog,
cnt: 0,
hideCnt: notify.hideCnt
});
prog = p;
}
}
}
if (opts.progress) {
try {
opts.progress(e);
} catch(e) {}
}
}
}, opts.options || {}),
/**
* Default success handler.
* Call default data handlers and fire event with command name.
*
* @param Object normalized response data
* @return void
**/
done = function(data) {
data.warning && self.error(data.warning);
if (isOpen) {
open(data);
} else {
self.updateCache(data);
}
self.lazy(function() {
// fire some event to update cache/ui
data.removed && data.removed.length && self.remove(data);
data.added && data.added.length && self.add(data);
data.changed && data.changed.length && self.change(data);
}).then(function() {
// fire event with command name
return self.lazy(function() {
self.trigger(cmd, data, false);
});
}).then(function() {
// fire event with command name + 'done'
return self.lazy(function() {
self.trigger(cmd + 'done');
});
}).then(function() {
// make toast message
if (data.toasts && Array.isArray(data.toasts)) {
$.each(data.toasts, function() {
this.msg && self.toast(this);
});
}
// force update content
data.sync && self.sync();
});
},
/**
* Request error handler. Reject dfrd with correct error message.
*
* @param jqxhr request object
* @param String request status
* @return void
**/
error = function(xhr, status) {
var error, data,
d = self.options.debug;
switch (status) {
case 'abort':
error = xhr.quiet ? '' : ['errConnect', 'errAbort'];
break;
case 'timeout':
error = ['errConnect', 'errTimeout'];
break;
case 'parsererror':
error = ['errResponse', 'errDataNotJSON'];
if (xhr.responseText) {
if (! cwd || (d && (d === 'all' || d['backend-error']))) {
error.push(xhr.responseText);
}
}
break;
default:
if (xhr.responseText) {
// check responseText, Is that JSON?
try {
data = JSON.parse(xhr.responseText);
if (data && data.error) {
error = data.error;
}
} catch(e) {}
}
if (! error) {
if (xhr.status == 403) {
error = ['errConnect', 'errAccess', 'HTTP error ' + xhr.status];
} else if (xhr.status == 404) {
error = ['errConnect', 'errNotFound', 'HTTP error ' + xhr.status];
} else if (xhr.status >= 500) {
error = ['errResponse', 'errServerError', 'HTTP error ' + xhr.status];
} else {
if (xhr.status == 414 && options.type === 'get') {
// retry by POST method
options.type = 'post';
self.abortXHR(xhr);
dfrd.xhr = xhr = self.transport.send(options).fail(error).done(success);
return;
}
error = xhr.quiet ? '' : ['errConnect', 'HTTP error ' + xhr.status];
}
}
}
self.trigger(cmd + 'done');
dfrd.reject({error: error}, xhr, status);
},
/**
* Request success handler. Valid response data and reject/resolve dfrd.
*
* @param Object response data
* @param String request status
* @return void
**/
success = function(response) {
// Set currrent request command name
self.currentReqCmd = cmd;
response.debug && self.responseDebug(response);
self.setCustomHeaderByXhr(xhr);
if (raw) {
self.abortXHR(xhr);
response && response.debug && self.debug('backend-debug', response);
return dfrd.resolve(response);
}
if (!response) {
return dfrd.reject({error :['errResponse', 'errDataEmpty']}, xhr, response);
} else if (!$.isPlainObject(response)) {
return dfrd.reject({error :['errResponse', 'errDataNotJSON']}, xhr, response);
} else if (response.error) {
if (isOpen) {
// check leafRoots
$.each(self.leafRoots, function(phash, roots) {
self.leafRoots[phash] = $.grep(roots, function(h) { return h !== data.target; });
});
}
return dfrd.reject({error :response.error}, xhr, response);
}
var resolve = function() {
var pushLeafRoots = function(name) {
if (self.leafRoots[data.target] && response[name]) {
$.each(self.leafRoots[data.target], function(i, h) {
var root;
if (root = self.file(h)) {
response[name].push(root);
}
});
}
},
setTextMimes = function() {
self.textMimes = {};
$.each(self.res('mimes', 'text'), function() {
self.textMimes[this.toLowerCase()] = true;
});
},
actionTarget;
if (isOpen) {
pushLeafRoots('files');
} else if (cmd === 'tree') {
pushLeafRoots('tree');
}
response = self.normalize(response);
if (!self.validResponse(cmd, response)) {
return dfrd.reject({error :(response.norError || 'errResponse')}, xhr, response);
}
if (isOpen) {
if (!self.api) {
self.api = response.api || 1;
if (self.api == '2.0' && typeof response.options.uploadMaxSize !== 'undefined') {
self.api = '2.1';
}
self.newAPI = self.api >= 2;
self.oldAPI = !self.newAPI;
}
if (response.textMimes && Array.isArray(response.textMimes)) {
self.resources.mimes.text = response.textMimes;
setTextMimes();
}
!self.textMimes && setTextMimes();
if (response.options) {
cwdOptions = Object.assign({}, cwdOptionsDefault, response.options);
}
if (response.netDrivers) {
self.netDrivers = response.netDrivers;
}
if (response.maxTargets) {
self.maxTargets = response.maxTargets;
}
if (!!data.init) {
self.uplMaxSize = self.returnBytes(response.uplMaxSize);
self.uplMaxFile = !!response.uplMaxFile? Math.min(parseInt(response.uplMaxFile), 50) : 20;
}
}
if (typeof prepare === 'function') {
prepare(response);
}
if (navigate) {
actionTarget = navigate.target || 'added';
if (response[actionTarget] && response[actionTarget].length) {
self.one(cmd + 'done', function() {
var targets = response[actionTarget],
newItems = self.findCwdNodes(targets),
inCwdHashes = function() {
var cwdHash = self.cwd().hash;
return $.map(targets, function(f) { return (f.phash && cwdHash === f.phash)? f.hash : null; });
},
hashes = inCwdHashes(),
makeToast = function(t) {
var node = void(0),
data = t.action? t.action.data : void(0),
cmd, msg, done;
if ((data || hashes.length) && t.action && (msg = t.action.msg) && (cmd = t.action.cmd) && (!t.action.cwdNot || t.action.cwdNot !== self.cwd().hash)) {
done = t.action.done;
data = t.action.data;
node = $('')
.append(
$('')
.on('mouseenter mouseleave', function(e) {
$(this).toggleClass('ui-state-hover', e.type == 'mouseenter');
})
.on('click', function() {
self.exec(cmd, data || hashes, {_userAction: true, _currentType: 'toast', _currentNode: $(this) });
if (done) {
self.one(cmd+'done', function() {
if (typeof done === 'function') {
done();
} else if (done === 'select') {
self.trigger('selectfiles', {files : inCwdHashes()});
}
});
}
})
);
}
delete t.action;
t.extNode = node;
return t;
};
if (! navigate.toast) {
navigate.toast = {};
}
!navigate.noselect && self.trigger('selectfiles', {files : self.searchStatus.state > 1 ? $.map(targets, function(f) { return f.hash; }) : hashes});
if (newItems.length) {
if (!navigate.noscroll) {
newItems.first().trigger('scrolltoview', {blink : false});
self.resources.blink(newItems, 'lookme');
}
if ($.isPlainObject(navigate.toast.incwd)) {
self.toast(makeToast(navigate.toast.incwd));
}
} else {
if ($.isPlainObject(navigate.toast.inbuffer)) {
self.toast(makeToast(navigate.toast.inbuffer));
}
}
});
}
}
dfrd.resolve(response);
response.debug && self.debug('backend-debug', response);
};
self.abortXHR(xhr);
lazy? self.lazy(resolve) : resolve();
},
xhr, _xhr,
xhrAbort = function(e) {
if (xhr && xhr.state() === 'pending') {
self.abortXHR(xhr, { quiet: true , abort: true });
if (!e || (e.type !== 'unload' && e.type !== 'destroy')) {
self.autoSync();
}
}
},
abort = function(e){
self.trigger(cmd + 'done');
if (e.type == 'autosync') {
if (e.data.action != 'stop') return;
} else if (e.type != 'unload' && e.type != 'destroy' && e.type != 'openxhrabort') {
if (!e.data.added || !e.data.added.length) {
return;
}
}
xhrAbort(e);
},
request = function(mode) {
var queueAbort = function() {
syncOnFail = false;
dfrd.reject();
};
if (mode) {
if (mode === 'cmd') {
return cmd;
}
}
if (isOpen) {
if (currentOpenCmd && currentOpenCmd.state() === 'pending') {
if (currentOpenCmd._target === data.target) {
return dfrd.reject('openabort');
} else {
if (currentOpenCmd.xhr) {
currentOpenCmd.xhr.queueAbort();
} else {
currentOpenCmd.reject('openabort');
}
}
}
currentOpenCmd = dfrd;
currentOpenCmd._target = data.target;
}
dfrd.always(function() {
delete options.headers['X-elFinderReqid'];
if (isOpen) {
currentOpenCmd = null;
}
}).fail(function(error, xhr, response) {
var errData, errMsg;
if (isOpen && error === 'openabort') {
error = '';
syncOnFail = false;
}
errData = {
cmd: cmd,
err: error,
xhr: xhr,
rc: response
};
// unset this cmd queue when user canceling
// see notify : function - `cancel.reject(0);`
if (error === 0) {
if (requestQueue.length) {
requestQueue = $.grep(requestQueue, function(req) {
return (req('cmd') === cmd) ? false : true;
});
}
}
// trigger "requestError" event
self.trigger('requestError', errData);
if (errData._getEvent && errData._getEvent().isDefaultPrevented()) {
deffail = false;
syncOnFail = false;
if (error) {
error.error = '';
}
}
// abort xhr
xhrAbort();
if (isOpen) {
openDir = self.file(data.target);
openDir && openDir.volumeid && self.isRoot(openDir) && delete self.volumeExpires[openDir.volumeid];
}
self.trigger(cmd + 'fail', response);
errMsg = (typeof error === 'object')? error.error : error;
if (errMsg) {
deffail ? self.error(errMsg) : self.debug('error', self.i18n(errMsg));
}
syncOnFail && self.sync();
});
if (!cmd) {
syncOnFail = false;
return dfrd.reject({error :'errCmdReq'});
}
if (self.maxTargets && data.targets && data.targets.length > self.maxTargets) {
syncOnFail = false;
return dfrd.reject({error :['errMaxTargets', self.maxTargets]});
}
defdone && dfrd.done(done);
// quiet abort not completed "open" requests
if (isOpen) {
while ((_xhr = queue.pop())) {
_xhr.queueAbort();
}
if (cwd !== data.target) {
while ((_xhr = cwdQueue.pop())) {
_xhr.queueAbort();
}
}
}
// trigger abort autoSync for commands to add the item
if ($.inArray(cmd, (self.cmdsToAdd + ' autosync').split(' ')) !== -1) {
if (cmd !== 'autosync') {
self.autoSync('stop');
dfrd.always(function() {
self.autoSync();
});
}
self.trigger('openxhrabort');
}
delete options.preventFail;
if (self.api >= 2.1029) {
if (useCache) {
options.headers['X-elFinderReqid'] = reqId;
} else {
Object.assign(options.data, { reqid : reqId });
}
}
// function for set value of this syncOnFail
dfrd.syncOnFail = function(state) {
syncOnFail = !!state;
};
requestCnt++;
dfrd.xhr = xhr = self.transport.send(options).always(function() {
// set responseURL from native xhr object
if (options._xhr && typeof options._xhr.responseURL !== 'undefined') {
xhr.responseURL = options._xhr.responseURL || '';
}
--requestCnt;
if (requestQueue.length) {
requestQueue.shift()();
}
}).fail(error).done(success);
if (self.api >= 2.1029) {
xhr._requestId = reqId;
}
if (isOpen || (data.compare && cmd === 'info')) {
// regist function queueAbort
xhr.queueAbort = queueAbort;
// add autoSync xhr into queue
queue.unshift(xhr);
// bind abort()
data.compare && self.bind(self.cmdsToAdd + ' autosync openxhrabort', abort);
dfrd.always(function() {
var ndx = $.inArray(xhr, queue);
data.compare && self.unbind(self.cmdsToAdd + ' autosync openxhrabort', abort);
ndx !== -1 && queue.splice(ndx, 1);
});
} else if ($.inArray(cmd, self.abortCmdsOnOpen) !== -1) {
// regist function queueAbort
xhr.queueAbort = queueAbort;
// add "open" xhr, only cwd xhr into queue
cwdQueue.unshift(xhr);
dfrd.always(function() {
var ndx = $.inArray(xhr, cwdQueue);
ndx !== -1 && cwdQueue.splice(ndx, 1);
});
}
// abort pending xhr on window unload or elFinder destroy
self.bind('unload destroy', abort);
dfrd.always(function() {
self.unbind('unload destroy', abort);
});
return dfrd;
},
queueingRequest = function() {
// show notify
if (notify.type && notify.cnt) {
if (cancel) {
notify.cancel = dfrd;
opts.eachCancel && (notify.id = +new Date());
}
timeout = setTimeout(function() {
// start fake count up
progTm = setTimeout(progFakeUp, 1000);
self.notify(notify);
hasNotify = true;
dfrd.always(function() {
notify.cnt = -(parseInt(notify.cnt)||0);
self.notify(notify);
hasNotify = false;
});
}, self.notifyDelay);
dfrd.always(function() {
clearTimeout(timeout);
});
}
// queueing
if (requestCnt < requestMaxConn) {
// do request
return request();
} else {
if (isOpen) {
requestQueue.unshift(request);
} else {
requestQueue.push(request);
}
return dfrd;
}
},
progFakeUp = function() {
var add;
if (hasNotify && progTm) {
add = 1 * notify.cnt;
progTm = null;
self.notify({
type: notify.type,
progress: add,
cnt: 0,
hideCnt: notify.hideCnt
});
prog += add;
if ((prog / notify.cnt) < 80) {
progTm = setTimeout(progFakeUp, 500);
}
}
},
bindData = {opts: opts, result: true},
openDir;
// prevent request initial request is completed
if (!self.api && !data.init) {
syncOnFail = false;
return dfrd.reject();
}
// trigger "request.cmd" that callback be able to cancel request by substituting "false" for "event.data.result"
self.trigger('request.' + cmd, bindData, true);
if (! bindData.result) {
self.trigger(cmd + 'done');
return dfrd.reject();
} else if (typeof bindData.result === 'object' && bindData.result.promise) {
bindData.result
.done(queueingRequest)
.fail(function() {
self.trigger(cmd + 'done');
dfrd.reject();
});
return dfrd;
}
return queueingRequest();
};
/**
* Call cache()
* Store info about files/dirs in "files" object.
*
* @param Array files
* @param String type
* @return void
*/
this.cache = function(dataArray, type) {
if (! Array.isArray(dataArray)) {
dataArray = [ dataArray ];
}
cache(dataArray, type);
};
/**
* Update file object caches by respose data object
*
* @param Object respose data object
* @return void
*/
this.updateCache = function(data) {
if ($.isPlainObject(data)) {
data.files && data.files.length && cache(data.files, 'files');
data.tree && data.tree.length && cache(data.tree, 'tree');
data.removed && data.removed.length && remove(data.removed);
data.added && data.added.length && cache(data.added, 'add');
data.changed && data.changed.length && cache(data.changed, 'change');
}
};
/**
* Compare current files cache with new files and return diff
*
* @param Array new files
* @param String target folder hash
* @param Array exclude properties to compare
* @return Object
*/
this.diff = function(incoming, onlydir, excludeProps) {
var raw = {},
added = [],
removed = [],
changed = [],
excludes = null,
isChanged = function(hash) {
var l = changed.length;
while (l--) {
if (changed[l].hash == hash) {
return true;
}
}
};
$.each(incoming, function(i, f) {
raw[f.hash] = f;
});
// make excludes object
if (excludeProps && excludeProps.length) {
excludes = {};
$.each(excludeProps, function() {
excludes[this] = true;
});
}
// find removed
$.each(files, function(hash, f) {
if (! raw[hash] && (! onlydir || f.phash === onlydir)) {
removed.push(hash);
}
});
// compare files
$.each(raw, function(hash, file) {
var origin = files[hash],
orgKeys = {},
chkKeyLen;
if (!origin) {
added.push(file);
} else {
// make orgKeys object
$.each(Object.keys(origin), function() {
orgKeys[this] = true;
});
$.each(file, function(prop) {
delete orgKeys[prop];
if (! excludes || ! excludes[prop]) {
if (file[prop] !== origin[prop]) {
changed.push(file);
orgKeys = {};
return false;
}
}
});
chkKeyLen = Object.keys(orgKeys).length;
if (chkKeyLen !== 0) {
if (excludes) {
$.each(orgKeys, function(prop) {
if (excludes[prop]) {
--chkKeyLen;
}
});
}
(chkKeyLen !== 0) && changed.push(file);
}
}
});
// parents of removed dirs mark as changed (required for tree correct work)
$.each(removed, function(i, hash) {
var file = files[hash],
phash = file.phash;
if (phash
&& file.mime == 'directory'
&& $.inArray(phash, removed) === -1
&& raw[phash]
&& !isChanged(phash)) {
changed.push(raw[phash]);
}
});
return {
added : added,
removed : removed,
changed : changed
};
};
/**
* Sync content
*
* @return jQuery.Deferred
*/
this.sync = function(onlydir, polling) {
this.autoSync('stop');
var self = this,
compare = function(){
var c = '', cnt = 0, mtime = 0;
if (onlydir && polling) {
$.each(files, function(h, f) {
if (f.phash && f.phash === onlydir) {
++cnt;
mtime = Math.max(mtime, f.ts);
}
c = cnt+':'+mtime;
});
}
return c;
},
comp = compare(),
dfrd = $.Deferred().always(function() { !reqFail && self.trigger('sync'); }),
opts = [this.request({
data : {cmd : 'open', reload : 1, target : cwd, tree : (! onlydir && this.ui.tree) ? 1 : 0, compare : comp},
preventDefault : true
})],
exParents = function() {
var parents = [],
curRoot = self.file(self.root(cwd)),
curId = curRoot? curRoot.volumeid : null,
phash = self.cwd().phash,
isroot,pdir;
while(phash) {
if (pdir = self.file(phash)) {
if (phash.indexOf(curId) !== 0) {
parents.push( {target: phash, cmd: 'tree'} );
if (! self.isRoot(pdir)) {
parents.push( {target: phash, cmd: 'parents'} );
}
curRoot = self.file(self.root(phash));
curId = curRoot? curRoot.volumeid : null;
}
phash = pdir.phash;
} else {
phash = null;
}
}
return parents;
},
reqFail;
if (! onlydir && self.api >= 2) {
(cwd !== this.root()) && opts.push(this.request({
data : {cmd : 'parents', target : cwd},
preventDefault : true
}));
$.each(exParents(), function(i, data) {
opts.push(self.request({
data : {cmd : data.cmd, target : data.target},
preventDefault : true
}));
});
}
$.when.apply($, opts)
.fail(function(error, xhr) {
reqFail = (xhr && xhr.status != 200);
if (! polling || $.inArray('errOpen', error) !== -1) {
dfrd.reject(error);
self.parseError(error) && self.request({
data : {cmd : 'open', target : (self.lastDir('') || self.root()), tree : 1, init : 1},
notify : {type : 'open', cnt : 1, hideCnt : true}
});
} else {
dfrd.reject((error && xhr.status != 0)? error : void 0);
}
})
.done(function(odata) {
var pdata, argLen, i;
if (odata.cwd.compare) {
if (comp === odata.cwd.compare) {
return dfrd.reject();
}
}
// for 2nd and more requests
pdata = {tree : []};
// results marge of 2nd and more requests
argLen = arguments.length;
if (argLen > 1) {
for(i = 1; i < argLen; i++) {
if (arguments[i].tree && arguments[i].tree.length) {
pdata.tree.push.apply(pdata.tree, arguments[i].tree);
}
}
}
if (self.api < 2.1) {
if (! pdata.tree) {
pdata.tree = [];
}
pdata.tree.push(odata.cwd);
}
// data normalize
odata = self.normalize(odata);
if (!self.validResponse('open', odata)) {
return dfrd.reject((odata.norError || 'errResponse'));
}
pdata = self.normalize(pdata);
if (!self.validResponse('tree', pdata)) {
return dfrd.reject((pdata.norError || 'errResponse'));
}
var diff = self.diff(odata.files.concat(pdata && pdata.tree ? pdata.tree : []), onlydir);
diff.added.push(odata.cwd);
self.updateCache(diff);
// trigger events
diff.removed.length && self.remove(diff);
diff.added.length && self.add(diff);
diff.changed.length && self.change(diff);
return dfrd.resolve(diff);
})
.always(function() {
self.autoSync();
});
return dfrd;
};
this.upload = function(files) {
return this.transport.upload(files, this);
};
/**
* Bind keybord shortcut to keydown event
*
* @example
* elfinder.shortcut({
* pattern : 'ctrl+a',
* description : 'Select all files',
* callback : function(e) { ... },
* keypress : true|false (bind to keypress instead of keydown)
* })
*
* @param Object shortcut config
* @return elFinder
*/
this.shortcut = function(s) {
var patterns, pattern, code, i, parts;
if (this.options.allowShortcuts && s.pattern && $.isFunction(s.callback)) {
patterns = s.pattern.toUpperCase().split(/\s+/);
for (i= 0; i < patterns.length; i++) {
pattern = patterns[i];
parts = pattern.split('+');
code = (code = parts.pop()).length == 1
? (code > 0 ? code : code.charCodeAt(0))
: (code > 0 ? code : $.ui.keyCode[code]);
if (code && !shortcuts[pattern]) {
shortcuts[pattern] = {
keyCode : code,
altKey : $.inArray('ALT', parts) != -1,
ctrlKey : $.inArray('CTRL', parts) != -1,
shiftKey : $.inArray('SHIFT', parts) != -1,
type : s.type || 'keydown',
callback : s.callback,
description : s.description,
pattern : pattern
};
}
}
}
return this;
};
/**
* Registered shortcuts
*
* @type Object
**/
this.shortcuts = function() {
var ret = [];
$.each(shortcuts, function(i, s) {
ret.push([s.pattern, self.i18n(s.description)]);
});
return ret;
};
/**
* Get/set clipboard content.
* Return new clipboard content.
*
* @example
* this.clipboard([]) - clean clipboard
* this.clipboard([{...}, {...}], true) - put 2 files in clipboard and mark it as cutted
*
* @param Array new files hashes
* @param Boolean cut files?
* @return Array
*/
this.clipboard = function(hashes, cut) {
var map = function() { return $.map(clipboard, function(f) { return f.hash; }); };
if (hashes !== void(0)) {
clipboard.length && this.trigger('unlockfiles', {files : map()});
remember = {};
clipboard = $.map(hashes||[], function(hash) {
var file = files[hash];
if (file) {
remember[hash] = true;
return {
hash : hash,
phash : file.phash,
name : file.name,
mime : file.mime,
read : file.read,
locked : file.locked,
cut : !!cut
};
}
return null;
});
this.trigger('changeclipboard', {clipboard : clipboard.slice(0, clipboard.length)});
cut && this.trigger('lockfiles', {files : map()});
}
// return copy of clipboard instead of refrence
return clipboard.slice(0, clipboard.length);
};
/**
* Return true if command enabled
*
* @param String command name
* @param String|void hash for check of own volume's disabled cmds
* @return Boolean
*/
this.isCommandEnabled = function(name, dstHash) {
var disabled, cmd,
cvid = self.cwd().volumeid || '';
// In serach results use selected item hash to check
if (!dstHash && self.searchStatus.state > 1 && self.selected().length) {
dstHash = self.selected()[0];
}
if (dstHash && (! cvid || dstHash.indexOf(cvid) !== 0)) {
disabled = self.option('disabledFlip', dstHash);
//if (! disabled) {
// disabled = {};
//}
} else {
disabled = cwdOptions.disabledFlip/* || {}*/;
}
cmd = this._commands[name];
return cmd ? (cmd.alwaysEnabled || !disabled[name]) : false;
};
/**
* Exec command and return result;
*
* @param String command name
* @param String|Array usualy files hashes
* @param String|Array command options
* @param String|void hash for enabled check of own volume's disabled cmds
* @return $.Deferred
*/
this.exec = function(cmd, files, opts, dstHash) {
var dfrd, resType;
// apply commandMap for keyboard shortcut
if (!dstHash && this.commandMap[cmd] && this.commandMap[cmd] !== 'hidden') {
cmd = this.commandMap[cmd];
}
if (cmd === 'open') {
if (this.searchStatus.state || this.searchStatus.ininc) {
this.trigger('searchend', { noupdate: true });
}
this.autoSync('stop');
}
if (!dstHash && files) {
if ($.isArray(files)) {
if (files.length) {
dstHash = files[0];
}
} else {
dstHash = files;
}
}
dfrd = this._commands[cmd] && this.isCommandEnabled(cmd, dstHash)
? this._commands[cmd].exec(files, opts)
: $.Deferred().reject('errUnknownCmd');
resType = typeof dfrd;
if (!(resType === 'object' && dfrd.promise)) {
self.debug('warning', '"cmd.exec()" should be returned "$.Deferred" but cmd "' + cmd + '" returned "' + resType + '"');
dfrd = $.Deferred().resolve();
}
this.trigger('exec', { dfrd : dfrd, cmd : cmd, files : files, opts : opts, dstHash : dstHash });
return dfrd;
};
/**
* Create and return dialog.
*
* @param String|DOMElement dialog content
* @param Object dialog options
* @return jQuery
*/
this.dialog = function(content, options) {
var dialog = $('').append(content).appendTo(node).elfinderdialog(options, self),
dnode = dialog.closest('.ui-dialog'),
resize = function(){
! dialog.data('draged') && dialog.is(':visible') && dialog.elfinderdialog('posInit');
};
if (dnode.length) {
self.bind('resize', resize);
dnode.on('remove', function() {
self.unbind('resize', resize);
});
}
return dialog;
};
/**
* Create and return toast.
*
* @param Object toast options - see ui/toast.js
* @return jQuery
*/
this.toast = function(options) {
return $('').appendTo(this.ui.toast).elfindertoast(options || {}, this);
};
/**
* Return UI widget or node
*
* @param String ui name
* @return jQuery
*/
this.getUI = function(ui) {
return ui? (this.ui[ui] || $()) : node;
};
/**
* Return elFinder.command instance or instances array
*
* @param String command name
* @return Object | Array
*/
this.getCommand = function(name) {
return name === void(0) ? this._commands : this._commands[name];
};
/**
* Resize elfinder node
*
* @param String|Number width
* @param String|Number height
* @return void
*/
this.resize = function(w, h) {
var getMargin = function() {
var m = node.outerHeight(true) - node.innerHeight(),
p = node;
while(p.get(0) !== heightBase.get(0)) {
p = p.parent();
m += p.outerHeight(true) - p.innerHeight();
if (! p.parent().length) {
// reached the document
break;
}
}
return m;
},
fit = ! node.hasClass('ui-resizable'),
prv = node.data('resizeSize') || {w: 0, h: 0},
mt, size = {};
if (heightBase && heightBase.data('resizeTm')) {
clearTimeout(heightBase.data('resizeTm'));
}
if (typeof h === 'string') {
if (mt = h.match(/^([0-9.]+)%$/)) {
// setup heightBase
if (! heightBase || ! heightBase.length) {
heightBase = $(window);
}
if (! heightBase.data('marginToMyNode')) {
heightBase.data('marginToMyNode', getMargin());
}
if (! heightBase.data('fitToBaseFunc')) {
heightBase.data('fitToBaseFunc', function(e) {
var tm = heightBase.data('resizeTm');
e.preventDefault();
e.stopPropagation();
tm && cancelAnimationFrame(tm);
if (! node.hasClass('elfinder-fullscreen') && (!self.UA.Mobile || heightBase.data('rotated') !== self.UA.Rotated)) {
heightBase.data('rotated', self.UA.Rotated);
heightBase.data('resizeTm', requestAnimationFrame(function() {
self.restoreSize();
}));
}
});
}
if (typeof heightBase.data('rotated') === 'undefined') {
heightBase.data('rotated', self.UA.Rotated);
}
h = heightBase.height() * (mt[1] / 100) - heightBase.data('marginToMyNode');
heightBase.off('resize.' + self.namespace, heightBase.data('fitToBaseFunc'));
fit && heightBase.on('resize.' + self.namespace, heightBase.data('fitToBaseFunc'));
}
}
node.css({ width : w, height : parseInt(h) });
size.w = Math.round(node.width());
size.h = Math.round(node.height());
node.data('resizeSize', size);
if (size.w !== prv.w || size.h !== prv.h) {
node.trigger('resize');
this.trigger('resize', {width : size.w, height : size.h});
}
};
/**
* Restore elfinder node size
*
* @return elFinder
*/
this.restoreSize = function() {
this.resize(width, height);
};
this.show = function() {
node.show();
this.enable().trigger('show');
};
this.hide = function() {
if (this.options.enableAlways) {
prevEnabled = enabled;
enabled = false;
}
this.disable();
this.trigger('hide');
node.hide();
};
/**
* Lazy execution function
*
* @param Object function
* @param Number delay
* @param Object options
* @return Object jQuery.Deferred
*/
this.lazy = function(func, delay, opts) {
var busy = function(state) {
var cnt = node.data('lazycnt'),
repaint;
if (state) {
repaint = node.data('lazyrepaint')? false : opts.repaint;
if (! cnt) {
node.data('lazycnt', 1)
.addClass('elfinder-processing');
} else {
node.data('lazycnt', ++cnt);
}
if (repaint) {
node.data('lazyrepaint', true).css('display'); // force repaint
}
} else {
if (cnt && cnt > 1) {
node.data('lazycnt', --cnt);
} else {
repaint = node.data('lazyrepaint');
node.data('lazycnt', 0)
.removeData('lazyrepaint')
.removeClass('elfinder-processing');
repaint && node.css('display'); // force repaint;
self.trigger('lazydone');
}
}
},
dfd = $.Deferred(),
callFunc = function() {
dfd.resolve(func.call(dfd));
busy(false);
};
delay = delay || 0;
opts = opts || {};
busy(true);
if (delay) {
setTimeout(callFunc, delay);
} else {
requestAnimationFrame(callFunc);
}
return dfd;
};
/**
* Destroy this elFinder instance
*
* @return void
**/
this.destroy = function() {
if (node && node[0].elfinder) {
node.hasClass('elfinder-fullscreen') && self.toggleFullscreen(node);
this.options.syncStart = false;
this.autoSync('forcestop');
this.trigger('destroy').disable();
clipboard = [];
selected = [];
listeners = {};
shortcuts = {};
$(window).off('.' + namespace);
$(document).off('.' + namespace);
self.trigger = function(){};
$(beeper).remove();
node.off()
.removeData()
.empty()
.append(prevContent.contents())
.attr('class', prevContent.attr('class'))
.attr('style', prevContent.attr('style'));
delete node[0].elfinder;
// restore kept events
$.each(prevEvents, function(n, arr) {
$.each(arr, function(i, o) {
node.on(o.type + (o.namespace? '.'+o.namespace : ''), o.selector, o.handler);
});
});
}
};
/**
* Start or stop auto sync
*
* @param String|Bool stop
* @return void
*/
this.autoSync = function(mode) {
var sync;
if (self.options.sync >= 1000) {
if (syncInterval) {
clearTimeout(syncInterval);
syncInterval = null;
self.trigger('autosync', {action : 'stop'});
}
if (mode === 'stop') {
++autoSyncStop;
} else {
autoSyncStop = Math.max(0, --autoSyncStop);
}
if (autoSyncStop || mode === 'forcestop' || ! self.options.syncStart) {
return;
}
// run interval sync
sync = function(start){
var timeout;
if (cwdOptions.syncMinMs && (start || syncInterval)) {
start && self.trigger('autosync', {action : 'start'});
timeout = Math.max(self.options.sync, cwdOptions.syncMinMs);
syncInterval && clearTimeout(syncInterval);
syncInterval = setTimeout(function() {
var dosync = true, hash = cwd, cts;
if (cwdOptions.syncChkAsTs && files[hash] && (cts = files[hash].ts)) {
self.request({
data : {cmd : 'info', targets : [hash], compare : cts, reload : 1},
preventDefault : true
})
.done(function(data){
var ts;
dosync = true;
if (data.compare) {
ts = data.compare;
if (ts == cts) {
dosync = false;
}
}
if (dosync) {
self.sync(hash).always(function(){
if (ts) {
// update ts for cache clear etc.
files[hash].ts = ts;
}
sync();
});
} else {
sync();
}
})
.fail(function(error, xhr){
var err = self.parseError(error);
if (err && xhr.status != 0) {
self.error(err);
if (Array.isArray(err) && $.inArray('errOpen', err) !== -1) {
self.request({
data : {cmd : 'open', target : (self.lastDir('') || self.root()), tree : 1, init : 1},
notify : {type : 'open', cnt : 1, hideCnt : true}
});
}
} else {
syncInterval = setTimeout(function() {
sync();
}, timeout);
}
});
} else {
self.sync(cwd, true).always(function(){
sync();
});
}
}, timeout);
}
};
sync(true);
}
};
/**
* Return bool is inside work zone of specific point
*
* @param Number event.pageX
* @param Number event.pageY
* @return Bool
*/
this.insideWorkzone = function(x, y, margin) {
var rectangle = this.getUI('workzone').data('rectangle');
margin = margin || 1;
if (x < rectangle.left + margin
|| x > rectangle.left + rectangle.width + margin
|| y < rectangle.top + margin
|| y > rectangle.top + rectangle.height + margin) {
return false;
}
return true;
};
/**
* Target ui node move to last of children of elFinder node fot to show front
*
* @param Object target Target jQuery node object
*/
this.toFront = function(target) {
var nodes = node.children('.ui-front').removeClass('elfinder-frontmost'),
lastnode = nodes.last();
nodes.css('z-index', '');
$(target).addClass('ui-front elfinder-frontmost').css('z-index', lastnode.css('z-index') + 1);
};
/**
* Remove class 'elfinder-frontmost' and hide() to target ui node
*
* @param Object target Target jQuery node object
* @param Boolean nohide Do not hide
*/
this.toHide =function(target, nohide) {
var tgt = $(target),
last;
!nohide && tgt.hide();
if (tgt.hasClass('elfinder-frontmost')) {
tgt.removeClass('elfinder-frontmost');
last = node.children('.ui-front:visible:not(.elfinder-frontmost)').last();
if (last.length) {
requestAnimationFrame(function() {
if (!node.children('.elfinder-frontmost:visible').length) {
self.toFront(last);
last.trigger('frontmost');
}
});
}
}
};
/**
* Return css object for maximize
*
* @return Object
*/
this.getMaximizeCss = function() {
return {
width : '100%',
height : '100%',
margin : 0,
top : 0,
left : 0,
display : 'block',
position: 'fixed',
zIndex : Math.max(self.zIndex? (self.zIndex + 1) : 0 , 1000),
maxWidth : '',
maxHeight: ''
};
};
// Closure for togglefullscreen
(function() {
// check is in iframe
if (inFrame && self.UA.Fullscreen) {
self.UA.Fullscreen = false;
if (parentIframe && typeof parentIframe.attr('allowfullscreen') !== 'undefined') {
self.UA.Fullscreen = true;
}
}
var orgStyle, bodyOvf, resizeTm, fullElm, exitFull, toFull, funcObj,
cls = 'elfinder-fullscreen',
clsN = 'elfinder-fullscreen-native',
checkDialog = function() {
var t = 0,
l = 0;
$.each(node.children('.ui-dialog,.ui-draggable'), function(i, d) {
var $d = $(d),
pos = $d.position();
if (pos.top < 0) {
$d.css('top', t);
t += 20;
}
if (pos.left < 0) {
$d.css('left', l);
l += 20;
}
});
},
setFuncObj = function() {
var useFullscreen = self.storage('useFullscreen');
funcObj = self.UA.Fullscreen && (useFullscreen? useFullscreen > 0 : self.options.commandsOptions.fullscreen.mode === 'screen') ? {
// native full screen mode
fullElm: function() {
return document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement || null;
},
exitFull: function() {
if (document.exitFullscreen) {
return document.exitFullscreen();
} else if (document.webkitExitFullscreen) {
return document.webkitExitFullscreen();
} else if (document.mozCancelFullScreen) {
return document.mozCancelFullScreen();
} else if (document.msExitFullscreen) {
return document.msExitFullscreen();
}
},
toFull: function(elem) {
if (elem.requestFullscreen) {
return elem.requestFullscreen();
} else if (elem.webkitRequestFullscreen) {
return elem.webkitRequestFullscreen();
} else if (elem.mozRequestFullScreen) {
return elem.mozRequestFullScreen();
} else if (elem.msRequestFullscreen) {
return elem.msRequestFullscreen();
}
return false;
}
} : {
// node element maximize mode
fullElm: function() {
var full;
if (node.hasClass(cls)) {
return node.get(0);
} else {
full = node.find('.' + cls);
if (full.length) {
return full.get(0);
}
}
return null;
},
exitFull: function() {
var elm;
$(window).off('resize.' + namespace, resize);
if (bodyOvf !== void(0)) {
$('body').css('overflow', bodyOvf);
}
bodyOvf = void(0);
if (orgStyle) {
elm = orgStyle.elm;
restoreStyle(elm);
$(elm).trigger('resize', {fullscreen: 'off'});
}
$(window).trigger('resize');
},
toFull: function(elem) {
bodyOvf = $('body').css('overflow') || '';
$('body').css('overflow', 'hidden');
$(elem).css(self.getMaximizeCss())
.addClass(cls)
.trigger('resize', {fullscreen: 'on'});
checkDialog();
$(window).on('resize.' + namespace, resize).trigger('resize');
return true;
}
};
},
restoreStyle = function(elem) {
if (orgStyle && orgStyle.elm == elem) {
$(elem).removeClass(cls + ' ' + clsN).attr('style', orgStyle.style);
orgStyle = null;
}
},
resize = function(e) {
var elm;
if (e.target === window) {
resizeTm && cancelAnimationFrame(resizeTm);
resizeTm = requestAnimationFrame(function() {
if (elm = funcObj.fullElm()) {
$(elm).trigger('resize', {fullscreen: 'on'});
}
});
}
};
setFuncObj();
$(document).on('fullscreenchange.' + namespace + ' webkitfullscreenchange.' + namespace + ' mozfullscreenchange.' + namespace + ' MSFullscreenChange.' + namespace, function(e){
if (self.UA.Fullscreen) {
var elm = funcObj.fullElm(),
win = $(window);
resizeTm && cancelAnimationFrame(resizeTm);
if (elm === null) {
win.off('resize.' + namespace, resize);
if (orgStyle) {
elm = orgStyle.elm;
restoreStyle(elm);
$(elm).trigger('resize', {fullscreen: 'off'});
}
} else {
$(elm).addClass(cls + ' ' + clsN)
.attr('style', 'width:100%; height:100%; margin:0; padding:0;')
.trigger('resize', {fullscreen: 'on'});
win.on('resize.' + namespace, resize);
checkDialog();
}
win.trigger('resize');
}
});
/**
* Toggle Full Scrren Mode
*
* @param Object target
* @param Bool full
* @return Object | Null DOM node object of current full scrren
*/
self.toggleFullscreen = function(target, full) {
var elm = $(target).get(0),
curElm = null;
curElm = funcObj.fullElm();
if (curElm) {
if (curElm == elm) {
if (full === true) {
return curElm;
}
} else {
if (full === false) {
return curElm;
}
}
funcObj.exitFull();
return null;
} else {
if (full === false) {
return null;
}
}
setFuncObj();
orgStyle = {elm: elm, style: $(elm).attr('style')};
if (funcObj.toFull(elm) !== false) {
return elm;
} else {
orgStyle = null;
return null;
}
};
})();
// Closure for toggleMaximize
(function(){
var cls = 'elfinder-maximized',
resizeTm,
resize = function(e) {
if (e.target === window && e.data && e.data.elm) {
var elm = e.data.elm;
resizeTm && cancelAnimationFrame(resizeTm);
resizeTm = requestAnimationFrame(function() {
elm.trigger('resize', {maximize: 'on'});
});
}
},
exitMax = function(elm) {
$(window).off('resize.' + namespace, resize);
$('body').css('overflow', elm.data('bodyOvf'));
elm.removeClass(cls)
.attr('style', elm.data('orgStyle'))
.removeData('bodyOvf')
.removeData('orgStyle');
elm.trigger('resize', {maximize: 'off'});
},
toMax = function(elm) {
elm.data('bodyOvf', $('body').css('overflow') || '')
.data('orgStyle', elm.attr('style'))
.addClass(cls)
.css(self.getMaximizeCss());
$('body').css('overflow', 'hidden');
$(window).on('resize.' + namespace, {elm: elm}, resize);
elm.trigger('resize', {maximize: 'on'});
};
/**
* Toggle Maximize target node
*
* @param Object target
* @param Bool max
* @return void
*/
self.toggleMaximize = function(target, max) {
var elm = $(target),
maximized = elm.hasClass(cls);
if (maximized) {
if (max === true) {
return;
}
exitMax(elm);
} else {
if (max === false) {
return;
}
toMax(elm);
}
};
})();
/************* init stuffs ****************/
Object.assign($.ui.keyCode, {
'F1' : 112,
'F2' : 113,
'F3' : 114,
'F4' : 115,
'F5' : 116,
'F6' : 117,
'F7' : 118,
'F8' : 119,
'F9' : 120,
'F10' : 121,
'F11' : 122,
'F12' : 123,
'DIG0' : 48,
'DIG1' : 49,
'DIG2' : 50,
'DIG3' : 51,
'DIG4' : 52,
'DIG5' : 53,
'DIG6' : 54,
'DIG7' : 55,
'DIG8' : 56,
'DIG9' : 57,
'NUM0' : 96,
'NUM1' : 97,
'NUM2' : 98,
'NUM3' : 99,
'NUM4' : 100,
'NUM5' : 101,
'NUM6' : 102,
'NUM7' : 103,
'NUM8' : 104,
'NUM9' : 105,
'CONTEXTMENU' : 93,
'DOT' : 190
});
this.dragUpload = false;
this.xhrUpload = (typeof XMLHttpRequestUpload != 'undefined' || typeof XMLHttpRequestEventTarget != 'undefined') && typeof File != 'undefined' && typeof FormData != 'undefined';
// configure transport object
this.transport = {};
if (typeof(this.options.transport) == 'object') {
this.transport = this.options.transport;
if (typeof(this.transport.init) == 'function') {
this.transport.init(this);
}
}
if (typeof(this.transport.send) != 'function') {
this.transport.send = function(opts) {
if (!self.UA.IE) {
// keep native xhr object for handling property responseURL
opts._xhr = new XMLHttpRequest();
opts.xhr = function() {
if (opts.progress) {
opts._xhr.addEventListener('progress', opts.progress);
}
return opts._xhr;
};
}
return $.ajax(opts);
};
}
if (this.transport.upload == 'iframe') {
this.transport.upload = $.proxy(this.uploads.iframe, this);
} else if (typeof(this.transport.upload) == 'function') {
this.dragUpload = !!this.options.dragUploadAllow;
} else if (this.xhrUpload && !!this.options.dragUploadAllow) {
this.transport.upload = $.proxy(this.uploads.xhr, this);
this.dragUpload = true;
} else {
this.transport.upload = $.proxy(this.uploads.iframe, this);
}
/**
* Decoding 'raw' string converted to unicode
*
* @param String str
* @return String
*/
this.decodeRawString = function(str) {
var charCodes = function(str) {
var i, len, arr;
for (i=0,len=str.length,arr=[]; i= 0xd800 && c <= 0xdbff) {
scalars.push((c & 1023) + 64 << 10 | arr[++i] & 1023);
} else {
scalars.push(c);
}
}
return scalars;
},
decodeUTF8 = function(arr) {
var i, len, c, str, char = String.fromCharCode;
for (i=0,len=arr.length,str=""; c=arr[i],i= 0xc2) {
str += char((c&31)<<6 | arr[++i]&63);
} else if (c <= 0xef && c >= 0xe0) {
str += char((c&15)<<12 | (arr[++i]&63)<<6 | arr[++i]&63);
} else if (c <= 0xf7 && c >= 0xf0) {
str += char(
0xd800 | ((c&7)<<8 | (arr[++i]&63)<<2 | arr[++i]>>>4&3) - 64,
0xdc00 | (arr[i++]&15)<<6 | arr[i]&63
);
} else {
str += char(0xfffd);
}
}
return str;
};
return decodeUTF8(scalarValues(str));
};
/**
* Gets target file contents by file.hash
*
* @param String hash The hash
* @param String responseType 'blob' or 'arraybuffer' (default)
* @param Object requestOpts The request options
* @return arraybuffer|blob The contents.
*/
this.getContents = function(hash, responseType, requestOpts) {
var self = this,
dfd = $.Deferred(),
type = responseType || 'arraybuffer',
url, req;
dfd.fail(function() {
req && req.state() === 'pending' && req.reject();
});
url = self.openUrl(hash);
if (!self.isSameOrigin(url)) {
url = self.openUrl(hash, true);
}
req = self.request(Object.assign({
data : {cmd : 'get'},
options : {
url: url,
type: 'get',
cache : true,
dataType : 'binary',
responseType : type,
processData: false
},
notify : {
type: 'file',
cnt: 1,
hideCnt: true
},
cancel : true
}, requestOpts || {}))
.fail(function() {
dfd.reject();
})
.done(function(data) {
dfd.resolve(data);
});
return dfd;
};
/**
* Gets the binary by url.
*
* @param {Object} opts The options
* @param {Function} callback The callback
* @param {Object} requestOpts The request options
* @return arraybuffer|blob The contents.
*/
this.getBinaryByUrl = function(opts, callback, requestOpts) {
var self = this,
dfd = $.Deferred(),
url, req;
dfd.fail(function() {
req && req.state() === 'pending' && req.reject();
});
req = self.request(Object.assign({
data : {cmd : 'get'},
options : Object.assign({
type: 'get',
cache : true,
dataType : 'binary',
responseType : 'blob',
processData: false
}, opts)
}, requestOpts || {}))
.fail(function() {
dfd.reject();
})
.done(function(data) {
callback && callback(data);
dfd.resolve(data);
});
return dfd;
};
/**
* Gets the mimetype.
*
* @param {string} name The name
* @param {string} orgMime The organization mime
* @return {string} The mimetype.
*/
this.getMimetype = function(name, orgMime) {
var mime = orgMime,
ext, m;
m = (name + '').match(/\.([^.]+)$/);
if (m && (ext = m[1])) {
if (!extToMimeTable) {
extToMimeTable = self.arrayFlip(self.mimeTypes);
}
if (!(mime = extToMimeTable[ext.toLowerCase()])) {
mime = orgMime;
}
}
return mime;
};
/**
* Supported check hash algorisms
*
* @type Array
*/
self.hashCheckers = [];
/**
* Closure of getContentsHashes()
*/
(function(self) {
var hashLibs = {};
if (window.Worker && window.ArrayBuffer) {
// make fm.hashCheckers
if (self.options.cdns.sparkmd5) {
hashLibs.SparkMD5 = true;
self.hashCheckers.push('md5');
}
if (self.options.cdns.jssha) {
hashLibs.jsSHA = true;
self.hashCheckers = self.hashCheckers.concat(['sha1', 'sha224', 'sha256', 'sha384', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512', 'shake128', 'shake256']);
}
}
/**
* Gets the contents hashes.
*
* @param String target target file.hash
* @param Object needHashes need hash lib names
* @param Object requestOpts The request options
* @return Object hashes with lib name as key
*/
self.getContentsHashes = function(target, needHashes, hashOpts, requestOpts) {
var dfd = $.Deferred(),
needs = self.arrayFlip(needHashes || ['md5'], true),
libs = [],
jobs = [],
res = {},
opts = hashOpts? hashOpts : {
shake128len : 256,
shake256len : 512
},
req;
dfd.fail(function() {
req && req.reject();
});
if (Object.keys(hashLibs).length) {
req = self.getContents(target, 'arraybuffer', requestOpts).done(function(arrayBuffer) {
if (needs.md5 && hashLibs.SparkMD5) {
jobs.push((function() {
var job = $.Deferred();
try {
var wk = self.getWorker();
job.fail(function() {
wk && wk.terminate();
});
wk.onmessage = function(ans) {
wk && wk.terminate();
if (ans.data.hash) {
var f;
res.md5 = ans.data.hash;
if (f = self.file(target)) {
f.md5 = res.md5;
}
} else if (ans.data.error) {
res.md5 = ans.data.error;
}
dfd.notify(res);
job.resolve();
};
wk.onerror = function(e) {
job.reject();
};
wk.postMessage({
scripts: [self.options.cdns.sparkmd5, self.getWorkerUrl('calcfilehash.js')],
data: { type: 'md5', bin: arrayBuffer }
});
dfd.fail(function() {
job.reject();
});
} catch(e) {
job.reject();
delete hashLibs.SparkMD5;
}
return job;
})());
}
if (hashLibs.jsSHA) {
$.each(['1', '224', '256', '384', '512', '3-224', '3-256', '3-384', '3-512', 'ke128', 'ke256'], function(i, v) {
if (needs['sha' + v]) {
jobs.push((function() {
var job = $.Deferred();
try {
var wk = self.getWorker();
job.fail(function() {
wk && wk.terminate();
});
wk.onmessage = function(ans) {
wk && wk.terminate();
if (ans.data.hash) {
var f;
res['sha' + v] = ans.data.hash;
if (f = self.file(target)) {
f['sha' + v] = res['sha' + v];
}
} else if (ans.data.error) {
res['sha' + v] = ans.data.error;
}
dfd.notify(res);
job.resolve();
};
wk.onerror = function(e) {
job.reject();
};
wk.postMessage({
scripts: [self.options.cdns.jssha, self.getWorkerUrl('calcfilehash.js')],
data: { type: v, bin: arrayBuffer, hashOpts: opts }
});
dfd.fail(function() {
job.reject();
});
} catch(e) {
job.reject();
delete hashLibs.jsSHA;
}
return job;
})());
}
});
}
if (jobs.length) {
$.when.apply(null, jobs).always(function() {
dfd.resolve(res);
});
} else {
dfd.reject();
}
}).fail(function() {
dfd.reject();
});
} else {
dfd.reject();
}
return dfd;
};
})(this);
/**
* Parse error value to display
*
* @param Mixed error
* @return Mixed parsed error
*/
this.parseError = function(error) {
var arg = error;
if ($.isPlainObject(arg)) {
arg = arg.error;
}
return arg;
};
/**
* Alias for this.trigger('error', {error : 'message'})
*
* @param String error message
* @return elFinder
**/
this.error = function() {
var arg = arguments[0],
opts = arguments[1] || null,
err;
if (arguments.length == 1 && typeof(arg) === 'function') {
return self.bind('error', arg);
} else {
err = this.parseError(arg);
return (err === true || !err)? this : self.trigger('error', {error: err, opts : opts});
}
};
// create bind/trigger aliases for build-in events
$.each(events, function(i, name) {
self[name] = function() {
var arg = arguments[0];
return arguments.length == 1 && typeof(arg) == 'function'
? self.bind(name, arg)
: self.trigger(name, $.isPlainObject(arg) ? arg : {});
};
});
// bind core event handlers
this
.enable(function() {
if (!enabled && self.api && self.visible() && self.ui.overlay.is(':hidden') && ! node.children('.elfinder-dialog.' + self.res('class', 'editing') + ':visible').length) {
enabled = true;
document.activeElement && document.activeElement.blur();
node.removeClass('elfinder-disabled');
}
})
.disable(function() {
prevEnabled = enabled;
enabled = false;
node.addClass('elfinder-disabled');
})
.open(function() {
selected = [];
})
.select(function(e) {
var cnt = 0,
unselects = [];
selected = $.grep(e.data.selected || e.data.value|| [], function(hash) {
if (unselects.length || (self.maxTargets && ++cnt > self.maxTargets)) {
unselects.push(hash);
return false;
} else {
return files[hash] ? true : false;
}
});
if (unselects.length) {
self.trigger('unselectfiles', {files: unselects, inselect: true});
self.toast({mode: 'warning', msg: self.i18n(['errMaxTargets', self.maxTargets])});
}
})
.error(function(e) {
var opts = {
cssClass : 'elfinder-dialog-error',
title : self.i18n('error'),
resizable : false,
destroyOnClose : true,
buttons : {}
},
node = self.getUI(),
cnt = node.children('.elfinder-dialog-error').length,
last, counter;
if (cnt < self.options.maxErrorDialogs) {
opts.buttons[self.i18n(self.i18n('btnClose'))] = function() { $(this).elfinderdialog('close'); };
if (e.data.opts && $.isPlainObject(e.data.opts)) {
Object.assign(opts, e.data.opts);
}
self.dialog(''+self.i18n(e.data.error), opts);
} else {
last = node.children('.elfinder-dialog-error:last').children('.ui-dialog-content:first');
counter = last.children('.elfinder-error-counter');
if (counter.length) {
counter.data('cnt', parseInt(counter.data('cnt')) + 1).html(self.i18n(['moreErrors', counter.data('cnt')]));
} else {
counter = $(''+ self.i18n(['moreErrors', 1]) +'').data('cnt', 1);
last.append(' ', counter);
}
}
})
.bind('tmb', function(e) {
$.each(e.data.images||[], function(hash, tmb) {
if (files[hash]) {
files[hash].tmb = tmb;
}
});
})
.bind('searchstart', function(e) {
Object.assign(self.searchStatus, e.data);
self.searchStatus.state = 1;
})
.bind('search', function(e) {
self.searchStatus.state = 2;
})
.bind('searchend', function() {
self.searchStatus.state = 0;
self.searchStatus.ininc = false;
self.searchStatus.mixed = false;
})
.bind('canMakeEmptyFile', function(e) {
var data = e.data,
obj = {};
if (data && Array.isArray(data.mimes)) {
if (!data.unshift) {
obj = self.mimesCanMakeEmpty;
}
$.each(data.mimes, function() {
if (!obj[this]) {
obj[this] = self.mimeTypes[this];
}
});
if (data.unshift) {
self.mimesCanMakeEmpty = Object.assign(obj, self.mimesCanMakeEmpty);
}
}
})
.bind('themechange', function() {
requestAnimationFrame(function() {
self.trigger('uiresize');
});
})
;
// We listen and emit a sound on delete according to option
if (true === this.options.sound) {
this.bind('playsound', function(e) {
var play = beeper.canPlayType && beeper.canPlayType('audio/wav; codecs="1"'),
file = e.data && e.data.soundFile;
play && file && play != '' && play != 'no' && $(beeper).html('')[0].play();
});
}
// bind external event handlers
$.each(this.options.handlers, function(event, callback) {
self.bind(event, callback);
});
/**
* History object. Store visited folders
*
* @type Object
**/
this.history = new this.history(this);
/**
* Root hashed
*
* @type Object
*/
this.roots = {};
/**
* leaf roots
*
* @type Object
*/
this.leafRoots = {};
this.volumeExpires = {};
/**
* Loaded commands
*
* @type Object
**/
this._commands = {};
if (!Array.isArray(this.options.commands)) {
this.options.commands = [];
}
if ($.inArray('*', this.options.commands) !== -1) {
this.options.commands = Object.keys(this.commands);
}
/**
* UI command map of cwd volume ( That volume driver option `uiCmdMap` )
*
* @type Object
**/
this.commandMap = {};
/**
* cwd options of each volume
* key: volumeid
* val: options object
*
* @type Object
*/
this.volOptions = {};
/**
* Has volOptions data
*
* @type Boolean
*/
this.hasVolOptions = false;
/**
* Hash of trash holders
* key: trash folder hash
* val: source volume hash
*
* @type Object
*/
this.trashes = {};
/**
* cwd options of each folder/file
* key: hash
* val: options object
*
* @type Object
*/
this.optionsByHashes = {};
/**
* UI Auto Hide Functions
* Each auto hide function mast be call to `fm.trigger('uiautohide')` at end of process
*
* @type Array
**/
this.uiAutoHide = [];
// trigger `uiautohide`
this.one('open', function() {
if (self.uiAutoHide.length) {
setTimeout(function() {
self.trigger('uiautohide');
}, 500);
}
});
// Auto Hide Functions sequential processing start
this.bind('uiautohide', function() {
if (self.uiAutoHide.length) {
self.uiAutoHide.shift()();
}
});
if (this.options.width) {
width = this.options.width;
}
if (this.options.height) {
height = this.options.height;
}
if (this.options.heightBase) {
heightBase = $(this.options.heightBase);
}
if (this.options.soundPath) {
soundPath = this.options.soundPath.replace(/\/+$/, '') + '/';
} else {
soundPath = this.baseUrl + soundPath;
}
if (this.options.parrotHeaders && Array.isArray(this.options.parrotHeaders) && this.options.parrotHeaders.length) {
this.parrotHeaders = this.options.parrotHeaders;
// check sessionStorage
$.each(this.parrotHeaders, function(i, h) {
var v = self.sessionStorage('core-ph:' + h);
if (v) {
self.customHeaders[h] = v;
}
});
} else {
this.parrotHeaders = [];
}
self.one('opendone', function() {
var tm;
// attach events to document
$(document)
// disable elfinder on click outside elfinder
.on('click.'+namespace, function(e) { enabled && ! self.options.enableAlways && !$(e.target).closest(node).length && self.disable(); })
// exec shortcuts
.on(keydown+' '+keypress+' '+keyup+' '+mousedown, execShortcut);
// attach events to window
self.options.useBrowserHistory && $(window)
.on('popstate.' + namespace, function(ev) {
var state = ev.originalEvent.state || {},
hasThash = state.thash? true : false,
dialog = node.find('.elfinder-frontmost:visible'),
input = node.find('.elfinder-navbar-dir,.elfinder-cwd-filename').find('input,textarea'),
onOpen, toast;
if (!hasThash) {
state = { thash: self.cwd().hash };
// scroll to elFinder node
$('html,body').animate({ scrollTop: node.offset().top });
}
if (dialog.length || input.length) {
history.pushState(state, null, location.pathname + location.search + '#elf_' + state.thash);
if (dialog.length) {
if (!dialog.hasClass(self.res('class', 'preventback'))) {
if (dialog.hasClass('elfinder-contextmenu')) {
$(document).trigger($.Event('keydown', { keyCode: $.ui.keyCode.ESCAPE, ctrlKey : false, shiftKey : false, altKey : false, metaKey : false }));
} else if (dialog.hasClass('elfinder-dialog')) {
dialog.elfinderdialog('close');
} else {
dialog.trigger('close');
}
}
} else {
input.trigger($.Event('keydown', { keyCode: $.ui.keyCode.ESCAPE, ctrlKey : false, shiftKey : false, altKey : false, metaKey : false }));
}
} else {
if (hasThash) {
!$.isEmptyObject(self.files()) && self.request({
data : {cmd : 'open', target : state.thash, onhistory : 1},
notify : {type : 'open', cnt : 1, hideCnt : true},
syncOnFail : true
});
} else {
onOpen = function() {
toast.trigger('click');
};
self.one('open', onOpen, true);
toast = self.toast({
msg: self.i18n('pressAgainToExit'),
onHidden: function() {
self.unbind('open', onOpen);
history.pushState(state, null, location.pathname + location.search + '#elf_' + state.thash);
}
});
}
}
});
$(window).on('resize.' + namespace, function(e){
if (e.target === this) {
tm && cancelAnimationFrame(tm);
tm = requestAnimationFrame(function() {
var prv = node.data('resizeSize') || {w: 0, h: 0},
size = {w: Math.round(node.width()), h: Math.round(node.height())};
node.data('resizeSize', size);
if (size.w !== prv.w || size.h !== prv.h) {
node.trigger('resize');
self.trigger('resize', {width : size.w, height : size.h});
}
});
}
})
.on('beforeunload.' + namespace,function(e){
var msg, cnt;
if (!self.pauseUnloadCheck()) {
if (node.is(':visible')) {
if (self.ui.notify.children().length && $.inArray('hasNotifyDialog', self.options.windowCloseConfirm) !== -1) {
msg = self.i18n('ntfsmth');
} else if (node.find('.'+self.res('class', 'editing')).length && $.inArray('editingFile', self.options.windowCloseConfirm) !== -1) {
msg = self.i18n('editingFile');
} else if ((cnt = Object.keys(self.selected()).length) && $.inArray('hasSelectedItem', self.options.windowCloseConfirm) !== -1) {
msg = self.i18n('hasSelected', ''+cnt);
} else if ((cnt = Object.keys(self.clipboard()).length) && $.inArray('hasClipboardData', self.options.windowCloseConfirm) !== -1) {
msg = self.i18n('hasClipboard', ''+cnt);
}
if (msg) {
e.returnValue = msg;
return msg;
}
}
self.trigger('unload');
}
});
// bind window onmessage for CORS
$(window).on('message.' + namespace, function(e){
var res = e.originalEvent || null,
obj, data;
if (res && (self.convAbsUrl(self.options.url).indexOf(res.origin) === 0 || self.convAbsUrl(self.uploadURL).indexOf(res.origin) === 0)) {
try {
obj = JSON.parse(res.data);
data = obj.data || null;
if (data) {
if (data.error) {
if (obj.bind) {
self.trigger(obj.bind+'fail', data);
}
self.error(data.error);
} else {
data.warning && self.error(data.warning);
self.updateCache(data);
data.removed && data.removed.length && self.remove(data);
data.added && data.added.length && self.add(data);
data.changed && data.changed.length && self.change(data);
if (obj.bind) {
self.trigger(obj.bind, data);
self.trigger(obj.bind+'done');
}
data.sync && self.sync();
}
}
} catch (e) {
self.sync();
}
}
});
// elFinder enable always
if (self.options.enableAlways) {
$(window).on('focus.' + namespace, function(e){
(e.target === this) && self.enable();
});
if (inFrame) {
$(window.top).on('focus.' + namespace, function() {
if (self.enable() && (! parentIframe || parentIframe.is(':visible'))) {
requestAnimationFrame(function() {
$(window).trigger('focus');
});
}
});
}
} else if (inFrame) {
$(window).on('blur.' + namespace, function(e){
enabled && e.target === this && self.disable();
});
}
// return focus to the window on click (elFInder in the frame)
if (inFrame) {
node.on('click', function(e) {
$(window).trigger('focus');
});
}
// elFinder to enable by mouse over
if (self.options.enableByMouseOver) {
node.on('mouseenter touchstart', function(e) {
(inFrame) && $(window).trigger('focus');
! self.enabled() && self.enable();
});
}
// When the browser tab turn to foreground/background
$(window).on('visibilitychange.' + namespace, function(e) {
var background = document.hidden || document.webkitHidden || document.msHidden;
// AutoSync turn On/Off
if (self.options.syncStart) {
self.autoSync(background? 'stop' : void(0));
}
});
});
// store instance in node
node[0].elfinder = this;
// auto load language file
dfrdsBeforeBootup.push((function() {
var lang = self.lang,
langJs = self.i18nBaseUrl + 'elfinder.' + lang + '.js',
dfd = $.Deferred().done(function() {
if (self.i18[lang]) {
self.lang = lang;
}
self.trigger('i18load');
i18n = self.lang === 'en'
? self.i18['en']
: $.extend(true, {}, self.i18['en'], self.i18[self.lang]);
});
if (!self.i18[lang]) {
self.lang = 'en';
if (self.hasRequire) {
require([langJs], function() {
dfd.resolve();
}, function() {
dfd.resolve();
});
} else {
self.loadScript([langJs], function() {
dfd.resolve();
}, {
loadType: 'tag',
error : function() {
dfd.resolve();
}
});
}
} else {
dfd.resolve();
}
return dfd;
})());
// elFinder boot up function
bootUp = function() {
var columnNames;
/**
* i18 messages
*
* @type Object
**/
self.messages = i18n.messages;
// check jquery ui
if (!($.fn.selectable && $.fn.draggable && $.fn.droppable && $.fn.resizable && $.fn.button && $.fn.slider)) {
return alert(self.i18n('errJqui'));
}
// check node
if (!node.length) {
return alert(self.i18n('errNode'));
}
// check connector url
if (!self.options.url) {
return alert(self.i18n('errURL'));
}
// column key/name map for fm.getColumnName()
columnNames = Object.assign({
name : self.i18n('name'),
perm : self.i18n('perms'),
date : self.i18n('modify'),
size : self.i18n('size'),
kind : self.i18n('kind'),
modestr : self.i18n('mode'),
modeoct : self.i18n('mode'),
modeboth : self.i18n('mode')
}, self.options.uiOptions.cwd.listView.columnsCustomName);
/**
* Gets the column name of cwd list view
*
* @param String key The key
* @return String The column name.
*/
self.getColumnName = function(key) {
return columnNames[key] || self.i18n(key);
};
/**
* Interface direction
*
* @type String
* @default "ltr"
**/
self.direction = i18n.direction;
/**
* Date/time format
*
* @type String
* @default "m.d.Y"
**/
self.dateFormat = self.options.dateFormat || i18n.dateFormat;
/**
* Date format like "Yesterday 10:20:12"
*
* @type String
* @default "{day} {time}"
**/
self.fancyFormat = self.options.fancyDateFormat || i18n.fancyDateFormat;
/**
* Date format for if upload file has not original unique name
* e.g. Clipboard image data, Image data taken with iOS
*
* @type String
* @default "ymd-His"
**/
self.nonameDateFormat = (self.options.nonameDateFormat || i18n.nonameDateFormat).replace(/[\/\\]/g, '_');
/**
* Css classes
*
* @type String
**/
self.cssClass = 'ui-helper-reset ui-helper-clearfix ui-widget ui-widget-content ui-corner-all elfinder elfinder-'
+(self.direction == 'rtl' ? 'rtl' : 'ltr')
+(self.UA.Touch? (' elfinder-touch' + (self.options.resizable ? ' touch-punch' : '')) : '')
+(self.UA.Mobile? ' elfinder-mobile' : '')
+(self.UA.iOS? ' elfinder-ios' : '')
+' '+self.options.cssClass;
// prepare node
node.addClass(self.cssClass)
.on(mousedown, function() {
!enabled && self.enable();
});
// draggable closure
(function() {
var ltr, wzRect, wzBottom, wzBottom2, nodeStyle,
keyEvt = keydown + 'draggable' + ' keyup.' + namespace + 'draggable';
/**
* Base draggable options
*
* @type Object
**/
self.draggable = {
appendTo : node,
addClasses : false,
distance : 4,
revert : true,
refreshPositions : false,
cursor : 'crosshair',
cursorAt : {left : 50, top : 47},
scroll : false,
start : function(e, ui) {
var helper = ui.helper,
targets = $.grep(helper.data('files')||[], function(h) {
if (h) {
remember[h] = true;
return true;
}
return false;
}),
locked = false,
cnt, h;
// fix node size
nodeStyle = node.attr('style');
node.width(node.width()).height(node.height());
// set var for drag()
ltr = (self.direction === 'ltr');
wzRect = self.getUI('workzone').data('rectangle');
wzBottom = wzRect.top + wzRect.height;
wzBottom2 = wzBottom - self.getUI('navdock').outerHeight(true);
self.draggingUiHelper = helper;
cnt = targets.length;
while (cnt--) {
h = targets[cnt];
if (files[h].locked) {
locked = true;
helper.data('locked', true);
break;
}
}
!locked && self.trigger('lockfiles', {files : targets});
helper.data('autoScrTm', setInterval(function() {
if (helper.data('autoScr')) {
self.autoScroll[helper.data('autoScr')](helper.data('autoScrVal'));
}
}, 50));
},
drag : function(e, ui) {
var helper = ui.helper,
autoScr, autoUp, bottom;
if ((autoUp = wzRect.top > e.pageY) || wzBottom2 < e.pageY) {
if (wzRect.cwdEdge > e.pageX) {
autoScr = (ltr? 'navbar' : 'cwd') + (autoUp? 'Up' : 'Down');
} else {
autoScr = (ltr? 'cwd' : 'navbar') + (autoUp? 'Up' : 'Down');
}
if (!autoUp) {
if (autoScr.substr(0, 3) === 'cwd') {
if (wzBottom < e.pageY) {
bottom = wzBottom;
} else {
autoScr = null;
}
} else {
bottom = wzBottom2;
}
}
if (autoScr) {
helper.data('autoScr', autoScr);
helper.data('autoScrVal', Math.pow((autoUp? wzRect.top - e.pageY : e.pageY - bottom), 1.3));
}
}
if (! autoScr) {
if (helper.data('autoScr')) {
helper.data('refreshPositions', 1).data('autoScr', null);
}
}
if (helper.data('refreshPositions') && $(this).elfUiWidgetInstance('draggable')) {
if (helper.data('refreshPositions') > 0) {
$(this).draggable('option', { refreshPositions : true, elfRefresh : true });
helper.data('refreshPositions', -1);
} else {
$(this).draggable('option', { refreshPositions : false, elfRefresh : false });
helper.data('refreshPositions', null);
}
}
},
stop : function(e, ui) {
var helper = ui.helper,
files;
$(document).off(keyEvt);
$(this).elfUiWidgetInstance('draggable') && $(this).draggable('option', { refreshPositions : false });
self.draggingUiHelper = null;
self.trigger('focus').trigger('dragstop');
if (! helper.data('droped')) {
files = $.grep(helper.data('files')||[], function(h) { return h? true : false ;});
self.trigger('unlockfiles', {files : files});
self.trigger('selectfiles', {files : self.selected()});
}
self.enable();
// restore node style
node.attr('style', nodeStyle);
helper.data('autoScrTm') && clearInterval(helper.data('autoScrTm'));
},
helper : function(e, ui) {
var element = this.id ? $(this) : $(this).parents('[id]:first'),
helper = $('
'),
icon = function(f) {
var mime = f.mime, i, tmb = self.tmb(f);
i = '';
if (tmb) {
i = $(i).addClass(tmb.className).css('background-image', "url('"+tmb.url+"')").get(0).outerHTML;
} else if (f.icon) {
i = $(i).css(self.getIconStyle(f, true)).get(0).outerHTML;
}
if (f.csscls) {
i = '