xref: /plugin/combo/ComboStrap/Prism.php (revision 564b1ae3619b65849aa27b721fa6229598967fe4)
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