-
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.
// 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>
<head>
...
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
Becomes this:
<h1>Hello World</h1>
At that point, 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. What you'll modify, specifically, is the dialog function in elFinder.prototype.commands.edit:
elFinder.prototype.commands.edit = function() {
...
dialog = function(id, file, content) {
// tim 4til7 wood, 16 Feb 2016
// _trkr to remember what wraps the content
if( self.options.rewrapHtmlOnSave && file.mime == "text/html" ) {
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 ) : '';
} else {
var bC = '';
var aC = '';
}
var _trkr = {
//content : content,
surroundingContent : {
before : bC,
after : aC
}
};
// end remember what wraps the content
var dfrd = $.Deferred(),
And then handle adding it back. Unfortunately, I wasn't able to track down a proper place to do this in elFinder itself, but it can be done in your file (e.g. elfinder.html) by turning on the change above (with the rewrapHtmlOnSave flag) and then wrapping the edited html with the stuff that was stripped out:
commandsOptions = {
edit : {
mimes : ['text/plain', 'text/html', 'text/javascript', 'text/css'], //types to edit
rewrapHtmlOnSave : true,
editors : [
{
...
save : function(textarea, editor, _trkr ) {
textarea.value = _trkr.surroundingContent.before
+ tinymce.get(textarea.id).getContent({format : 'html'})
+ _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.
An alternate solution is to handle almost everything in your code by just modifying the open command in the elfinder.full.js file (just after the elFinder.prototype.commands.edit shown above) by adding "content" as a second parameter to ta.editor.load:
open : function() {
fm.disable();
ta.focus();
ta[0].setSelectionRange && ta[0].setSelectionRange(0, 0);
if (ta.editor) {
ta.editor.instance = ta.editor.load( ta[0], content ) || null;
ta.editor.focus(ta[0], ta.editor.instance);