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