@@ -71,11 +71,12 @@ const TextPreview = React.memo(function TextPreview({
7171 file,
7272} : TextPreviewProps ) : React . ReactElement {
7373 const isMarkdown = isMarkdownPreview ( file )
74+ const fileIdentity = `${ file . name } :${ file . extension } `
7475 const [ mode , setMode ] = useState < TextPreviewMode > ( 'code' )
7576
7677 useEffect ( ( ) => {
7778 setMode ( 'code' )
78- } , [ file . name , file . extension ] )
79+ } , [ fileIdentity ] )
7980
8081 const handleModeChange = ( next : string ) : void => {
8182 // Radix emits an empty string when the active item is clicked again.
@@ -249,8 +250,11 @@ const MarkdownReadingPreview = React.memo(function MarkdownReadingPreview({
249250 )
250251
251252 return (
252- < div className = "flex-1 min-h-0 overflow-auto bg-background" >
253- < article className = "max-w-none px-7 py-6 pb-10 text-sm leading-7 text-foreground" >
253+ < div
254+ className = "flex-1 min-h-0 overflow-y-auto overflow-x-hidden bg-background"
255+ data-markdown-reading-scroll
256+ >
257+ < article className = "min-w-0 max-w-full break-words px-7 py-6 pb-10 text-sm leading-7 text-foreground" >
254258 < ReactMarkdown
255259 remarkPlugins = { [ remarkGfm ] }
256260 components = { markdownComponents }
@@ -298,11 +302,49 @@ function looksLikeYamlFrontmatter(rawBlock: string): boolean {
298302 } )
299303}
300304
305+ /**
306+ * Remove react-markdown's internal AST prop before DOM spreading.
307+ * @param props - Component override props from react-markdown.
308+ * @returns The same props without the non-DOM `node` field.
309+ * @example
310+ * omitMarkdownNode({ node: astNode, href: '/docs' }) // => { href: '/docs' }
311+ */
312+ function omitMarkdownNode < Props extends { node ?: unknown } > (
313+ props : Props ,
314+ ) : Omit < Props , 'node' > {
315+ const { node, ...domProps } = props
316+ // `node` is useful for custom renderers, but invalid on real DOM elements.
317+ void node
318+ return domProps
319+ }
320+
321+ /**
322+ * Detect code blocks produced by react-markdown.
323+ * @param children - Rendered code text from the Markdown AST.
324+ * @param className - Optional `language-*` class from a fenced code info string.
325+ * @returns True for fenced/indented code blocks, including fences with no language.
326+ * @example
327+ * isMarkdownCodeBlock('pnpm validate\n') // => true
328+ */
329+ function isMarkdownCodeBlock (
330+ children : React . ReactNode ,
331+ className ?: string ,
332+ ) : boolean {
333+ // Language-tagged fences are always block code.
334+ if ( className ?. includes ( 'language-' ) ) return true
335+
336+ // Guard the react-markdown v10 newline heuristic with browser tests: block
337+ // code keeps a trailing newline, while inline code does not.
338+ return typeof children === 'string' && children . endsWith ( '\n' )
339+ }
340+
301341const markdownComponents : Components = {
302342 a ( { children, className, href, ...props } ) {
343+ const domProps = omitMarkdownNode ( props )
344+
303345 return (
304346 < a
305- { ...props }
347+ { ...domProps }
306348 href = { href }
307349 target = "_blank"
308350 rel = "noreferrer"
@@ -316,9 +358,11 @@ const markdownComponents: Components = {
316358 )
317359 } ,
318360 blockquote ( { children, className, ...props } ) {
361+ const domProps = omitMarkdownNode ( props )
362+
319363 return (
320364 < blockquote
321- { ...props }
365+ { ...domProps }
322366 className = { cn (
323367 'my-4 border-l-2 border-primary/60 pl-4 text-muted-foreground' ,
324368 className ,
@@ -329,11 +373,12 @@ const markdownComponents: Components = {
329373 )
330374 } ,
331375 code ( { children, className, ...props } ) {
332- const isBlock = className ?. includes ( 'language-' ) ?? false
333- if ( isBlock ) {
376+ const domProps = omitMarkdownNode ( props )
377+
378+ if ( isMarkdownCodeBlock ( children , className ) ) {
334379 return (
335380 < code
336- { ...props }
381+ { ...domProps }
337382 className = { cn (
338383 'block overflow-x-auto rounded-md border border-border bg-muted px-3 py-2 font-mono text-xs leading-6 text-foreground' ,
339384 className ,
@@ -346,7 +391,7 @@ const markdownComponents: Components = {
346391
347392 return (
348393 < code
349- { ...props }
394+ { ...domProps }
350395 className = { cn (
351396 'rounded bg-muted px-1.5 py-0.5 font-mono text-[0.85em] text-foreground' ,
352397 className ,
@@ -357,9 +402,11 @@ const markdownComponents: Components = {
357402 )
358403 } ,
359404 h1 ( { children, className, ...props } ) {
405+ const domProps = omitMarkdownNode ( props )
406+
360407 return (
361408 < h1
362- { ...props }
409+ { ...domProps }
363410 className = { cn (
364411 'mb-4 mt-0 border-b border-border pb-3 text-2xl font-semibold leading-tight' ,
365412 className ,
@@ -370,9 +417,11 @@ const markdownComponents: Components = {
370417 )
371418 } ,
372419 h2 ( { children, className, ...props } ) {
420+ const domProps = omitMarkdownNode ( props )
421+
373422 return (
374423 < h2
375- { ...props }
424+ { ...domProps }
376425 className = { cn (
377426 'mb-3 mt-7 border-b border-border/70 pb-2 text-xl font-semibold leading-tight' ,
378427 className ,
@@ -383,48 +432,60 @@ const markdownComponents: Components = {
383432 )
384433 } ,
385434 h3 ( { children, className, ...props } ) {
435+ const domProps = omitMarkdownNode ( props )
436+
386437 return (
387438 < h3
388- { ...props }
439+ { ...domProps }
389440 className = { cn ( 'mb-2 mt-6 text-base font-semibold' , className ) }
390441 >
391442 { children }
392443 </ h3 >
393444 )
394445 } ,
395446 li ( { children, className, ...props } ) {
447+ const domProps = omitMarkdownNode ( props )
448+
396449 return (
397- < li { ...props } className = { cn ( 'my-1 pl-1' , className ) } >
450+ < li { ...domProps } className = { cn ( 'my-1 pl-1' , className ) } >
398451 { children }
399452 </ li >
400453 )
401454 } ,
402455 ol ( { children, className, ...props } ) {
456+ const domProps = omitMarkdownNode ( props )
457+
403458 return (
404- < ol { ...props } className = { cn ( 'my-4 list-decimal pl-6' , className ) } >
459+ < ol { ...domProps } className = { cn ( 'my-4 list-decimal pl-6' , className ) } >
405460 { children }
406461 </ ol >
407462 )
408463 } ,
409464 p ( { children, className, ...props } ) {
465+ const domProps = omitMarkdownNode ( props )
466+
410467 return (
411- < p { ...props } className = { cn ( 'my-3' , className ) } >
468+ < p { ...domProps } className = { cn ( 'my-3' , className ) } >
412469 { children }
413470 </ p >
414471 )
415472 } ,
416473 pre ( { children, className, ...props } ) {
474+ const domProps = omitMarkdownNode ( props )
475+
417476 return (
418- < pre { ...props } className = { cn ( 'my-4' , className ) } >
477+ < pre { ...domProps } className = { cn ( 'my-4' , className ) } >
419478 { children }
420479 </ pre >
421480 )
422481 } ,
423482 table ( { children, className, ...props } ) {
483+ const domProps = omitMarkdownNode ( props )
484+
424485 return (
425486 < div className = "my-4 overflow-x-auto rounded-md border border-border" >
426487 < table
427- { ...props }
488+ { ...domProps }
428489 className = { cn ( 'w-full border-collapse text-left text-sm' , className ) }
429490 >
430491 { children }
@@ -433,19 +494,23 @@ const markdownComponents: Components = {
433494 )
434495 } ,
435496 td ( { children, className, ...props } ) {
497+ const domProps = omitMarkdownNode ( props )
498+
436499 return (
437500 < td
438- { ...props }
501+ { ...domProps }
439502 className = { cn ( 'border-t border-border px-3 py-2 align-top' , className ) }
440503 >
441504 { children }
442505 </ td >
443506 )
444507 } ,
445508 th ( { children, className, ...props } ) {
509+ const domProps = omitMarkdownNode ( props )
510+
446511 return (
447512 < th
448- { ...props }
513+ { ...domProps }
449514 className = { cn (
450515 'border-b border-border bg-muted px-3 py-2 font-semibold' ,
451516 className ,
@@ -456,8 +521,10 @@ const markdownComponents: Components = {
456521 )
457522 } ,
458523 ul ( { children, className, ...props } ) {
524+ const domProps = omitMarkdownNode ( props )
525+
459526 return (
460- < ul { ...props } className = { cn ( 'my-4 list-disc pl-6' , className ) } >
527+ < ul { ...domProps } className = { cn ( 'my-4 list-disc pl-6' , className ) } >
461528 { children }
462529 </ ul >
463530 )
0 commit comments