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