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