Add column plugin and re-flow columns on mobile

This commit is contained in:
Andy Williams 2017-07-15 10:53:16 +01:00
parent ca37d68e4b
commit 62069b6490
15 changed files with 1657 additions and 0 deletions

View File

@ -0,0 +1,675 @@
<?php
/**
* Plugin Columns: Layout parser
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Mykola Ostrovskyy <spambox03@mail.ru>
*/
/* 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 <newcolumn> 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 </colums> 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 </div> 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;
}
}

View File

@ -0,0 +1,12 @@
<?php
/**
* Plugin Columns: Configuration defaults
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Mykola Ostrovskyy <spambox03@mail.ru>
*/
$conf['kwcolumns'] = '';
$conf['kwnewcol'] = '';
$conf['wrapnewcol'] = 1;

View File

@ -0,0 +1,12 @@
<?php
/**
* Plugin Columns: Metadata for configuration manager plugin
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Mykola Ostrovskyy <spambox03@mail.ru>
*/
$meta['kwcolumns'] = array('string');
$meta['kwnewcol'] = array('string');
$meta['wrapnewcol'] = array('onoff');

View File

@ -0,0 +1,12 @@
<?php
/**
* Plugin Columns: English language file
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Mykola Ostrovskyy <spambox03@mail.ru>
*/
$lang['kwcolumns'] = 'columns';
$lang['kwnewcol'] = 'newcolumn';

View File

@ -0,0 +1,12 @@
<?php
/**
* Plugin Columns: English language file
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Mykola Ostrovskyy <spambox03@mail.ru>
*/
$lang['kwcolumns'] = 'Columns tag (default: columns); must be specified within angle brackets (&lt;columns&gt;...&lt;/columns&gt;)';
$lang['kwnewcol'] = 'New column tag (default: newcolumn)';
$lang['wrapnewcol'] = 'Wrap the new column tag in angle brackets (&lt;newcolumn&gt;)';

View File

@ -0,0 +1,12 @@
<?php
/**
* Plugin Columns: Spanish language file
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Digna González Otero <digna.gonzalezotero [at] gmail [dot] com>
*/
$lang['kwcolumns'] = 'columnas';
$lang['kwnewcol'] = 'nueva_columna';

View File

@ -0,0 +1,12 @@
<?php
/**
* Plugin Columns: Spanish language file
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Digna González Otero <digna.gonzalezotero [at] gmail [dot] com>
*/
$lang['kwcolumns'] = 'Etiqueta de columnas (por defecto: columnas); Se debe especificar entre paréntesis angulares (&lt;columnas&gt;...&lt;/columnas&gt;)';
$lang['kwnewcol'] = 'Etiqueta de nueva columna (por defecto: nueva_columna)';
$lang['wrapnewcol'] = 'Encerrar la etiqueta de nueva columna entre paréntesis angulares (&lt;nueva_columna&gt;)';

View File

@ -0,0 +1,12 @@
<?php
/**
* Plugin Columns: Russian language file
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Mykola Ostrovskyy <spambox03@mail.ru>
*/
$lang['kwcolumns'] = 'колонки';
$lang['kwnewcol'] = 'новаяколонка';

View File

@ -0,0 +1,12 @@
<?php
/**
* Plugin Columns: Russian language file
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Mykola Ostrovskyy <spambox03@mail.ru>
*/
$lang['kwcolumns'] = 'Тег колонок (по умолчанию: колонки); тег должен быть заключен в угловые скобки (&lt;колонки&gt;...&lt;/колонки&gt;)';
$lang['kwnewcol'] = 'Тег новой колонки (по умолчанию: новаяколонка)';
$lang['wrapnewcol'] = 'Заключать тег новой колонки в угловые скобки (&lt;новаяколонка&gt;)';

View File

@ -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

View File

@ -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;
}

View File

@ -0,0 +1,190 @@
<?php
/**
* Instruction re-writer
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Mykola Ostrovskyy <spambox03@mail.ru>
*/
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;
}
}
}

View File

@ -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;
}

View File

@ -0,0 +1,611 @@
<?php
/**
* Plugin Columns: Syntax & rendering
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Mykola Ostrovskyy <spambox03@mail.ru>
* Based on plugin by Michael Arlt <michael.arlt [at] sk-schwanstetten [dot] de>
*/
/* 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 .= '<tr>' . $this->renderTd($attribute) . DOKU_LF;
}
/**
*
*/
protected function render_matched(Doku_Renderer $renderer, $attribute) {
$renderer->doc .= '</td>' . $this->renderTd($attribute) . DOKU_LF;
}
/**
*
*/
protected function render_exit(Doku_Renderer $renderer, $attribute) {
$renderer->doc .= '</td></tr></table>' . DOKU_LF;
}
/**
*
*/
private function renderTable($attribute) {
$width = $this->getAttribute($attribute, 'table-width');
if ($width != '') {
return '<table class="columns-plugin" style="width:' . $width . '">';
}
else {
return '<table class="columns-plugin">';
}
}
/**
*
*/
private function renderTd($attribute) {
$class[] = 'columns-plugin';
$class[] = $this->getAttribute($attribute, 'class');
$class[] = $this->getAttribute($attribute, 'text-align');
$html = '<td class="' . implode(' ', array_filter($class)) . '"';
$style = $this->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:style style:name="' . $styleName . '" style:family="table">';
$style .= '<style:table-properties';
$width = $this->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 .= '</style: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:style style:name="' . $styleName . '" style:family="table-column">';
$style .= '<style:table-column-properties';
$width = $this->getAttribute($attribute, 'column-width');
if ($width != '') {
$metrics = $this->getOdtMetrics($renderer->autostyles);
$style .= ' style:column-width="' . $this->getOdtAbsoluteWidth($metrics, $width) . '"';
}
$style .= '/>';
$style .= '</style:style>';
$renderer->autostyles[$styleName] = $style;
$styleName = $this->getOdtTableStyleName($blockId, $columnId, 1);
$style = '<style:style style:name="' . $styleName . '" style:family="table-cell">';
$style .= '<style:table-cell-properties';
$style .= ' fo:border="none"';
$style .= ' fo:padding-top="0cm"';
$style .= ' fo:padding-bottom="0cm"';
switch ($this->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 .= '</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 .= '<table:table table:style-name="' . $styleName . '">';
for ($c = 0; $c < $columns; $c++) {
$styleName = $this->getOdtTableStyleName($blockId, $c + 1);
$renderer->doc .= '<table:table-column table:style-name="' . $styleName . '" />';
}
$renderer->doc .= '<table:table-row>';
}
/**
*
*/
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 .= '<table:table-cell table:style-name="' . $styleName . '" office:value-type="string">';
}
/**
*
*/
private function renderOdtColumnExit(Doku_Renderer $renderer) {
$renderer->doc .= '</table:table-cell>';
}
/**
*
*/
private function renderOdtTableExit(Doku_Renderer $renderer) {
$renderer->doc .= '</table:table-row>';
$renderer->doc .= '</table:table>';
}
/**
* 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 style:family="paragraph">(.+?)<\/style:default-style>/s', $styles, $match) == 1) {
if (preg_match('/<style:text-properties(.+?)>/', $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();
}
}

View File

@ -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;