1<?php 2/** 3 * DokuWiki Syntax Plugin Combostrap. 4 * 5 */ 6 7use ComboStrap\PluginUtility; 8 9if (!defined('DOKU_INC')) { 10 die(); 11} 12 13require_once(__DIR__ . '/../ComboStrap/PluginUtility.php'); 14 15/** 16 * All DokuWiki plugins to extend the parser/rendering mechanism 17 * need to inherit from this class 18 * 19 * The name of the class must follow a pattern (don't change it) 20 * ie: 21 * syntax_plugin_PluginName_ComponentName 22 * 23 * See also: doc : 24 * https://getbootstrap.com/docs/5.0/components/navbar/ 25 * https://material.io/components/app-bars-top 26 * 27 * Name: 28 * * header bar: http://themenectar.com/docs/salient/theme-options/header-navigation 29 * * menu bar: https://en.wikipedia.org/wiki/Menu_bar 30 * * app bar: https://material.io/components/app-bars-top 31 * * navbar: https://getbootstrap.com/docs/5.0/examples/navbars/# 32 */ 33class syntax_plugin_combo_menubar extends DokuWiki_Syntax_Plugin 34{ 35 36 const TAG = 'menubar'; 37 const OLD_TAG = "navbar"; 38 const TAGS = [self::TAG,self::OLD_TAG]; 39 40 /** 41 * Do we need to add a container 42 * @var bool 43 */ 44 private $containerInside = false; 45 46 /** 47 * Syntax Type. 48 * 49 * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 50 * @see DokuWiki_Syntax_Plugin::getType() 51 */ 52 function getType() 53 { 54 return 'container'; 55 } 56 57 /** 58 * @return array 59 * Allow which kind of plugin inside 60 * All 61 */ 62 public function getAllowedTypes() 63 { 64 return array('container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs'); 65 } 66 67 public function accepts($mode) 68 { 69 70 $accept = syntax_plugin_combo_preformatted::disablePreformatted($mode); 71 72 73 // Create P element 74 if ($mode == "eol") { 75 $accept = false; 76 } 77 78 return $accept; 79 80 } 81 82 /** 83 * How Dokuwiki will add P element 84 * 85 * * 'normal' - The plugin can be used inside paragraphs 86 * * 'block' - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs 87 * * 'stack' - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs 88 * 89 * @see DokuWiki_Syntax_Plugin::getPType() 90 */ 91 function getPType() 92 { 93 return 'block'; 94 } 95 96 /** 97 * @see Doku_Parser_Mode::getSort() 98 * 99 * the mode with the lowest sort number will win out 100 * the container (parent) must then have a lower number than the child 101 */ 102 function getSort() 103 { 104 return 100; 105 } 106 107 /** 108 * Create a pattern that will called this plugin 109 * 110 * @param string $mode 111 * @see Doku_Parser_Mode::connectTo() 112 */ 113 function connectTo($mode) 114 { 115 116 foreach(self::TAGS as $tag) { 117 $pattern = PluginUtility::getContainerTagPattern($tag); 118 $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 119 } 120 121 } 122 123 public function postConnect() 124 { 125 foreach(self::TAGS as $tag) { 126 $this->Lexer->addExitPattern('</' . $tag . '>', PluginUtility::getModeFromTag($this->getPluginComponent())); 127 } 128 129 } 130 131 132 /** 133 * 134 * The handle function goal is to parse the matched syntax through the pattern function 135 * and to return the result for use in the renderer 136 * This result is always cached until the page is modified. 137 * @param string $match 138 * @param int $state 139 * @param int $pos 140 * @param Doku_Handler $handler 141 * @return array|bool 142 * @see DokuWiki_Syntax_Plugin::handle() 143 * 144 */ 145 function handle($match, $state, $pos, Doku_Handler $handler) 146 { 147 148 switch ($state) { 149 150 case DOKU_LEXER_ENTER: 151 152 $tagAttributes = PluginUtility::getTagAttributes($match); 153 return array( 154 PluginUtility::STATE => $state, 155 PluginUtility::ATTRIBUTES => $tagAttributes 156 ); 157 158 case DOKU_LEXER_UNMATCHED: 159 160 return PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); 161 162 case DOKU_LEXER_EXIT : 163 164 return array( 165 PluginUtility::STATE => $state 166 ); 167 168 169 } 170 171 return array(); 172 173 } 174 175 /** 176 * Render the output 177 * @param string $format 178 * @param Doku_Renderer $renderer 179 * @param array $data - what the function handle() return'ed 180 * @return boolean - rendered correctly? (however, returned value is not used at the moment) 181 * @see DokuWiki_Syntax_Plugin::render() 182 * 183 * 184 */ 185 function render($format, Doku_Renderer $renderer, $data): bool 186 { 187 188 if ($format == 'xhtml') { 189 190 /** @var Doku_Renderer_xhtml $renderer */ 191 $state = $data[PluginUtility::STATE]; 192 switch ($state) { 193 194 case DOKU_LEXER_ENTER : 195 196 $attributes = $data[PluginUtility::ATTRIBUTES]; 197 $class = 'navbar'; 198 if (array_key_exists("class", $attributes)) { 199 $attributes["class"] .= ' ' . $class; 200 } else { 201 $attributes["class"] = $class; 202 } 203 204 if (!array_key_exists("background-color", $attributes)) { 205 $attributes["background-color"] = 'light'; 206 } 207 208 /** 209 * Without the expand, the flex has a row direction 210 * and not a column 211 */ 212 $breakpoint = "lg"; 213 if (array_key_exists("breakpoint", $attributes)) { 214 $breakpoint = $attributes["breakpoint"]; 215 unset($attributes["breakpoint"]); 216 } 217 $attributes["class"] .= ' navbar-expand-' . $breakpoint; 218 219 // Grab the position 220 if (array_key_exists("position", $attributes)) { 221 $position = $attributes["position"]; 222 if ($position === "top") { 223 $fixedTopName = 'fixed-top'; 224 $attributes["data-type"] .= $fixedTopName; 225 $fixedTopSnippetId = self::TAG."-".$fixedTopName; 226 // See http://stackoverflow.com/questions/17181355/boostrap-using-fixed-navbar-and-anchor-tags-to-jump-to-sections 227 PluginUtility::getSnippetManager()->attachInternalJavascriptForSlot($fixedTopSnippetId); 228 } 229 unset($attributes["position"]); 230 } 231 232 // Theming 233 $theme = "light"; 234 if (array_key_exists("theme", $attributes)) { 235 $theme = $attributes["theme"]; 236 unset($attributes["theme"]); 237 } 238 $attributes["class"] .= ' navbar-' . $theme; 239 240 // Align 241 $align = "center"; 242 if (array_key_exists("align", $attributes)) { 243 $align = $attributes["align"]; 244 unset($attributes["align"]); 245 } 246 247 // Container 248 if ($align === "center") { 249 $this->containerInside = true; 250 } 251 252 253 $inlineAttributes = PluginUtility::array2HTMLAttributesAsString($attributes); 254 255 $containerTag = ""; 256 if ($this->containerInside) { 257 $containerTag = '<div class="container">'; 258 } 259 260 $renderer->doc .= "<nav {$inlineAttributes}>" . DOKU_LF; 261 262 // When the top is fixed, the container should be inside the navbar 263 $renderer->doc .= "{$containerTag}" . DOKU_LF; 264 break; 265 266 case DOKU_LEXER_UNMATCHED: 267 $renderer->doc .= PluginUtility::renderUnmatched($data); 268 break; 269 270 case DOKU_LEXER_EXIT : 271 $containerTag = ""; 272 if ($this->containerInside) { 273 $containerTag = '</div>'; 274 } 275 $renderer->doc .= "{$containerTag}</nav>" . DOKU_LF; 276 break; 277 } 278 return true; 279 } 280 return false; 281 } 282 283 284} 285