DokuWiki

It's better when it's simple

User Tools

Site Tools


Sidebar

Translations of this page?:

Learn about DokuWiki

Advanced Use

Corporate Use

Our Community


Follow us on Facebook, Twitter and other social networks.

devel:section_editor

Custom Section Editors in plugins

DokuWiki allows plugins to provide custom edit forms for their syntax elements. First, you need to mark a DIV element in the XHTML output as editable. Optional you can also change sectiontype name when you like. Next you have to provide an editor for your content.

Marking the DIV

The XHTML renderer provides two methods for the DIV section edit marking:

These are used to create markers in the xhtml output of the renderer and after the render is done, a replacement or remove of the markers by html_secedit($text,$show=true) is executed.

Note that finishSectionEdit() assumes correctly nested edit sections. To use this methods, you will need to save your byte positions in your handle() method of your syntax plugin.

Before DokuWiki release “Greebo” the method signatures were different:

  • startSectionEdit($bytepos_start, $section_type, $section_title = null)
  • finishSectionEdit($bytepos_end = null)

But do not worry. You can easily write backwards compatible code by checking the definition of SEC_EDIT_PATTERN as done in the example code below.

A step-by-step example for a syntax plugin:

syntax.php
    public function render($format, Doku_Renderer $renderer, $data) {
        $class = '';
        // Add section edit infos only in XHTML renderers which are
        // sufficiently new
        if ($format === 'xhtml' && method_exists($renderer, 'startSectionEdit')) {
            // Prepare section edit data in a backwards compatible way.
            // FIXME: Insert plugin name here as 'target' (previously section type)
            $sectionEditData = ['target' => 'plugin_exampleplugin'];
            if (!defined('SEC_EDIT_PATTERN')) {
                // backwards-compatibility for Frusterick Manners (2017-02-19)
                $sectionEditData = 'plugin_exampleplugin';
            }
 
            /* @var Doku_Renderer_xhtml $renderer */
            $class = $renderer->startSectionEdit($data['bytepos_start'],
                                                 $sectionEditData);
        }
        $renderer->doc .= '<div class="' . $class . '">';
 
        // FIXME: Put your content here
 
        $renderer->doc .= '</div>';
        // Add section edit infos only in XHTML renderers which are
        // sufficiently new
        if ($format === 'xhtml' &&
            method_exists($renderer, 'finishSectionEdit')) {
            /* @var Doku_Renderer_xhtml $renderer */
            $renderer->finishSectionEdit($data['bytepos_end']);
        }
    }

If the section of your plugin has a distinguishable name, you may add it to the method call as well. In this case the old and new method signatures are to different to achieve backwards compatibility by just assigning different values to $sectionEditData. It requires two different method calls:

syntax.php
    public function render($format, Doku_Renderer $renderer, $data) {
        $class = '';
        // Add section edit infos only in XHTML renderers which are
        // sufficiently new
        if ($format === 'xhtml' && method_exists($renderer, 'startSectionEdit')) {
            // Call 'startSectionEdit' in two different ways...
            if (defined('SEC_EDIT_PATTERN')) {
                // FIXME: Insert plugin name here as 'target'
                // and section name as 'name'
                $sectionEditData = ['target' => 'plugin_exampleplugin',
                                    'name' => 'section-name'];
 
                /* @var Doku_Renderer_xhtml $renderer */
                $class = $renderer->startSectionEdit($data['bytepos_start'],
                                                     $sectionEditData);
            } else {
                // backwards-compatibility for Frusterick Manners (2017-02-19)
                // FIXME: Insert plugin name here as section type
                // and section name as title
                /* @var Doku_Renderer_xhtml $renderer */
                $class = $renderer->startSectionEdit($data['bytepos_start'],
                             'plugin_exampleplugin', 'section-name');
            }
        }
        $renderer->doc .= '<div class="' . $class . '">';
 
        // FIXME: Put your content here
 
        $renderer->doc .= '</div>';
        // Add section edit infos only in XHTML renderers which are
        // sufficiently new
        if ($format === 'xhtml' &&
            method_exists($renderer, 'finishSectionEdit')) {
            /* @var Doku_Renderer_xhtml $renderer */
            $renderer->finishSectionEdit($data['bytepos_end']);
        }
    }

