1<?php 2/** 3 * DokuWiki Syntax Plugin Combostrap. 4 * 5 */ 6 7use ComboStrap\CallStack; 8use ComboStrap\ColorRgb; 9use ComboStrap\MarkupRef; 10use ComboStrap\PluginUtility; 11use ComboStrap\Shadow; 12use ComboStrap\Site; 13use ComboStrap\Skin; 14use ComboStrap\TagAttributes; 15use ComboStrap\TextColor; 16 17if (!defined('DOKU_INC')) { 18 die(); 19} 20 21if (!defined('DOKU_PLUGIN')) { 22 define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/'); 23} 24 25 26require_once(DOKU_PLUGIN . 'syntax.php'); 27require_once(DOKU_INC . 'inc/parserutils.php'); 28require_once(__DIR__ . '/../ComboStrap/PluginUtility.php'); 29 30/** 31 * All DokuWiki plugins to extend the parser/rendering mechanism 32 * need to inherit from this class 33 * 34 * The name of the class must follow a pattern (don't change it) 35 * ie: 36 * syntax_plugin_PluginName_ComponentName 37 * 38 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 39 * !!!!!!!!!!! The component name must be the name of the php file !!! 40 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 41 * 42 * ===== For the Geek ===== 43 * This is not the [[https://www.w3.org/TR/wai-aria-practices/#button|Button as describe by the Web Specification]] 44 * but a styling over a [[https://www.w3.org/TR/wai-aria-practices/#link|link]] 45 * 46 * ===== Documentation / Reference ===== 47 * https://material.io/components/buttons 48 * https://getbootstrap.com/docs/4.5/components/buttons/ 49 */ 50class syntax_plugin_combo_button extends DokuWiki_Syntax_Plugin 51{ 52 53 54 const TAG = "button"; 55 56 57 /** 58 * Syntax Type. 59 * 60 * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 61 * @see DokuWiki_Syntax_Plugin::getType() 62 */ 63 function getType(): string 64 { 65 return 'formatting'; 66 } 67 68 /** 69 * @return array 70 * Allow which kind of plugin inside 71 * 72 * No one of array('container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs') 73 * because we manage self the content and we call self the parser 74 */ 75 public function getAllowedTypes(): array 76 { 77 return array('container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs'); 78 } 79 80 public function accepts($mode): bool 81 { 82 83 return syntax_plugin_combo_preformatted::disablePreformatted($mode); 84 85 } 86 87 /** 88 * How Dokuwiki will add P element 89 * 90 * * 'normal' - The plugin can be used inside paragraphs 91 * * 'block' - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs 92 * * 'stack' - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs 93 * 94 * @see DokuWiki_Syntax_Plugin::getPType() 95 */ 96 function getPType() 97 { 98 return 'normal'; 99 } 100 101 /** 102 * @see Doku_Parser_Mode::getSort() 103 * the mode with the lowest sort number will win out 104 * the lowest in the tree must have the lowest sort number 105 * No idea why it must be low but inside a teaser, it will work 106 * https://www.dokuwiki.org/devel:parser#order_of_adding_modes_important 107 */ 108 function getSort(): int 109 { 110 return 10; 111 } 112 113 /** 114 * Create a pattern that will called this plugin 115 * 116 * @param string $mode 117 * @see Doku_Parser_Mode::connectTo() 118 */ 119 function connectTo($mode) 120 { 121 122 foreach (self::getTags() as $tag) { 123 124 $pattern = PluginUtility::getContainerTagPattern($tag); 125 $this->Lexer->addEntryPattern($pattern, $mode, 'plugin_' . PluginUtility::PLUGIN_BASE_NAME . '_' . $this->getPluginComponent()); 126 127 } 128 129 } 130 131 public function postConnect() 132 { 133 134 foreach (self::getTags() as $tag) { 135 $this->Lexer->addExitPattern('</' . $tag . '>', 'plugin_' . PluginUtility::PLUGIN_BASE_NAME . '_' . $this->getPluginComponent()); 136 } 137 138 139 } 140 141 /** 142 * 143 * The handle function goal is to parse the matched syntax through the pattern function 144 * and to return the result for use in the renderer 145 * This result is always cached until the page is modified. 146 * @param string $match 147 * @param int $state 148 * @param int $pos 149 * @param Doku_Handler $handler 150 * @return array|bool 151 * @throws Exception 152 * @see DokuWiki_Syntax_Plugin::handle() 153 * 154 */ 155 function handle($match, $state, $pos, Doku_Handler $handler) 156 { 157 158 switch ($state) { 159 160 case DOKU_LEXER_ENTER: 161 162 $types = [ColorRgb::PRIMARY_VALUE, ColorRgb::SECONDARY_VALUE, "success", "danger", "warning", "info", "light", "dark"]; 163 $defaultAttributes = array( 164 Skin::SKIN_ATTRIBUTE => Skin::FILLED_VALUE, 165 TagAttributes::TYPE_KEY => ColorRgb::PRIMARY_VALUE 166 ); 167 $attributes = TagAttributes::createFromTagMatch($match, $defaultAttributes, $types); 168 169 /** 170 * Note: Branding color (primary and secondary) 171 * are set with the {@link Skin} 172 */ 173 174 /** 175 * The parent 176 * to apply automatically styling in a bar 177 */ 178 $callStack = CallStack::createFromHandler($handler); 179 $isInMenuBar = false; 180 while ($parent = $callStack->moveToParent()) { 181 if ($parent->getTagName() === syntax_plugin_combo_menubar::TAG) { 182 $isInMenuBar = true; 183 break; 184 } 185 } 186 if ($isInMenuBar) { 187 if (!$attributes->hasAttribute("class") && !$attributes->hasAttribute("spacing")) { 188 $attributes->addComponentAttributeValue("spacing", "mr-2 mb-2 mt-2 mb-lg-0 mt-lg-0"); 189 } 190 } 191 192 /** 193 * The context give set if this is a button 194 * or a link button 195 * The context is checked in the `exit` state 196 * Default context: This is not a link button 197 */ 198 $context = self::TAG; 199 200 201 return array( 202 PluginUtility::STATE => $state, 203 PluginUtility::ATTRIBUTES => $attributes->toCallStackArray(), 204 PluginUtility::CONTEXT => $context 205 ); 206 207 case DOKU_LEXER_UNMATCHED : 208 209 return PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); 210 211 212 case DOKU_LEXER_EXIT : 213 $callStack = CallStack::createFromHandler($handler); 214 $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall(); 215 /** 216 * Button or link button 217 */ 218 $context = self::TAG; 219 $descendant = $callStack->moveToFirstChildTag(); 220 if ($descendant !== false) { 221 if ($descendant->getTagName() === syntax_plugin_combo_link::TAG) { 222 $context = syntax_plugin_combo_link::TAG; 223 } 224 } 225 $openingTag->setContext($context); 226 227 return array( 228 PluginUtility::STATE => $state, 229 PluginUtility::CONTEXT => $context 230 ); 231 232 233 } 234 235 return array(); 236 237 } 238 239 /** 240 * Render the output 241 * @param string $format 242 * @param Doku_Renderer $renderer 243 * @param array $data - what the function handle() return'ed 244 * @return boolean - rendered correctly? (however, returned value is not used at the moment) 245 * @see DokuWiki_Syntax_Plugin::render() 246 * 247 * 248 */ 249 function render($format, Doku_Renderer $renderer, $data): bool 250 { 251 252 switch ($format) { 253 254 case 'xhtml': 255 { 256 257 /** @var Doku_Renderer_xhtml $renderer */ 258 259 /** 260 * CSS if dokuwiki class name for link 261 */ 262 if ($this->getConf(MarkupRef::CONF_USE_DOKUWIKI_CLASS_NAME, false)) { 263 PluginUtility::getSnippetManager()->attachCssInternalStyleSheetForSlot(self::TAG); 264 } 265 266 /** 267 * HTML 268 */ 269 $state = $data[PluginUtility::STATE]; 270 $callStackAttributes = $data[PluginUtility::ATTRIBUTES]; 271 $context = $data[PluginUtility::CONTEXT]; 272 switch ($state) { 273 274 case DOKU_LEXER_ENTER : 275 276 /** 277 * If this not a link button 278 * The context is set on the handle exit 279 */ 280 if ($context == self::TAG) { 281 $tagAttributes = TagAttributes::createFromCallStackArray($callStackAttributes, self::TAG) 282 ->setDefaultStyleClassShouldBeAdded(false); 283 self::processButtonAttributesToHtmlAttributes($tagAttributes); 284 $tagAttributes->addOutputAttributeValue("type", "button"); 285 $renderer->doc .= $tagAttributes->toHtmlEnterTag('button'); 286 } 287 break; 288 289 case DOKU_LEXER_UNMATCHED: 290 291 292 /** 293 * If this is a button and not a link button 294 */ 295 $renderer->doc .= PluginUtility::renderUnmatched($data); 296 break; 297 298 case DOKU_LEXER_EXIT : 299 300 301 /** 302 * If this is a button and not a link button 303 */ 304 if ($context === self::TAG) { 305 $renderer->doc .= '</button>'; 306 } 307 308 break; 309 } 310 return true; 311 } 312 313 } 314 return false; 315 } 316 317 318 public static function getTags(): array 319 { 320 $elements[] = self::TAG; 321 $elements[] = 'btn'; 322 return $elements; 323 } 324 325 /** 326 * @param TagAttributes $tagAttributes 327 */ 328 public static function processButtonAttributesToHtmlAttributes(TagAttributes &$tagAttributes) 329 { 330 # A button 331 $btn = "btn"; 332 $tagAttributes->addClassName($btn); 333 334 $type = $tagAttributes->getValue(TagAttributes::TYPE_KEY, "primary"); 335 $skin = $tagAttributes->getValue(Skin::SKIN_ATTRIBUTE, Skin::FILLED_VALUE); 336 switch ($skin) { 337 case "contained": 338 { 339 $tagAttributes->addClassName("$btn-$type"); 340 $tagAttributes->addComponentAttributeValue(Shadow::CANONICAL, true); 341 break; 342 } 343 case "filled": 344 { 345 $tagAttributes->addClassName("$btn-$type"); 346 break; 347 } 348 case "outline": 349 { 350 $tagAttributes->addClassName("$btn-outline-$type"); 351 break; 352 } 353 case "text": 354 { 355 $tagAttributes->addClassName("$btn-link"); 356 $tagAttributes->addComponentAttributeValue(TextColor::TEXT_COLOR_ATTRIBUTE, $type); 357 break; 358 } 359 } 360 361 362 $sizeAttribute = "size"; 363 if ($tagAttributes->hasComponentAttribute($sizeAttribute)) { 364 $size = $tagAttributes->getValueAndRemove($sizeAttribute); 365 switch ($size) { 366 case "lg": 367 case "large": 368 $tagAttributes->addClassName("btn-lg"); 369 break; 370 case "sm": 371 case "small": 372 $tagAttributes->addClassName("btn-sm"); 373 break; 374 } 375 } 376 } 377 378 379} 380