xref: /plugin/combo/ComboStrap/CardTag.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
1*04fd306cSNickeau<?php
2*04fd306cSNickeau
3*04fd306cSNickeaunamespace ComboStrap;
4*04fd306cSNickeau
5*04fd306cSNickeauuse ComboStrap\Tag\BoxTag;
6*04fd306cSNickeauuse Doku_Renderer_xhtml;
7*04fd306cSNickeauuse syntax_plugin_combo_header;
8*04fd306cSNickeau
9*04fd306cSNickeau/**
10*04fd306cSNickeau * * Horizontal Card
11*04fd306cSNickeau * https://getbootstrap.com/docs/4.3/components/card/#horizontal
12*04fd306cSNickeau *
13*04fd306cSNickeau * https://material.io/components/cards
14*04fd306cSNickeau * [[https://getbootstrap.com/docs/5.0/components/card/|Bootstrap card]]
15*04fd306cSNickeau */
16*04fd306cSNickeauclass CardTag
17*04fd306cSNickeau{
18*04fd306cSNickeau
19*04fd306cSNickeau    public const CANONICAL = CardTag::LOGICAL_TAG;
20*04fd306cSNickeau    public const CARD_TAG = 'card';
21*04fd306cSNickeau    public const CONF_ENABLE_SECTION_EDITING = "enableCardSectionEditing";
22*04fd306cSNickeau    const TEASER_TAG = 'teaser';
23*04fd306cSNickeau    const LOGICAL_TAG = self::CARD_TAG;
24*04fd306cSNickeau
25*04fd306cSNickeau
26*04fd306cSNickeau    public static function handleEnter(TagAttributes $tagAttributes, \Doku_Handler $handler): array
27*04fd306cSNickeau    {
28*04fd306cSNickeau
29*04fd306cSNickeau        /** A card without context */
30*04fd306cSNickeau        $tagAttributes->addClassName("card");
31*04fd306cSNickeau
32*04fd306cSNickeau        /**
33*04fd306cSNickeau         * Context
34*04fd306cSNickeau         */
35*04fd306cSNickeau        $callStack = CallStack::createFromHandler($handler);
36*04fd306cSNickeau        $parent = $callStack->moveToParent();
37*04fd306cSNickeau        $context = null;
38*04fd306cSNickeau        if ($parent !== false) {
39*04fd306cSNickeau            $context = $parent->getTagName();
40*04fd306cSNickeau            if ($context === FragmentTag::FRAGMENT_TAG) {
41*04fd306cSNickeau                $parent = $callStack->moveToParent();
42*04fd306cSNickeau                if ($parent !== false) {
43*04fd306cSNickeau                    $context = $parent->getTagName();
44*04fd306cSNickeau                }
45*04fd306cSNickeau            }
46*04fd306cSNickeau        }
47*04fd306cSNickeau
48*04fd306cSNickeau        $returnedArray = array(
49*04fd306cSNickeau            PluginUtility::CONTEXT => $context
50*04fd306cSNickeau        );
51*04fd306cSNickeau
52*04fd306cSNickeau        $id = ExecutionContext::getActualOrCreateFromEnv()
53*04fd306cSNickeau            ->getIdManager()
54*04fd306cSNickeau            ->generateNewHtmlIdForComponent(CardTag::CARD_TAG);
55*04fd306cSNickeau        $returnedArray[TagAttributes::ID_KEY] = $id;
56*04fd306cSNickeau
57*04fd306cSNickeau        return $returnedArray;
58*04fd306cSNickeau    }
59*04fd306cSNickeau
60*04fd306cSNickeau    public static function handleExit(\Doku_Handler $handler, $pos, $match): array
61*04fd306cSNickeau    {
62*04fd306cSNickeau        $callStack = CallStack::createFromHandler($handler);
63*04fd306cSNickeau
64*04fd306cSNickeau        /**
65*04fd306cSNickeau         * Check and add a scroll toggle if the
66*04fd306cSNickeau         * card is constrained by height
67*04fd306cSNickeau         */
68*04fd306cSNickeau        Dimension::addScrollToggleOnClickIfNoControl($callStack);
69*04fd306cSNickeau
70*04fd306cSNickeau        // Processing
71*04fd306cSNickeau        $callStack->moveToEnd();
72*04fd306cSNickeau        $previousOpening = $callStack->moveToPreviousCorrespondingOpeningCall();
73*04fd306cSNickeau        /**
74*04fd306cSNickeau         * Do we have an illustrative image ?
75*04fd306cSNickeau         *
76*04fd306cSNickeau         * Because the image is considered an inline component
77*04fd306cSNickeau         * We need to be careful to not wrap it into
78*04fd306cSNickeau         * a paragraph (when the {@link syntax_plugin_combo_para::fromEolToParagraphUntilEndOfStack() process is kicking)
79*04fd306cSNickeau         */
80*04fd306cSNickeau        while ($actualCall = $callStack->next()) {
81*04fd306cSNickeau
82*04fd306cSNickeau            if ($actualCall->isUnMatchedEmptyCall()) {
83*04fd306cSNickeau                continue;
84*04fd306cSNickeau            }
85*04fd306cSNickeau
86*04fd306cSNickeau            $tagName = $actualCall->getTagName();
87*04fd306cSNickeau            $imageTag = "image";
88*04fd306cSNickeau            $tagImage = null;
89*04fd306cSNickeau            if (in_array($tagName, Call::IMAGE_TAGS)) {
90*04fd306cSNickeau                $tagImage = $tagName;
91*04fd306cSNickeau                $tagName = $imageTag;
92*04fd306cSNickeau            }
93*04fd306cSNickeau            switch ($tagName) {
94*04fd306cSNickeau                case $imageTag:
95*04fd306cSNickeau                    $actualCall->addClassName("card-img-top");
96*04fd306cSNickeau                    if ($tagImage !== PageImageTag::MARKUP) {
97*04fd306cSNickeau                        $actualCall->setType(FetcherSvg::ILLUSTRATION_TYPE);
98*04fd306cSNickeau                    }
99*04fd306cSNickeau                    $actualCall->addAttribute(MediaMarkup::LINKING_KEY, MediaMarkup::LINKING_NOLINK_VALUE);
100*04fd306cSNickeau                    if (!$actualCall->hasAttribute(Dimension::RATIO_ATTRIBUTE)) {
101*04fd306cSNickeau                        $actualCall->addAttribute(Dimension::RATIO_ATTRIBUTE, "16:9");
102*04fd306cSNickeau                    }
103*04fd306cSNickeau                    $actualCall->setDisplay(Call::BlOCK_DISPLAY);
104*04fd306cSNickeau                    // an image should stretch into the card
105*04fd306cSNickeau                    $actualCall->addCssStyle("max-width", "100%");
106*04fd306cSNickeau                    break 2;
107*04fd306cSNickeau                case "eol":
108*04fd306cSNickeau                    break;
109*04fd306cSNickeau                default:
110*04fd306cSNickeau                    break 2;
111*04fd306cSNickeau
112*04fd306cSNickeau            }
113*04fd306cSNickeau
114*04fd306cSNickeau        }
115*04fd306cSNickeau        /**
116*04fd306cSNickeau         * If there is an Header
117*04fd306cSNickeau         * go to the end
118*04fd306cSNickeau         */
119*04fd306cSNickeau        if ($actualCall->getTagName() === syntax_plugin_combo_header::TAG && $actualCall->getState() === DOKU_LEXER_ENTER) {
120*04fd306cSNickeau            while ($actualCall = $callStack->next()) {
121*04fd306cSNickeau                if (
122*04fd306cSNickeau                    $actualCall->getTagName() === syntax_plugin_combo_header::TAG
123*04fd306cSNickeau                    && $actualCall->getState() === DOKU_LEXER_EXIT) {
124*04fd306cSNickeau                    break;
125*04fd306cSNickeau                }
126*04fd306cSNickeau            }
127*04fd306cSNickeau        }
128*04fd306cSNickeau        /**
129*04fd306cSNickeau         * Insert card-body
130*04fd306cSNickeau         */
131*04fd306cSNickeau        $bodyCall = self::createCardBodyEnterCall();
132*04fd306cSNickeau        $insertBodyAfterThisCalls = PluginUtility::mergeAttributes(Call::IMAGE_TAGS, [syntax_plugin_combo_header::TAG]);
133*04fd306cSNickeau        if (in_array($actualCall->getTagName(), $insertBodyAfterThisCalls)) {
134*04fd306cSNickeau
135*04fd306cSNickeau            $callStack->insertAfter($bodyCall);
136*04fd306cSNickeau
137*04fd306cSNickeau        } else {
138*04fd306cSNickeau            /**
139*04fd306cSNickeau             * Body was reached
140*04fd306cSNickeau             */
141*04fd306cSNickeau            $callStack->insertBefore($bodyCall);
142*04fd306cSNickeau            /**
143*04fd306cSNickeau             * Previous because the next function (EOL processing)
144*04fd306cSNickeau             * should start from previous
145*04fd306cSNickeau             */
146*04fd306cSNickeau            $callStack->previous();
147*04fd306cSNickeau        }
148*04fd306cSNickeau
149*04fd306cSNickeau        /**
150*04fd306cSNickeau         * Process the body
151*04fd306cSNickeau         */
152*04fd306cSNickeau        $callStack->insertEolIfNextCallIsNotEolOrBlock();
153*04fd306cSNickeau        $callStack->processEolToEndStack([TagAttributes::CLASS_KEY => "card-text"]);
154*04fd306cSNickeau
155*04fd306cSNickeau        /**
156*04fd306cSNickeau         * Insert the card body exit
157*04fd306cSNickeau         */
158*04fd306cSNickeau        $callStack->insertBefore(
159*04fd306cSNickeau            Call::createComboCall(
160*04fd306cSNickeau                BoxTag::TAG,
161*04fd306cSNickeau                DOKU_LEXER_EXIT,
162*04fd306cSNickeau                [BoxTag::HTML_TAG_ATTRIBUTE => "div"],
163*04fd306cSNickeau                null,
164*04fd306cSNickeau                null,
165*04fd306cSNickeau                null,
166*04fd306cSNickeau                null,
167*04fd306cSNickeau                \syntax_plugin_combo_xmlblocktag::TAG
168*04fd306cSNickeau            )
169*04fd306cSNickeau        );
170*04fd306cSNickeau
171*04fd306cSNickeau
172*04fd306cSNickeau        /**
173*04fd306cSNickeau         * File Section editing
174*04fd306cSNickeau         */
175*04fd306cSNickeau        if (SiteConfig::getConfValue(CardTag::CONF_ENABLE_SECTION_EDITING, 1)) {
176*04fd306cSNickeau            /**
177*04fd306cSNickeau             * +1 to go at the line ?
178*04fd306cSNickeau             */
179*04fd306cSNickeau            $endPosition = $pos + strlen($match) + 1;
180*04fd306cSNickeau            $position = $previousOpening->getFirstMatchedCharacterPosition();
181*04fd306cSNickeau            $id = $previousOpening->getIdOrDefault();
182*04fd306cSNickeau            $editButtonCall = EditButton::create("Edit Card $id")
183*04fd306cSNickeau                ->setStartPosition($position)
184*04fd306cSNickeau                ->setEndPosition($endPosition)
185*04fd306cSNickeau                ->toComboCallComboFormat();
186*04fd306cSNickeau            $callStack->moveToEnd();
187*04fd306cSNickeau            $callStack->insertBefore($editButtonCall);
188*04fd306cSNickeau
189*04fd306cSNickeau        }
190*04fd306cSNickeau
191*04fd306cSNickeau        return array(PluginUtility::CONTEXT => $previousOpening->getContext());
192*04fd306cSNickeau    }
193*04fd306cSNickeau
194*04fd306cSNickeau    public static function renderEnterXhtml(TagAttributes $attributes, Doku_Renderer_xhtml $renderer, array $data): string
195*04fd306cSNickeau    {
196*04fd306cSNickeau        /**
197*04fd306cSNickeau         * Add the CSS
198*04fd306cSNickeau         */
199*04fd306cSNickeau        $snippetManager = PluginUtility::getSnippetManager();
200*04fd306cSNickeau        $snippetManager->attachCssInternalStyleSheet(CardTag::CARD_TAG);
201*04fd306cSNickeau
202*04fd306cSNickeau
203*04fd306cSNickeau        $context = $data[PluginUtility::CONTEXT];
204*04fd306cSNickeau        if ($context === MasonryTag::MASONRY_TAG) {
205*04fd306cSNickeau            MasonryTag::addColIfBootstrap5AndCardColumns($renderer, $context);
206*04fd306cSNickeau        }
207*04fd306cSNickeau
208*04fd306cSNickeau        /**
209*04fd306cSNickeau         * Card
210*04fd306cSNickeau         */
211*04fd306cSNickeau        return $attributes->toHtmlEnterTag("div");
212*04fd306cSNickeau    }
213*04fd306cSNickeau
214*04fd306cSNickeau    public static function handleExitXhtml(array $data, Doku_Renderer_xhtml $renderer)
215*04fd306cSNickeau    {
216*04fd306cSNickeau        /**
217*04fd306cSNickeau         * End card
218*04fd306cSNickeau         */
219*04fd306cSNickeau        $renderer->doc .= "</div>" . DOKU_LF;
220*04fd306cSNickeau
221*04fd306cSNickeau        /**
222*04fd306cSNickeau         * End Masonry column if any
223*04fd306cSNickeau         * {@link MasonryTag::addColIfBootstrap5AndCardColumns()}
224*04fd306cSNickeau         */
225*04fd306cSNickeau        $context = $data[PluginUtility::CONTEXT];
226*04fd306cSNickeau        if ($context === MasonryTag::MASONRY_TAG) {
227*04fd306cSNickeau            MasonryTag::endColIfBootstrap5AnCardColumns($renderer, $context);
228*04fd306cSNickeau        }
229*04fd306cSNickeau    }
230*04fd306cSNickeau
231*04fd306cSNickeau    public static function createCardBodyExitCall(): Call
232*04fd306cSNickeau    {
233*04fd306cSNickeau        return Call::createComboCall(
234*04fd306cSNickeau            BoxTag::TAG,
235*04fd306cSNickeau            DOKU_LEXER_EXIT,
236*04fd306cSNickeau            [],
237*04fd306cSNickeau            null,
238*04fd306cSNickeau            null,
239*04fd306cSNickeau            null,
240*04fd306cSNickeau            null,
241*04fd306cSNickeau            \syntax_plugin_combo_xmlblocktag::TAG
242*04fd306cSNickeau        );
243*04fd306cSNickeau    }
244*04fd306cSNickeau
245*04fd306cSNickeau    public static function createCardBodyEnterCall($context = null): Call
246*04fd306cSNickeau    {
247*04fd306cSNickeau        return Call::createComboCall(
248*04fd306cSNickeau            BoxTag::TAG,
249*04fd306cSNickeau            DOKU_LEXER_ENTER,
250*04fd306cSNickeau            [
251*04fd306cSNickeau                BoxTag::HTML_TAG_ATTRIBUTE => "div",
252*04fd306cSNickeau                BoxTag::LOGICAL_TAG_ATTRIBUTE => 'card-body',
253*04fd306cSNickeau                TagAttributes::CLASS_KEY => 'card-body',
254*04fd306cSNickeau            ],
255*04fd306cSNickeau            $context,
256*04fd306cSNickeau            null,
257*04fd306cSNickeau            null,
258*04fd306cSNickeau            null,
259*04fd306cSNickeau            \syntax_plugin_combo_xmlblocktag::TAG
260*04fd306cSNickeau        );
261*04fd306cSNickeau    }
262*04fd306cSNickeau}
263