- Methods: All Methods Documented Methods Hide Methods
- Source: Display Source Hide Source
- Variables: Show Variables Hide Variables
Class ::xowiki::formfield::richtext::tinymce
::xowiki::formfield::richtext::tinymce create ... \TinyMCE XoWiki richtext-editor integration.
[ -additionalConfig (default "") ] \
[ -customConfig (default "") ] \
[ -extraPlugins (default "") ]
Defined in /var/www/openacs.org/packages/xowiki/tcl/form-field-procs.tcl
- Documented Parameters:
- extraPlugins
- a list of couples 'pluginId' 'pluginURL' specifying additional plugins.
- customConfig
- a configuration dict that will completely override configuration coming from the parameters.
- additionalConfig
- 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:
- https://www.tiny.cloud/docs/tinymce/latest/
Class Relations
::xotcl::Class create ::xowiki::formfield::richtext::tinymce \ -superclass ::xowiki::formfield::richtextMethods (to be applied on instances)
additionalConfig (setter)
compute_config (scripted)
# # 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]customConfig (setter)
extraPlugins (setter)
initialize (scripted)
next set :widget_type richtextrender_input (scripted)
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
- Methods: All Methods Documented Methods Hide Methods
- Source: Display Source Hide Source
- Variables: Show Variables Hide Variables