11import { AlertTriangleIcon , LoaderCircleIcon } from "lucide-react" ;
2- import { memo , type CSSProperties , useEffect , useMemo , useState } from "react" ;
2+ import { memo , useEffect , useMemo , useState , type CSSProperties } from "react" ;
33
4- import { MARKDOWN_PREVIEW_CLASS_PREFIX , scopeMarkdownPreviewThemeCss } from "~/markdownPreview " ;
4+ import { useTheme } from "~/hooks/useTheme " ;
55
66interface MarkdownPreviewProps {
77 contents : string ;
88}
99
1010interface MarkdownPreviewState {
11+ status : "loading" | "ready" | "error" ;
1112 html : string ;
1213 css : string ;
1314 error : string | null ;
1415}
1516
16- const INITIAL_STATE : MarkdownPreviewState = {
17- html : "" ,
18- css : "" ,
19- error : null ,
20- } ;
17+ function createLoadingState ( ) : MarkdownPreviewState {
18+ return {
19+ status : "loading" ,
20+ html : "" ,
21+ css : "" ,
22+ error : null ,
23+ } ;
24+ }
25+
26+ function createReadyState ( html : string , css : string ) : MarkdownPreviewState {
27+ return {
28+ status : "ready" ,
29+ html,
30+ css,
31+ error : null ,
32+ } ;
33+ }
34+
35+ function createErrorState ( error : unknown ) : MarkdownPreviewState {
36+ return {
37+ status : "error" ,
38+ html : "" ,
39+ css : "" ,
40+ error : error instanceof Error ? error . message : "Failed to render Markdown preview." ,
41+ } ;
42+ }
43+
44+ const MARKDOWN_PREVIEW_CONTAINER_STYLE = {
45+ "--cm-bg" : "transparent" ,
46+ "--cm-text" : "var(--foreground)" ,
47+ "--cm-border" : "var(--border)" ,
48+ "--cm-muted" : "var(--muted-foreground)" ,
49+ "--cm-link" : "var(--primary)" ,
50+ "--cm-code-bg" : "var(--secondary)" ,
51+ "--cm-inline-code-bg" : "var(--secondary)" ,
52+ "--cm-table-header-bg" : "var(--secondary)" ,
53+ "--cm-table-stripe-bg" : "var(--accent)" ,
54+ "--cm-callout-bg" : "var(--secondary)" ,
55+ "--cm-radius" : "12px" ,
56+ "--cm-font" :
57+ 'var(--font-ui, "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif)' ,
58+ "--cm-mono" : '"SF Mono", "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace' ,
59+ } as CSSProperties ;
2160
2261export const MarkdownPreview = memo ( function MarkdownPreview ( { contents } : MarkdownPreviewProps ) {
23- const [ state , setState ] = useState < MarkdownPreviewState > ( INITIAL_STATE ) ;
62+ const { resolvedTheme } = useTheme ( ) ;
63+ const theme = resolvedTheme === "dark" ? "github-dark" : "github" ;
64+ const [ state , setState ] = useState < MarkdownPreviewState > ( ( ) => {
65+ if ( contents . trim ( ) . length === 0 ) {
66+ return createReadyState ( "" , "" ) ;
67+ }
68+ return createLoadingState ( ) ;
69+ } ) ;
2470
2571 useEffect ( ( ) => {
2672 let cancelled = false ;
27- setState ( INITIAL_STATE ) ;
73+ if ( contents . trim ( ) . length === 0 ) {
74+ setState ( createReadyState ( "" , "" ) ) ;
75+ return ( ) => {
76+ cancelled = true ;
77+ } ;
78+ }
2879
2980 void ( async ( ) => {
3081 try {
31- const preview = await import ( "@create-markdown/preview" ) ;
32- const html = await preview . markdownToHTML ( contents , {
33- classPrefix : MARKDOWN_PREVIEW_CLASS_PREFIX ,
34- linkTarget : "_blank" ,
35- theme : "system" ,
36- } ) ;
37- const css = scopeMarkdownPreviewThemeCss ( preview . themes . system ) ;
82+ const { renderMarkdownHtml } = await import ( "../lib/markdownHtml" ) ;
83+ const { html, css } = renderMarkdownHtml ( contents , theme ) ;
3884
3985 if ( ! cancelled ) {
40- setState ( { html, css, error : null } ) ;
86+ setState ( createReadyState ( html , css ) ) ;
4187 }
4288 } catch ( error ) {
4389 if ( ! cancelled ) {
44- setState ( {
45- html : "" ,
46- css : "" ,
47- error : error instanceof Error ? error . message : "Failed to render Markdown preview." ,
48- } ) ;
90+ setState ( createErrorState ( error ) ) ;
4991 }
5092 }
5193 } ) ( ) ;
5294
5395 return ( ) => {
5496 cancelled = true ;
5597 } ;
56- } , [ contents ] ) ;
98+ } , [ contents , theme ] ) ;
5799
58100 const markup = useMemo ( ( ) => ( { __html : state . html } ) , [ state . html ] ) ;
59101
60- if ( state . error ) {
102+ if ( state . status === " error" ) {
61103 return (
62104 < div className = "flex h-full min-h-0 items-center justify-center px-5 text-center" >
63105 < div className = "flex max-w-md flex-col items-center gap-2 text-destructive/80" >
@@ -69,7 +111,7 @@ export const MarkdownPreview = memo(function MarkdownPreview({ contents }: Markd
69111 ) ;
70112 }
71113
72- if ( ! state . html ) {
114+ if ( state . status === "loading" ) {
73115 return (
74116 < div className = "flex h-full min-h-0 items-center justify-center px-5 text-muted-foreground/70" >
75117 < div className = "flex items-center gap-2 text-xs" >
@@ -85,25 +127,7 @@ export const MarkdownPreview = memo(function MarkdownPreview({ contents }: Markd
85127 < style > { state . css } </ style >
86128 < div
87129 className = "mx-auto min-h-full max-w-4xl px-6 py-5"
88- style = {
89- {
90- "--cm-bg" : "transparent" ,
91- "--cm-text" : "var(--foreground)" ,
92- "--cm-border" : "var(--border)" ,
93- "--cm-muted" : "var(--muted-foreground)" ,
94- "--cm-link" : "var(--primary)" ,
95- "--cm-code-bg" : "var(--secondary)" ,
96- "--cm-inline-code-bg" : "var(--secondary)" ,
97- "--cm-table-header-bg" : "var(--secondary)" ,
98- "--cm-table-stripe-bg" : "var(--accent)" ,
99- "--cm-callout-bg" : "var(--secondary)" ,
100- "--cm-radius" : "12px" ,
101- "--cm-font" :
102- 'var(--font-ui, "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif)' ,
103- "--cm-mono" :
104- '"SF Mono", "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace' ,
105- } as CSSProperties
106- }
130+ style = { MARKDOWN_PREVIEW_CONTAINER_STYLE }
107131 >
108132 < div data-testid = "markdown-preview" dangerouslySetInnerHTML = { markup } />
109133 </ div >
0 commit comments