1
0
Fork 0

Adding edittable version 2023-01-14 (66785d9).

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2024-12-01 20:32:06 +01:00
parent 51b386fcf7
commit 778f9ac0bf
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
101 changed files with 56770 additions and 0 deletions

View file

@ -0,0 +1,262 @@
<?php
/**
* Table editor
*
* @author Adrian Lang <lang@cosmocode.de>
* @author Andreas Gohr <gohr@cosmocode.de>
*/
use dokuwiki\Form\Form;
use dokuwiki\Utf8;
/**
* handles all the editor related things
*
* like displaying the editor and adding custom edit buttons
*/
class action_plugin_edittable_editor extends DokuWiki_Action_Plugin
{
/**
* Register its handlers with the DokuWiki's event controller
*/
public function register(Doku_Event_Handler $controller)
{
// register custom edit buttons
$controller->register_hook('HTML_SECEDIT_BUTTON', 'BEFORE', $this, 'secedit_button');
// register our editor
$controller->register_hook('EDIT_FORM_ADDTEXTAREA', 'BEFORE', $this, 'editform');
$controller->register_hook('HTML_EDIT_FORMSELECTION', 'BEFORE', $this, 'editform');
// register preprocessing for accepting editor data
// $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_table_post');
$controller->register_hook('PLUGIN_EDITTABLE_PREPROCESS_EDITOR', 'BEFORE', $this, 'handle_table_post');
}
/**
* Add a custom edit button under each table
*
* The target 'table' is provided by DokuWiki's XHTML core renderer in the table_close() method
*
* @param Doku_Event $event
*/
public function secedit_button(Doku_Event $event)
{
if ($event->data['target'] !== 'table') return;
$event->data['name'] = $this->getLang('secedit_name');
}
/**
* Creates the actual Table Editor form
*
* @param Doku_Event $event
*/
public function editform(Doku_Event $event)
{
global $TEXT;
global $RANGE;
global $INPUT;
if ($event->data['target'] !== 'table') return;
if (!$RANGE){
// section editing failed, use default editor instead
$event->data['target'] = 'section';
return;
}
$event->stopPropagation();
$event->preventDefault();
/** @var renderer_plugin_edittable_json $Renderer our own renderer to convert table to array */
$Renderer = plugin_load('renderer', 'edittable_json', true);
$instructions = p_get_instructions($TEXT);
// Loop through the instructions
foreach ($instructions as $instruction) {
// Execute the callback against the Renderer
call_user_func_array(array(&$Renderer, $instruction[0]), $instruction[1]);
}
// output data and editor field
/** @var Doku_Form $form */
$form =& $event->data['form'];
if (is_a($form, Form::class)) { // $event->name is EDIT_FORM_ADDTEXTAREA
// data for handsontable
$form->setHiddenField('edittable_data', $Renderer->getDataJSON());
$form->setHiddenField('edittable_meta', $Renderer->getMetaJSON());
$form->addHTML('<div id="edittable__editor"></div>');
// set data from action asigned to "New Table" button in the toolbar
foreach ($INPUT->post->arr('edittable__new', []) as $k => $v) {
$form->setHiddenField("edittable__new[$k]", $v);
}
// set target and range to keep track during previews
$form->setHiddenField('target', 'table');
$form->setHiddenField('range', $RANGE);
} else { // $event->name is HTML_EDIT_FORMSELECTION
// data for handsontable
$form->addHidden('edittable_data', $Renderer->getDataJSON());
$form->addHidden('edittable_meta', $Renderer->getMetaJSON());
$form->addElement('<div id="edittable__editor"></div>');
// set data from action asigned to "New Table" button in the toolbar
foreach ($INPUT->post->arr('edittable__new', []) as $k => $v) {
$form->addHidden("edittable__new[$k]", $v);
}
// set target and range to keep track during previews
$form->addHidden('target', 'table');
$form->addHidden('range', $RANGE);
}
}
/**
* Handles a POST from the table editor
*
* This function preprocesses a POST from the table editor and converts it to plain DokuWiki markup
*
* @author Andreas Gohr <gohr@cosmocode,de>
*/
public function handle_table_post(Doku_Event $event)
{
global $TEXT;
global $INPUT;
if (!$INPUT->post->has('edittable_data')) return;
$data = json_decode($INPUT->post->str('edittable_data'), true);
$meta = json_decode($INPUT->post->str('edittable_meta'), true);
$TEXT = $this->build_table($data, $meta);
}
/**
* Create a DokuWiki table
*
* converts the table array to plain wiki markup text. pads the table so the markup is easy to read
*
* @param array $data table content for each cell
* @param array $meta meta data for each cell
* @return string
*/
public function build_table($data, $meta)
{
$table = '';
$rows = count($data);
$cols = $rows ? count($data[0]) : 0;
$colmax = $cols ? array_fill(0, $cols, 0) : array();
// find maximum column widths
for ($row = 0; $row < $rows; $row++) {
for ($col = 0; $col < $cols; $col++) {
$len = $this->strWidth($data[$row][$col]);
// alignment adds padding
if (isset($meta[$row][$col]['align']) && $meta[$row][$col]['align'] == 'center') {
$len += 4;
} else {
$len += 3;
}
// remember lenght
$meta[$row][$col]['length'] = $len;
if ($len > $colmax[$col]) $colmax[$col] = $len;
}
}
$last = '|'; // used to close the last cell
for ($row = 0; $row < $rows; $row++) {
for ($col = 0; $col < $cols; $col++) {
// minimum padding according to alignment
if (isset($meta[$row][$col]['align']) && $meta[$row][$col]['align'] == 'center') {
$lpad = 2;
$rpad = 2;
} elseif (isset($meta[$row][$col]['align']) && $meta[$row][$col]['align'] == 'right') {
$lpad = 2;
$rpad = 1;
} else {
$lpad = 1;
$rpad = 2;
}
// target width of this column
$target = $colmax[$col];
// colspanned columns span all the cells
for ($i = 1; $i < $meta[$row][$col]['colspan']; $i++) {
$target += $colmax[$col + $i];
}
// copy colspans to rowspans below if any
if ($meta[$row][$col]['colspan'] > 1) {
for ($i = 1; $i < $meta[$row][$col]['rowspan']; $i++) {
$meta[$row + $i][$col]['colspan'] = $meta[$row][$col]['colspan'];
}
}
// how much padding needs to be added?
$length = $meta[$row][$col]['length'];
$addpad = $target - $length;
// decide which side needs padding
if (isset($meta[$row][$col]['align']) && $meta[$row][$col]['align'] == 'right') {
$lpad += $addpad;
} else {
$rpad += $addpad;
}
// add the padding
$cdata = $data[$row][$col];
if (!(isset($meta[$row][$col]['hide']) && $meta[$row][$col]['hide']) || $cdata) {
$cdata = str_pad('', $lpad).$cdata.str_pad('', $rpad);
}
// finally add the cell
$last = (isset($meta[$row][$col]['tag']) && $meta[$row][$col]['tag'] == 'th') ? '^' : '|';
$table .= $last;
$table .= $cdata;
}
// close the row
$table .= "$last\n";
}
$table = rtrim($table, "\n");
return $table;
}
/**
* Return width of string
*
* @param string $str
* @return int
*/
public function strWidth($str)
{
static $callable;
if (isset($callable)) {
return $callable($str);
} else {
if (UTF8_MBSTRING) {
// count fullwidth characters as 2, halfwidth characters as 1
$callable = 'mb_strwidth';
} elseif (method_exists(Utf8\PhpString::class, 'strlen')) {
// count any characters as 1
$callable = [Utf8\PhpString::class, 'strlen'];
} else {
// fallback deprecated utf8_strlen since 2019-06-09
$callable = 'utf8_strlen';
}
return $this->strWidth($str);
}
}
}

