====== 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: * [[xref>startSectionEdit()|startSectionEdit($start, $data)]] \\ Returns a classname and save the position. * [[xref>finishSectionEdit()|finishSectionEdit($end = null, $hid = null)]] \\ Places a marker with data about this editable section. 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 [[xref>html_secedit|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: 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 .= '
'; // FIXME: Put your content here $renderer->doc .= '
'; // 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: 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 .= '
'; // FIXME: Put your content here $renderer->doc .= '
'; // 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: 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 [[devel:event:HTML_SECEDIT_BUTTON]] event providing a general-purpose name used for all section with target/sectiontype ''plugin_exampleplugin''. 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: /* 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 [[devel:event:HTML_EDIT_FORMSELECTION|HTML_EDIT_FORMSELECTION]] in an action plugin. 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. 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 [[plugin:Data]] Plugin and the [[plugin:EditTable]] Plugin uses this mechanisms. Have a look on their source.