19337a630SNickeau<?php 29337a630SNickeau 39337a630SNickeau 49337a630SNickeauuse ComboStrap\Call; 59337a630SNickeauuse ComboStrap\CallStack; 6*04fd306cSNickeauuse ComboStrap\DataType; 7*04fd306cSNickeauuse ComboStrap\Dimension; 8*04fd306cSNickeauuse ComboStrap\ExceptionBadArgument; 9*04fd306cSNickeauuse ComboStrap\LogUtility; 109337a630SNickeauuse ComboStrap\PluginUtility; 11*04fd306cSNickeauuse ComboStrap\Tag\BoxTag; 12*04fd306cSNickeauuse ComboStrap\TagAttribute\Align; 139337a630SNickeauuse ComboStrap\TagAttributes; 14*04fd306cSNickeauuse ComboStrap\XmlTagProcessing; 159337a630SNickeau 169337a630SNickeau 179337a630SNickeau/** 189337a630SNickeau * Class syntax_plugin_combo_list 199337a630SNickeau * Implementation of a list 209337a630SNickeau * 219337a630SNickeau * Content list is a list implementation that permits to 229337a630SNickeau * create simple and complex list such as media list 239337a630SNickeau * 249337a630SNickeau * https://getbootstrap.com/docs/4.0/layout/media-object/#media-list - Bootstrap media list 259337a630SNickeau * https://getbootstrap.com/docs/5.0/utilities/flex/#media-object 269337a630SNickeau * https://github.com/material-components/material-components-web/tree/master/packages/mdc-list - mdc list 279337a630SNickeau * 289337a630SNickeau * It's implemented on the basis of: 299337a630SNickeau * * bootstrap list-group 309337a630SNickeau * * flex utility on the list-group-item 319337a630SNickeau * * with the row/cell (grid) adjusted in order to add automatically a space between col (cell) 329337a630SNickeau * 339337a630SNickeau * Note: 349337a630SNickeau * * The cell inside a row are centered vertically automatically 359337a630SNickeau * * The illustrative image does not get any [[ui:image#link|link]] 369337a630SNickeau * 379337a630SNickeau * Documentation: 389337a630SNickeau * https://getbootstrap.com/docs/4.1/components/list-group/ 399337a630SNickeau * https://getbootstrap.com/docs/5.0/components/list-group/ 409337a630SNickeau * 419337a630SNickeau * https://getbootstrap.com/docs/5.0/utilities/flex/ 429337a630SNickeau * https://getbootstrap.com/docs/5.0/utilities/flex/#media-object 439337a630SNickeau * 449337a630SNickeau */ 459337a630SNickeauclass syntax_plugin_combo_contentlist extends DokuWiki_Syntax_Plugin 469337a630SNickeau{ 479337a630SNickeau 489337a630SNickeau const DOKU_TAG = "contentlist"; 499337a630SNickeau 509337a630SNickeau /** 519337a630SNickeau * To allow a minus 529337a630SNickeau */ 539337a630SNickeau const MARKI_TAG = "content-list"; 549337a630SNickeau const COMBO_TAG_OLD = "list"; 559337a630SNickeau const COMBO_TAGS = [self::MARKI_TAG, self::COMBO_TAG_OLD]; 569337a630SNickeau 579337a630SNickeau 58*04fd306cSNickeau const FLUSH_TYPE = "flush"; 59*04fd306cSNickeau const NUMBERED = "numbered"; 60*04fd306cSNickeau const NUMBERED_DEFAULT = false; 61*04fd306cSNickeau const CANONICAL = self::MARKI_TAG; 62*04fd306cSNickeau 63*04fd306cSNickeau /** 64*04fd306cSNickeau * @throws ExceptionBadArgument 65*04fd306cSNickeau */ 66*04fd306cSNickeau private static function insertNumberedWrapperCloseTag(CallStack $callStack) 67*04fd306cSNickeau { 68*04fd306cSNickeau 69*04fd306cSNickeau $callStack->insertBefore(Call::createComboCall( 70*04fd306cSNickeau BoxTag::TAG, 71*04fd306cSNickeau DOKU_LEXER_EXIT, 72*04fd306cSNickeau [BoxTag::HTML_TAG_ATTRIBUTE => "li"], 73*04fd306cSNickeau null, 74*04fd306cSNickeau null, 75*04fd306cSNickeau null, 76*04fd306cSNickeau null, 77*04fd306cSNickeau \syntax_plugin_combo_xmlblocktag::TAG 78*04fd306cSNickeau )); 79*04fd306cSNickeau 80*04fd306cSNickeau } 81*04fd306cSNickeau 82*04fd306cSNickeau 83*04fd306cSNickeau /** 84*04fd306cSNickeau * 85*04fd306cSNickeau * @param CallStack $callStack 86*04fd306cSNickeau * @return void 87*04fd306cSNickeau * @throws ExceptionBadArgument 88*04fd306cSNickeau */ 89*04fd306cSNickeau private static function insertNumberedWrapperOpenTag(CallStack $callStack) 90*04fd306cSNickeau { 91*04fd306cSNickeau $attributesNumberedWrapper = [ 92*04fd306cSNickeau Align::ALIGN_ATTRIBUTE => Align::Y_TOP_CHILDREN, // To have the number at the top and not centered as for a combostrap flex 93*04fd306cSNickeau TagAttributes::CLASS_KEY => syntax_plugin_combo_contentlistitem::LIST_GROUP_ITEM_CLASS, 94*04fd306cSNickeau BoxTag::HTML_TAG_ATTRIBUTE => "li" 95*04fd306cSNickeau ]; 96*04fd306cSNickeau $callStack->insertBefore(Call::createComboCall( 97*04fd306cSNickeau BoxTag::TAG, 98*04fd306cSNickeau DOKU_LEXER_ENTER, 99*04fd306cSNickeau $attributesNumberedWrapper, 100*04fd306cSNickeau null, 101*04fd306cSNickeau null, 102*04fd306cSNickeau null, 103*04fd306cSNickeau null, 104*04fd306cSNickeau \syntax_plugin_combo_xmlblocktag::TAG 105*04fd306cSNickeau )); 106*04fd306cSNickeau } 107*04fd306cSNickeau 108*04fd306cSNickeau 1099337a630SNickeau /** 1109337a630SNickeau * Syntax Type. 1119337a630SNickeau * 1129337a630SNickeau * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 1139337a630SNickeau * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 1149337a630SNickeau * @see DokuWiki_Syntax_Plugin::getType() 1159337a630SNickeau */ 116*04fd306cSNickeau function getType(): string 1179337a630SNickeau { 1189337a630SNickeau return 'container'; 1199337a630SNickeau } 1209337a630SNickeau 1219337a630SNickeau /** 1229337a630SNickeau * How Dokuwiki will add P element 1239337a630SNickeau * 124*04fd306cSNickeau * * 'normal' - Inline 125*04fd306cSNickeau * * 'block' - Block (p are not created inside) 126*04fd306cSNickeau * * 'stack' - Block (p can be created inside) 1279337a630SNickeau * 1289337a630SNickeau * @see DokuWiki_Syntax_Plugin::getPType() 1299337a630SNickeau * @see https://www.dokuwiki.org/devel:syntax_plugins#ptype 1309337a630SNickeau */ 131*04fd306cSNickeau function getPType(): string 1329337a630SNickeau { 133*04fd306cSNickeau return 'stack'; 1349337a630SNickeau } 1359337a630SNickeau 1369337a630SNickeau /** 1379337a630SNickeau * @return array 1389337a630SNickeau * Allow which kind of plugin inside 1399337a630SNickeau * 1409337a630SNickeau * No one of array('baseonly','container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs') 1419337a630SNickeau * because we manage self the content and we call self the parser 1429337a630SNickeau * 1439337a630SNickeau * Return an array of one or more of the mode types {@link $PARSER_MODES} in Parser.php 1449337a630SNickeau */ 145*04fd306cSNickeau function getAllowedTypes(): array 1469337a630SNickeau { 1479337a630SNickeau return array('container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs'); 1489337a630SNickeau } 1499337a630SNickeau 150*04fd306cSNickeau public function accepts($mode): bool 1519337a630SNickeau { 1529337a630SNickeau 1539337a630SNickeau return syntax_plugin_combo_preformatted::disablePreformatted($mode); 1549337a630SNickeau 1559337a630SNickeau } 1569337a630SNickeau 1579337a630SNickeau 158*04fd306cSNickeau function getSort(): int 1599337a630SNickeau { 1609337a630SNickeau return 15; 1619337a630SNickeau } 1629337a630SNickeau 1639337a630SNickeau 1649337a630SNickeau function connectTo($mode) 1659337a630SNickeau { 1669337a630SNickeau 1679337a630SNickeau foreach (self::COMBO_TAGS as $tag) { 168*04fd306cSNickeau $pattern = XmlTagProcessing::getContainerTagPattern($tag); 1699337a630SNickeau $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 1709337a630SNickeau } 1719337a630SNickeau 1729337a630SNickeau } 1739337a630SNickeau 1749337a630SNickeau public function postConnect() 1759337a630SNickeau { 1769337a630SNickeau foreach (self::COMBO_TAGS as $tag) { 1779337a630SNickeau $this->Lexer->addExitPattern('</' . $tag . '>', PluginUtility::getModeFromTag($this->getPluginComponent())); 1789337a630SNickeau } 1799337a630SNickeau 1809337a630SNickeau } 1819337a630SNickeau 1829337a630SNickeau 1839337a630SNickeau /** 1849337a630SNickeau * 1859337a630SNickeau * The handle function goal is to parse the matched syntax through the pattern function 1869337a630SNickeau * and to return the result for use in the renderer 1879337a630SNickeau * This result is always cached until the page is modified. 1889337a630SNickeau * @param string $match 1899337a630SNickeau * @param int $state 1909337a630SNickeau * @param int $pos - byte position in the original source file 1919337a630SNickeau * @param Doku_Handler $handler 192*04fd306cSNickeau * @return array 1939337a630SNickeau * @see DokuWiki_Syntax_Plugin::handle() 1949337a630SNickeau * 1959337a630SNickeau */ 196*04fd306cSNickeau function handle($match, $state, $pos, Doku_Handler $handler): array 1979337a630SNickeau { 1989337a630SNickeau 1999337a630SNickeau switch ($state) { 2009337a630SNickeau 2019337a630SNickeau case DOKU_LEXER_ENTER : 2029337a630SNickeau 203*04fd306cSNickeau $knownType = [self::FLUSH_TYPE]; 204*04fd306cSNickeau $default = [ 205*04fd306cSNickeau Dimension::WIDTH_KEY => "fit", 206*04fd306cSNickeau self::NUMBERED => self::NUMBERED_DEFAULT 207*04fd306cSNickeau ]; 208*04fd306cSNickeau $attributes = TagAttributes::createFromTagMatch($match, $default, $knownType); 2099337a630SNickeau 2109337a630SNickeau if ($attributes->hasComponentAttribute(TagAttributes::TYPE_KEY)) { 2119337a630SNickeau $type = trim(strtolower($attributes->getType())); 212*04fd306cSNickeau if ($type === self::FLUSH_TYPE) { 2139337a630SNickeau // https://getbootstrap.com/docs/5.0/components/list-group/#flush 2149337a630SNickeau // https://getbootstrap.com/docs/4.1/components/list-group/#flush 2159337a630SNickeau $attributes->addClassName("list-group-flush"); 2169337a630SNickeau } 2179337a630SNickeau } 218*04fd306cSNickeau 2199337a630SNickeau return array( 2209337a630SNickeau PluginUtility::STATE => $state, 2219337a630SNickeau PluginUtility::ATTRIBUTES => $attributes->toCallStackArray() 2229337a630SNickeau ); 2239337a630SNickeau 2249337a630SNickeau case DOKU_LEXER_UNMATCHED : 2259337a630SNickeau 2269337a630SNickeau return PluginUtility::handleAndReturnUnmatchedData(self::MARKI_TAG, $match, $handler); 2279337a630SNickeau 2289337a630SNickeau case DOKU_LEXER_EXIT : 2299337a630SNickeau 2309337a630SNickeau /** 231*04fd306cSNickeau * Add to all children the list-group-item 2329337a630SNickeau */ 2339337a630SNickeau $callStack = CallStack::createFromHandler($handler); 234*04fd306cSNickeau $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall(); 2359337a630SNickeau 236*04fd306cSNickeau /** 237*04fd306cSNickeau * The number are inside (this is a **content** list) 238*04fd306cSNickeau * and not as with a marker box, outside. 239*04fd306cSNickeau * 240*04fd306cSNickeau * It's in the `before` box and is therefore a invisible box 241*04fd306cSNickeau * To make it easy for the user (it does need to known that), 242*04fd306cSNickeau * we wrap the user markup in a flex with a top placement 243*04fd306cSNickeau */ 244*04fd306cSNickeau $numbered = DataType::toBoolean($openingTag->getAttribute(self::NUMBERED, self::NUMBERED_DEFAULT)); 245*04fd306cSNickeau if ($numbered === true) { 246*04fd306cSNickeau $firstChild = $callStack->moveToFirstChildTag(); 247*04fd306cSNickeau if ($firstChild !== false) { 248*04fd306cSNickeau try { 249*04fd306cSNickeau self::insertNumberedWrapperOpenTag($callStack); 250*04fd306cSNickeau while ($callStack->moveToNextSiblingTag()) { 251*04fd306cSNickeau self::insertNumberedWrapperCloseTag($callStack); 252*04fd306cSNickeau self::insertNumberedWrapperOpenTag($callStack); 253*04fd306cSNickeau } 254*04fd306cSNickeau self::insertNumberedWrapperCloseTag($callStack); 255*04fd306cSNickeau } catch (ExceptionBadArgument $e) { 256*04fd306cSNickeau LogUtility::error("We were unable to wrap the content list to enable numbering placement. Error: {$e->getMessage()}", self::CANONICAL); 257*04fd306cSNickeau } 258*04fd306cSNickeau } 259*04fd306cSNickeau } else { 260*04fd306cSNickeau foreach ($callStack->getChildren() as $child) { 261*04fd306cSNickeau $child->addClassName(syntax_plugin_combo_contentlistitem::LIST_GROUP_ITEM_CLASS); 262*04fd306cSNickeau if ($child->getTagName() === BoxTag::HTML_TAG_ATTRIBUTE) { 263*04fd306cSNickeau $child->addAttribute(BoxTag::HTML_TAG_ATTRIBUTE, "li"); 264*04fd306cSNickeau } 2659337a630SNickeau } 2669337a630SNickeau } 2679337a630SNickeau 268*04fd306cSNickeau return array( 269*04fd306cSNickeau PluginUtility::STATE => $state, 270*04fd306cSNickeau PluginUtility::ATTRIBUTES => $openingTag->getAttributes() 271*04fd306cSNickeau ); 2729337a630SNickeau 2739337a630SNickeau 2749337a630SNickeau } 2759337a630SNickeau return array(); 2769337a630SNickeau 2779337a630SNickeau } 2789337a630SNickeau 2799337a630SNickeau /** 2809337a630SNickeau * Render the output 2819337a630SNickeau * @param string $format 2829337a630SNickeau * @param Doku_Renderer $renderer 2839337a630SNickeau * @param array $data - what the function handle() return'ed 2849337a630SNickeau * @return boolean - rendered correctly? (however, returned value is not used at the moment) 2859337a630SNickeau * @see DokuWiki_Syntax_Plugin::render() 2869337a630SNickeau * 2879337a630SNickeau * 2889337a630SNickeau */ 289*04fd306cSNickeau function render($format, Doku_Renderer $renderer, $data): bool 2909337a630SNickeau { 2919337a630SNickeau if ($format == 'xhtml') { 2929337a630SNickeau 2939337a630SNickeau /** @var Doku_Renderer_xhtml $renderer */ 2949337a630SNickeau $state = $data[PluginUtility::STATE]; 2959337a630SNickeau switch ($state) { 2969337a630SNickeau case DOKU_LEXER_ENTER : 2979337a630SNickeau 298*04fd306cSNickeau PluginUtility::getSnippetManager()->attachCssInternalStyleSheet(self::MARKI_TAG); 2999337a630SNickeau $tagAttributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES], self::MARKI_TAG); 3009337a630SNickeau $tagAttributes->addClassName("list-group"); 3019337a630SNickeau 302*04fd306cSNickeau $numbered = $tagAttributes->getBooleanValueAndRemoveIfPresent(self::NUMBERED, self::NUMBERED_DEFAULT); 303*04fd306cSNickeau 304*04fd306cSNickeau $htmlElement = "ul"; 305*04fd306cSNickeau if ($numbered) { 306*04fd306cSNickeau $tagAttributes->addClassName("list-group-numbered"); 307*04fd306cSNickeau $htmlElement = "ol"; 308*04fd306cSNickeau } 309*04fd306cSNickeau 310*04fd306cSNickeau $renderer->doc .= $tagAttributes->toHtmlEnterTag($htmlElement); 3119337a630SNickeau break; 3129337a630SNickeau case DOKU_LEXER_UNMATCHED : 313*04fd306cSNickeau 3149337a630SNickeau $renderer->doc .= PluginUtility::renderUnmatched($data); 3159337a630SNickeau break; 316*04fd306cSNickeau 317*04fd306cSNickeau case DOKU_LEXER_EXIT : 318*04fd306cSNickeau $tagAttributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES], self::MARKI_TAG); 319*04fd306cSNickeau $numbered = $tagAttributes->getValueAndRemoveIfPresent(self::NUMBERED, self::NUMBERED_DEFAULT); 320*04fd306cSNickeau $htmlElement = "ul"; 321*04fd306cSNickeau if ($numbered) { 322*04fd306cSNickeau $htmlElement = "ol"; 323*04fd306cSNickeau } 324*04fd306cSNickeau $renderer->doc .= "</$htmlElement>"; 325*04fd306cSNickeau break; 3269337a630SNickeau } 3279337a630SNickeau return true; 3289337a630SNickeau } 3299337a630SNickeau 3309337a630SNickeau // unsupported $mode 3319337a630SNickeau return false; 3329337a630SNickeau } 3339337a630SNickeau 3349337a630SNickeau 3359337a630SNickeau} 3369337a630SNickeau 337