(function(editors, elFinder) {
if (typeof define === 'function' && define.amd) {
define(['elfinder'], editors);
var optEditors = elFinder.prototype._options.commandsOptions.edit.editors;
elFinder.prototype._options.commandsOptions.edit.editors = optEditors.concat(editors(elFinder));
getfile = window.location.search.match(/getfile=([a-z]+)/),
useRequire = elFinder.prototype.hasRequire,
dng: 'image/x-adobe-dng',
ppm: 'image/x-portable-pixmap',
psd: 'image/vnd.adobe.photoshop',
pxd: 'image/x-pixlr-data',
sketch: 'application/x-sketch',
emf: 'application/x-msmetafile'
getExtention = function(mime, fm, jpeg) {
mime2ext = fm.arrayFlip(ext2mime);
var ext = mime2ext[mime] || fm.mimeTypes[mime];
changeImageType = function(src, toMime) {
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d'),
var url = canvas.toDataURL(toMime),
if (m = url.match(/^data:([a-z0-9]+\/[a-z0-9.+-]+)/i)) {
if (mime.toLowerCase() === toMime.toLowerCase()) {
dfd.resolve(canvas.toDataURL(toMime), canvas);
$(img).on('load', function() {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
}).on('error', function () {
initImgTag = function(id, file, content, fm) {
var node = $(this).children('img:first').data('ext', getExtention(file.mime, fm)),
spnr = $('<div class="elfinder-edit-spinner elfinder-edit-image"></div>')
.html('<span class="elfinder-spinner-text">' + fm.i18n('ntfloadimg') + '</span><span class="elfinder-spinner"></span>')
node.attr('id', id+'-img')
.attr('src', url || content)
.css({'height':'', 'max-width':'100%', 'max-height':'100%', 'cursor':'pointer'})
.data('loading', function(done) {
var btns = node.closest('.elfinder-dialog').find('button,.elfinder-titlebar-button');
btns.prop('disabled', !done)[done? 'removeClass' : 'addClass']('ui-state-disabled');
node.css('opacity', done? '' : '0.3');
spnr[done? 'hide' : 'show']();
if (!content.match(/^data:/)) {
fm.openUrl(file.hash, false, function(v) {
node.attr('_src', content);
imgBase64 = function(node, mime) {
var style = node.attr('style'),
// reset css for getting image size
canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
node.attr('style', style);
canvas.getContext('2d').drawImage(img, 0, 0);
data = canvas.toDataURL(mime);
iframeClose = function(ifm) {
dfd = $.Deferred().always(function() {
tm = setTimeout(function() {
src = base.contentWindow.location.href;
cnt = 20, // 500ms * 20 = 10sec wait
// check getfile callback function
if (getfile === 'ckeditor') {
elFinder.prototype._options.getFileCallback = function(file, fm) {
window.opener.CKEDITOR.tools.callFunction((function() {
var reParam = new RegExp('(?:[?&]|&)CKEditorFuncNum=([^&]+)', 'i'),
match = window.location.search.match(reParam);
return (match && match.length > 1) ? match[1] : '';
})(), fm.convAbsUrl(file.url));
// tui.image-editor - https://github.com/nhnent/tui.image-editor
name: 'TUI Image Editor',
iconImg: 'img/editor-icons.png 0 -48',
title: 'TOAST UI Image Editor',
link: 'http://ui.toast.com/tui-image-editor/'
mimes : ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml', 'image/x-ms-bmp'],
html : '<div class="elfinder-edit-imageeditor"><canvas></canvas></div>',
// called on initialization of elFinder cmd edit (this: this editor's config object)
setup : function(opts, fm) {
if (fm.UA.ltIE8 || fm.UA.Mobile) {
this.opts = Object.assign({
}, opts.extraOptions.tuiImgEditOpts || {}, {
iconsPath : fm.baseUrl + 'img/tui-',
if (!fm.isSameOrigin(this.opts.iconsPath)) {
fm.debug('warning', 'Setting `commandOptions.edit.extraOptions.tuiImgEditOpts.iconsPath` MUST follow the same origin policy.');
// Initialization of editing node (this: this editors HTML node)
init : function(id, file, content, fm) {
this.data('url', content);
ver = self.confObj.opts.version,
init = function(editor) {
bParent = $base.parent(),
opts = self.confObj.opts,
iconsPath = opts.iconsPath,
tmpContainer = $('<div class="tui-image-editor-container">').appendTo(bParent),
$('<div class="tui-image-editor-submenu"></div>').appendTo(tmpContainer),
$('<div class="tui-image-editor-controls"></div>').appendTo(tmpContainer)
iEditor = new editor(base, {
menuBarPosition: 'bottom'
cssMaxWidth: Math.max(300, bParent.width()),
cssMaxHeight: Math.max(200, bParent.height() - (tmpDiv[0].height() + tmpDiv[1].height() + 3 /*margin*/)),
canvas = $base.find('canvas:first').get(0),
if (typeof v !== 'undefined') {
w = parseInt(c.attr('width')),
h = parseInt(c.attr('height')),
mw = parseInt(c.css('max-width')) + Number(v);
z = Math.round(mw / w * 100);
// Control zoom button of TUI Image Editor
iEditor.stopDrawingMode();
iEditor.resizeCanvasDimension({width: mw, height: mh});
// continually change more
zup = $('<span class="ui-icon ui-icon-plusthick"></span>').data('val', 10),
zdown = $('<span class="ui-icon ui-icon-minusthick"></span>').data('val', -10),
per = $('<button></button>').css('width', '4em').text('%').attr('title', '100%').data('val', 0),
quty, qutyTm, zoomTm, zoomMore;
$base.removeData('url').data('mime', self.file.mime);
if (self.file.mime === 'image/jpeg') {
$base.data('quality', fm.storage('jpgQuality') || fm.option('jpgQuality'));
quty = $('<input type="number" class="ui-corner-all elfinder-resize-quality elfinder-tabstop"/>')
.attr('title', '1 - 100')
.on('change', function() {
$base.data('quality', q);
qutyTm && cancelAnimationFrame(qutyTm);
qutyTm = requestAnimationFrame(function() {
canvas.toBlob(function(blob) {
blob && quty.next('span').text(' (' + fm.formatSize(blob.size) + ')');
}, 'image/jpeg', Math.max(Math.min(q, 100), 1) / 100);
.val($base.data('quality'));
$('<div class="ui-dialog-buttonset elfinder-edit-extras elfinder-edit-extras-quality"></div>')
$('<span>').html(fm.i18n('quality') + ' : '), quty, $('<span></span>')
.prependTo($base.parent().next());
} else if (self.file.mime === 'image/svg+xml') {
$base.closest('.ui-dialog').trigger('changeType', {
$('<div class="ui-dialog-buttonset elfinder-edit-extras"></div>')
.attr('title', fm.i18n('scale'))
.on('click', 'span,button', function() {
zoom($(this).data('val'));
.on('mousedown mouseup mouseleave', 'span', function(e) {
zoomTm && clearTimeout(zoomTm);
if (e.type === 'mousedown') {
zoomTm = setTimeout(function() {
zoom($(e.target).data('val'));
.prependTo($base.parent().next());
iEditor.on('redoStackChanged undoStackChanged', function() {
// ZOOM controls of TUI Image Editor
tuiZoomCtrls = $base.find('.tie-btn-zoomIn,.tie-btn-zoomOut,.tie-btn-hand');
// show color slider (maybe TUI-Image-Editor's bug)
// see https://github.com/nhn/tui.image-editor/issues/153
$base.find('.tui-colorpicker-palette-container').on('click', '.tui-colorpicker-palette-preview', function() {
$(this).closest('.color-picker-control').height('auto').find('.tui-colorpicker-slider-container').toggle();
$base.on('click', function() {
$base.find('.tui-colorpicker-slider-container').hide();
if (!self.confObj.editor) {
cdns.tui + '/tui-color-picker/latest/tui-color-picker.css',
cdns.tui + '/tui-image-editor/'+ver+'/tui-image-editor.css'
'fabric/dist/fabric.require' : cdns.fabric + '/fabric.require.min', // for fabric < 2.0.1
'fabric' : cdns.fabric + '/fabric.min', // for fabric >= 2.0.1
'tui-code-snippet' : cdns.tui + '/tui.code-snippet/latest/tui-code-snippet.min',
'tui-color-picker' : cdns.tui + '/tui-color-picker/latest/tui-color-picker.min',
'tui-image-editor' : cdns.tui + '/tui-image-editor/'+ver+'/tui-image-editor.min'
require(['tui-image-editor'], function(ImageEditor) {
loader.resolve(ImageEditor);
cdns.fabric + '/fabric.min.js',
cdns.tui + '/tui.code-snippet/latest/tui-code-snippet.min.js'
cdns.tui + '/tui-color-picker/latest/tui-color-picker.min.js'
cdns.tui + '/tui-image-editor/'+ver+'/tui-image-editor.min.js'
loader.resolve(window.tui.ImageEditor);
loader.done(function(editor) {
self.confObj.editor = editor;
init(self.confObj.editor);
getContent : function(base) {
var editor = this.editor,
quality = $base.data('quality');
if ($base.data('mime') === 'image/jpeg') {
quality = quality || fm.storage('jpgQuality') || fm.option('jpgQuality');
quality = Math.max(0.1, Math.min(1, quality / 100));
return editor.instance.toDataURL({
format: getExtention($base.data('mime'), fm, true),
quality = $base.data('quality'),
hash = $base.data('hash'),
this.instance.deactivateAll();
if (typeof quality !== 'undefined') {
this.fm.storage('jpgQuality', quality);
file = this.fm.file(hash);
$base.data('mime', file.mime);
// Photopea advanced image editor
iconImg : 'img/editor-icons.png 0 -160',
arrayBufferContent: true,
// Disable file types that cannot be saved on Photopea.
canMakeEmpty: ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml', 'image/x-ms-bmp', 'image/tiff', /*'image/x-adobe-dng',*/ 'image/webp', /*'image/x-xcf',*/ 'image/vnd.adobe.photoshop', 'application/pdf', 'image/x-portable-pixmap', 'image/x-sketch', 'image/x-icon', 'image/vnd-ms.dds', /*'application/x-msmetafile'*/],
link: 'https://www.photopea.com/learn/'
mimes : ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml', 'image/x-ms-bmp', 'image/tiff', 'image/x-adobe-dng', 'image/webp', 'image/x-xcf', 'image/vnd.adobe.photoshop', 'application/pdf', 'image/x-portable-pixmap', 'image/x-sketch', 'image/x-icon', 'image/vnd-ms.dds', 'application/x-msmetafile'],
html : '<iframe style="width:100%;height:100%;border:none;"></iframe>',
// setup on elFinder bootup
setup : function(opts, fm) {
if (fm.UA.IE || fm.UA.Mobile) {
// Initialization of editing node (this: this editors HTML node)
init : function(id, file, dum, fm) {
var orig = 'https://www.photopea.com',
//.css('box-sizing', 'border-box')