Class ::xowiki::formfield::richtext::tinymce (public)

 ::xotcl::Class ::xowiki::formfield::richtext::tinymce[i] \
    [ -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
[ hide source ] | [ make this the default ]
Show another procedure: