File: //home/retile.ru/public_html/admin/view/javascript/d_elfinder/ui/cwd.js
"use strict";
/**
* elFinder current working directory ui.
*
* @author Dmitry (dio) Levashov
**/
$.fn.elfindercwd = function(fm) {
this.not('.elfinder-cwd').each(function() {
// fm.time('cwdLoad');
var
list = fm.storage('view') == 'list',
undef = 'undefined',
/**
* Select event full name
*
* @type String
**/
evtSelect = 'select.'+fm.namespace,
/**
* Unselect event full name
*
* @type String
**/
evtUnselect = 'unselect.'+fm.namespace,
/**
* Disable event full name
*
* @type String
**/
evtDisable = 'disable.'+fm.namespace,
/**
* Disable event full name
*
* @type String
**/
evtEnable = 'enable.'+fm.namespace,
c = 'class',
/**
* File css class
*
* @type String
**/
clFile = fm.res(c, 'cwdfile'),
/**
* Selected css class
*
* @type String
**/
fileSelector = '.'+clFile,
/**
* Selected css class
*
* @type String
**/
clSelected = 'ui-selected',
/**
* Disabled css class
*
* @type String
**/
clDisabled = fm.res(c, 'disabled'),
/**
* Draggable css class
*
* @type String
**/
clDraggable = fm.res(c, 'draggable'),
/**
* Droppable css class
*
* @type String
**/
clDroppable = fm.res(c, 'droppable'),
/**
* Hover css class
*
* @type String
**/
clHover = fm.res(c, 'hover'),
/**
* Hover css class
*
* @type String
**/
clDropActive = fm.res(c, 'adroppable'),
/**
* Css class for temporary nodes (for mkdir/mkfile) commands
*
* @type String
**/
clTmp = clFile+'-tmp',
/**
* Number of thumbnails to load in one request (new api only)
*
* @type Number
**/
tmbNum = fm.options.loadTmbs > 0 ? fm.options.loadTmbs : 5,
/**
* Current search query.
*
* @type String
*/
query = '',
lastSearch = [],
/**
* File templates
*
* @type Object
**/
templates = {
icon : '<div id="{hash}" class="'+clFile+' {permsclass} {dirclass} ui-corner-all"><div class="elfinder-cwd-file-wrapper ui-corner-all"><div class="elfinder-cwd-icon {mime} ui-corner-all" unselectable="on" {style}/>{marker}</div><div class="elfinder-cwd-filename" title="{name}">{name}</div></div>',
row : '<tr id="{hash}" class="'+clFile+' {permsclass} {dirclass}"><td><div class="elfinder-cwd-file-wrapper"><span class="elfinder-cwd-icon {mime}"/>{marker}<span class="elfinder-cwd-filename">{name}</span></div></td><td>{perms}</td><td>{date}</td><td>{size}</td><td>{kind}</td></tr>'
},
permsTpl = fm.res('tpl', 'perms'),
symlinkTpl = fm.res('tpl', 'symlink'),
/**
* Template placeholders replacement rules
*
* @type Object
**/
replacement = {
permsclass : function(f) {
return fm.perms2class(f);
},
perms : function(f) {
return fm.formatPermissions(f);
},
dirclass : function(f) {
return f.mime == 'directory' ? 'directory' : '';
},
mime : function(f) {
return fm.mime2class(f.mime);
},
size : function(f) {
return fm.formatSize(f.size);
},
date : function(f) {
return fm.formatDate(f);
},
kind : function(f) {
return fm.mime2kind(f);
},
marker : function(f) {
return (f.alias || f.mime == 'symlink-broken' ? symlinkTpl : '')+(!f.read || !f.write ? permsTpl : '');
}
},
/**
* Return file html
*
* @param Object file info
* @return String
**/
itemhtml = function(f) {
f.name = fm.escape(f.name);
return templates[list ? 'row' : 'icon']
.replace(/\{([a-z]+)\}/g, function(s, e) {
return replacement[e] ? replacement[e](f) : (f[e] ? f[e] : '');
});
},
/**
* Flag. Required for msie to avoid unselect files on dragstart
*
* @type Boolean
**/
selectLock = false,
/**
* Move selection to prev/next file
*
* @param String move direction
* @param Boolean append to current selection
* @return void
* @rise select
*/
select = function(keyCode, append) {
var code = $.ui.keyCode,
prev = keyCode == code.LEFT || keyCode == code.UP,
sel = cwd.find('[id].'+clSelected),
selector = prev ? 'first' : 'last',
s, n, sib, top, left;
function sibling(n, direction) {
return n[direction+'All']('[id]:not(.'+clDisabled+'):first');
}
if (sel.length) {
s = sel.filter(prev ? ':first' : ':last');
sib = sibling(s, prev ? 'prev' : 'next');
if (!sib.length) {
// there is no sibling on required side - do not move selection
n = s;
} else if (list || keyCode == code.LEFT || keyCode == code.RIGHT) {
// find real prevoius file
n = sib;
} else {
// find up/down side file in icons view
top = s.position().top;
left = s.position().left;
n = s;
if (prev) {
do {
n = n.prev('[id]');
} while (n.length && !(n.position().top < top && n.position().left <= left));
if (n.is('.'+clDisabled)) {
n = sibling(n, 'next');
}
} else {
do {
n = n.next('[id]');
} while (n.length && !(n.position().top > top && n.position().left >= left));
if (n.is('.'+clDisabled)) {
n = sibling(n, 'prev');
}
// there is row before last one - select last file
if (!n.length) {
sib = cwd.find('[id]:not(.'+clDisabled+'):last');
if (sib.position().top > top) {
n = sib;
}
}
}
}
} else {
// there are no selected file - select first/last one
n = cwd.find('[id]:not(.'+clDisabled+'):'+(prev ? 'last' : 'first'))
}
if (n && n.length) {
if (append) {
// append new files to selected
n = s.add(s[prev ? 'prevUntil' : 'nextUntil']('#'+n.attr('id'))).add(n);
} else {
// unselect selected files
sel.trigger(evtUnselect);
}
// select file(s)
n.trigger(evtSelect);
// set its visible
scrollToView(n.filter(prev ? ':first' : ':last'));
// update cache/view
trigger();
}
},
selectFile = function(hash) {
cwd.find('#'+hash).trigger(evtSelect);
},
/**
* Unselect all files
*
* @return void
*/
unselectAll = function() {
cwd.find('[id].'+clSelected).trigger(evtUnselect);
},
/**
* Return selected files hashes list
*
* @return Array
*/
selected = function() {
return $.map(cwd.find('[id].'+clSelected), function(n) {
n = $(n);
return n.is('.'+clDisabled) ? null : $(n).attr('id');
});
},
/**
* Fire elfinder "select" event and pass selected files to it
*
* @return void
*/
trigger = function() {
fm.trigger('select', {selected : selected()});
},
/**
* Scroll file to set it visible
*
* @param DOMElement file/dir node
* @return void
*/
scrollToView = function(o) {
var ftop = o.position().top,
fheight = o.outerHeight(true),
wtop = wrapper.scrollTop(),
wheight = wrapper.innerHeight();
if (ftop + fheight > wtop + wheight) {
wrapper.scrollTop(parseInt(ftop + fheight - wheight));
} else if (ftop < wtop) {
wrapper.scrollTop(ftop);
}
},
/**
* Files we get from server but not show yet
*
* @type Array
**/
buffer = [],
/**
* Return index of elements with required hash in buffer
*
* @param String file hash
* @return Number
*/
index = function(hash) {
var l = buffer.length;
while (l--) {
if (buffer[l].hash == hash) {
return l;
}
}
return -1;
},
/**
* Scroll event name
*
* @type String
**/
scrollEvent = 'scroll.'+fm.namespace,
/**
* Cwd scroll event handler.
* Lazy load - append to cwd not shown files
*
* @return void
*/
render = function() {
var html = [],
dirs = false,
ltmb = [],
atmb = {},
last = cwd.find('[id]:last'),
top = !last.length,
place = list ? cwd.children('table').children('tbody') : cwd,
files;
if (!buffer.length) {
return wrapper.unbind(scrollEvent);
}
while ((!last.length || last.position().top <= wrapper.height() + wrapper.scrollTop() + fm.options.showThreshold)
&& (files = buffer.splice(0, fm.options.showFiles)).length) {
html = $.map(files, function(f) {
if (f.hash && f.name) {
if (f.mime == 'directory') {
dirs = true;
}
if (f.tmb) {
f.tmb === 1 ? ltmb.push(f.hash) : (atmb[f.hash] = f.tmb)
}
return itemhtml(f);
}
return null;
});
place.append(html.join(''));
last = cwd.find('[id]:last');
// scroll top on dir load to avoid scroll after page reload
top && cwd.scrollTop(0);
}
// load/attach thumbnails
attachThumbnails(atmb);
ltmb.length && loadThumbnails(ltmb);
// make directory droppable
dirs && makeDroppable();
},
/**
* Droppable options for cwd.
* Do not add class on childs file over
*
* @type Object
*/
droppable = $.extend({}, fm.droppable, {
over : function(e, ui) {
var hash = fm.cwd().hash;
$.each(ui.helper.data('files'), function(i, h) {
if (fm.file(h).phash == hash) {
cwd.removeClass(clDropActive);
return false;
}
})
}
}),
/**
* Make directory droppable
*
* @return void
*/
makeDroppable = function() {
setTimeout(function() {
cwd.find('.directory:not(.'+clDroppable+',.elfinder-na,.elfinder-ro)').droppable(fm.droppable);
}, 20);
},
/**
* Preload required thumbnails and on load add css to files.
* Return false if required file is not visible yet (in buffer) -
* required for old api to stop loading thumbnails.
*
* @param Object file hash -> thumbnail map
* @return Boolean
*/
attachThumbnails = function(images) {
var url = fm.option('tmbUrl'),
ret = true,
ndx;
$.each(images, function(hash, tmb) {
var node = cwd.find('#'+hash);
if (node.length) {
(function(node, tmb) {
$('<img/>')
.load(function() { node.find('.elfinder-cwd-icon').css('background', "url('"+tmb+"') center center no-repeat"); })
.attr('src', tmb);
})(node, url+tmb);
} else {
ret = false;
if ((ndx = index(hash)) != -1) {
buffer[ndx].tmb = tmb;
}
}
});
return ret;
},
/**
* Load thumbnails from backend.
*
* @param Array|Boolean files hashes list for new api | true for old api
* @return void
*/
loadThumbnails = function(files) {
var tmbs = [];
if (fm.oldAPI) {
fm.request({
data : {cmd : 'tmb', current : fm.cwd().hash},
preventFail : true
})
.done(function(data) {
if (attachThumbnails(data.images||[]) && data.tmb) {
loadThumbnails();
}
})
return;
}
tmbs = tmbs = files.splice(0, tmbNum);
if (tmbs.length) {
fm.request({
data : {cmd : 'tmb', targets : tmbs},
preventFail : true
})
.done(function(data) {
if (attachThumbnails(data.images||[])) {
loadThumbnails(files);
}
});
}
},
/**
* Add new files to cwd/buffer
*
* @param Array new files
* @return void
*/
add = function(files) {
var place = list ? cwd.find('tbody') : cwd,
l = files.length,
ltmb = [],
atmb = {},
dirs = false,
findNode = function(file) {
var pointer = cwd.find('[id]:first'), file2;
while (pointer.length) {
file2 = fm.file(pointer.attr('id'));
if (file2 && fm.compare(file, file2) < 0) {
return pointer;
}
pointer = pointer.next('[id]');
}
},
findIndex = function(file) {
var l = buffer.length, i;
for (i =0; i < l; i++) {
if (fm.compare(file, buffer[i]) < 0) {
return i;
}
}
return l || -1;
},
file, hash, node, ndx;
while (l--) {
file = files[l];
hash = file.hash;
if (cwd.find('#'+hash).length) {
continue;
}
if ((node = findNode(file)) && node.length) {
node.before(itemhtml(file));
} else if ((ndx = findIndex(file)) >= 0) {
buffer.splice(ndx, 0, file);
} else {
place.append(itemhtml(file));
}
if (cwd.find('#'+hash).length) {
if (file.mime == 'directory') {
dirs = true;
} else if (file.tmb) {
file.tmb === 1 ? ltmb.push(hash) : (atmb[hash] = file.tmb);
}
}
}
attachThumbnails(atmb);
ltmb.length && loadThumbnails(ltmb);
dirs && makeDroppable();
},
/**
* Remove files from cwd/buffer
*
* @param Array files hashes
* @return void
*/
remove = function(files) {
var l = files.length, hash, n, ndx;
while (l--) {
hash = files[l];
if ((n = cwd.find('#'+hash)).length) {
try {
n.detach();
} catch(e) {
fm.debug('error', e);
}
} else if ((ndx = index(hash)) != -1) {
buffer.splice(ndx, 1);
}
}
},
msg = {
name : fm.i18n('name'),
perm : fm.i18n('perms'),
mod : fm.i18n('modify'),
size : fm.i18n('size'),
kind : fm.i18n('kind')
},
/**
* Update directory content
*
* @param Array files
* @return void
*/
content = function(files, any) {
var phash = fm.cwd().hash;
// console.log(files)
try {
// to avoid problem with draggable
cwd.children('table,'+fileSelector).remove().end();
} catch (e) {
cwd.html('');
}
cwd.removeClass('elfinder-cwd-view-icons elfinder-cwd-view-list')
.addClass('elfinder-cwd-view-'+(list ? 'list' :'icons'));
wrapper[list ? 'addClass' : 'removeClass']('elfinder-cwd-wrapper-list');
list && cwd.html('<table><thead><tr class="ui-state-default"><td >'+msg.name+'</td><td>'+msg.perm+'</td><td>'+msg.mod+'</td><td>'+msg.size+'</td><td>'+msg.kind+'</td></tr></thead><tbody/></table>');
buffer = $.map(files, function(f) { return any || f.phash == phash ? f : null; });
buffer = fm.sortFiles(buffer);
wrapper.bind(scrollEvent, render).trigger(scrollEvent);
trigger();
},
/**
* CWD node itself
*
* @type JQuery
**/
cwd = $(this)
.addClass('ui-helper-clearfix elfinder-cwd')
.attr('unselectable', 'on')
// fix ui.selectable bugs and add shift+click support
.delegate(fileSelector, 'click.'+fm.namespace, function(e) {
var p = this.id ? $(this) : $(this).parents('[id]:first'),
prev = p.prevAll('.'+clSelected+':first'),
next = p.nextAll('.'+clSelected+':first'),
pl = prev.length,
nl = next.length,
sib;
e.stopImmediatePropagation();
if (e.shiftKey && (pl || nl)) {
sib = pl ? p.prevUntil('#'+prev.attr('id')) : p.nextUntil('#'+next.attr('id'));
sib.add(p).trigger(evtSelect);
} else if (e.ctrlKey || e.metaKey) {
p.trigger(p.is('.'+clSelected) ? evtUnselect : evtSelect);
} else {
cwd.find('[id].'+clSelected).trigger(evtUnselect);
p.trigger(evtSelect);
}
trigger();
})
// call fm.open()
.delegate(fileSelector, 'dblclick.'+fm.namespace, function(e) {
fm.dblclick({file : this.id});
})
// attach draggable
.delegate(fileSelector, 'mouseenter.'+fm.namespace, function(e) {
var $this = $(this),
target = list ? $this : $this.children();
if (!$this.is('.'+clTmp) && !target.is('.'+clDraggable+',.'+clDisabled)) {
target.draggable(fm.draggable);
}
})
// add hover class to selected file
.delegate(fileSelector, evtSelect, function(e) {
var $this = $(this);
!selectLock && !$this.is('.'+clDisabled) && $this.addClass(clSelected).children().addClass(clHover);
})
// remove hover class from unselected file
.delegate(fileSelector, evtUnselect, function(e) {
!selectLock && $(this).removeClass(clSelected).children().removeClass(clHover);
})
// disable files wich removing or moving
.delegate(fileSelector, evtDisable, function() {
var $this = $(this).removeClass(clSelected).addClass(clDisabled),
target = (list ? $this : $this.children()).removeClass(clHover);
$this.is('.'+clDroppable) && $this.droppable('disable');
target.is('.'+clDraggable) && target.draggable('disable');
!list && target.removeClass(clDisabled);
})
// if any files was not removed/moved - unlock its
.delegate(fileSelector, evtEnable, function() {
var $this = $(this).removeClass(clDisabled),
target = list ? $this : $this.children();
$this.is('.'+clDroppable) && $this.droppable('enable');
target.is('.'+clDraggable) && target.draggable('enable');
})
.delegate(fileSelector, 'scrolltoview', function() {
scrollToView($(this))
})
.delegate(fileSelector, 'hover', function(e) {
fm.trigger('hover', {hash : $(this).attr('id'), type : e.type});
})
.bind('contextmenu.'+fm.namespace, function(e) {
var file = $(e.target).closest('.'+clFile);
if (file.length) {
e.stopPropagation();
e.preventDefault();
if (!file.is('.'+clDisabled)) {
if (!file.is('.'+clSelected)) {
cwd.trigger('unselectall');
file.trigger(evtSelect);
trigger();
}
fm.trigger('contextmenu', {
'type' : 'files',
'targets' : fm.selected(),
'x' : e.clientX,
'y' : e.clientY
});
}
}
// e.preventDefault();
})
// make files selectable
.selectable({
filter : fileSelector,
stop : trigger,
selected : function(e, ui) { $(ui.selected).trigger(evtSelect); },
unselected : function(e, ui) { $(ui.unselected).trigger(evtUnselect); }
})
// make cwd itself droppable for folders from nav panel
.droppable(droppable)
// prepend fake file/dir
.bind('create.'+fm.namespace, function(e, file) {
var parent = list ? cwd.find('tbody') : cwd;
cwd.trigger('unselectall');
parent.prepend($(itemhtml(file)).addClass(clTmp));
cwd.scrollTop(0)
})
// unselect all selected files
.bind('unselectall', function() {
cwd.find('[id].'+clSelected+'').trigger(evtUnselect);
trigger();
})
.bind('selectfile', function(e, id) {
cwd.find('#'+id).trigger(evtSelect);
trigger();
}),
wrapper = $('<div class="elfinder-cwd-wrapper"/>')
.bind('contextmenu', function(e) {
e.preventDefault();
fm.trigger('contextmenu', {
'type' : 'cwd',
'targets' : [fm.cwd().hash],
'x' : e.clientX,
'y' : e.clientY
});
}),
resize = function() {
var h = 0;
wrapper.siblings('.elfinder-panel:visible').each(function() {
h += $(this).outerHeight(true);
});
wrapper.height(wz.height() - h);
},
// elfinder node
parent = $(this).parent().resize(resize),
// workzone node
wz = parent.children('.elfinder-workzone').append(wrapper.append(this))
;
if (fm.dragUpload) {
wrapper[0].addEventListener('dragenter', function(e) {
e.preventDefault();
e.stopPropagation();
wrapper.addClass(clDropActive);
}, false);
wrapper[0].addEventListener('dragleave', function(e) {
e.preventDefault();
e.stopPropagation();
e.target == cwd[0] && wrapper.removeClass(clDropActive);
}, false);
wrapper[0].addEventListener('dragover', function(e) {
e.preventDefault();
e.stopPropagation();
}, false);
wrapper[0].addEventListener('drop', function(e) {
e.preventDefault();
wrapper.removeClass(clDropActive);
e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length && fm.exec('upload', {files : e.dataTransfer.files})//fm.upload({files : e.dataTransfer.files});
}, false);
}
fm
.bind('open', function(e) {
content(e.data.files);
})
.bind('search', function(e) {
lastSearch = e.data.files;
content(lastSearch, true);
})
.bind('searchend', function() {
query && content(fm.files());
lastSearch = [];
query = '';
})
.bind('searchstart', function(e) {
query = e.data.query;
})
.bind('sortchange', function() {
content(query ? lastSearch : fm.files(), !!query);
})
.bind('viewchange', function() {
var sel = fm.selected(),
l = fm.storage('view') == 'list';
if (l != list) {
list = l;
content(fm.files());
$.each(sel, function(i, h) {
selectFile(h);
});
trigger();
}
resize();
})
.add(function(e) {
var phash = fm.cwd().hash,
files = query
? $.map(e.data.added || [], function(f) { return f.name.indexOf(query) === -1 ? null : f })
: $.map(e.data.added || [], function(f) { return f.phash == phash ? f : null; })
;
add(files)
// add($.map(e.data.added || [], function(f) { return f.phash == phash ? f : null; }));
})
.change(function(e) {
var phash = fm.cwd().hash,
sel = fm.selected(),
files;
if (query) {
$.each(e.data.changed || [], function(i, file) {
remove([file.hash]);
if (file.name.indexOf(query) !== -1) {
add([file]);
$.inArray(file.hash, sel) !== -1 && selectFile(file.hash);
}
})
} else {
$.each($.map(e.data.changed || [], function(f) { return f.phash == phash ? f : null; }), function(i, file) {
remove([file.hash]);
add([file]);
$.inArray(file.hash, sel) !== -1 && selectFile(file.hash);
});
}
trigger();
})
.remove(function(e) {
remove(e.data.removed || []);
trigger();
})
// fix cwd height if it less then wrapper
.bind('open add search searchend', function() {
cwd.css('height', 'auto');
if (cwd.outerHeight(true) < wrapper.height()) {
cwd.height(wrapper.height() - (cwd.outerHeight(true) - cwd.height()) - 2);
}
})
// select dragged file if no selected, disable selectable
.dragstart(function(e) {
var target = $(e.data.target),
oe = e.data.originalEvent;
if (target.is(fileSelector)) {
if (!target.is('.'+clSelected)) {
!(oe.ctrlKey || oe.metaKey || oe.shiftKey) && unselectAll();
target.trigger(evtSelect);
trigger();
}
cwd.droppable('disable');
}
cwd.selectable('disable').removeClass(clDisabled);
selectLock = true;
})
// enable selectable
.dragstop(function() {
cwd.selectable('enable');
selectLock = false;
})
.bind('lockfiles unlockfiles', function(e) {
var event = e.type == 'lockfiles' ? evtDisable : evtEnable,
files = e.data.files || [],
l = files.length;
while (l--) {
cwd.find('#'+files[l]).trigger(event);
}
trigger();
})
// select new files after some actions
.bind('mkdir mkfile duplicate upload rename archive extract', function(e) {
var phash = fm.cwd().hash, files;
cwd.trigger('unselectall');
$.each(e.data.added || [], function(i, file) {
file && file.phash == phash && selectFile(file.hash);
});
trigger();
})
.shortcut({
pattern :'ctrl+a',
description : 'selectall',
callback : function() {
var sel = [], phash;
cwd.find('[id]:not(.'+clSelected+')').trigger(evtSelect);
if (buffer.length) {
phash = fm.cwd().hash;
fm.select({selected : $.map(fm.files(), function(f) { return f.phash == phash ? f.hash : null; })})
} else {
trigger();
}
}
})
.shortcut({
pattern : 'left right up down shift+left shift+right shift+up shift+down',
description : 'selectfiles',
type : 'keydown' , //$.browser.mozilla || $.browser.opera ? 'keypress' : 'keydown',
callback : function(e) { select(e.keyCode, e.shiftKey); }
})
.shortcut({
pattern : 'home',
description : 'selectffile',
callback : function(e) {
unselectAll();
scrollToView(cwd.find('[id]:first').trigger(evtSelect))
trigger();
}
})
.shortcut({
pattern : 'end',
description : 'selectlfile',
callback : function(e) {
unselectAll();
scrollToView(cwd.find('[id]:last').trigger(evtSelect)) ;
trigger();
}
});
});
// fm.timeEnd('cwdLoad')
return this;
}