xref: /plugin/combo/syntax/para.php (revision 70bbd7f1f72440223cc13f3495efdcb2b0a11514)
121913ab3SNickeau<?php
221913ab3SNickeau
321913ab3SNickeau
437748cd8SNickeaurequire_once(__DIR__ . "/../ComboStrap/PluginUtility.php");
5c3437056SNickeau
621913ab3SNickeau
721913ab3SNickeauuse ComboStrap\Call;
89337a630SNickeauuse ComboStrap\CallStack;
921913ab3SNickeauuse ComboStrap\LogUtility;
1021913ab3SNickeauuse ComboStrap\PluginUtility;
1121913ab3SNickeauuse ComboStrap\TagAttributes;
1221913ab3SNickeau
1321913ab3SNickeau
1421913ab3SNickeau/**
1521913ab3SNickeau *
1621913ab3SNickeau * A paragraph syntax
1721913ab3SNickeau *
1821913ab3SNickeau * This syntax component is used dynamically while parsing (at the {@link DOKU_LEXER_END} of {@link \dokuwiki\Extension\SyntaxPlugin::handle()}
1921913ab3SNickeau * with the function {@link \syntax_plugin_combo_para::fromEolToParagraphUntilEndOfStack()}
2021913ab3SNickeau *
2121913ab3SNickeau *
2221913ab3SNickeau * !!!!!
2321913ab3SNickeau *
2421913ab3SNickeau * Info: The `eol` call are temporary created with {@link \dokuwiki\Parsing\ParserMode\Eol}
2521913ab3SNickeau * and transformed to `p_open` and `p_close` via {@link \dokuwiki\Parsing\Handler\Block::process()}
2621913ab3SNickeau *
2721913ab3SNickeau * Note: p_open call may appears when the {@link \ComboStrap\Syntax::getPType()} is set to `block` or `stack`
2821913ab3SNickeau * and the next call is not a block or a stack
2921913ab3SNickeau *
3021913ab3SNickeau * !!!!!
3121913ab3SNickeau *
3221913ab3SNickeau *
3321913ab3SNickeau * Note on Typography
3421913ab3SNickeau * TODO: https://github.com/typekit/webfontloader
3521913ab3SNickeau * https://www.dokuwiki.org/plugin:typography
3621913ab3SNickeau * https://stackoverflow.design/email/base/typography/
3721913ab3SNickeau * http://kyleamathews.github.io/typography.js/
3821913ab3SNickeau * https://docs.gitbook.com/features/advanced-branding (Roboto, Roboto Slab, Open Sans, Source Sans Pro, Lato, Ubuntu, Raleway, Merriweather)
3921913ab3SNickeau * http://themenectar.com/docs/salient/theme-options/typography/ (Doc)
4021913ab3SNickeau * https://www.modularscale.com/ - see the size of font
4121913ab3SNickeau *
4221913ab3SNickeau * See the fonts on your computer
4321913ab3SNickeau * https://wordmark.it/
4421913ab3SNickeau *
4521913ab3SNickeau * What's a type ? Type terminology
4621913ab3SNickeau * https://www.supremo.co.uk/typeterms/
4721913ab3SNickeau *
4821913ab3SNickeau * https://theprotoolbox.com/browse/font-tools/
4921913ab3SNickeau */
5021913ab3SNickeauclass syntax_plugin_combo_para extends DokuWiki_Syntax_Plugin
5121913ab3SNickeau{
5221913ab3SNickeau
5321913ab3SNickeau    const TAG = 'para';
5421913ab3SNickeau    const COMPONENT = "combo_para";
5521913ab3SNickeau
5621913ab3SNickeau
5721913ab3SNickeau    /**
58531e725cSNickeau     * The component that have a `normal` {@link \dokuwiki\Extension\SyntaxPlugin::getPType()}
59531e725cSNickeau     * are wrapped in a p element by {@link Block::process()} if they are not in a component with a `block` ptype
60531e725cSNickeau     *
61531e725cSNickeau     * This function makes it easy for the test
62531e725cSNickeau     * to do it and gives a little bit of documentation
63531e725cSNickeau     * on why there is a `p` in the test
64531e725cSNickeau     * @param $html
65531e725cSNickeau     * @return string the html wrapped in p
66531e725cSNickeau     */
67531e725cSNickeau    public static function wrapInP($html)
68531e725cSNickeau    {
69531e725cSNickeau        return "<p>" . $html . "</p>";
70531e725cSNickeau    }
71531e725cSNickeau
72531e725cSNickeau
73531e725cSNickeau    /**
7421913ab3SNickeau     * Syntax Type.
7521913ab3SNickeau     *
7621913ab3SNickeau     * Needs to return one of the mode types defined in $PARSER_MODES in parser.php
7721913ab3SNickeau     * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types
7821913ab3SNickeau     */
7904fd306cSNickeau    function getType(): string
8021913ab3SNickeau    {
8104fd306cSNickeau        /**
8204fd306cSNickeau         * Not `paragraphs' because we don't allow them in {@link syntax_plugin_combo_xmlblocktag}
8304fd306cSNickeau         */
8404fd306cSNickeau        return 'formatting';
8521913ab3SNickeau    }
8621913ab3SNickeau
8721913ab3SNickeau    /**
8821913ab3SNickeau     * How Dokuwiki will add P element
8921913ab3SNickeau     *
9021913ab3SNickeau     *  * 'normal' - The plugin can be used inside paragraphs
9121913ab3SNickeau     *  * 'block'  - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs
9221913ab3SNickeau     *  * 'stack'  - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs
9321913ab3SNickeau     *
9421913ab3SNickeau     * @see DokuWiki_Syntax_Plugin::getPType()
9521913ab3SNickeau     */
9621913ab3SNickeau    function getPType()
9721913ab3SNickeau    {
9821913ab3SNickeau        /**
9921913ab3SNickeau         * !important!
10021913ab3SNickeau         * The {@link \dokuwiki\Parsing\Handler\Block::process()}
10121913ab3SNickeau         * will then not create an extra paragraph after it encounters a block
10221913ab3SNickeau         */
10321913ab3SNickeau        return 'block';
10421913ab3SNickeau    }
10521913ab3SNickeau
10621913ab3SNickeau    /**
10721913ab3SNickeau     * @return array
10821913ab3SNickeau     * Allow which kind of plugin inside
10921913ab3SNickeau     *
11021913ab3SNickeau     * No one of array('container', 'baseonly', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs')
11121913ab3SNickeau     * because we manage self the content and we call self the parser
11221913ab3SNickeau     */
11321913ab3SNickeau    function getAllowedTypes()
11421913ab3SNickeau    {
11521913ab3SNickeau        /**
11621913ab3SNickeau         * Not needed as we don't have any {@link syntax_plugin_combo_para::connectTo()}
11721913ab3SNickeau         */
11821913ab3SNickeau        return array();
11921913ab3SNickeau    }
12021913ab3SNickeau
12121913ab3SNickeau
12221913ab3SNickeau    /**
12321913ab3SNickeau     * @see Doku_Parser_Mode::getSort()
12421913ab3SNickeau     * The mode with the lowest sort number will win out
12521913ab3SNickeau     *
12621913ab3SNickeau     */
12721913ab3SNickeau    function getSort()
12821913ab3SNickeau    {
12921913ab3SNickeau        /**
13021913ab3SNickeau         * Not really needed as we don't have any {@link syntax_plugin_combo_para::connectTo()}
13121913ab3SNickeau         *
13221913ab3SNickeau         * Note: if we start to use it should be less than 370
13321913ab3SNickeau         * Ie Less than {@link \dokuwiki\Parsing\ParserMode\Eol::getSort()}
13421913ab3SNickeau         */
13521913ab3SNickeau        return 369;
13621913ab3SNickeau    }
13721913ab3SNickeau
13821913ab3SNickeau
13921913ab3SNickeau    function connectTo($mode)
14021913ab3SNickeau    {
14121913ab3SNickeau
14221913ab3SNickeau        /**
14321913ab3SNickeau         * No need to connect
14404fd306cSNickeau         * This syntax plugin is added dynamically with the {@link CallStack::processEolToEndStack()}
14521913ab3SNickeau         * function
14621913ab3SNickeau         */
14721913ab3SNickeau
14821913ab3SNickeau    }
14921913ab3SNickeau
15021913ab3SNickeau
15121913ab3SNickeau    /**
15221913ab3SNickeau     * The handler for an internal link
15321913ab3SNickeau     * based on `internallink` in {@link Doku_Handler}
15421913ab3SNickeau     * The handler call the good renderer in {@link Doku_Renderer_xhtml} with
15521913ab3SNickeau     * the parameters (ie for instance internallink)
15621913ab3SNickeau     * @param string $match
15721913ab3SNickeau     * @param int $state
15821913ab3SNickeau     * @param int $pos
15921913ab3SNickeau     * @param Doku_Handler $handler
16021913ab3SNickeau     * @return array|bool
16121913ab3SNickeau     */
16221913ab3SNickeau    function handle($match, $state, $pos, Doku_Handler $handler)
16321913ab3SNickeau    {
16421913ab3SNickeau
16521913ab3SNickeau        /**
16621913ab3SNickeau         * No need to handle,
16721913ab3SNickeau         * there is no {@link syntax_plugin_combo_para::connectTo() connection}
16821913ab3SNickeau         */
16921913ab3SNickeau        return true;
17021913ab3SNickeau
17121913ab3SNickeau
17221913ab3SNickeau    }
17321913ab3SNickeau
17421913ab3SNickeau    /**
17521913ab3SNickeau     * Render the output
17621913ab3SNickeau     * @param string $format
17721913ab3SNickeau     * @param Doku_Renderer $renderer
17821913ab3SNickeau     * @param array $data - what the function handle() return'ed
17921913ab3SNickeau     * @return boolean - rendered correctly? (however, returned value is not used at the moment)
18021913ab3SNickeau     * @see DokuWiki_Syntax_Plugin::render()
18121913ab3SNickeau     *
18221913ab3SNickeau     *
18321913ab3SNickeau     */
1844cadd4f8SNickeau    function render($format, Doku_Renderer $renderer, $data): bool
18521913ab3SNickeau    {
18621913ab3SNickeau        // The data
18721913ab3SNickeau        switch ($format) {
18821913ab3SNickeau            case 'xhtml':
18921913ab3SNickeau
19021913ab3SNickeau                /** @var Doku_Renderer_xhtml $renderer */
19121913ab3SNickeau                $state = $data[PluginUtility::STATE];
192531e725cSNickeau
19321913ab3SNickeau                switch ($state) {
19421913ab3SNickeau                    case DOKU_LEXER_ENTER:
195531e725cSNickeau                        $attributes = $data[PluginUtility::ATTRIBUTES];
196531e725cSNickeau                        $tagAttributes = TagAttributes::createFromCallStackArray($attributes);
197531e725cSNickeau                        if ($tagAttributes->hasComponentAttribute(TagAttributes::TYPE_KEY)) {
198531e725cSNickeau                            $class = $tagAttributes->getType();
199531e725cSNickeau                            $tagAttributes->addClassName($class);
200531e725cSNickeau                        }
20121913ab3SNickeau                        $renderer->doc .= $tagAttributes->toHtmlEnterTag("p");
20221913ab3SNickeau                        break;
20321913ab3SNickeau                    case DOKU_LEXER_SPECIAL:
204531e725cSNickeau                        $attributes = $data[PluginUtility::ATTRIBUTES];
205531e725cSNickeau                        $tagAttributes = TagAttributes::createFromCallStackArray($attributes);
20621913ab3SNickeau                        $renderer->doc .= $tagAttributes->toHtmlEnterTag("p");
20721913ab3SNickeau                        $renderer->doc .= "</p>";
20821913ab3SNickeau                        break;
20921913ab3SNickeau                    case DOKU_LEXER_EXIT:
21021913ab3SNickeau                        $renderer->doc .= "</p>";
21121913ab3SNickeau                        break;
21221913ab3SNickeau                }
21321913ab3SNickeau                return true;
21421913ab3SNickeau
21521913ab3SNickeau            case 'metadata':
21621913ab3SNickeau
21721913ab3SNickeau                /** @var Doku_Renderer_metadata $renderer */
21821913ab3SNickeau
21921913ab3SNickeau
22021913ab3SNickeau                return true;
22121913ab3SNickeau
22221913ab3SNickeau
223531e725cSNickeau            case renderer_plugin_combo_analytics::RENDERER_FORMAT:
22421913ab3SNickeau
22521913ab3SNickeau                /**
22621913ab3SNickeau                 * @var renderer_plugin_combo_analytics $renderer
22721913ab3SNickeau                 */
22821913ab3SNickeau                return true;
22921913ab3SNickeau
23021913ab3SNickeau        }
23121913ab3SNickeau        // unsupported $mode
23221913ab3SNickeau        return false;
23321913ab3SNickeau    }
23421913ab3SNickeau
23521913ab3SNickeau    /**
23621913ab3SNickeau     *
23721913ab3SNickeau     * Transform EOL into paragraph
23821913ab3SNickeau     * between the {@link CallStack::getActualCall()} and the {@link CallStack::isPointerAtEnd()}
23921913ab3SNickeau     * of the stack
24021913ab3SNickeau     *
24121913ab3SNickeau     * Info: Basically, you get an new paragraph with a blank line or `\\` : https://www.dokuwiki.org/faq:newlines
24221913ab3SNickeau     *
24321913ab3SNickeau     * It replaces the  {@link \dokuwiki\Parsing\Handler\Block::process() eol to paragraph Dokuwiki process}
24421913ab3SNickeau     * that takes place at the end of parsing process on the whole stack
24521913ab3SNickeau     *
24621913ab3SNickeau     * Info: The `eol` call are temporary created with {@link \dokuwiki\Parsing\ParserMode\Eol}
24721913ab3SNickeau     * and transformed to `p_open` and `p_close` via {@link \dokuwiki\Parsing\Handler\Block::process()}
24821913ab3SNickeau     *
2499337a630SNickeau     * @param CallStack $callstack
250531e725cSNickeau     * @param array $attributes - the attributes passed to the paragraph
25121913ab3SNickeau     */
2521fa8c418SNickeau    public static function fromEolToParagraphUntilEndOfStack(CallStack &$callstack, array $attributes)
25321913ab3SNickeau    {
254531e725cSNickeau
255531e725cSNickeau        if (!is_array($attributes)) {
256531e725cSNickeau            LogUtility::msg("The passed attributes array ($attributes) for the creation of the paragraph is not an array", LogUtility::LVL_MSG_ERROR);
257531e725cSNickeau            $attributes = [];
258531e725cSNickeau        }
25921913ab3SNickeau
26021913ab3SNickeau        /**
26121913ab3SNickeau         * The syntax plugin that implements the paragraph
26221913ab3SNickeau         * ie {@link \syntax_plugin_combo_para}
26321913ab3SNickeau         * We will transform the eol with a call to this syntax plugin
26421913ab3SNickeau         * to create the paragraph
26521913ab3SNickeau         */
26621913ab3SNickeau        $paragraphComponent = \syntax_plugin_combo_para::COMPONENT;
267531e725cSNickeau        $paragraphTag = \syntax_plugin_combo_para::TAG;
26821913ab3SNickeau
26921913ab3SNickeau        /**
27021913ab3SNickeau         * The running variables
27121913ab3SNickeau         */
27221913ab3SNickeau        $paragraphIsOpen = false; // A pointer to see if the paragraph is open
273531e725cSNickeau        while ($actualCall = $callstack->next()) {
27421913ab3SNickeau
275531e725cSNickeau            /**
276531e725cSNickeau             * end of line is not always present
277531e725cSNickeau             * because the pattern is eating it
278531e725cSNickeau             * Example (list_open)
279531e725cSNickeau             */
280531e725cSNickeau            if ($paragraphIsOpen && $actualCall->getTagName() !== "eol") {
281531e725cSNickeau                if ($actualCall->getDisplay() == Call::BlOCK_DISPLAY) {
282531e725cSNickeau                    $paragraphIsOpen = false;
283531e725cSNickeau                    $callstack->insertBefore(
284531e725cSNickeau                        Call::createComboCall(
285531e725cSNickeau                            $paragraphTag,
286531e725cSNickeau                            DOKU_LEXER_EXIT,
287531e725cSNickeau                            $attributes
288531e725cSNickeau                        )
289531e725cSNickeau                    );
290531e725cSNickeau                }
291531e725cSNickeau            }
292531e725cSNickeau
29321913ab3SNickeau            if ($actualCall->getTagName() === "eol") {
29421913ab3SNickeau
29521913ab3SNickeau                /**
296531e725cSNickeau                 * Next Call that is not the empty string
297531e725cSNickeau                 * Because Empty string would create an empty paragraph
298531e725cSNickeau                 *
299531e725cSNickeau                 * Start at 1 because we may not do
300531e725cSNickeau                 * a loop if we are at the end, the next call
301531e725cSNickeau                 * will return false
30221913ab3SNickeau                 */
303531e725cSNickeau                $i = 1;
304531e725cSNickeau                while ($nextCall = $callstack->next()) {
305*70bbd7f1Sgerardnico                    $capturedContent = $nextCall->getCapturedContent();
306*70bbd7f1Sgerardnico                    $captureContentisEmpty = $capturedContent===null || trim($capturedContent) == "";
307531e725cSNickeau                    if (!(
308*70bbd7f1Sgerardnico                        $captureContentisEmpty &&
309531e725cSNickeau                        $nextCall->isTextCall()
310531e725cSNickeau                    )) {
311531e725cSNickeau                        break;
312531e725cSNickeau                    }
313531e725cSNickeau                    $i++;
314531e725cSNickeau                }
315531e725cSNickeau                while ($i > 0) { // go back
316531e725cSNickeau                    $i--;
317531e725cSNickeau                    $callstack->previous();
318531e725cSNickeau                }
31921913ab3SNickeau                if ($nextCall === false) {
32021913ab3SNickeau                    $nextDisplay = "last";
32121913ab3SNickeau                    $nextCall = null;
32221913ab3SNickeau                } else {
32321913ab3SNickeau                    $nextDisplay = $nextCall->getDisplay();
32421913ab3SNickeau                }
32521913ab3SNickeau
32621913ab3SNickeau
32721913ab3SNickeau                /**
32821913ab3SNickeau                 * Processing
32921913ab3SNickeau                 */
33021913ab3SNickeau                if (!$paragraphIsOpen) {
33121913ab3SNickeau
33221913ab3SNickeau                    switch ($nextDisplay) {
33321913ab3SNickeau                        case Call::BlOCK_DISPLAY:
33421913ab3SNickeau                        case "last":
33521913ab3SNickeau                            $callstack->deleteActualCallAndPrevious();
33621913ab3SNickeau                            break;
33721913ab3SNickeau                        case Call::INLINE_DISPLAY:
33821913ab3SNickeau                            $paragraphIsOpen = true;
33921913ab3SNickeau                            $actualCall->updateToPluginComponent(
34021913ab3SNickeau                                $paragraphComponent,
34121913ab3SNickeau                                DOKU_LEXER_ENTER,
34221913ab3SNickeau                                $attributes
34321913ab3SNickeau                            );
34421913ab3SNickeau                            break;
34521913ab3SNickeau                        case "eol":
34621913ab3SNickeau                            /**
34721913ab3SNickeau                             * Empty line
34821913ab3SNickeau                             */
34921913ab3SNickeau                            $actualCall->updateToPluginComponent(
35021913ab3SNickeau                                $paragraphComponent,
35121913ab3SNickeau                                DOKU_LEXER_ENTER,
35221913ab3SNickeau                                $attributes
35321913ab3SNickeau                            );
35421913ab3SNickeau                            $nextCall->updateToPluginComponent(
35521913ab3SNickeau                                $paragraphComponent,
35621913ab3SNickeau                                DOKU_LEXER_EXIT
35721913ab3SNickeau                            );
35821913ab3SNickeau                            $callstack->next();
35921913ab3SNickeau                            break;
36021913ab3SNickeau                        default:
36121913ab3SNickeau                            LogUtility::msg("The eol action for the combination enter / (" . $nextDisplay . ") of the call ( $nextCall ) was not implemented", LogUtility::LVL_MSG_ERROR);
36221913ab3SNickeau                            break;
36321913ab3SNickeau                    }
36421913ab3SNickeau                } else {
36521913ab3SNickeau                    /**
36621913ab3SNickeau                     * Paragraph is open
36721913ab3SNickeau                     */
36821913ab3SNickeau                    switch ($nextDisplay) {
36921913ab3SNickeau                        case "eol":
37021913ab3SNickeau                            /**
37121913ab3SNickeau                             * Empty line
37221913ab3SNickeau                             */
37321913ab3SNickeau                            $actualCall->updateToPluginComponent(
37421913ab3SNickeau                                $paragraphComponent,
37521913ab3SNickeau                                DOKU_LEXER_EXIT
37621913ab3SNickeau                            );
37721913ab3SNickeau                            $nextCall->updateToPluginComponent(
37821913ab3SNickeau                                $paragraphComponent,
37921913ab3SNickeau                                DOKU_LEXER_ENTER,
38021913ab3SNickeau                                $attributes
38121913ab3SNickeau                            );
38221913ab3SNickeau                            $callstack->next();
38321913ab3SNickeau                            break;
38421913ab3SNickeau                        case Call::INLINE_DISPLAY:
38521913ab3SNickeau                            // A space
38621913ab3SNickeau                            $actualCall->updateEolToSpace();
38721913ab3SNickeau                            break;
38821913ab3SNickeau                        case Call::BlOCK_DISPLAY:
38921913ab3SNickeau                        case "last";
39021913ab3SNickeau                            $actualCall->updateToPluginComponent(
39121913ab3SNickeau                                $paragraphComponent,
39221913ab3SNickeau                                DOKU_LEXER_EXIT
39321913ab3SNickeau                            );
39421913ab3SNickeau                            $paragraphIsOpen = false;
39521913ab3SNickeau                            break;
39621913ab3SNickeau                        default:
39721913ab3SNickeau                            LogUtility::msg("The display for a open paragraph (" . $nextDisplay . ") is not implemented", LogUtility::LVL_MSG_ERROR);
39821913ab3SNickeau                            break;
39921913ab3SNickeau                    }
40021913ab3SNickeau                }
40121913ab3SNickeau
40221913ab3SNickeau            }
40321913ab3SNickeau        }
40421913ab3SNickeau
40521913ab3SNickeau        // if the paragraph is open close it
40621913ab3SNickeau        if ($paragraphIsOpen) {
40721913ab3SNickeau            $callstack->insertBefore(
40821913ab3SNickeau                Call::createComboCall(
40921913ab3SNickeau                    \syntax_plugin_combo_para::TAG,
41021913ab3SNickeau                    DOKU_LEXER_EXIT
41121913ab3SNickeau                )
41221913ab3SNickeau            );
41321913ab3SNickeau        }
41421913ab3SNickeau    }
41521913ab3SNickeau
41621913ab3SNickeau}
41721913ab3SNickeau
418