1<?php 2 3namespace ComboStrap; 4 5 6use Doku_Renderer_xhtml; 7use syntax_plugin_combo_code; 8 9/** 10 * Concurrent: https://highlightjs.org/ used by remark powerpoint 11 */ 12class Prism 13{ 14 15 const SNIPPET_NAME = 'prism'; 16 /** 17 * The class used to mark the added prism code 18 * See: https://cdnjs.com/libraries/prism/ 19 */ 20 const BASE_PRISM_CDN = "https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0"; 21 /** 22 * The default prompt for bash 23 */ 24 const CONF_BASH_PROMPT = "bashPrompt"; 25 /** 26 * The default prompt for batch (dos) 27 */ 28 const CONF_BATCH_PROMPT = "batchPrompt"; 29 /** 30 * The default prompt for powershell 31 */ 32 const CONF_POWERSHELL_PROMPT = "powershellPrompt"; 33 34 /** 35 * The default name of prism 36 * It does not follow the naming of the theming 37 */ 38 const PRISM_THEME = "prism"; 39 40 /** 41 * @var string[] https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/themes/prism-{theme}.min.css 42 * 43 * or default 44 * 45 * https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/themes/prism.min.css 46 * 47 * or 48 * 49 * https://github.com/PrismJS/prism-themes 50 * 51 * from https://cdnjs.com/libraries/prism 52 */ 53 const THEMES_INTEGRITY = [ 54 Prism::PRISM_THEME => "sha256-ko4j5rn874LF8dHwW29/xabhh8YBleWfvxb8nQce4Fc=", 55 "coy" => "sha256-gkHLZLptZZHaBY+jqrRkAVzOGfMa4HBhSCJteem8wy8=", 56 "dark" => "sha256-l+VX6V333ll/PXrjqG1W6DyZvDEw+50M7aAP6dcD7Qc=", 57 "funky" => "sha256-l9GTgvTMmAvPQ6IlNCd/I2FQwXVlJCLbGId7z6QlOpo=", 58 "okaidia" => "sha256-zzHVEO0xOoVm0I6bT9v5SgpRs1cYNyvEvHXW/1yCgqU=", 59 "solarizedlight" => "sha256-Lr49DyE+/KstnLdBxqZBoDYgNi6ONfZyAZw3LDhxB9I=", 60 "tomorrow" => "sha256-GxX+KXGZigSK67YPJvbu12EiBx257zuZWr0AMiT1Kpg=", 61 "twilight" => "sha256-R7PF7y9XAuz19FB93NgH/WQUVGk30iytl7EwtETrypo=" 62 ]; 63 64 /** 65 * The theme 66 */ 67 const CONF_PRISM_THEME = "prismTheme"; 68 const PRISM_THEME_DEFAULT = "tomorrow"; 69 const SNIPPET_ID_AUTOLOADER = self::SNIPPET_NAME . "-autoloader"; 70 const LINE_NUMBERS_ATTR = "line-numbers"; 71 72 73 /** 74 * 75 * @param $theme 76 * 77 * Ter info: The theme of the default wiki is in the print.css file (search for code blocks) 78 */ 79 public static function addSnippet($theme) 80 { 81 $BASE_PRISM_CDN = self::BASE_PRISM_CDN; 82 83 if ($theme == self::PRISM_THEME) { 84 $themeStyleSheet = "prism.min.css"; 85 } else { 86 $themeStyleSheet = "prism-$theme.min.css"; 87 } 88 $themeIntegrity = self::THEMES_INTEGRITY[$theme]; 89 90 /** 91 * We miss a bottom margin 92 * as a paragraph 93 */ 94 $snippetManager = PluginUtility::getSnippetManager(); 95 $snippetManager->attachCssInternalStyleSheet(self::SNIPPET_NAME); 96 97 /** 98 * Javascript 99 */ 100 $snippetManager->attachRemoteJavascriptLibraryFromLiteral( 101 self::SNIPPET_NAME, 102 "$BASE_PRISM_CDN/components/prism-core.min.js", 103 "sha256-vlRYHThwdq55dA+n1BKQRzzLwFtH9VINdSI68+5JhpU="); 104 $snippetManager->attachRemoteJavascriptLibraryFromLiteral( 105 self::SNIPPET_NAME, 106 "$BASE_PRISM_CDN/plugins/toolbar/prism-toolbar.min.js", 107 "sha256-FyIVdIHL0+ppj4Q4Ft05K3wyCsYikpHIDGI7dcaBalU=" 108 ); 109 $snippetManager->attachRemoteCssStyleSheetFromLiteral( 110 self::SNIPPET_NAME, 111 "$BASE_PRISM_CDN/plugins/toolbar/prism-toolbar.css", 112 "sha256-kK4/JIYJUKI4Zdg9ZQ7FYyRIqeWPfYKi5QZHO2n/lJI=" 113 ); 114 // https://prismjs.com/plugins/normalize-whitespace/ 115 $snippetManager->attachRemoteJavascriptLibraryFromLiteral( 116 self::SNIPPET_NAME, 117 "$BASE_PRISM_CDN/plugins/normalize-whitespace/prism-normalize-whitespace.min.js", 118 "sha256-gBzABGbXfQYYnyr8xmDFjx6KGO9dBYuypG1QBjO76pY="); 119 // https://prismjs.com/plugins/copy-to-clipboard/ 120 $snippetManager->attachRemoteJavascriptLibraryFromLiteral( 121 self::SNIPPET_NAME, 122 "$BASE_PRISM_CDN/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js", 123 "sha512-pUNGXbOrc+Y3dm5z2ZN7JYQ/2Tq0jppMDOUsN4sQHVJ9AUQpaeERCUfYYBAnaRB9r8d4gtPKMWICNhm3tRr4Fg=="); 124 // https://prismjs.com/plugins/show-language/ 125 $snippetManager->attachRemoteJavascriptLibraryFromLiteral( 126 self::SNIPPET_NAME, 127 "$BASE_PRISM_CDN/plugins/show-language/prism-show-language.min.js", 128 "sha256-Z3GTw2RIadLG7KyP/OYB+aAxVYzvg2PByKzYrJlA1EM="); 129 // https://prismjs.com/plugins/command-line/ 130 $snippetManager->attachRemoteJavascriptLibraryFromLiteral( 131 self::SNIPPET_NAME, 132 "$BASE_PRISM_CDN/plugins/command-line/prism-command-line.min.js", 133 "sha256-9WlakH0Upf3N8DDteHlbeKCHxSsljby+G9ucUCQNiU0="); 134 $snippetManager->attachRemoteCssStyleSheetFromLiteral( 135 self::SNIPPET_NAME, 136 "$BASE_PRISM_CDN/plugins/command-line/prism-command-line.css", 137 "sha256-UvoA9bIYCYQkCMTYG5p2LM8ZpJmnC4G8k0oIc89nuQA=" 138 ); 139 // https://prismjs.com/plugins/line-highlight/ 140 $snippetManager->attachRemoteJavascriptLibraryFromLiteral( 141 self::SNIPPET_NAME, 142 "$BASE_PRISM_CDN/plugins/line-highlight/prism-line-highlight.min.js", 143 "sha512-O5GVPBZIURR9MuNiCjSa1wNTL3w91tojKlgCXmOjWDT5a3+9Ms+wGsTkBO93PI3anfdajkJD0sJiS6qdQq7jRA=="); 144 $snippetManager->attachRemoteCssStyleSheetFromLiteral( 145 self::SNIPPET_NAME, 146 "$BASE_PRISM_CDN/plugins/line-highlight/prism-line-highlight.min.css", 147 "sha512-nXlJLUeqPMp1Q3+Bd8Qds8tXeRVQscMscwysJm821C++9w6WtsFbJjPenZ8cQVMXyqSAismveQJc0C1splFDCA==" 148 ); 149 //https://prismjs.com/plugins/line-numbers/ 150 $snippetManager->attachRemoteJavascriptLibraryFromLiteral( 151 self::SNIPPET_NAME, 152 "$BASE_PRISM_CDN/plugins/line-numbers/prism-line-numbers.min.js", 153 "sha256-K837BwIyiXo5k/9fCYgqUyA14bN4/Ve9P2SIT0KmZD0="); 154 $snippetManager->attachRemoteJavascriptLibraryFromLiteral( 155 self::SNIPPET_NAME, 156 "$BASE_PRISM_CDN/plugins/line-numbers/prism-line-numbers.css", 157 "sha256-ye8BkHf2lHXUtqZ18U0KI3xjJ1Yv7P8lvdKBt9xmVJM=" 158 ); 159 160 // https://prismjs.com/plugins/download-button/--> 161 $snippetManager->attachRemoteJavascriptLibraryFromLiteral( 162 self::SNIPPET_NAME, 163 "$BASE_PRISM_CDN/plugins/download-button/prism-download-button.min.js", 164 "sha256-CQyVQ5ejeTshlzOS/eCiry40br9f4fQ9jb5e4qPl7ZA="); 165 166 // Loading the theme 167 $snippetManager->attachRemoteCssStyleSheetFromLiteral( 168 self::SNIPPET_NAME, 169 "$BASE_PRISM_CDN/themes/$themeStyleSheet", 170 $themeIntegrity 171 ); 172 173 $javascriptCode = <<<EOD 174window.addEventListener('load', (event) => { 175 176 Prism.plugins.NormalizeWhitespace.setDefaults({ 177 'remove-trailing': true, 178 'remove-indent': true, 179 'left-trim': true, 180 'right-trim': true, 181 }); 182 183}); 184EOD; 185 $snippetManager->attachJavascriptFromComponentId(self::SNIPPET_NAME, $javascriptCode); 186 187 } 188 189 /** 190 * Add the first block of prism 191 * @param \Doku_Renderer_xhtml $renderer 192 * @param TagAttributes $attributes 193 * @param \DokuWiki_Syntax_Plugin $plugin 194 */ 195 public static function htmlEnter(\Doku_Renderer_xhtml $renderer, \DokuWiki_Syntax_Plugin $plugin, $attributes = null) 196 { 197 198 if ($attributes == null) { 199 $attributes = TagAttributes::createEmpty(); 200 } 201 202 /** 203 * Display none, no rendering 204 */ 205 $display = $attributes->getValueAndRemove("display"); 206 if ($display != null) { 207 if ($display == "none") { 208 return; 209 } 210 } 211 212 213 /** 214 * Add prism theme 215 */ 216 $theme = $plugin->getConf(Prism::CONF_PRISM_THEME, Prism::PRISM_THEME_DEFAULT); 217 Prism::addSnippet($theme); 218 219 /** 220 * Logical tag 221 */ 222 $logicalTag = $plugin->getPluginComponent(); 223 if ($attributes->getLogicalTag() != null) { 224 $logicalTag = $attributes->getLogicalTag(); 225 } 226 // for the https://combostrap.com/styling/userstyle 227 $attributes->setLogicalTag($logicalTag . "-container"); 228 229 /** 230 * The child element (code) of the `pre` element 231 * The container is the passed `attributes` 232 * We can then constrained in height ... 233 * It contains the language 234 */ 235 $codeAttributes = TagAttributes::createEmpty($logicalTag); 236 $codeAttributes->setType($attributes->getType()); 237 $language = $attributes->getValue(TagAttributes::TYPE_KEY); 238 if ($language == null) { 239 // Prism does not have any default language 240 // There is a bug has it tried to download the txt javascript 241 // but without language, there is no styling 242 $language = "txt"; 243 } else { 244 $language = strtolower($language); 245 Prism::addAutoloaderSnippet(); 246 } 247 248 if (in_array($language, Tag\WebCodeTag::MARKIS)) { 249 // Marki is not fully markdown 250 // because it accepts space in super set html container and 251 // prism will highlight them as indented code 252 $language = "html"; 253 } 254 /** 255 * Language name mapping between the syntax name and prism 256 */ 257 switch ($language) { 258 case "rsplus": 259 $language = "r"; 260 break; 261 case "dos": 262 case "bat": 263 $language = "batch"; 264 break; 265 case "grok": 266 $language = "regex"; 267 break; 268 case "jinja": 269 // https://github.com/PrismJS/prism/issues/759 270 $language = "twig"; 271 break; 272 case "apache": 273 $language = "apacheconf"; 274 break; 275 case "babel": 276 $language = "jsx"; 277 break; 278 case "antlr": 279 $language = "g4"; 280 break; 281 282 } 283 284 StringUtility::addEolCharacterIfNotPresent($renderer->doc); 285 $codeAttributes->addClassName('language-' . $language); 286 /** 287 * Code element 288 * Don't put a fucking EOL after it 289 * Otherwise it fucked up the output as the text below a code tag is printed 290 */ 291 $codeHtml = $codeAttributes->toHtmlEnterTag('code'); 292 $attributes->addHtmlAfterEnterTag($codeHtml); 293 294 295 /** 296 * Pre Element 297 * Line numbers 298 */ 299 $lineNumberEnabled = false; 300 if ($attributes->hasComponentAttribute(self::LINE_NUMBERS_ATTR)) { 301 $attributes->removeComponentAttribute(self::LINE_NUMBERS_ATTR); 302 $attributes->addClassName("line-numbers"); 303 $lineNumberEnabled = true; 304 } 305 306 307 /** 308 * Command line prompt 309 * (Line element and prompt cannot be chosen together 310 * otherwise they endup on top of each other) 311 */ 312 if (!$lineNumberEnabled) { 313 if ($attributes->hasComponentAttribute("prompt")) { 314 $promptValue = $attributes->getValueAndRemove("prompt"); 315 // prompt may be the empty string 316 if (!empty($promptValue)) { 317 $attributes->addClassName("command-line"); 318 $attributes->addOutputAttributeValue("data-prompt", $promptValue); 319 } 320 } else { 321 /** 322 * Default prompt 323 */ 324 switch ($language) { 325 case "bash": 326 $prompt = $plugin->getConf(self::CONF_BASH_PROMPT); 327 break; 328 case "batch": 329 $prompt = trim($plugin->getConf(self::CONF_BATCH_PROMPT)); 330 if (!empty($prompt)) { 331 if (!strpos($prompt, -1) == ">") { 332 $prompt .= ">"; 333 } 334 } 335 break; 336 case "powershell": 337 $prompt = trim($plugin->getConf(self::CONF_POWERSHELL_PROMPT)); 338 if (!empty($prompt)) { 339 if (!strpos($prompt, -1) == ">") { 340 $prompt .= ">"; 341 } 342 } 343 break; 344 } 345 if(!empty($prompt)) { 346 $attributes->addClassName("command-line"); 347 $attributes->addOutputAttributeValue("data-prompt", $prompt); 348 } 349 } 350 } 351 352 /** 353 * Line highlight 354 */ 355 if ($attributes->hasComponentAttribute("line-highlight")) { 356 $lineHiglight = $attributes->getValueAndRemove("line-highlight"); 357 if(!empty($lineHiglight)) { 358 $attributes->addOutputAttributeValue('data-line', $lineHiglight); 359 } 360 } 361 362 // Download 363 $attributes->addOutputAttributeValue('data-download-link', true); 364 if ($attributes->hasComponentAttribute(syntax_plugin_combo_code::FILE_PATH_KEY)) { 365 $fileSrc = $attributes->getValueAndRemove(syntax_plugin_combo_code::FILE_PATH_KEY); 366 $attributes->addOutputAttributeValue('data-src', $fileSrc); 367 $attributes->addOutputAttributeValue('data-download-link-label', "Download " . $fileSrc); 368 } else { 369 $fileName = "file." . $language; 370 $attributes->addOutputAttributeValue('data-src', $fileName); 371 } 372 /** 373 * No end of line after the pre, please, otherwise we get a new line 374 * in the code output 375 */ 376 $htmlCode = $attributes->toHtmlEnterTag("pre"); 377 378 379 /** 380 * Return 381 */ 382 $renderer->doc .= $htmlCode; 383 384 } 385 386 /** 387 * @param Doku_Renderer_xhtml $renderer 388 * @param TagAttributes $attributes 389 */ 390 public static function htmlExit(\Doku_Renderer_xhtml $renderer, $attributes = null) 391 { 392 393 if ($attributes != null) { 394 /** 395 * Display none, no rendering 396 */ 397 $display = $attributes->getValueAndRemove("display"); 398 if ($display != null) { 399 if ($display == "none") { 400 return; 401 } 402 } 403 } 404 $renderer->doc .= '</code>' . DOKU_LF . '</pre>' . DOKU_LF; 405 } 406 407 /** 408 * The autoloader try to download all language 409 * Even the one such as txt that does not exist 410 * This function was created to add it conditionally 411 */ 412 private static function addAutoloaderSnippet() 413 { 414 PluginUtility::getSnippetManager() 415 ->attachRemoteJavascriptLibrary( 416 self::SNIPPET_ID_AUTOLOADER, 417 self::BASE_PRISM_CDN . "/plugins/autoloader/prism-autoloader.min.js" 418 ); 419 } 420 421 422} 423