xref: /plugin/combo/syntax/tooltip.php (revision 70bbd7f1f72440223cc13f3495efdcb2b0a11514)
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