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