You may have noticed that I used $data['bytepos_start'] and $data['bytepos_end'] which your handler has to provide:

syntax.php
    public function handle($match, $state, $pos, Doku_Handler $handler) {
        $data = array();
 
        // FIXME: Do your handling
 
        return $data + array('bytepos_start' => $pos,
                             'bytepos_end'   => $pos + strlen($match));
    }

If you did not provide a special section name as third argument in the startSectionEdit() call, you must hook the HTML_SECEDIT_BUTTON event providing a general-purpose name used for all section with target/sectiontype plugin_exampleplugin.

action.php
    public function register(Doku_Event_Handler $controller) {
        $controller->register_hook('HTML_SECEDIT_BUTTON', 'BEFORE', $this, '_editbutton');
    }
 
    public function _editbutton(Doku_Event $event, $param) {
        // FIXME: Insert plugin name
        if ($event->data['target'] !== 'plugin_exampleplugin') {
            return;
        }
 
        // FIXME: Add your lang field to your lang files
        $event->data['name'] = $this->getLang('sectioneditname');
    }

Now your users will get a special edit button for your syntax elements. You will probably want to style it:

style.css
/* FIXME: Insert plugin name here */
div.dokuwiki div.editbutton_plugin_exampleplugin {
 
}
 
/* FIXME: Insert plugin name here */
div.dokuwiki div.editbutton_plugin_exampleplugin form input.button {
 
}

Now there is a nice button, but still no form – the users have to use the plain wikitext editor!

Providing the form

For this step, we need to hook HTML_EDIT_FORMSELECTION in an action plugin.

action.php
    public function register(Doku_Event_Handler $controller) {
        $controller->register_hook('HTML_SECEDIT_BUTTON', 'BEFORE', $this, '_editbutton');
        $controller->register_hook('HTML_EDIT_FORMSELECTION', 'BEFORE', $this, '_editform');
    }
 
    public function _editform(Doku_Event $event, $param) {
        global $TEXT;
        // FIXME: Insert plugin name
        if ($event->data['target'] !== 'plugin_exampleplugin') {
            // Not an edit for exampleplugin
            return;
        }
        $event->preventDefault();
 
        // FIXME: Remove this if you want the default edit intro
        unset($event->data['intro_locale']);
 
        // FIXME: Remove this if you want a media manager fallback link
        // You will probably want a media link if you want a normal toolbar
        $event->data['media_manager'] = false;
 
        // FIXME: Create the lang files edit_intro.txt
        echo $this->locale_xhtml('edit_intro');
 
        // FIXME: Add real edit form
        $attr = array();
        if (!$event->data['wr']) $attr['readonly'] = 'readonly';
        $event->data['form']->addElement(form_makeWikiText($TEXT, $attr));
    }

The form you provided here has to be rendered into wiki text again on POST.

action.php
    public function register(Doku_Event_Handler $controller) {
        $controller->register_hook('HTML_SECEDIT_BUTTON', 'BEFORE', $this, '_editbutton');
        $controller->register_hook('HTML_EDIT_FORMSELECTION', 'BEFORE', $this, '_editform');
        $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, '_editpost');
    }
 
    public function _handle_edit_post(Doku_Event $event) {
        // FIXME: Insert the name of a form field you use
        if (!isset($_POST['some_of_the_form_fields_you_use'])) {
            return;
        }
        global $TEXT;
 
        // FIXME: Create wikitext from post
        $TEXT = magic_form_to_wiki_function();
    }

Example implementations

The Data Plugin and the EditTable Plugin uses this mechanisms. Have a look on their source.

devel/section_editor.txt · Last modified: 2018-04-06 21:42 by LarsDW223