diff --git a/api/js/etemplate/et2_core_editableWidget.js b/api/js/etemplate/et2_core_editableWidget.js index 4bd0f27f2d..63028d781a 100644 --- a/api/js/etemplate/et2_core_editableWidget.js +++ b/api/js/etemplate/et2_core_editableWidget.js @@ -1,3 +1,4 @@ +"use strict"; /** * EGroupware eTemplate2 - JS Widget base class * @@ -5,15 +6,27 @@ * @package etemplate * @subpackage api * @link http://www.egroupware.org - * @author Andreas Stöckel - * @copyright Stylite 2011 - * @version $Id$ + * @author Nathan Gray */ - +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); /*egw:uses - et2_core_inputWidget; + et2_core_inputWidget; */ - +var et2_core_inputWidget_1 = require("./et2_core_inputWidget"); +var et2_core_inheritance_1 = require("./et2_core_inheritance"); /** * et2_editableWidget derives from et2_inputWidget and adds the ability to start * readonly, then turn editable on double-click. If we decide to do this with @@ -21,184 +34,161 @@ * * @augments et2_inputWidget */ -var et2_editableWidget = (function(){ "use strict"; return et2_inputWidget.extend(et2_ISubmitListener, -{ - attributes: { - readonly: { - name: "readonly", - type: "string", // | boolean - default: false, - description: "If set to 'editable' will start readonly, double clicking will make it editable and clicking out will save" - }, - toggle_readonly: { - name: "toggle_readonly", - type: "boolean", - default: true, - description: "Double clicking makes widget editable. If off, must be made editable in some other way." - }, - save_callback: { - name: "save_callback", - type: "string", - default: et2_no_init, - description: "Ajax callback to save changed value when readonly is 'editable'. If not provided, a regular submit is done." - }, - save_callback_params: { - name: "readonly", - type: "string", - default: et2_no_init, - description: "Additional parameters passed to save_callback" - }, - editable_height: { - name: "Editable height", - description: "Set height for widget while in edit mode", - type: "string" - } - }, - - /** - * Constructor - * - * @memberOf et2_inputWidget - */ - init: function(_parent, _attrs) { - // 'Editable' really should be boolean for everything else to work - if(_attrs.readonly && typeof _attrs.readonly === 'string') - { - _attrs.readonly = true; - this._toggle_readonly = _attrs.toggle_readonly; - } - this._super.apply(this, arguments); - }, - - destroy: function() { - var node = this.getInputNode(); - if (node) - { - jQuery(node).off('.et2_editableWidget'); - } - - this._super.apply(this, arguments); - }, - - /** - * Load the validation errors from the server - * - * @param {object} _attrs - */ - transformAttributes: function(_attrs) { - this._super.apply(this, arguments); - - }, - - attachToDOM: function() { - this._super.apply(this,arguments); - var node = this.getDOMNode(); - if (node && this._toggle_readonly) - { - jQuery(node) - .off('.et2_editableWidget') - .on("dblclick.et2_editableWidget", this, function(e) { - e.data.dblclick.call(e.data, this); - }) - .addClass('et2_clickable et2_editable'); - } - else - { - jQuery(node).addClass('et2_editable_readonly'); - } - - }, - - detatchFromDOM: function() { - this._super.apply(this,arguments); - }, - - /** - * Handle double click - * - * Turn widget editable - * - * @param {DOMNode} _node - */ - dblclick: function (_node) { - // Turn off readonly - this.set_readonly(false); - - jQuery('body').on("click.et2_editableWidget", this, function(e) { - // Make sure click comes from body, not a popup - if(jQuery.contains(this, e.target) && e.target.type != 'textarea') - { - jQuery(this).off("click.et2_editableWidget"); - e.data.focusout.call(e.data, this); - } - }); - }, - - /** - * User clicked somewhere else, save and turn back to readonly - * - * @param {DOMNode} _node Body node - * @returns {et2_core_editableWidgetet2_editableWidget.et2_core_editableWidgetAnonym$0@call;getInstanceManager@call;submit} - */ - focusout: function (_node) - { - var value = this.get_value(); - var oldValue = this._oldValue; - - // Change back to readonly - this.set_readonly(true); - - // No change, do nothing - if(value == oldValue) return; - - - // Submit - if(this.options.save_callback) - { - var params = [value]; - if(this.options.save_callback_params) - { - params = params.concat(this.options.save_callback_params.split(',')); - } - - egw.json(this.options.save_callback, params, function() { - }, this, true, this).sendRequest(); - } - else - { - this.set_value(value); - return this.getInstanceManager().submit(); - } - }, - - /** - * Called whenever the template gets submitted. - * If we have a save_callback, we call that before the submit (no check on - * the result) - * - * @param _values contains the values which will be sent to the server. - * Listeners may change these values before they get submitted. - */ - submit: function(_values) { - if(this.options.readonly) - { - // Not currently editing, just continue on - return true; - } - // Change back to readonly - this.set_readonly(true); - - var params = [this.get_value()]; - if(this.options.save_callback_params) - { - params = params.concat(this.options.save_callback_params.split(',')); - } - if(this.options.save_callback) - { - egw.json(this.options.save_callback, params, function() { - }, this, true, this).sendRequest(); - } - return true; - } -});}).call(this); - +var et2_editableWidget = /** @class */ (function (_super) { + __extends(et2_editableWidget, _super); + /** + * Constructor + */ + function et2_editableWidget(_parent, _attrs, _child) { + var _this = this; + // 'Editable' really should be boolean for everything else to work + if (_attrs.readonly && typeof _attrs.readonly === 'string') { + _attrs.readonly = true; + var toggle_readonly = _attrs.toggle_readonly; + } + // Call the inherited constructor + _this = _super.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_editableWidget._attributes, _child || {})) || this; + if (typeof toggle_readonly != 'undefined') + _this._toggle_readonly = toggle_readonly; + return _this; + } + et2_editableWidget.prototype.destroy = function () { + var node = this.getInputNode(); + if (node) { + jQuery(node).off('.et2_editableWidget'); + } + _super.prototype.destroy.call(this); + }; + /** + * Load the validation errors from the server + * + * @param {object} _attrs + */ + et2_editableWidget.prototype.transformAttributes = function (_attrs) { + _super.prototype.transformAttributes.call(this, _attrs); + }; + et2_editableWidget.prototype.attachToDOM = function () { + var res = _super.prototype.attachToDOM.call(this); + var node = this.getDOMNode(); + if (node && this._toggle_readonly) { + jQuery(node) + .off('.et2_editableWidget') + .on("dblclick.et2_editableWidget", this, function (e) { + e.data.dblclick.call(e.data, this); + }) + .addClass('et2_clickable et2_editable'); + } + else { + jQuery(node).addClass('et2_editable_readonly'); + } + return res; + }; + et2_editableWidget.prototype.detatchFromDOM = function () { + _super.prototype.detatchFromDOM.call(this); + }; + /** + * Handle double click + * + * Turn widget editable + * + * @param {DOMNode} _node + */ + et2_editableWidget.prototype.dblclick = function (_node) { + // Turn off readonly + this.set_readonly(false); + jQuery('body').on("click.et2_editableWidget", this, function (e) { + // Make sure click comes from body, not a popup + if (jQuery.contains(this, e.target) && e.target.type != 'textarea') { + jQuery(this).off("click.et2_editableWidget"); + e.data.focusout.call(e.data, this); + } + }); + }; + /** + * User clicked somewhere else, save and turn back to readonly + * + * @param {DOMNode} _node Body node + * @returns {et2_core_editableWidgetet2_editableWidget.et2_core_editableWidgetAnonym$0@call;getInstanceManager@call;submit} + */ + et2_editableWidget.prototype.focusout = function (_node) { + var value = this.get_value(); + var oldValue = this._oldValue; + // Change back to readonly + this.set_readonly(true); + // No change, do nothing + if (value == oldValue) + return; + // Submit + if (this.options.save_callback) { + var params = [value]; + if (this.options.save_callback_params) { + params = params.concat(this.options.save_callback_params.split(',')); + } + egw.json(this.options.save_callback, params, function () { + }, this, true, this).sendRequest(); + } + else { + this.set_value(value); + return this.getInstanceManager().submit(); + } + }; + /** + * Called whenever the template gets submitted. + * If we have a save_callback, we call that before the submit (no check on + * the result) + * + * @param _values contains the values which will be sent to the server. + * Listeners may change these values before they get submitted. + */ + et2_editableWidget.prototype.submit = function (_values) { + if (this.options.readonly) { + // Not currently editing, just continue on + return true; + } + // Change back to readonly + this.set_readonly(true); + var params = [this.get_value()]; + if (this.options.save_callback_params) { + params = params.concat(this.options.save_callback_params.split(',')); + } + if (this.options.save_callback) { + egw.json(this.options.save_callback, params, function () { + }, this, true, this).sendRequest(); + } + return true; + }; + et2_editableWidget._attributes = { + readonly: { + name: "readonly", + type: "string", + default: false, + description: "If set to 'editable' will start readonly, double clicking will make it editable and clicking out will save" + }, + toggle_readonly: { + name: "toggle_readonly", + type: "boolean", + default: true, + description: "Double clicking makes widget editable. If off, must be made editable in some other way." + }, + save_callback: { + name: "save_callback", + type: "string", + default: et2_no_init, + description: "Ajax callback to save changed value when readonly is 'editable'. If not provided, a regular submit is done." + }, + save_callback_params: { + name: "readonly", + type: "string", + default: et2_no_init, + description: "Additional parameters passed to save_callback" + }, + editable_height: { + name: "Editable height", + description: "Set height for widget while in edit mode", + type: "string" + } + }; + return et2_editableWidget; +}(et2_core_inputWidget_1.et2_inputWidget)); +exports.et2_editableWidget = et2_editableWidget; +//# sourceMappingURL=et2_core_editableWidget.js.map \ No newline at end of file diff --git a/api/js/etemplate/et2_core_editableWidget.ts b/api/js/etemplate/et2_core_editableWidget.ts new file mode 100644 index 0000000000..c60e58b7e8 --- /dev/null +++ b/api/js/etemplate/et2_core_editableWidget.ts @@ -0,0 +1,213 @@ +/** + * EGroupware eTemplate2 - JS Widget base class + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package etemplate + * @subpackage api + * @link http://www.egroupware.org + * @author Nathan Gray + */ + +/*egw:uses + et2_core_inputWidget; +*/ + +import {et2_inputWidget} from "./et2_core_inputWidget"; +import {WidgetConfig} from "./et2_core_widget"; +import {ClassWithAttributes} from "./et2_core_inheritance"; + +/** + * et2_editableWidget derives from et2_inputWidget and adds the ability to start + * readonly, then turn editable on double-click. If we decide to do this with + * more widgets, it should just be merged with et2_inputWidget. + * + * @augments et2_inputWidget + */ +export class et2_editableWidget extends et2_inputWidget implements et2_ISubmitListener +{ + static readonly _attributes : any = { + readonly: { + name: "readonly", + type: "string", // | boolean + default: false, + description: "If set to 'editable' will start readonly, double clicking will make it editable and clicking out will save" + }, + toggle_readonly: { + name: "toggle_readonly", + type: "boolean", + default: true, + description: "Double clicking makes widget editable. If off, must be made editable in some other way." + }, + save_callback: { + name: "save_callback", + type: "string", + default: et2_no_init, + description: "Ajax callback to save changed value when readonly is 'editable'. If not provided, a regular submit is done." + }, + save_callback_params: { + name: "readonly", + type: "string", + default: et2_no_init, + description: "Additional parameters passed to save_callback" + }, + editable_height: { + name: "Editable height", + description: "Set height for widget while in edit mode", + type: "string" + } + }; + + private _toggle_readonly : boolean; + + /** + * Constructor + */ + constructor(_parent, _attrs? : WidgetConfig, _child? : object) + { + // 'Editable' really should be boolean for everything else to work + if(_attrs.readonly && typeof _attrs.readonly === 'string') + { + _attrs.readonly = true; + var toggle_readonly = _attrs.toggle_readonly; + } + // Call the inherited constructor + super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_editableWidget._attributes, _child || {})); + if (typeof toggle_readonly != 'undefined') this._toggle_readonly = toggle_readonly; + } + + destroy() + { + let node = this.getInputNode(); + if (node) + { + jQuery(node).off('.et2_editableWidget'); + } + super.destroy(); + } + + /** + * Load the validation errors from the server + * + * @param {object} _attrs + */ + transformAttributes(_attrs) + { + super.transformAttributes(_attrs); + } + + attachToDOM() + { + let res = super.attachToDOM(); + let node = this.getDOMNode(); + if (node && this._toggle_readonly) + { + jQuery(node) + .off('.et2_editableWidget') + .on("dblclick.et2_editableWidget", this, function(e) { + e.data.dblclick.call(e.data, this); + }) + .addClass('et2_clickable et2_editable'); + } + else + { + jQuery(node).addClass('et2_editable_readonly'); + } + return res; + } + + detatchFromDOM() + { + super.detatchFromDOM(); + } + + /** + * Handle double click + * + * Turn widget editable + * + * @param {DOMNode} _node + */ + dblclick(_node) + { + // Turn off readonly + this.set_readonly(false); + + jQuery('body').on("click.et2_editableWidget", this, function(e) { + // Make sure click comes from body, not a popup + if(jQuery.contains(this, e.target) && e.target.type != 'textarea') + { + jQuery(this).off("click.et2_editableWidget"); + e.data.focusout.call(e.data, this); + } + }); + } + + /** + * User clicked somewhere else, save and turn back to readonly + * + * @param {DOMNode} _node Body node + * @returns {et2_core_editableWidgetet2_editableWidget.et2_core_editableWidgetAnonym$0@call;getInstanceManager@call;submit} + */ + focusout(_node) + { + var value = this.get_value(); + var oldValue = this._oldValue; + + // Change back to readonly + this.set_readonly(true); + + // No change, do nothing + if(value == oldValue) return; + + + // Submit + if(this.options.save_callback) + { + var params = [value]; + if(this.options.save_callback_params) + { + params = params.concat(this.options.save_callback_params.split(',')); + } + + egw.json(this.options.save_callback, params, function() { + }, this, true, this).sendRequest(); + } + else + { + this.set_value(value); + return this.getInstanceManager().submit(); + } + } + + /** + * Called whenever the template gets submitted. + * If we have a save_callback, we call that before the submit (no check on + * the result) + * + * @param _values contains the values which will be sent to the server. + * Listeners may change these values before they get submitted. + */ + submit(_values) + { + if(this.options.readonly) + { + // Not currently editing, just continue on + return true; + } + // Change back to readonly + this.set_readonly(true); + + var params = [this.get_value()]; + if(this.options.save_callback_params) + { + params = params.concat(this.options.save_callback_params.split(',')); + } + if(this.options.save_callback) + { + egw.json(this.options.save_callback, params, function() { + }, this, true, this).sendRequest(); + } + return true; + } +} + diff --git a/api/js/etemplate/et2_types.d.ts b/api/js/etemplate/et2_types.d.ts index b41ef0ff98..7c600c5f7f 100644 --- a/api/js/etemplate/et2_types.d.ts +++ b/api/js/etemplate/et2_types.d.ts @@ -30,7 +30,9 @@ declare var et2_placeholder : any; declare var et2_validTypes : string[]; declare var et2_typeDefaults : object; //declare const et2_no_init : object; -declare var et2_editableWidget : any; +declare class et2_editableWidget extends et2_inputWidget { + public set_readonly(value : boolean); +} /*declare var et2_IDOMNode : any; declare var et2_IInput : any; declare var et2_IResizeable : any; @@ -161,6 +163,7 @@ declare var et2_vfsUpload : any; declare var et2_vfsSelect : any; declare var et2_video : any; declare var et2_IExposable : any; +declare var tinymce : any; declare class et2_nextmatch_sortheader extends et2_nextmatch_header {} declare class et2_nextmatch_filterheader extends et2_nextmatch_header {} declare class et2_nextmatch_accountfilterheader extends et2_nextmatch_header {} diff --git a/api/js/etemplate/et2_widget_htmlarea.js b/api/js/etemplate/et2_widget_htmlarea.js index d9cf2b5b46..d1db82a936 100644 --- a/api/js/etemplate/et2_widget_htmlarea.js +++ b/api/js/etemplate/et2_widget_htmlarea.js @@ -1,3 +1,4 @@ +"use strict"; /** * EGroupware eTemplate2 - JS widget for HTML editing * @@ -9,499 +10,465 @@ * @copyright Hadi Nategh * @version $Id$ */ - +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); /*egw:uses - jsapi.jsapi; // Needed for egw_seperateJavaScript - /vendor/tinymce/tinymce/tinymce.min.js; - et2_core_editableWidget; + jsapi.jsapi; // Needed for egw_seperateJavaScript + /vendor/tinymce/tinymce/tinymce.min.js; + et2_core_editableWidget; */ - +var et2_core_editableWidget_1 = require("./et2_core_editableWidget"); +var et2_core_inheritance_1 = require("./et2_core_inheritance"); +var et2_core_widget_1 = require("./et2_core_widget"); /** * @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() - { +var et2_htmlarea = /** @class */ (function (_super) { + __extends(et2_htmlarea, _super); + /** + * Constructor + */ + function et2_htmlarea(_parent, _attrs, _child) { + var _this = + // Call the inherited constructor + _super.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_htmlarea._attributes, _child || {})) || this; + _this.editor = null; + _this.htmlNode = null; + _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]); + return _this; + } + /** + * + * @returns {undefined} + */ + et2_htmlarea.prototype.doLoadingFinished = function () { + _super.prototype.doLoadingFinished.call(this); + this.init_editor(); + return true; + }; + et2_htmlarea.prototype.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_id + '_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'); + + 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" - } -}); \ No newline at end of file + }; + // 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} + */ + et2_htmlarea.prototype.set_disabled = function (_value) { + _super.prototype.set_disabled.call(this, _value); + if (_value) { + jQuery(this.tinymce_container).css('display', 'none'); + } + else { + jQuery(this.tinymce_container).css('display', 'flex'); + } + }; + et2_htmlarea.prototype.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 + */ + et2_htmlarea.prototype._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 + */ + et2_htmlarea.prototype._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 + */ + et2_htmlarea.prototype._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.keys(rte_toolbar).map(function (key) { return rte_toolbar[key]; }); + } + 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; + }; + et2_htmlarea.prototype.destroy = function () { + if (this.editor) { + this.editor.destroy(); + } + this.editor = null; + this.tinymce = null; + this.tinymce_container = null; + this.htmlNode.remove(); + this.htmlNode = null; + _super.prototype.destroy.call(this); + }; + et2_htmlarea.prototype.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; + }; + et2_htmlarea.prototype.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 + */ + et2_htmlarea.prototype.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 = void 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); + } + } + } + }; + et2_htmlarea._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", + 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]" + } + }; + /** + * Array of toolbars + * @constant + */ + et2_htmlarea.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 + */ + et2_htmlarea.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 + */ + et2_htmlarea.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 + */ + et2_htmlarea.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 + */ + et2_htmlarea.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 + */ + et2_htmlarea.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" + }; + return et2_htmlarea; +}(et2_core_editableWidget_1.et2_editableWidget)); +et2_core_widget_1.et2_register_widget(et2_htmlarea, ["htmlarea"]); +//# sourceMappingURL=et2_widget_htmlarea.js.map \ No newline at end of file diff --git a/api/js/etemplate/et2_widget_htmlarea.ts b/api/js/etemplate/et2_widget_htmlarea.ts new file mode 100644 index 0000000000..01cdb6ea49 --- /dev/null +++ b/api/js/etemplate/et2_widget_htmlarea.ts @@ -0,0 +1,525 @@ +/** + * 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; +*/ + +import {et2_editableWidget} from "./et2_core_editableWidget"; +import {ClassWithAttributes} from "./et2_core_inheritance"; +import {WidgetConfig, et2_register_widget} from "./et2_core_widget"; + +/** + * @augments et2_inputWidget + */ +class et2_htmlarea extends et2_editableWidget implements et2_IResizeable +{ + static readonly _attributes : any = { + 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]" + } + }; + + /** + * Array of toolbars + * @constant + */ + public static readonly TOOLBAR_LIST : string[] = ['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 + */ + public static readonly TOOLBAR_SIMPLE : string = "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 + */ + public static readonly TOOLBAR_EXTENDED : string = "fontselect fontsizeselect | bold italic strikethrough forecolor backcolor | "+ + "link | alignleft aligncenter alignright alignjustify | numlist "+ + "bullist outdent indent | removeformat | image"; + + /** + * arranged toolbars as advanced mode + * @constant + */ + public static readonly TOOLBAR_ADVANCED : string = "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 + */ + public static readonly FONT_SIZE_FORMATS : {pt : string, px : string} = { + 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 + */ + public static readonly 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" + }; + + editor : any = null; + supportedWidgetClasses : any; + htmlNode : JQuery = null; + mode : string; + tinymce : any; + tinymce_container : HTMLElement; + file_picker_callback : Function; + menubar : boolean; + protected value : string; + + /** + * Constructor + */ + constructor(_parent, _attrs? : WidgetConfig, _child? : object) + { + // Call the inherited constructor + super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_htmlarea._attributes, _child || {})); + 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() + { + super.doLoadingFinished(); + this.init_editor(); + return true; + } + + init_editor() { + if(this.mode == 'ascii' || this.editor != null || this.options.readonly) return; + let imageUpload; + let 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 + let settings = { + target: this.htmlNode[0], + body_id: this.dom_id + '_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(_value) + { + super.set_disabled(_value); + if (_value) + { + jQuery(this.tinymce_container).css('display', 'none'); + } + else + { + jQuery(this.tinymce_container).css('display', 'flex'); + } + } + + set_readonly(_value) + { + if(this.options.readonly === _value) return; + let 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 + */ + private _file_picker_callback(_callback : Function, _value, _meta) + { + if (typeof this.file_picker_callback == 'function') return this.file_picker_callback.call(arguments, this); + let 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. + let etemplate = jQuery('form').data('etemplate'); + let 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; + } + + let 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 + */ + private _instanceIsReady(_editor) + { + console.log("Editor: " + _editor.id + " is now initialized."); + // try to reserve focus state as running command on editor may steal the + // current focus. + let 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 + */ + private _extendedSettings() : object + { + let rte_menubar = egw.preference('rte_menubar', 'common'); + let 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.keys(rte_toolbar).map(function(key){return rte_toolbar[key]}); + } + let 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) + { + let 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){ + let r = new RegExp(a); + settings['toolbar'] = settings['toolbar'].replace(r, ''); + }); + } + return settings; + } + + destroy() + { + if (this.editor) + { + this.editor.destroy(); + } + this.editor = null; + this.tinymce = null; + this.tinymce_container = null; + this.htmlNode.remove(); + this.htmlNode = null; + super.destroy(); + } + set_value(_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() + { + 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(_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 + { + let h; + 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); + } + } + } + } +} +et2_register_widget(et2_htmlarea, ["htmlarea"]); \ No newline at end of file