praiadeseselle/wire/modules/Inputfield/InputfieldTinyMCE/InputfieldTinyMCE.js

950 lines
24 KiB
JavaScript
Raw Normal View History

/**
* InputfieldTinyMCE.js
*
* ProcessWire 3.x, Copyright 2022 by Ryan Cramer
* https://processwire.com
*
* TinyMCE 6.x, Copyright (c) 2022 Ephox Corporation DBA Tiny Technologies, Inc.
* https://www.tiny.cloud/docs/tinymce/6/
*
*/
/**
* Handler for image uploads
*
* @param blobInfo
* @param progress
* @returns {Promise<unknown>}
*
*/
var InputfieldTinyMCEUploadHandler = (blobInfo, progress) => new Promise((resolve, reject) => {
var editor = tinymce.activeEditor;
var $inputfield = $('#' + editor.id).closest('.InputfieldTinyMCE');
var imageFieldName = $inputfield.attr('data-upload-field');
var $imageInputfield = $('#wrap_Inputfield_' + imageFieldName);
var pageId = $inputfield.attr('data-upload-page');
var uploadUrl = ProcessWire.config.urls.admin + 'page/edit/?id=' + pageId + '&InputfieldFileAjax=1&ckeupload=1';
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.upload.onprogress = (e) => { progress(e.loaded / e.total * 100); };
xhr.open('POST', uploadUrl);
xhr.onload = () => {
if(xhr.status === 403) {
reject({ message: 'HTTP Error: ' + xhr.status, remove: true });
return;
} else if(xhr.status < 200 || xhr.status >= 300) {
reject('HTTP Error: ' + xhr.status);
return;
}
var response = JSON.parse(xhr.responseText);
if(!response) {
reject('Invalid JSON in response: ' + xhr.responseText);
return;
}
resolve(response.url);
};
xhr.onerror = () => {
reject('Image upload failed due to a XHR Transport error. Code: ' + xhr.status);
};
$imageInputfield.trigger('pwimageupload', {
'name': blobInfo.filename(),
'file': blobInfo.blob(),
'xhr': xhr
});
});
/**
* InputfieldTinyMCE main
*
*/
var InputfieldTinyMCE = {
/**
* Debug mode?
*
*/
debug: false,
/**
* Are document events attached?
*
*/
eventsReady: false,
/**
* Is document ready?
*
*/
isDocumentReady: false,
/**
* Editor selectors to init at document ready
*
*/
editorIds: [],
/**
* Are we currently processing an editor init? (bool or string)
*
*/
initializing: false,
/**
* Allow lazy loaded editor init()? (adjusted by this class at runtime)
*
*/
allowLazy: true,
/**
* Ccallback functions
*
*/
callbacks: { onSetup: [], onConfig: [], onReady: [] },
/**
* Recognized class names
*
*/
cls: {
lazy: 'InputfieldTinyMCELazy',
inline: 'InputfieldTinyMCEInline',
normal: 'InputfieldTinyMCENormal',
loaded: 'InputfieldTinyMCELoaded',
editor: 'InputfieldTinyMCEEditor',
placeholder: 'InputfieldTinyMCEPlaceholder'
},
/**
* Console log
*
* @param a
* @param b
*
*/
log: function(a, b) {
if(!this.debug) return;
if(typeof b !== 'undefined') {
if(typeof a === 'string') a = 'TinyMCE ' + a;
console.log(a, b);
} else {
console.log('TinyMCE', a);
}
},
/**
* Add a setup callback function
*
* ~~~~~
* InputfieldTinyMCE.onSetup(function(editor) {
* // ...
* });
* ~~~~~
*
* @param callback
*
*/
onSetup: function(callback) {
this.callbacks.onSetup.push(callback);
},
/**
* Add a config callback function
*
* ~~~~~
* InputfieldTinyMCE.onConfig(function(settings, $editor, $inputfield) {
* // ...
* });
* ~~~~~
*
* @param callback
*
*/
onConfig: function(callback) {
this.callbacks.onConfig.push(callback);
},
/**
* Add a ready callback function
*
* ~~~~~
* InputfieldTinyMCE.onReady(function(editor) {
* // ...
* });
* ~~~~~
*
* @param callback
*
*/
onReady: function(callback) {
this.callbacks.onReady.push(callback);
},
/**
* Set editor initializing state
*
* @param {boolean|string} initializing Boolean or editor ID
*
*/
setInitializing: function(initializing) {
this.initializing = initializing;
},
/**
* Is editor initializing?
*
* @returns boolean|string False or editor id (string)
*
*/
isInitializing: function() {
return this.initializing;
},
/**
* Modify image dimensions
*
* @param editor
* @param img
* @param width
*
*/
imageResized: function(editor, img, width) {
var t = this;
var src = img.src;
var hidpi = img.className.indexOf('hidpi') > -1 ? 1 : 0;
var basename = src.substring(src.lastIndexOf('/')+1);
var path = src.substring(0, src.lastIndexOf('/')+1);
var dot1 = basename.indexOf('.');
var dot2 = basename.lastIndexOf('.');
var crop = '';
if(basename.indexOf('-cropx') > -1 || basename.indexOf('.cropx') > -1) {
// example: gonzo_the_great.205x183-cropx38y2-is.jpg
// use image file as-is
// @todo re-crop and resize from original?
} else if(dot1 !== dot2) {
// extract any existing resize data present to get original file
// i.e. file.123x456-is-hidpi.jpg => file.jpg
var basename2 = basename.substring(0, dot1) + basename.substring(dot2);
src = path + basename2;
}
var url = ProcessWire.config.urls.admin + 'page/image/resize' +
'?json=1' +
'&width=' + width +
'&hidpi=' + hidpi +
'&file=' + src;
t.log('Resizing image to width=' + width, url);
jQuery.getJSON(url, function(data) {
editor.dom.setAttrib(img, 'src', data.src);
// editor.dom.setAttrib(img, 'width', data.width);
// editor.dom.setAttrib(img, 'height', data.height);
t.log('Resized image to width=' + data.width, data.src);
});
},
/**
* Called when an element has an align class applied to it
*
* This function ensures only 1 align class is applied at a time.
*
* @param editor
*
*/
elementAligned: function(editor) {
var selection = editor.selection;
var node = selection.getNode();
var className = node.className;
var n;
// if only one align class then return now
if(className.indexOf('align') === className.lastIndexOf('align')) return;
var alignNames = [];
var classNames = className.split(' ');
for(n = 0; n < classNames.length; n++) {
if(classNames[n].indexOf('align') === 0) {
alignNames.push(classNames[n]);
}
}
// pop off last align class, which we will keep
alignNames.pop();
for(n = 0; n < alignNames.length; n++) {
className = className.replace(alignNames[n], '');
}
node.className = className.trim();
},
/**
* Lazy load placeholder click event
*
* @param e
* @returns {boolean}
*
*/
clickPlaceholder: function(e) {
var t = InputfieldTinyMCE;
var $placeholder = jQuery(this);
var $textarea = $placeholder.next('textarea');
$placeholder.remove();
t.log('placeholderClick', $placeholder);
if($textarea.length) t.init('#' + $textarea.attr('id'), 'event.' + e.type);
return false;
},
/**
* Init/populate lazy load placeholder elements within given target
*
* @param $placeholders Placeholders are wrapper that has them within
*
*/
initPlaceholders: function($placeholders) {
if(!$placeholders.length) return;
var t = this;
var $item = $placeholders.first();
if(!$item.hasClass(t.cls.placeholder)) $placeholders = $item.find('.' + t.cls.placeholder);
$placeholders.each(function() {
var $placeholder = jQuery(this);
var $textarea = $placeholder.next('textarea');
t.log('initPlaceholder', $placeholder);
$placeholder.children('.mce-content-body').html($textarea.val());
$placeholder.on('click touchstart', t.clickPlaceholder);
});
},
/**
* Init callback function
*
* @param editor
* @param features
*
*/
editorReady: function(editor, features) {
var t = this;
var $editor = jQuery('#' + editor.id);
var $inputfield = $editor.closest('.InputfieldTinyMCE');
var inputTimeout = null;
if(!$inputfield.length) $inputfield = $editor.closest('.Inputfield');
function changed() {
if(inputTimeout) clearTimeout(inputTimeout);
inputTimeout = setTimeout(function() {
$inputfield.trigger('change');
}, 500);
}
editor.on('Dirty', function() { changed() });
editor.on('SetContent', function() { changed() });
editor.on('input', function() { changed() });
// for image resizes
if(features.indexOf('imgResize') > -1) {
editor.on('ObjectResized', function(e, data) {
// @todo account for case where image in figure is resized, and figure needs its width updated with the image
if(e.target.nodeName === 'IMG') {
t.imageResized(editor, e.target, e.width);
changed();
}
});
}
for(var n = 0; n < t.callbacks.onReady.length; n++) {
t.callbacks.onReady[n](editor);
}
editor.on('ExecCommand', function(e, f) {
if(e.command === 'mceFocus') return;
t.log('command: ' + e.command, e);
if(e.command === 'mceToggleFormat') {
if(e.value && e.value.indexOf('align') === 0) {
var editor = this;
t.elementAligned(editor);
}
changed();
}
});
/*
* uncomment to show inline init effect
if(jQuery.ui) {
if($editor.hasClass('InputfieldTinyMCEInline')) {
$editor.effect('highlight', {}, 500);
}
}
*/
/*
editor.on('ResizeEditor', function(e) {
// editor resized
t.log('ResizeEditor');
});
*/
},
/**
* config.setup handler function
*
* @param editor
*
*/
setupEditor: function(editor) {
var t = InputfieldTinyMCE;
var $editor = jQuery('#' + editor.id);
if($editor.hasClass(t.cls.loaded)) {
t.log('mceInit called on input that is already loaded', editor.id);
} else {
$editor.addClass(t.cls.loaded);
}
for(var n = 0; n < t.callbacks.onSetup.length; n++) {
t.callbacks.onSetup[n](editor);
}
/*
editor.on('init', function() {
// var n = performance.now();
// t.log(editor.id + ': ' + (n - mceTimer) + ' ms');
});
*/
/*
editor.on('Load', function() {
// t.log('iframe loaded', editor.id);
});
*/
},
/**
* Destroy given editors
*
* @param $editors
*
*/
destroyEditors: function($editors) {
var t = this;
$editors.each(function() {
var $editor = jQuery(this);
if(!$editor.hasClass(t.cls.loaded)) return;
var editorId = $editor.attr('id');
var editor = tinymce.get(editorId);
$editor.removeClass(t.cls.loaded).removeClass(t.cls.lazy);
t.log('destroyEditor', editor.id);
// $editor.css('display', 'none');
editor.destroy();
});
},
/**
* Destroy editors in given wrapper
*
* @param $wrapper
*
*/
destroyEditorsIn($wrapper) {
this.destroyEditors($wrapper.find('.' + this.cls.loaded));
},
/**
* Reset given editors (destroy and re-init)
*
* @param $editors
*
*/
resetEditors: function($editors) {
var t = this;
t.allowLazy = false;
$editors.each(function() {
var $editor = jQuery(this);
if(!$editor.hasClass(t.cls.loaded)) return;
var editorId = $editor.attr('id');
var editor = tinymce.get(editorId);
editor.destroy();
$editor.removeClass(t.cls.loaded);
// t.init('#' + editorId, 'resetEditors');
});
t.initEditors($editors);
t.allowLazy = true;
},
/**
* Initialize given jQuery object editors
*
* @param $editors
*
*/
initEditors: function($editors) {
var t = this;
$editors.each(function() {
var $editor = jQuery(this);
var editorId = $editor.attr('id');
if($editor.hasClass(t.cls.loaded)) return;
//t.log('init', id);
t.init('#' + editorId, 'initEditors');
});
},
/**
* Find and initialize editors within a wrapper
*
* @param $wrapper
* @param selector Optional
*
*/
initEditorsIn: function($wrapper, selector) {
if(typeof selector === 'undefined') {
selector =
'.' + this.cls.lazy + ':visible, ' +
'.' + this.cls.editor +
':not(.' + this.cls.loaded + ')' +
':not(.' + this.cls.lazy + ')' +
':not(.' + this.cls.inline + ')';
}
var $placeholders = $wrapper.find('.' + this.cls.placeholder);
var $editors = $wrapper.find(selector);
if($placeholders.length) this.initPlaceholders($placeholders);
if($editors.length) this.initEditors($editors);
},
/**
* Get config (config + custom settings)
*
* @param $editor Editor Textarea (Regular) or div (Inline)
* @param $inputfield Editor Wrapping .Inputfield element
* @param features
* @returns {{}}
*
*/
getConfig: function($editor, $inputfield, features) {
var configName = $inputfield.attr('data-configName');
var globalConfig = ProcessWire.config.InputfieldTinyMCE;
var settings = globalConfig.settings.default;
var namedSettings = globalConfig.settings[configName];
var dataSettings = $inputfield.attr('data-settings');
if(typeof settings === 'undefined') {
settings = {};
} else {
settings = jQuery.extend(true, {}, settings);
}
if(typeof namedSettings === 'undefined') {
this.log('Cant find ProcessWire.config.InputfieldTinyMCE.settings.' + configName);
} else {
jQuery.extend(settings, namedSettings);
}
if(typeof dataSettings === 'undefined') {
dataSettings = null;
} else if(dataSettings && dataSettings.length > 2) {
dataSettings = JSON.parse(dataSettings);
jQuery.extend(settings, dataSettings);
}
if(settings.inline) settings.content_css = null; // we load this separately for inline mode
for(var n = 0; n < this.callbacks.onConfig.length; n++) {
this.callbacks.onConfig[n](settings, $editor, $inputfield);
}
if(features.indexOf('pasteFilter') > -1) {
if(globalConfig.pasteFilter === 'text') {
settings.paste_as_text = true;
} else if(globalConfig.pasteFilter.length) {
settings.paste_preprocess = this.pastePreprocess;
}
}
/*
settings.paste_postprocess = function(editor, args) {
console.log(args.node);
args.node.setAttribute('id', '42');
};
*/
return settings;
},
/**
* Pre-process paste
*
* @param editor
* @param args
*
*/
pastePreprocess: function(editor, args) {
var t = InputfieldTinyMCE;
var allow = ',' + ProcessWire.config.InputfieldTinyMCE.pasteFilter + ',';
var regexTag = /<([a-z0-9]+)([^>]*)>/gi;
var regexAttr = /([-_a-z0-9]+)=["']([^"']*)["']/gi;
var html = args.content;
var matchTag, matchAttr;
var removals = [];
var finds = [];
var replaces = [];
var startLength = html.length;
if(args.internal) {
t.log('Skipping pasteFilter for interal copy/paste');
return; // skip filtering for internal copy/paste operations
}
if(allow === ',text,') {
t.log('Skipping pasteFilter since paste_as_text settingw will be used');
return; // will be processed by paste_as_text setting
}
while((matchTag = regexTag.exec(html)) !== null) {
var tagOpen = matchTag[0]; // i.e. <strong>, <img src="..">, <h2>, etc.
var tagName = matchTag[1]; // i.e. 'strong', 'img', 'h2', etc.
var tagClose = '</' + tagName + '>'; // i.e. </strong>, </h2>
var tagAttrs = matchTag[2]; // i.e. 'src="a.jpg" alt="alt"'
var allowAttrs = false;
// first see if we can match a tag replacement
var find = ',' + tagName + '='; // i.e. ',b=strong'
var pos = allow.indexOf(find);
if(pos > -1) {
// tag replacement
var rule = allow.substring(pos + 1); // i.e. b=strong,and,more
rule = rule.substring(0, rule.indexOf(',')); // i.e. b=strong
rule = rule.split('=');
var replaceTag = rule[1];
finds.push(tagOpen);
replaces.push('<' + replaceTag + '>');
finds.push(tagClose);
replaces.push('</' + replaceTag + '>');
}
if(allow.indexOf(',' + tagName + '[') > -1) {
// tag appears in whitelist with attributes
allowAttrs = true;
} else if(allow.indexOf(',' + tagName + ',') === -1) {
// tag does not appear in whitelist
removals.push(tagOpen);
removals.push(tagClose);
continue;
} else {
// tag appears in whitelist (no attributes)
}
if(tagAttrs.length) {
// tag has attributes
if(!allowAttrs) {
// attributes not allowed, replace with non-attribute tag
finds.push(tagOpen);
replaces.push('<' + tagName + '>');
continue;
}
} else {
// no attributes, nothing further to do
continue;
}
var attrRemoves = [];
while((matchAttr = regexAttr.exec(tagAttrs)) !== null) {
var attrStr = matchAttr[0]; // i.e. alt="hello"
var attrName = matchAttr[1]; // i.e. alt
var attrVal = matchAttr[2]; // i.e. hello
if(allow.indexOf(',' + tagName + '[' + attrName + ']') > -1) {
// matches whitelist of tag with allowed attribute
} else if(allow.indexOf(',' + tagName + '[' + attrName + '=' + attrVal + ']') > -1) {
// matches whitelist of tag with allowed attribute having allowed value
} else {
// attributes do not match whitelist
attrRemoves.push(attrStr);
}
}
if(attrRemoves.length) {
var replaceOpenTag = tagOpen;
for(var n = 0; n < attrRemoves.length; n++) {
replaceOpenTag = replaceOpenTag.replace(attrRemoves[n], '');
}
finds.push(tagOpen);
replaces.push(replaceOpenTag);
}
}
// console.log('removals', removals);
for(var n = 0; n < removals.length; n++) {
html = html.replace(removals[n], '');
}
for(var n = 0; n < finds.length; n++) {
html = html.replace(finds[n], replaces[n]);
// console.log(finds[n] + ' => ' + replaces[n]);
}
while(html.indexOf('< ') > -1) html = html.replace('< ', '<');
while(html.indexOf(' >') > -1) html = html.replace(' >', '>');
while(html.indexOf('&nbsp;') > -1) html = html.replace('&nbsp;', ' ', html);
html = html.replaceAll(/<([-a-z0-9]+)[^>]*>\s*<\/\1>/ig, ''); // remove empty tags
t.log('Completed pasteFilter ' + startLength + ' => ' + html.length + ' bytes');
args.content = html;
},
/**
* Document ready events
*
*/
initDocumentEvents: function() {
var t = this;
jQuery(document)
.on('click mouseover focus touchstart', '.' + t.cls.inline + ':not(.' + t.cls.loaded + ')', function(e) {
// we initialize the inline editor only when moused over
// so that a page can handle lots of editors at once without
// them all being active
if(InputfieldTinyMCE.isInitializing() !== false) return;
t.init('#' + this.id, 'event.' + e.type);
})
.on('image-edit sort-stop', '.InputfieldTinyMCE', function(e) {
// all "normal" editors that are also "loaded"
var $editors = jQuery(this).find('.' + t.cls.normal + '.' + t.cls.loaded);
if($editors.length) {
t.log(e.type + '.resetEditors', $editors);
// force all loaded to reset
t.resetEditors($editors);
}
// all "normal" non-placeholder editors that are not yet "loaded"
var $editors = jQuery(this).find('.' + t.cls.normal + ':not(.' + t.cls.loaded + '):not(.' + t.cls.placeholder + ')');
if($editors.length) {
t.log(e.type + '.initEditors', $editors);
t.initEditors($editors);
}
})
.on('reload', '.Inputfield', function() {
var $inputfield = jQuery(this);
var $editors = $inputfield.find('.' + t.cls.loaded);
if($editors.length) {
t.log('reload', $inputfield.attr('id'));
t.destroyEditors($editors);
}
})
.on('reloaded', '.Inputfield', function(e) {
t.initEditorsIn(jQuery(this));
/*
var $inputfield = $(this);
var s = '.' + t.cls.editor + ':not(.' + t.cls.loaded + '):not(.' + t.cls.lazy + ')';
var $editors = $inputfield.find(s);
if($editors.length) {
t.log(e.type, $inputfield.attr('id'));
t.initEditors($editors);
}
var $placeholders = $inputfield.find('.' + t.cls.placeholder);
if($placeholders.length) t.initPlaceholders($placeholders);
return false;
*/
})
.on('sortstop', function(e) {
var $editors = jQuery(e.target).find('.' + t.cls.loaded);
if($editors.length) {
t.log('sortstop');
t.resetEditors($editors);
}
})
.on('opened', '.Inputfield', function(e) {
t.initEditorsIn(jQuery(this));
})
.on('clicklangtab wiretabclick', function(e, $newTab) {
t.initEditorsIn($newTab);
/*
var $placeholders = $newTab.find('.' + t.cls.placeholder);
var $editors = $newTab.find('.' + t.cls.lazy + ':visible');
t.log(e.type, $newTab.attr('id'));
if($placeholders.length) t.initPlaceholders($placeholders);
if($editors.length) t.initEditors($editors);
*/
});
/*
.on('sortstart', function() {
var $editors = $(e.target).find('.InputfieldTinyMCELoaded');
$editors.each(function() {
var $editor = $(this);
t.log('sortstart', $editor.attr('id'));
});
})
*/
this.eventsReady = true;
},
/**
* Document ready
*
*/
documentReady: function() {
this.debug = ProcessWire.config.InputfieldTinyMCE.debug;
this.isDocumentReady = true;
this.log('documentReady', this.editorIds);
while(this.editorIds.length > 0) {
var editorId = this.editorIds.shift();
this.init(editorId, 'documentReady');
}
this.initDocumentEvents();
var $placeholders = jQuery('.' + this.cls.placeholder + ':visible');
if($placeholders.length) this.initPlaceholders($placeholders);
if(this.debug) {
this.log('qty',
'normal=' + jQuery('.' + this.cls.normal).length + ', ' +
'inline=' + jQuery('.' + this.cls.inline).length + ', ' +
'lazy=' + jQuery('.' + this.cls.lazy).length + ', ' +
'loaded=' + jQuery('.' + this.cls.loaded).length + ', ' +
'placeholders=' + $placeholders.length
);
}
},
/**
* Initialize an editor
*
* ~~~~~
* InputfieldTinyMCE.init('#my-textarea');
* ~~~~~
*
* @param id Editor id or selector string
* @param caller Optional name of caller (for debugging purposes)
* @returns {boolean}
*
*/
init: function(id, caller) {
var $editor, config, features, $inputfield, isFront = false,
selector, isLazy, useLazy, _id = id, t = this;
if(!this.isDocumentReady) {
this.editorIds.push(id);
return true;
}
this.setInitializing(id);
caller = (t.debug && typeof caller !== 'undefined' ? ' (caller=' + caller + ')' : '');
if(typeof id === 'string') {
// literal id or selector string
if(id.indexOf('#') === 0 || id.indexOf('.') === 0) {
selector = id;
id = '';
} else {
selector = '#' + id;
}
$editor = jQuery(selector);
if(id === '') id = $editor.attr('id');
} else if(typeof id === 'object') {
// element or jQuery element
if(id instanceof jQuery) {
$editor = id;
} else {
$editor = jQuery(id);
}
id = $editor.attr('id');
selector = '#' + id;
}
if(!$editor.length) {
console.error('Cannot find element to init TinyMCE: ' + _id);
this.setInitializing(false);
return false;
}
if(id.indexOf('Inputfield_') === 0) {
$inputfield = jQuery('#wrap_' + id);
} else if($editor.hasClass('pw-edit-copy')) {
$inputfield = $editor.closest('.pw-edit-InputfieldTinyMCE');
isFront = true; // PageFrontEdit
} else {
$inputfield = jQuery('#wrap_Inputfield_' + id);
}
if(!$inputfield.length) {
$inputfield = $editor.closest('.InputfieldTinyMCE');
if(!$inputfield.length) $inputfield = $editor.closest('.Inputfield');
}
features = $inputfield.attr('data-features');
if(typeof features === 'undefined') features = '';
useLazy = t.allowLazy && features.indexOf('lazyMode') > -1;
isLazy = $editor.hasClass(t.cls.lazy);
if(useLazy && !isLazy && !$editor.is(':visible') && !$editor.hasClass(t.cls.inline)) {
$editor.addClass(t.cls.lazy);
this.log('init-lazy', id + caller);
this.setInitializing(false);
return true;
} else if(isLazy) {
$editor.removeClass(t.cls.lazy);
}
this.log('init', id + caller);
config = this.getConfig($editor, $inputfield, features);
config.selector = selector;
config.setup = this.setupEditor;
config.init_instance_callback = function(editor) {
t.setInitializing('');
setTimeout(function() { if(t.isInitializing() === '') t.setInitializing(false); }, 100);
t.log('ready', editor.id);
t.editorReady(editor, features);
}
if(isFront) {
// disable drag/drop image uploads when PageFrontEdit
config.images_upload_url = '';
config.automatic_uploads = false;
config.paste_data_images = false;
} else if(features.indexOf('imgUpload') > -1) {
config.images_upload_handler = InputfieldTinyMCEUploadHandler;
}
tinymce.init(config);
return true;
}
};
jQuery(document).ready(function() {
InputfieldTinyMCE.documentReady();
});
/*
InputfieldTinyMCE.onSetup(function(editor) {
editor.ui.registry.addButton('hello', {
icon: 'user',
text: 'Hello',
onAction: function() {
editor.insertContent('Hello World!')
}
});
});
*/