1007225e5Sgerardnico<?php 2007225e5Sgerardnico 3007225e5Sgerardnico 421913ab3SNickeauuse ComboStrap\Bootstrap; 585e82846SNickeauuse ComboStrap\CallStack; 685e82846SNickeauuse ComboStrap\LogUtility; 7007225e5Sgerardnicouse ComboStrap\PluginUtility; 885e82846SNickeauuse ComboStrap\TagAttributes; 94cadd4f8SNickeauuse ComboStrap\Tooltip; 1004fd306cSNickeauuse ComboStrap\XmlTagProcessing; 11007225e5Sgerardnico 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"; 424cadd4f8SNickeau public const TEXT_ATTRIBUTE = "text"; 4385e82846SNickeau 4485e82846SNickeau /** 454cadd4f8SNickeau * To see the tooltip immediately when hovering the class d-inline-block 464cadd4f8SNickeau * 474cadd4f8SNickeau * The inline block is to make the element (span) take the whole space 484cadd4f8SNickeau * of the image (ie dimension) otherwise it has no dimension and 494cadd4f8SNickeau * you can't click on it 504cadd4f8SNickeau * 514cadd4f8SNickeau * TODO: Add this to the {@link Tooltip} ??? 5285e82846SNickeau */ 534cadd4f8SNickeau 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 */ 624cadd4f8SNickeau 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 */ 804cadd4f8SNickeau 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 */ 944cadd4f8SNickeau function getAllowedTypes(): array 95007225e5Sgerardnico { 96007225e5Sgerardnico return array('baseonly', 'container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs'); 97007225e5Sgerardnico } 98007225e5Sgerardnico 9904fd306cSNickeau public function accepts($mode): bool 10004fd306cSNickeau { 10104fd306cSNickeau return syntax_plugin_combo_preformatted::disablePreformatted($mode); 10204fd306cSNickeau } 10304fd306cSNickeau 1044cadd4f8SNickeau function getSort(): int 105007225e5Sgerardnico { 106007225e5Sgerardnico return 201; 107007225e5Sgerardnico } 108007225e5Sgerardnico 109007225e5Sgerardnico 110007225e5Sgerardnico function connectTo($mode) 111007225e5Sgerardnico { 112007225e5Sgerardnico 11304fd306cSNickeau $pattern = XmlTagProcessing::getContainerTagPattern(self::TAG); 1149337a630SNickeau $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 115007225e5Sgerardnico 116007225e5Sgerardnico } 117007225e5Sgerardnico 118007225e5Sgerardnico function postConnect() 119007225e5Sgerardnico { 120007225e5Sgerardnico 1219337a630SNickeau $this->Lexer->addExitPattern('</' . self::TAG . '>', PluginUtility::getModeFromTag($this->getPluginComponent())); 122007225e5Sgerardnico 123007225e5Sgerardnico } 124007225e5Sgerardnico 125007225e5Sgerardnico /** 126007225e5Sgerardnico * 127007225e5Sgerardnico * The handle function goal is to parse the matched syntax through the pattern function 128007225e5Sgerardnico * and to return the result for use in the renderer 129007225e5Sgerardnico * This result is always cached until the page is modified. 130007225e5Sgerardnico * @param string $match 131007225e5Sgerardnico * @param int $state 132007225e5Sgerardnico * @param int $pos - byte position in the original source file 133007225e5Sgerardnico * @param Doku_Handler $handler 1344cadd4f8SNickeau * @return array 135007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::handle() 136007225e5Sgerardnico * 137007225e5Sgerardnico */ 1384cadd4f8SNickeau function handle($match, $state, $pos, Doku_Handler $handler): array 139007225e5Sgerardnico { 140007225e5Sgerardnico 141007225e5Sgerardnico switch ($state) { 142007225e5Sgerardnico 143007225e5Sgerardnico case DOKU_LEXER_ENTER : 14485e82846SNickeau $tagAttributes = TagAttributes::createFromTagMatch($match); 14585e82846SNickeau return array( 14685e82846SNickeau PluginUtility::STATE => $state, 14785e82846SNickeau PluginUtility::ATTRIBUTES => $tagAttributes->toCallStackArray() 14885e82846SNickeau ); 14985e82846SNickeau 150007225e5Sgerardnico 151007225e5Sgerardnico case DOKU_LEXER_UNMATCHED : 15232b85071SNickeau return PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); 153007225e5Sgerardnico 154007225e5Sgerardnico case DOKU_LEXER_EXIT : 155007225e5Sgerardnico 15685e82846SNickeau $callStack = CallStack::createFromHandler($handler); 15785e82846SNickeau $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall(); 1584cadd4f8SNickeau if ($openingTag->hasAttribute(self::TEXT_ATTRIBUTE)) { 1594cadd4f8SNickeau /** 1604cadd4f8SNickeau * Old syntax where the tooltip was the wrapper 1614cadd4f8SNickeau */ 162007225e5Sgerardnico return array( 163007225e5Sgerardnico PluginUtility::STATE => $state, 16485e82846SNickeau PluginUtility::ATTRIBUTES => $openingTag->getAttributes() 165007225e5Sgerardnico ); 1664cadd4f8SNickeau } 1674cadd4f8SNickeau $parent = $callStack->moveToParent(); 1684cadd4f8SNickeau if ($parent === false) { 1694cadd4f8SNickeau return array( 1704cadd4f8SNickeau PluginUtility::STATE => $state, 1714cadd4f8SNickeau PluginUtility::EXIT_MESSAGE => "A parent is mandatory for a tooltip", 1724cadd4f8SNickeau PluginUtility::EXIT_CODE => 1 1734cadd4f8SNickeau ); 1744cadd4f8SNickeau } 1754cadd4f8SNickeau 1764cadd4f8SNickeau /** 1774cadd4f8SNickeau * Capture the callstack 1784cadd4f8SNickeau */ 1794cadd4f8SNickeau $callStack->moveToCall($openingTag); 1804cadd4f8SNickeau $toolTipCallStack = null; 1814cadd4f8SNickeau while ($actualCall = $callStack->next()) { 1824cadd4f8SNickeau $toolTipCallStack[] = $actualCall->toCallArray(); 1834cadd4f8SNickeau } 1844cadd4f8SNickeau $callStack->deleteAllCallsAfter($openingTag); 1854cadd4f8SNickeau 1864cadd4f8SNickeau /** 1874cadd4f8SNickeau * Set on the parent the tooltip attributes 1884cadd4f8SNickeau * It will be processed by the {@link Tooltip} 1894cadd4f8SNickeau * class at the end of {@link TagAttributes::toHtmlEnterTag()} 1904cadd4f8SNickeau */ 1914cadd4f8SNickeau $attributes = $openingTag->getAttributes(); 1924cadd4f8SNickeau $attributes[Tooltip::CALLSTACK] = $toolTipCallStack; 1934cadd4f8SNickeau $parent->addAttribute(Tooltip::TOOLTIP_ATTRIBUTE, $attributes); 1944cadd4f8SNickeau 1954cadd4f8SNickeau return array( 1964cadd4f8SNickeau PluginUtility::STATE => $state 1974cadd4f8SNickeau ); 198007225e5Sgerardnico 199007225e5Sgerardnico 200007225e5Sgerardnico } 201007225e5Sgerardnico return array(); 202007225e5Sgerardnico 203007225e5Sgerardnico } 204007225e5Sgerardnico 205007225e5Sgerardnico /** 206007225e5Sgerardnico * Render the output 207007225e5Sgerardnico * @param string $format 208007225e5Sgerardnico * @param Doku_Renderer $renderer 209007225e5Sgerardnico * @param array $data - what the function handle() return'ed 210007225e5Sgerardnico * @return boolean - rendered correctly? (however, returned value is not used at the moment) 211007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::render() 212007225e5Sgerardnico * 213007225e5Sgerardnico * 214007225e5Sgerardnico */ 2154cadd4f8SNickeau function render($format, Doku_Renderer $renderer, $data): bool 216007225e5Sgerardnico { 217007225e5Sgerardnico if ($format == 'xhtml') { 218007225e5Sgerardnico 219007225e5Sgerardnico /** @var Doku_Renderer_xhtml $renderer */ 220007225e5Sgerardnico $state = $data[PluginUtility::STATE]; 221007225e5Sgerardnico switch ($state) { 222007225e5Sgerardnico 22385e82846SNickeau case DOKU_LEXER_ENTER : 2244cadd4f8SNickeau /** 2254cadd4f8SNickeau * Old syntax 2264cadd4f8SNickeau * where tooltip was enclosing the text with the tooltip 2274cadd4f8SNickeau */ 2284cadd4f8SNickeau $callStackArray = $data[PluginUtility::ATTRIBUTES]; 2294cadd4f8SNickeau $tagAttributes = TagAttributes::createFromCallStackArray($callStackArray); 2304cadd4f8SNickeau $text = $tagAttributes->getValue(self::TEXT_ATTRIBUTE); 2314cadd4f8SNickeau if ($text !== null) { 2324cadd4f8SNickeau /** 2334cadd4f8SNickeau * Old syntax where the tooltip was the wrapper 2344cadd4f8SNickeau */ 2354cadd4f8SNickeau $renderer->doc .= TagAttributes::createFromCallStackArray([Tooltip::TOOLTIP_ATTRIBUTE => $callStackArray]) 2364cadd4f8SNickeau ->addClassName(self::TOOLTIP_CLASS_INLINE_BLOCK) 2374cadd4f8SNickeau ->toHtmlEnterTag("span"); 23885e82846SNickeau } 23985e82846SNickeau break; 24085e82846SNickeau 241007225e5Sgerardnico case DOKU_LEXER_UNMATCHED: 24232b85071SNickeau $renderer->doc .= PluginUtility::renderUnmatched($data); 243007225e5Sgerardnico break; 244007225e5Sgerardnico 245007225e5Sgerardnico case DOKU_LEXER_EXIT: 246*70bbd7f1Sgerardnico $message = $data[PluginUtility::EXIT_MESSAGE] ?? null; 2474cadd4f8SNickeau if ($message !== null) { 2484cadd4f8SNickeau $renderer->doc .= LogUtility::wrapInRedForHtml($message); 24985e82846SNickeau return false; 25085e82846SNickeau } 25185e82846SNickeau 252*70bbd7f1Sgerardnico $callStackArray = $data[PluginUtility::ATTRIBUTES] ?? null; 2534cadd4f8SNickeau $tagAttributes = TagAttributes::createFromCallStackArray($callStackArray); 2544cadd4f8SNickeau $text = $tagAttributes->getValue(self::TEXT_ATTRIBUTE); 2554cadd4f8SNickeau if ($text !== null) { 2564cadd4f8SNickeau /** 2574cadd4f8SNickeau * Old syntax where the tooltip was the wrapper 2584cadd4f8SNickeau */ 2595f891b7eSNickeau $renderer->doc .= "</span>"; 260007225e5Sgerardnico } 2615f891b7eSNickeau 2625f891b7eSNickeau break; 2635f891b7eSNickeau 264007225e5Sgerardnico 265007225e5Sgerardnico } 266007225e5Sgerardnico return true; 267007225e5Sgerardnico } 268007225e5Sgerardnico 269007225e5Sgerardnico // unsupported $mode 270007225e5Sgerardnico return false; 271007225e5Sgerardnico } 272007225e5Sgerardnico 273007225e5Sgerardnico 274007225e5Sgerardnico} 275007225e5Sgerardnico 276