File
Blob: src/shared/editor/highlight/code-highlight-runtime.js
| 1 | // Keep this runtime as JavaScript with a local .d.ts type facade. Importing |
| 2 | // highlight.js types directly pulls in lib.dom, which breaks Worker no-DOM |
| 3 | // typechecking for Sites static rendering. |
| 4 | import { createLowlight } from "lowlight"; |
| 5 | import c from "highlight.js/lib/languages/c"; |
| 6 | import cpp from "highlight.js/lib/languages/cpp"; |
| 7 | import csharp from "highlight.js/lib/languages/csharp"; |
| 8 | import dockerfile from "highlight.js/lib/languages/dockerfile"; |
| 9 | import go from "highlight.js/lib/languages/go"; |
| 10 | import ini from "highlight.js/lib/languages/ini"; |
| 11 | import java from "highlight.js/lib/languages/java"; |
| 12 | import javascript from "highlight.js/lib/languages/javascript"; |
| 13 | import json from "highlight.js/lib/languages/json"; |
| 14 | import markdown from "highlight.js/lib/languages/markdown"; |
| 15 | import php from "highlight.js/lib/languages/php"; |
| 16 | import plaintext from "highlight.js/lib/languages/plaintext"; |
| 17 | import python from "highlight.js/lib/languages/python"; |
| 18 | import rust from "highlight.js/lib/languages/rust"; |
| 19 | import shell from "highlight.js/lib/languages/shell"; |
| 20 | import sql from "highlight.js/lib/languages/sql"; |
| 21 | import typescript from "highlight.js/lib/languages/typescript"; |
| 22 | import yaml from "highlight.js/lib/languages/yaml"; |
| 23 | import { CODE_LANGUAGES, resolveLanguage } from "../schema/code-block-model"; |
| 24 | |
| 25 | const PLAIN_TEXT_LANGUAGES = new Set(["text", "plaintext", "txt"]); |
| 26 | |
| 27 | export const codeBlockLowlight = createLowlight({ |
| 28 | text: plaintext, |
| 29 | plaintext, |
| 30 | c, |
| 31 | csharp, |
| 32 | cpp, |
| 33 | dockerfile, |
| 34 | go, |
| 35 | hcl: ini, |
| 36 | java, |
| 37 | javascript, |
| 38 | json, |
| 39 | jsonc: json, |
| 40 | markdown, |
| 41 | php, |
| 42 | python, |
| 43 | rust, |
| 44 | shell, |
| 45 | sql, |
| 46 | toml: ini, |
| 47 | typescript, |
| 48 | yaml, |
| 49 | }); |
| 50 | |
| 51 | for (const [language, meta] of Object.entries(CODE_LANGUAGES)) { |
| 52 | const aliases = (meta.aliases ?? []).filter((alias) => alias !== language); |
| 53 | if (aliases.length > 0) { |
| 54 | codeBlockLowlight.registerAlias({ [language]: aliases }); |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | export function isPlainTextCodeLanguage(language) { |
| 59 | return language ? PLAIN_TEXT_LANGUAGES.has(language.trim().toLowerCase()) : false; |
| 60 | } |
| 61 | |
| 62 | export function highlightCodeToSegments(language, code) { |
| 63 | const normalizedLanguage = resolveLanguage(typeof language === "string" ? language : null); |
| 64 | if (isPlainTextCodeLanguage(normalizedLanguage)) { |
| 65 | return plainCodeSegments(code); |
| 66 | } |
| 67 | |
| 68 | try { |
| 69 | const result = codeBlockLowlight.highlight(normalizedLanguage, code); |
| 70 | const segments = flattenHighlightNodes(getHighlightNodes(result)); |
| 71 | return segments.length > 0 ? segments : plainCodeSegments(code); |
| 72 | } catch { |
| 73 | return plainCodeSegments(code); |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | function plainCodeSegments(code) { |
| 78 | return [{ text: code, classes: [] }]; |
| 79 | } |
| 80 | |
| 81 | function getHighlightNodes(result) { |
| 82 | return result.value ?? result.children ?? []; |
| 83 | } |
| 84 | |
| 85 | function flattenHighlightNodes(nodes, inheritedClasses = []) { |
| 86 | return nodes.flatMap((node) => { |
| 87 | const classes = [...inheritedClasses, ...readClassNames(node.properties?.className)]; |
| 88 | if (Array.isArray(node.children) && node.children.length > 0) { |
| 89 | return flattenHighlightNodes(node.children, classes); |
| 90 | } |
| 91 | return node.value ? [{ text: node.value, classes }] : []; |
| 92 | }); |
| 93 | } |
| 94 | |
| 95 | function readClassNames(className) { |
| 96 | if (Array.isArray(className)) return className; |
| 97 | return className ? [className] : []; |
| 98 | } |