* @class elFinder command "rm"
* @author Dmitry (dio) Levashov
elFinder.prototype.commands.rm = function() {
tpl = '<div class="ui-helper-clearfix elfinder-rm-title"><span class="elfinder-cwd-icon {class} ui-corner-all"></span>{title}<div class="elfinder-rm-desc">{desc}</div></div>',
confirm = function(dfrd, targets, files, tHash, addTexts) {
var cnt = targets.length,
spinner = fm.i18n('calc') + '<span class="elfinder-spinner"></span>',
dialog, text, tmb, size, f, fname;
$.each(files, function(h, f) {
if (f.size && f.size != 'unknown' && f.mime !== 'directory') {
var s = parseInt(f.size);
if (s >= 0 && size >= 0) {
getSize = (size === 'unknown');
descs.push(fm.i18n('size')+': '+(getSize? spinner : fm.formatSize(size)));
text = [$(tpl.replace('{class}', 'elfinder-cwd-icon-group').replace('{title}', '<strong>' + fm.i18n('items')+ ': ' + cnt + '</strong>').replace('{desc}', descs.join('<br>')))];
getSize = (f.mime === 'directory');
descs.push(fm.i18n('size')+': '+(getSize? spinner : fm.formatSize(f.size)));
descs.push(fm.i18n('modify')+': '+fm.formatDate(f));
fname = fm.escape(f.i18 || f.name).replace(/([_.])/g, '​$1');
text = [$(tpl.replace('{class}', fm.mime2class(f.mime)).replace('{title}', '<strong>' + fname + '</strong>').replace('{desc}', descs.join('<br>')))];
text = text.concat(addTexts);
text.push(tHash? 'confirmTrash' : 'confirmRm');
self.toTrash(dfrd, targets, tHash);
fm.unlockfiles({files : targets});
if (targets.length === 1 && fm.file(targets[0]).phash !== cwd) {
fm.select({selected : targets});
fm.selectfiles({files : targets});
.on('load', function() { dialog.find('.elfinder-cwd-icon').addClass(tmb.className).css('background-image', "url('"+tmb.url+"')"); })
getSize = fm.getSize($.map(files, function(f) { return f.mime === 'directory'? f.hash : null; })).done(function(data) {
dialog.find('span.elfinder-spinner').parent().html(fm.i18n('size')+': '+data.formated);
dialog.find('span.elfinder-spinner').parent().html(fm.i18n('size')+': '+fm.i18n('unknown'));
toTrash = function(dfrd, targets, tHash) {
itemCnt = targets.length,
maxCnt = self.options.toTrashMaxItems,
self.confirm(dfrd, targets, self.files(targets), null, [fm.i18n('tooManyToTrash')]);
// Directory preparation preparation and directory enumeration
$.each(targets, function(i, h) {
path = fm.path(h).replace(/\\/g, '/'),
m = path.match(/^[^\/]+?(\/(?:[^\/]+?\/)*)[^\/]+?$/);
m[1] = m[1].replace(/(^\/.*?)\/?$/, '$1');
if (file.mime === 'directory') {
// Check directory information
data : {cmd : 'size', targets : checkDirs},
notify : {type: 'readdir', cnt: 1, hideCnt: true},
data.fileCnt && (cnt += parseInt(data.fileCnt));
data.dirCnt && (cnt += parseInt(data.dirCnt));
reqDfd[cnt > maxCnt ? 'reject' : 'resolve']();
var xhr = (req && req.xhr)? req.xhr : null;
if (xhr && xhr.state() == 'pending') {
}, self.options.infoCheckWait * 1000);
// Directory creation and paste command execution
dirs = Object.keys(dsts);
data : {cmd : 'mkdir', target : tHash, dirs : dirs},
notify : {type : 'chkdir', cnt : cnt},
fm.unlockfiles({files : targets});
var margeRes = function(data, phash, reqData) {
var undo, prevUndo, redo, prevRedo;
$.each(data, function(k, v) {
res[k] = res[k].concat(v);
if (data.added && data.added.length) {
dirs = $.map(data.added, function(f) { return f.mime === 'directory'? f.hash : null; });
$.each(data.added, function(i, f) {
if ($.inArray(f.phash, dirs) === -1) {
return fm.exec('restore', targets, {noToast: true});
notify : {type : 'redo', cnt : targets.length}
return fm.ui.notify.children('.elfinder-notify-trash').length;
if (hashes = data.hashes) {
prgSt = cnt === 1? 100 : 5;
tm = setTimeout(function() {
fm.notify({type : 'trash', cnt : 1, hideCnt : true, progress : prgSt});
$.each(dsts, function(dir, files) {
var phash = fm.file(files[0]).phash,
reqData = {cmd : 'paste', dst : hashes[dir], targets : files, cut : 1};
data = fm.normalize(data);
margeRes(data, phash, reqData);
err = err.concat(data.warning);
// fire some event to update cache/ui
data.removed && data.removed.length && fm.remove(data);
data.added && data.added.length && fm.add(data);
data.changed && data.changed.length && fm.change(data);
// fire event with command name
fm.trigger('paste', data);
// fire event with command name + 'done'
var hashes = [], addTexts, end = 2;
fm.notify({type : 'trash', cnt : 0, hideCnt : true, progress : prg});
hasNtf() && fm.notify({type : 'trash', cnt : -1});
fm.unlockfiles({files : targets});
if (Object.keys(res).length) {
if (res.removed || res.removed.length) {
hashes = $.grep(targets, function(h) {
return $.inArray(h, res.removed) === -1? true : false;
end = (fm.messages[err[end-1]] || '').indexOf('$') === -1? end : end + 1;
fm.exec('rm', hashes, { addTexts: err.slice(0, end), forceRm: true });
if (res.undo && res.redo) {
dfrd.reject('errFolderNotFound');
fm.unlockfiles({files : targets});
dfrd.reject(['error', 'The folder hierarchy to be deleting can not be determined.']);
fm.unlockfiles({files : targets});
self.confirm(dfrd, targets, self.files(targets), null, [fm.i18n('tooManyToTrash')]);
remove = function(dfrd, targets, quiet) {
var notify = quiet? {} : {type : 'rm', cnt : targets.length};
data : {cmd : 'rm', targets : targets},
if (data.error || data.warning) {
fm.unlockfiles({files : targets});
getTHash = function(targets) {
if (targets && targets.length) {
if (targets.length > 1 && fm.searchStatus.state === 2) {
root1st = fm.file(fm.root(targets[0])).volumeid;
if (!$.grep(targets, function(h) { return h.indexOf(root1st) !== 0? true : false ; }).length) {
thash = fm.option('trashHash', targets[0]);
thash = fm.option('trashHash', targets[0]);
// for to be able to overwrite
this.syncTitleOnChange = true;
this.updateOnSelect = false;
pattern : 'delete ctrl+backspace shift+delete'
var update = function(origin) {
self.title = fm.i18n('cmd' + self.value);
self.className = self.value;
self.button && self.button.children('span.elfinder-button-icon')[self.value === 'trash'? 'addClass' : 'removeClass']('elfinder-button-icon-trash');
if (origin && origin !== 'cwd' && (self.state > -1 || origin === 'navbar')) {
if (self.value === 'trash') {
.attr({title: fm.i18n('cmdrm')})
.on('ready', function(e, data) {
.on('click touchstart', function(e){
if (e.type === 'touchstart' && e.originalEvent.touches.length > 1) {
fm.getUI().trigger('click'); // to close the context menu immediately
fm.exec('rm', targets, {_userAction: true, forceRm : true});
// re-assign for extended command
// bind function of change
fm.bind('contextmenucreate', function(e) {
this.getstate = function(select) {
var sel = this.hashes(select),
filter = function(files) {
return $.grep(files, function(h) {
fres = fres && (f = fm.file(h)) && ! f.locked && ! fm.isRoot(f)? true : false;
return sel.length && filter(sel).length == sel.length ? 0 : -1;
this.exec = function(hashes, cOpts) {
if (getSize && getSize.state && getSize.state() === 'pending') {
error && fm.error(error);
!opts.quiet && !data._noSound && data.removed && data.removed.length && fm.trigger('playsound', {soundFile : 'rm.wav'});
files = self.files(hashes),
addTexts = opts.addTexts? opts.addTexts : null,
$.each(files, function(i, file) {
return !dfrd.reject(['errRm', file.name, 'errPerm']);
return !dfrd.reject(['errLocked', file.name]);
if (dfrd.state() === 'pending') {
targets = self.hashes(hashes);
if (forceRm || (self.event && self.event.originalEvent && self.event.originalEvent.shiftKey)) {
self.title = fm.i18n('cmdrm');
tHash = getTHash(targets);
fm.lockfiles({files : targets});
if (tHash && self.options.quickTrash) {
self.toTrash(dfrd, targets, tHash);
remove(dfrd, targets, quiet);
self.confirm(dfrd, targets, files, tHash, addTexts);
fm.bind('select contextmenucreate closecontextmenu', function(e) {
var targets = (e.data? (e.data.selected || e.data.targets) : null) || fm.selected();
if (targets && targets.length) {
self.update(void(0), (targets? getTHash(targets) : fm.option('trashHash'))? 'trash' : 'rm');