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