diff --git a/frontend/package.json b/frontend/package.json index a553706..0d4fab7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,6 +18,8 @@ "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "remark-gfm": "^4.0.1", + "shiki": "^4.3.1", + "@shikijs/rehype": "^4.3.1", "tailwindcss": "^4.2.2" }, "devDependencies": { diff --git a/frontend/src/App.css b/frontend/src/App.css index 411b6ca..f7a040c 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -58,3 +58,15 @@ .stream-frame iframe { @apply w-full h-full border-0; } + +.shiki, +.shiki .line span { + color: var(--shiki-light); + background-color: var(--shiki-light-bg); +} + +.macchiato .shiki, +.macchiato .shiki .line span { + color: var(--shiki-dark); + background-color: var(--shiki-dark-bg); +} diff --git a/frontend/src/components/MdProcessor.tsx b/frontend/src/components/MdProcessor.tsx index 46d6ce8..1554ef4 100644 --- a/frontend/src/components/MdProcessor.tsx +++ b/frontend/src/components/MdProcessor.tsx @@ -9,6 +9,7 @@ import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import rehypeRaw from "rehype-raw"; import rehypeSanitize from "rehype-sanitize"; +import highlighter from "../utils/highlighter"; const toRawUrl = (repoUrl: string): string => { if (!repoUrl) return repoUrl; @@ -136,10 +137,7 @@ export default function MdProcessor({ if (isBlock) { return ( - + {children} ); @@ -157,13 +155,67 @@ export default function MdProcessor({ pre({ children, - ...props }: ComponentPropsWithoutRef<"pre">): ReactElement { + const codeChild = Array.isArray(children) + ? children[0] + : children; + + let lang = ""; + let code = ""; + + if ( + codeChild && + typeof codeChild === "object" && + "props" in codeChild + ) { + const el = codeChild as { + props: { + className?: string; + children?: string; + }; + }; + const cls = el.props?.className; + const match = cls?.match(/language-(\w+)/); + if (match) lang = match[1]; + if ( + typeof el.props?.children === "string" + ) { + code = el.props.children; + } + } + + const highlighted = + lang && code + ? highlighter.codeToHtml(code, { + lang, + themes: { + dark: "catppuccin-macchiato", + light: "catppuccin-latte", + }, + defaultColor: false, + }) + : null; + + if (highlighted) { + return ( +
+ {lang && ( + + {lang} + + )} +
+
+ ); + } + return ( -
+                                    
                                         {children}
                                     
); diff --git a/frontend/src/utils/highlighter.ts b/frontend/src/utils/highlighter.ts new file mode 100644 index 0000000..f35a5ee --- /dev/null +++ b/frontend/src/utils/highlighter.ts @@ -0,0 +1,71 @@ +import { createHighlighter } from "shiki"; + +export default await createHighlighter({ + themes: ["catppuccin-macchiato", "catppuccin-latte"], + langs: [ + "javascript", + "typescript", + "jsx", + "tsx", + "css", + "html", + "json", + "bash", + "shellscript", + "python", + "markdown", + "yaml", + "toml", + "rust", + "sql", + "diff", + "docker", + "graphql", + "go", + "java", + "kotlin", + "swift", + "c", + "cpp", + "odin", + "csharp", + "php", + "ruby", + "perl", + "lua", + "haskell", + "scala", + "elixir", + "erlang", + "r", + "matlab", + "sass", + "scss", + "less", + "vue", + "svelte", + "astro", + "solidity", + "nix", + "zig", + "ocaml", + "coq", + "lean", + "purescript", + "racket", + "scheme", + "clojure", + "common-lisp", + "makefile", + "cmake", + "powershell", + "proto", + "prisma", + "terraform", + "hcl", + "nginx", + "apache", + "latex", + "tex", + ], +});