-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Using custom editor to edit files within elfinder
edit command supports any (I hope) WYSIWYG.
Make sure you include all required files for CodeMirror.
$('#elfinder').elfinder({
url: '//connector-elfinder.rhcloud.com/php/connector.minimal.php',
// use CodeMirror as external editor
commandsOptions: {
edit: {
mimes: [],
editors: [{
mimes: ['text/plain', 'text/html', 'text/javascript'],
load: function(textarea) {
var mimeType = this.file.mime;
return CodeMirror.fromTextArea(textarea, {
mode: mimeType,
lineNumbers: true,
indentUnit: 4
});
},
save: function(textarea, editor) {
$(textarea).val(editor.getValue());
}
}]
}
}
});Working example http://jsbin.com/boqeho/14/edit?html,js,output
// init
tinyMCE.init({});
// elfinder options
var opts = {
commandsOptions : {
edit : {
editors : [
{
mimes : ['text/html'], // add here other mimes if required
load : function(textarea) {
tinyMCE.execCommand('mceAddControl', true, textarea.id);
},
close : function(textarea, instance) {
tinyMCE.execCommand('mceRemoveControl', false, textarea.id);
},
save : function(textarea, editor) {
textarea.value = tinyMCE.get(textarea.id).selection.getContent({format : 'html'});
tinyMCE.execCommand('mceRemoveControl', false, textarea.id);
}
},
{...} // probably other editors for other mime types
]
}
}
}-
mimes- enable current editor only on next mime types -
load- calls after edit dialog shown -
close- before dialog close -
save- before send textarea content to backend
This is just example to get the idea how it works (I'm not TinyMCE guru so maybe somebody can fix the code).
In file 'elfinder.html':
- on head section add:
<script src="http://tinymce.cachefly.net/4.0/tinymce.min.js"></script>
- inside the '$().ready(function()' after elfinder options "$('#finder').elfinder({});" add:
tinymce.init({});
- inside the 'elfinder options':
commandsOptions : {
edit : {
mimes : ['text/plain', 'text/html', 'text/javascript'], //types to edit
editors : [
{
mimes : ['text/html'], //types to edit with tinyMCE
load : function(textarea) {
tinymce.execCommand('mceAddEditor', false, textarea.id);
},
close : function(textarea, instance) {
tinymce.execCommand('mceRemoveEditor', false, textarea.id);
},
save : function(textarea, editor) {
textarea.value = tinyMCE.get(textarea.id).selection.getContent({format : 'html'});
tinymce.execCommand('mceRemoveEditor', false, textarea.id);
}
},
{...} // probably other editors for other mime types
]
}
}With this method you don't have to download tinyMCE because you get it from CDN site.
Others WYSIWYGs examples are welcome.
And here is example for CKEditor4(HTML file) and Ace editor(Any text file) as custom editor on elFinder 2.1
commandsOptions : {
edit : {
editors : [
{
// CKEditor for html file
mimes : ['text/html'],
exts : ['htm', 'html', 'xhtml'],
load : function(textarea) {
$('head').append($('<script>').attr('src', '[PATH TO]/ckeditor.js'));
return CKEDITOR.replace( textarea.id, {
startupFocus : true,
fullPage: true,
allowedContent: true
});
},
close : function(textarea, instance) {
instance.destroy();
},
save : function(textarea, instance) {
textarea.value = instance.getData();
},
focus : function(textarea, instance) {
instance && instance.focus();
}
},
{
// `mimes` is not set for support everything kind of text file
load : function(textarea) {
if (typeof ace !== 'object') {
$('head').append($('<script>').attr('src', '[PATH TO]/ace.js'));
$('head').append($('<script>').attr('src', '[PATH TO]/ext-modelist.js'));
$('head').append($('<script>').attr('src', '[PATH TO]/ext-settings_menu.js'));
$('head').append($('<script>').attr('src', '[PATH TO]/ext-language_tools.js'));
}
var self = this, editor, editorBase, mode,
ta = $(textarea),
taBase = ta.parent(),
dialog = taBase.parent(),
id = textarea.id + '_ace',
ext = this.file.name.replace(/^.+\.([^.]+)|(.+)$/, '$1$2').toLowerCase(),
mimeMode = {
'text/x-php' : 'php',
'application/x-php' : 'php',
'text/html' : 'html',
'application/xhtml+xml' : 'html',
'text/javascript' : 'javascript',
'application/javascript' : 'javascript',
'text/css' : 'css',
'text/x-c' : 'c_cpp',
'text/x-csrc' : 'c_cpp',
'text/x-chdr' : 'c_cpp',
'text/x-c++' : 'c_cpp',
'text/x-c++src' : 'c_cpp',
'text/x-c++hdr' : 'c_cpp',
'text/x-shellscript' : 'sh',
'application/x-csh' : 'sh',
'text/x-python' : 'python',
'text/x-java' : 'java',
'text/x-java-source' : 'java',
'text/x-ruby' : 'ruby',
'text/x-perl' : 'perl',
'application/x-perl' : 'perl',
'text/x-sql' : 'sql',
'text/xml' : 'xml',
'application/docbook+xml' : 'xml',
'application/xml' : 'xml'
},
resize = function(){
dialog.height($(window).height() * 0.9).trigger('posinit');
taBase.height(dialog.height() - taBase.prev().outerHeight(true) - taBase.next().outerHeight(true) - 8);
};
mode = ace.require('ace/ext/modelist').getModeForPath('/' + self.file.name).name;
if (mode === 'text') {
if (mimeMode[self.file.mime]) {
mode = mimeMode[self.file.mime];
}
}
taBase.prev().append(' (' + self.file.mime + ' : ' + mode.split(/[\/\\]/).pop() + ')');
$('<div class="ui-dialog-buttonset"/>').css('float', 'left')
.append(
$('<button>TextArea</button>')
.button()
.on('click', function(){
if (ta.data('ace')) {
ta.data('ace', false);
editorBase.hide();
ta.val(editor.session.getValue()).show().focus();
$(this).find('span').text('AceEditor');
} else {
ta.data('ace', true);
editor.setValue(ta.hide().val(), -1);
editorBase.show();
editor.focus();
$(this).find('span').text('TextArea');
}
})
)
.append(
$('<button>Ace editor setting</button>')
.button({
icons: {
primary: 'ui-icon-gear',
secondary: 'ui-icon-triangle-1-e'
},
text: false
})
.on('click', function(){
editor.showSettingsMenu();
})
)
.prependTo(taBase.next());
editorBase = $('<div id="'+id+'" style="width:100%; height:100%;"/>').text(ta.val()).insertBefore(ta.hide());
ta.data('ace', true);
editor = ace.edit(id);
ace.require('ace/ext/settings_menu').init(editor);
editor.$blockScrolling = Infinity;
editor.setOptions({
theme: 'ace/theme/monokai',
mode: 'ace/mode/' + mode,
wrap: true,
enableBasicAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: false
});
editor.commands.addCommand({
name : "saveFile",
bindKey: {
win : 'Ctrl-s',
mac : 'Command-s'
},
exec: function(editor) {
self.doSave();
}
});
editor.commands.addCommand({
name : "closeEditor",
bindKey: {
win : 'Ctrl-w|Ctrl-q',
mac : 'Command-w|Command-q'
},
exec: function(editor) {
self.doCancel();
}
});
dialog.on('resize', function(){ editor.resize(); });
$(window).on('resize', function(e){
if (e.target !== this) return;
dialog.data('resizeTimer') && clearTimeout(dialog.data('resizeTimer'));
dialog.data('resizeTimer', setTimeout(function(){ resize(); }, 300));
});
resize();
editor.resize();
return editor;
},
close : function(textarea, instance) {
instance.destroy();
$(textarea).show();
},
save : function(textarea, instance) {
if ($(textarea).data('ace')) {
$(textarea).val(instance.session.getValue());
}
},
focus : function(textarea, instance) {
instance.focus();
}
}
]
}
}There are a couple of possible challenges with the code above.
The portion of the example that covers save has two issues.
First, the buttons shown to the user at the bottom of the edit dialog are "Cancel", "Save & Close" and "Save". The save code above does both a save and a close on the editor. To match the expected behavior, remove the "mceRemoveEditor" line.
Second, the example only grabs the selected area. To get the results you expect, remove ".selection" from the other line in the save function.
The results look like this:
save : function(textarea, editor, trkt ) {
textarea.value = tinymce.get(textarea.id).getContent({format : 'html'});
}
The second challenge is that part of the html cleanup TinyMCE does when loading content is to strip out everything that's not within the body tags.
So this html file:
<html>
<head>
...
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
Becomes this:
<h1>Hello World</h1>
When that's saved, the mime type elFinder gets from the server is no longer text/html so elFinder won't allow you to edit the file as html.
One way to change this behavior is to use the full elfinder js file (e.g. js/elfinder.full.js) and modify it to remember what's changed. Modify the open portion of dialog function in elFinder.prototype.commands.edit to pass the file content back to your elfinder.html file (change marked by comments):
elFinder.prototype.commands.edit = function() {
...
dialog = function(id, file, content) {
var dfrd = $.Deferred(),
...
open : function() {
fm.disable();
ta.focus();
ta[0].setSelectionRange && ta[0].setSelectionRange(0, 0);
if (ta.editor) {
// tim 4til7 wood, 17 Feb 2016
// pass the content back to the editor
// so html outside body tags can be saved later
ta.editor.instance = ta.editor.load(ta[0], content ) || null;
And then remember, as well as restore, the html that tinyMCE will strip out in elfinder.html. Before you initialize elFinder:
var _o = {};
// remember what wraps the content
_o.remember = function( content ) {
var o = _o;
var b1 = content.indexOf( '<body', 0 ) + 1;
var b2 = ( b1 > -1 ) ? content.indexOf( '>', b1 ) + 1 : -1;
var bC = ( b2 > -1 ) ? content.substring( 0, b2 ) : '';
var a1 = content.lastIndexOf('</body');
var aC = ( a1 > -1 ) ? content.substring( a1 ) : '';
o.trkr = {
//content : content,
surroundingContent : {
before : bC,
after : aC
}
};
};
Call the rememember function in your load function in commandOptions:
commandsOptions : {
edit : {
...
editors : [
...
load : function( textarea, content ) {
var o = _o;
o.remember( content );
tinymce.execCommand('mceAddEditor', false, textarea.id);
},
Then add the stripped html back in your save function:
commandsOptions : {
edit : {
...
editors : [
save : function(textarea, editor ) {
var o = _o;
textarea.value = o.trkr.surroundingContent.before
+ tinymce.get(textarea.id).getContent({format : 'html'})
+ o.trkr.surroundingContent.after;
}
Because we're passing the "fixed" html via the textarea, this is an imperfect solution. Some things, depending on browser, will still get stripped out by the browser itself. To avoid this, the rewrapping would need to be done in elFinder.