11import type { ReactNode } from "react" ;
2- import React from "react" ;
3- import { Prism as SyntaxHighlighter } from "react-syntax-highlighter" ;
4- import { syntaxStyleNoBackgrounds } from "@/styles/syntaxHighlighting" ;
2+ import React , { useState , useEffect } from "react" ;
53import { Mermaid } from "./Mermaid" ;
4+ import {
5+ getShikiHighlighter ,
6+ mapToShikiLang ,
7+ SHIKI_THEME ,
8+ } from "@/utils/highlighting/shikiHighlighter" ;
69
710interface CodeProps {
811 node ?: unknown ;
@@ -24,6 +27,63 @@ interface SummaryProps {
2427 children ?: ReactNode ;
2528}
2629
30+ interface CodeBlockProps {
31+ code : string ;
32+ language : string ;
33+ }
34+
35+ /**
36+ * CodeBlock component with async Shiki highlighting
37+ * Reuses shared highlighter instance from diff rendering
38+ */
39+ const CodeBlock : React . FC < CodeBlockProps > = ( { code, language } ) => {
40+ const [ html , setHtml ] = useState < string | null > ( null ) ;
41+
42+ useEffect ( ( ) => {
43+ let cancelled = false ;
44+
45+ async function highlight ( ) {
46+ try {
47+ const highlighter = await getShikiHighlighter ( ) ;
48+ const shikiLang = mapToShikiLang ( language ) ;
49+
50+ // codeToHtml lazy-loads languages automatically
51+ const result = highlighter . codeToHtml ( code , {
52+ lang : shikiLang ,
53+ theme : SHIKI_THEME ,
54+ } ) ;
55+
56+ if ( ! cancelled ) {
57+ setHtml ( result ) ;
58+ }
59+ } catch ( error ) {
60+ console . warn ( `Failed to highlight code block (${ language } ):` , error ) ;
61+ if ( ! cancelled ) {
62+ setHtml ( null ) ;
63+ }
64+ }
65+ }
66+
67+ void highlight ( ) ;
68+
69+ return ( ) => {
70+ cancelled = true ;
71+ } ;
72+ } , [ code , language ] ) ;
73+
74+ // Show loading state or fall back to plain code
75+ if ( html === null ) {
76+ return (
77+ < pre >
78+ < code > { code } </ code >
79+ </ pre >
80+ ) ;
81+ }
82+
83+ // Render highlighted HTML
84+ return < div dangerouslySetInnerHTML = { { __html : html } } /> ;
85+ } ;
86+
2787// Custom components for markdown rendering
2888export const markdownComponents = {
2989 // Pass through pre element - let code component handle the wrapping
@@ -58,48 +118,29 @@ export const markdownComponents = {
58118 </ summary >
59119 ) ,
60120
61- // Custom code block renderer with syntax highlighting
121+ // Custom code block renderer with async Shiki highlighting
62122 code : ( { inline, className, children, node, ...props } : CodeProps ) => {
63123 const match = / l a n g u a g e - ( \w + ) / . exec ( className ?? "" ) ;
64124 const language = match ? match [ 1 ] : "" ;
65125
66- // Better inline detection: check for multiline content
126+ // Extract text content
67127 const childString =
68128 typeof children === "string" ? children : Array . isArray ( children ) ? children . join ( "" ) : "" ;
69129 const hasMultipleLines = childString . includes ( "\n" ) ;
70130 const isInline = inline ?? ! hasMultipleLines ;
71131
72- if ( ! isInline && language ) {
73- // Extract text content from children (react-markdown passes string or array of strings)
74- const code =
75- typeof children === "string" ? children : Array . isArray ( children ) ? children . join ( "" ) : "" ;
76-
77- // Handle mermaid diagrams
78- if ( language === "mermaid" ) {
79- return < Mermaid chart = { code } /> ;
80- }
132+ // Handle mermaid diagrams specially
133+ if ( ! isInline && language === "mermaid" ) {
134+ return < Mermaid chart = { childString } /> ;
135+ }
81136
82- // Code block with language - use syntax highlighter
83- return (
84- < SyntaxHighlighter
85- style = { syntaxStyleNoBackgrounds }
86- language = { language }
87- PreTag = "pre"
88- customStyle = { {
89- background : "rgba(0, 0, 0, 0.3)" ,
90- margin : "1em 0" ,
91- borderRadius : "4px" ,
92- fontSize : "12px" ,
93- padding : "12px" ,
94- } }
95- >
96- { code . replace ( / \n $ / , "" ) }
97- </ SyntaxHighlighter >
98- ) ;
137+ // Code blocks with language - use async Shiki highlighting
138+ if ( ! isInline && language ) {
139+ return < CodeBlock code = { childString } language = { language } /> ;
99140 }
100141
142+ // Code blocks without language (global CSS provides styling)
101143 if ( ! isInline ) {
102- // Code block without language - plain pre/code
103144 return (
104145 < pre >
105146 < code className = { className } { ...props } >
0 commit comments