1007225e5Sgerardnico<?php 2007225e5Sgerardnico 3007225e5Sgerardnico 421913ab3SNickeauuse ComboStrap\Bootstrap; 585e82846SNickeauuse ComboStrap\CallStack; 685e82846SNickeauuse ComboStrap\LogUtility; 7007225e5Sgerardnicouse ComboStrap\PluginUtility; 885e82846SNickeauuse ComboStrap\TagAttributes; 9*4cadd4f8SNickeauuse ComboStrap\Tooltip; 10007225e5Sgerardnico 11007225e5Sgerardnicoif (!defined('DOKU_INC')) die(); 12007225e5Sgerardnico 13007225e5Sgerardnico/** 14007225e5Sgerardnico * Class syntax_plugin_combo_tooltip 15007225e5Sgerardnico * Implementation of a tooltip 1685e82846SNickeau * 1785e82846SNickeau * A tooltip is implemented as a super title attribute 1885e82846SNickeau * on a HTML element such as a link or a button 1985e82846SNickeau * 2085e82846SNickeau * The implementation pass the information that there is 2185e82846SNickeau * a tooltip on the container which makes the output of {@link TagAttributes::toHtmlEnterTag()} 2285e82846SNickeau * to print all attributes until the title and not closing. 2385e82846SNickeau * 2485e82846SNickeau * Bootstrap generate the <a href="https://getbootstrap.com/docs/5.0/components/tooltips/#markup">markup tooltip</a> 2585e82846SNickeau * on the fly. It's possible to generate a bootstrap markup like and use popper directly 2685e82846SNickeau * but this is far more difficult 2785e82846SNickeau * 2885e82846SNickeau * 2985e82846SNickeau * https://material.io/components/tooltips 3085e82846SNickeau * [[https://getbootstrap.com/docs/4.0/components/tooltips/|Tooltip Boostrap version 4]] 3185e82846SNickeau * [[https://getbootstrap.com/docs/5.0/components/tooltips/|Tooltip Boostrap version 5]] 32007225e5Sgerardnico */ 33007225e5Sgerardnicoclass syntax_plugin_combo_tooltip extends DokuWiki_Syntax_Plugin 34007225e5Sgerardnico{ 35007225e5Sgerardnico 36007225e5Sgerardnico const TAG = "tooltip"; 3785e82846SNickeau 3885e82846SNickeau /** 3985e82846SNickeau * Class added to the parent 4085e82846SNickeau */ 4185e82846SNickeau const CANONICAL = "tooltip"; 42*4cadd4f8SNickeau public const TEXT_ATTRIBUTE = "text"; 4385e82846SNickeau 4485e82846SNickeau /** 45*4cadd4f8SNickeau * To see the tooltip immediately when hovering the class d-inline-block 46*4cadd4f8SNickeau * 47*4cadd4f8SNickeau * The inline block is to make the element (span) take the whole space 48*4cadd4f8SNickeau * of the image (ie dimension) otherwise it has no dimension and 49*4cadd4f8SNickeau * you can't click on it 50*4cadd4f8SNickeau * 51*4cadd4f8SNickeau * TODO: Add this to the {@link Tooltip} ??? 5285e82846SNickeau */ 53*4cadd4f8SNickeau const TOOLTIP_CLASS_INLINE_BLOCK = "d-inline-block"; 54007225e5Sgerardnico 55007225e5Sgerardnico /** 56007225e5Sgerardnico * Syntax Type. 57007225e5Sgerardnico * 58007225e5Sgerardnico * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 59007225e5Sgerardnico * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 60007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::getType() 61007225e5Sgerardnico */ 62*4cadd4f8SNickeau function getType(): string 63007225e5Sgerardnico { 6485e82846SNickeau /** 6585e82846SNickeau * You could add a tooltip to a {@link syntax_plugin_combo_itext} 6685e82846SNickeau */ 6785e82846SNickeau return 'formatting'; 68007225e5Sgerardnico } 69007225e5Sgerardnico 70007225e5Sgerardnico /** 71007225e5Sgerardnico * How Dokuwiki will add P element 72007225e5Sgerardnico * 73007225e5Sgerardnico * * 'normal' - The plugin can be used inside paragraphs (inline) 74007225e5Sgerardnico * * 'block' - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs 75007225e5Sgerardnico * * 'stack' - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs 76007225e5Sgerardnico * 77007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::getPType() 78007225e5Sgerardnico * @see https://www.dokuwiki.org/devel:syntax_plugins#ptype 79007225e5Sgerardnico */ 80*4cadd4f8SNickeau function getPType(): string 81007225e5Sgerardnico { 82007225e5Sgerardnico return 'normal'; 83007225e5Sgerardnico } 84007225e5Sgerardnico 85007225e5Sgerardnico /** 86007225e5Sgerardnico * @return array 87007225e5Sgerardnico * Allow which kind of plugin inside 88007225e5Sgerardnico * 89007225e5Sgerardnico * No one of array('baseonly','container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs') 90007225e5Sgerardnico * because we manage self the content and we call self the parser 91007225e5Sgerardnico * 92007225e5Sgerardnico * Return an array of one or more of the mode types {@link $PARSER_MODES} in Parser.php 93007225e5Sgerardnico */ 94*4cadd4f8SNickeau function getAllowedTypes(): array 95007225e5Sgerardnico { 96007225e5Sgerardnico return array('baseonly', 'container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs'); 97007225e5Sgerardnico } 98007225e5Sgerardnico 99*4cadd4f8SNickeau function getSort(): int 100007225e5Sgerardnico { 101007225e5Sgerardnico return 201; 102007225e5Sgerardnico } 103007225e5Sgerardnico 104007225e5Sgerardnico 105007225e5Sgerardnico function connectTo($mode) 106007225e5Sgerardnico { 107007225e5Sgerardnico 108007225e5Sgerardnico $pattern = PluginUtility::getContainerTagPattern(self::TAG); 1099337a630SNickeau $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 110007225e5Sgerardnico 111007225e5Sgerardnico } 112007225e5Sgerardnico 113007225e5Sgerardnico function postConnect() 114007225e5Sgerardnico { 115007225e5Sgerardnico 1169337a630SNickeau $this->Lexer->addExitPattern('</' . self::TAG . '>', PluginUtility::getModeFromTag($this->getPluginComponent())); 117007225e5Sgerardnico 118007225e5Sgerardnico } 119007225e5Sgerardnico 120007225e5Sgerardnico /** 121007225e5Sgerardnico * 122007225e5Sgerardnico * The handle function goal is to parse the matched syntax through the pattern function 123007225e5Sgerardnico * and to return the result for use in the renderer 124007225e5Sgerardnico * This result is always cached until the page is modified. 125007225e5Sgerardnico * @param string $match 126007225e5Sgerardnico * @param int $state 127007225e5Sgerardnico * @param int $pos - byte position in the original source file 128007225e5Sgerardnico * @param Doku_Handler $handler 129*4cadd4f8SNickeau * @return array 130007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::handle() 131007225e5Sgerardnico * 132007225e5Sgerardnico */ 133*4cadd4f8SNickeau function handle($match, $state, $pos, Doku_Handler $handler): array 134007225e5Sgerardnico { 135007225e5Sgerardnico 136007225e5Sgerardnico switch ($state) { 137007225e5Sgerardnico 138007225e5Sgerardnico case DOKU_LEXER_ENTER : 13985e82846SNickeau $tagAttributes = TagAttributes::createFromTagMatch($match); 14085e82846SNickeau return array( 14185e82846SNickeau PluginUtility::STATE => $state, 14285e82846SNickeau PluginUtility::ATTRIBUTES => $tagAttributes->toCallStackArray() 14385e82846SNickeau ); 14485e82846SNickeau 145007225e5Sgerardnico 146007225e5Sgerardnico case DOKU_LEXER_UNMATCHED : 14732b85071SNickeau return PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); 148007225e5Sgerardnico 149007225e5Sgerardnico case DOKU_LEXER_EXIT : 150007225e5Sgerardnico 15185e82846SNickeau $callStack = CallStack::createFromHandler($handler); 15285e82846SNickeau $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall(); 153*4cadd4f8SNickeau if ($openingTag->hasAttribute(self::TEXT_ATTRIBUTE)) { 154*4cadd4f8SNickeau /** 155*4cadd4f8SNickeau * Old syntax where the tooltip was the wrapper 156*4cadd4f8SNickeau */ 157007225e5Sgerardnico return array( 158007225e5Sgerardnico PluginUtility::STATE => $state, 15985e82846SNickeau PluginUtility::ATTRIBUTES=>$openingTag->getAttributes() 160007225e5Sgerardnico ); 161*4cadd4f8SNickeau } 162*4cadd4f8SNickeau $parent = $callStack->moveToParent(); 163*4cadd4f8SNickeau if ($parent === false) { 164*4cadd4f8SNickeau return array( 165*4cadd4f8SNickeau PluginUtility::STATE => $state, 166*4cadd4f8SNickeau PluginUtility::EXIT_MESSAGE => "A parent is mandatory for a tooltip", 167*4cadd4f8SNickeau PluginUtility::EXIT_CODE => 1 168*4cadd4f8SNickeau ); 169*4cadd4f8SNickeau } 170*4cadd4f8SNickeau 171*4cadd4f8SNickeau /** 172*4cadd4f8SNickeau * Capture the callstack 173*4cadd4f8SNickeau */ 174*4cadd4f8SNickeau $callStack->moveToCall($openingTag); 175*4cadd4f8SNickeau $toolTipCallStack = null; 176*4cadd4f8SNickeau while ($actualCall = $callStack->next()) { 177*4cadd4f8SNickeau $toolTipCallStack[] = $actualCall->toCallArray(); 178*4cadd4f8SNickeau } 179*4cadd4f8SNickeau $callStack->deleteAllCallsAfter($openingTag); 180*4cadd4f8SNickeau 181*4cadd4f8SNickeau /** 182*4cadd4f8SNickeau * Set on the parent the tooltip attributes 183*4cadd4f8SNickeau * It will be processed by the {@link Tooltip} 184*4cadd4f8SNickeau * class at the end of {@link TagAttributes::toHtmlEnterTag()} 185*4cadd4f8SNickeau */ 186*4cadd4f8SNickeau $attributes = $openingTag->getAttributes(); 187*4cadd4f8SNickeau $attributes[Tooltip::CALLSTACK] = $toolTipCallStack; 188*4cadd4f8SNickeau $parent->addAttribute(Tooltip::TOOLTIP_ATTRIBUTE, $attributes); 189*4cadd4f8SNickeau 190*4cadd4f8SNickeau return array( 191*4cadd4f8SNickeau PluginUtility::STATE => $state 192*4cadd4f8SNickeau ); 193007225e5Sgerardnico 194007225e5Sgerardnico 195007225e5Sgerardnico } 196007225e5Sgerardnico return array(); 197007225e5Sgerardnico 198007225e5Sgerardnico } 199007225e5Sgerardnico 200007225e5Sgerardnico /** 201007225e5Sgerardnico * Render the output 202007225e5Sgerardnico * @param string $format 203007225e5Sgerardnico * @param Doku_Renderer $renderer 204007225e5Sgerardnico * @param array $data - what the function handle() return'ed 205007225e5Sgerardnico * @return boolean - rendered correctly? (however, returned value is not used at the moment) 206007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::render() 207007225e5Sgerardnico * 208007225e5Sgerardnico * 209007225e5Sgerardnico */ 210*4cadd4f8SNickeau function render($format, Doku_Renderer $renderer, $data): bool 211007225e5Sgerardnico { 212007225e5Sgerardnico if ($format == 'xhtml') { 213007225e5Sgerardnico 214007225e5Sgerardnico /** @var Doku_Renderer_xhtml $renderer */ 215007225e5Sgerardnico $state = $data[PluginUtility::STATE]; 216007225e5Sgerardnico switch ($state) { 217007225e5Sgerardnico 21885e82846SNickeau case DOKU_LEXER_ENTER : 219*4cadd4f8SNickeau /** 220*4cadd4f8SNickeau * Old syntax 221*4cadd4f8SNickeau * where tooltip was enclosing the text with the tooltip 222*4cadd4f8SNickeau */ 223*4cadd4f8SNickeau $callStackArray = $data[PluginUtility::ATTRIBUTES]; 224*4cadd4f8SNickeau $tagAttributes = TagAttributes::createFromCallStackArray($callStackArray); 225*4cadd4f8SNickeau $text = $tagAttributes->getValue(self::TEXT_ATTRIBUTE); 226*4cadd4f8SNickeau if ($text !== null) { 227*4cadd4f8SNickeau /** 228*4cadd4f8SNickeau * Old syntax where the tooltip was the wrapper 229*4cadd4f8SNickeau */ 230*4cadd4f8SNickeau $renderer->doc .= TagAttributes::createFromCallStackArray([Tooltip::TOOLTIP_ATTRIBUTE => $callStackArray]) 231*4cadd4f8SNickeau ->addClassName(self::TOOLTIP_CLASS_INLINE_BLOCK) 232*4cadd4f8SNickeau ->toHtmlEnterTag("span"); 23385e82846SNickeau } 23485e82846SNickeau break; 23585e82846SNickeau 236007225e5Sgerardnico case DOKU_LEXER_UNMATCHED: 23732b85071SNickeau $renderer->doc .= PluginUtility::renderUnmatched($data); 238007225e5Sgerardnico break; 239007225e5Sgerardnico 240007225e5Sgerardnico case DOKU_LEXER_EXIT: 241*4cadd4f8SNickeau $message = $data[PluginUtility::EXIT_MESSAGE]; 242*4cadd4f8SNickeau if ($message !== null) { 243*4cadd4f8SNickeau $renderer->doc .= LogUtility::wrapInRedForHtml($message); 24485e82846SNickeau return false; 24585e82846SNickeau } 24685e82846SNickeau 247*4cadd4f8SNickeau $callStackArray = $data[PluginUtility::ATTRIBUTES]; 248*4cadd4f8SNickeau $tagAttributes = TagAttributes::createFromCallStackArray($callStackArray); 249*4cadd4f8SNickeau $text = $tagAttributes->getValue(self::TEXT_ATTRIBUTE); 250*4cadd4f8SNickeau if ($text !== null) { 251*4cadd4f8SNickeau /** 252*4cadd4f8SNickeau * Old syntax where the tooltip was the wrapper 253*4cadd4f8SNickeau */ 2545f891b7eSNickeau $renderer->doc .= "</span>"; 255007225e5Sgerardnico } 2565f891b7eSNickeau 2575f891b7eSNickeau break; 2585f891b7eSNickeau 259007225e5Sgerardnico 260007225e5Sgerardnico } 261007225e5Sgerardnico return true; 262007225e5Sgerardnico } 263007225e5Sgerardnico 264007225e5Sgerardnico // unsupported $mode 265007225e5Sgerardnico return false; 266007225e5Sgerardnico } 267007225e5Sgerardnico 268007225e5Sgerardnico 269007225e5Sgerardnico} 270007225e5Sgerardnico 271