META-INF.resources.elfinder.js.elfinder.full.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of efw Show documentation
Show all versions of efw Show documentation
"efw" is an Ajax framework for server site JavaScript designed
and developed by Escco Co., Ltd. using a goal-oriented method.
It is provided as open source free software.
The newest version!
/*!
* elFinder - file manager for web
* Version 2.1.20 (2017-01-11)
* http://elfinder.org
*
* Copyright 2009-2017, 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(node, opts) {
//this.time('load');
var self = this,
/**
* Node on which elfinder creating
*
* @type jQuery
**/
node = $(node),
/**
* Store node contents.
*
* @see this.destroy
* @type jQuery
**/
prevContent = $('').append(node.contents()),
/**
* Store node inline styles
*
* @see this.destroy
* @type String
**/
prevStyle = node.attr('style'),
/**
* Instance ID. Required to get/set cookie
*
* @type String
**/
id = node.attr('id') || '',
/**
* Events namespace
*
* @type String
**/
namespace = 'elfinder-' + (id ? id : Math.random().toString().substr(2, 7)),
/**
* Mousedown event
*
* @type String
**/
mousedown = 'mousedown.'+namespace,
/**
* Keydown event
*
* @type String
**/
keydown = 'keydown.'+namespace,
/**
* Keypress event
*
* @type String
**/
keypress = 'keypress.'+namespace,
/**
* Is shortcuts/commands enabled
*
* @type Boolean
**/
enabled = true,
/**
* Store enabled value before ajax requiest
*
* @type Boolean
**/
prevEnabled = true,
/**
* 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
*
* @type Object
**/
cwdOptions = {
path : '',
url : '',
tmbUrl : '',
disabled : [],
separator : '/',
archives : [],
extract : [],
copyOverwrite : true,
uploadOverwrite : true,
uploadMaxSize : 0,
jpgQuality : 100,
tmbCrop : false,
tmb : false // old API
},
/**
* Files/dirs cache
*
* @type Object
**/
files = {},
/**
* 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 Array
**/
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
*
* @type Number
* @default 400
**/
height = 400,
/**
* elfinder path for sound played on remove
* @type String
* @default ./sounds/
**/
soundPath = './sounds/',
beeper = $(document.createElement('audio')).hide().appendTo('body')[0],
syncInterval,
autoSyncStop = 0,
uiCmdMapPrev = '',
open = function(data) {
// NOTES: Do not touch data object
var volumeid, contextmenu, emptyDirs = {}, stayDirs = {},
rmClass, hashes, calc, gc, collapsed, prevcwd;
if (self.api >= 2.1) {
self.commandMap = (data.options.uiCmdMap && Object.keys(data.options.uiCmdMap).length)? data.options.uiCmdMap : {};
// support volume driver option `uiCmdMap`
if (uiCmdMapPrev !== JSON.stringify(self.commandMap)) {
uiCmdMapPrev = JSON.stringify(self.commandMap);
if (Object.keys(self.commandMap).length) {
// for contextmenu
contextmenu = self.getUI('contextmenu');
if (!contextmenu.data('cmdMaps')) {
contextmenu.data('cmdMaps', {});
}
volumeid = data.cwd? data.cwd.volumeid : null;
if (volumeid && !contextmenu.data('cmdMaps')[volumeid]) {
contextmenu.data('cmdMaps')[volumeid] = self.commandMap;
}
}
}
} else {
self.options.sync = 0;
}
if (data.init) {
// init - reset cache
files = {};
} 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(n, i) {
if (!files[i]) {
return true;
}
var isDir = (files[i].mime === 'directory'),
phash = files[i].phash,
pnav;
if (
(!isDir
|| emptyDirs[phash]
|| (!stayDirs[phash]
&& $('#'+self.navHash2Id(files[i].hash)).is(':hidden')
&& $('#'+self.navHash2Id(phash)).next('.elfinder-navbar-subtree').children().length > 100
)
)
&& (isDir || phash !== cwd)
&& $.inArray(i, remember) === -1
) {
if (isDir && !emptyDirs[phash]) {
emptyDirs[phash] = true;
$('#'+self.navHash2Id(phash))
.removeClass(rmClass)
.next('.elfinder-navbar-subtree').empty();
}
delete files[i];
} else if (isDir) {
stayDirs[phash] = true;
}
};
gc = function() {
if (hashes.length) {
$.each(hashes.splice(0, 100), calc);
if (hashes.length) {
setTimeout(gc, 20);
}
}
};
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]);
}
self.lastDir(cwd);
self.autoSync();
},
/**
* Store info about files/dirs in "files" object.
*
* @param Array files
* @return void
**/
cache = function(data) {
var defsorter = { name: true, perm: true, date: true, size: true, kind: true },
sorterChk = (self.sorters.length === 0),
l = data.length,
f, i;
for (i = 0; i < l; i++) {
f = data[i];
if (f.name && f.hash && f.mime) {
if (sorterChk && f.phash === cwd) {
$.each(self.sortRules, function(key) {
if (defsorter[key] || typeof f[key] !== 'undefined' || (key === 'mode' && typeof f.perm !== 'undefined')) {
self.sorters.push(key);
}
});
sorterChk = false;
}
// make or update of leaf roots cache
if (f.isroot && f.phash) {
if (! self.leafRoots[f.phash]) {
self.leafRoots[f.phash] = [ f.hash ];
} else {
if ($.inArray(f.hash, self.leafRoots[f.phash]) === -1) {
self.leafRoots[f.phash].push(f.hash);
}
}
if (files[f.phash]) {
if (! files[f.phash].dirs) {
files[f.phash].dirs = 1;
}
if (f.ts && (files[f.phash].ts || 0) < f.ts) {
files[f.phash].ts = f.ts;
}
}
}
files[f.hash] = f;
}
}
},
/**
* Exec shortcut
*
* @param jQuery.Event keydown/keypress event
* @return void
*/
execShortcut = function(e) {
var code = e.keyCode,
ctrlKey = !!(e.ctrlKey || e.metaKey),
ddm;
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
node.find('.ui-widget.elfinder-button-menu').hide();
}
}
},
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;
})();
;
/**
* 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 = [];
/**
* Configuration options
*
* @type Object
**/
this.options = $.extend(true, {}, this._options, opts||{});
// auto load required CSS
if (this.options.cssAutoLoad) {
(function(fm) {
var myTag = $('head > script[src$="js/elfinder.min.js"],script[src$="js/elfinder.full.js"]:first'),
baseUrl, hide, fi, cnt;
if (myTag.length) {
// hide elFinder node while css loading
hide = $('');
$('head').append(hide);
baseUrl = myTag.attr('src').replace(/js\/[^\/]+$/, '');
fm.loadCss([baseUrl+'css/elfinder.min.css', baseUrl+'css/theme.css']);
// additional CSS files
if ($.isArray(fm.options.cssAutoLoad)) {
fm.loadCss(fm.options.cssAutoLoad);
}
// check css loaded and remove hide
cnt = 1000; // timeout 10 secs
fi = setInterval(function() {
if (--cnt > 0 && node.css('visibility') !== 'hidden') {
clearInterval(fi);
hide.remove();
fm.trigger('cssloaded');
}
}, 10);
} else {
fm.options.cssAutoLoad = false;
}
})(this);
}
/**
* Volume option to set the properties of the root Stat
*
* @type Array
*/
this.optionProperties = ['icon', 'csscls', 'tmbUrl', 'uiCmdMap', 'netkey'];
if (opts.ui) {
this.options.ui = opts.ui;
}
if (opts.commands) {
this.options.commands = opts.commands;
}
if (opts.uiOptions && opts.uiOptions.toolbar) {
this.options.uiOptions.toolbar = opts.uiOptions.toolbar;
}
if (opts.uiOptions && opts.uiOptions.cwd && opts.uiOptions.cwd.listView && opts.uiOptions.cwd.listView.columns) {
this.options.uiOptions.cwd.listView.columns = opts.uiOptions.cwd.listView.columns;
}
if (opts.uiOptions && opts.uiOptions.cwd && opts.uiOptions.cwd.listView && opts.uiOptions.cwd.listView.columnsCustomName) {
this.options.uiOptions.cwd.listView.columnsCustomName = opts.uiOptions.cwd.listView.columnsCustomName;
}
if (! inFrame && ! this.options.enableAlways && $('body').children().length === 2) { // only node and beeper
this.options.enableAlways = true;
}
/**
* Is elFinder over CORS
*
* @type Boolean
**/
this.isCORS = false;
// configure for CORS
(function(){
var parseUrl = document.createElement('a'),
parseUploadUrl;
parseUrl.href = opts.url;
if (opts.urlUpload && (opts.urlUpload !== opts.url)) {
parseUploadUrl = document.createElement('a');
parseUploadUrl.href = opts.urlUpload;
}
if (window.location.host !== parseUrl.host || (parseUploadUrl && (window.location.host !== parseUploadUrl.host))) {
self.isCORS = true;
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;
}
})();
$.extend(this.options.contextmenu, opts.contextmenu);
/**
* Ajax request type
*
* @type String
* @default "get"
**/
this.requestType = /^(get|post)$/i.test(this.options.requestType) ? this.options.requestType.toLowerCase() : 'get';
/**
* Any data to send across every ajax request
*
* @type Object
* @default {}
**/
this.customData = $.isPlainObject(this.options.customData) ? this.options.customData : {};
/**
* 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 : {};
/**
* command names for into queue for only cwd requests
* these commands aborts before `open` request
*
* @type Array
* @default ['tmb']
*/
this.abortCmdsOnOpen = this.options.abortCmdsOnOpen || ['tmb'];
/**
* ID. Required to create unique cookie name
*
* @type String
**/
this.id = id;
/**
* 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;
/**
* Interface language
*
* @type String
* @default "en"
**/
this.lang = this.i18[this.options.lang] && this.i18[this.options.lang].messages ? this.options.lang : 'en';
i18n = this.lang == 'en'
? this.i18['en']
: $.extend(true, {}, this.i18['en'], this.i18[this.lang]);
/**
* Interface direction
*
* @type String
* @default "ltr"
**/
this.direction = i18n.direction;
/**
* i18 messages
*
* @type Object
**/
this.messages = i18n.messages;
/**
* Date/time format
*
* @type String
* @default "m.d.Y"
**/
this.dateFormat = this.options.dateFormat || i18n.dateFormat;
/**
* Date format like "Yesterday 10:20:12"
*
* @type String
* @default "{day} {time}"
**/
this.fancyFormat = this.options.fancyDateFormat || i18n.fancyDateFormat;
/**
* 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';
/**
* Css classes
*
* @type String
**/
this.cssClass = 'ui-helper-reset ui-helper-clearfix ui-widget ui-widget-content ui-corner-all elfinder elfinder-'
+(this.direction == 'rtl' ? 'rtl' : 'ltr')
+(this.UA.Touch? (' elfinder-touch' + (this.options.resizable ? ' touch-punch' : '')) : '')
+(this.UA.Mobile? ' elfinder-mobile' : '')
+' '+this.options.cssClass;
/**
* 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
ininc : false // in incremental search
};
/**
* 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;
}
})();
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.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;
// draggable closure
(function() {
var ltr, wzRect, wzBottom, 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 = $.map(helper.data('files')||[], function(h) { return h || null ;}),
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;
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,
autoUp;
if ((autoUp = wzRect.top > e.pageY) || wzBottom < e.pageY) {
if (wzRect.cwdEdge > e.pageX) {
helper.data('autoScr', (ltr? 'navbar' : 'cwd') + (autoUp? 'Up' : 'Down'));
} else {
helper.data('autoScr', (ltr? 'cwd' : 'navbar') + (autoUp? 'Up' : 'Down'));
}
helper.data('autoScrVal', Math.pow((autoUp? wzRect.top - e.pageY : e.pageY - wzBottom), 1.3));
} else {
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 = $.map(helper.data('files')||[], function(h) { return h || null ;});
self.trigger('unlockfiles', {files : files});
self.trigger('selectfiles', {files : files});
}
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;
}
return i;
},
hashes, l, ctr;
self.draggingUiHelper && self.draggingUiHelper.stop(true, true);
self.trigger('dragstart', {target : element[0], originalEvent : e});
hashes = element.hasClass(self.res('class', 'cwdfile'))
? self.selected()
: [self.navId2Hash(element.attr('id'))];
helper.append(icon(files[hashes[0]])).data('files', hashes).data('locked', false).data('droped', false).data('namespace', namespace).data('dropover', 0);
if ((l = hashes.length) > 1) {
helper.append(icon(files[hashes[l-1]]) + ''+l+'');
}
$(document).on(keyEvt, function(e){
var chk = (e.shiftKey||e.ctrlKey||e.metaKey);
if (ctr !== chk) {
ctr = chk;
if (helper.is(':visible') && helper.data('dropover') && ! helper.data('droped')) {
helper.toggleClass('elfinder-drag-helper-plus', helper.data('locked')? true : ctr);
self.trigger(ctr? 'unlockfiles' : 'lockfiles', {files : hashes, helper: helper});
}
}
});
return helper;
}
};
})();
/**
* 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 = $.map(ui.helper.data('files')||[], function(h) { return h || null }),
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);
}
if (result.length) {
ui.helper.hide();
self.clipboard(result, !isCopy);
self.exec('paste', hash, void 0, 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.phash && !dir.mime == 'directory' && 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;
target = target || cwd;
if (self.optionsByHashes[target] && typeof self.optionsByHashes[target][name] !== 'undefined') {
return self.optionsByHashes[target][name];
}
if (cwd !== target) {
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) {
var disabled = [];
if (! $.isArray(targets)) {
targets = [ targets ];
}
$.each(targets, function(i, h) {
var disCmds = self.option('disabled', h);
if (disCmds) {
$.each(disCmds, function(i, cmd) {
if ($.inArray(cmd, disabled) === -1) {
disabled.push(cmd);
}
});
}
});
return disabled;
}
/**
* Return file data from current dir or tree by it's hash
*
* @param String file hash
* @return Object
*/
this.file = function(hash) {
return hash? files[hash] : void(0);
};
/**
* Return all cached files
*
* @return Array
*/
this.files = function() {
return $.extend(true, {}, files);
};
/**
* Return list of file parents hashes include file hash
*
* @param String file hash
* @return Array
*/
this.parents = function(hash) {
var parents = [],
dir;
while ((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 = $.extend({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
* @return String
*/
this.url = function(hash) {
var file = files[hash],
baseUrl;
if (!file || !file.read) {
return '';
}
if (file.url == '1') {
this.request({
data : {cmd : 'url', target : hash},
preventFail : true,
options: {async: false}
})
.done(function(data) {
file.url = data.url || '';
})
.fail(function() {
file.url = '';
});
}
if (file.url) {
return file.url;
}
baseUrl = (file.hash.indexOf(self.cwd().volumeid) === 0)? cwdOptions.url : this.option('url', file.hash);
if (baseUrl) {
return baseUrl + $.map(this.path2array(hash), function(n) { return encodeURIComponent(n); }).slice(1).join('/')
}
var params = $.extend({}, this.customData, {
cmd: 'file',
target: file.hash
});
if (this.oldAPI) {
params.cmd = 'open';
params.current = file.phash;
}
return this.options.url + (this.options.url.indexOf('?') === -1 ? '?' : '&') + $.param(params, true);
};
/**
* Return file url for open in elFinder
*
* @param String file hash
* @param Boolean for download link
* @return String
*/
this.openUrl = function(hash, download) {
var file = files[hash],
url = '';
if (!file || !file.read) {
return '';
}
if (!download) {
if (file.url) {
if (file.url != 1) {
return file.url;
}
} else if (cwdOptions.url && file.hash.indexOf(self.cwd().volumeid) === 0) {
return cwdOptions.url + $.map(this.path2array(hash), function(n) { return encodeURIComponent(n); }).slice(1).join('/');
}
}
url = this.options.url;
url = url + (url.indexOf('?') === -1 ? '?' : '&')
+ (this.oldAPI ? 'cmd=open¤t='+file.phash : 'cmd=file')
+ '&target=' + file.hash;
if (download) {
url += '&download=1';
}
$.each(this.options.customData, function(key, val) {
url += '&' + encodeURIComponent(key) + '=' + encodeURIComponent(val);
});
return url;
};
/**
* Return thumbnail url
*
* @param Object file object
* @return String
*/
this.tmb = function(file) {
var tmbUrl, tmbCrop,
cls = 'elfinder-cwd-bgurl',
url = '';
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;
}
if (url) {
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] ? $.extend({}, 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;
};
/**
* Proccess ajax request.
* Fired events :
* @todo
* @example
* @todo
* @return $.Deferred
*/
this.request = function(opts) {
var self = this,
o = this.options,
dfrd = $.Deferred(),
// request data
data = $.extend({}, o.customData, {mimes : o.onlyMimes}, opts.data || opts),
// command name
cmd = data.cmd,
isOpen = (cmd === 'open'),
// call default fail callback (display error dialog) ?
deffail = !(opts.preventDefault || opts.preventFail),
// call default success callback ?
defdone = !(opts.preventDefault || opts.preventDone),
// options for notify dialog
notify = $.extend({}, opts.notify),
// make cancel button
cancel = !!opts.cancel,
// do not normalize data - return as is
raw = !!opts.raw,
// sync files on request fail
syncOnFail = opts.syncOnFail,
// use lazy()
lazy = !!opts.lazy,
// prepare function before done()
prepare = opts.prepare,
// open notify dialog timeout
timeout,
// request options
options = $.extend({
url : o.url,
async : true,
type : this.requestType,
dataType : 'json',
cache : false,
// timeout : 100,
data : data,
headers : this.customHeaders,
xhrFields: this.xhrFields
}, 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);
isOpen && open(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);
});
}).then(function() {
// fire event with command name + 'done'
return self.lazy(function() {
self.trigger(cmd + 'done');
});
}).then(function() {
// 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;
switch (status) {
case 'abort':
error = xhr.quiet ? '' : ['errConnect', 'errAbort'];
break;
case 'timeout':
error = ['errConnect', 'errTimeout'];
break;
case 'parsererror':
error = ['errResponse', 'errDataNotJSON'];
if (xhr.responseText) {
self.debug('backend-debug', { debug: {phpErrors: [ xhr.responseText] }});
if (! cwd) {
xhr.responseText && 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 == 414 && options.type === 'get') {
// retry by POST method
options.type = 'post';
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, 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;
if (raw) {
response && response.debug && self.debug('backend-debug', response);
return dfrd.resolve(response);
}
if (!response) {
return dfrd.reject(['errResponse', 'errDataEmpty'], xhr, response);
} else if (!$.isPlainObject(response)) {
return dfrd.reject(['errResponse', 'errDataNotJSON'], xhr, response);
} else if (response.error) {
return dfrd.reject(response.error, xhr, response);
} else if (!self.validResponse(cmd, response)) {
return dfrd.reject('errResponse', 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);
}
});
}
};
if (isOpen) {
pushLeafRoots('files');
} else if (cmd === 'tree') {
pushLeafRoots('tree');
}
response = self.normalize(response);
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.options) {
cwdOptions = $.extend({}, cwdOptions, response.options);
}
if (response.netDrivers) {
self.netDrivers = response.netDrivers;
}
if (response.maxTargets) {
self.maxTargets = response.maxTargets;
}
if (isOpen && !!data.init) {
self.uplMaxSize = self.returnBytes(response.uplMaxSize);
self.uplMaxFile = !!response.uplMaxFile? parseInt(response.uplMaxFile) : 20;
}
if (typeof prepare === 'function') {
prepare(response);
}
dfrd.resolve(response);
response.debug && self.debug('backend-debug', response);
};
lazy? self.lazy(resolve) : resolve();
},
xhr, _xhr,
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;
}
}
if (xhr.state() == 'pending') {
xhr.quiet = true;
xhr.abort();
if (e.type != 'unload' && e.type != 'destroy') {
self.autoSync();
}
}
},
request = function() {
dfrd.fail(function(error, xhr, response) {
self.trigger(cmd + 'fail', response);
if (error) {
deffail ? self.error(error) : self.debug('error', self.i18n(error));
}
syncOnFail && self.sync();
})
if (!cmd) {
syncOnFail = false;
return dfrd.reject('errCmdReq');
}
if (self.maxTargets && data.targets && data.targets.length > self.maxTargets) {
syncOnFail = false;
return dfrd.reject(['errMaxTargets', self.maxTargets]);
}
defdone && dfrd.done(done);
if (notify.type && notify.cnt) {
if (cancel) {
notify.cancel = dfrd;
}
timeout = setTimeout(function() {
self.notify(notify);
dfrd.always(function() {
notify.cnt = -(parseInt(notify.cnt)||0);
self.notify(notify);
})
}, self.notifyDelay)
dfrd.always(function() {
clearTimeout(timeout);
});
}
// quiet abort not completed "open" requests
if (isOpen) {
while ((_xhr = queue.pop())) {
if (_xhr.state() == 'pending') {
_xhr.quiet = true;
_xhr.abort();
}
}
if (cwd !== data.target) {
while ((_xhr = cwdQueue.pop())) {
if (_xhr.state() == 'pending') {
_xhr.quiet = true;
_xhr.abort();
}
}
}
}
// 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
dfrd.xhr = xhr = self.transport.send(options).fail(error).done(success);
if (isOpen || (data.compare && cmd === 'info')) {
// 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) {
// 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;
},
bindData = {opts: opts, result: true};
// 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(request)
.fail(function() {
self.trigger(cmd + 'done');
dfrd.reject();
});
return dfrd;
}
// do request
return request();
};
/**
* 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 = [],
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;
});
// 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];
if (!origin) {
added.push(file);
} else {
$.each(file, function(prop) {
if (! excludeProps || $.inArray(prop, excludeProps) === -1) {
if (file[prop] !== origin[prop]) {
changed.push(file)
return false;
}
}
});
}
});
// 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().done(function() { 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) {
if (! self.isRoot(pdir)) {
parents.push( {target: phash, cmd: 'tree'} );
}
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;
};
if (! onlydir) {
(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) {
if (! polling || $.inArray('errOpen', error) !== -1) {
dfrd.reject(error);
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) {
pdata.tree = (pdata.tree || []).push(odata.cwd);
}
// data normalize
odata = self.normalize(odata);
pdata = self.normalize(pdata);
var diff = self.diff(odata.files.concat(pdata && pdata.tree ? pdata.tree : []), onlydir);
diff.added.push(odata.cwd);
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);
};
/**
* 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
* @return elFinder
*/
this.bind = function(event, callback) {
var i;
if (typeof(callback) == 'function') {
event = ('' + event).toLowerCase().split(/\s+/);
for (i = 0; i < event.length; i++) {
if (listeners[event[i]] === void(0)) {
listeners[event[i]] = [];
}
listeners[event[i]].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, l, ci;
event = ('' + event).toLowerCase().split(/\s+/);
for (i = 0; i < event.length; i++) {
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
*
* @param String event type
* @param Object data to send across event
* @param Boolean allow modify data (call by reference of data)
* @return elFinder
*/
this.trigger = function(event, data, allowModify) {
var event = event.toLowerCase(),
isopen = (event === 'open'),
handlers = listeners[event] || [], i, l, jst;
this.debug('event-'+event, data);
if (isopen && !allowModify) {
// for performance tuning
jst = JSON.stringify(data);
}
if (l = handlers.length) {
event = $.Event(event);
if (allowModify) {
event.data = data;
}
for (i = 0; i < l; i++) {
if (! handlers[i]) {
// probably un-binded this handler
continue;
}
// only callback has argument
if (handlers[i].length) {
if (!allowModify) {
// to avoid data modifications. remember about "sharing" passing arguments in js :)
event.data = isopen? JSON.parse(jst) : $.extend(true, {}, data);
}
}
try {
if (handlers[i](event, this) === false
|| event.isDefaultPrevented()) {
this.debug('event-stoped', event.type);
break;
}
} catch (ex) {
window.console && window.console.log && window.console.log(ex);
}
}
}
return this;
};
/**
* Get event listeners
*
* @param String event type
* @return Array listed event functions
*/
this.getListeners = function(event) {
return event? listeners[event.toLowerCase()] : listeners;
};
/**
* 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.push(hash);
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,
cvid = self.cwd().volumeid || '';
if (dstHash && (! cvid || dstHash.indexOf(cvid) !== 0)) {
disabled = self.option('disabled', dstHash);
if (! disabled) {
disabled = [];
}
} else {
disabled = cwdOptions.disabled;
}
return this._commands[name] ? $.inArray(name, disabled) === -1 : 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) {
if (cmd === 'open') {
if (this.searchStatus.state || this.searchStatus.ininc) {
this.trigger('searchend', { noupdate: true });
}
this.autoSync('stop');
}
return this._commands[cmd] && this.isCommandEnabled(cmd, dstHash)
? this._commands[cmd].exec(files, opts)
: $.Deferred().reject('No such command');
};
/**
* 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, this),
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 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 Number height
* @return void
*/
this.resize = function(w, h) {
node.css('width', w).height(h).trigger('resize');
this.trigger('resize', {width : node.width(), height : node.height()});
};
/**
* 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().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();
delay = delay || 0;
opts = opts || {};
busy(true);
setTimeout(function() {
dfd.resolve(func.call(dfd));
busy(false);
}, delay);
return dfd;
}
/**
* Destroy this elFinder instance
*
* @return void
**/
this.destroy = function() {
if (node && node[0].elfinder) {
this.options.syncStart = false;
this.autoSync('forcestop');
this.trigger('destroy').disable();
clipboard = [];
selected = [];
listeners = {};
shortcuts = {};
$(window).off('.' + namespace);
$(document).off('.' + namespace);
self.trigger = function(){}
node.off();
node.removeData();
node.empty();
node[0].elfinder = null;
$(beeper).remove();
node.append(prevContent.contents()).removeClass(this.cssClass).attr('style', prevStyle);
}
};
/**
* 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 && (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){
if (error && xhr.status != 0) {
self.error(error);
if ($.inArray('errOpen', error) !== -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 lastnode = node.children(':last');
target = $(target);
if (lastnode.get(0) !== target.get(0)) {
lastnode.after(target);
}
};
/**
* Return css object for maximize
*
* @return Object
*/
this.getMaximizeCss = function() {
return {
width : '100%',
height : '100%',
margin : 0,
padding : 0,
top : 0,
left : 0,
display : 'block',
position: 'fixed',
zIndex : Math.max(self.zIndex? (self.zIndex + 1) : 0 , 1000)
};
};
// 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,
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;
}
});
},
funcObj = self.UA.Fullscreen? {
// 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 && clearTimeout(resizeTm);
resizeTm = setTimeout(function() {
if (elm = funcObj.fullElm()) {
$(elm).trigger('resize', {fullscreen: 'on'});
}
}, 100);
}
};
$(document).on('fullscreenchange.' + namespace + ' webkitfullscreenchange.' + namespace + ' mozfullscreenchange.' + namespace + ' MSFullscreenChange.' + namespace, function(e){
if (self.UA.Fullscreen) {
var elm = funcObj.fullElm(),
win = $(window);
resizeTm && clearTimeout(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;
}
}
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 && clearTimeout(resizeTm);
resizeTm = setTimeout(function() {
elm.trigger('resize', {maximize: 'on'});
}, 100);
}
},
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).trigger('resize');
};
/**
* 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 ****************/
// check jquery ui
if (!($.fn.selectable && $.fn.draggable && $.fn.droppable)) {
return alert(this.i18n('errJqui'));
}
// check node
if (!node.length) {
return alert(this.i18n('errNode'));
}
// check connector url
if (!this.options.url) {
return alert(this.i18n('errURL'));
}
$.extend($.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,
'CONTEXTMENU' : 93
});
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) { 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 = $.isFunction(this.options.rawStringDecoder)? this.options.rawStringDecoder : 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));
};
/**
* Alias for this.trigger('error', {error : 'message'})
*
* @param String error message
* @return elFinder
**/
this.error = function() {
var arg = arguments[0],
opts = arguments[1] || null;
return arguments.length == 1 && typeof(arg) == 'function'
? self.bind('error', arg)
: (arg === true? this : self.trigger('error', {error : arg, 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.visible() && self.ui.overlay.is(':hidden') && ! node.children('.elfinder-dialog').find('.'+self.res('class', 'editing')).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 = $.map(e.data.selected || e.data.value|| [], function(hash) {
if (unselects.length || (self.maxTargets && ++cnt > self.maxTargets)) {
unselects.push(hash);
return null;
} else {
return files[hash] ? hash : null;
}
});
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(self.i18n('error')),
resizable : false,
destroyOnClose : true,
buttons : {}
};
opts.buttons[self.i18n(self.i18n('btnClose'))] = function() { $(this).elfinderdialog('close'); };
if (e.data.opts && $.isPlainObject(e.data.opts)) {
$.extend(opts, e.data.opts);
}
self.dialog(''+self.i18n(e.data.error), opts);
})
.bind('tree parents', function(e) {
cache(e.data.tree || []);
})
.bind('tmb', function(e) {
$.each(e.data.images||[], function(hash, tmb) {
if (files[hash]) {
files[hash].tmb = tmb;
}
})
})
.add(function(e) {
cache(e.data.added || []);
})
.change(function(e) {
$.each(e.data.changed||[], function(i, file) {
var hash = file.hash;
if (files[hash]) {
$.each(['locked', 'hidden', 'width', 'height'], function(i, v){
if (files[hash][v] && !file[v]) {
delete files[hash][v];
}
});
}
files[hash] = files[hash] ? $.extend(files[hash], file) : file;
});
})
.remove(function(e) {
var removed = e.data.removed||[],
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]];
}
$.each(files, function(h, f) {
f.phash == hash && rm(h);
});
}
delete files[hash];
}
};
$.each(self.roots, function(k, v) {
roots[v] = k;
});
while (l--) {
rm(removed[l]);
}
})
.bind('searchstart', function(e) {
$.extend(self.searchStatus, e.data);
self.searchStatus.state = 1;
})
.bind('search', function(e) {
self.searchStatus.state = 2;
cache(e.data.files || []);
})
.bind('searchend', function() {
self.searchStatus.state = 0;
self.searchStatus.mixed = false;
})
;
// We listen and emit a sound on delete according to option
if (true === this.options.sound) {
this.bind('rm', function(e) {
var play = beeper.canPlayType && beeper.canPlayType('audio/wav; codecs="1"');
play && play != '' && play != 'no' && $(beeper).html('