From 62069b6490d113a6c8a179a364f92f8ebc5040a4 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Sat, 15 Jul 2017 10:53:16 +0100 Subject: [PATCH] Add column plugin and re-flow columns on mobile --- public_html/lib/plugins/columns/action.php | 675 ++++++++++++++++++ .../lib/plugins/columns/conf/default.php | 12 + .../lib/plugins/columns/conf/metadata.php | 12 + .../lib/plugins/columns/lang/en/lang.php | 12 + .../lib/plugins/columns/lang/en/settings.php | 12 + .../lib/plugins/columns/lang/es/lang.php | 12 + .../lib/plugins/columns/lang/es/settings.php | 12 + .../lib/plugins/columns/lang/ru/lang.php | 12 + .../lib/plugins/columns/lang/ru/settings.php | 12 + .../lib/plugins/columns/plugin.info.txt | 7 + public_html/lib/plugins/columns/print.css | 24 + public_html/lib/plugins/columns/rewriter.php | 190 +++++ public_html/lib/plugins/columns/style.css | 43 ++ public_html/lib/plugins/columns/syntax.php | 611 ++++++++++++++++ public_html/lib/tpl/e/css/modifications.css | 11 + 15 files changed, 1657 insertions(+) create mode 100644 public_html/lib/plugins/columns/action.php create mode 100644 public_html/lib/plugins/columns/conf/default.php create mode 100644 public_html/lib/plugins/columns/conf/metadata.php create mode 100644 public_html/lib/plugins/columns/lang/en/lang.php create mode 100644 public_html/lib/plugins/columns/lang/en/settings.php create mode 100644 public_html/lib/plugins/columns/lang/es/lang.php create mode 100644 public_html/lib/plugins/columns/lang/es/settings.php create mode 100644 public_html/lib/plugins/columns/lang/ru/lang.php create mode 100644 public_html/lib/plugins/columns/lang/ru/settings.php create mode 100644 public_html/lib/plugins/columns/plugin.info.txt create mode 100644 public_html/lib/plugins/columns/print.css create mode 100644 public_html/lib/plugins/columns/rewriter.php create mode 100644 public_html/lib/plugins/columns/style.css create mode 100644 public_html/lib/plugins/columns/syntax.php diff --git a/public_html/lib/plugins/columns/action.php b/public_html/lib/plugins/columns/action.php new file mode 100644 index 00000000..c96e54b5 --- /dev/null +++ b/public_html/lib/plugins/columns/action.php @@ -0,0 +1,675 @@ + + */ + +/* Must be run within Dokuwiki */ +if(!defined('DOKU_INC')) die(); + +if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/'); +require_once(DOKU_PLUGIN . 'action.php'); +require_once(DOKU_PLUGIN . 'columns/rewriter.php'); + +class action_plugin_columns extends DokuWiki_Action_Plugin { + + private $block; + private $currentBlock; + private $currentSectionLevel; + private $sectionEdit; + + /** + * Register callbacks + */ + public function register(Doku_Event_Handler $controller) { + $controller->register_hook('PARSER_HANDLER_DONE', 'AFTER', $this, 'handle'); + } + + /** + * + */ + public function handle(&$event, $param) { + $this->reset(); + $this->buildLayout($event); + $rewriter = new instruction_rewriter(); + foreach ($this->block as $block) { + $block->processAttributes($event); + $rewriter->addCorrections($block->getCorrections()); + } + $rewriter->process($event->data->calls); + } + + /** + * Find all columns instructions and construct columns layout based on them + */ + private function buildLayout(&$event) { + $calls = count($event->data->calls); + for ($c = 0; $c < $calls; $c++) { + $call =& $event->data->calls[$c]; + switch ($call[0]) { + case 'section_open': + $this->currentSectionLevel = $call[1][0]; + $this->currentBlock->openSection(); + break; + + case 'section_close': + $this->currentBlock->closeSection($c); + break; + + case 'plugin': + if ($call[1][0] == 'columns') { + $this->handleColumns($c, $call[1][1][0], $this->detectSectionEdit($event->data->calls, $c)); + } + break; + } + } + } + + /** + * Reset internal state + */ + private function reset() { + $this->block = array(); + $this->block[0] = new columns_root_block(); + $this->currentBlock = $this->block[0]; + $this->currentSectionLevel = 0; + $this->sectionEdit = array(); + } + + /** + * + */ + private function detectSectionEdit($call, $start) { + $result = null; + $calls = count($call); + for ($c = $start + 1; $c < $calls; $c++) { + switch ($call[$c][0]) { + case 'section_close': + case 'p_open': + case 'p_close': + /* Skip these instructions */ + break; + + case 'header': + if (end($this->sectionEdit) != $c) { + $this->sectionEdit[] = $c; + $result = $call[$c][2]; + } + break 2; + + case 'plugin': + if ($call[$c][1][0] == 'columns') { + break; + } else { + break 2; + } + + default: + break 2; + } + } + return $result; + } + + /** + * + */ + private function handleColumns($callIndex, $state, $sectionEdit) { + switch ($state) { + case DOKU_LEXER_ENTER: + $this->currentBlock = new columns_block(count($this->block), $this->currentBlock); + $this->currentBlock->addColumn($callIndex, $this->currentSectionLevel); + $this->currentBlock->startSection($sectionEdit); + $this->block[] = $this->currentBlock; + break; + + case DOKU_LEXER_MATCHED: + $this->currentBlock->addColumn($callIndex, $this->currentSectionLevel); + $this->currentBlock->startSection($sectionEdit); + break; + + case DOKU_LEXER_EXIT: + $this->currentBlock->endSection($sectionEdit); + $this->currentBlock->close($callIndex); + $this->currentBlock = $this->currentBlock->getParent(); + break; + } + } +} + +class columns_root_block { + + private $sectionLevel; + private $call; + + /** + * Constructor + */ + public function __construct() { + $this->sectionLevel = 0; + $this->call = array(); + } + + /** + * + */ + public function getParent() { + return $this; + } + + /** + * Collect stray tags + */ + public function addColumn($callIndex, $sectionLevel) { + $this->call[] = $callIndex; + } + + /** + * + */ + public function openSection() { + $this->sectionLevel++; + } + + /** + * + */ + public function closeSection($callIndex) { + if ($this->sectionLevel > 0) { + $this->sectionLevel--; + } + else { + $this->call[] = $callIndex; + } + } + + /** + * + */ + public function startSection($callInfo) { + } + + /** + * + */ + public function endSection($callInfo) { + } + + /** + * Collect stray tags + */ + public function close($callIndex) { + $this->call[] = $callIndex; + } + + /** + * + */ + public function processAttributes(&$event) { + } + + /** + * Delete all captured tags + */ + public function getCorrections() { + $correction = array(); + foreach ($this->call as $call) { + $correction[] = new instruction_rewriter_delete($call); + } + return $correction; + } +} + +class columns_block { + + private $id; + private $parent; + private $column; + private $currentColumn; + private $closed; + + /** + * Constructor + */ + public function __construct($id, $parent) { + $this->id = $id; + $this->parent = $parent; + $this->column = array(); + $this->currentColumn = null; + $this->closed = false; + } + + /** + * + */ + public function getParent() { + return $this->parent; + } + + /** + * + */ + public function addColumn($callIndex, $sectionLevel) { + if ($this->currentColumn != null) { + $this->currentColumn->close($callIndex); + } + $this->currentColumn = new columns_column($callIndex, $sectionLevel); + $this->column[] = $this->currentColumn; + } + + /** + * + */ + public function openSection() { + $this->currentColumn->openSection(); + } + + /** + * + */ + public function closeSection($callIndex) { + $this->currentColumn->closeSection($callIndex); + } + + /** + * + */ + public function startSection($callInfo) { + $this->currentColumn->startSection($callInfo); + } + + /** + * + */ + public function endSection($callInfo) { + $this->currentColumn->endSection($callInfo); + } + + /** + * + */ + public function close($callIndex) { + $this->currentColumn->close($callIndex); + $this->closed = true; + } + + /** + * Convert raw attributes and layout information into column attributes + */ + public function processAttributes(&$event) { + $columns = count($this->column); + for ($c = 0; $c < $columns; $c++) { + $call =& $event->data->calls[$this->column[$c]->getOpenCall()]; + if ($c == 0) { + $this->loadBlockAttributes($call[1][1][1]); + $this->column[0]->addAttribute('columns', $columns); + $this->column[0]->addAttribute('class', 'first'); + } + else { + $this->loadColumnAttributes($c, $call[1][1][1]); + if ($c == ($columns - 1)) { + $this->column[$c]->addAttribute('class', 'last'); + } + } + $this->column[$c]->addAttribute('block-id', $this->id); + $this->column[$c]->addAttribute('column-id', $c + 1); + $call[1][1][1] = $this->column[$c]->getAttributes(); + } + } + + /** + * Convert raw attributes into column attributes + */ + private function loadBlockAttributes($attribute) { + $column = -1; + $nextColumn = -1; + foreach ($attribute as $a) { + list($name, $temp) = $this->parseAttribute($a); + if ($name == 'width') { + if (($column == -1) && array_key_exists('column-width', $temp)) { + $this->column[0]->addAttribute('table-width', $temp['column-width']); + } + $nextColumn = $column + 1; + } + if (($column >= 0) && ($column < count($this->column))) { + $this->column[$column]->addAttributes($temp); + } + $column = $nextColumn; + } + } + + /** + * Convert raw attributes into column attributes + */ + private function loadColumnAttributes($column, $attribute) { + foreach ($attribute as $a) { + list($name, $temp) = $this->parseAttribute($a); + $this->column[$column]->addAttributes($temp); + } + } + + /** + * + */ + private function parseAttribute($attribute) { + static $syntax = array( + '/^left|right|center|justify$/' => 'text-align', + '/^top|middle|bottom$/' => 'vertical-align', + '/^[lrcjtmb]{1,2}$/' => 'align', + '/^continue|\.{3}$/' => 'continue', + '/^(\*?)((?:-|(?:\d+\.?|\d*\.\d+)(?:%|em|px|cm|mm|in|pt)))(\*?)$/' => 'width' + ); + $result = array(); + $attributeName = ''; + foreach ($syntax as $pattern => $name) { + if (preg_match($pattern, $attribute, $match) == 1) { + $attributeName = $name; + break; + } + } + switch ($attributeName) { + case 'text-align': + case 'vertical-align': + $result[$attributeName] = $match[0]; + break; + + case 'align': + $result = $this->parseAlignAttribute($match[0]); + break; + + case 'continue': + $result[$attributeName] = 'on'; + break; + + case 'width': + $result = $this->parseWidthAttribute($match); + break; + } + return array($attributeName, $result); + } + + /** + * + */ + private function parseAlignAttribute($syntax) { + $result = array(); + $align1 = $this->getAlignStyle($syntax{0}); + if (strlen($syntax) == 2) { + $align2 = $this->getAlignStyle($syntax{1}); + if ($align1 != $align2) { + $result[$align1] = $this->getAlignment($syntax{0}); + $result[$align2] = $this->getAlignment($syntax{1}); + } + } + else{ + $result[$align1] = $this->getAlignment($syntax{0}); + } + return $result; + } + + /** + * + */ + private function getAlignStyle($align) { + return preg_match('/[lrcj]/', $align) ? 'text-align' : 'vertical-align'; + } + + /** + * + */ + private function parseWidthAttribute($syntax) { + $result = array(); + if ($syntax[2] != '-') { + $result['column-width'] = $syntax[2]; + } + $align = $syntax[1] . '-' . $syntax[3]; + if ($align != '-') { + $result['text-align'] = $this->getAlignment($align); + } + return $result; + } + + /** + * Returns column text alignment + */ + private function getAlignment($syntax) { + static $align = array( + 'l' => 'left', '-*' => 'left', + 'r' => 'right', '*-' => 'right', + 'c' => 'center', '*-*' => 'center', + 'j' => 'justify', + 't' => 'top', + 'm' => 'middle', + 'b' => 'bottom' + ); + if (array_key_exists($syntax, $align)) { + return $align[$syntax]; + } + else { + return ''; + } + } + + /** + * Returns a list of corrections that have to be applied to the instruction array + */ + public function getCorrections() { + if ($this->closed) { + $correction = $this->fixSections(); + } + else { + $correction = $this->deleteColumns(); + } + return $correction; + } + + /** + * Re-write section open/close instructions to produce valid HTML + * See columns:design#section_fixing for details + */ + private function fixSections() { + $correction = array(); + foreach ($this->column as $column) { + $correction = array_merge($correction, $column->getCorrections()); + } + return $correction; + } + + /** + * + */ + private function deleteColumns() { + $correction = array(); + foreach ($this->column as $column) { + $correction[] = $column->delete(); + } + return $correction; + } +} + +class columns_attributes_bag { + + private $attribute; + + /** + * Constructor + */ + public function __construct() { + $this->attribute = array(); + } + + /** + * + */ + public function addAttribute($name, $value) { + $this->attribute[$name] = $value; + } + + /** + * + */ + public function addAttributes($attribute) { + if (is_array($attribute) && (count($attribute) > 0)) { + $this->attribute = array_merge($this->attribute, $attribute); + } + } + + /** + * + */ + public function getAttribute($name) { + $result = ''; + if (array_key_exists($name, $this->attribute)) { + $result = $this->attribute[$name]; + } + return $result; + } + + /** + * + */ + public function getAttributes() { + return $this->attribute; + } +} + +class columns_column extends columns_attributes_bag { + + private $open; + private $close; + private $sectionLevel; + private $sectionOpen; + private $sectionClose; + private $sectionStart; + private $sectionEnd; + + /** + * Constructor + */ + public function __construct($open, $sectionLevel) { + parent::__construct(); + + $this->open = $open; + $this->close = -1; + $this->sectionLevel = $sectionLevel; + $this->sectionOpen = false; + $this->sectionClose = -1; + $this->sectionStart = null; + $this->sectionEnd = null; + } + + /** + * + */ + public function getOpenCall() { + return $this->open; + } + + /** + * + */ + public function openSection() { + $this->sectionOpen = true; + } + + /** + * + */ + public function closeSection($callIndex) { + if ($this->sectionClose == -1) { + $this->sectionClose = $callIndex; + } + } + + /** + * + */ + public function startSection($callInfo) { + $this->sectionStart = $callInfo; + } + + /** + * + */ + public function endSection($callInfo) { + $this->sectionEnd = $callInfo; + } + + /** + * + */ + public function close($callIndex) { + $this->close = $callIndex; + } + + /** + * + */ + public function delete() { + return new instruction_rewriter_delete($this->open); + } + + /** + * Re-write section open/close instructions to produce valid HTML + * See columns:design#section_fixing for details + */ + public function getCorrections() { + $result = array(); + $deleteSectionClose = ($this->sectionClose != -1); + $closeSection = $this->sectionOpen; + if ($this->sectionStart != null) { + $result = array_merge($result, $this->moveStartSectionEdit()); + } + if (($this->getAttribute('continue') == 'on') && ($this->sectionLevel > 0)) { + $result[] = $this->openStartSection(); + /* Ensure that this section will be properly closed */ + $deleteSectionClose = false; + $closeSection = true; + } + if ($deleteSectionClose) { + /* Remove first section_close from the column to prevent in the middle of the column */ + $result[] = new instruction_rewriter_delete($this->sectionClose); + } + if ($closeSection || ($this->sectionEnd != null)) { + $result = array_merge($result, $this->closeLastSection($closeSection)); + } + return $result; + } + + /** + * Moves section_edit at the start of the column out of the column + */ + private function moveStartSectionEdit() { + $result = array(); + $result[0] = new instruction_rewriter_insert($this->open); + $result[0]->addPluginCall('columns', array(987, $this->sectionStart - 1), DOKU_LEXER_MATCHED); + return $result; + } + + /** + * Insert section_open at the start of the column + */ + private function openStartSection() { + $insert = new instruction_rewriter_insert($this->open + 1); + $insert->addCall('section_open', array($this->sectionLevel)); + return $insert; + } + + /** + * Close last open section in the column + */ + private function closeLastSection($closeSection) { + $result = array(); + $result[0] = new instruction_rewriter_insert($this->close); + if ($closeSection) { + $result[0]->addCall('section_close', array()); + } + if ($this->sectionEnd != null) { + $result[0]->addPluginCall('columns', array(987, $this->sectionEnd - 1), DOKU_LEXER_MATCHED); + } + return $result; + } +} diff --git a/public_html/lib/plugins/columns/conf/default.php b/public_html/lib/plugins/columns/conf/default.php new file mode 100644 index 00000000..e83f6c23 --- /dev/null +++ b/public_html/lib/plugins/columns/conf/default.php @@ -0,0 +1,12 @@ + + */ + +$conf['kwcolumns'] = ''; +$conf['kwnewcol'] = ''; +$conf['wrapnewcol'] = 1; diff --git a/public_html/lib/plugins/columns/conf/metadata.php b/public_html/lib/plugins/columns/conf/metadata.php new file mode 100644 index 00000000..011bdf79 --- /dev/null +++ b/public_html/lib/plugins/columns/conf/metadata.php @@ -0,0 +1,12 @@ + + */ + +$meta['kwcolumns'] = array('string'); +$meta['kwnewcol'] = array('string'); +$meta['wrapnewcol'] = array('onoff'); diff --git a/public_html/lib/plugins/columns/lang/en/lang.php b/public_html/lib/plugins/columns/lang/en/lang.php new file mode 100644 index 00000000..7182dd62 --- /dev/null +++ b/public_html/lib/plugins/columns/lang/en/lang.php @@ -0,0 +1,12 @@ + + */ + +$lang['kwcolumns'] = 'columns'; +$lang['kwnewcol'] = 'newcolumn'; + diff --git a/public_html/lib/plugins/columns/lang/en/settings.php b/public_html/lib/plugins/columns/lang/en/settings.php new file mode 100644 index 00000000..6fb55e55 --- /dev/null +++ b/public_html/lib/plugins/columns/lang/en/settings.php @@ -0,0 +1,12 @@ + + */ + +$lang['kwcolumns'] = 'Columns tag (default: columns); must be specified within angle brackets (<columns>...</columns>)'; +$lang['kwnewcol'] = 'New column tag (default: newcolumn)'; +$lang['wrapnewcol'] = 'Wrap the new column tag in angle brackets (<newcolumn>)'; diff --git a/public_html/lib/plugins/columns/lang/es/lang.php b/public_html/lib/plugins/columns/lang/es/lang.php new file mode 100644 index 00000000..4e552e1d --- /dev/null +++ b/public_html/lib/plugins/columns/lang/es/lang.php @@ -0,0 +1,12 @@ + + */ + +$lang['kwcolumns'] = 'columnas'; +$lang['kwnewcol'] = 'nueva_columna'; + diff --git a/public_html/lib/plugins/columns/lang/es/settings.php b/public_html/lib/plugins/columns/lang/es/settings.php new file mode 100644 index 00000000..a1106b27 --- /dev/null +++ b/public_html/lib/plugins/columns/lang/es/settings.php @@ -0,0 +1,12 @@ + + */ + +$lang['kwcolumns'] = 'Etiqueta de columnas (por defecto: columnas); Se debe especificar entre paréntesis angulares (<columnas>...</columnas>)'; +$lang['kwnewcol'] = 'Etiqueta de nueva columna (por defecto: nueva_columna)'; +$lang['wrapnewcol'] = 'Encerrar la etiqueta de nueva columna entre paréntesis angulares (<nueva_columna>)'; diff --git a/public_html/lib/plugins/columns/lang/ru/lang.php b/public_html/lib/plugins/columns/lang/ru/lang.php new file mode 100644 index 00000000..4f3270fd --- /dev/null +++ b/public_html/lib/plugins/columns/lang/ru/lang.php @@ -0,0 +1,12 @@ + + */ + +$lang['kwcolumns'] = 'колонки'; +$lang['kwnewcol'] = 'новаяколонка'; + diff --git a/public_html/lib/plugins/columns/lang/ru/settings.php b/public_html/lib/plugins/columns/lang/ru/settings.php new file mode 100644 index 00000000..08dd8701 --- /dev/null +++ b/public_html/lib/plugins/columns/lang/ru/settings.php @@ -0,0 +1,12 @@ + + */ + +$lang['kwcolumns'] = 'Тег колонок (по умолчанию: колонки); тег должен быть заключен в угловые скобки (<колонки>...</колонки>)'; +$lang['kwnewcol'] = 'Тег новой колонки (по умолчанию: новаяколонка)'; +$lang['wrapnewcol'] = 'Заключать тег новой колонки в угловые скобки (<новаяколонка>)'; diff --git a/public_html/lib/plugins/columns/plugin.info.txt b/public_html/lib/plugins/columns/plugin.info.txt new file mode 100644 index 00000000..4c514f8d --- /dev/null +++ b/public_html/lib/plugins/columns/plugin.info.txt @@ -0,0 +1,7 @@ +base columns +author Mykola Ostrovskyy +email spambox03@mail.ru +date 2016-09-07 +name Columns Plugin +desc Arrange information in multiple columns. +url http://www.dokuwiki.org/plugin:columns \ No newline at end of file diff --git a/public_html/lib/plugins/columns/print.css b/public_html/lib/plugins/columns/print.css new file mode 100644 index 00000000..7f07371b --- /dev/null +++ b/public_html/lib/plugins/columns/print.css @@ -0,0 +1,24 @@ +div.dokuwiki table.columns-plugin td { + vertical-align: top; + padding: 0 0.3cm 0 0.3cm; +} + +div.dokuwiki table.columns-plugin td.first { + padding-left: 0; +} + +div.dokuwiki table.columns-plugin td.last { + padding-right: 0; +} + +div.dokuwiki table.columns-plugin td.left { + text-align: left; +} + +div.dokuwiki table.columns-plugin td.center { + text-align: center; +} + +div.dokuwiki table.columns-plugin td.right { + text-align: right; +} diff --git a/public_html/lib/plugins/columns/rewriter.php b/public_html/lib/plugins/columns/rewriter.php new file mode 100644 index 00000000..eaa7296b --- /dev/null +++ b/public_html/lib/plugins/columns/rewriter.php @@ -0,0 +1,190 @@ + + */ + +if (!class_exists('instruction_rewriter', false)) { + +class instruction_rewriter { + + private $correction; + + /** + * Constructor + */ + public function __construct() { + $this->correction = array(); + } + + /** + * + */ + public function addCorrections($correction) { + foreach ($correction as $c) { + $this->correction[$c->getIndex()][] = $c; + } + } + + /** + * + */ + public function process(&$instruction) { + if (count($this->correction) > 0) { + $index = $this->getCorrectionIndex(); + $corrections = count($index); + $instructions = count($instruction); + $output = array(); + for ($c = 0, $i = 0; $c < $corrections; $c++, $i++) { + /* Copy all instructions that are before the next correction */ + for ( ; $i < $index[$c]; $i++) { + $output[] = $instruction[$i]; + } + /* Apply the corrections */ + $preventDefault = false; + foreach ($this->correction[$i] as $correction) { + $preventDefault = ($preventDefault || $correction->apply($instruction, $output)); + } + if (!$preventDefault) { + $output[] = $instruction[$i]; + } + } + /* Copy the rest of instructions after the last correction */ + for ( ; $i < $instructions; $i++) { + $output[] = $instruction[$i]; + } + /* Handle appends */ + if (array_key_exists(-1, $this->correction)) { + $this->correction[-1]->apply($instruction, $output); + } + $instruction = $output; + } + } + + /** + * + */ + private function getCorrectionIndex() { + $result = array_keys($this->correction); + asort($result); + /* Remove appends */ + if (reset($result) == -1) { + unset($result[key($result)]); + } + return array_values($result); + } +} + +class instruction_rewriter_correction { + + private $index; + + /** + * Constructor + */ + public function __construct($index) { + $this->index = $index; + } + + /** + * + */ + public function getIndex() { + return $this->index; + } +} + +class instruction_rewriter_delete extends instruction_rewriter_correction { + + /** + * Constructor + */ + public function __construct($index) { + parent::__construct($index); + } + + /** + * + */ + public function apply($input, &$output) { + return true; + } +} + +class instruction_rewriter_call_list extends instruction_rewriter_correction { + + private $call; + + /** + * Constructor + */ + public function __construct($index) { + parent::__construct($index); + $this->call = array(); + } + + /** + * + */ + public function addCall($name, $data) { + $this->call[] = array($name, $data); + } + + /** + * + */ + public function addPluginCall($name, $data, $state, $text = '') { + $this->call[] = array('plugin', array($name, $data, $state, $text)); + } + + /** + * + */ + public function appendCalls(&$output, $position) { + foreach ($this->call as $call) { + $output[] = array($call[0], $call[1], $position); + } + } +} + +class instruction_rewriter_insert extends instruction_rewriter_call_list { + + /** + * Constructor + */ + public function __construct($index) { + parent::__construct($index); + } + + /** + * + */ + public function apply($input, &$output) { + $this->appendCalls($output, $input[$this->index][2]); + return false; + } +} + +class instruction_rewriter_append extends instruction_rewriter_call_list { + + /** + * Constructor + */ + public function __construct() { + parent::__construct(-1); + } + + /** + * + */ + public function apply($input, &$output) { + $lastCall = end($output); + $this->appendCalls($output, $lastCall[2]); + return false; + } +} + +} diff --git a/public_html/lib/plugins/columns/style.css b/public_html/lib/plugins/columns/style.css new file mode 100644 index 00000000..eab6bf10 --- /dev/null +++ b/public_html/lib/plugins/columns/style.css @@ -0,0 +1,43 @@ +div.dokuwiki table.columns-plugin { + border: 0; + border-spacing: 0; + border-collapse: collapse; + margin: 0; +} + +div.dokuwiki table.columns-plugin td.columns-plugin { + border: 0; + text-align: justify; + vertical-align: top; + padding: 0 0.5em 0 0.5em; +} + +div.dokuwiki table.columns-plugin td.columns-plugin.first { + padding-left: 0; +} + +div.dokuwiki table.columns-plugin td.columns-plugin.last { + padding-right: 0; +} + +div.dokuwiki table.columns-plugin td.columns-plugin.left { + text-align: left; +} + +div.dokuwiki table.columns-plugin td.columns-plugin.center { + text-align: center; +} + +div.dokuwiki table.columns-plugin td.columns-plugin.right { + text-align: right; +} + +div.dokuwiki table.columns-plugin td.columns-plugin.center table { + margin-left: auto; + margin-right: auto; +} + +div.dokuwiki table.columns-plugin td.columns-plugin.right table { + margin-left: auto; + margin-right: 0; +} diff --git a/public_html/lib/plugins/columns/syntax.php b/public_html/lib/plugins/columns/syntax.php new file mode 100644 index 00000000..82ec9053 --- /dev/null +++ b/public_html/lib/plugins/columns/syntax.php @@ -0,0 +1,611 @@ + + * Based on plugin by Michael Arlt + */ + +/* Must be run within Dokuwiki */ +if(!defined('DOKU_INC')) die(); + +if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/'); +require_once(DOKU_PLUGIN . 'syntax.php'); + +class syntax_plugin_columns extends DokuWiki_Syntax_Plugin { + + private $mode; + private $lexerSyntax; + private $syntax; + private $xhtmlRenderer; + private $odtRenderer; + + /** + * Constructor + */ + public function __construct() { + $this->mode = substr(get_class($this), 7); + + $columns = $this->getColumnsTagName(); + $newColumn = $this->getNewColumnTagName(); + if ($this->getConf('wrapnewcol') == 1) { + $newColumnLexer = '<' . $newColumn . '(?:>|\s.*?>)'; + $newColumnHandler = '<' . $newColumn . '(.*?)>'; + } + else { + $newColumnLexer = $newColumn; + $newColumnHandler = $newColumn; + } + $enterLexer = '<' . $columns . '(?:>|\s.*?>)'; + $enterHandler = '<' . $columns . '(.*?)>'; + $exit = '<\/' . $columns . '>'; + + $this->lexerSyntax['enter'] = $enterLexer; + $this->lexerSyntax['newcol'] = $newColumnLexer; + $this->lexerSyntax['exit'] = $exit; + + $this->syntax[DOKU_LEXER_ENTER] = '/' . $enterHandler . '/'; + $this->syntax[DOKU_LEXER_MATCHED] = '/' . $newColumnHandler . '/'; + $this->syntax[DOKU_LEXER_EXIT] = '/' . $exit . '/'; + } + + /** + * What kind of syntax are we? + */ + public function getType() { + return 'substition'; + } + + public function getPType() { + return 'block'; + } + + /** + * Where to sort in? + */ + public function getSort() { + return 65; + } + + public function connectTo($mode) { + $this->Lexer->addSpecialPattern($this->lexerSyntax['enter'], $mode, $this->mode); + $this->Lexer->addSpecialPattern($this->lexerSyntax['newcol'], $mode, $this->mode); + $this->Lexer->addSpecialPattern($this->lexerSyntax['exit'], $mode, $this->mode); + } + + /** + * Handle the match + */ + public function handle($match, $state, $pos, Doku_Handler $handler) { + foreach ($this->syntax as $state => $pattern) { + if (preg_match($pattern, $match, $data) == 1) { + break; + } + } + switch ($state) { + case DOKU_LEXER_ENTER: + case DOKU_LEXER_MATCHED: + return array($state, preg_split('/\s+/', $data[1], -1, PREG_SPLIT_NO_EMPTY)); + + case DOKU_LEXER_EXIT: + return array($state); + } + return false; + } + + /** + * Create output + */ + public function render($mode, Doku_Renderer $renderer, $data) { + $columnsRenderer = $this->getRenderer($mode, $renderer); + + if ($columnsRenderer != NULL) { + $columnsRenderer->render($data[0], $renderer, $data[1]); + return true; + } + return false; + } + + /** + * + */ + private function getRenderer($mode, Doku_Renderer $renderer) { + switch ($mode) { + case 'xhtml': + if ($this->xhtmlRenderer == NULL) { + $this->xhtmlRenderer = new columns_renderer_xhtml(); + } + return $this->xhtmlRenderer; + + case 'odt': + if ($this->odtRenderer == NULL) { + if (method_exists($renderer, 'getODTPropertiesFromElement')) { + $this->odtRenderer = new columns_renderer_odt_v2(); + } + else { + $this->odtRenderer = new columns_renderer_odt_v1(); + } + } + return $this->odtRenderer; + } + + return NULL; + } + + /** + * Returns columns tag + */ + private function getColumnsTagName() { + $tag = $this->getConf('kwcolumns'); + if ($tag == '') { + $tag = $this->getLang('kwcolumns'); + } + return $tag; + } + + /** + * Returns new column tag + */ + private function getNewColumnTagName() { + $tag = $this->getConf('kwnewcol'); + if ($tag == '') { + $tag = $this->getLang('kwnewcol'); + } + return $tag; + } +} + +/** + * Base class for columns rendering. + */ +abstract class columns_renderer { + /** + * + */ + public function render($state, Doku_Renderer $renderer, $attribute) { + switch ($state) { + case DOKU_LEXER_ENTER: + $this->render_enter($renderer, $attribute); + break; + + case DOKU_LEXER_MATCHED: + $this->render_matched($renderer, $attribute); + break; + + case DOKU_LEXER_EXIT: + $this->render_exit($renderer, $attribute); + break; + } + } + + abstract protected function render_enter(Doku_Renderer $renderer, $attribute); + abstract protected function render_matched(Doku_Renderer $renderer, $attribute); + abstract protected function render_exit(Doku_Renderer $renderer, $attribute); + + /** + * + */ + protected function getAttribute($attribute, $name) { + $result = ''; + if (array_key_exists($name, $attribute)) { + $result = $attribute[$name]; + } + return $result; + } + + /** + * + */ + protected function getStyle($attribute, $attributeName, $styleName = '') { + $result = $this->getAttribute($attribute, $attributeName); + if ($result != '') { + if ($styleName == '') { + $styleName = $attributeName; + } + $result = $styleName . ':' . $result . ';'; + } + return $result; + } +} + +/** + * Class columns_renderer_xhtml + * @author LarsDW223 + */ +class columns_renderer_xhtml extends columns_renderer { + /** + * + */ + public function render($state, Doku_Renderer $renderer, $attribute) { + parent::render($state, $renderer, $attribute); + + if ($state == 987 && method_exists($renderer, 'finishSectionEdit')) { + $renderer->finishSectionEdit($attribute); + } + } + + /** + * + */ + protected function render_enter(Doku_Renderer $renderer, $attribute) { + $renderer->doc .= $this->renderTable($attribute) . DOKU_LF; + $renderer->doc .= '' . $this->renderTd($attribute) . DOKU_LF; + } + + /** + * + */ + protected function render_matched(Doku_Renderer $renderer, $attribute) { + $renderer->doc .= '' . $this->renderTd($attribute) . DOKU_LF; + } + + /** + * + */ + protected function render_exit(Doku_Renderer $renderer, $attribute) { + $renderer->doc .= '' . DOKU_LF; + } + + /** + * + */ + private function renderTable($attribute) { + $width = $this->getAttribute($attribute, 'table-width'); + if ($width != '') { + return ''; + } + else { + return '
'; + } + } + + /** + * + */ + private function renderTd($attribute) { + $class[] = 'columns-plugin'; + $class[] = $this->getAttribute($attribute, 'class'); + $class[] = $this->getAttribute($attribute, 'text-align'); + $html = '
getStyle($attribute, 'column-width', 'width'); + $style .= $this->getStyle($attribute, 'vertical-align'); + if ($style != '') { + $html .= ' style="' . $style . '"'; + } + return $html . '>'; + } +} + +/** + * Class columns_renderer_odt_v1 + */ +class columns_renderer_odt_v1 extends columns_renderer { + /** + * + */ + protected function render_enter(Doku_Renderer $renderer, $attribute) { + $this->addOdtTableStyle($renderer, $attribute); + $this->addOdtColumnStyles($renderer, $attribute); + $this->renderOdtTableEnter($renderer, $attribute); + $this->renderOdtColumnEnter($renderer, $attribute); + } + + /** + * + */ + protected function render_matched(Doku_Renderer $renderer, $attribute) { + $this->addOdtColumnStyles($renderer, $attribute); + $this->renderOdtColumnExit($renderer); + $this->renderOdtColumnEnter($renderer, $attribute); + } + + /** + * + */ + protected function render_exit(Doku_Renderer $renderer, $attribute) { + $this->renderOdtColumnExit($renderer); + $this->renderOdtTableExit($renderer); + } + + /** + * + */ + private function addOdtTableStyle(Doku_Renderer $renderer, $attribute) { + $styleName = $this->getOdtTableStyleName($this->getAttribute($attribute, 'block-id')); + $style = ''; + $style .= 'getAttribute($attribute, 'table-width'); + + if (($width != '') && ($width != '100%')) { + $metrics = $this->getOdtMetrics($renderer->autostyles); + $style .= ' style:width="' . $this->getOdtAbsoluteWidth($metrics, $width) . '"'; + } + $align = ($width == '100%') ? 'margins' : 'left'; + $style .= ' table:align="' . $align . '"/>'; + $style .= ''; + + $renderer->autostyles[$styleName] = $style; + } + + /** + * + */ + private function addOdtColumnStyles(Doku_Renderer $renderer, $attribute) { + $blockId = $this->getAttribute($attribute, 'block-id'); + $columnId = $this->getAttribute($attribute, 'column-id'); + $styleName = $this->getOdtTableStyleName($blockId, $columnId); + + $style = ''; + $style .= 'getAttribute($attribute, 'column-width'); + + if ($width != '') { + $metrics = $this->getOdtMetrics($renderer->autostyles); + $style .= ' style:column-width="' . $this->getOdtAbsoluteWidth($metrics, $width) . '"'; + } + $style .= '/>'; + $style .= ''; + + $renderer->autostyles[$styleName] = $style; + + $styleName = $this->getOdtTableStyleName($blockId, $columnId, 1); + + $style = ''; + $style .= 'getAttribute($attribute, 'class')) { + case 'first': + $style .= ' fo:padding-left="0cm"'; + $style .= ' fo:padding-right="0.4cm"'; + break; + + case 'last': + $style .= ' fo:padding-left="0.4cm"'; + $style .= ' fo:padding-right="0cm"'; + break; + } + + /* There seems to be no easy way to control horizontal alignment of text within + the column as fo:text-align aplies to individual paragraphs. */ + //TODO: $this->getAttribute($attribute, 'text-align'); + + $align = $this->getAttribute($attribute, 'vertical-align'); + if ($align != '') { + $style .= ' style:vertical-align="' . $align . '"'; + } + else { + $style .= ' style:vertical-align="top"'; + } + + $style .= '/>'; + $style .= ''; + + $renderer->autostyles[$styleName] = $style; + } + + /** + * + */ + private function renderOdtTableEnter(Doku_Renderer $renderer, $attribute) { + $columns = $this->getAttribute($attribute, 'columns'); + $blockId = $this->getAttribute($attribute, 'block-id'); + $styleName = $this->getOdtTableStyleName($blockId); + + $renderer->doc .= ''; + for ($c = 0; $c < $columns; $c++) { + $styleName = $this->getOdtTableStyleName($blockId, $c + 1); + $renderer->doc .= ''; + } + $renderer->doc .= ''; + } + + /** + * + */ + private function renderOdtColumnEnter(Doku_Renderer $renderer, $attribute) { + $blockId = $this->getAttribute($attribute, 'block-id'); + $columnId = $this->getAttribute($attribute, 'column-id'); + $styleName = $this->getOdtTableStyleName($blockId, $columnId, 1); + $renderer->doc .= ''; + } + + /** + * + */ + private function renderOdtColumnExit(Doku_Renderer $renderer) { + $renderer->doc .= ''; + } + + /** + * + */ + private function renderOdtTableExit(Doku_Renderer $renderer) { + $renderer->doc .= ''; + $renderer->doc .= ''; + } + + /** + * Convert relative units to absolute + */ + private function getOdtAbsoluteWidth($metrics, $width) { + if (preg_match('/([\d\.]+)(.+)/', $width, $match) == 1) { + switch ($match[2]) { + case '%': + /* Won't work for nested column blocks */ + $width = ($match[1] / 100 * $metrics['page-width']) . $metrics['page-width-units']; + break; + case 'em': + /* Rough estimate */ + $width = ($match[1] * 0.8 * $metrics['font-size']) . $metrics['font-size-units']; + break; + } + } + return $width; + } + + /** + * + */ + private function getOdtTableStyleName($blockId, $columnId = 0, $cell = 0) { + $result = 'ColumnsBlock' . $blockId; + if ($columnId != 0) { + if ($columnId <= 26) { + $result .= '.' . chr(ord('A') + $columnId - 1); + } + else { + /* To unlikey to handle it properly */ + $result .= '.a'; + } + if ($cell != 0) { + $result .= $cell; + } + } + return $result; + } + + /** + * + */ + private function getOdtMetrics($autoStyle) { + $result = array(); + if (array_key_exists('pm1', $autoStyle)) { + $style = $autoStyle['pm1']; + if (preg_match('/fo:page-width="([\d\.]+)(.+?)"/', $style, $match) == 1) { + $result['page-width'] = floatval($match[1]); + $result['page-width-units'] = $match[2]; + $units = $match[2]; + + if (preg_match('/fo:margin-left="([\d\.]+)(.+?)"/', $style, $match) == 1) { + // TODO: Unit conversion + if ($match[2] == $units) { + $result['page-width'] -= floatval($match[1]); + } + } + if (preg_match('/fo:margin-right="([\d\.]+)(.+?)"/', $style, $match) == 1) { + if ($match[2] == $units) { + $result['page-width'] -= floatval($match[1]); + } + } + } + } + if (!array_key_exists('page-width', $result)) { + $result['page-width'] = 17; + $result['page-width-units'] = 'cm'; + } + + /* There seems to be no easy way to get default font size apart from loading styles.xml. */ + $styles = io_readFile(DOKU_PLUGIN . 'odt/styles.xml'); + if (preg_match('/(.+?)<\/style:default-style>/s', $styles, $match) == 1) { + if (preg_match('//', $match[1], $match) == 1) { + if (preg_match('/fo:font-size="([\d\.]+)(.+?)"/', $match[1], $match) == 1) { + $result['font-size'] = floatval($match[1]); + $result['font-size-units'] = $match[2]; + } + } + } + if (!array_key_exists('font-size', $result)) { + $result['font-size'] = 12; + $result['font-size-units'] = 'pt'; + } + return $result; + } +} + +/** + * Class columns_renderer_odt_v2 + * @author LarsDW223 + */ +class columns_renderer_odt_v2 extends columns_renderer { + /** + * + */ + protected function render_enter(Doku_Renderer $renderer, $attribute) { + $this->renderOdtTableEnter($renderer, $attribute); + $this->renderOdtColumnEnter($renderer, $attribute); + } + + /** + * + */ + protected function render_matched(Doku_Renderer $renderer, $attribute) { + $this->renderOdtColumnExit($renderer); + $this->renderOdtColumnEnter($renderer, $attribute); + } + + /** + * + */ + protected function render_exit(Doku_Renderer $renderer, $attribute) { + $this->renderOdtColumnExit($renderer); + $this->renderOdtTableExit($renderer); + } + + /** + * + */ + private function renderOdtTableEnter(Doku_Renderer $renderer, $attribute) { + $properties = array(); + $properties ['width'] = $this->getAttribute($attribute, 'table-width'); + $properties ['align'] = 'left'; + $renderer->_odtTableOpenUseProperties ($properties); + $renderer->tablerow_open(); + } + + /** + * + */ + private function renderOdtColumnEnter(Doku_Renderer $renderer, $attribute) { + $properties = array(); + $properties ['width'] = $this->getAttribute($attribute, 'column-width'); + $properties ['border'] = 'none'; + $properties ['padding-top'] = '0cm'; + $properties ['padding-bottom'] = '0cm'; + switch ($this->getAttribute($attribute, 'class')) { + case 'first': + $properties ['padding-left'] = '0cm'; + $properties ['padding-right'] = '0.4cm'; + break; + + case 'last': + $properties ['padding-left'] = '0.4cm'; + $properties ['padding-right'] = '0cm'; + break; + } + $align = $this->getAttribute($attribute, 'vertical-align'); + if ($align != '') { + $properties ['vertical-align'] = $align; + } + else { + $properties ['vertical-align'] = 'top'; + } + $align = $this->getAttribute($attribute, 'text-align'); + if ($align != '') { + $properties ['text-align'] = $align; + } + else { + $properties ['text-align'] = 'left'; + } + + $renderer->_odtTableCellOpenUseProperties($properties); + } + + /** + * + */ + private function renderOdtColumnExit(Doku_Renderer $renderer) { + $renderer->tablecell_close(); + } + + /** + * + */ + private function renderOdtTableExit(Doku_Renderer $renderer) { + $renderer->tablerow_close(); + $renderer->table_close(); + } +} diff --git a/public_html/lib/tpl/e/css/modifications.css b/public_html/lib/tpl/e/css/modifications.css index fe4d17e8..9cc73777 100644 --- a/public_html/lib/tpl/e/css/modifications.css +++ b/public_html/lib/tpl/e/css/modifications.css @@ -90,6 +90,17 @@ h1, h2, h3, h4, h5, h6, } } +@media screen and (max-width: 768px) { + table.columns-plugin { + border: none; + } + table.columns-plugin, table.columns-plugin tbody, + table.columns-plugin tr, table.columns-plugin td { + display: block; + width: 100% !important; + } +} + @media (min-width: 768px) and (max-width: 993px) { .page { padding: 60px 0;