/** * EGroupware eTemplate2 - JS widget for HTML editing * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @package etemplate * @subpackage api * @link http://www.egroupware.org * @author Hadi Nategh * @copyright Hadi Nategh * @version $Id$ */ /*egw:uses jsapi.jsapi; // Needed for egw_seperateJavaScript /vendor/tinymce/tinymce/tinymce.min.js; et2_core_editableWidget; */ /** * @augments et2_inputWidget */ var et2_htmlarea = (function(){ "use strict"; return et2_editableWidget.extend([et2_IResizeable], { attributes: { mode: { 'name': 'Mode', 'description': 'One of {ascii|simple|extended|advanced}', 'default': '', 'type': 'string' }, height: { 'name': 'Height', 'default': et2_no_init, 'type': 'string' }, width: { 'name': 'Width', 'default': et2_no_init, 'type': 'string' }, value: { name: "Value", description: "The value of the widget", type: "html", // "string" would remove html tags by running html_entity_decode default: et2_no_init }, imageUpload: { name: "imageUpload", description: "Url to upload images dragged in or id of link_to widget to it's vfs upload. Can also be just a name for which content array contains a path to upload the picture.", type: "string", default: null }, file_picker_callback: { name: "File picker callback", description: "Callback function to get called when file picker is clicked", type: 'js', default: et2_no_init }, images_upload_handler: { name: "Images upload handler", description: "Callback function for handling image upload", type: 'js', default: et2_no_init }, menubar: { name: "Menubar", description: "Display menubar at the top of the editor", type: "boolean", default: true }, statusbar: { name: "Status bar", description: "Enable/disable status bar on the bottom of editor", type: "boolean", default: true }, valid_children: { name: "Valid children", description: "Enables to control what child tag is allowed or not allowed of the present tag. For instance: +body[style], makes style tag allowed inside body", type: "string", default: "+body[style]" } }, /** * Constructor * * @param _parent * @param _attrs * @memberOf et2_htmlarea */ init: function(_parent, _attrs) { this._super.apply(this, arguments); this.editor = null; // TinyMce editor instance this.supportedWidgetClasses = []; // Allow no child widgets this.htmlNode = jQuery(document.createElement(this.options.readonly ? "div" : "textarea")) .addClass('et2_textbox_ro'); if(this.options.height) { this.htmlNode.css('height', this.options.height); } this.setDOMNode(this.htmlNode[0]); }, /** * * @returns {undefined} */ doLoadingFinished: function() { this._super.apply(this, arguments); this.init_editor(); }, init_editor: function() { if(this.mode == 'ascii' || this.editor != null || this.options.readonly) return; var imageUpload = ''; var self = this; if (this.options.imageUpload && this.options.imageUpload[0] !== '/' && this.options.imageUpload.substr(0, 4) != 'http') { imageUpload = egw.ajaxUrl("EGroupware\\Api\\Etemplate\\Widget\\Vfs::ajax_htmlarea_upload")+ '&request_id='+this.getInstanceManager().etemplate_exec_id+'&widget_id='+this.options.imageUpload+'&type=htmlarea'; imageUpload = imageUpload.substr(egw.webserverUrl.length+1); } else if (imageUpload) { imageUpload = this.options.imageUpload.substr(egw.webserverUrl.length+1); } else { imageUpload = egw.ajaxUrl("EGroupware\\Api\\Etemplate\\Widget\\Vfs::ajax_htmlarea_upload")+ '&request_id='+this.getInstanceManager().etemplate_exec_id+'&type=htmlarea'; } // default settings for initialization var settings = { target: this.htmlNode[0], body_id: this.dom + '_htmlarea', menubar: false, statusbar: this.options.statusbar, branding: false, resize: false, height: this.options.height, width: this.options.width, mobile: { theme: 'silver' }, formats: { customparagraph: { block: 'p', styles: {"margin-block-start": "0px", "margin-block-end": "0px"}} }, min_height: 100, convert_urls: false, language: et2_htmlarea.LANGUAGE_CODE[egw.preference('lang', 'common')], language_url: egw.webserverUrl+'/api/js/tinymce/langs/'+et2_htmlarea.LANGUAGE_CODE[egw.preference('lang', 'common')]+'.js', paste_data_images: true, paste_filter_drop: true, browser_spellcheck: true, contextmenu: false, images_upload_url: imageUpload, file_picker_callback: jQuery.proxy(this._file_picker_callback, this), images_upload_handler: this.options.images_upload_handler, init_instance_callback : jQuery.proxy(this._instanceIsReady, this), auto_focus: false, valid_children : this.options.valid_children, plugins: [ "print searchreplace autolink directionality ", "visualblocks visualchars image link media template ", "codesample table charmap hr pagebreak nonbreaking anchor toc ", "insertdatetime advlist lists textcolor wordcount imagetools ", "colorpicker textpattern help paste code searchreplace tabfocus" ], toolbar: et2_htmlarea.TOOLBAR_SIMPLE, block_formats: "Paragraph=p;Heading 1=h1;Heading 2=h2;Heading 3=h3;"+ "Heading 4=h4;Heading 5=h5;Heading 6=h6;Preformatted=pre;Custom Paragraph=customparagraph", font_formats: "Andale Mono=andale mono,times;Arial=arial,helvetica,"+ "sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book "+ "antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;"+ "Courier New=courier new,courier;Georgia=georgia,palatino;"+ "Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;"+ "Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,"+ "monaco;Times New Roman=times new roman,times;Trebuchet "+ "MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;"+ "Wingdings=wingdings,zapf dingbats", fontsize_formats: '8pt 10pt 12pt 14pt 18pt 24pt 36pt', setup : function(ed) { ed.on('init', function() { this.getDoc().body.style.fontSize = egw.preference('rte_font_size', 'common') + egw.preference('rte_font_unit', 'common'); this.getDoc().body.style.fontFamily = egw.preference('rte_font', 'common'); }); } }; // extend default settings with configured options and preferences jQuery.extend(settings, this._extendedSettings()); this.tinymce = tinymce.init(settings); // make sure value gets set in case of widget gets loaded by delay like // inside an inactive tabs this.tinymce.then(function() { self.set_value(self.htmlNode.val()); if (self.editor && self.editor.editorContainer) { self.editor.formatter.toggle(egw.preference('rte_formatblock', 'common')); jQuery(self.editor.editorContainer).height(self.options.height); jQuery(self.editor.iframeElement.contentWindow.document).on('dragenter', function(){ if (jQuery('#dragover-tinymce').length < 1) jQuery("").appendTo('head'); }); } }); }, /** * set disabled * * @param {type} _value * @returns {undefined} */ set_disabled: function(_value) { this._super.apply(this, arguments); if (_value) { jQuery(this.tinymce_container).css('display', 'none'); } else { jQuery(this.tinymce_container).css('display', 'flex'); } }, set_readonly: function(_value) { if(this.options.readonly === _value) return; var value = this.get_value(); this.options.readonly = _value; if(this.options.readonly) { if (this. editor) this.editor.remove(); this.htmlNode = jQuery(document.createElement(this.options.readonly ? "div" : "textarea")) .addClass('et2_textbox_ro'); if(this.options.height) { this.htmlNode.css('height', this.options.height) } this.editor = null; this.setDOMNode(this.htmlNode[0]); this.set_value(value); } else { if(!this.editor) { this.htmlNode = jQuery(document.createElement("textarea")) .val(value); if(this.options.height || this.options.editable_height) { this.htmlNode.css('height', (this.options.editable_height ? this.options.editable_height : this.options.height)); } this.setDOMNode(this.htmlNode[0]); this.init_editor(); } } }, /** * Callback function runs when the filepicker in image dialog is clicked * * @param {type} _callback * @param {type} _value * @param {type} _meta * @returns {unresolved} */ _file_picker_callback: function(_callback, _value, _meta) { if (typeof this.file_picker_callback == 'function') return this.file_picker_callback.call(arguments, this); var callback = _callback; // Don't rely only on app_name to fetch et2 object as app_name may not // always represent current app of the window, e.g.: mail admin account. // Try to fetch et2 from its template name. var etemplate = jQuery('form').data('etemplate'); var et2 = {}; if (etemplate && etemplate.name && !app[egw(window).app_name()]) { et2 = etemplate2.getByTemplate(etemplate.name)[0]['widgetContainer']; } else { et2 = app[egw(window).app_name()].et2; } var vfsSelect = et2_createWidget('vfs-select', { id:'upload', mode: 'open', name: '', button_caption:"Link", button_label:"Link", dialog_title: "Link file", method: "download" }, et2); jQuery(vfsSelect.getDOMNode()).on('change', function (){ callback(vfsSelect.get_value(), {alt:vfsSelect.get_value()}); }); // start the file selector dialog vfsSelect.click(); }, /** * Callback when instance is ready * * @param {type} _editor */ _instanceIsReady: function(_editor) { console.log("Editor: " + _editor.id + " is now initialized."); // try to reserve focus state as running command on editor may steal the // current focus. var focusedEl = jQuery(':focus'); this.editor = _editor; this.editor.on('drop', function(e){ e.preventDefault(); }); if (!this.disabled) jQuery(this.editor.editorContainer).css('display', 'flex'); this.tinymce_container = this.editor.editorContainer; // go back to reserved focused element focusedEl.focus(); }, /** * Takes all relevant preferences into account and set settings accordingly * * @returns {object} returns a object including all settings */ _extendedSettings: function () { var rte_menubar = egw.preference('rte_menubar', 'common'); var rte_toolbar = egw.preference('rte_toolbar', 'common'); // we need to have rte_toolbar values as an array if (rte_toolbar && typeof rte_toolbar == "object") { rte_toolbar = Object.values(rte_toolbar); } var settings = { fontsize_formats: et2_htmlarea.FONT_SIZE_FORMATS[egw.preference('rte_font_unit', 'common')], menubar: parseInt(rte_menubar) && this.menubar ? true : typeof rte_menubar != 'undefined' ? false : this.menubar }; switch (this.mode) { case 'simple': settings.toolbar = et2_htmlarea.TOOLBAR_SIMPLE; break; case 'extended': settings.toolbar = et2_htmlarea.TOOLBAR_EXTENDED; break; case 'advanced': settings.toolbar = et2_htmlarea.TOOLBAR_ADVANCED; break; default: this.mode = ''; } // take rte_toolbar into account if no mode restrictly set from template if (rte_toolbar && !this.mode) { var toolbar_diff = et2_htmlarea.TOOLBAR_LIST.filter(function(i){return !(rte_toolbar.indexOf(i) > -1);}); settings.toolbar = et2_htmlarea.TOOLBAR_ADVANCED; toolbar_diff.forEach(function(a){ var r = new RegExp(a); settings.toolbar = settings.toolbar.replace(r, ''); }); } return settings; }, destroy: function() { if (this.editor) { this.editor.destroy(); } this.editor = null; this.tinymce = null; this.tinymce_container = null; this.htmlNode.remove(); this.htmlNode = null; this._super.apply(this, arguments); }, set_value: function(_value) { this._oldValue = _value; if (this.editor) { this.editor.setContent(_value); } else { if(this.options.readonly) { this.htmlNode.empty().append(_value); } else { this.htmlNode.val(_value); } } this.value = _value; }, getValue: function() { return this.editor ? this.editor.getContent() : ( this.options.readonly ? this.value : this.htmlNode.val() ); }, /** * Resize htmlNode tag according to window size * @param {type} _height excess height which comes from window resize */ resize: function (_height) { if (_height && this.options.resize_ratio !== '0') { // apply the ratio _height = (this.options.resize_ratio != '')? _height * this.options.resize_ratio: _height; if (_height != 0) { if (this.editor) // TinyMCE HTML { var h = 0; if (typeof this.editor.iframeElement !='undefined' && this.editor.editorContainer.clientHeight > 0) { h = (this.editor.editorContainer.clientHeight + _height) > 0 ? (this.editor.editorContainer.clientHeight) + _height: this.editor.settings.min_height; } else // fallback height size { h = this.editor.settings.min_height + _height; } jQuery(this.editor.editorContainer).height(h); jQuery(this.editor.iframeElement).height(h - (this.editor.editorContainer.getElementsByClassName('tox-toolbar')[0].clientHeight + this.editor.editorContainer.getElementsByClassName('tox-statusbar')[0].clientHeight)); } else // No TinyMCE { this.htmlNode.height(this.htmlNode.height() + _height); } } } } });}).call(this); et2_register_widget(et2_htmlarea, ["htmlarea"]); // Static class stuff jQuery.extend(et2_htmlarea, { /** * Array of toolbars * @constant */ TOOLBAR_LIST: ['undo', 'redo', 'formatselect', 'fontselect', 'fontsizeselect', 'bold', 'italic', 'strikethrough', 'forecolor', 'backcolor', 'link', 'alignleft', 'aligncenter', 'alignright', 'alignjustify', 'numlist', 'bullist', 'outdent', 'indent', 'ltr', 'rtl', 'removeformat', 'code', 'image', 'searchreplace' ], /** * arranged toolbars as simple mode * @constant */ TOOLBAR_SIMPLE: "undo redo|formatselect fontselect fontsizeselect | bold italic removeformat forecolor backcolor | "+ "alignleft aligncenter alignright alignjustify | numlist "+ "bullist outdent indent| link image pastetext", /** * arranged toolbars as extended mode * @constant */ TOOLBAR_EXTENDED: "fontselect fontsizeselect | bold italic strikethrough forecolor backcolor | "+ "link | alignleft aligncenter alignright alignjustify | numlist "+ "bullist outdent indent | removeformat | image", /** * arranged toolbars as advanced mode * @constant */ TOOLBAR_ADVANCED: "undo redo| formatselect | fontselect fontsizeselect | bold italic strikethrough forecolor backcolor | "+ "link | alignleft aligncenter alignright alignjustify | numlist "+ "bullist outdent indent ltr rtl | removeformat code| image | searchreplace", /** * font size formats * @constant */ FONT_SIZE_FORMATS: { pt: "8pt 10pt 12pt 14pt 18pt 24pt 36pt 48pt 72pt", px:"8px 10px 12px 14px 18px 24px 36px 48px 72px" }, /** * language code represention for TinyMCE lang code */ LANGUAGE_CODE: { bg: "bg_BG", ca: "ca", cs: "cs", da: "da", de: "de", en:"en_CA", el:"el", "es-es":"es", et: "et", eu: "eu" , fa: "fa_IR", fi: "fi", fr: "fr_FR", hi:"", hr:"hr", hu:"hu_HU", id: "id", it: "it", iw: "", ja: "ja", ko: "ko_KR", lo: "", lt: "lt", lv: "lv", nl: "nl", no: "nb_NO", pl: "pl", pt: "pt_PT", "pt-br": "pt_BR", ru: "ru", sk: "sk", sl: "sl_SI", sv: "sv_SE", th: "th_TH", tr: "tr_TR", uk: "en_GB", vi: "vi_VN", zh: "zh_CN", "zh-tw": "zh_TW" } });