feat(blog): Markdown 渲染组件(react-markdown + 暖色编辑风)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
d25c65ef81
commit
f89345b6fa
1476
Client/package-lock.json
generated
1476
Client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -14,7 +14,9 @@
|
||||
"@fontsource/hanken-grotesk": "^5.2.8",
|
||||
"framer-motion": "^12.40.0",
|
||||
"react": "^19.2.6",
|
||||
"react-dom": "^19.2.6"
|
||||
"react-dom": "^19.2.6",
|
||||
"react-markdown": "^10.1.0",
|
||||
"remark-gfm": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
|
||||
67
Client/src/components/Markdown.tsx
Normal file
67
Client/src/components/Markdown.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
import ReactMarkdown, { type Components } from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
|
||||
// 把 Markdown 渲染套上站点暖色编辑风。只渲染自有可信内容;
|
||||
// react-markdown 默认不解析原始 HTML(未引 rehype-raw),天然防注入。
|
||||
const components: Components = {
|
||||
h1: ({ children }) => (
|
||||
<h1 className="mt-8 mb-4 font-display text-3xl font-medium text-text">{children}</h1>
|
||||
),
|
||||
h2: ({ children }) => (
|
||||
<h2 className="mt-7 mb-3 font-display text-2xl font-medium text-text">{children}</h2>
|
||||
),
|
||||
h3: ({ children }) => (
|
||||
<h3 className="mt-6 mb-2 font-display text-xl font-medium text-text">{children}</h3>
|
||||
),
|
||||
p: ({ children }) => <p className="my-4 leading-relaxed text-muted">{children}</p>,
|
||||
a: ({ href, children }) => (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-accent underline-offset-4 hover:underline"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
ul: ({ children }) => (
|
||||
<ul className="my-4 list-disc pl-6 text-muted marker:text-accent/60">{children}</ul>
|
||||
),
|
||||
ol: ({ children }) => (
|
||||
<ol className="my-4 list-decimal pl-6 text-muted marker:text-accent/60">{children}</ol>
|
||||
),
|
||||
li: ({ children }) => <li className="my-1 leading-relaxed">{children}</li>,
|
||||
blockquote: ({ children }) => (
|
||||
<blockquote className="my-4 border-l-2 border-accent pl-4 italic text-muted/90">
|
||||
{children}
|
||||
</blockquote>
|
||||
),
|
||||
pre: ({ children }) => <pre className="my-4">{children}</pre>,
|
||||
code: ({ className, children }) => {
|
||||
const isBlock = /language-/.test(className ?? '')
|
||||
return isBlock ? (
|
||||
<code className="block overflow-x-auto rounded-lg border border-line bg-surface px-4 py-3 font-mono text-sm text-text">
|
||||
{children}
|
||||
</code>
|
||||
) : (
|
||||
<code className="rounded bg-surface px-1.5 py-0.5 font-mono text-[0.85em] text-accent-soft">
|
||||
{children}
|
||||
</code>
|
||||
)
|
||||
},
|
||||
img: ({ src, alt }) => (
|
||||
<img src={typeof src === 'string' ? src : ''} alt={alt ?? ''} className="my-4 max-w-full rounded-lg" />
|
||||
),
|
||||
hr: () => <hr className="my-8 border-line" />,
|
||||
}
|
||||
|
||||
export function Markdown({ children }: { children: string }) {
|
||||
// data-selectable:全站默认禁选,正文区放开以便复制。
|
||||
return (
|
||||
<div data-selectable>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} components={components}>
|
||||
{children}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user