Convert et2_core_editableWidget and et2_widget_htmlarea to TS

This commit is contained in:
Hadi Nategh 2020-02-06 14:30:22 +01:00
parent edf95a4bdf
commit 7d01281fa0
5 changed files with 1373 additions and 675 deletions

View File

@ -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

View File

@ -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;
}
}

View File

@ -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 {}

View File

@ -1,3 +1,4 @@
"use strict";
/**
* EGroupware eTemplate2 - JS widget for HTML editing
*
@ -9,499 +10,465 @@
* @copyright Hadi Nategh <hn@egroupware.org>
* @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("<style id='dragover-tinymce'>.dragover:after {height:calc(100% - "+jQuery(this).height()+"px) !important;}</style>").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"
}
});
};
// 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("<style id='dragover-tinymce'>.dragover:after {height:calc(100% - " + jQuery(this).height() + "px) !important;}</style>").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

View File

@ -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 <hn@egroupware.org>
* @copyright Hadi Nategh <hn@egroupware.org>
* @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[<string><unknown>egw.preference('lang', 'common')],
language_url: egw.webserverUrl+'/api/js/tinymce/langs/'+et2_htmlarea.LANGUAGE_CODE[<string><unknown>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 = <string><unknown>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(<string><unknown>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("<style id='dragover-tinymce'>.dragover:after {height:calc(100% - "+jQuery(this).height()+"px) !important;}</style>").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 = <string>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[<string>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 !((<string[]>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"]);