mirror of
https://github.com/Studio-42/elFinder
synced 2026-06-08 12:37:09 +00:00
2892 lines
71 KiB
JavaScript
2892 lines
71 KiB
JavaScript
"use strict";
|
|
/**
|
|
* @class elFinder - file manager for web
|
|
*
|
|
* @author Dmitry (dio) Levashov
|
|
**/
|
|
window.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 = $('<div/>').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 || 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', 'dragstart', 'dragstop'],
|
|
|
|
/**
|
|
* 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,
|
|
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 = [],
|
|
|
|
/**
|
|
* 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,
|
|
|
|
beeper = $(document.createElement('audio')).hide().appendTo('body')[0],
|
|
|
|
syncInterval,
|
|
|
|
open = function(data) {
|
|
if (data.init) {
|
|
// init - reset cache
|
|
files = {};
|
|
} else {
|
|
// remove only files from prev cwd
|
|
for (var i in files) {
|
|
if (files.hasOwnProperty(i)
|
|
&& files[i].mime != 'directory'
|
|
&& files[i].phash == cwd
|
|
&& $.inArray(i, remember) === -1) {
|
|
delete files[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
cwd = data.cwd.hash;
|
|
cache(data.files);
|
|
if (!files[cwd]) {
|
|
cache([data.cwd]);
|
|
}
|
|
self.lastDir(cwd);
|
|
},
|
|
|
|
/**
|
|
* Store info about files/dirs in "files" object.
|
|
*
|
|
* @param Array files
|
|
* @return void
|
|
**/
|
|
cache = function(data) {
|
|
var l = data.length, f;
|
|
|
|
while (l--) {
|
|
f = data[l];
|
|
if (f.name && f.hash && f.mime) {
|
|
if (!f.phash) {
|
|
var name = 'volume_'+f.name,
|
|
i18 = self.i18n(name);
|
|
|
|
if (name != i18) {
|
|
f.i18 = i18;
|
|
}
|
|
}
|
|
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);
|
|
|
|
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 == 9 && !$(e.target).is(':input')) {
|
|
e.preventDefault();
|
|
}
|
|
|
|
}
|
|
},
|
|
date = new Date(),
|
|
utc,
|
|
i18n
|
|
;
|
|
|
|
|
|
/**
|
|
* 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;
|
|
|
|
/**
|
|
* User os. Required to bind native shortcuts for open/rename
|
|
*
|
|
* @type String
|
|
**/
|
|
this.OS = navigator.userAgent.indexOf('Mac') !== -1 ? 'mac' : navigator.userAgent.indexOf('Win') !== -1 ? 'win' : 'other';
|
|
|
|
/**
|
|
* User browser UA.
|
|
* jQuery.browser: version deprecated: 1.3, removed: 1.9
|
|
*
|
|
* @type Object
|
|
**/
|
|
this.UA = (function(){
|
|
var webkit = !document.uniqueID && !window.opera && !window.sidebar && window.localStorage && typeof window.orientation == "undefined";
|
|
return {
|
|
// Browser IE <= IE 6
|
|
ltIE6:typeof window.addEventListener == "undefined" && typeof document.documentElement.style.maxHeight == "undefined",
|
|
// Browser IE <= IE 7
|
|
ltIE7:typeof window.addEventListener == "undefined" && typeof document.querySelectorAll == "undefined",
|
|
// Browser IE <= IE 8
|
|
ltIE8:typeof window.addEventListener == "undefined" && typeof document.getElementsByClassName == "undefined",
|
|
IE:document.uniqueID,
|
|
Firefox:window.sidebar,
|
|
Opera:window.opera,
|
|
Webkit:webkit,
|
|
Chrome:webkit && window.chrome,
|
|
Safari:webkit && !window.chrome,
|
|
Mobile:typeof window.orientation != "undefined"
|
|
}
|
|
})();
|
|
|
|
/**
|
|
* Configuration options
|
|
*
|
|
* @type Object
|
|
**/
|
|
this.options = $.extend(true, {}, this._options, opts||{});
|
|
|
|
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;
|
|
}
|
|
|
|
$.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 : {};
|
|
|
|
/**
|
|
* ID. Required to create unique cookie name
|
|
*
|
|
* @type String
|
|
**/
|
|
this.id = id;
|
|
|
|
/**
|
|
* 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.options.cssClass;
|
|
|
|
/**
|
|
* Method to store/fetch data
|
|
*
|
|
* @type Function
|
|
**/
|
|
this.storage = (function() {
|
|
try {
|
|
return 'localStorage' in window && window['localStorage'] !== null ? self.localStorage : 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.sortRules = $.extend(true, {}, this._sortRules, this.options.sortsRules);
|
|
|
|
$.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;
|
|
|
|
/**
|
|
* Base draggable options
|
|
*
|
|
* @type Object
|
|
**/
|
|
this.draggable = {
|
|
appendTo : 'body',
|
|
addClasses : true,
|
|
delay : 30,
|
|
distance : 8,
|
|
revert : true,
|
|
refreshPositions : true,
|
|
cursor : 'move',
|
|
cursorAt : {left : 50, top : 47},
|
|
drag : function(e, ui) {
|
|
if (! ui.helper.data('locked')) {
|
|
ui.helper.toggleClass('elfinder-drag-helper-plus', e.shiftKey||e.ctrlKey||e.metaKey);
|
|
}
|
|
},
|
|
start : function(e, ui) {
|
|
var targets = $.map(ui.helper.data('files')||[], function(h) { return h || null ;}),
|
|
cnt, h;
|
|
cnt = targets.length;
|
|
while (cnt--) {
|
|
h = targets[cnt];
|
|
if (files[h].locked) {
|
|
ui.helper.addClass('elfinder-drag-helper-plus').data('locked', true);
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
stop : function() { self.trigger('focus').trigger('dragstop'); },
|
|
helper : function(e, ui) {
|
|
var element = this.id ? $(this) : $(this).parents('[id]:first'),
|
|
helper = $('<div class="elfinder-drag-helper"><span class="elfinder-drag-helper-icon-plus"/></div>'),
|
|
icon = function(mime) { return '<div class="elfinder-cwd-icon '+self.mime2class(mime)+' ui-corner-all"/>'; },
|
|
hashes, l;
|
|
|
|
self.trigger('dragstart', {target : element[0], originalEvent : e});
|
|
|
|
hashes = element.is('.'+self.res('class', 'cwdfile'))
|
|
? self.selected()
|
|
: [self.navId2Hash(element.attr('id'))];
|
|
|
|
helper.append(icon(files[hashes[0]].mime)).data('files', hashes).data('locked', false);
|
|
|
|
if ((l = hashes.length) > 1) {
|
|
helper.append(icon(files[hashes[l-1]].mime) + '<span class="elfinder-drag-num">'+l+'</span>');
|
|
}
|
|
|
|
return helper;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Base droppable options
|
|
*
|
|
* @type Object
|
|
**/
|
|
this.droppable = {
|
|
// greedy : true,
|
|
tolerance : 'pointer',
|
|
accept : '.elfinder-cwd-file-wrapper,.elfinder-navbar-dir,.elfinder-cwd-file',
|
|
hoverClass : this.res('class', 'adroppable'),
|
|
drop : function(e, ui) {
|
|
var dst = $(this),
|
|
targets = $.map(ui.helper.data('files')||[], function(h) { return h || null }),
|
|
result = [],
|
|
c = 'class',
|
|
cnt, hash, i, h;
|
|
|
|
if (dst.is('.'+self.res(c, 'cwd'))) {
|
|
hash = cwd;
|
|
} else if (dst.is('.'+self.res(c, 'cwdfile'))) {
|
|
hash = dst.attr('id');
|
|
} else if (dst.is('.'+self.res(c, 'navdir'))) {
|
|
hash = self.navId2Hash(dst.attr('id'));
|
|
}
|
|
|
|
cnt = targets.length;
|
|
|
|
while (cnt--) {
|
|
h = targets[cnt];
|
|
// ignore drop into itself or in own location
|
|
h != hash && files[h].phash != hash && result.push(h);
|
|
}
|
|
|
|
if (result.length) {
|
|
ui.helper.hide();
|
|
self.clipboard(result, !(e.ctrlKey||e.shiftKey||e.metaKey||ui.helper.data('locked')));
|
|
self.exec('paste', hash);
|
|
self.trigger('drop', {files : targets});
|
|
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Return true if filemanager is active
|
|
*
|
|
* @return Boolean
|
|
**/
|
|
this.enabled = function() {
|
|
return node.is(':visible') && enabled;
|
|
}
|
|
|
|
/**
|
|
* Return true if filemanager is visible
|
|
*
|
|
* @return Boolean
|
|
**/
|
|
this.visible = function() {
|
|
return node.is(':visible');
|
|
}
|
|
|
|
/**
|
|
* Return root dir hash for current working directory
|
|
*
|
|
* @return String
|
|
*/
|
|
this.root = function(hash) {
|
|
var dir = files[hash || cwd], i;
|
|
|
|
while (dir && dir.phash) {
|
|
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
|
|
* @return mixed
|
|
*/
|
|
this.option = function(name) {
|
|
return cwdOptions[name]||'';
|
|
}
|
|
|
|
/**
|
|
* Return file data from current dir or tree by it's hash
|
|
*
|
|
* @param String file hash
|
|
* @return Object
|
|
*/
|
|
this.file = function(hash) {
|
|
return files[hash];
|
|
};
|
|
|
|
/**
|
|
* 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 && (file = files[hash]) && file.hash) {
|
|
path.unshift(i18 && file.i18 ? file.i18 : file.name);
|
|
hash = file.phash;
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
/**
|
|
* Return file path
|
|
*
|
|
* @param Object file
|
|
* @return String
|
|
*/
|
|
this.path = function(hash, i18) {
|
|
return files[hash] && files[hash].path
|
|
? files[hash].path
|
|
: this.path2array(hash, i18).join(cwdOptions.separator);
|
|
}
|
|
|
|
/**
|
|
* Return file url if set
|
|
*
|
|
* @param Object file
|
|
* @return String
|
|
*/
|
|
this.url = function(hash) {
|
|
var file = files[hash];
|
|
|
|
if (!file || !file.read) {
|
|
return '';
|
|
}
|
|
|
|
if (file.url) {
|
|
return file.url;
|
|
}
|
|
|
|
if (cwdOptions.url) {
|
|
return cwdOptions.url + $.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 thumbnail url
|
|
*
|
|
* @param String file hash
|
|
* @return String
|
|
*/
|
|
this.tmb = function(hash) {
|
|
var file = files[hash],
|
|
url = file && file.tmb && file.tmb != 1 ? cwdOptions['tmbUrl'] + file.tmb : '';
|
|
|
|
if (url && (this.UA.Opera || this.UA.IE)) {
|
|
url += '?_=' + new Date().getTime();
|
|
}
|
|
return url;
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* Proccess ajax request.
|
|
* Fired events :
|
|
* @todo
|
|
* @example
|
|
* @todo
|
|
* @return $.Deferred
|
|
*/
|
|
this.request = function(options) {
|
|
var self = this,
|
|
o = this.options,
|
|
dfrd = $.Deferred(),
|
|
// request data
|
|
data = $.extend({}, o.customData, {mimes : o.onlyMimes}, options.data || options),
|
|
// command name
|
|
cmd = data.cmd,
|
|
// call default fail callback (display error dialog) ?
|
|
deffail = !(options.preventDefault || options.preventFail),
|
|
// call default success callback ?
|
|
defdone = !(options.preventDefault || options.preventDone),
|
|
// options for notify dialog
|
|
notify = $.extend({}, options.notify),
|
|
// do not normalize data - return as is
|
|
raw = !!options.raw,
|
|
// sync files on request fail
|
|
syncOnFail = options.syncOnFail,
|
|
// 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
|
|
}, options.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);
|
|
|
|
cmd == 'open' && open($.extend(true, {}, data));
|
|
|
|
// 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);
|
|
|
|
// fire event with command name
|
|
self.trigger(cmd, data);
|
|
|
|
// 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;
|
|
|
|
switch (status) {
|
|
case 'abort':
|
|
error = xhr.quiet ? '' : ['errConnect', 'errAbort'];
|
|
break;
|
|
case 'timeout':
|
|
error = ['errConnect', 'errTimeout'];
|
|
break;
|
|
case 'parsererror':
|
|
error = ['errResponse', 'errDataNotJSON'];
|
|
break;
|
|
default:
|
|
if (xhr.status == 403) {
|
|
error = ['errConnect', 'errAccess'];
|
|
} else if (xhr.status == 404) {
|
|
error = ['errConnect', 'errNotFound'];
|
|
} else {
|
|
error = 'errConnect';
|
|
}
|
|
}
|
|
|
|
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) {
|
|
if (raw) {
|
|
return dfrd.resolve(response);
|
|
}
|
|
|
|
if (!response) {
|
|
return dfrd.reject(['errResponse', 'errDataEmpty'], xhr);
|
|
} else if (!$.isPlainObject(response)) {
|
|
return dfrd.reject(['errResponse', 'errDataNotJSON'], xhr);
|
|
} else if (response.error) {
|
|
return dfrd.reject(response.error, xhr);
|
|
} else if (!self.validResponse(cmd, response)) {
|
|
return dfrd.reject('errResponse', xhr);
|
|
}
|
|
|
|
response = self.normalize(response);
|
|
|
|
if (!self.api) {
|
|
self.api = response.api || 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;
|
|
}
|
|
|
|
dfrd.resolve(response);
|
|
response.debug && self.debug('backend-debug', response.debug);
|
|
},
|
|
xhr, _xhr
|
|
;
|
|
|
|
defdone && dfrd.done(done);
|
|
dfrd.fail(function(error) {
|
|
if (error) {
|
|
deffail ? self.error(error) : self.debug('error', self.i18n(error));
|
|
}
|
|
})
|
|
|
|
if (!cmd) {
|
|
return dfrd.reject('errCmdReq');
|
|
}
|
|
|
|
if (syncOnFail) {
|
|
dfrd.fail(function(error) {
|
|
error && self.sync();
|
|
});
|
|
}
|
|
|
|
if (notify.type && notify.cnt) {
|
|
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 (cmd == 'open') {
|
|
while ((_xhr = queue.pop())) {
|
|
if (_xhr.state() == 'pending') {
|
|
_xhr.quiet = true;
|
|
_xhr.abort();
|
|
}
|
|
}
|
|
}
|
|
|
|
delete options.preventFail
|
|
|
|
xhr = this.transport.send(options).fail(error).done(success);
|
|
|
|
// this.transport.send(options)
|
|
|
|
// add "open" xhr into queue
|
|
if (cmd == 'open') {
|
|
queue.unshift(xhr);
|
|
dfrd.always(function() {
|
|
var ndx = $.inArray(xhr, queue);
|
|
|
|
ndx !== -1 && queue.splice(ndx, 1);
|
|
});
|
|
}
|
|
|
|
return dfrd;
|
|
};
|
|
|
|
/**
|
|
* Compare current files cache with new files and return diff
|
|
*
|
|
* @param Array new files
|
|
* @return Object
|
|
*/
|
|
this.diff = function(incoming) {
|
|
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) {
|
|
!raw[hash] && 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 (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() {
|
|
var self = this,
|
|
dfrd = $.Deferred().done(function() { self.trigger('sync'); }),
|
|
opts1 = {
|
|
data : {cmd : 'open', init : 1, target : cwd, tree : this.ui.tree ? 1 : 0},
|
|
preventDefault : true
|
|
},
|
|
opts2 = {
|
|
data : {cmd : 'tree', target : (cwd == this.root())? cwd : this.file(cwd).phash},
|
|
preventDefault : true
|
|
};
|
|
|
|
$.when(
|
|
this.request(opts1),
|
|
this.request(opts2)
|
|
)
|
|
.fail(function(error) {
|
|
dfrd.reject(error);
|
|
error && self.request({
|
|
data : {cmd : 'open', target : self.lastDir(''), tree : 1, init : 1},
|
|
notify : {type : 'open', cnt : 1, hideCnt : true},
|
|
preventDefault : true
|
|
});
|
|
})
|
|
.done(function(odata, pdata) {
|
|
var diff = self.diff(odata.files.concat(pdata && pdata.tree ? pdata.tree : []));
|
|
|
|
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);
|
|
});
|
|
|
|
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
|
|
*
|
|
* @param String event name
|
|
* @param Function callback
|
|
* @return elFinder
|
|
*/
|
|
this.unbind = function(event, callback) {
|
|
var l = listeners[('' + event).toLowerCase()] || [],
|
|
i = l.indexOf(callback);
|
|
|
|
i > -1 && l.splice(i, 1);
|
|
//delete callback; // need this?
|
|
callback = null
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Fire event - send notification to all event listeners
|
|
*
|
|
* @param String event type
|
|
* @param Object data to send across event
|
|
* @return elFinder
|
|
*/
|
|
this.trigger = function(event, data) {
|
|
var event = event.toLowerCase(),
|
|
handlers = listeners[event] || [], i, j;
|
|
|
|
this.debug('event-'+event, data)
|
|
|
|
if (handlers.length) {
|
|
event = $.Event(event);
|
|
|
|
for (i = 0; i < handlers.length; i++) {
|
|
// to avoid data modifications. remember about "sharing" passing arguments in js :)
|
|
event.data = $.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;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
: $.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
|
|
* @return Boolean
|
|
*/
|
|
this.isCommandEnabled = function(name) {
|
|
return this._commands[name] ? $.inArray(name, cwdOptions.disabled) === -1 : false;
|
|
}
|
|
|
|
/**
|
|
* Exec command and return result;
|
|
*
|
|
* @param String command name
|
|
* @param String|Array usualy files hashes
|
|
* @param String|Array command options
|
|
* @return $.Deferred
|
|
*/
|
|
this.exec = function(cmd, files, opts) {
|
|
return this._commands[cmd] && this.isCommandEnabled(cmd)
|
|
? 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) {
|
|
return $('<div/>').append(content).appendTo(node).elfinderdialog(options);
|
|
}
|
|
|
|
/**
|
|
* Return UI widget or node
|
|
*
|
|
* @param String ui name
|
|
* @return jQuery
|
|
*/
|
|
this.getUI = function(ui) {
|
|
return this.ui[ui] || node;
|
|
}
|
|
|
|
this.command = 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() {
|
|
this.disable().trigger('hide');
|
|
node.hide();
|
|
}
|
|
|
|
/**
|
|
* Destroy this elFinder instance
|
|
*
|
|
* @return void
|
|
**/
|
|
this.destroy = function() {
|
|
if (node && node[0].elfinder) {
|
|
this.trigger('destroy').disable();
|
|
listeners = {};
|
|
shortcuts = {};
|
|
$(document).add(node).unbind('.'+this.namespace);
|
|
self.trigger = function() { }
|
|
node.children().remove();
|
|
node.append(prevContent.contents()).removeClass(this.cssClass).attr('style', prevStyle);
|
|
node[0].elfinder = null;
|
|
if (syncInterval) {
|
|
clearInterval(syncInterval);
|
|
}
|
|
}
|
|
}
|
|
|
|
/************* 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
|
|
});
|
|
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* Alias for this.trigger('error', {error : 'message'})
|
|
*
|
|
* @param String error message
|
|
* @return elFinder
|
|
**/
|
|
this.error = function() {
|
|
var arg = arguments[0];
|
|
return arguments.length == 1 && typeof(arg) == 'function'
|
|
? self.bind('error', arg)
|
|
: self.trigger('error', {error : arg});
|
|
}
|
|
|
|
// create bind/trigger aliases for build-in events
|
|
$.each(['enable', 'disable', 'load', 'open', 'reload', 'select', 'add', 'remove', 'change', 'dblclick', 'getfile', 'lockfiles', 'unlockfiles', 'dragstart', 'dragstop', 'search', 'searchend', 'viewchange'], 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')) {
|
|
enabled = true;
|
|
$('texarea:focus,input:focus,button').blur();
|
|
node.removeClass('elfinder-disabled');
|
|
}
|
|
})
|
|
.disable(function() {
|
|
prevEnabled = enabled;
|
|
enabled = false;
|
|
node.addClass('elfinder-disabled');
|
|
})
|
|
.open(function() {
|
|
selected = [];
|
|
})
|
|
.select(function(e) {
|
|
selected = $.map(e.data.selected || e.data.value|| [], function(hash) { return files[hash] ? hash : null; });
|
|
})
|
|
.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'); };
|
|
|
|
self.dialog('<span class="elfinder-dialog-icon elfinder-dialog-icon-error"/>'+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].width && !file.width) || (files[hash].height && !file.height)) {
|
|
files[hash].width = undefined;
|
|
files[hash].height = undefined;
|
|
}
|
|
files[hash] = files[hash] ? $.extend(files[hash], file) : file;
|
|
});
|
|
})
|
|
.remove(function(e) {
|
|
var removed = e.data.removed||[],
|
|
l = removed.length,
|
|
rm = function(hash) {
|
|
var file = files[hash];
|
|
if (file) {
|
|
if (file.mime == 'directory' && file.dirs) {
|
|
$.each(files, function(h, f) {
|
|
f.phash == hash && rm(h);
|
|
});
|
|
}
|
|
delete files[hash];
|
|
}
|
|
};
|
|
|
|
while (l--) {
|
|
rm(removed[l]);
|
|
}
|
|
|
|
})
|
|
.bind('search', function(e) {
|
|
cache(e.data.files);
|
|
})
|
|
.bind('rm', function(e) {
|
|
var play = beeper.canPlayType && beeper.canPlayType('audio/wav; codecs="1"');
|
|
|
|
play && play != '' && play != 'no' && $(beeper).html('<source src="./sounds/rm.wav" type="audio/wav">')[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);
|
|
|
|
// in getFileCallback set - change default actions on double click/enter/ctrl+enter
|
|
if (typeof(this.options.getFileCallback) == 'function' && this.commands.getfile) {
|
|
this.bind('dblclick', function(e) {
|
|
e.preventDefault();
|
|
self.exec('getfile').fail(function() {
|
|
self.exec('open');
|
|
});
|
|
});
|
|
this.shortcut({
|
|
pattern : 'enter',
|
|
description : this.i18n('cmdgetfile'),
|
|
callback : function() { self.exec('getfile').fail(function() { self.exec(self.OS == 'mac' ? 'rename' : 'open') }) }
|
|
})
|
|
.shortcut({
|
|
pattern : 'ctrl+enter',
|
|
description : this.i18n(this.OS == 'mac' ? 'cmdrename' : 'cmdopen'),
|
|
callback : function() { self.exec(self.OS == 'mac' ? 'rename' : 'open') }
|
|
});
|
|
|
|
}
|
|
|
|
/**
|
|
* Loaded commands
|
|
*
|
|
* @type Object
|
|
**/
|
|
this._commands = {};
|
|
|
|
if (!$.isArray(this.options.commands)) {
|
|
this.options.commands = [];
|
|
}
|
|
// check required commands
|
|
$.each(['open', 'reload', 'back', 'forward', 'up', 'home', 'info', 'quicklook', 'getfile', 'help'], function(i, cmd) {
|
|
$.inArray(cmd, self.options.commands) === -1 && self.options.commands.push(cmd);
|
|
});
|
|
|
|
// load commands
|
|
$.each(this.options.commands, function(i, name) {
|
|
var cmd = self.commands[name];
|
|
if ($.isFunction(cmd) && !self._commands[name]) {
|
|
cmd.prototype = base;
|
|
self._commands[name] = new cmd();
|
|
self._commands[name].setup(name, self.options.commandsOptions[name]||{});
|
|
}
|
|
});
|
|
|
|
// prepare node
|
|
node.addClass(this.cssClass)
|
|
.bind(mousedown, function() {
|
|
!enabled && self.enable();
|
|
});
|
|
|
|
/**
|
|
* UI nodes
|
|
*
|
|
* @type Object
|
|
**/
|
|
this.ui = {
|
|
// container for nav panel and current folder container
|
|
workzone : $('<div/>').appendTo(node).elfinderworkzone(this),
|
|
// container for folders tree / places
|
|
navbar : $('<div/>').appendTo(node).elfindernavbar(this, this.options.uiOptions.navbar || {}),
|
|
// contextmenu
|
|
contextmenu : $('<div/>').appendTo(node).elfindercontextmenu(this),
|
|
// overlay
|
|
overlay : $('<div/>').appendTo(node).elfinderoverlay({
|
|
show : function() { self.disable(); },
|
|
hide : function() { prevEnabled && self.enable(); }
|
|
}),
|
|
// current folder container
|
|
cwd : $('<div/>').appendTo(node).elfindercwd(this, this.options.uiOptions.cwd || {}),
|
|
// notification dialog window
|
|
notify : this.dialog('', {
|
|
cssClass : 'elfinder-dialog-notify',
|
|
position : {top : '12px', right : '12px'},
|
|
resizable : false,
|
|
autoOpen : false,
|
|
title : ' ',
|
|
width : 280
|
|
}),
|
|
statusbar : $('<div class="ui-widget-header ui-helper-clearfix ui-corner-bottom elfinder-statusbar"/>').hide().appendTo(node)
|
|
}
|
|
|
|
// load required ui
|
|
$.each(this.options.ui || [], function(i, ui) {
|
|
var name = 'elfinder'+ui,
|
|
opts = self.options.uiOptions[ui] || {};
|
|
|
|
if (!self.ui[ui] && $.fn[name]) {
|
|
self.ui[ui] = $('<'+(opts.tag || 'div')+'/>').appendTo(node)[name](self, opts);
|
|
}
|
|
});
|
|
|
|
|
|
|
|
// store instance in node
|
|
node[0].elfinder = this;
|
|
|
|
// make node resizable
|
|
this.options.resizable
|
|
&& $.fn.resizable
|
|
&& node.resizable({
|
|
handles : 'se',
|
|
minWidth : 300,
|
|
minHeight : 200
|
|
});
|
|
|
|
if (this.options.width) {
|
|
width = this.options.width;
|
|
}
|
|
|
|
if (this.options.height) {
|
|
height = parseInt(this.options.height);
|
|
}
|
|
|
|
// update size
|
|
self.resize(width, height);
|
|
|
|
// attach events to document
|
|
$(document)
|
|
// disable elfinder on click outside elfinder
|
|
.bind('click.'+this.namespace, function(e) { enabled && !$(e.target).closest(node).length && self.disable(); })
|
|
// exec shortcuts
|
|
.bind(keydown+' '+keypress, execShortcut);
|
|
|
|
// attach events to window
|
|
self.options.useBrowserHistory && $(window)
|
|
.on('popstate', function(ev) {
|
|
var target = ev.originalEvent.state && ev.originalEvent.state.thash;
|
|
target && !$.isEmptyObject(self.files()) && self.request({
|
|
data : {cmd : 'open', target : target, onhistory : 1},
|
|
notify : {type : 'open', cnt : 1, hideCnt : true},
|
|
syncOnFail : true
|
|
});
|
|
});
|
|
|
|
// send initial request and start to pray >_<
|
|
this.trigger('init')
|
|
.request({
|
|
data : {cmd : 'open', target : self.startDir(), init : 1, tree : this.ui.tree ? 1 : 0},
|
|
preventDone : true,
|
|
notify : {type : 'open', cnt : 1, hideCnt : true},
|
|
freeze : true
|
|
})
|
|
.fail(function() {
|
|
self.trigger('fail').disable().lastDir('');
|
|
listeners = {};
|
|
shortcuts = {};
|
|
$(document).add(node).unbind('.'+this.namespace);
|
|
self.trigger = function() { };
|
|
})
|
|
.done(function(data) {
|
|
self.load().debug('api', self.api);
|
|
data = $.extend(true, {}, data);
|
|
open(data);
|
|
self.trigger('open', data);
|
|
});
|
|
|
|
// update ui's size after init
|
|
this.one('load', function() {
|
|
node.trigger('resize');
|
|
if (self.options.sync > 1000) {
|
|
syncInterval = setInterval(function() {
|
|
self.sync();
|
|
}, self.options.sync)
|
|
|
|
}
|
|
|
|
});
|
|
|
|
// self.timeEnd('load');
|
|
|
|
}
|
|
|
|
/**
|
|
* Prototype
|
|
*
|
|
* @type Object
|
|
*/
|
|
elFinder.prototype = {
|
|
|
|
res : function(type, id) {
|
|
return this.resources[type] && this.resources[type][id];
|
|
},
|
|
|
|
/**
|
|
* Internationalization object
|
|
*
|
|
* @type Object
|
|
*/
|
|
i18 : {
|
|
en : {
|
|
translator : '',
|
|
language : 'English',
|
|
direction : 'ltr',
|
|
dateFormat : 'd.m.Y H:i',
|
|
fancyDateFormat : '$1 H:i',
|
|
messages : {}
|
|
},
|
|
months : ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
|
monthsShort : ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
|
|
|
days : ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
|
|
daysShort : ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
|
},
|
|
|
|
/**
|
|
* File mimetype to kind mapping
|
|
*
|
|
* @type Object
|
|
*/
|
|
kinds : {
|
|
'unknown' : 'Unknown',
|
|
'directory' : 'Folder',
|
|
'symlink' : 'Alias',
|
|
'symlink-broken' : 'AliasBroken',
|
|
'application/x-empty' : 'TextPlain',
|
|
'application/postscript' : 'Postscript',
|
|
'application/vnd.ms-office' : 'MsOffice',
|
|
'application/vnd.ms-word' : 'MsWord',
|
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' : 'MsWord',
|
|
'application/vnd.ms-word.document.macroEnabled.12' : 'MsWord',
|
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.template' : 'MsWord',
|
|
'application/vnd.ms-word.template.macroEnabled.12' : 'MsWord',
|
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' : 'MsWord',
|
|
'application/vnd.ms-excel' : 'MsExcel',
|
|
'application/vnd.ms-excel.sheet.macroEnabled.12' : 'MsExcel',
|
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.template' : 'MsExcel',
|
|
'application/vnd.ms-excel.template.macroEnabled.12' : 'MsExcel',
|
|
'application/vnd.ms-excel.sheet.binary.macroEnabled.12' : 'MsExcel',
|
|
'application/vnd.ms-excel.addin.macroEnabled.12' : 'MsExcel',
|
|
'application/vnd.ms-powerpoint' : 'MsPP',
|
|
'application/vnd.openxmlformats-officedocument.presentationml.presentation' : 'MsPP',
|
|
'application/vnd.ms-powerpoint.presentation.macroEnabled.12' : 'MsPP',
|
|
'application/vnd.openxmlformats-officedocument.presentationml.slideshow' : 'MsPP',
|
|
'application/vnd.ms-powerpoint.slideshow.macroEnabled.12' : 'MsPP',
|
|
'application/vnd.openxmlformats-officedocument.presentationml.template' : 'MsPP',
|
|
'application/vnd.ms-powerpoint.template.macroEnabled.12' : 'MsPP',
|
|
'application/vnd.ms-powerpoint.addin.macroEnabled.12' : 'MsPP',
|
|
'application/vnd.openxmlformats-officedocument.presentationml.slide' : 'MsPP',
|
|
'application/vnd.ms-powerpoint.slide.macroEnabled.12' : 'MsPP',
|
|
'application/pdf' : 'PDF',
|
|
'application/xml' : 'XML',
|
|
'application/vnd.oasis.opendocument.text' : 'OO',
|
|
'application/vnd.oasis.opendocument.text-template' : 'OO',
|
|
'application/vnd.oasis.opendocument.text-web' : 'OO',
|
|
'application/vnd.oasis.opendocument.text-master' : 'OO',
|
|
'application/vnd.oasis.opendocument.graphics' : 'OO',
|
|
'application/vnd.oasis.opendocument.graphics-template' : 'OO',
|
|
'application/vnd.oasis.opendocument.presentation' : 'OO',
|
|
'application/vnd.oasis.opendocument.presentation-template' : 'OO',
|
|
'application/vnd.oasis.opendocument.spreadsheet' : 'OO',
|
|
'application/vnd.oasis.opendocument.spreadsheet-template' : 'OO',
|
|
'application/vnd.oasis.opendocument.chart' : 'OO',
|
|
'application/vnd.oasis.opendocument.formula' : 'OO',
|
|
'application/vnd.oasis.opendocument.database' : 'OO',
|
|
'application/vnd.oasis.opendocument.image' : 'OO',
|
|
'application/vnd.openofficeorg.extension' : 'OO',
|
|
'application/x-shockwave-flash' : 'AppFlash',
|
|
'application/flash-video' : 'Flash video',
|
|
'application/x-bittorrent' : 'Torrent',
|
|
'application/javascript' : 'JS',
|
|
'application/rtf' : 'RTF',
|
|
'application/rtfd' : 'RTF',
|
|
'application/x-font-ttf' : 'TTF',
|
|
'application/x-font-otf' : 'OTF',
|
|
'application/x-rpm' : 'RPM',
|
|
'application/x-web-config' : 'TextPlain',
|
|
'application/xhtml+xml' : 'HTML',
|
|
'application/docbook+xml' : 'DOCBOOK',
|
|
'application/x-awk' : 'AWK',
|
|
'application/x-gzip' : 'GZIP',
|
|
'application/x-bzip2' : 'BZIP',
|
|
'application/zip' : 'ZIP',
|
|
'application/x-zip' : 'ZIP',
|
|
'application/x-rar' : 'RAR',
|
|
'application/x-tar' : 'TAR',
|
|
'application/x-7z-compressed' : '7z',
|
|
'application/x-jar' : 'JAR',
|
|
'text/plain' : 'TextPlain',
|
|
'text/x-php' : 'PHP',
|
|
'text/html' : 'HTML',
|
|
'text/javascript' : 'JS',
|
|
'text/css' : 'CSS',
|
|
'text/rtf' : 'RTF',
|
|
'text/rtfd' : 'RTF',
|
|
'text/x-c' : 'C',
|
|
'text/x-csrc' : 'C',
|
|
'text/x-chdr' : 'CHeader',
|
|
'text/x-c++' : 'CPP',
|
|
'text/x-c++src' : 'CPP',
|
|
'text/x-c++hdr' : 'CPPHeader',
|
|
'text/x-shellscript' : 'Shell',
|
|
'application/x-csh' : 'Shell',
|
|
'text/x-python' : 'Python',
|
|
'text/x-java' : 'Java',
|
|
'text/x-java-source' : 'Java',
|
|
'text/x-ruby' : 'Ruby',
|
|
'text/x-perl' : 'Perl',
|
|
'text/x-sql' : 'SQL',
|
|
'text/xml' : 'XML',
|
|
'text/x-comma-separated-values' : 'CSV',
|
|
'image/x-ms-bmp' : 'BMP',
|
|
'image/jpeg' : 'JPEG',
|
|
'image/gif' : 'GIF',
|
|
'image/png' : 'PNG',
|
|
'image/tiff' : 'TIFF',
|
|
'image/x-targa' : 'TGA',
|
|
'image/vnd.adobe.photoshop' : 'PSD',
|
|
'image/xbm' : 'XBITMAP',
|
|
'image/pxm' : 'PXM',
|
|
'audio/mpeg' : 'AudioMPEG',
|
|
'audio/midi' : 'AudioMIDI',
|
|
'audio/ogg' : 'AudioOGG',
|
|
'audio/mp4' : 'AudioMPEG4',
|
|
'audio/x-m4a' : 'AudioMPEG4',
|
|
'audio/wav' : 'AudioWAV',
|
|
'audio/x-mp3-playlist' : 'AudioPlaylist',
|
|
'video/x-dv' : 'VideoDV',
|
|
'video/mp4' : 'VideoMPEG4',
|
|
'video/mpeg' : 'VideoMPEG',
|
|
'video/x-msvideo' : 'VideoAVI',
|
|
'video/quicktime' : 'VideoMOV',
|
|
'video/x-ms-wmv' : 'VideoWM',
|
|
'video/x-flv' : 'VideoFlash',
|
|
'video/x-matroska' : 'VideoMKV',
|
|
'video/ogg' : 'VideoOGG'
|
|
},
|
|
|
|
/**
|
|
* Ajax request data validation rules
|
|
*
|
|
* @type Object
|
|
*/
|
|
rules : {
|
|
defaults : function(data) {
|
|
if (!data
|
|
|| (data.added && !$.isArray(data.added))
|
|
|| (data.removed && !$.isArray(data.removed))
|
|
|| (data.changed && !$.isArray(data.changed))) {
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
open : function(data) { return data && data.cwd && data.files && $.isPlainObject(data.cwd) && $.isArray(data.files); },
|
|
tree : function(data) { return data && data.tree && $.isArray(data.tree); },
|
|
parents : function(data) { return data && data.tree && $.isArray(data.tree); },
|
|
tmb : function(data) { return data && data.images && ($.isPlainObject(data.images) || $.isArray(data.images)); },
|
|
upload : function(data) { return data && ($.isPlainObject(data.added) || $.isArray(data.added));},
|
|
search : function(data) { return data && data.files && $.isArray(data.files)}
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Commands costructors
|
|
*
|
|
* @type Object
|
|
*/
|
|
commands : {},
|
|
|
|
parseUploadData : function(text) {
|
|
var data;
|
|
|
|
if (!$.trim(text)) {
|
|
return {error : ['errResponse', 'errDataEmpty']};
|
|
}
|
|
|
|
try {
|
|
data = $.parseJSON(text);
|
|
} catch (e) {
|
|
return {error : ['errResponse', 'errDataNotJSON']}
|
|
}
|
|
|
|
if (!this.validResponse('upload', data)) {
|
|
return {error : ['errResponse']};
|
|
}
|
|
data = this.normalize(data);
|
|
data.removed = $.map(data.added||[], function(f) { return f.hash; })
|
|
return data;
|
|
|
|
},
|
|
|
|
iframeCnt : 0,
|
|
|
|
uploads : {
|
|
// upload transport using iframe
|
|
iframe : function(data, fm) {
|
|
var self = fm ? fm : this,
|
|
input = data.input,
|
|
dfrd = $.Deferred()
|
|
.fail(function(error) {
|
|
error && self.error(error);
|
|
})
|
|
.done(function(data) {
|
|
data.warning && self.error(data.warning);
|
|
data.removed && self.remove(data);
|
|
data.added && self.add(data);
|
|
data.changed && self.change(data);
|
|
self.trigger('upload', data);
|
|
data.sync && self.sync();
|
|
}),
|
|
name = 'iframe-'+self.namespace+(++self.iframeCnt),
|
|
form = $('<form action="'+self.uploadURL+'" method="post" enctype="multipart/form-data" encoding="multipart/form-data" target="'+name+'" style="display:none"><input type="hidden" name="cmd" value="upload" /></form>'),
|
|
msie = this.UA.IE,
|
|
// clear timeouts, close notification dialog, remove form/iframe
|
|
onload = function() {
|
|
abortto && clearTimeout(abortto);
|
|
notifyto && clearTimeout(notifyto);
|
|
notify && self.notify({type : 'upload', cnt : -cnt});
|
|
|
|
setTimeout(function() {
|
|
msie && $('<iframe src="javascript:false;"/>').appendTo(form);
|
|
form.remove();
|
|
iframe.remove();
|
|
}, 100);
|
|
},
|
|
iframe = $('<iframe src="'+(msie ? 'javascript:false;' : 'about:blank')+'" name="'+name+'" style="position:absolute;left:-1000px;top:-1000px" />')
|
|
.bind('load', function() {
|
|
iframe.unbind('load')
|
|
.bind('load', function() {
|
|
var data = self.parseUploadData(iframe.contents().text());
|
|
|
|
onload();
|
|
data.error ? dfrd.reject(data.error) : dfrd.resolve(data);
|
|
});
|
|
|
|
// notify dialog
|
|
notifyto = setTimeout(function() {
|
|
notify = true;
|
|
self.notify({type : 'upload', cnt : cnt});
|
|
}, self.options.notifyDelay);
|
|
|
|
// emulate abort on timeout
|
|
if (self.options.iframeTimeout > 0) {
|
|
abortto = setTimeout(function() {
|
|
onload();
|
|
dfrd.reject([errors.connect, errors.timeout]);
|
|
}, self.options.iframeTimeout);
|
|
}
|
|
|
|
form.submit();
|
|
}),
|
|
cnt, notify, notifyto, abortto
|
|
|
|
;
|
|
|
|
if (input && $(input).is(':file') && $(input).val()) {
|
|
form.append(input);
|
|
} else {
|
|
return dfrd.reject();
|
|
}
|
|
|
|
cnt = input.files ? input.files.length : 1;
|
|
|
|
form.append('<input type="hidden" name="'+(self.newAPI ? 'target' : 'current')+'" value="'+self.cwd().hash+'"/>')
|
|
.append('<input type="hidden" name="html" value="1"/>')
|
|
.append($(input).attr('name', 'upload[]'));
|
|
|
|
$.each(self.options.onlyMimes||[], function(i, mime) {
|
|
form.append('<input type="hidden" name="mimes[]" value="'+mime+'"/>');
|
|
});
|
|
|
|
$.each(self.options.customData, function(key, val) {
|
|
form.append('<input type="hidden" name="'+key+'" value="'+val+'"/>');
|
|
});
|
|
|
|
form.appendTo('body');
|
|
iframe.appendTo('body');
|
|
|
|
return dfrd;
|
|
},
|
|
// upload transport using XMLHttpRequest
|
|
xhr : function(data, fm) {
|
|
var self = fm ? fm : this,
|
|
dfrd = $.Deferred()
|
|
.fail(function(error) {
|
|
error && self.error(error);
|
|
})
|
|
.done(function(data) {
|
|
data.warning && self.error(data.warning);
|
|
data.removed && self.remove(data);
|
|
data.added && self.add(data);
|
|
data.changed && self.change(data);
|
|
self.trigger('upload', data);
|
|
data.sync && self.sync();
|
|
})
|
|
.always(function() {
|
|
notifyto && clearTimeout(notifyto);
|
|
notify && self.notify({type : 'upload', cnt : -cnt, progress : 100*cnt});
|
|
}),
|
|
xhr = new XMLHttpRequest(),
|
|
formData = new FormData(),
|
|
files = data.input ? data.input.files : data.files,
|
|
cnt = files.length,
|
|
loaded = 5,
|
|
notify = false,
|
|
startNotify = function() {
|
|
return setTimeout(function() {
|
|
notify = true;
|
|
self.notify({type : 'upload', cnt : cnt, progress : loaded*cnt});
|
|
}, self.options.notifyDelay);
|
|
},
|
|
notifyto;
|
|
|
|
if (!cnt) {
|
|
return dfrd.reject();
|
|
}
|
|
|
|
xhr.addEventListener('error', function() {
|
|
dfrd.reject('errConnect');
|
|
}, false);
|
|
|
|
xhr.addEventListener('abort', function() {
|
|
dfrd.reject(['errConnect', 'errAbort']);
|
|
}, false);
|
|
|
|
xhr.addEventListener('load', function() {
|
|
var status = xhr.status, data;
|
|
|
|
if (status > 500) {
|
|
return dfrd.reject('errResponse');
|
|
}
|
|
if (status != 200) {
|
|
return dfrd.reject('errConnect');
|
|
}
|
|
if (xhr.readyState != 4) {
|
|
return dfrd.reject(['errConnect', 'errTimeout']); // am i right?
|
|
}
|
|
if (!xhr.responseText) {
|
|
return dfrd.reject(['errResponse', 'errDataEmpty']);
|
|
}
|
|
|
|
data = self.parseUploadData(xhr.responseText);
|
|
data.error ? dfrd.reject(data.error) : dfrd.resolve(data);
|
|
}, false);
|
|
|
|
xhr.upload.addEventListener('progress', function(e) {
|
|
var prev = loaded, curr;
|
|
|
|
if (e.lengthComputable) {
|
|
|
|
curr = parseInt(e.loaded*100 / e.total);
|
|
|
|
// to avoid strange bug in safari (not in chrome) with drag&drop.
|
|
// bug: macos finder opened in any folder,
|
|
// reset safari cache (option+command+e), reload elfinder page,
|
|
// drop file from finder
|
|
// on first attempt request starts (progress callback called ones) but never ends.
|
|
// any next drop - successfull.
|
|
if (curr > 0 && !notifyto) {
|
|
notifyto = startNotify();
|
|
}
|
|
|
|
if (curr - prev > 4) {
|
|
loaded = curr;
|
|
notify && self.notify({type : 'upload', cnt : 0, progress : (loaded - prev)*cnt});
|
|
}
|
|
}
|
|
}, false);
|
|
|
|
|
|
xhr.open('POST', self.uploadURL, true);
|
|
formData.append('cmd', 'upload');
|
|
formData.append(self.newAPI ? 'target' : 'current', self.cwd().hash);
|
|
$.each(self.options.customData, function(key, val) {
|
|
formData.append(key, val);
|
|
});
|
|
$.each(self.options.onlyMimes, function(i, mime) {
|
|
formData.append('mimes['+i+']', mime);
|
|
});
|
|
|
|
$.each(files, function(i, file) {
|
|
formData.append('upload[]', file);
|
|
});
|
|
|
|
xhr.onreadystatechange = function() {
|
|
if (xhr.readyState == 4 && xhr.status == 0) {
|
|
// ff bug while send zero sized file
|
|
// for safari - send directory
|
|
dfrd.reject(['errConnect', 'errAbort']);
|
|
}
|
|
}
|
|
|
|
xhr.send(formData);
|
|
|
|
if (!this.UA.Safari || !data.files) {
|
|
notifyto = startNotify();
|
|
}
|
|
|
|
return dfrd;
|
|
}
|
|
},
|
|
|
|
|
|
/**
|
|
* Bind callback to event(s) The callback is executed at most once per event.
|
|
* To bind to multiply events at once, separate events names by space
|
|
*
|
|
* @param String event name
|
|
* @param Function callback
|
|
* @return elFinder
|
|
*/
|
|
one : function(event, callback) {
|
|
var self = this,
|
|
h = $.proxy(callback, function(event) {
|
|
setTimeout(function() {self.unbind(event.type, h);}, 3);
|
|
return callback.apply(this, arguments);
|
|
});
|
|
return this.bind(event, h);
|
|
},
|
|
|
|
/**
|
|
* Set/get data into/from localStorage
|
|
*
|
|
* @param String key
|
|
* @param String|void value
|
|
* @return String
|
|
*/
|
|
localStorage : function(key, val) {
|
|
var s = window.localStorage;
|
|
|
|
key = 'elfinder-'+key+this.id;
|
|
|
|
if (val === null) {
|
|
console.log('remove', key)
|
|
return s.removeItem(key);
|
|
}
|
|
|
|
if (val !== void(0)) {
|
|
try {
|
|
s.setItem(key, val);
|
|
} catch (e) {
|
|
s.clear();
|
|
s.setItem(key, val);
|
|
}
|
|
}
|
|
|
|
return s.getItem(key);
|
|
},
|
|
|
|
/**
|
|
* Get/set cookie
|
|
*
|
|
* @param String cookie name
|
|
* @param String|void cookie value
|
|
* @return String
|
|
*/
|
|
cookie : function(name, value) {
|
|
var d, o, c, i;
|
|
|
|
name = 'elfinder-'+name+this.id;
|
|
|
|
if (value === void(0)) {
|
|
if (document.cookie && document.cookie != '') {
|
|
c = document.cookie.split(';');
|
|
name += '=';
|
|
for (i=0; i<c.length; i++) {
|
|
c[i] = $.trim(c[i]);
|
|
if (c[i].substring(0, name.length) == name) {
|
|
return decodeURIComponent(c[i].substring(name.length));
|
|
}
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
|
|
o = $.extend({}, this.options.cookie);
|
|
if (value === null) {
|
|
value = '';
|
|
o.expires = -1;
|
|
}
|
|
if (typeof(o.expires) == 'number') {
|
|
d = new Date();
|
|
d.setTime(d.getTime()+(o.expires * 86400000));
|
|
o.expires = d;
|
|
}
|
|
document.cookie = name+'='+encodeURIComponent(value)+'; expires='+o.expires.toUTCString()+(o.path ? '; path='+o.path : '')+(o.domain ? '; domain='+o.domain : '')+(o.secure ? '; secure' : '');
|
|
return value;
|
|
},
|
|
|
|
/**
|
|
* Get start directory (by location.hash or last opened directory)
|
|
*
|
|
* @return String
|
|
*/
|
|
startDir : function() {
|
|
var locHash = window.location.hash;
|
|
if (locHash && locHash.match(/^#elf_/)) {
|
|
return locHash.replace(/^#elf_/, '');
|
|
} else {
|
|
return this.lastDir();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Get/set last opened directory
|
|
*
|
|
* @param String|undefined dir hash
|
|
* @return String
|
|
*/
|
|
lastDir : function(hash) {
|
|
return this.options.rememberLastDir ? this.storage('lastdir', hash) : '';
|
|
},
|
|
|
|
/**
|
|
* Node for escape html entities in texts
|
|
*
|
|
* @type jQuery
|
|
*/
|
|
_node : $('<span/>'),
|
|
|
|
/**
|
|
* Replace not html-safe symbols to html entities
|
|
*
|
|
* @param String text to escape
|
|
* @return String
|
|
*/
|
|
escape : function(name) {
|
|
return this._node.text(name).html();
|
|
},
|
|
|
|
/**
|
|
* Cleanup ajax data.
|
|
* For old api convert data into new api format
|
|
*
|
|
* @param String command name
|
|
* @param Object data from backend
|
|
* @return Object
|
|
*/
|
|
normalize : function(data) {
|
|
var filter = function(file) {
|
|
|
|
if (file && file.hash && file.name && file.mime) {
|
|
if (file.mime == 'application/x-empty') {
|
|
file.mime = 'text/plain';
|
|
}
|
|
return file;
|
|
}
|
|
return null;
|
|
return file && file.hash && file.name && file.mime ? file : null;
|
|
};
|
|
|
|
|
|
if (data.files) {
|
|
data.files = $.map(data.files, filter);
|
|
}
|
|
if (data.tree) {
|
|
data.tree = $.map(data.tree, filter);
|
|
}
|
|
if (data.added) {
|
|
data.added = $.map(data.added, filter);
|
|
}
|
|
if (data.changed) {
|
|
data.changed = $.map(data.changed, filter);
|
|
}
|
|
if (data.api) {
|
|
data.init = true;
|
|
}
|
|
return data;
|
|
},
|
|
|
|
/**
|
|
* Update sort options
|
|
*
|
|
* @param {String} sort type
|
|
* @param {String} sort order
|
|
* @param {Boolean} show folder first
|
|
*/
|
|
setSort : function(type, order, stickFolders) {
|
|
this.storage('sortType', (this.sortType = this.sortRules[type] ? type : 'name'));
|
|
this.storage('sortOrder', (this.sortOrder = /asc|desc/.test(order) ? order : 'asc'));
|
|
this.storage('sortStickFolders', (this.sortStickFolders = !!stickFolders) ? 1 : '');
|
|
this.trigger('sortchange');
|
|
},
|
|
|
|
_sortRules : {
|
|
name : function(file1, file2) { return file1.name.toLowerCase().localeCompare(file2.name.toLowerCase()); },
|
|
size : function(file1, file2) {
|
|
var size1 = parseInt(file1.size) || 0,
|
|
size2 = parseInt(file2.size) || 0;
|
|
|
|
return size1 == size2 ? 0 : size1 > size2 ? 1 : -1;
|
|
return (parseInt(file1.size) || 0) > (parseInt(file2.size) || 0) ? 1 : -1; },
|
|
kind : function(file1, file2) { return file1.mime.localeCompare(file2.mime); },
|
|
date : function(file1, file2) {
|
|
var date1 = file1.ts || file1.date,
|
|
date2 = file2.ts || file2.date;
|
|
|
|
return date1 == date2 ? 0 : date1 > date2 ? 1 : -1
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Compare files based on elFinder.sort
|
|
*
|
|
* @param Object file
|
|
* @param Object file
|
|
* @return Number
|
|
*/
|
|
compare : function(file1, file2) {
|
|
var self = this,
|
|
type = self.sortType,
|
|
asc = self.sortOrder == 'asc',
|
|
stick = self.sortStickFolders,
|
|
rules = self.sortRules,
|
|
sort = rules[type],
|
|
d1 = file1.mime == 'directory',
|
|
d2 = file2.mime == 'directory',
|
|
res;
|
|
|
|
if (stick) {
|
|
if (d1 && !d2) {
|
|
return -1;
|
|
} else if (!d1 && d2) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
res = asc ? sort(file1, file2) : sort(file2, file1);
|
|
|
|
return type != 'name' && res == 0
|
|
? res = asc ? rules.name(file1, file2) : rules.name(file2, file1)
|
|
: res;
|
|
},
|
|
|
|
/**
|
|
* Sort files based on config
|
|
*
|
|
* @param Array files
|
|
* @return Array
|
|
*/
|
|
sortFiles : function(files) {
|
|
return files.sort(this.compare);
|
|
},
|
|
|
|
/**
|
|
* Open notification dialog
|
|
* and append/update message for required notification type.
|
|
*
|
|
* @param Object options
|
|
* @example
|
|
* this.notify({
|
|
* type : 'copy',
|
|
* msg : 'Copy files', // not required for known types @see this.notifyType
|
|
* cnt : 3,
|
|
* hideCnt : false, // true for not show count
|
|
* progress : 10 // progress bar percents (use cnt : 0 to update progress bar)
|
|
* })
|
|
* @return elFinder
|
|
*/
|
|
notify : function(opts) {
|
|
var type = opts.type,
|
|
msg = this.messages['ntf'+type] ? this.i18n('ntf'+type) : this.i18n('ntfsmth'),
|
|
ndialog = this.ui.notify,
|
|
notify = ndialog.children('.elfinder-notify-'+type),
|
|
ntpl = '<div class="elfinder-notify elfinder-notify-{type}"><span class="elfinder-dialog-icon elfinder-dialog-icon-{type}"/><span class="elfinder-notify-msg">{msg}</span> <span class="elfinder-notify-cnt"/><div class="elfinder-notify-progressbar"><div class="elfinder-notify-progress"/></div></div>',
|
|
delta = opts.cnt,
|
|
progress = opts.progress >= 0 && opts.progress <= 100 ? opts.progress : 0,
|
|
cnt, total, prc;
|
|
|
|
if (!type) {
|
|
return this;
|
|
}
|
|
|
|
if (!notify.length) {
|
|
notify = $(ntpl.replace(/\{type\}/g, type).replace(/\{msg\}/g, msg))
|
|
.appendTo(ndialog)
|
|
.data('cnt', 0);
|
|
|
|
if (progress) {
|
|
notify.data({progress : 0, total : 0});
|
|
}
|
|
}
|
|
|
|
cnt = delta + parseInt(notify.data('cnt'));
|
|
|
|
if (cnt > 0) {
|
|
!opts.hideCnt && notify.children('.elfinder-notify-cnt').text('('+cnt+')');
|
|
ndialog.is(':hidden') && ndialog.elfinderdialog('open');
|
|
notify.data('cnt', cnt);
|
|
|
|
if (progress < 100
|
|
&& (total = notify.data('total')) >= 0
|
|
&& (prc = notify.data('progress')) >= 0) {
|
|
|
|
total = delta + parseInt(notify.data('total'));
|
|
prc = progress + prc;
|
|
progress = parseInt(prc/total);
|
|
notify.data({progress : prc, total : total});
|
|
|
|
ndialog.find('.elfinder-notify-progress')
|
|
.animate({
|
|
width : (progress < 100 ? progress : 100)+'%'
|
|
}, 20);
|
|
}
|
|
|
|
} else {
|
|
notify.remove();
|
|
!ndialog.children().length && ndialog.elfinderdialog('close');
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Open confirmation dialog
|
|
*
|
|
* @param Object options
|
|
* @example
|
|
* this.confirm({
|
|
* title : 'Remove files',
|
|
* text : 'Here is question text',
|
|
* accept : { // accept callback - required
|
|
* label : 'Continue',
|
|
* callback : function(applyToAll) { fm.log('Ok') }
|
|
* },
|
|
* cancel : { // cancel callback - required
|
|
* label : 'Cancel',
|
|
* callback : function() { fm.log('Cancel')}
|
|
* },
|
|
* reject : { // reject callback - optionally
|
|
* label : 'No',
|
|
* callback : function(applyToAll) { fm.log('No')}
|
|
* },
|
|
* all : true // display checkbox "Apply to all"
|
|
* })
|
|
* @return elFinder
|
|
*/
|
|
confirm : function(opts) {
|
|
var complete = false,
|
|
options = {
|
|
cssClass : 'elfinder-dialog-confirm',
|
|
modal : true,
|
|
resizable : false,
|
|
title : this.i18n(opts.title || 'confirmReq'),
|
|
buttons : {},
|
|
close : function() {
|
|
!complete && opts.cancel.callback();
|
|
$(this).elfinderdialog('destroy');
|
|
}
|
|
},
|
|
apply = this.i18n('apllyAll'),
|
|
label, checkbox;
|
|
|
|
|
|
if (opts.reject) {
|
|
options.buttons[this.i18n(opts.reject.label)] = function() {
|
|
opts.reject.callback(!!(checkbox && checkbox.prop('checked')))
|
|
complete = true;
|
|
$(this).elfinderdialog('close')
|
|
};
|
|
}
|
|
|
|
options.buttons[this.i18n(opts.accept.label)] = function() {
|
|
opts.accept.callback(!!(checkbox && checkbox.prop('checked')))
|
|
complete = true;
|
|
$(this).elfinderdialog('close')
|
|
};
|
|
|
|
options.buttons[this.i18n(opts.cancel.label)] = function() {
|
|
$(this).elfinderdialog('close')
|
|
};
|
|
|
|
if (opts.all) {
|
|
if (opts.reject) {
|
|
options.width = 370;
|
|
}
|
|
options.create = function() {
|
|
checkbox = $('<input type="checkbox" />');
|
|
$(this).next().children().before($('<label>'+apply+'</label>').prepend(checkbox));
|
|
}
|
|
|
|
options.open = function() {
|
|
var pane = $(this).next(),
|
|
width = parseInt(pane.children(':first').outerWidth() + pane.children(':last').outerWidth());
|
|
|
|
if (width > parseInt(pane.width())) {
|
|
$(this).closest('.elfinder-dialog').width(width+30);
|
|
}
|
|
}
|
|
}
|
|
|
|
return this.dialog('<span class="elfinder-dialog-icon elfinder-dialog-icon-confirm"/>' + this.i18n(opts.text), options);
|
|
},
|
|
|
|
/**
|
|
* Create unique file name in required dir
|
|
*
|
|
* @param String file name
|
|
* @param String parent dir hash
|
|
* @return String
|
|
*/
|
|
uniqueName : function(prefix, phash) {
|
|
var i = 0, ext = '', p, name;
|
|
|
|
prefix = this.i18n(prefix);
|
|
phash = phash || this.cwd().hash;
|
|
|
|
if ((p = prefix.indexOf('.txt')) != -1) {
|
|
ext = '.txt';
|
|
prefix = prefix.substr(0, p);
|
|
}
|
|
|
|
name = prefix+ext;
|
|
|
|
if (!this.fileByName(name, phash)) {
|
|
return name;
|
|
}
|
|
while (i < 10000) {
|
|
name = prefix + ' ' + (++i) + ext;
|
|
if (!this.fileByName(name, phash)) {
|
|
return name;
|
|
}
|
|
}
|
|
return prefix + Math.random() + ext;
|
|
},
|
|
|
|
/**
|
|
* Return message translated onto current language
|
|
*
|
|
* @param String|Array message[s]
|
|
* @return String
|
|
**/
|
|
i18n : function() {
|
|
var self = this,
|
|
messages = this.messages,
|
|
input = [],
|
|
ignore = [],
|
|
message = function(m) {
|
|
var file;
|
|
if (m.indexOf('#') === 0) {
|
|
if ((file = self.file(m.substr(1)))) {
|
|
return file.name;
|
|
}
|
|
}
|
|
return m;
|
|
},
|
|
i, j, m;
|
|
|
|
for (i = 0; i< arguments.length; i++) {
|
|
m = arguments[i];
|
|
|
|
if (typeof m == 'string') {
|
|
input.push(message(m));
|
|
} else if ($.isArray(m)) {
|
|
for (j = 0; j < m.length; j++) {
|
|
if (typeof m[j] == 'string') {
|
|
input.push(message(m[j]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < input.length; i++) {
|
|
// dont translate placeholders
|
|
if ($.inArray(i, ignore) !== -1) {
|
|
continue;
|
|
}
|
|
m = input[i];
|
|
// translate message
|
|
m = messages[m] || m;
|
|
// replace placeholders in message
|
|
m = m.replace(/\$(\d+)/g, function(match, placeholder) {
|
|
placeholder = i + parseInt(placeholder);
|
|
if (placeholder > 0 && input[placeholder]) {
|
|
ignore.push(placeholder)
|
|
}
|
|
return input[placeholder] || '';
|
|
});
|
|
|
|
input[i] = m;
|
|
}
|
|
|
|
return $.map(input, function(m, i) { return $.inArray(i, ignore) === -1 ? m : null; }).join('<br>');
|
|
},
|
|
|
|
/**
|
|
* Convert mimetype into css classes
|
|
*
|
|
* @param String file mimetype
|
|
* @return String
|
|
*/
|
|
mime2class : function(mime) {
|
|
var prefix = 'elfinder-cwd-icon-';
|
|
|
|
mime = mime.split('/');
|
|
|
|
return prefix+mime[0]+(mime[0] != 'image' && mime[1] ? ' '+prefix+mime[1].replace(/(\.|\+)/g, '-') : '');
|
|
},
|
|
|
|
/**
|
|
* Return localized kind of file
|
|
*
|
|
* @param Object|String file or file mimetype
|
|
* @return String
|
|
*/
|
|
mime2kind : function(f) {
|
|
var mime = typeof(f) == 'object' ? f.mime : f, kind;
|
|
|
|
if (f.alias) {
|
|
kind = 'Alias';
|
|
} else if (this.kinds[mime]) {
|
|
kind = this.kinds[mime];
|
|
} else {
|
|
if (mime.indexOf('text') === 0) {
|
|
kind = 'Text';
|
|
} else if (mime.indexOf('image') === 0) {
|
|
kind = 'Image';
|
|
} else if (mime.indexOf('audio') === 0) {
|
|
kind = 'Audio';
|
|
} else if (mime.indexOf('video') === 0) {
|
|
kind = 'Video';
|
|
} else if (mime.indexOf('application') === 0) {
|
|
kind = 'App';
|
|
} else {
|
|
kind = mime;
|
|
}
|
|
}
|
|
|
|
return this.messages['kind'+kind] ? this.i18n('kind'+kind) : mime;
|
|
|
|
var mime = typeof(f) == 'object' ? f.mime : f,
|
|
kind = this.kinds[mime]||'unknown';
|
|
|
|
if (f.alias) {
|
|
kind = 'Alias';
|
|
} else if (kind == 'unknown') {
|
|
if (mime.indexOf('text') === 0) {
|
|
kind = 'Text';
|
|
} else if (mime.indexOf('image') === 0) {
|
|
kind = 'Image';
|
|
} else if (mime.indexOf('audio') === 0) {
|
|
kind = 'Audio';
|
|
} else if (mime.indexOf('video') === 0) {
|
|
kind = 'Video';
|
|
} else if (mime.indexOf('application') === 0) {
|
|
kind = 'Application';
|
|
}
|
|
}
|
|
|
|
return this.i18n(kind);
|
|
},
|
|
|
|
/**
|
|
* Return localized date
|
|
*
|
|
* @param Object file object
|
|
* @return String
|
|
*/
|
|
formatDate : function(file, ts) {
|
|
var self = this,
|
|
ts = ts || file.ts,
|
|
i18 = self.i18,
|
|
date, format, output, d, dw, m, y, h, g, i, s;
|
|
|
|
if (self.options.clientFormatDate && ts > 0) {
|
|
|
|
date = new Date(ts*1000);
|
|
|
|
h = date[self.getHours]();
|
|
g = h > 12 ? h - 12 : h;
|
|
i = date[self.getMinutes]();
|
|
s = date[self.getSeconds]();
|
|
d = date[self.getDate]();
|
|
dw = date[self.getDay]();
|
|
m = date[self.getMonth]() + 1;
|
|
y = date[self.getFullYear]();
|
|
|
|
format = ts >= this.yesterday
|
|
? this.fancyFormat
|
|
: this.dateFormat;
|
|
|
|
output = format.replace(/[a-z]/gi, function(val) {
|
|
switch (val) {
|
|
case 'd': return d > 9 ? d : '0'+d;
|
|
case 'j': return d;
|
|
case 'D': return self.i18n(i18.daysShort[dw]);
|
|
case 'l': return self.i18n(i18.days[dw]);
|
|
case 'm': return m > 9 ? m : '0'+m;
|
|
case 'n': return m;
|
|
case 'M': return self.i18n(i18.monthsShort[m-1]);
|
|
case 'F': return self.i18n(i18.months[m-1]);
|
|
case 'Y': return y;
|
|
case 'y': return (''+y).substr(2);
|
|
case 'H': return h > 9 ? h : '0'+h;
|
|
case 'G': return h;
|
|
case 'g': return g;
|
|
case 'h': return g > 9 ? g : '0'+g;
|
|
case 'a': return h > 12 ? 'pm' : 'am';
|
|
case 'A': return h > 12 ? 'PM' : 'AM';
|
|
case 'i': return i > 9 ? i : '0'+i;
|
|
case 's': return s > 9 ? s : '0'+s;
|
|
}
|
|
return val;
|
|
});
|
|
|
|
return ts >= this.yesterday
|
|
? output.replace('$1', this.i18n(ts >= this.today ? 'Today' : 'Yesterday'))
|
|
: output;
|
|
} else if (file.date) {
|
|
return file.date.replace(/([a-z]+)\s/i, function(a1, a2) { return self.i18n(a2)+' '; });
|
|
}
|
|
|
|
return self.i18n('dateUnknown');
|
|
},
|
|
|
|
/**
|
|
* Return css class marks file permissions
|
|
*
|
|
* @param Object file
|
|
* @return String
|
|
*/
|
|
perms2class : function(o) {
|
|
var c = '';
|
|
|
|
if (!o.read && !o.write) {
|
|
c = 'elfinder-na';
|
|
} else if (!o.read) {
|
|
c = 'elfinder-wo';
|
|
} else if (!o.write) {
|
|
c = 'elfinder-ro';
|
|
}
|
|
return c;
|
|
},
|
|
|
|
/**
|
|
* Return localized string with file permissions
|
|
*
|
|
* @param Object file
|
|
* @return String
|
|
*/
|
|
formatPermissions : function(f) {
|
|
var p = [];
|
|
|
|
f.read && p.push(this.i18n('read'));
|
|
f.write && p.push(this.i18n('write'));
|
|
|
|
return p.length ? p.join(' '+this.i18n('and')+' ') : this.i18n('noaccess');
|
|
},
|
|
|
|
/**
|
|
* Return formated file size
|
|
*
|
|
* @param Number file size
|
|
* @return String
|
|
*/
|
|
formatSize : function(s) {
|
|
var n = 1, u = 'b';
|
|
|
|
if (s == 'unknown') {
|
|
return this.i18n('unknown');
|
|
}
|
|
|
|
if (s > 1073741824) {
|
|
n = 1073741824;
|
|
u = 'GB';
|
|
} else if (s > 1048576) {
|
|
n = 1048576;
|
|
u = 'MB';
|
|
} else if (s > 1024) {
|
|
n = 1024;
|
|
u = 'KB';
|
|
}
|
|
s = s/n;
|
|
return (s > 0 ? n >= 1048576 ? s.toFixed(2) : Math.round(s) : 0) +' '+u;
|
|
},
|
|
|
|
|
|
navHash2Id : function(hash) {
|
|
return 'nav-'+hash;
|
|
},
|
|
|
|
navId2Hash : function(id) {
|
|
return typeof(id) == 'string' ? id.substr(4) : false;
|
|
},
|
|
|
|
log : function(m) { window.console && window.console.log && window.console.log(m); return this; },
|
|
|
|
debug : function(type, m) {
|
|
var d = this.options.debug;
|
|
|
|
if (d == 'all' || d === true || ($.isArray(d) && $.inArray(type, d) != -1)) {
|
|
window.console && window.console.log && window.console.log('elfinder debug: ['+type+'] ['+this.id+']', m);
|
|
}
|
|
return this;
|
|
},
|
|
time : function(l) { window.console && window.console.time && window.console.time(l); },
|
|
timeEnd : function(l) { window.console && window.console.timeEnd && window.console.timeEnd(l); }
|
|
|
|
|
|
}
|