1<?php 2 3 4require_once(__DIR__ . "/../ComboStrap/PluginUtility.php"); 5 6 7use ComboStrap\Call; 8use ComboStrap\CallStack; 9use ComboStrap\LogUtility; 10use ComboStrap\PluginUtility; 11use ComboStrap\Tag; 12use ComboStrap\TagAttributes; 13 14if (!defined('DOKU_INC')) die(); 15 16/** 17 * 18 * A paragraph syntax 19 * 20 * This syntax component is used dynamically while parsing (at the {@link DOKU_LEXER_END} of {@link \dokuwiki\Extension\SyntaxPlugin::handle()} 21 * with the function {@link \syntax_plugin_combo_para::fromEolToParagraphUntilEndOfStack()} 22 * 23 * 24 * !!!!! 25 * 26 * Info: The `eol` call are temporary created with {@link \dokuwiki\Parsing\ParserMode\Eol} 27 * and transformed to `p_open` and `p_close` via {@link \dokuwiki\Parsing\Handler\Block::process()} 28 * 29 * Note: p_open call may appears when the {@link \ComboStrap\Syntax::getPType()} is set to `block` or `stack` 30 * and the next call is not a block or a stack 31 * 32 * !!!!! 33 * 34 * 35 * Note on Typography 36 * TODO: https://github.com/typekit/webfontloader 37 * https://www.dokuwiki.org/plugin:typography 38 * https://stackoverflow.design/email/base/typography/ 39 * http://kyleamathews.github.io/typography.js/ 40 * https://docs.gitbook.com/features/advanced-branding (Roboto, Roboto Slab, Open Sans, Source Sans Pro, Lato, Ubuntu, Raleway, Merriweather) 41 * http://themenectar.com/docs/salient/theme-options/typography/ (Doc) 42 * https://www.modularscale.com/ - see the size of font 43 * 44 * See the fonts on your computer 45 * https://wordmark.it/ 46 * 47 * What's a type ? Type terminology 48 * https://www.supremo.co.uk/typeterms/ 49 * 50 * https://theprotoolbox.com/browse/font-tools/ 51 */ 52class syntax_plugin_combo_para extends DokuWiki_Syntax_Plugin 53{ 54 55 const TAG = 'para'; 56 const COMPONENT = "combo_para"; 57 58 59 /** 60 * The component that have a `normal` {@link \dokuwiki\Extension\SyntaxPlugin::getPType()} 61 * are wrapped in a p element by {@link Block::process()} if they are not in a component with a `block` ptype 62 * 63 * This function makes it easy for the test 64 * to do it and gives a little bit of documentation 65 * on why there is a `p` in the test 66 * @param $html 67 * @return string the html wrapped in p 68 */ 69 public static function wrapInP($html) 70 { 71 return "<p>" . $html . "</p>"; 72 } 73 74 75 /** 76 * Syntax Type. 77 * 78 * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 79 * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 80 */ 81 function getType() 82 { 83 return 'paragraphs'; 84 } 85 86 /** 87 * How Dokuwiki will add P element 88 * 89 * * 'normal' - The plugin can be used inside paragraphs 90 * * 'block' - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs 91 * * 'stack' - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs 92 * 93 * @see DokuWiki_Syntax_Plugin::getPType() 94 */ 95 function getPType() 96 { 97 /** 98 * !important! 99 * The {@link \dokuwiki\Parsing\Handler\Block::process()} 100 * will then not create an extra paragraph after it encounters a block 101 */ 102 return 'block'; 103 } 104 105 /** 106 * @return array 107 * Allow which kind of plugin inside 108 * 109 * No one of array('container', 'baseonly', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs') 110 * because we manage self the content and we call self the parser 111 */ 112 function getAllowedTypes() 113 { 114 /** 115 * Not needed as we don't have any {@link syntax_plugin_combo_para::connectTo()} 116 */ 117 return array(); 118 } 119 120 121 /** 122 * @see Doku_Parser_Mode::getSort() 123 * The mode with the lowest sort number will win out 124 * 125 */ 126 function getSort() 127 { 128 /** 129 * Not really needed as we don't have any {@link syntax_plugin_combo_para::connectTo()} 130 * 131 * Note: if we start to use it should be less than 370 132 * Ie Less than {@link \dokuwiki\Parsing\ParserMode\Eol::getSort()} 133 */ 134 return 369; 135 } 136 137 138 function connectTo($mode) 139 { 140 141 /** 142 * No need to connect 143 * This syntax plugin is added dynamically with the {@link Tag::processEolToEndStack()} 144 * function 145 */ 146 147 } 148 149 150 /** 151 * The handler for an internal link 152 * based on `internallink` in {@link Doku_Handler} 153 * The handler call the good renderer in {@link Doku_Renderer_xhtml} with 154 * the parameters (ie for instance internallink) 155 * @param string $match 156 * @param int $state 157 * @param int $pos 158 * @param Doku_Handler $handler 159 * @return array|bool 160 */ 161 function handle($match, $state, $pos, Doku_Handler $handler) 162 { 163 164 /** 165 * No need to handle, 166 * there is no {@link syntax_plugin_combo_para::connectTo() connection} 167 */ 168 return true; 169 170 171 } 172 173 /** 174 * Render the output 175 * @param string $format 176 * @param Doku_Renderer $renderer 177 * @param array $data - what the function handle() return'ed 178 * @return boolean - rendered correctly? (however, returned value is not used at the moment) 179 * @see DokuWiki_Syntax_Plugin::render() 180 * 181 * 182 */ 183 function render($format, Doku_Renderer $renderer, $data): bool 184 { 185 // The data 186 switch ($format) { 187 case 'xhtml': 188 189 /** @var Doku_Renderer_xhtml $renderer */ 190 $state = $data[PluginUtility::STATE]; 191 192 switch ($state) { 193 case DOKU_LEXER_ENTER: 194 $attributes = $data[PluginUtility::ATTRIBUTES]; 195 $tagAttributes = TagAttributes::createFromCallStackArray($attributes); 196 if ($tagAttributes->hasComponentAttribute(TagAttributes::TYPE_KEY)) { 197 $class = $tagAttributes->getType(); 198 $tagAttributes->addClassName($class); 199 } 200 $renderer->doc .= $tagAttributes->toHtmlEnterTag("p"); 201 break; 202 case DOKU_LEXER_SPECIAL: 203 $attributes = $data[PluginUtility::ATTRIBUTES]; 204 $tagAttributes = TagAttributes::createFromCallStackArray($attributes); 205 $renderer->doc .= $tagAttributes->toHtmlEnterTag("p"); 206 $renderer->doc .= "</p>"; 207 break; 208 case DOKU_LEXER_EXIT: 209 $renderer->doc .= "</p>"; 210 break; 211 } 212 return true; 213 214 case 'metadata': 215 216 /** @var Doku_Renderer_metadata $renderer */ 217 218 219 return true; 220 221 222 case renderer_plugin_combo_analytics::RENDERER_FORMAT: 223 224 /** 225 * @var renderer_plugin_combo_analytics $renderer 226 */ 227 return true; 228 229 } 230 // unsupported $mode 231 return false; 232 } 233 234 /** 235 * 236 * Transform EOL into paragraph 237 * between the {@link CallStack::getActualCall()} and the {@link CallStack::isPointerAtEnd()} 238 * of the stack 239 * 240 * Info: Basically, you get an new paragraph with a blank line or `\\` : https://www.dokuwiki.org/faq:newlines 241 * 242 * It replaces the {@link \dokuwiki\Parsing\Handler\Block::process() eol to paragraph Dokuwiki process} 243 * that takes place at the end of parsing process on the whole stack 244 * 245 * Info: The `eol` call are temporary created with {@link \dokuwiki\Parsing\ParserMode\Eol} 246 * and transformed to `p_open` and `p_close` via {@link \dokuwiki\Parsing\Handler\Block::process()} 247 * 248 * @param CallStack $callstack 249 * @param array $attributes - the attributes passed to the paragraph 250 */ 251 public static function fromEolToParagraphUntilEndOfStack(CallStack &$callstack, array $attributes) 252 { 253 254 if (!is_array($attributes)) { 255 LogUtility::msg("The passed attributes array ($attributes) for the creation of the paragraph is not an array", LogUtility::LVL_MSG_ERROR); 256 $attributes = []; 257 } 258 259 /** 260 * The syntax plugin that implements the paragraph 261 * ie {@link \syntax_plugin_combo_para} 262 * We will transform the eol with a call to this syntax plugin 263 * to create the paragraph 264 */ 265 $paragraphComponent = \syntax_plugin_combo_para::COMPONENT; 266 $paragraphTag = \syntax_plugin_combo_para::TAG; 267 268 /** 269 * The running variables 270 */ 271 $paragraphIsOpen = false; // A pointer to see if the paragraph is open 272 while ($actualCall = $callstack->next()) { 273 274 /** 275 * end of line is not always present 276 * because the pattern is eating it 277 * Example (list_open) 278 */ 279 if ($paragraphIsOpen && $actualCall->getTagName() !== "eol") { 280 if ($actualCall->getDisplay() == Call::BlOCK_DISPLAY) { 281 $paragraphIsOpen = false; 282 $callstack->insertBefore( 283 Call::createComboCall( 284 $paragraphTag, 285 DOKU_LEXER_EXIT, 286 $attributes 287 ) 288 ); 289 } 290 } 291 292 if ($actualCall->getTagName() === "eol") { 293 294 /** 295 * Next Call that is not the empty string 296 * Because Empty string would create an empty paragraph 297 * 298 * Start at 1 because we may not do 299 * a loop if we are at the end, the next call 300 * will return false 301 */ 302 $i = 1; 303 while ($nextCall = $callstack->next()) { 304 if (!( 305 trim($nextCall->getCapturedContent()) == "" && 306 $nextCall->isTextCall() 307 )) { 308 break; 309 } 310 $i++; 311 } 312 while ($i > 0) { // go back 313 $i--; 314 $callstack->previous(); 315 } 316 if ($nextCall === false) { 317 $nextDisplay = "last"; 318 $nextCall = null; 319 } else { 320 $nextDisplay = $nextCall->getDisplay(); 321 } 322 323 324 /** 325 * Processing 326 */ 327 if (!$paragraphIsOpen) { 328 329 switch ($nextDisplay) { 330 case Call::BlOCK_DISPLAY: 331 case "last": 332 $callstack->deleteActualCallAndPrevious(); 333 break; 334 case Call::INLINE_DISPLAY: 335 $paragraphIsOpen = true; 336 $actualCall->updateToPluginComponent( 337 $paragraphComponent, 338 DOKU_LEXER_ENTER, 339 $attributes 340 ); 341 break; 342 case "eol": 343 /** 344 * Empty line 345 */ 346 $actualCall->updateToPluginComponent( 347 $paragraphComponent, 348 DOKU_LEXER_ENTER, 349 $attributes 350 ); 351 $nextCall->updateToPluginComponent( 352 $paragraphComponent, 353 DOKU_LEXER_EXIT 354 ); 355 $callstack->next(); 356 break; 357 default: 358 LogUtility::msg("The eol action for the combination enter / (" . $nextDisplay . ") of the call ( $nextCall ) was not implemented", LogUtility::LVL_MSG_ERROR); 359 break; 360 } 361 } else { 362 /** 363 * Paragraph is open 364 */ 365 switch ($nextDisplay) { 366 case "eol": 367 /** 368 * Empty line 369 */ 370 $actualCall->updateToPluginComponent( 371 $paragraphComponent, 372 DOKU_LEXER_EXIT 373 ); 374 $nextCall->updateToPluginComponent( 375 $paragraphComponent, 376 DOKU_LEXER_ENTER, 377 $attributes 378 ); 379 $callstack->next(); 380 break; 381 case Call::INLINE_DISPLAY: 382 // A space 383 $actualCall->updateEolToSpace(); 384 break; 385 case Call::BlOCK_DISPLAY: 386 case "last"; 387 $actualCall->updateToPluginComponent( 388 $paragraphComponent, 389 DOKU_LEXER_EXIT 390 ); 391 $paragraphIsOpen = false; 392 break; 393 default: 394 LogUtility::msg("The display for a open paragraph (" . $nextDisplay . ") is not implemented", LogUtility::LVL_MSG_ERROR); 395 break; 396 } 397 } 398 399 } 400 } 401 402 // if the paragraph is open close it 403 if ($paragraphIsOpen) { 404 $callstack->insertBefore( 405 Call::createComboCall( 406 \syntax_plugin_combo_para::TAG, 407 DOKU_LEXER_EXIT 408 ) 409 ); 410 } 411 } 412 413} 414 415