From a8a847beb235f31e532a04f9e108fd631d3daf72 Mon Sep 17 00:00:00 2001 From: Caihaohan Date: Sun, 28 Dec 2025 10:32:49 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20Markdown=20?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=9D=97=E6=B8=B2=E6=9F=93=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E7=9A=84=20HTML=20hydration=20=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除客户端状态管理,改为服务端渲染 - 添加 pre 组件处理代码块的包裹样式 - 修改 code 组件只返回 元素,避免重复渲染 - 添加 p 组件块级元素检测,防止

 嵌套
- 使用 className 和换行符检测区分 inline code 和代码块

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude 
---
 src/components/project/MarkdownContent.tsx | 98 +++++++++++-----------
 1 file changed, 51 insertions(+), 47 deletions(-)

diff --git a/src/components/project/MarkdownContent.tsx b/src/components/project/MarkdownContent.tsx
index 2f55ea6..9ca261a 100644
--- a/src/components/project/MarkdownContent.tsx
+++ b/src/components/project/MarkdownContent.tsx
@@ -1,10 +1,8 @@
-'use client'
-
+import React from 'react'
 import ReactMarkdown from 'react-markdown'
 import remarkGfm from 'remark-gfm'
 import rehypeRaw from 'rehype-raw'
 import rehypeSanitize from 'rehype-sanitize'
-import { useState, useEffect } from 'react'
 import type { Components } from 'react-markdown'
 
 interface MarkdownContentProps {
@@ -25,6 +23,14 @@ function generateHeadingId(text: string): string {
     .replace(/-+$/, '')             // Trim - from end
 }
 
+// Helper function to escape HTML
+function escapeHtml(code: string): string {
+  return code
+    .replace(/&/g, '&')
+    .replace(//g, '>')
+}
+
 // Custom GitHub-style components
 const components: Components = {
   // Headings with anchor links
@@ -81,12 +87,26 @@ const components: Components = {
     
   ),
 
-  // Paragraphs
-  p: ({ children, ...props }) => (
-    

- {children} -

- ), + // Paragraphs - skip wrapping if contains block-level elements like pre + p: ({ children, node, ...props }: any) => { + // Check if the paragraph node contains block-level elements in its children + // This uses the AST node data from react-markdown + const hasBlockElement = node?.children?.some((child: any) => { + const tagName = child?.tagName + // Check if any direct child is a block-level element + return ['pre', 'table', 'blockquote', 'ul', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'img', 'hr'].includes(tagName) + }) + + if (hasBlockElement) { + return <>{children} + } + + return ( +

+ {children} +

+ ) + }, // Lists ul: ({ children, className, ...props }) => ( @@ -111,9 +131,24 @@ const components: Components = { ), - // Code blocks with syntax highlighting + // Code blocks (pre element wrapper) + pre: ({ children, ...props }: any) => { + return ( +
+        {children}
+      
+ ) + }, + + // Code elements (both inline and in code blocks) code: ({ inline, className, children, ...props }: any) => { - if (inline) { + // If inline is explicitly true, or if there's no language class and no newlines, treat as inline + const hasLanguageClass = className && typeof className === 'string' && className.startsWith('language-') + const childStr = String(children) + const hasNewlines = childStr.includes('\n') + const isInline = inline === true || (!hasLanguageClass && !hasNewlines) + + if (isInline) { return ( + + {children} + ) }, @@ -202,44 +238,12 @@ const components: Components = { checked={checked} readOnly className="mr-2 h-4 w-4 rounded border-gray-300" + name="task-item" {...props} /> ) : null, } -// Syntax highlighter wrapper (will use Shiki for server-side, Prism for client) -function SyntaxHighlighterWrapper({ language, code }: { language: string; code: string }) { - const [highlighted, setHighlighted] = useState(null) - - useEffect(() => { - // For now, use simple highlighting with proper escaping - // In production, you'd want to use Shiki or Prism for better highlighting - const escaped = code - .replace(/&/g, '&') - .replace(//g, '>') - - setHighlighted(escaped) - }, [code]) - - if (!highlighted) { - return ( -
-        {code}
-      
- ) - } - - return ( -
-      
-    
- ) -} - export function MarkdownContent({ content, className = '' }: MarkdownContentProps) { if (!content || content.trim() === '') {