View file

@ -0,0 +1,24 @@
<?php
/**
* handles the data that has to be written into jsinfo
*
* like displaying the editor and adding custom edit buttons
*/
class action_plugin_edittable_jsinfo extends DokuWiki_Action_Plugin
{
/**
* Register its handlers with the DokuWiki's event controller
*/
public function register(Doku_Event_Handler $controller)
{
// register custom edit buttons
$controller->register_hook('DOKUWIKI_STARTED', 'BEFORE', $this, 'fill_jsinfo');
}
public function fill_jsinfo()
{
global $JSINFO;
$JSINFO['plugins']['edittable']['default columnwidth'] = $this->getConf('default colwidth');
}
}

View file

@ -0,0 +1,94 @@
<?php
/**
* Table editor
*
* @author Adrian Lang <lang@cosmocode.de>
*/
/**
* Handles the inserting of a new table in a running edit session
*/
class action_plugin_edittable_newtable extends DokuWiki_Action_Plugin
{
/**
* Register its handlers with the DokuWiki's event controller
*/
function register(Doku_Event_Handler $controller)
{
$controller->register_hook('TOOLBAR_DEFINE', 'AFTER', $this, 'toolbar');
//$controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_newtable');
$controller->register_hook('PLUGIN_EDITTABLE_PREPROCESS_NEWTABLE', 'BEFORE', $this, 'handle_newtable');
}
/**
* Add a button for inserting tables to the toolbar array
*
* @param Doku_Event $event
*/
public function toolbar(Doku_Event $event)
{
$event->data[] = array(
'title' => $this->getLang('add_table'),
'type' => 'NewTable',
'icon' => '../../plugins/edittable/images/add_table.png',
'block' => true
);
}
/**
* Handle the click on the new table button in the toolbar
*
* @param Doku_Event $event
*/
public function handle_newtable(Doku_Event $event)
{
global $INPUT;
global $TEXT;
if (!$INPUT->post->has('edittable__new')) return;
/*
* $fields['pre'] has all data before the selection when the "Insert table" button was clicked
* $fields['text'] has all data inside the selection when the "Insert table" button was clicked
* $fields['suf'] has all data after the selection when the "Insert table" button was clicked
* $TEXT has the table created by the editor (from action_plugin_edittable_editor::handle_table_post())
*/
$fields = $INPUT->post->arr('edittable__new');
// clean the fields (undos formText()) and update the post and request arrays
$fields['pre'] = cleanText($fields['pre']);
$fields['text'] = cleanText($fields['text']);
$fields['suf'] = cleanText($fields['suf']);
$INPUT->post->set('edittable__new', $fields);
$event->data = act_clean($event->data);
switch ($event->data) {
case 'preview':
// preview view of a table edit
$INPUT->post->set('target', 'table');
break;
case 'edit':
// edit view of a table (first edit)
$INPUT->post->set('target', 'table');
$TEXT = "^ ^ ^\n";
foreach (explode("\n", $fields['text']) as $line) {
$TEXT .= "| $line | |\n";
}
break;
case 'draftdel':
// not sure if/how this would happen, we restore all data and hand over to section edit
$INPUT->post->set('target', 'section');
$TEXT = $fields['pre'].$fields['text'].$fields['suf'];
$event->data = 'edit';
break;
case 'save':
// return to edit page
$INPUT->post->set('target', 'section');
$TEXT = $fields['pre'].$TEXT.$fields['suf'];
$event->data = 'edit';
break;
}
}
}

