/home/arranoyd/magicraft/wp-content/plugins/file-manager/js/elFinder.js
"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 = [],
/**
* Net drivers names
*
* @type Array
**/
netDrivers = [],
/**
* 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('quicklook');
});
});
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); }
}