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",
|
"@fontsource/hanken-grotesk": "^5.2.8",
|
||||||
"framer-motion": "^12.40.0",
|
"framer-motion": "^12.40.0",
|
||||||
"react": "^19.2.6",
|
"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": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^10.0.1",
|
"@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