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