@@ -37,6 +37,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip";
3737interface MarkdownProps {
3838 children : string ;
3939 className ?: string ;
40+ emptyState ?: React . ReactNode ;
4041}
4142
4243// Pattern to match @mentions (GitHub-style: @username)
@@ -46,7 +47,18 @@ const MENTION_REGEX = /@([a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38})/g;
4647export const Markdown = memo ( function Markdown ( {
4748 children,
4849 className,
50+ emptyState,
4951} : MarkdownProps ) {
52+ const containerRef = useRef < HTMLDivElement > ( null ) ;
53+ const [ isEmpty , setIsEmpty ] = useState ( false ) ;
54+
55+ // Check if rendered content is empty after mount
56+ useEffect ( ( ) => {
57+ if ( containerRef . current && emptyState ) {
58+ const text = containerRef . current . textContent ?. trim ( ) || "" ;
59+ setIsEmpty ( text . length === 0 ) ;
60+ }
61+ } , [ children , emptyState ] ) ;
5062 // Parse the content to find @mentions and wrap them
5163 const processedContent = useMemo ( ( ) => {
5264 // Split by @mentions but keep the mentions
@@ -81,7 +93,52 @@ export const Markdown = memo(function Markdown({
8193
8294 if ( ! hasMentions ) {
8395 return (
84- < div className = { cn ( "markdown-body" , className ) } >
96+ < >
97+ { isEmpty && emptyState }
98+ < div
99+ ref = { containerRef }
100+ className = { cn ( "markdown-body" , className , isEmpty && "hidden" ) }
101+ >
102+ < ReactMarkdown
103+ remarkPlugins = { [ remarkGfm , remarkGemoji ] }
104+ rehypePlugins = { [
105+ rehypeRaw ,
106+ rehypeSanitize ,
107+ [ rehypeHighlight , { detect : true , ignoreMissing : true } ] ,
108+ ] }
109+ components = { {
110+ // Custom link handling - open external links in new tab
111+ a : ( { href, children, ...props } ) => {
112+ const isExternal = href ?. startsWith ( "http" ) ;
113+ return (
114+ < a
115+ href = { href }
116+ target = { isExternal ? "_blank" : undefined }
117+ rel = { isExternal ? "noopener noreferrer" : undefined }
118+ { ...props }
119+ >
120+ { children }
121+ </ a >
122+ ) ;
123+ } ,
124+ } }
125+ >
126+ { children }
127+ </ ReactMarkdown >
128+ </ div >
129+ </ >
130+ ) ;
131+ }
132+
133+ // Render with mentions wrapped in hover cards
134+ // We need to process mentions within the markdown, so we'll use a custom component
135+ return (
136+ < >
137+ { isEmpty && emptyState }
138+ < div
139+ ref = { containerRef }
140+ className = { cn ( "markdown-body" , className , isEmpty && "hidden" ) }
141+ >
85142 < ReactMarkdown
86143 remarkPlugins = { [ remarkGfm , remarkGemoji ] }
87144 rehypePlugins = { [
@@ -104,58 +161,25 @@ export const Markdown = memo(function Markdown({
104161 </ a >
105162 ) ;
106163 } ,
164+ // Process text nodes to find and wrap @mentions
165+ p : ( { children, ...props } ) => {
166+ return < p { ...props } > { processChildren ( children ) } </ p > ;
167+ } ,
168+ li : ( { children, ...props } ) => {
169+ return < li { ...props } > { processChildren ( children ) } </ li > ;
170+ } ,
171+ td : ( { children, ...props } ) => {
172+ return < td { ...props } > { processChildren ( children ) } </ td > ;
173+ } ,
174+ th : ( { children, ...props } ) => {
175+ return < th { ...props } > { processChildren ( children ) } </ th > ;
176+ } ,
107177 } }
108178 >
109179 { children }
110180 </ ReactMarkdown >
111181 </ div >
112- ) ;
113- }
114-
115- // Render with mentions wrapped in hover cards
116- // We need to process mentions within the markdown, so we'll use a custom component
117- return (
118- < div className = { cn ( "markdown-body" , className ) } >
119- < ReactMarkdown
120- remarkPlugins = { [ remarkGfm , remarkGemoji ] }
121- rehypePlugins = { [
122- rehypeRaw ,
123- rehypeSanitize ,
124- [ rehypeHighlight , { detect : true , ignoreMissing : true } ] ,
125- ] }
126- components = { {
127- // Custom link handling - open external links in new tab
128- a : ( { href, children, ...props } ) => {
129- const isExternal = href ?. startsWith ( "http" ) ;
130- return (
131- < a
132- href = { href }
133- target = { isExternal ? "_blank" : undefined }
134- rel = { isExternal ? "noopener noreferrer" : undefined }
135- { ...props }
136- >
137- { children }
138- </ a >
139- ) ;
140- } ,
141- // Process text nodes to find and wrap @mentions
142- p : ( { children, ...props } ) => {
143- return < p { ...props } > { processChildren ( children ) } </ p > ;
144- } ,
145- li : ( { children, ...props } ) => {
146- return < li { ...props } > { processChildren ( children ) } </ li > ;
147- } ,
148- td : ( { children, ...props } ) => {
149- return < td { ...props } > { processChildren ( children ) } </ td > ;
150- } ,
151- th : ( { children, ...props } ) => {
152- return < th { ...props } > { processChildren ( children ) } </ th > ;
153- } ,
154- } }
155- >
156- { children }
157- </ ReactMarkdown >
158- </ div >
182+ </ >
159183 ) ;
160184} ) ;
161185
0 commit comments