Class ::xowiki::formfield::richtext::tinymce (public)
::xotcl::Class ::xowiki::formfield::richtext::tinymce \ [ -extraPlugins extraPlugins ] [ -customConfig customConfig ] \ [ -additionalConfig additionalConfig ]
Defined in /var/www/openacs.org/packages/xowiki/tcl/form-field-procs.tcl
TinyMCE XoWiki richtext-editor integration.
- Switches:
- -extraPlugins (optional)
- a list of couples 'pluginId' 'pluginURL' specifying additional plugins.
- -customConfig (optional)
- a configuration dict that will completely override configuration coming from the parameters.
- -additionalConfig (optional)
- a configuration dict that will be merged with configuration coming from the parameters. Values specified here will take precedence on the parameter values.
- See Also:
- Testcases:
- No testcase defined.
Source code: ::nsf::object::alloc ::xotcl::Class ::xowiki::formfield::richtext::tinymce {set :__default_metaclass ::xotcl::Class set :__default_superclass ::xotcl::Object set :editor_mixin 1} ::xowiki::formfield::richtext::tinymce instproc initialize {} { next set :widget_type richtext } ::xowiki::formfield::richtext::tinymce instproc compute_config {} { # # Here we compute the editor config, merging requested one with # systems configurations and presets. # # @return a dict # if {${:customConfig} ne ""} { set default_config ${:customConfig} } else { set default_config [::richtext::tinymce::default_config] } set config [list] if {${:extraPlugins} ne ""} { set extra_plugins [::richtext::tinymce::serialize_options ${:extraPlugins}] lappend config external_plugins "{$extra_plugins}" } # # Inline means the editor will not be displayed unless we click on # its content. We also supported an inplace mode, where the editor # does something similar, but with explicit save and cancel # buttons underneath. # # As the difference is subtle and there is currently not a # requirement for inplace mode, we treat both mode the same as # "inline". # set inline_p [expr {${:displayMode} in {"inplace" "inline"} ? true : false}] lappend config inline $inline_p # # Inject a reference to the current object, useful e.g. for # plugins to know where to point to. # lappend config object_id [${:object} item_id] set config [dict merge [list language [ad_conn language]] $default_config [:preset_conf] ${:additionalConfig} $config] } ::xowiki::formfield::richtext::tinymce instproc render_input {} { set disabled [:is_disabled] set is_repeat_template [:is_repeat_template_p] # # Field is disabled. We simply render as a div. # if {$disabled && !$is_repeat_template} { :render_as_div return } set config [:compute_config] set inline_p [dict get $config inline] set config [::richtext::tinymce::serialize_options $config] # # Include the relevant javascript. # ::richtext::tinymce::add_editor -init=false if {$is_repeat_template} { # # A repeated field. We use a MutationObserver to detect whenever # a new field has been appended and we enhance it on the fly. # ::template::add_body_handler -identifier richtext_tinymce_editor_init_repeat -event load -script { function richtext_tinymce_editor_init_repeat(id, containerId, editorConfig) { // Via this pattern we recognize fields that are // appended to the DOM that are relevant to our repeated // formfield. const namePattern = id.replace(/^F\.[^\.]+\./g, '').replaceAll(/\.[0-9]+/g, '.[1-9][0-9]*'); const targetNode = document.getElementById(containerId); const config = { childList: true, subtree: true }; const callback = (mutationList, observer) => { for (const mutation of mutationList) { for (const node of mutation.addedNodes) { // Skip text nodes if (!node.querySelectorAll) { continue; } for (const inputField of node.querySelectorAll('[name]')) { // Skip things that are not instances of our field. if (!inputField.getAttribute('name').match(`^${namePattern}\$`)) { continue; } editorConfig.selector = `[id='${inputField.id}']`; tinyMCE.init(editorConfig); } } } }; const observer = new MutationObserver(callback); observer.observe(targetNode, config); } } # # The repeat container is the topmost ancestor of this # formfield. This is true both for regular and compound repeated # fields. # set obj [self] while {[$obj exists parent_field]} { set parent_field [$obj set parent_field] set obj $parent_field } set repeat_container_id [$parent_field id] ::template::add_body_handler -event load -script [subst -nocommands { richtext_tinymce_editor_init_repeat('${:id}', '${repeat_container_id}', {$config}); }] } else { # # A regular non-repeated field. # ::template::add_body_handler -identifier richtext_tinymce_editor_init -event load -script { function richtext_tinymce_editor_init(elementId, fieldName, editorConfig) { editorConfig.selector = `[id='${elementId}']`; tinyMCE.init(editorConfig).then((editors) => { const replacedField = document.querySelector(`input[type=hidden][name='${elementId}']`); replacedField?.form.addEventListener('submit', (evt) => { replacedField.name = fieldName; replacedField.value = editors[0].getContent(); }); }); } } ::template::add_body_handler -event load -script [subst -nocommands { richtext_tinymce_editor_init('${:id}', '${:name}', {$config}); }] } if {$inline_p} { # # In inline mode, the markup we send is a div, that TinyMCE # replaces with an editor + a hidden input field with our same id, # but no name attribute. # # Before the form is submitted, we get the content from the # editor and store it as the hidden field value, then set the # name attribute as XoWiki expects it. This logic is found in # both the promise handler for TinyMCE.init up in the js # functions. # # We do not want to stop inheritance here, because we want to be # able to plug behavior in subclasses of richtext. # set :render_as_div_p true } next } ::xowiki::formfield::richtext::tinymce instparametercmd customConfig ::xowiki::formfield::richtext::tinymce instparametercmd extraPlugins ::xowiki::formfield::richtext::tinymce instparametercmd additionalConfig ::nsf::relation::set ::xowiki::formfield::richtext::tinymce superclass ::xowiki::formfield::richtext ::nx::slotObj -container slot ::xowiki::formfield::richtext::tinymce ::xowiki::formfield::richtext::tinymce::slot eval {set :__parameter { {extraPlugins ""} {customConfig ""} {additionalConfig ""} }} ::nsf::object::alloc ::xotcl::Attribute ::xowiki::formfield::richtext::tinymce::slot::additionalConfig {set :accessor public set :configurable true set :convert false set :default {} set :defaultmethods {} set :disposition alias set :domain ::xowiki::formfield::richtext::tinymce set :incremental 0 set :manager ::xowiki::formfield::richtext::tinymce::slot::additionalConfig set :methodname additionalConfig set :multiplicity 1..1 set :name additionalConfig set :per-object false set :position 0 set :required false set :substdefault 0b111 set :trace none : init} ::nsf::object::alloc ::xotcl::Attribute ::xowiki::formfield::richtext::tinymce::slot::customConfig {set :accessor public set :configurable true set :convert false set :default {} set :defaultmethods {} set :disposition alias set :domain ::xowiki::formfield::richtext::tinymce set :incremental 0 set :manager ::xowiki::formfield::richtext::tinymce::slot::customConfig set :methodname customConfig set :multiplicity 1..1 set :name customConfig set :per-object false set :position 0 set :required false set :substdefault 0b111 set :trace none : init} ::nsf::object::alloc ::xotcl::Attribute ::xowiki::formfield::richtext::tinymce::slot::extraPlugins {set :accessor public set :configurable true set :convert false set :default {} set :defaultmethods {} set :disposition alias set :domain ::xowiki::formfield::richtext::tinymce set :incremental 0 set :manager ::xowiki::formfield::richtext::tinymce::slot::extraPlugins set :methodname extraPlugins set :multiplicity 1..1 set :name extraPlugins set :per-object false set :position 0 set :required false set :substdefault 0b111 set :trace none : init}XQL Not present: Generic, PostgreSQL, Oracle