1- import { useEffect , useMemo , useCallback , useState } from 'react' ;
1+ import { useEffect , useMemo , useCallback } from 'react' ;
22import React , { Children } from 'react' ;
33import ReactMarkdown , { Components } from 'react-markdown' ;
44import remarkGfm from 'remark-gfm' ;
@@ -164,37 +164,37 @@ const processMarkdownContent = (content: string): string => {
164164 '<del class="line-through text-gray-500">$1</del>' ,
165165 ) ;
166166
167- // Collapsible sections - ensure proper block separation
167+ // Collapsible sections
168168 processed = processed . replace (
169169 / : : : d e t a i l s \s + ( .* ?) \n ( [ \s \S ] * ?) : : : / gim,
170- '\n\n <details class="my-4 border border-gray-200 rounded-lg overflow-hidden bg-white">' +
171- '<summary class="bg-gray-50 px-4 py-3 cursor-pointer font-medium text-gray-800 hover:bg-gray-100 transition-colors border-b border-gray-200">$1</summary>' +
172- '<div class="px-4 py-3 text-gray-700">$2</div></details>\n\n ' ,
170+ '<details class="my-4 border border-gray-200 rounded-lg overflow-hidden bg-white">' +
171+ '<summary class="bg-gray-50 px-4 py-3 cursor-pointer font-medium text-gray-800 hover:bg-gray-100 transition-colors border-b border-gray-200">$1</summary>' +
172+ '<div class="px-4 py-3 text-gray-700">$2</div></details>' ,
173173 ) ;
174174
175- // GitHub-style alerts - ensure proper block separation
175+ // GitHub-style alerts
176176 processed = processed . replace (
177177 / : : : ( \w + ) \s * ( .* ?) \n ( [ \s \S ] * ?) : : : / gim,
178178 ( _ , type , title , content ) => {
179179 const alert =
180180 ALERT_TYPES [ type as keyof typeof ALERT_TYPES ] || ALERT_TYPES . note ;
181- return `\n\n <div class="my-4 p-4 border-l-4 ${ alert . border } ${ alert . bg } rounded-r-lg">
181+ return `<div class="my-4 p-4 border-l-4 ${ alert . border } ${ alert . bg } rounded-r-lg">
182182 <div class="flex items-center mb-2">
183183 <span class="mr-2 text-lg">${ alert . icon } </span>
184184 <strong class="${ alert . text } font-semibold uppercase text-sm tracking-wide">${ type } ${ title ? `: ${ title } ` : '' } </strong>
185185 </div>
186186 <div class="${ alert . text } ">${ content } </div>
187- </div>\n\n ` ;
187+ </div>` ;
188188 } ,
189189 ) ;
190190
191- // YouTube embeds - ensure proper block separation
191+ // YouTube embeds
192192 processed = processed . replace (
193193 / \[ y o u t u b e : \s * ( [ \w - ] + ) \] / gim,
194194 ( _ , videoId ) =>
195- `\n\n <div class="my-8 mx-auto max-w-4xl"><div class="relative rounded-xl shadow-lg overflow-hidden bg-black" style="aspect-ratio: 16/9;">
195+ `<div class="my-8 mx-auto max-w-4xl"><div class="relative rounded-xl shadow-lg overflow-hidden bg-black" style="aspect-ratio: 16/9;">
196196 <iframe src="https://www.youtube.com/embed/${ videoId } ?autoplay=0&rel=0&modestbranding=1" class="absolute inset-0 w-full h-full border-0"
197- allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen loading="lazy" title="YouTube video player"></iframe></div></div>\n\n ` ,
197+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen loading="lazy" title="YouTube video player"></iframe></div></div>` ,
198198 ) ;
199199
200200 return processed ;
@@ -205,11 +205,6 @@ const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
205205 setZoomableImages,
206206 frontmatter,
207207} ) => {
208- const [ isClient , setIsClient ] = useState ( false ) ;
209-
210- useEffect ( ( ) => {
211- setIsClient ( true ) ;
212- } , [ ] ) ;
213208
214209 const processedContent = useMemo (
215210 ( ) => processMarkdownContent ( content ) ,
@@ -327,19 +322,19 @@ const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
327322
328323 const createHeading =
329324 ( level : keyof typeof headingClasses ) =>
330- ( {
331- children,
332- ...props
333- } : React . HTMLAttributes < HTMLHeadingElement > & {
334- children ?: React . ReactNode ;
335- } ) => {
336- const Tag = level ;
337- return (
338- < Tag { ...props } className = { headingClasses [ level ] } >
339- { children }
340- </ Tag >
341- ) ;
342- } ;
325+ ( {
326+ children,
327+ ...props
328+ } : React . HTMLAttributes < HTMLHeadingElement > & {
329+ children ?: React . ReactNode ;
330+ } ) => {
331+ const Tag = level ;
332+ return (
333+ < Tag { ...props } className = { headingClasses [ level ] } >
334+ { children }
335+ </ Tag >
336+ ) ;
337+ } ;
343338
344339 const components : Components = {
345340 h1 : createHeading ( 'h1' ) ,
@@ -349,73 +344,14 @@ const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
349344 h5 : createHeading ( 'h5' ) ,
350345 h6 : createHeading ( 'h6' ) ,
351346
352- p : ( { children, ...props } ) => {
353- // Convert children to array and check for problematic content
354- const childArray = React . Children . toArray ( children ) ;
355-
356- // Check if any child contains HTML that would create block elements or is an image
357- const hasProblematicContent = childArray . some ( ( child ) => {
358- if ( typeof child === 'string' ) {
359- // Check for HTML tags that create block elements
360- return / < ( d i v | f i g u r e | b l o c k q u o t e | p r e | t a b l e | u l | o l | d e t a i l s | i f r a m e | h [ 1 - 6 ] | i m g ) / i. test (
361- child ,
362- ) ;
363- }
364- if ( React . isValidElement ( child ) ) {
365- const type = child . type ;
366- // Check for React components that render as block elements or images
367- return (
368- typeof type === 'string' &&
369- [
370- 'div' ,
371- 'figure' ,
372- 'blockquote' ,
373- 'pre' ,
374- 'table' ,
375- 'ul' ,
376- 'ol' ,
377- 'details' ,
378- 'iframe' ,
379- 'img' ,
380- 'span' ,
381- ] . includes ( type )
382- ) ;
383- }
384- return false ;
385- } ) ;
386-
387- // Check if this paragraph only contains an image
388- const isImageOnly =
389- childArray . length === 1 &&
390- React . isValidElement ( childArray [ 0 ] ) &&
391- ( childArray [ 0 ] . type === 'img' ||
392- ( typeof childArray [ 0 ] . type === 'function' &&
393- childArray [ 0 ] . props &&
394- typeof childArray [ 0 ] . props === 'object' &&
395- 'src' in childArray [ 0 ] . props ) ) ;
396-
397- // If contains problematic content or is image-only, render as div
398- if ( hasProblematicContent || isImageOnly ) {
399- return (
400- < div
401- { ...props }
402- className = "my-4 text-gray-700 dark:text-gray-300 leading-relaxed"
403- >
404- { children }
405- </ div >
406- ) ;
407- }
408-
409- // Safe to render as paragraph
410- return (
411- < p
412- { ...props }
413- className = "my-4 text-gray-700 dark:text-gray-300 leading-relaxed"
414- >
415- { children }
416- </ p >
417- ) ;
418- } ,
347+ p : ( { children, ...props } ) => (
348+ < p
349+ { ...props }
350+ className = "my-4 text-gray-700 dark:text-gray-300 leading-relaxed"
351+ >
352+ { children }
353+ </ p >
354+ ) ,
419355
420356 blockquote : ( { children, ...props } ) => (
421357 < blockquote
@@ -511,30 +447,28 @@ const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
511447 const imageSrc =
512448 src === '' && frontmatter ?. image ? String ( frontmatter . image ) : src ;
513449 return (
514- < span className = "block my-6" >
515- < span className = "flex flex-col items-center" >
516- < span className = "overflow-hidden rounded-lg shadow-md hover:shadow-lg transition-all duration-300 border border-gray-200 inline-block" >
517- < img
518- { ...props }
519- src = { imageSrc }
520- alt = { alt }
521- title = { title || alt || '' }
522- className = "max-w-full h-auto rounded-lg object-contain hover:scale-105 transition-transform duration-300"
523- data-zoomable = "true"
524- loading = "lazy"
525- onError = { ( e ) => {
526- ( e . target as HTMLImageElement ) . src =
527- '/assets/Images/SugarNewsLogo.webp' ;
528- } }
529- />
530- </ span >
531- { ( title || alt ) && (
532- < span className = "text-center text-sm text-gray-600 mt-3 italic block" >
533- { title || alt }
534- </ span >
535- ) }
536- </ span >
537- </ span >
450+ < figure className = "flex flex-col items-center my-6" >
451+ < div className = "overflow-hidden rounded-lg shadow-md hover:shadow-lg transition-all duration-300 border border-gray-200" >
452+ < img
453+ { ...props }
454+ src = { imageSrc }
455+ alt = { alt }
456+ title = { title || alt || '' }
457+ className = "max-w-full h-auto rounded-lg object-contain hover:scale-105 transition-transform duration-300"
458+ data-zoomable = "true"
459+ loading = "lazy"
460+ onError = { ( e ) => {
461+ ( e . target as HTMLImageElement ) . src =
462+ '/assets/Images/SugarNewsLogo.webp' ;
463+ } }
464+ />
465+ </ div >
466+ { ( title || alt ) && (
467+ < figcaption className = "text-center text-sm text-gray-600 mt-3 italic" >
468+ { title || alt }
469+ </ figcaption >
470+ ) }
471+ </ figure >
538472 ) ;
539473 } ,
540474
@@ -693,19 +627,6 @@ const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
693627 ) ,
694628 } ;
695629
696- // Prevent hydration mismatch by only rendering on client
697- if ( ! isClient ) {
698- return (
699- < div className = "prose prose-lg dark:prose-invert prose-headings:dark:text-gray-100 prose-p:dark:text-gray-300 prose-strong:dark:text-gray-100 prose-em:dark:text-gray-300 prose-li:dark:text-gray-300 max-w-none" >
700- < div className = "animate-pulse" >
701- < div className = "h-4 bg-gray-200 rounded w-3/4 mb-4" > </ div >
702- < div className = "h-4 bg-gray-200 rounded w-1/2 mb-4" > </ div >
703- < div className = "h-4 bg-gray-200 rounded w-5/6 mb-4" > </ div >
704- </ div >
705- </ div >
706- ) ;
707- }
708-
709630 return (
710631 < div className = "prose prose-lg dark:prose-invert prose-headings:dark:text-gray-100 prose-p:dark:text-gray-300 prose-strong:dark:text-gray-100 prose-em:dark:text-gray-300 prose-li:dark:text-gray-300 max-w-none" >
711632 < ReactMarkdown
0 commit comments