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