1231 lines
34 KiB
JavaScript
1231 lines
34 KiB
JavaScript
/**
|
|
* @class elFinder command "edit".
|
|
* Edit text file in dialog window
|
|
*
|
|
* @author Dmitry (dio) Levashov, dio@std42.ru
|
|
**/
|
|
elFinder.prototype.commands.edit = function() {
|
|
"use strict";
|
|
var self = this,
|
|
fm = this.fm,
|
|
clsEditing = fm.res('class', 'editing'),
|
|
mimesSingle = [],
|
|
mimes = [],
|
|
allowAll = false,
|
|
rtrim = function(str){
|
|
return str.replace(/\s+$/, '');
|
|
},
|
|
getEncSelect = function(heads) {
|
|
var sel = jQuery('<select class="ui-corner-all"></select>'),
|
|
hval;
|
|
if (heads) {
|
|
jQuery.each(heads, function(i, head) {
|
|
hval = fm.escape(head.value);
|
|
sel.append('<option value="'+hval+'">'+(head.caption? fm.escape(head.caption) : hval)+'</option>');
|
|
});
|
|
}
|
|
jQuery.each(self.options.encodings, function(i, v) {
|
|
sel.append('<option value="'+v+'">'+v+'</option>');
|
|
});
|
|
return sel;
|
|
},
|
|
getDlgWidth = function() {
|
|
var win = fm.options.dialogContained? fm.getUI() : jQuery(window),
|
|
m, width;
|
|
if (typeof self.options.dialogWidth === 'string' && (m = self.options.dialogWidth.match(/(\d+)%/))) {
|
|
width = parseInt(win.width() * (m[1] / 100));
|
|
} else {
|
|
width = parseInt(self.options.dialogWidth || 650);
|
|
}
|
|
return Math.min(width, win.width());
|
|
},
|
|
getDlgHeight = function() {
|
|
if (!self.options.dialogHeight) {
|
|
return void(0);
|
|
}
|
|
var win = fm.options.dialogContained? fm.getUI() : jQuery(window),
|
|
m, height;
|
|
if (typeof self.options.dialogHeight === 'string' && (m = self.options.dialogHeight.match(/(\d+)%/))) {
|
|
height = parseInt(win.height() * (m[1] / 100));
|
|
} else {
|
|
height = parseInt(self.options.dialogHeight || win.height());
|
|
}
|
|
return Math.min(height, win.height());
|
|
},
|
|
|
|
/**
|
|
* Return files acceptable to edit
|
|
*
|
|
* @param Array files hashes
|
|
* @return Array
|
|
**/
|
|
filter = function(files) {
|
|
var cnt = files.length,
|
|
mime, ext, skip;
|
|
|
|
if (cnt > 1) {
|
|
mime = files[0].mime;
|
|
ext = files[0].name.replace(/^.*(\.[^.]+)$/, '$1');
|
|
}
|
|
return jQuery.grep(files, function(file) {
|
|
var res;
|
|
if (skip || file.mime === 'directory') {
|
|
return false;
|
|
}
|
|
res = file.read
|
|
&& (allowAll || fm.mimeIsText(file.mime) || jQuery.inArray(file.mime, cnt === 1? mimesSingle : mimes) !== -1)
|
|
&& (!self.onlyMimes.length || jQuery.inArray(file.mime, self.onlyMimes) !== -1)
|
|
&& (cnt === 1 || (file.mime === mime && file.name.substr(ext.length * -1) === ext))
|
|
&& (fm.uploadMimeCheck(file.mime, file.phash)? true : false)
|
|
&& setEditors(file, cnt)
|
|
&& Object.keys(editors).length;
|
|
if (!res) {
|
|
skip = true;
|
|
}
|
|
return res;
|
|
});
|
|
},
|
|
|
|
fileSync = function(hash) {
|
|
var old = fm.file(hash),
|
|
f;
|
|
fm.request({
|
|
cmd: 'info',
|
|
targets: [hash],
|
|
preventDefault: true
|
|
}).done(function(data) {
|
|
var changed;
|
|
if (data && data.files && data.files.length) {
|
|
f = data.files[0];
|
|
if (old.ts != f.ts || old.size != f.size) {
|
|
changed = { changed: [ f ] };
|
|
fm.updateCache(changed);
|
|
fm.change(changed);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Open dialog with textarea to edit file
|
|
*
|
|
* @param String id dialog id
|
|
* @param Object file file object
|
|
* @param String content file content
|
|
* @return jQuery.Deferred
|
|
**/
|
|
dialog = function(id, file, content, encoding, editor, toasts) {
|
|
|
|
var dfrd = jQuery.Deferred(),
|
|
_loaded = false,
|
|
loaded = function() {
|
|
if (!_loaded) {
|
|
fm.toast({
|
|
mode: 'warning',
|
|
msg: fm.i18n('nowLoading')
|
|
});
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
makeToasts = function() {
|
|
// make toast message
|
|
if (toasts && Array.isArray(toasts)) {
|
|
jQuery.each(toasts, function() {
|
|
this.msg && fm.toast(this);
|
|
});
|
|
}
|
|
},
|
|
save = function() {
|
|
var encord = selEncoding? selEncoding.val():void(0),
|
|
saveDfd = jQuery.Deferred().fail(function(err) {
|
|
dialogNode.show().find('button.elfinder-btncnt-0,button.elfinder-btncnt-1').hide();
|
|
}),
|
|
conf, res, tm;
|
|
if (!loaded()) {
|
|
return saveDfd.resolve();
|
|
}
|
|
if (ta.editor) {
|
|
ta.editor.save(ta[0], ta.editor.instance);
|
|
conf = ta.editor.confObj;
|
|
if (conf.info && (conf.info.schemeContent || conf.info.arrayBufferContent)) {
|
|
encord = 'scheme';
|
|
}
|
|
}
|
|
res = getContent();
|
|
setOld(res);
|
|
if (res.promise) {
|
|
tm = setTimeout(function() {
|
|
fm.notify({
|
|
type : 'chkcontent',
|
|
cnt : 1,
|
|
hideCnt: true,
|
|
cancel : function() {
|
|
res.reject();
|
|
}
|
|
});
|
|
}, 100);
|
|
res.always(function() {
|
|
tm && clearTimeout(tm);
|
|
fm.notify({ type : 'chkcontent', cnt: -1 });
|
|
}).done(function(data) {
|
|
dfrd.notifyWith(ta, [encord, ta.data('hash'), old, saveDfd]);
|
|
}).fail(function(err) {
|
|
saveDfd.reject(err);
|
|
});
|
|
} else {
|
|
dfrd.notifyWith(ta, [encord, ta.data('hash'), old, saveDfd]);
|
|
}
|
|
return saveDfd;
|
|
},
|
|
saveon = function() {
|
|
if (!loaded()) { return; }
|
|
save().fail(function(err) {
|
|
err && fm.error(err);
|
|
});
|
|
},
|
|
cancel = function() {
|
|
ta.elfinderdialog('close');
|
|
},
|
|
savecl = function() {
|
|
if (!loaded()) { return; }
|
|
dialogNode.hide();
|
|
save().done(function() {
|
|
_loaded = false;
|
|
dialogNode.show();
|
|
cancel();
|
|
}).fail(function(err) {
|
|
dialogNode.show();
|
|
err && fm.error(err);
|
|
});
|
|
},
|
|
saveAs = function() {
|
|
if (!loaded()) { return; }
|
|
var prevOld = old,
|
|
phash = file.phash,
|
|
fail = function(err) {
|
|
dialogs.addClass(clsEditing).fadeIn(function() {
|
|
err && fm.error(err);
|
|
});
|
|
old = prevOld;
|
|
fm.disable();
|
|
},
|
|
make = function() {
|
|
self.mime = saveAsFile.mime || file.mime;
|
|
self.prefix = (saveAsFile.name || file.name).replace(/ \d+(\.[^.]+)?$/, '$1');
|
|
self.requestCmd = 'mkfile';
|
|
self.nextAction = {};
|
|
self.data = {target : phash};
|
|
jQuery.proxy(fm.res('mixin', 'make'), self)()
|
|
.done(function(data) {
|
|
var oldHash;
|
|
if (data.added && data.added.length) {
|
|
oldHash = ta.data('hash');
|
|
ta.data('hash', data.added[0].hash);
|
|
save().done(function() {
|
|
_loaded = false;
|
|
dialogNode.show();
|
|
cancel();
|
|
dialogs.fadeIn();
|
|
}).fail(function() {
|
|
fm.exec('rm', [data.added[0].hash], { forceRm: true, quiet: true });
|
|
ta.data('hash', oldHash);
|
|
dialogNode.find('button.elfinder-btncnt-2').hide();
|
|
fail();
|
|
});
|
|
} else {
|
|
fail();
|
|
}
|
|
})
|
|
.progress(function(err) {
|
|
if (err && err === 'errUploadMime') {
|
|
ta.trigger('saveAsFail');
|
|
}
|
|
})
|
|
.fail(fail)
|
|
.always(function() {
|
|
delete self.mime;
|
|
delete self.prefix;
|
|
delete self.nextAction;
|
|
delete self.data;
|
|
});
|
|
fm.trigger('unselectfiles', { files: [ file.hash ] });
|
|
},
|
|
reqOpen = null,
|
|
reqInfo = null,
|
|
dialogs = fm.getUI().children('.' + self.dialogClass + ':visible');
|
|
if (dialogNode.is(':hidden')) {
|
|
dialogs = dialogs.add(dialogNode);
|
|
}
|
|
dialogs.removeClass(clsEditing).fadeOut();
|
|
|
|
fm.enable();
|
|
|
|
if (fm.searchStatus.state < 2 && phash !== fm.cwd().hash) {
|
|
reqOpen = fm.exec('open', [phash], {thash: phash});
|
|
} else if (!fm.file(phash)) {
|
|
reqInfo = fm.request({cmd: 'info', targets: [phash]});
|
|
}
|
|
|
|
jQuery.when([reqOpen, reqInfo]).done(function() {
|
|
if (reqInfo) {
|
|
fm.one('infodone', function() {
|
|
fm.file(phash)? make() : fail('errFolderNotFound');
|
|
});
|
|
} else {
|
|
reqOpen? fm.one('cwdrender', make) : make();
|
|
}
|
|
}).fail(fail);
|
|
},
|
|
changed = function() {
|
|
var dfd = jQuery.Deferred(),
|
|
res, tm;
|
|
if (!_loaded) {
|
|
return dfd.resolve(false);
|
|
}
|
|
ta.editor && ta.editor.save(ta[0], ta.editor.instance);
|
|
res = getContent();
|
|
if (res && res.promise) {
|
|
tm = setTimeout(function() {
|
|
fm.notify({
|
|
type : 'chkcontent',
|
|
cnt : 1,
|
|
hideCnt: true,
|
|
cancel : function() {
|
|
res.reject();
|
|
}
|
|
});
|
|
}, 100);
|
|
res.always(function() {
|
|
tm && clearTimeout(tm);
|
|
fm.notify({ type : 'chkcontent', cnt: -1 });
|
|
}).done(function(d) {
|
|
dfd.resolve(old !== d);
|
|
}).fail(function(err) {
|
|
dfd.resolve(err || (old === undefined? false : true));
|
|
});
|
|
} else {
|
|
dfd.resolve(old !== res);
|
|
}
|
|
return dfd;
|
|
},
|
|
opts = {
|
|
title : fm.escape(file.name),
|
|
width : getDlgWidth(),
|
|
height : getDlgHeight(),
|
|
buttons : {},
|
|
cssClass : clsEditing,
|
|
maxWidth : 'window',
|
|
maxHeight : 'window',
|
|
allowMinimize : true,
|
|
allowMaximize : true,
|
|
openMaximized : editorMaximized() || (editor && editor.info && editor.info.openMaximized),
|
|
btnHoverFocus : false,
|
|
closeOnEscape : false,
|
|
propagationEvents : ['mousemove', 'mouseup', 'click'],
|
|
minimize : function() {
|
|
var conf;
|
|
if (ta.editor && dialogNode.closest('.ui-dialog').is(':hidden')) {
|
|
conf = ta.editor.confObj;
|
|
if (conf.info && conf.info.syncInterval) {
|
|
fileSync(file.hash);
|
|
}
|
|
}
|
|
},
|
|
close : function() {
|
|
var close = function() {
|
|
var conf;
|
|
dfrd.resolve();
|
|
if (ta.editor) {
|
|
ta.editor.close(ta[0], ta.editor.instance);
|
|
conf = ta.editor.confObj;
|
|
if (conf.info && conf.info.syncInterval) {
|
|
fileSync(file.hash);
|
|
}
|
|
}
|
|
ta.elfinderdialog('destroy');
|
|
},
|
|
onlySaveAs = (typeof saveAsFile.name !== 'undefined'),
|
|
accept = onlySaveAs? {
|
|
label : 'btnSaveAs',
|
|
callback : function() {
|
|
requestAnimationFrame(saveAs);
|
|
}
|
|
} : {
|
|
label : 'btnSaveClose',
|
|
callback : function() {
|
|
save().done(function() {
|
|
close();
|
|
});
|
|
}
|
|
};
|
|
changed().done(function(change) {
|
|
var msgs = ['confirmNotSave'];
|
|
if (change) {
|
|
if (typeof change === 'string') {
|
|
msgs.unshift(change);
|
|
}
|
|
fm.confirm({
|
|
title : self.title,
|
|
text : msgs,
|
|
accept : accept,
|
|
cancel : {
|
|
label : 'btnClose',
|
|
callback : close
|
|
},
|
|
buttons : onlySaveAs? null : [{
|
|
label : 'btnSaveAs',
|
|
callback : function() {
|
|
requestAnimationFrame(saveAs);
|
|
}
|
|
}]
|
|
});
|
|
} else {
|
|
close();
|
|
}
|
|
});
|
|
},
|
|
open : function() {
|
|
var loadRes, conf, interval;
|
|
ta.initEditArea.call(ta, id, file, content, fm);
|
|
if (ta.editor) {
|
|
loadRes = ta.editor.load(ta[0]) || null;
|
|
if (loadRes && loadRes.done) {
|
|
loadRes.always(function() {
|
|
_loaded = true;
|
|
}).done(function(instance) {
|
|
ta.editor.instance = instance;
|
|
ta.editor.focus(ta[0], ta.editor.instance);
|
|
setOld(getContent());
|
|
requestAnimationFrame(function() {
|
|
dialogNode.trigger('resize');
|
|
});
|
|
}).fail(function(error) {
|
|
error && fm.error(error);
|
|
ta.elfinderdialog('destroy');
|
|
return;
|
|
}).always(makeToasts);
|
|
} else {
|
|
_loaded = true;
|
|
if (loadRes && (typeof loadRes === 'string' || Array.isArray(loadRes))) {
|
|
fm.error(loadRes);
|
|
ta.elfinderdialog('destroy');
|
|
return;
|
|
}
|
|
ta.editor.instance = loadRes;
|
|
ta.editor.focus(ta[0], ta.editor.instance);
|
|
setOld(getContent());
|
|
requestAnimationFrame(function() {
|
|
dialogNode.trigger('resize');
|
|
});
|
|
makeToasts();
|
|
}
|
|
conf = ta.editor.confObj;
|
|
if (conf.info && conf.info.syncInterval) {
|
|
if (interval = parseInt(conf.info.syncInterval)) {
|
|
setTimeout(function() {
|
|
autoSync(interval);
|
|
}, interval);
|
|
}
|
|
}
|
|
} else {
|
|
_loaded = true;
|
|
setOld(getContent());
|
|
}
|
|
},
|
|
resize : function(e, data) {
|
|
ta.editor && ta.editor.resize(ta[0], ta.editor.instance, e, data || {});
|
|
}
|
|
},
|
|
getContent = function() {
|
|
var res = ta.getContent.call(ta, ta[0]);
|
|
if (res === undefined || res === false || res === null) {
|
|
res = jQuery.Deferred().reject();
|
|
}
|
|
return res;
|
|
},
|
|
setOld = function(res) {
|
|
if (res && res.promise) {
|
|
res.done(function(d) {
|
|
old = d;
|
|
});
|
|
} else {
|
|
old = res;
|
|
}
|
|
},
|
|
autoSync = function(interval) {
|
|
if (dialogNode.is(':visible')) {
|
|
fileSync(file.hash);
|
|
setTimeout(function() {
|
|
autoSync(interval);
|
|
}, interval);
|
|
}
|
|
},
|
|
stateChange = function() {
|
|
if (selEncoding) {
|
|
changed().done(function(change) {
|
|
if (change) {
|
|
selEncoding.attr('title', fm.i18n('saveAsEncoding')).addClass('elfinder-edit-changed');
|
|
} else {
|
|
selEncoding.attr('title', fm.i18n('openAsEncoding')).removeClass('elfinder-edit-changed');
|
|
}
|
|
});
|
|
}
|
|
},
|
|
saveAsFile = {},
|
|
ta, old, dialogNode, selEncoding, extEditor, maxW, syncInterval;
|
|
|
|
if (editor) {
|
|
if (editor.html) {
|
|
ta = jQuery(editor.html);
|
|
}
|
|
extEditor = {
|
|
init : editor.init || null,
|
|
load : editor.load,
|
|
getContent : editor.getContent || null,
|
|
save : editor.save,
|
|
beforeclose : typeof editor.beforeclose == 'function' ? editor.beforeclose : void 0,
|
|
close : typeof editor.close == 'function' ? editor.close : function() {},
|
|
focus : typeof editor.focus == 'function' ? editor.focus : function() {},
|
|
resize : typeof editor.resize == 'function' ? editor.resize : function() {},
|
|
instance : null,
|
|
doSave : saveon,
|
|
doCancel : cancel,
|
|
doClose : savecl,
|
|
file : file,
|
|
fm : fm,
|
|
confObj : editor,
|
|
trigger : function(evName, data) {
|
|
fm.trigger('editEditor' + evName, Object.assign({}, editor.info || {}, data));
|
|
}
|
|
};
|
|
}
|
|
|
|
if (!ta) {
|
|
if (!fm.mimeIsText(file.mime)) {
|
|
return dfrd.reject('errEditorNotFound');
|
|
}
|
|
(function() {
|
|
ta = jQuery('<textarea class="elfinder-file-edit" rows="20" id="'+id+'-ta"></textarea>')
|
|
.on('input propertychange', stateChange);
|
|
|
|
if (!editor || !editor.info || editor.info.useTextAreaEvent) {
|
|
ta.on('keydown', function(e) {
|
|
var code = e.keyCode,
|
|
value, start;
|
|
|
|
e.stopPropagation();
|
|
if (code == jQuery.ui.keyCode.TAB) {
|
|
e.preventDefault();
|
|
// insert tab on tab press
|
|
if (this.setSelectionRange) {
|
|
value = this.value;
|
|
start = this.selectionStart;
|
|
this.value = value.substr(0, start) + "\t" + value.substr(this.selectionEnd);
|
|
start += 1;
|
|
this.setSelectionRange(start, start);
|
|
}
|
|
}
|
|
|
|
if (e.ctrlKey || e.metaKey) {
|
|
// close on ctrl+w/q
|
|
if (code == 'Q'.charCodeAt(0) || code == 'W'.charCodeAt(0)) {
|
|
e.preventDefault();
|
|
cancel();
|
|
}
|
|
if (code == 'S'.charCodeAt(0)) {
|
|
e.preventDefault();
|
|
saveon();
|
|
}
|
|
}
|
|
|
|
})
|
|
.on('mouseenter', function(){this.focus();});
|
|
}
|
|
|
|
ta.initEditArea = function(id, file, content) {
|
|
// ta.hide() for performance tune. Need ta.show() in `load()` if use textarea node.
|
|
ta.hide().val(content);
|
|
this._setupSelEncoding(content);
|
|
};
|
|
})();
|
|
}
|
|
|
|
// extended function to setup selector of encoding for text editor
|
|
ta._setupSelEncoding = function(content) {
|
|
var heads = (encoding && encoding !== 'unknown')? [{value: encoding}] : [],
|
|
wfake = jQuery('<select></select>').hide(),
|
|
setSelW = function(init) {
|
|
init && wfake.appendTo(selEncoding.parent());
|
|
wfake.empty().append(jQuery('<option></option>').text(selEncoding.val()));
|
|
selEncoding.width(wfake.width());
|
|
};
|
|
if (content === '' || ! encoding || encoding !== 'UTF-8') {
|
|
heads.push({value: 'UTF-8'});
|
|
}
|
|
selEncoding = getEncSelect(heads).on('touchstart', function(e) {
|
|
// for touch punch event handler
|
|
e.stopPropagation();
|
|
}).on('change', function() {
|
|
// reload to change encoding if not edited
|
|
changed().done(function(change) {
|
|
if (! change && getContent() !== '') {
|
|
cancel();
|
|
edit(file, selEncoding.val(), editor).fail(function(err) { err && fm.error(err); });
|
|
}
|
|
});
|
|
setSelW();
|
|
}).on('mouseover', stateChange);
|
|
ta.parent().next().prepend(jQuery('<div class="ui-dialog-buttonset elfinder-edit-extras"></div>').append(selEncoding));
|
|
setSelW(true);
|
|
};
|
|
|
|
ta.data('hash', file.hash);
|
|
|
|
if (extEditor) {
|
|
ta.editor = extEditor;
|
|
|
|
if (typeof extEditor.beforeclose === 'function') {
|
|
opts.beforeclose = function() {
|
|
return extEditor.beforeclose(ta[0], extEditor.instance);
|
|
};
|
|
}
|
|
|
|
if (typeof extEditor.init === 'function') {
|
|
ta.initEditArea = extEditor.init;
|
|
}
|
|
|
|
if (typeof extEditor.getContent === 'function') {
|
|
ta.getContent = extEditor.getContent;
|
|
}
|
|
}
|
|
|
|
if (! ta.initEditArea) {
|
|
ta.initEditArea = function() {};
|
|
}
|
|
|
|
if (! ta.getContent) {
|
|
ta.getContent = function() {
|
|
return rtrim(ta.val());
|
|
};
|
|
}
|
|
|
|
if (!editor || !editor.info || !editor.info.preventGet) {
|
|
opts.buttons[fm.i18n('btnSave')] = saveon;
|
|
opts.buttons[fm.i18n('btnSaveClose')] = savecl;
|
|
opts.buttons[fm.i18n('btnSaveAs')] = saveAs;
|
|
opts.buttons[fm.i18n('btnCancel')] = cancel;
|
|
}
|
|
|
|
if (editor && typeof editor.prepare === 'function') {
|
|
editor.prepare(ta, opts, file);
|
|
}
|
|
|
|
dialogNode = self.fmDialog(ta, opts)
|
|
.attr('id', id)
|
|
.on('keydown keyup keypress', function(e) {
|
|
e.stopPropagation();
|
|
})
|
|
.css({ overflow: 'hidden', minHeight: '7em' })
|
|
.addClass('elfinder-edit-editor')
|
|
.closest('.ui-dialog')
|
|
.on('changeType', function(e, data) {
|
|
if (data.extention && data.mime) {
|
|
var ext = data.extention,
|
|
mime = data.mime,
|
|
btnSet = jQuery(this).children('.ui-dialog-buttonpane').children('.ui-dialog-buttonset');
|
|
btnSet.children('.elfinder-btncnt-0,.elfinder-btncnt-1').hide();
|
|
saveAsFile.name = fm.splitFileExtention(file.name)[0] + '.' + data.extention;
|
|
saveAsFile.mime = data.mime;
|
|
if (!data.keepEditor) {
|
|
btnSet.children('.elfinder-btncnt-2').trigger('click');
|
|
}
|
|
}
|
|
});
|
|
|
|
// care to viewport scale change with mobile devices
|
|
maxW = (fm.options.dialogContained? fm.getUI() : jQuery(window)).width();
|
|
(dialogNode.width() > maxW) && dialogNode.width(maxW);
|
|
|
|
return dfrd.promise();
|
|
},
|
|
|
|
/**
|
|
* Get file content and
|
|
* open dialog with textarea to edit file content
|
|
*
|
|
* @param String file hash
|
|
* @return jQuery.Deferred
|
|
**/
|
|
edit = function(file, convert, editor) {
|
|
var hash = file.hash,
|
|
opts = fm.options,
|
|
dfrd = jQuery.Deferred(),
|
|
id = 'edit-'+fm.namespace+'-'+file.hash,
|
|
d = fm.getUI().find('#'+id),
|
|
conv = !convert? 0 : convert,
|
|
noContent = false,
|
|
req, error, res;
|
|
|
|
|
|
if (d.length) {
|
|
d.elfinderdialog('toTop');
|
|
return dfrd.resolve();
|
|
}
|
|
|
|
if (!file.read || (!file.write && (!editor.info || !editor.info.converter))) {
|
|
error = ['errOpen', file.name, 'errPerm'];
|
|
return dfrd.reject(error);
|
|
}
|
|
|
|
if (editor && editor.info) {
|
|
if (typeof editor.info.edit === 'function') {
|
|
res = editor.info.edit.call(fm, file, editor);
|
|
if (res.promise) {
|
|
res.done(function() {
|
|
dfrd.resolve();
|
|
}).fail(function(error) {
|
|
dfrd.reject(error);
|
|
});
|
|
} else {
|
|
res? dfrd.resolve() : dfrd.reject();
|
|
}
|
|
return dfrd;
|
|
}
|
|
|
|
noContent = editor.info.preventGet || editor.info.noContent;
|
|
if (editor.info.urlAsContent || noContent) {
|
|
req = jQuery.Deferred();
|
|
if (editor.info.urlAsContent) {
|
|
fm.url(hash, { async: true, onetime: true, temporary: true }).done(function(url) {
|
|
req.resolve({content: url});
|
|
});
|
|
} else {
|
|
req.resolve({});
|
|
}
|
|
} else {
|
|
if (conv) {
|
|
file.encoding = conv;
|
|
fm.cache(file, 'change');
|
|
}
|
|
req = fm.request({
|
|
data : {cmd : 'get', target : hash, conv : conv, _t : file.ts},
|
|
options : {type: 'get', cache : true},
|
|
notify : {type : 'file', cnt : 1},
|
|
preventDefault : true
|
|
});
|
|
}
|
|
|
|
req.done(function(data) {
|
|
var selEncoding, reg, m, res;
|
|
if (data.doconv) {
|
|
fm.confirm({
|
|
title : self.title,
|
|
text : data.doconv === 'unknown'? 'confirmNonUTF8' : 'confirmConvUTF8',
|
|
accept : {
|
|
label : 'btnConv',
|
|
callback : function() {
|
|
dfrd = edit(file, selEncoding.val(), editor);
|
|
}
|
|
},
|
|
cancel : {
|
|
label : 'btnCancel',
|
|
callback : function() { dfrd.reject(); }
|
|
},
|
|
optionsCallback : function(options) {
|
|
options.create = function() {
|
|
var base = jQuery('<div class="elfinder-dialog-confirm-encoding"></div>'),
|
|
head = {value: data.doconv},
|
|
detected;
|
|
|
|
if (data.doconv === 'unknown') {
|
|
head.caption = '-';
|
|
}
|
|
selEncoding = getEncSelect([head]);
|
|
jQuery(this).next().find('.ui-dialog-buttonset')
|
|
.prepend(base.append(jQuery('<label>'+fm.i18n('encoding')+' </label>').append(selEncoding)));
|
|
};
|
|
}
|
|
});
|
|
} else {
|
|
if (!noContent && fm.mimeIsText(file.mime)) {
|
|
reg = new RegExp('^(data:'+file.mime.replace(/([.+])/g, '\\$1')+';base64,)', 'i');
|
|
if (!editor.info.dataScheme) {
|
|
if (window.atob && (m = data.content.match(reg))) {
|
|
data.content = atob(data.content.substr(m[1].length));
|
|
}
|
|
} else {
|
|
if (window.btoa && !data.content.match(reg)) {
|
|
data.content = 'data:'+file.mime+';base64,'+btoa(data.content);
|
|
}
|
|
}
|
|
}
|
|
dialog(id, file, data.content, data.encoding, editor, data.toasts)
|
|
.done(function(data) {
|
|
dfrd.resolve(data);
|
|
})
|
|
.progress(function(encoding, newHash, data, saveDfd) {
|
|
var ta = this;
|
|
if (newHash) {
|
|
hash = newHash;
|
|
}
|
|
fm.request({
|
|
options : {type : 'post'},
|
|
data : {
|
|
cmd : 'put',
|
|
target : hash,
|
|
encoding : encoding || data.encoding,
|
|
content : data
|
|
},
|
|
notify : {type : 'save', cnt : 1},
|
|
syncOnFail : true,
|
|
preventFail : true,
|
|
navigate : {
|
|
target : 'changed',
|
|
toast : {
|
|
inbuffer : {msg: fm.i18n(['complete', fm.i18n('btnSave')])}
|
|
}
|
|
}
|
|
})
|
|
.fail(function(error) {
|
|
dfrd.reject(error);
|
|
saveDfd.reject();
|
|
})
|
|
.done(function(data) {
|
|
requestAnimationFrame(function(){
|
|
ta.trigger('focus');
|
|
ta.editor && ta.editor.focus(ta[0], ta.editor.instance);
|
|
});
|
|
saveDfd.resolve();
|
|
});
|
|
})
|
|
.fail(function(error) {
|
|
dfrd.reject(error);
|
|
});
|
|
}
|
|
})
|
|
.fail(function(error) {
|
|
var err = fm.parseError(error);
|
|
err = Array.isArray(err)? err[0] : err;
|
|
if (file.encoding) {
|
|
file.encoding = '';
|
|
fm.cache(file, 'change');
|
|
}
|
|
(err !== 'errConvUTF8') && fm.sync();
|
|
dfrd.reject(error);
|
|
});
|
|
}
|
|
|
|
return dfrd.promise();
|
|
},
|
|
|
|
/**
|
|
* Current editors of selected files
|
|
*
|
|
* @type Object
|
|
*/
|
|
editors = {},
|
|
|
|
/**
|
|
* Fallback editor (Simple text editor)
|
|
*
|
|
* @type Object
|
|
*/
|
|
fallbackEditor = {
|
|
// Simple Text (basic textarea editor)
|
|
info : {
|
|
id : 'textarea',
|
|
name : 'TextArea',
|
|
useTextAreaEvent : true
|
|
},
|
|
load : function(textarea) {
|
|
// trigger event 'editEditorPrepare'
|
|
this.trigger('Prepare', {
|
|
node: textarea,
|
|
editorObj: void(0),
|
|
instance: void(0),
|
|
opts: {}
|
|
});
|
|
textarea.setSelectionRange && textarea.setSelectionRange(0, 0);
|
|
jQuery(textarea).trigger('focus').show();
|
|
},
|
|
save : function(){}
|
|
},
|
|
|
|
/**
|
|
* Set current editors
|
|
*
|
|
* @param Object file object
|
|
* @param Number cnt count of selected items
|
|
* @return Void
|
|
*/
|
|
setEditors = function(file, cnt) {
|
|
var mimeMatch = function(fileMime, editorMimes){
|
|
if (!editorMimes) {
|
|
return fm.mimeIsText(fileMime);
|
|
} else {
|
|
if (editorMimes[0] === '*' || jQuery.inArray(fileMime, editorMimes) !== -1) {
|
|
return true;
|
|
}
|
|
var i, l;
|
|
l = editorMimes.length;
|
|
for (i = 0; i < l; i++) {
|
|
if (fileMime.indexOf(editorMimes[i]) === 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
},
|
|
extMatch = function(fileName, editorExts){
|
|
if (!editorExts || !editorExts.length) {
|
|
return true;
|
|
}
|
|
var ext = fileName.replace(/^.+\.([^.]+)|(.+)$/, '$1$2').toLowerCase(),
|
|
i, l;
|
|
l = editorExts.length;
|
|
for (i = 0; i < l; i++) {
|
|
if (ext === editorExts[i].toLowerCase()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
optEditors = self.options.editors || [],
|
|
cwdWrite = fm.cwd().write;
|
|
|
|
stored = fm.storage('storedEditors') || {};
|
|
editors = {};
|
|
if (!optEditors.length) {
|
|
optEditors = [fallbackEditor];
|
|
}
|
|
jQuery.each(optEditors, function(i, editor) {
|
|
var name;
|
|
if ((cnt === 1 || !editor.info.single)
|
|
&& ((!editor.info || !editor.info.converter)? file.write : cwdWrite)
|
|
&& (file.size > 0 || (!editor.info.converter && editor.info.canMakeEmpty !== false && fm.mimesCanMakeEmpty[file.mime]))
|
|
&& (!editor.info.maxSize || file.size <= editor.info.maxSize)
|
|
&& mimeMatch(file.mime, editor.mimes || null)
|
|
&& extMatch(file.name, editor.exts || null)
|
|
&& typeof editor.load == 'function'
|
|
&& typeof editor.save == 'function') {
|
|
|
|
name = editor.info.name? editor.info.name : ('Editor ');
|
|
editor.id = editor.info.id? editor.info.id : ('editor' + i),
|
|
editor.name = name;
|
|
editor.i18n = fm.i18n(name);
|
|
editors[editor.id] = editor;
|
|
}
|
|
});
|
|
return Object.keys(editors).length? true : false;
|
|
},
|
|
store = function(mime, editor) {
|
|
if (mime && editor) {
|
|
if (!jQuery.isPlainObject(stored)) {
|
|
stored = {};
|
|
}
|
|
stored[mime] = editor.id;
|
|
fm.storage('storedEditors', stored);
|
|
fm.trigger('selectfiles', {files : fm.selected()});
|
|
}
|
|
},
|
|
useStoredEditor = function() {
|
|
var d = fm.storage('useStoredEditor');
|
|
return d? (d > 0) : self.options.useStoredEditor;
|
|
},
|
|
editorMaximized = function() {
|
|
var d = fm.storage('editorMaximized');
|
|
return d? (d > 0) : self.options.editorMaximized;
|
|
},
|
|
getSubMenuRaw = function(files, callback) {
|
|
var subMenuRaw = [];
|
|
jQuery.each(editors, function(id, ed) {
|
|
subMenuRaw.push(
|
|
{
|
|
label : fm.escape(ed.i18n),
|
|
icon : ed.info && ed.info.icon? ed.info.icon : 'edit',
|
|
options : { iconImg: ed.info && ed.info.iconImg? fm.baseUrl + ed.info.iconImg : void(0) },
|
|
callback : function() {
|
|
store(files[0].mime, ed);
|
|
callback && callback.call(ed);
|
|
}
|
|
}
|
|
);
|
|
});
|
|
return subMenuRaw;
|
|
},
|
|
getStoreId = function(name) {
|
|
// for compatibility to previous version
|
|
return name.toLowerCase().replace(/ +/g, '');
|
|
},
|
|
getStoredEditor = function(mime) {
|
|
var name = stored[mime];
|
|
return name && Object.keys(editors).length? editors[getStoreId(name)] : void(0);
|
|
},
|
|
infoRequest = function() {
|
|
|
|
},
|
|
stored;
|
|
|
|
// make public method
|
|
this.getEncSelect = getEncSelect;
|
|
|
|
this.shortcuts = [{
|
|
pattern : 'ctrl+e'
|
|
}];
|
|
|
|
this.init = function() {
|
|
var self = this,
|
|
fm = this.fm,
|
|
opts = this.options,
|
|
cmdChecks = [],
|
|
ccData, dfd;
|
|
|
|
this.onlyMimes = this.options.mimes || [];
|
|
|
|
fm.one('open', function() {
|
|
// editors setup
|
|
if (opts.editors && Array.isArray(opts.editors)) {
|
|
fm.trigger('canMakeEmptyFile', {mimes: Object.keys(fm.storage('mkfileTextMimes') || {}).concat(opts.makeTextMimes || ['text/plain'])});
|
|
jQuery.each(opts.editors, function(i, editor) {
|
|
if (editor.info && editor.info.cmdCheck) {
|
|
cmdChecks.push(editor.info.cmdCheck);
|
|
}
|
|
});
|
|
if (cmdChecks.length) {
|
|
if (fm.api >= 2.1030) {
|
|
dfd = fm.request({
|
|
data : {
|
|
cmd: 'editor',
|
|
name: cmdChecks,
|
|
method: 'enabled'
|
|
},
|
|
preventDefault : true
|
|
}).done(function(d) {
|
|
ccData = d;
|
|
}).fail(function() {
|
|
ccData = {};
|
|
});
|
|
} else {
|
|
ccData = {};
|
|
dfd = jQuery.Deferred().resolve();
|
|
}
|
|
} else {
|
|
dfd = jQuery.Deferred().resolve();
|
|
}
|
|
|
|
dfd.always(function() {
|
|
if (ccData) {
|
|
opts.editors = jQuery.grep(opts.editors, function(e) {
|
|
if (e.info && e.info.cmdCheck) {
|
|
return ccData[e.info.cmdCheck]? true : false;
|
|
} else {
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
jQuery.each(opts.editors, function(i, editor) {
|
|
if (editor.setup && typeof editor.setup === 'function') {
|
|
editor.setup.call(editor, opts, fm);
|
|
}
|
|
if (!editor.disabled) {
|
|
if (editor.mimes && Array.isArray(editor.mimes)) {
|
|
mimesSingle = mimesSingle.concat(editor.mimes);
|
|
if (!editor.info || !editor.info.single) {
|
|
mimes = mimes.concat(editor.mimes);
|
|
}
|
|
}
|
|
if (!allowAll && editor.mimes && editor.mimes[0] === '*') {
|
|
allowAll = true;
|
|
}
|
|
if (!editor.info) {
|
|
editor.info = {};
|
|
}
|
|
if (editor.info.integrate) {
|
|
fm.trigger('helpIntegration', Object.assign({cmd: 'edit'}, editor.info.integrate));
|
|
}
|
|
if (editor.info.canMakeEmpty) {
|
|
fm.trigger('canMakeEmptyFile', {mimes: Array.isArray(editor.info.canMakeEmpty)? editor.info.canMakeEmpty : editor.mimes});
|
|
}
|
|
}
|
|
});
|
|
|
|
mimesSingle = (jQuery.uniqueSort || jQuery.unique)(mimesSingle);
|
|
mimes = (jQuery.uniqueSort || jQuery.unique)(mimes);
|
|
|
|
opts.editors = jQuery.grep(opts.editors, function(e) {
|
|
return e.disabled? false : true;
|
|
});
|
|
});
|
|
}
|
|
})
|
|
.bind('select', function() {
|
|
editors = null;
|
|
})
|
|
.bind('contextmenucreate', function(e) {
|
|
var file, editor,
|
|
single = function(editor) {
|
|
var title = self.title;
|
|
fm.one('contextmenucreatedone', function() {
|
|
self.title = title;
|
|
});
|
|
self.title = fm.escape(editor.i18n);
|
|
if (editor.info && editor.info.iconImg) {
|
|
self.contextmenuOpts = {
|
|
iconImg: fm.baseUrl + editor.info.iconImg
|
|
};
|
|
}
|
|
delete self.variants;
|
|
};
|
|
|
|
self.contextmenuOpts = void(0);
|
|
if (e.data.type === 'files' && self.enabled()) {
|
|
file = fm.file(e.data.targets[0]);
|
|
if (setEditors(file, e.data.targets.length)) {
|
|
if (Object.keys(editors).length > 1) {
|
|
if (!useStoredEditor() || !(editor = getStoredEditor(file.mime))) {
|
|
delete self.extra;
|
|
self.variants = [];
|
|
jQuery.each(editors, function(id, editor) {
|
|
self.variants.push([{ editor: editor }, editor.i18n, editor.info && editor.info.iconImg? fm.baseUrl + editor.info.iconImg : 'edit']);
|
|
});
|
|
} else {
|
|
single(editor);
|
|
self.extra = {
|
|
icon: 'menu',
|
|
node: jQuery('<span></span>')
|
|
.attr({title: fm.i18n('select')})
|
|
.on('click touchstart', function(e){
|
|
if (e.type === 'touchstart' && e.originalEvent.touches.length > 1) {
|
|
return;
|
|
}
|
|
var node = jQuery(this);
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
fm.trigger('contextmenu', {
|
|
raw: getSubMenuRaw(fm.selectedFiles(), function() {
|
|
var hashes = fm.selected();
|
|
fm.exec('edit', hashes, {editor: this});
|
|
fm.trigger('selectfiles', {files : hashes});
|
|
}),
|
|
x: node.offset().left,
|
|
y: node.offset().top
|
|
});
|
|
})
|
|
};
|
|
}
|
|
} else {
|
|
single(editors[Object.keys(editors)[0]]);
|
|
delete self.extra;
|
|
}
|
|
}
|
|
}
|
|
})
|
|
.bind('canMakeEmptyFile', function(e) {
|
|
if (e.data && e.data.resetTexts) {
|
|
var defs = fm.arrayFlip(self.options.makeTextMimes || ['text/plain']),
|
|
hides = self.getMkfileHides();
|
|
|
|
jQuery.each((fm.storage('mkfileTextMimes') || {}), function(mime, type) {
|
|
if (!defs[mime]) {
|
|
delete fm.mimesCanMakeEmpty[mime];
|
|
delete hides[mime];
|
|
}
|
|
});
|
|
fm.storage('mkfileTextMimes', null);
|
|
if (Object.keys(hides).length) {
|
|
fm.storage('mkfileHides', hides);
|
|
} else {
|
|
fm.storage('mkfileHides', null);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
this.getstate = function(select) {
|
|
var sel = this.files(select),
|
|
cnt = sel.length;
|
|
|
|
return cnt && filter(sel).length == cnt ? 0 : -1;
|
|
};
|
|
|
|
this.exec = function(select, opts) {
|
|
var fm = this.fm,
|
|
files = filter(this.files(select)),
|
|
hashes = jQuery.map(files, function(f) { return f.hash; }),
|
|
list = [],
|
|
editor = opts && opts.editor? opts.editor : null,
|
|
node = jQuery(opts && opts._currentNode? opts._currentNode : fm.cwdHash2Elm(hashes[0])),
|
|
getEditor = function() {
|
|
var dfd = jQuery.Deferred(),
|
|
storedId;
|
|
|
|
if (!editor && Object.keys(editors).length > 1) {
|
|
if (useStoredEditor() && (editor = getStoredEditor(files[0].mime))) {
|
|
return dfd.resolve(editor);
|
|
}
|
|
fm.trigger('contextmenu', {
|
|
raw: getSubMenuRaw(files, function() {
|
|
dfd.resolve(this);
|
|
}),
|
|
x: node.offset().left,
|
|
y: node.offset().top + 22,
|
|
opened: function() {
|
|
fm.one('closecontextmenu',function() {
|
|
requestAnimationFrame(function() {
|
|
if (dfd.state() === 'pending') {
|
|
dfd.reject();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
});
|
|
|
|
fm.trigger('selectfiles', {files : hashes});
|
|
|
|
return dfd;
|
|
} else {
|
|
Object.keys(editors).length > 1 && editor && store(files[0].mime, editor);
|
|
return dfd.resolve(editor? editor : (Object.keys(editors).length? editors[Object.keys(editors)[0]] : null));
|
|
}
|
|
},
|
|
dfrd = jQuery.Deferred(),
|
|
file;
|
|
|
|
if (editors === null) {
|
|
setEditors(files[0], hashes.length);
|
|
}
|
|
|
|
if (!node.length) {
|
|
node = fm.getUI('cwd');
|
|
}
|
|
|
|
getEditor().done(function(editor) {
|
|
while ((file = files.shift())) {
|
|
list.push(edit(file, (file.encoding || void(0)), editor).fail(function(error) {
|
|
error && fm.error(error);
|
|
}));
|
|
}
|
|
|
|
if (list.length) {
|
|
jQuery.when.apply(null, list).done(function() {
|
|
dfrd.resolve();
|
|
}).fail(function() {
|
|
dfrd.reject();
|
|
});
|
|
} else {
|
|
dfrd.reject();
|
|
}
|
|
}).fail(function() {
|
|
dfrd.reject();
|
|
});
|
|
|
|
return dfrd;
|
|
};
|
|
|
|
this.getMkfileHides = function() {
|
|
return fm.storage('mkfileHides') || fm.arrayFlip(self.options.mkfileHideMimes || []);
|
|
};
|
|
|
|
};
|