@@ -4,6 +4,118 @@ import Link from "next/link";
44import { YouTubeEmbed } from "@next/third-parties/google" ;
55import { Highlight , themes } from "prism-react-renderer" ;
66import { CopyButton } from "@/components/ui/copy-button" ;
7+ import ReactMarkdown from "react-markdown" ;
8+ import { ReactNode , Children , isValidElement } from "react" ;
9+
10+ const mdComponents = {
11+ h1 : ( { children } : { children ?: ReactNode } ) => (
12+ < h1 className = "text-3xl font-semibold mb-4 mt-6" > { children } </ h1 >
13+ ) ,
14+ h2 : ( { children } : { children ?: ReactNode } ) => (
15+ < h2 className = "text-2xl font-semibold mb-4 mt-6" > { children } </ h2 >
16+ ) ,
17+ h3 : ( { children } : { children ?: ReactNode } ) => (
18+ < h3 className = "text-xl font-semibold mb-4 mt-6" > { children } </ h3 >
19+ ) ,
20+ p : ( { children } : { children ?: ReactNode } ) => (
21+ < p className = "mb-4 leading-7" > { children } </ p >
22+ ) ,
23+ ul : ( { children } : { children ?: ReactNode } ) => (
24+ < ul className = "pl-6 mb-4 list-disc list-inside" > { children } </ ul >
25+ ) ,
26+ ol : ( { children } : { children ?: ReactNode } ) => (
27+ < ol className = "pl-6 mb-4 list-decimal list-inside" > { children } </ ol >
28+ ) ,
29+ li : ( { children } : { children ?: ReactNode } ) => (
30+ < li className = "mb-2" > { children } </ li >
31+ ) ,
32+ a : ( { href, children, ...props } : any ) => {
33+ const isExternal =
34+ ( href || "" ) . startsWith ( "http" ) ||
35+ ( href || "" ) . startsWith ( "https" ) ||
36+ ( href || "" ) . startsWith ( "mailto" ) ;
37+ return (
38+ < Link
39+ href = { href || "#" }
40+ target = { isExternal ? "_blank" : undefined }
41+ rel = { isExternal ? "noopener" : undefined }
42+ className = "underline"
43+ { ...props }
44+ >
45+ { children }
46+ </ Link >
47+ ) ;
48+ } ,
49+ code : ( {
50+ node,
51+ inline,
52+ className,
53+ children,
54+ ...props
55+ } : {
56+ node ?: any ;
57+ inline ?: boolean ;
58+ className ?: string ;
59+ children ?: ReactNode ;
60+ } ) => {
61+ const match = / l a n g u a g e - ( \w + ) / . exec ( className || "" ) ;
62+ const lang = match ? match [ 1 ] : "" ;
63+ const codeStr = String ( children ) . replace ( / \n $ / , "" ) ;
64+
65+ const isInlineCode =
66+ inline || ( ! className && ! codeStr . includes ( "\n" ) && codeStr . length < 100 ) ;
67+
68+ if ( isInlineCode ) {
69+ return (
70+ < code
71+ className = "bg-muted px-1 py-0.5 rounded text-sm font-mono"
72+ { ...props }
73+ >
74+ { children }
75+ </ code >
76+ ) ;
77+ }
78+
79+ return (
80+ < div className = "grid my-4 overflow-x-auto rounded-lg border border-border text-xs lg:text-sm bg-primary/80 dark:bg-muted/80" >
81+ < div className = "flex items-center justify-between px-4 py-2 border-b border-border bg-primary/80 dark:bg-muted" >
82+ < div className = "text-muted-foreground font-mono" > { lang } </ div >
83+ < CopyButton code = { codeStr } />
84+ </ div >
85+ < Highlight
86+ theme = { themes . vsDark }
87+ code = { codeStr }
88+ language = { lang || "text" }
89+ >
90+ { ( { style, tokens, getLineProps, getTokenProps } ) => (
91+ < pre
92+ style = { {
93+ ...style ,
94+ padding : "1.5rem" ,
95+ margin : 0 ,
96+ overflow : "auto" ,
97+ backgroundColor : "transparent" ,
98+ } }
99+ >
100+ { tokens . map ( ( line , i ) => (
101+ < div key = { i } { ...getLineProps ( { line } ) } >
102+ { line . map ( ( token , key ) => (
103+ < span key = { key } { ...getTokenProps ( { token } ) } />
104+ ) ) }
105+ </ div >
106+ ) ) }
107+ </ pre >
108+ ) }
109+ </ Highlight >
110+ </ div >
111+ ) ;
112+ } ,
113+ pre : ( { children } : { children ?: ReactNode } ) => < > { children } </ > ,
114+ } ;
115+
116+ const renderMarkdownText = ( text : string ) => {
117+ return < ReactMarkdown components = { mdComponents } > { text } </ ReactMarkdown > ;
118+ } ;
7119
8120const portableTextComponents : PortableTextProps [ "components" ] = {
9121 types : {
@@ -74,9 +186,17 @@ const portableTextComponents: PortableTextProps["components"] = {
74186 } ,
75187 } ,
76188 block : {
77- normal : ( { children } ) => (
78- < p style = { { marginBottom : "1rem" } } > { children } </ p >
79- ) ,
189+ normal : ( { children } ) => {
190+ const text = Children . toArray ( children )
191+ . map ( ( child : any ) => {
192+ if ( isValidElement ( child ) && ( child . props as any ) ?. children ) {
193+ return ( child . props as any ) . children ;
194+ }
195+ return child ;
196+ } )
197+ . join ( "" ) ;
198+ return renderMarkdownText ( text ) ;
199+ } ,
80200 h1 : ( { children } ) => (
81201 < h1 style = { { marginBottom : "1rem" , marginTop : "1rem" } } > { children } </ h1 >
82202 ) ,
0 commit comments