1<?php 2 3 4use ComboStrap\Bootstrap; 5use ComboStrap\CallStack; 6use ComboStrap\LogUtility; 7use ComboStrap\PluginUtility; 8use ComboStrap\TagAttributes; 9use ComboStrap\Tooltip; 10 11if (!defined('DOKU_INC')) die(); 12 13/** 14 * Class syntax_plugin_combo_tooltip 15 * Implementation of a tooltip 16 * 17 * A tooltip is implemented as a super title attribute 18 * on a HTML element such as a link or a button 19 * 20 * The implementation pass the information that there is 21 * a tooltip on the container which makes the output of {@link TagAttributes::toHtmlEnterTag()} 22 * to print all attributes until the title and not closing. 23 * 24 * Bootstrap generate the <a href="https://getbootstrap.com/docs/5.0/components/tooltips/#markup">markup tooltip</a> 25 * on the fly. It's possible to generate a bootstrap markup like and use popper directly 26 * but this is far more difficult 27 * 28 * 29 * https://material.io/components/tooltips 30 * [[https://getbootstrap.com/docs/4.0/components/tooltips/|Tooltip Boostrap version 4]] 31 * [[https://getbootstrap.com/docs/5.0/components/tooltips/|Tooltip Boostrap version 5]] 32 */ 33class syntax_plugin_combo_tooltip extends DokuWiki_Syntax_Plugin 34{ 35 36 const TAG = "tooltip"; 37 38 /** 39 * Class added to the parent 40 */ 41 const CANONICAL = "tooltip"; 42 public const TEXT_ATTRIBUTE = "text"; 43 44 /** 45 * To see the tooltip immediately when hovering the class d-inline-block 46 * 47 * The inline block is to make the element (span) take the whole space 48 * of the image (ie dimension) otherwise it has no dimension and 49 * you can't click on it 50 * 51 * TODO: Add this to the {@link Tooltip} ??? 52 */ 53 const TOOLTIP_CLASS_INLINE_BLOCK = "d-inline-block"; 54 55 /** 56 * Syntax Type. 57 * 58 * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 59 * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 60 * @see DokuWiki_Syntax_Plugin::getType() 61 */ 62 function getType(): string 63 { 64 /** 65 * You could add a tooltip to a {@link syntax_plugin_combo_itext} 66 */ 67 return 'formatting'; 68 } 69 70 /** 71 * How Dokuwiki will add P element 72 * 73 * * 'normal' - The plugin can be used inside paragraphs (inline) 74 * * 'block' - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs 75 * * 'stack' - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs 76 * 77 * @see DokuWiki_Syntax_Plugin::getPType() 78 * @see https://www.dokuwiki.org/devel:syntax_plugins#ptype 79 */ 80 function getPType(): string 81 { 82 return 'normal'; 83 } 84 85 /** 86 * @return array 87 * Allow which kind of plugin inside 88 * 89 * No one of array('baseonly','container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs') 90 * because we manage self the content and we call self the parser 91 * 92 * Return an array of one or more of the mode types {@link $PARSER_MODES} in Parser.php 93 */ 94 function getAllowedTypes(): array 95 { 96 return array('baseonly', 'container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs'); 97 } 98 99 function getSort(): int 100 { 101 return 201; 102 } 103 104 105 function connectTo($mode) 106 { 107 108 $pattern = PluginUtility::getContainerTagPattern(self::TAG); 109 $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 110 111 } 112 113 function postConnect() 114 { 115 116 $this->Lexer->addExitPattern('</' . self::TAG . '>', PluginUtility::getModeFromTag($this->getPluginComponent())); 117 118 } 119 120 /** 121 * 122 * The handle function goal is to parse the matched syntax through the pattern function 123 * and to return the result for use in the renderer 124 * This result is always cached until the page is modified. 125 * @param string $match 126 * @param int $state 127 * @param int $pos - byte position in the original source file 128 * @param Doku_Handler $handler 129 * @return array 130 * @see DokuWiki_Syntax_Plugin::handle() 131 * 132 */ 133 function handle($match, $state, $pos, Doku_Handler $handler): array 134 { 135 136 switch ($state) { 137 138 case DOKU_LEXER_ENTER : 139 $tagAttributes = TagAttributes::createFromTagMatch($match); 140 return array( 141 PluginUtility::STATE => $state, 142 PluginUtility::ATTRIBUTES => $tagAttributes->toCallStackArray() 143 ); 144 145 146 case DOKU_LEXER_UNMATCHED : 147 return PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); 148 149 case DOKU_LEXER_EXIT : 150 151 $callStack = CallStack::createFromHandler($handler); 152 $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall(); 153 if ($openingTag->hasAttribute(self::TEXT_ATTRIBUTE)) { 154 /** 155 * Old syntax where the tooltip was the wrapper 156 */ 157 return array( 158 PluginUtility::STATE => $state, 159 PluginUtility::ATTRIBUTES=>$openingTag->getAttributes() 160 ); 161 } 162 $parent = $callStack->moveToParent(); 163 if ($parent === false) { 164 return array( 165 PluginUtility::STATE => $state, 166 PluginUtility::EXIT_MESSAGE => "A parent is mandatory for a tooltip", 167 PluginUtility::EXIT_CODE => 1 168 ); 169 } 170 171 /** 172 * Capture the callstack 173 */ 174 $callStack->moveToCall($openingTag); 175 $toolTipCallStack = null; 176 while ($actualCall = $callStack->next()) { 177 $toolTipCallStack[] = $actualCall->toCallArray(); 178 } 179 $callStack->deleteAllCallsAfter($openingTag); 180 181 /** 182 * Set on the parent the tooltip attributes 183 * It will be processed by the {@link Tooltip} 184 * class at the end of {@link TagAttributes::toHtmlEnterTag()} 185 */ 186 $attributes = $openingTag->getAttributes(); 187 $attributes[Tooltip::CALLSTACK] = $toolTipCallStack; 188 $parent->addAttribute(Tooltip::TOOLTIP_ATTRIBUTE, $attributes); 189 190 return array( 191 PluginUtility::STATE => $state 192 ); 193 194 195 } 196 return array(); 197 198 } 199 200 /** 201 * Render the output 202 * @param string $format 203 * @param Doku_Renderer $renderer 204 * @param array $data - what the function handle() return'ed 205 * @return boolean - rendered correctly? (however, returned value is not used at the moment) 206 * @see DokuWiki_Syntax_Plugin::render() 207 * 208 * 209 */ 210 function render($format, Doku_Renderer $renderer, $data): bool 211 { 212 if ($format == 'xhtml') { 213 214 /** @var Doku_Renderer_xhtml $renderer */ 215 $state = $data[PluginUtility::STATE]; 216 switch ($state) { 217 218 case DOKU_LEXER_ENTER : 219 /** 220 * Old syntax 221 * where tooltip was enclosing the text with the tooltip 222 */ 223 $callStackArray = $data[PluginUtility::ATTRIBUTES]; 224 $tagAttributes = TagAttributes::createFromCallStackArray($callStackArray); 225 $text = $tagAttributes->getValue(self::TEXT_ATTRIBUTE); 226 if ($text !== null) { 227 /** 228 * Old syntax where the tooltip was the wrapper 229 */ 230 $renderer->doc .= TagAttributes::createFromCallStackArray([Tooltip::TOOLTIP_ATTRIBUTE => $callStackArray]) 231 ->addClassName(self::TOOLTIP_CLASS_INLINE_BLOCK) 232 ->toHtmlEnterTag("span"); 233 } 234 break; 235 236 case DOKU_LEXER_UNMATCHED: 237 $renderer->doc .= PluginUtility::renderUnmatched($data); 238 break; 239 240 case DOKU_LEXER_EXIT: 241 $message = $data[PluginUtility::EXIT_MESSAGE]; 242 if ($message !== null) { 243 $renderer->doc .= LogUtility::wrapInRedForHtml($message); 244 return false; 245 } 246 247 $callStackArray = $data[PluginUtility::ATTRIBUTES]; 248 $tagAttributes = TagAttributes::createFromCallStackArray($callStackArray); 249 $text = $tagAttributes->getValue(self::TEXT_ATTRIBUTE); 250 if ($text !== null) { 251 /** 252 * Old syntax where the tooltip was the wrapper 253 */ 254 $renderer->doc .= "</span>"; 255 } 256 257 break; 258 259 260 } 261 return true; 262 } 263 264 // unsupported $mode 265 return false; 266 } 267 268 269} 270 271