ajwillia-ms pushed a commit to branch master. http://git.enlightenment.org/website/www.git/commit/?id=e727f07ce51ac56852466f8d2fd66dad8f95a4e3
commit e727f07ce51ac56852466f8d2fd66dad8f95a4e3 Author: Andy Williams <[email protected]> Date: Wed Nov 29 11:52:30 2017 +0000 plugins: Manage our navigation through a plugin not manually --- public_html/lib/plugins/navi/.travis.yml | 16 ++ .../lib/plugins/navi/_test/basicList.test.php | 97 +++++++++ .../lib/plugins/navi/_test/externalLinks.test.php | 76 +++++++ .../lib/plugins/navi/_test/general.test.php | 108 +++++++++ public_html/lib/plugins/navi/action.php | 39 ++++ public_html/lib/plugins/navi/conf/default.php | 3 + public_html/lib/plugins/navi/conf/metadata.php | 3 + public_html/lib/plugins/navi/plugin.info.txt | 8 + public_html/lib/plugins/navi/script.js | 12 + public_html/lib/plugins/navi/style.less | 45 ++++ public_html/lib/plugins/navi/syntax.php | 241 +++++++++++++++++++++ public_html/lib/tpl/e/css/modifications.css | 19 ++ 12 files changed, 667 insertions(+) diff --git a/public_html/lib/plugins/navi/.travis.yml b/public_html/lib/plugins/navi/.travis.yml new file mode 100644 index 00000000..f5207aae --- /dev/null +++ b/public_html/lib/plugins/navi/.travis.yml @@ -0,0 +1,16 @@ +# Config file for travis-ci.org + +language: php +php: + - "7.0" + - "5.6" + - "5.5" + - "5.4" + - "5.3" +env: + matrix: + - DOKUWIKI=master + - DOKUWIKI=stable +before_install: wget https://raw.github.com/splitbrain/dokuwiki-travis/master/travis.sh +install: sh travis.sh +script: cd _test && phpunit --stderr --group plugin_navi diff --git a/public_html/lib/plugins/navi/_test/basicList.test.php b/public_html/lib/plugins/navi/_test/basicList.test.php new file mode 100644 index 00000000..58b8da3b --- /dev/null +++ b/public_html/lib/plugins/navi/_test/basicList.test.php @@ -0,0 +1,97 @@ +<?php + +/** + * Tests for functionality of the navi plugin + * + * @group plugin_navi + * @group plugins + * + */ +class basic_plugin_navi_test extends DokuWikiTest { + + protected $pluginsEnabled = array('navi'); + + public function setUp() { + parent::setUp(); + } + + public function tearDown() { + parent::tearDown(); + } + + public function test_controlpage_simple() { + // arrange + $controlpage = " * [[a]]\n * [[b]]\n * [[c]]"; + saveWikiText('controlpage', $controlpage, ''); + saveWikiText('navi', '{{navi>controlpage}}', ''); + + // act + $info = array(); + $actualHTML = p_render('xhtml', p_get_instructions('{{navi>controlpage}}'), $info); + + // assert + $expectedHTML = '<div class="plugin__navi left"><ul> +<li class="level1 "><div class="li"><a href="/./doku.php?id=a" class="wikilink2" title="a" rel="nofollow">a</a></div> +</li> +<li class="level1 close"><div class="li"><a href="/./doku.php?id=b" class="wikilink2" title="b" rel="nofollow">b</a></div> +</li> +</ul> +</div>'; + $this->assertEquals($expectedHTML, $actualHTML); + + } + + public function test_controlpage_complex() { + // arrange + $controlpage = " + * [[en:products:a:start|BasePage]] + * [[en:products:b:d:start|2nd-level Page with hidden child]] + * [[en:products:c:projects|hidden 3rd-level page]] + * [[en:products:b:archive:start|2nd-level pape]] + * [[en:products:c:start|current 2nd-level page with visible child]] + * [[en:products:d:start|visible 3rd-level page]] +"; + saveWikiText('controlpage', $controlpage, ''); + saveWikiText('navi', '{{navi>controlpage}}', ''); + global $ID, $INFO; + + // act + $info = array(); + $ID = 'en:products:c:start'; + $INFO['id'] = 'en:products:c:start'; + $actualHTML = p_render('xhtml', p_get_instructions('{{navi>controlpage}}'), $info); + + $pq = phpQuery::newDocumentXHTML($actualHTML); + + $actualPages = array(); + foreach ($pq->find('a') as $page) { + $actualPages[] = $page->getAttribute('title'); + } + + $actualLiOpen = array(); + foreach ($pq->find('li.open > div > a, li.open > div > span > a') as $page) { + $actualLiOpen[] = $page->getAttribute('title'); + } + + $actualLiClose = array(); + foreach ($pq->find('li.close > div > a, li.close > div > span > a') as $page) { + $actualLiClose[] = $page->getAttribute('title'); + } + + $this->assertEquals(array( + 0 => 'en:products:a:start', + 1 => 'en:products:b:d:start', + 2 => 'en:products:b:archive:start', + 3 => 'en:products:c:start', + 4 => 'en:products:d:start', + ), $actualPages, 'the correct pages in the correct order'); + $this->assertEquals(array( + 0 => 'en:products:a:start', + 1 => 'en:products:c:start', + ), $actualLiOpen, 'the pages which have have children and are open should have the "open" class'); + $this->assertEquals(array( + 0 => 'en:products:b:d:start', + ), $actualLiClose, 'the pages which have have children, but are closed should have the "close" class'); + + } +} diff --git a/public_html/lib/plugins/navi/_test/externalLinks.test.php b/public_html/lib/plugins/navi/_test/externalLinks.test.php new file mode 100644 index 00000000..8d54a32e --- /dev/null +++ b/public_html/lib/plugins/navi/_test/externalLinks.test.php @@ -0,0 +1,76 @@ +<?php + +/** + * Tests for functionality of the navi plugin + * + * @group plugin_navi + * @group plugins + * + */ +class external_plugin_navi_test extends DokuWikiTest { + + protected $pluginsEnabled = array('navi'); + + public function setUp() { + parent::setUp(); + } + + public function tearDown() { + parent::tearDown(); + } + + public function test_controlpage_with_external_link() { + // arrange + $controlpage = " + * [[en:products:a:start|BasePage]] + * [[en:products:b:d:start|2nd-level Page with hidden child]] + * [[en:products:c:projects|hidden 3rd-level page]] + * [[en:products:b:archive:start|2nd-level pape]] + * [[en:products:c:start|current 2nd-level page with visible child]] + * [[https://www.example.org|Example Page]] +"; + saveWikiText('controlpage', $controlpage, ''); + saveWikiText('navi', '{{navi>controlpage}}', ''); + global $ID, $INFO; + + // act + $info = array(); + $ID = 'en:products:c:start'; + $INFO['id'] = 'en:products:c:start'; + $actualHTML = p_render('xhtml', p_get_instructions('{{navi>controlpage}}'), $info); +// print_r($actualHTML); + + $pq = phpQuery::newDocumentXHTML($actualHTML); + + $actualPages = array(); + foreach ($pq->find('a') as $page) { + $actualPages[] = $page->getAttribute('title'); + } + + $actualLiOpen = array(); + foreach ($pq->find('li.open > div > a, li.open > div > span > a') as $page) { + $actualLiOpen[] = $page->getAttribute('title'); + } + + $actualLiClose = array(); + foreach ($pq->find('li.close > div > a, li.close > div > span > a') as $page) { + $actualLiClose[] = $page->getAttribute('title'); + } + + $this->assertEquals(array( + 0 => 'en:products:a:start', + 1 => 'en:products:b:d:start', + 2 => 'en:products:b:archive:start', + 3 => 'en:products:c:start', + 4 => 'https://www.example.org', + ), $actualPages, 'the correct pages in the correct order'); + $this->assertEquals(array( + 0 => 'en:products:a:start', + 1 => 'en:products:c:start', + ), $actualLiOpen, 'the pages which have have children and are open should have the "open" class'); + $this->assertEquals(array( + 0 => 'en:products:b:d:start', + ), $actualLiClose, 'the pages which have have children, but are closed should have the "close" class'); + + } +} diff --git a/public_html/lib/plugins/navi/_test/general.test.php b/public_html/lib/plugins/navi/_test/general.test.php new file mode 100644 index 00000000..06eb77b0 --- /dev/null +++ b/public_html/lib/plugins/navi/_test/general.test.php @@ -0,0 +1,108 @@ +<?php +/** + * General tests for the navi plugin + * + * @group plugin_navi + * @group plugins + */ +class general_plugin_navi_test extends DokuWikiTest { + + /** + * Simple test to make sure the plugin.info.txt is in correct format + */ + public function test_plugininfo() { + $file = __DIR__.'/../plugin.info.txt'; + $this->assertFileExists($file); + + $info = confToHash($file); + + $this->assertArrayHasKey('base', $info); + $this->assertArrayHasKey('author', $info); + $this->assertArrayHasKey('email', $info); + $this->assertArrayHasKey('date', $info); + $this->assertArrayHasKey('name', $info); + $this->assertArrayHasKey('desc', $info); + $this->assertArrayHasKey('url', $info); + + $this->assertEquals('navi', $info['base']); + $this->assertRegExp('/^https?:\/\//', $info['url']); + $this->assertTrue(mail_isvalid($info['email'])); + $this->assertRegExp('/^\d\d\d\d-\d\d-\d\d$/', $info['date']); + $this->assertTrue(false !== strtotime($info['date'])); + } + + /** + * Test to ensure that every conf['...'] entry in conf/default.php has a corresponding meta['...'] entry in + * conf/metadata.php. + */ + public function test_plugin_conf() { + $conf_file = __DIR__.'/../conf/default.php'; + if (file_exists($conf_file)){ + include($conf_file); + } + $meta_file = __DIR__.'/../conf/metadata.php'; + if (file_exists($meta_file)) { + include($meta_file); + } + + $this->assertEquals(gettype($conf), gettype($meta),'Both ' . DOKU_PLUGIN . 'navi/conf/default.php and ' . DOKU_PLUGIN . 'navi/conf/metadata.php have to exist and contain the same keys.'); + + if (gettype($conf) != 'NULL' && gettype($meta) != 'NULL') { + foreach($conf as $key => $value) { + $this->assertArrayHasKey($key, $meta, 'Key $meta[\'' . $key . '\'] missing in ' . DOKU_PLUGIN . 'navi/conf/metadata.php'); + } + + foreach($meta as $key => $value) { + $this->assertArrayHasKey($key, $conf, 'Key $conf[\'' . $key . '\'] missing in ' . DOKU_PLUGIN . 'navi/conf/default.php'); + } + } + + } + + + + /*Test that the levels have the right classes + public function render_test() { + $data = array( + 0 => '/home/michael/public_html/dokuwiki/data/pages/plugins/navi.txt', + 1 => array( + 'lvl1' => array( + 'parents' => array(), + 'page' => 'lvl1:start', + 'title' => '', + 'lvl' => 1, + ), + 'lvl2' => array( + 'parents' => Array( + 0 => 'lvl1', + ), + 'page' => 'lvl2:start', + 'title' => '', + 'lvl' => 2, + ), + 'lvl3' => array( + 'parents' => Array( + 0 => 'lvl1', + 1 => 'lvl2', + ), + 'page' => 'lvl3:start', + 'title' => '', + 'lvl' => 3, + ), + 'lvl4' => array( + 'parents' => Array( + 0 => 'lvl1', + 1 => 'lvl2', + 2 => 'lvl3', + ), + 'page' => 'lvl4:start', + 'title' => '', + 'lvl' => 4, + ), + + ), + 2 => '', + ); + render('xhtml',$data); + }*/ +} diff --git a/public_html/lib/plugins/navi/action.php b/public_html/lib/plugins/navi/action.php new file mode 100644 index 00000000..52f133ef --- /dev/null +++ b/public_html/lib/plugins/navi/action.php @@ -0,0 +1,39 @@ +<?php +/** + * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) + * @author Andreas Gohr <[email protected]> + */ +if(!defined('DOKU_INC')) die(); + +if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); +require_once(DOKU_PLUGIN.'action.php'); + +/** + * All DokuWiki plugins to extend the parser/rendering mechanism + * need to inherit from this class + */ +class action_plugin_navi extends DokuWiki_Action_Plugin { + + /** + * plugin should use this method to register its handlers with the dokuwiki's event controller + */ + function register(Doku_Event_Handler $controller) { + $controller->register_hook('PARSER_CACHE_USE','BEFORE', $this, 'handle_cache_prepare'); + } + + /** + * prepare the cache object for default _useCache action + */ + function handle_cache_prepare(&$event, $param) { + $cache =& $event->data; + + // we're only interested in wiki pages + if (!isset($cache->page)) return; + if ($cache->mode != 'i') return; + + // get meta data + $depends = p_get_metadata($cache->page, 'relation naviplugin'); + if(!is_array($depends) || !count($depends)) return; // nothing to do + $cache->depends['files'] = !empty($cache->depends['files']) ? array_merge($cache->depends['files'], $depends) : $depends; + } +} diff --git a/public_html/lib/plugins/navi/conf/default.php b/public_html/lib/plugins/navi/conf/default.php new file mode 100644 index 00000000..3083ff0a --- /dev/null +++ b/public_html/lib/plugins/navi/conf/default.php @@ -0,0 +1,3 @@ +<?php + +$conf['arrow'] = 'left'; diff --git a/public_html/lib/plugins/navi/conf/metadata.php b/public_html/lib/plugins/navi/conf/metadata.php new file mode 100644 index 00000000..a2527be2 --- /dev/null +++ b/public_html/lib/plugins/navi/conf/metadata.php @@ -0,0 +1,3 @@ +<?php + +$meta['arrow'] = array('multichoice','_choices' => array('left', 'right', 'none')); diff --git a/public_html/lib/plugins/navi/plugin.info.txt b/public_html/lib/plugins/navi/plugin.info.txt new file mode 100644 index 00000000..e8ebcfaa --- /dev/null +++ b/public_html/lib/plugins/navi/plugin.info.txt @@ -0,0 +1,8 @@ +base navi +author Andreas Gohr +email [email protected] +date 2016-10-12 +name Navigation Plugin +desc Build a navigation menu from a list +url http://www.dokuwiki.org/plugin:navi + diff --git a/public_html/lib/plugins/navi/script.js b/public_html/lib/plugins/navi/script.js new file mode 100644 index 00000000..9658d361 --- /dev/null +++ b/public_html/lib/plugins/navi/script.js @@ -0,0 +1,12 @@ +jQuery(function() { + 'use strict'; + + jQuery('li.open, li.close').find('> div.li').each(function (index, element){ + var link = jQuery(element).find('a').attr('href'); + var $arrowSpan = jQuery('<span></span>').click(function (event) { + window.location = link; + }); + $arrowSpan.addClass('arrowUnderlay'); + jQuery(element).append($arrowSpan); + }); +}); diff --git a/public_html/lib/plugins/navi/style.less b/public_html/lib/plugins/navi/style.less new file mode 100644 index 00000000..887bf4ba --- /dev/null +++ b/public_html/lib/plugins/navi/style.less @@ -0,0 +1,45 @@ +#dokuwiki__aside, .sidebar_box { +div.plugin__navi { + span.arrowUnderlay { + padding: 4px; + cursor: pointer; + background: no-repeat top right/100%; + } + + li.open { + span.arrowUnderlay { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAANlBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3dmhyAAAAEXRSTlMAAQcJCgsUIDZGW2Jjmt75/Rmge4gAAABISURBVBjTY2CgJWDmwBRj4RPkxiImiC4KEuNHEwWJcbPyoYiyQLgsyKJwDpIoNiY2TUyYxnMxsAlgOISXgYGdE827PIy0CEUAdlAEbg1NcZ0AAAAASUVORK5CYII=); + } + } + + li.close { + span.arrowUnderlay { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUBAMAAAB/pwA+AAAAMFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABaPxwLAAAAD3RSTlMAAQIECiMlYWJ3psPc3v2CpcpnAAAARklEQVQIW2NgIAgUoLQBg9QVCIvjGUP8Xwcws/83A8d/sDCY6gcLg0kwHyoHEoBIgUSggiDh+VBjgMIwQZghYMB+hIF8AABJKyB+OnNcUQAAAABJRU5ErkJggg==); + } + } + + &.none { + span.arrowUnderlay { + display: none; + } + } + + + &.left { + li { + list-style-type: none; + } + + span.arrowUnderlay { + position: absolute; + left: 1.3em; + } + } + + &.right { + span.arrowUnderlay { + float: right; + } + } +} +} diff --git a/public_html/lib/plugins/navi/syntax.php b/public_html/lib/plugins/navi/syntax.php new file mode 100644 index 00000000..eb75e2f4 --- /dev/null +++ b/public_html/lib/plugins/navi/syntax.php @@ -0,0 +1,241 @@ +<?php +/** + * Build a navigation menu from a list + * + * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) + * @author Andreas Gohr <[email protected]> + */ +// 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_navi extends DokuWiki_Syntax_Plugin { + + /** + * What kind of syntax are we? + */ + function getType(){ + return 'substition'; + } + + /** + * What about paragraphs? + */ + function getPType(){ + return 'block'; + } + + /** + * Where to sort in? + */ + function getSort(){ + return 155; + } + + /** + * Connect pattern to lexer + */ + function connectTo($mode) { + $this->Lexer->addSpecialPattern('{{navi>[^}]+}}',$mode,'plugin_navi'); + } + + /** + * Handle the match + */ + function handle($match, $state, $pos, Doku_Handler $handler){ + global $ID; + + $id = substr($match,7,-2); + list($id,$opt) = explode('?',$id,2); + $id = cleanID($id); + + // fetch the instructions of the control page + $instructions = p_cached_instructions(wikiFN($id),false,$id); + + // prepare some vars + $max = count($instructions); + $pre = true; + $lvl = 0; + $parents = array(); + $page = ''; + $cnt = 0; + + // build a lookup table + for($i=0; $i<$max; $i++){ + if($instructions[$i][0] == 'listu_open'){ + $pre = false; + $lvl++; + if($page) array_push($parents,$page); + }elseif($instructions[$i][0] == 'listu_close'){ + $lvl--; + array_pop($parents); + }elseif($pre || $lvl == 0){ + unset($instructions[$i]); + }elseif($instructions[$i][0] == 'listitem_close'){ + $cnt++; + }elseif($instructions[$i][0] == 'internallink'){ + $foo = true; + $page = $instructions[$i][1][0]; + resolve_pageid(getNS($ID),$page,$foo); // resolve relative to sidebar ID + $list[$page] = array( + 'parents' => $parents, + 'page' => $page, + 'title' => $instructions[$i][1][1], + 'lvl' => $lvl + ); + } elseif ($instructions[$i][0] == 'externallink') { + $url = $instructions[$i][1][0]; + $list['_'.$page] = array( + 'parents' => $parents, + 'page' => $url, + 'title' => $instructions[$i][1][1], + 'lvl' => $lvl + ); + } + } + return array(wikiFN($id),$list,$opt); + } + + /** + * Create output + * + * We handle all modes (except meta) because we pass all output creation back to the parent + */ + function render($format, Doku_Renderer $R, $data) { + $fn = $data[0]; + $opt = $data[2]; + $data = $data[1]; + + if($format == 'metadata'){ + $R->meta['relation']['naviplugin'][] = $fn; + return true; + } + + $R->info['cache'] = false; // no cache please + + $path = $this->getOpenPath($data, $opt); + $arrowLocation = $this->getConf('arrow'); + + $R->doc .= '<div class="plugin__navi ' . $arrowLocation . '">'; + $this->renderTree($data, $path, $R); + $R->doc .= '</div>'; + + return true; + } + + public function getOpenPath($data, $opt) { + global $INFO; + $openPath = array(); + if(isset($data[$INFO['id']])){ + $openPath = (array) $data[$INFO['id']]['parents']; // get the "path" of the page we're on currently + array_push($openPath,$INFO['id']); + }elseif($opt == 'ns'){ + $ns = $INFO['id']; + + // traverse up for matching namespaces + if($data) do { + $ns = getNS($ns); + $try = "$ns:"; + resolve_pageid('',$try,$foo); + if(isset($data[$try])){ + // got a start page + $openPath = (array) $data[$try]['parents']; + array_push($openPath,$try); + break; + }else{ + // search for the first page matching the namespace + foreach($data as $key => $junk){ + if(getNS($key) == $ns){ + $openPath = (array) $data[$key]['parents']; + array_push($openPath,$key); + break 2; + } + } + } + + }while($ns); + } + return $openPath; + } + + /** + * @param $data + * @param $parent + * @param Doku_Renderer $R + */ + public function renderTree($data, $parent, Doku_Renderer $R) { +// create a correctly nested list (or so I hope) + $open = false; + $lvl = 1; + $R->listu_open(); + + // read if item has childs and if it is open or closed + $upper = array(); + foreach ((array)$data as $pid => $info) { + $state = (array_diff($info['parents'], $parent)) ? 'close' : ''; + $countparents = count($info['parents']); + if ($countparents > '0') { + for ($i = 0; $i < $countparents; $i++) { + $upperlevel = $countparents - 1; + $upper[$info['parents'][$upperlevel]] = ($state == 'close') ? 'close' : 'open'; + } + } + } + unset($pid); + + foreach ((array)$data as $pid => $info) { + // only show if we are in the "path" + if (array_diff($info['parents'], $parent)) { + continue; + } + if ($upper[$pid]) { + $menuitem = ($upper[$pid] == 'open') ? 'open' : 'close'; + } else { + $menuitem = ''; + } + + // skip every non readable page + if (auth_quickaclcheck(cleanID($info['page'])) < AUTH_READ) { + continue; + } + + if ($info['lvl'] == $lvl) { + if ($open) { + $R->listitem_close(); + } + $R->listitem_open($lvl . ' ' . $menuitem); + $open = true; + } elseif ($lvl > $info['lvl']) { + for ($lvl; $lvl > $info['lvl']; --$lvl) { + $R->listitem_close(); + $R->listu_close(); + } + $R->listitem_close(); + $R->listitem_open($lvl . ' ' . $menuitem); + } elseif ($lvl < $info['lvl']) { + // more than one run is bad nesting! + for ($lvl; $lvl < $info['lvl']; ++$lvl) { + $R->listu_open(); + $R->listitem_open($lvl + 1 . ' ' . $menuitem); + $open = true; + } + } + + $R->listcontent_open(); + if (substr($pid, 0, 1) != '_') { + $R->internallink(':' . $info['page'], $info['title']); + } else { + $R->externallink($info['page'], $info['title']); + } + + $R->listcontent_close(); + } + while ($lvl > 0) { + $R->listitem_close(); + $R->listu_close(); + --$lvl; + } + } +} diff --git a/public_html/lib/tpl/e/css/modifications.css b/public_html/lib/tpl/e/css/modifications.css index 18e7d906..65205259 100644 --- a/public_html/lib/tpl/e/css/modifications.css +++ b/public_html/lib/tpl/e/css/modifications.css @@ -106,6 +106,25 @@ h1, h2, h3, h4, h5, h6, display: block; } +.nav .close { + float: none; + font-size: inherit; + font-weight: normal; + line-height: inherit; + color: inherit; + text-shadow: inherit; + opacity: 1; + filter: none; +} + +.nav .open { + font-weight: bold; +} + +.nav .open ul { + font-weight: normal; +} + @media (min-width: 768px) and (max-width: 993px) { .bs-sidebar { margin-top: 50px; --
