diff --git a/src/public/app/services/syntax_highlight.ts b/src/public/app/services/syntax_highlight.ts new file mode 100644 index 000000000..c9f9449eb --- /dev/null +++ b/src/public/app/services/syntax_highlight.ts @@ -0,0 +1,94 @@ +import library_loader from "./library_loader.js"; +import mime_types from "./mime_types.js"; +import options from "./options.js"; + +export function getStylesheetUrl(theme) { + if (!theme) { + return null; + } + + const defaultPrefix = "default:"; + if (theme.startsWith(defaultPrefix)) { + return `${window.glob.assetPath}/node_modules/@highlightjs/cdn-assets/styles/${theme.substr(defaultPrefix.length)}.min.css`; + } + + return null; +} + +/** + * Identifies all the code blocks (as `pre code`) under the specified hierarchy and uses the highlight.js library to obtain the highlighted text which is then applied on to the code blocks. + * + * @param $container the container under which to look for code blocks and to apply syntax highlighting to them. + */ +export async function applySyntaxHighlight($container) { + if (!isSyntaxHighlightEnabled()) { + return; + } + + const codeBlocks = $container.find("pre code"); + for (const codeBlock of codeBlocks) { + const normalizedMimeType = extractLanguageFromClassList(codeBlock); + if (!normalizedMimeType) { + continue; + } + + applySingleBlockSyntaxHighlight($(codeBlock), normalizedMimeType); + } +} + +/** + * Applies syntax highlight to the given code block (assumed to be
), using highlight.js.
+ * 
+ * @param {*} $codeBlock 
+ * @param {*} normalizedMimeType 
+ */
+export async function applySingleBlockSyntaxHighlight($codeBlock, normalizedMimeType) {
+    $codeBlock.parent().toggleClass("hljs");
+    const text = $codeBlock.text();
+
+    if (!window.hljs) {
+        await library_loader.requireLibrary(library_loader.HIGHLIGHT_JS);
+    }
+
+    let highlightedText = null;
+    if (normalizedMimeType === mime_types.MIME_TYPE_AUTO) {
+        highlightedText = hljs.highlightAuto(text);
+    } else if (normalizedMimeType) {
+        const language = mime_types.getHighlightJsNameForMime(normalizedMimeType);
+        if (language) {
+            highlightedText = hljs.highlight(text, { language });
+        } else {
+            console.warn(`Unknown mime type: ${normalizedMimeType}.`);
+        }
+    }
+    
+    if (highlightedText) {            
+        $codeBlock.html(highlightedText.value);
+    }
+}
+
+/**
+ * Indicates whether syntax highlighting should be enabled for code blocks, by querying the value of the `codeblockTheme` option.
+ * @returns whether syntax highlighting should be enabled for code blocks.
+ */
+export function isSyntaxHighlightEnabled() {
+    const theme = options.get("codeBlockTheme");
+    return theme && theme !== "none";
+}
+
+/**
+ * Given a HTML element, tries to extract the `language-` class name out of it.
+ * 
+ * @param {string} el the HTML element from which to extract the language tag.
+ * @returns the normalized MIME type (e.g. `text-css` instead of `language-text-css`).
+ */
+function extractLanguageFromClassList(el) {
+    const prefix = "language-";
+    for (const className of el.classList) {
+        if (className.startsWith(prefix)) {
+            return className.substring(prefix.length);
+        }
+    }
+
+    return null;
+}
\ No newline at end of file
diff --git a/src/public/app/types.d.ts b/src/public/app/types.d.ts
index 0d55f41f6..705a16b3a 100644
--- a/src/public/app/types.d.ts
+++ b/src/public/app/types.d.ts
@@ -58,7 +58,13 @@ declare global {
     var __non_webpack_require__: RequireMethod | undefined;
 
     // Libraries
-    // Replace once library loader is replaced with webpack.
+    // TODO: Replace once library loader is replaced with webpack.
     var i18next: i18n;
     var i18nextHttpBackend: BackendModule;
+    var hljs: {
+        highlightAuto(text: string);
+        highlight(text: string, {
+            language: string
+        });
+    };
 }