View file

@ -0,0 +1,38 @@
<?php
/**
* Table editor
*
* @author Andreas Gohr <gohr@cosmocode.de>
*/
use dokuwiki\Extension\Event;
/**
* just intercepts ACTION_ACT_PREPROCESS and emits two new events
*
* We have two action components handling above event but need them to execute in a specific order.
* That's currently not possible to guarantee, so we catch the event only once and emit two of our own
* in the right order. Once DokuWiki supports a sort we can skip this.
*/
class action_plugin_edittable_preprocess extends DokuWiki_Action_Plugin
{
/**
* Register its handlers with the DokuWiki's event controller
*/
public function register(Doku_Event_Handler $controller)
{
// register preprocessing for accepting editor data
$controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_preprocess');
}
/**
* See class description for WTF we're doing here
*
* @param Doku_Event $event
*/
public function handle_preprocess(Doku_Event $event)
{
Event::createAndTrigger('PLUGIN_EDITTABLE_PREPROCESS_EDITOR', $event->data);
Event::createAndTrigger('PLUGIN_EDITTABLE_PREPROCESS_NEWTABLE', $event->data);
}
}

View file

@ -0,0 +1,38 @@
<?php
/**
* Table editor
*
* @author Adrian Lang <lang@cosmocode.de>
*/
/**
* redirect to the section containg the table
*/
class action_plugin_edittable_sectionjump extends DokuWiki_Action_Plugin
{
/**
* Register its handlers with the DokuWiki's event controller
*/
function register(Doku_Event_Handler $controller)
{
$controller->register_hook('ACTION_SHOW_REDIRECT', 'BEFORE', $this, 'jump_to_section');
}
/**
* Jump after save to the section containing this table
*
* @param Doku_Event $event
*/
function jump_to_section($event)
{
global $INPUT;
if (!$INPUT->has('edittable_data')) return;
global $PRE;
if (preg_match_all('/^\s*={2,}([^=\n]+)/m', $PRE, $match, PREG_SET_ORDER)) {
$check = false; //Byref
$match = array_pop($match);
$event->data['fragment'] = sectionID($match[1], $check);
}
}
}