1<?php 2 3 4use ComboStrap\Bootstrap; 5use ComboStrap\CallStack; 6use ComboStrap\LogUtility; 7use ComboStrap\PluginUtility; 8use ComboStrap\TagAttributes; 9use ComboStrap\Tooltip; 10use ComboStrap\XmlTagProcessing; 11 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 public function accepts($mode): bool 100 { 101 return syntax_plugin_combo_preformatted::disablePreformatted($mode); 102 } 103 104 function getSort(): int 105 { 106 return 201; 107 } 108 109 110 function connectTo($mode) 111 { 112 113 $pattern = XmlTagProcessing::getContainerTagPattern(self::TAG); 114 $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 115 116 } 117 118 function postConnect() 119 { 120 121 $this->Lexer->addExitPattern('</' . self::TAG . '>', PluginUtility::getModeFromTag($this->getPluginComponent())); 122 123 } 124 125 /** 126 * 127 * The handle function goal is to parse the matched syntax through the pattern function 128 * and to return the result for use in the renderer 129 * This result is always cached until the page is modified. 130 * @param string $match 131 * @param int $state 132 * @param int $pos - byte position in the original source file 133 * @param Doku_Handler $handler 134 * @return array 135 * @see DokuWiki_Syntax_Plugin::handle() 136 * 137 */ 138 function handle($match, $state, $pos, Doku_Handler $handler): array 139 { 140 141 switch ($state) { 142 143 case DOKU_LEXER_ENTER : 144 $tagAttributes = TagAttributes::createFromTagMatch($match); 145 return array( 146 PluginUtility::STATE => $state, 147 PluginUtility::ATTRIBUTES => $tagAttributes->toCallStackArray() 148 ); 149 150 151 case DOKU_LEXER_UNMATCHED : 152 return PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); 153 154 case DOKU_LEXER_EXIT : 155 156 $callStack = CallStack::createFromHandler($handler); 157 $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall(); 158 if ($openingTag->hasAttribute(self::TEXT_ATTRIBUTE)) { 159 /** 160 * Old syntax where the tooltip was the wrapper 161 */ 162 return array( 163 PluginUtility::STATE => $state, 164 PluginUtility::ATTRIBUTES => $openingTag->getAttributes() 165 ); 166 } 167 $parent = $callStack->moveToParent(); 168 if ($parent === false) { 169 return array( 170 PluginUtility::STATE => $state, 171 PluginUtility::EXIT_MESSAGE => "A parent is mandatory for a tooltip", 172 PluginUtility::EXIT_CODE => 1 173 ); 174 } 175 176 /** 177 * Capture the callstack 178 */ 179 $callStack->moveToCall($openingTag); 180 $toolTipCallStack = null; 181 while ($actualCall = $callStack->next()) { 182 $toolTipCallStack[] = $actualCall->toCallArray(); 183 } 184 $callStack->deleteAllCallsAfter($openingTag); 185 186 /** 187 * Set on the parent the tooltip attributes 188 * It will be processed by the {@link Tooltip} 189 * class at the end of {@link TagAttributes::toHtmlEnterTag()} 190 */ 191 $attributes = $openingTag->getAttributes(); 192 $attributes[Tooltip::CALLSTACK] = $toolTipCallStack; 193 $parent->addAttribute(Tooltip::TOOLTIP_ATTRIBUTE, $attributes); 194 195 return array( 196 PluginUtility::STATE => $state 197 ); 198 199 200 } 201 return array(); 202 203 } 204 205 /** 206 * Render the output 207 * @param string $format 208 * @param Doku_Renderer $renderer 209 * @param array $data - what the function handle() return'ed 210 * @return boolean - rendered correctly? (however, returned value is not used at the moment) 211 * @see DokuWiki_Syntax_Plugin::render() 212 * 213 * 214 */ 215 function render($format, Doku_Renderer $renderer, $data): bool 216 { 217 if ($format == 'xhtml') { 218 219 /** @var Doku_Renderer_xhtml $renderer */ 220 $state = $data[PluginUtility::STATE]; 221 switch ($state) { 222 223 case DOKU_LEXER_ENTER : 224 /** 225 * Old syntax 226 * where tooltip was enclosing the text with the tooltip 227 */ 228 $callStackArray = $data[PluginUtility::ATTRIBUTES]; 229 $tagAttributes = TagAttributes::createFromCallStackArray($callStackArray); 230 $text = $tagAttributes->getValue(self::TEXT_ATTRIBUTE); 231 if ($text !== null) { 232 /** 233 * Old syntax where the tooltip was the wrapper 234 */ 235 $renderer->doc .= TagAttributes::createFromCallStackArray([Tooltip::TOOLTIP_ATTRIBUTE => $callStackArray]) 236 ->addClassName(self::TOOLTIP_CLASS_INLINE_BLOCK) 237 ->toHtmlEnterTag("span"); 238 } 239 break; 240 241 case DOKU_LEXER_UNMATCHED: 242 $renderer->doc .= PluginUtility::renderUnmatched($data); 243 break; 244 245 case DOKU_LEXER_EXIT: 246 $message = $data[PluginUtility::EXIT_MESSAGE] ?? null; 247 if ($message !== null) { 248 $renderer->doc .= LogUtility::wrapInRedForHtml($message); 249 return false; 250 } 251 252 $callStackArray = $data[PluginUtility::ATTRIBUTES] ?? null; 253 $tagAttributes = TagAttributes::createFromCallStackArray($callStackArray); 254 $text = $tagAttributes->getValue(self::TEXT_ATTRIBUTE); 255 if ($text !== null) { 256 /** 257 * Old syntax where the tooltip was the wrapper 258 */ 259 $renderer->doc .= "</span>"; 260 } 261 262 break; 263 264 265 } 266 return true; 267 } 268 269 // unsupported $mode 270 return false; 271 } 272 273 274} 275 276