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