2020-02-06 14:30:22 +01:00
/ * *
* EGroupware eTemplate2 - JS widget for HTML editing
*
* @license http : //opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
2021-06-07 17:33:53 +02:00
* @link https : //www.egroupware.org
2020-02-06 14:30:22 +01:00
* @author Hadi Nategh < hn @ egroupware.org >
* @copyright Hadi Nategh < hn @ egroupware.org >
* /
/ * e g w : u s e s
jsapi . jsapi ; // Needed for egw_seperateJavaScript
/ v e n d o r / t i n y m c e / t i n y m c e / t i n y m c e . m i n . j s ;
et2_core_editableWidget ;
* /
import { et2_editableWidget } from "./et2_core_editableWidget" ;
import { ClassWithAttributes } from "./et2_core_inheritance" ;
2024-07-17 00:11:56 +02:00
import { et2_register_widget , WidgetConfig } from "./et2_core_widget" ;
2021-06-07 17:33:53 +02:00
import { et2_IResizeable } from "./et2_core_interfaces" ;
import { et2_no_init } from "./et2_core_common" ;
2021-06-08 14:11:59 +02:00
import { egw } from "../jsapi/egw_global" ;
2021-06-10 15:53:31 +02:00
import "../../../vendor/tinymce/tinymce/tinymce.min.js" ;
2021-06-10 11:38:54 +02:00
import { etemplate2 } from "./etemplate2" ;
2024-07-17 00:11:56 +02:00
import { loadWebComponent } from "./Et2Widget/Et2Widget" ;
import { Et2VfsSelectDialog } from "./Et2Vfs/Et2VfsSelectDialog" ;
2020-02-06 14:30:22 +01:00
/ * *
* @augments et2_inputWidget
* /
2020-10-08 12:15:01 +02:00
export class et2_htmlarea extends et2_editableWidget implements et2_IResizeable
2020-02-06 14:30:22 +01:00
{
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]"
2020-09-25 14:17:29 +02:00
} ,
toolbar : {
'name' : 'Toolbar' ,
'description' : 'Comma separated string of toolbar actions. It will only be considered if no Mode is restricted.' ,
'default' : '' ,
'type' : 'string'
2020-10-14 10:40:35 +02:00
} ,
toolbar_mode : {
'name' : 'toolbar mode' ,
'type' : 'string' ,
'default' : 'floating' ,
'description' : 'It allows to extend the toolbar to accommodate the overflowing toolbar buttons. {floating, sliding, scrolling, wrap}'
2024-02-05 09:54:06 +01:00
} ,
applyDefaultFont : {
name : 'apply default font and size' ,
type : 'boolean' ,
default : false ,
description : 'Add default font and size as style attribute to the markup. Also ensures all non-block elements are wrapped in p.'
2020-02-06 14:30:22 +01:00
}
} ;
/ * *
* 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' ,
2020-10-14 11:38:07 +02:00
'bullist' , 'outdent' , 'indent' , 'ltr' , 'rtl' , 'removeformat' , 'code' , 'image' , 'searchreplace' , 'fullscreen' , 'table'
2020-02-06 14:30:22 +01:00
] ;
/ * *
* arranged toolbars as simple mode
* @constant
* /
2021-12-06 10:14:37 +01:00
public static readonly TOOLBAR_SIMPLE : string = "undo redo|formatselect fontselect fontsizeselect | bold italic underline removeformat forecolor backcolor | " +
2021-04-12 11:29:18 +02:00
"alignleft aligncenter alignright alignjustify | bullist " +
"numlist outdent indent| link image pastetext | table" ;
2020-02-06 14:30:22 +01:00
/ * *
* arranged toolbars as extended mode
* @constant
* /
2021-12-06 10:14:37 +01:00
public static readonly TOOLBAR_EXTENDED : string = "fontselect fontsizeselect | bold italic underline strikethrough forecolor backcolor | " +
2020-02-06 14:30:22 +01:00
"link | alignleft aligncenter alignright alignjustify | numlist " +
2020-10-14 11:05:51 +02:00
"bullist outdent indent | removeformat | image | fullscreen | table" ;
2020-02-06 14:30:22 +01:00
/ * *
* arranged toolbars as advanced mode
* @constant
* /
2021-12-06 10:14:37 +01:00
public static readonly TOOLBAR_ADVANCED : string = "undo redo| formatselect | fontselect fontsizeselect | bold italic underline strikethrough forecolor backcolor | " +
2021-04-12 11:29:18 +02:00
"alignleft aligncenter alignright alignjustify | bullist " +
"numlist outdent indent ltr rtl | removeformat code| link image pastetext | searchreplace | fullscreen | table" ;
2020-02-06 14:30:22 +01:00
/ * *
* font size formats
* @constant
* /
public static readonly FONT_SIZE_FORMATS : { pt : string , px : string } = {
2020-02-11 17:05:19 +01:00
pt : "8pt 9pt 10pt 11pt 12pt 14pt 16pt 18pt 20pt 22pt 24pt 26pt 28pt 36pt 48pt 72pt" ,
px : "8px 9px 10px 11px 12px 14px 16px 18px 20px 22px 24px 26px 28px 36px 48px 72px"
2020-02-06 14:30:22 +01:00
} ;
/ * *
* 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 ;
2020-09-25 14:17:29 +02:00
toolbar : string ;
2020-02-06 14:30:22 +01:00
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 ( ) ;
2023-07-11 20:02:36 +02:00
return this . init_editor ( ) ;
2020-02-06 14:30:22 +01:00
}
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
2022-09-12 10:04:42 +02:00
let settings : any = {
2021-06-10 15:53:31 +02:00
base_url : egw.webserverUrl + '/vendor/tinymce/tinymce' ,
2020-02-06 14:30:22 +01:00
target : this.htmlNode [ 0 ] ,
body_id : this.dom_id + '_htmlarea' ,
menubar : false ,
statusbar : this.options.statusbar ,
2020-10-14 10:40:35 +02:00
toolbar_mode : this.options.toolbar_mode ,
2020-02-06 14:30:22 +01:00
branding : false ,
resize : false ,
height : this.options.height ,
width : this.options.width ,
2021-01-06 14:06:21 +01:00
end_container_on_empty_block : true ,
2020-02-06 14:30:22 +01:00
mobile : {
theme : 'silver'
} ,
formats : {
2022-05-09 18:37:35 +02:00
// setting p (and below also the preferred formatblock) to the users font and -size preference
p : { block : 'p' , styles : {
"font-family" : ( egw . preference ( 'rte_font' , 'common' ) || 'arial, helvetica, sans-serif' ) ,
2022-05-10 18:46:12 +02:00
"font-size" : ( < string > egw . preference ( 'rte_font_size' , 'common' ) || '10' ) +
( < string > egw . preference ( 'rte_font_unit' , 'common' ) || 'pt' )
2022-05-09 18:37:35 +02:00
} } ,
2020-02-06 14:30:22 +01:00
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 " ,
2020-03-05 14:45:25 +01:00
"visualblocks visualchars image link media template fullscreen" ,
2020-02-06 14:30:22 +01:00
"codesample table charmap hr pagebreak nonbreaking anchor toc " ,
"insertdatetime advlist lists textcolor wordcount imagetools " ,
2024-06-28 17:47:54 +02:00
"colorpicker textpattern help paste code searchreplace tabfocus" ,
"noneditable"
2020-02-06 14:30:22 +01:00
] ,
toolbar : et2_htmlarea.TOOLBAR_SIMPLE ,
block_formats : "Paragraph=p;Heading 1=h1;Heading 2=h2;Heading 3=h3;" +
2022-09-12 10:04:42 +02:00
"Heading 4=h4;Heading 5=h5;Heading 6=h6;Preformatted=pre" ,
2020-02-06 14:30:22 +01:00
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;" +
2020-12-09 13:17:37 +01:00
"Helvetica=helvetica;Impact=impact,chicago;Segoe=segoe,segoe ui;Symbol=symbol;" +
2020-02-06 14:30:22 +01:00
"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;" +
2024-04-04 13:27:13 +02:00
"Wingdings=wingdings,zapf dingbats;" +
"EGroupware=egroupware,arial,helvetica,sans-serif;" +
"EGroupware Bold=egroupware2,arial black,avant garde" ,
2020-02-06 14:30:22 +01:00
fontsize_formats : '8pt 10pt 12pt 14pt 18pt 24pt 36pt' ,
2022-05-10 18:46:12 +02:00
content_css : egw.webserverUrl + '/api/tinymce.php?' + // use the 3 prefs as cache-buster
2022-06-09 09:48:52 +02:00
btoa ( egw . preference ( 'rte_font' , 'common' ) + '::' +
egw . preference ( 'rte_font_size' , 'common' ) + '::' +
2022-05-10 18:46:12 +02:00
egw . preference ( 'rte_font_unit' , 'common' ) ) ,
2020-02-06 14:30:22 +01:00
} ;
2022-09-12 10:04:42 +02:00
let rte_formatblock = < string > ( egw . preference ( 'rte_formatblock' , 'common' ) || 'p' ) ;
if ( rte_formatblock === 'customparagraph' )
{
settings . forced_root_block = false ;
settings . force_br_newlines = true ;
settings . force_p_newlines = false ;
rte_formatblock = 'p' ;
}
else if ( rte_formatblock !== 'p' )
2022-05-09 18:37:35 +02:00
{
settings . formats [ rte_formatblock ] = jQuery . extend ( true , { } , settings . formats . p ) ;
settings . formats [ rte_formatblock ] . block = rte_formatblock ;
}
2020-02-06 14:30:22 +01:00
// 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 ( ) ) ;
2020-06-24 17:58:18 +02:00
self . resetDirty ( ) ;
2020-02-06 14:30:22 +01:00
if ( self . editor && self . editor . editorContainer )
{
2022-12-02 14:42:36 +01:00
const activeElement = document . activeElement ;
2022-09-12 10:04:42 +02:00
self . editor . formatter . toggle ( rte_formatblock ) ;
2020-02-06 14:30:22 +01:00
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' ) ;
} ) ;
2022-12-02 14:42:36 +01:00
// give focus back
activeElement && activeElement . focus && activeElement . focus ( ) ;
2020-02-06 14:30:22 +01:00
}
} ) ;
2023-07-11 20:02:36 +02:00
return this . tinymce ;
2020-02-06 14:30:22 +01:00
}
/ * *
* 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 ;
}
2024-07-17 00:11:56 +02:00
const dialog = < Et2VfsSelectDialog > < unknown > loadWebComponent ( "et2-vfs-select-dialog" , {
id : 'upload' ,
2020-02-06 14:30:22 +01:00
mode : 'open' ,
2024-07-17 00:11:56 +02:00
multiple : false ,
buttonLabel : "Link" ,
title : "Link file" ,
open : true
} , this ) ;
dialog . addEventListener ( 'change' , function ( )
{
const file = dialog . fileInfo ( Array . isArray ( dialog . value ) ? dialog . value [ 0 ] : dialog . value ) ;
callback ( egw . webserverUrl + file . downloadUrl , { alt : file.name } ) ;
2020-02-06 14:30:22 +01:00
} ) ;
// start the file selector dialog
2024-07-17 00:11:56 +02:00
document . body . append ( dialog ) ;
2020-02-06 14:30:22 +01:00
}
/ * *
* 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
2020-09-25 14:17:29 +02:00
if ( rte_toolbar && typeof rte_toolbar == "object" && this . toolbar == '' )
2020-02-06 14:30:22 +01:00
{
rte_toolbar = Object . keys ( rte_toolbar ) . map ( function ( key ) { return rte_toolbar [ key ] } ) ;
}
2020-09-25 14:17:29 +02:00
else if ( this . toolbar != '' )
{
rte_toolbar = this . toolbar . split ( ',' ) ;
}
2020-02-06 14:30:22 +01:00
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 )
{
2021-04-21 23:38:10 +02:00
try
{
this . editor . destroy ( ) ;
}
catch ( e )
{
egw ( ) . debug ( "Error destroying editor" , e ) ;
}
2020-02-06 14:30:22 +01:00
}
this . editor = null ;
this . tinymce = null ;
this . tinymce_container = null ;
this . htmlNode . remove ( ) ;
this . htmlNode = null ;
super . destroy ( ) ;
}
2024-01-23 12:54:26 +01:00
2020-02-06 14:30:22 +01:00
set_value ( _value )
{
this . _oldValue = _value ;
if ( this . editor )
{
this . editor . setContent ( _value ) ;
2024-01-23 12:54:26 +01:00
// need to defer a little, so TinyMCE does its modifications we want to counter
2024-02-05 09:54:06 +01:00
if ( this . options . applyDefaultFont )
{
window . setTimeout ( ( ) = > this . wrapTextNodes ( ) , 10 ) ;
}
2020-02-06 14:30:22 +01:00
}
else
{
if ( this . options . readonly )
{
this . htmlNode . empty ( ) . append ( _value ) ;
}
else
{
this . htmlNode . val ( _value ) ;
}
}
this . value = _value ;
}
2024-02-06 08:21:05 +01:00
/ * *
* @param boolean submit_value true : call by etemplate2 . ( getValues | submit | postSubmit ) ( )
* /
getValue ( submit_value? : boolean )
2020-02-06 14:30:22 +01:00
{
2022-05-10 18:46:12 +02:00
if ( this . editor )
{
2024-02-06 08:21:05 +01:00
// not always applying defaut font, as getValue() is called a lot, e.g. to test input is dirty
if ( this . options . applyDefaultFont && submit_value )
2024-02-05 09:54:06 +01:00
{
this . applyDefaultFont ( ) ;
}
2022-05-10 18:46:12 +02:00
return this . editor . getContent ( ) ;
}
return this . options . readonly ? this . value : this.htmlNode.val ( ) ;
2020-02-06 14:30:22 +01:00
}
2024-01-23 12:54:26 +01:00
/ * *
* Wrap text - nodes and other non - block elements with < p > < / p > as TinyMCE produces them , if we use small paragraphs
*
* This is done to create valid HTML and allow to set our default font , see applyDefaultFont ( ) ,
* which can NOT set a font on text - nodes or br !
* /
wrapTextNodes ( )
{
const body : HTMLBodyElement = this . editor ? . editorContainer . querySelector ( 'iframe' ) . contentDocument . querySelector ( 'body' ) ;
if ( ! body ) return false ;
let toWrap : ChildNode [ ] = [ ] ;
body . childNodes . forEach ( ( node ) = >
{
const text_non_block_node = node . nodeType === node . TEXT_NODE ||
node . nodeType === node . ELEMENT_NODE && typeof node . computedStyleMap !== 'undefined' && node . computedStyleMap ( ) . get ( 'display' ) ? . value !== 'block' ;
if ( text_non_block_node )
{
toWrap . push ( node ) ;
}
if ( ( ! text_non_block_node || node === body . lastChild ) && toWrap . length )
{
const wrap = body . ownerDocument . createElement ( 'p' ) ;
toWrap . forEach ( ( node ) = >
{
wrap . appendChild ( node === toWrap [ 0 ] ?
body . replaceChild ( wrap , node ) :
body . removeChild ( node ) ) ;
} ) ;
toWrap = [ ] ;
}
} ) ;
// TinyMCE inserts a BR in the first P --> remove it, if it's not the only child, as it is not wanted (moves the text down on each submit)
const firstChild = body . firstChild ;
if ( firstChild . nodeName === 'P' && firstChild . firstChild !== firstChild . lastChild && firstChild . firstChild . nodeName === 'BR' )
{
firstChild . removeChild ( firstChild . firstChild ) ;
}
return true ;
}
2022-05-23 19:36:04 +02:00
/ * *
* Apply default font and - size
* /
applyDefaultFont ( )
{
2023-11-15 16:27:50 +01:00
const edit_area = this . editor ? . editorContainer . querySelector ( 'iframe' ) . contentDocument ;
if ( ! edit_area ) return false ;
2024-01-23 12:54:26 +01:00
// we first need to wrap all text and non-block elements in p, to be able to set a font
this . wrapTextNodes ( ) ;
2022-05-23 19:36:04 +02:00
const font_family = egw . preference ( 'rte_font' , 'common' ) || 'arial, helvetica, sans-serif' ;
edit_area . querySelectorAll ( 'h1:not([style*="font-family"]),h2:not([style*="font-family"]),h3:not([style*="font-family"]),h4:not([style*="font-family"]),h5:not([style*="font-family"]),h6:not([style*="font-family"]),' +
'div:not([style*="font-family"]),li:not([style*="font-family"]),p:not([style*="font-family"]),blockquote:not([style*="font-family"]),' +
'td:not([style*="font-family"]),th:not([style*="font-family"]' ) . forEach ( ( elem ) = >
{
elem . style . fontFamily = font_family ;
} ) ;
const font_size = ( < string > egw . preference ( 'rte_font_size' , 'common' ) || '10' ) + ( egw . preference ( 'rte_font_unit' , 'common' ) || 'pt' ) ;
edit_area . querySelectorAll ( 'div:not([style*="font-size"]),li:not([style*="font-size"]),p:not([style*="font-size"]),blockquote:not([style*="font-size"]),' +
'td:not([style*="font-size"]),th:not([style*="font-size"])' ) . forEach ( ( elem ) = >
{
elem . style . fontSize = font_size ;
} ) ;
2023-11-15 16:27:50 +01:00
return true ;
2022-05-23 19:36:04 +02:00
}
2020-02-06 14:30:22 +01:00
/ * *
* 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 ) ;
2021-01-07 11:45:57 +01:00
jQuery ( this . editor . iframeElement ) . height ( h - ( this . editor . editorContainer . getElementsByClassName ( 'tox-editor-header' ) [ 0 ] ? . clientHeight +
this . editor . editorContainer . getElementsByClassName ( 'tox-statusbar' ) [ 0 ] ? . clientHeight ) ) ;
2020-02-06 14:30:22 +01:00
}
else // No TinyMCE
{
this . htmlNode . height ( this . htmlNode . height ( ) + _height ) ;
}
}
}
}
}
et2_register_widget ( et2_htmlarea , [ "htmlarea" ] ) ;