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
+ });
+ };
}