Skip to content

Commit 3d08c08

Browse files
committed
perf: 优化 markdown 文档渲染效果
1 parent d5623dd commit 3d08c08

9 files changed

Lines changed: 219 additions & 576 deletions

File tree

node_modules/.yarn-integrity

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

website/package-lock.json

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

website/src/components/ExperienceDetailApp.tsx

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import rehypeHighlight from 'rehype-highlight';
1010
import rehypeSlug from 'rehype-slug';
1111
import GithubSlugger from 'github-slugger';
1212
import 'highlight.js/styles/github-dark.css';
13+
import { parseFrontmatter } from '../lib/markdown';
1314

1415
type DownloadFile = {
1516
path: string;
@@ -56,6 +57,36 @@ function extractHeadings(markdown: string): Heading[] {
5657
return headings;
5758
}
5859

60+
function ExperienceMarkdown({ content }: { content: string }) {
61+
const { meta, body } = parseFrontmatter(content);
62+
return (
63+
<div className="space-y-4">
64+
{meta && (
65+
<div className="rounded-lg border bg-muted/30 px-4 py-3">
66+
<dl className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-1.5 text-sm">
67+
{Object.entries(meta).map(([key, val]) => (
68+
<React.Fragment key={key}>
69+
<dt className="text-muted-foreground font-medium">{key}</dt>
70+
<dd className="text-foreground break-words">{val}</dd>
71+
</React.Fragment>
72+
))}
73+
</dl>
74+
</div>
75+
)}
76+
{body.trim() && (
77+
<div className="prose dark:prose-invert max-w-none prose-headings:font-semibold prose-headings:scroll-mt-20 prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-img:rounded-lg prose-code:before:content-none prose-code:after:content-none">
78+
<ReactMarkdown
79+
remarkPlugins={[remarkGfm]}
80+
rehypePlugins={[rehypeHighlight, rehypeSlug]}
81+
>
82+
{body}
83+
</ReactMarkdown>
84+
</div>
85+
)}
86+
</div>
87+
);
88+
}
89+
5990
// In a real app, this data would come from the astro page props (fetched from getCollection)
6091
export function ExperienceDetailApp({ experienceId, experienceData, lang = 'zh' }: { experienceId: string, experienceData: ExperienceDetailData, lang?: 'zh' | 'en' }) {
6192
const [currentLang, setCurrentLang] = useState(lang);
@@ -207,16 +238,9 @@ export function ExperienceDetailApp({ experienceId, experienceData, lang = 'zh'
207238
<div className="flex-1 w-full order-2 lg:order-1 min-w-0">
208239
<div className="bg-card rounded-xl border shadow-sm p-6 md:p-10">
209240
{(activeFile.endsWith('.md') || activeFile.endsWith('.mdx')) ? (
210-
<div className="prose prose-slate dark:prose-invert max-w-none prose-headings:font-semibold prose-headings:scroll-mt-20 prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-img:rounded-lg">
211-
<ReactMarkdown
212-
remarkPlugins={[remarkGfm]}
213-
rehypePlugins={[rehypeHighlight, rehypeSlug]}
214-
>
215-
{currentFileContent}
216-
</ReactMarkdown>
217-
</div>
241+
<ExperienceMarkdown content={currentFileContent} />
218242
) : (
219-
<pre className="overflow-x-auto p-4 bg-muted/50 rounded-lg text-sm font-mono whitespace-pre-wrap">
243+
<pre className="overflow-x-auto p-4 bg-muted/50 rounded-lg text-[0.875rem] leading-relaxed font-mono whitespace-pre-wrap">
220244
<code>{currentFileContent}</code>
221245
</pre>
222246
)}

website/src/components/FileViewer.tsx

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import rehypeHighlight from 'rehype-highlight';
66
import 'highlight.js/styles/github-dark.css';
77
import { FileCode, Check, Copy } from 'lucide-react';
88
import { cn } from '../lib/utils';
9+
import { parseFrontmatter } from '../lib/markdown';
910

1011
export type DownloadFile = {
1112
path: string;
@@ -69,6 +70,33 @@ function isMarkdown(path: string) {
6970
return /\.(md|mdx)$/i.test(path);
7071
}
7172

73+
function MarkdownWithMeta({ content }: { content: string }) {
74+
const { meta, body } = parseFrontmatter(content);
75+
return (
76+
<div className="space-y-4">
77+
{meta && (
78+
<div className="rounded-lg border bg-muted/30 px-4 py-3">
79+
<dl className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-1.5 text-sm">
80+
{Object.entries(meta).map(([key, val]) => (
81+
<React.Fragment key={key}>
82+
<dt className="text-muted-foreground font-medium">{key}</dt>
83+
<dd className="text-foreground break-words">{val}</dd>
84+
</React.Fragment>
85+
))}
86+
</dl>
87+
</div>
88+
)}
89+
{body.trim() && (
90+
<div className="prose dark:prose-invert max-w-none prose-headings:font-semibold prose-a:text-primary prose-code:before:content-none prose-code:after:content-none">
91+
<ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeHighlight]}>
92+
{body}
93+
</ReactMarkdown>
94+
</div>
95+
)}
96+
</div>
97+
);
98+
}
99+
72100
export function FileViewer({ files = [], emptyLabel }: FileViewerProps) {
73101
const orderedFiles = React.useMemo(
74102
() => sortFiles(files.filter((file) => shouldPreviewFile(file.path))),
@@ -140,11 +168,7 @@ export function FileViewer({ files = [], emptyLabel }: FileViewerProps) {
140168

141169
<div className="p-6 relative group">
142170
{isMarkdown(activeFile.path) ? (
143-
<div className="prose prose-sm dark:prose-invert max-w-none prose-headings:font-bold prose-a:text-primary prose-pre:p-0 prose-pre:bg-transparent">
144-
<ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeHighlight]}>
145-
{activeFile.content}
146-
</ReactMarkdown>
147-
</div>
171+
<MarkdownWithMeta content={activeFile.content} />
148172
) : (
149173
<>
150174
<button
@@ -157,7 +181,7 @@ export function FileViewer({ files = [], emptyLabel }: FileViewerProps) {
157181
>
158182
{copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
159183
</button>
160-
<pre className="overflow-x-auto rounded-lg bg-[#0d1117] p-4 text-sm leading-relaxed text-slate-100">
184+
<pre className="overflow-x-auto rounded-lg bg-[#0d1117] p-4 text-[0.875rem] leading-relaxed text-slate-100">
161185
<code ref={codeRef} className={`language-${language}`}>
162186
{activeFile.content}
163187
</code>

website/src/layouts/Layout.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ const faviconHref = `${import.meta.env.BASE_URL}favicon.svg`;
5151
}
5252
</script>
5353
</head>
54-
<body class="min-h-screen bg-background font-sans antialiased">
54+
<body class="min-h-screen bg-background">
5555
<div class="relative flex min-h-screen flex-col">
5656
<slot />
5757
</div>

website/src/lib/markdown.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)([\s\S]*)$/;
2+
3+
export function parseFrontmatter(content: string): { meta: Record<string, string> | null; body: string } {
4+
const match = content.match(FRONTMATTER_RE);
5+
if (!match) return { meta: null, body: content };
6+
7+
const raw = match[1];
8+
const body = match[2];
9+
const meta: Record<string, string> = {};
10+
11+
for (const line of raw.split('\n')) {
12+
const idx = line.indexOf(':');
13+
if (idx > 0) {
14+
const key = line.slice(0, idx).trim();
15+
const val = line.slice(idx + 1).trim().replace(/^["']|["']$/g, '');
16+
if (key && val) meta[key] = val;
17+
}
18+
}
19+
20+
return { meta: Object.keys(meta).length > 0 ? meta : null, body };
21+
}

website/tailwind.config.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,62 @@ export default {
1111
},
1212
},
1313
extend: {
14+
typography: {
15+
DEFAULT: {
16+
css: {
17+
'--tw-prose-body': 'hsl(var(--foreground))',
18+
'--tw-prose-headings': 'hsl(var(--foreground))',
19+
'--tw-prose-bold': 'hsl(var(--foreground))',
20+
'--tw-prose-code': 'hsl(var(--foreground))',
21+
'--tw-prose-pre-code': 'hsl(var(--foreground))',
22+
'--tw-prose-pre-bg': 'hsl(var(--muted))',
23+
fontFamily: 'inherit',
24+
fontSize: '1rem',
25+
lineHeight: '1.75',
26+
h1: {
27+
fontSize: '1.375em',
28+
},
29+
h2: {
30+
fontSize: '1.2em',
31+
},
32+
h3: {
33+
fontSize: '1.1em',
34+
},
35+
h4: {
36+
fontSize: '1em',
37+
},
38+
strong: {
39+
fontWeight: '600',
40+
},
41+
code: {
42+
fontSize: '0.9375em',
43+
fontWeight: '500',
44+
},
45+
'pre code': {
46+
fontSize: '0.875rem',
47+
},
48+
pre: {
49+
fontSize: '0.875rem',
50+
lineHeight: '1.7',
51+
overflowX: 'auto',
52+
},
53+
blockquote: {
54+
fontSize: 'inherit',
55+
fontWeight: 'inherit',
56+
},
57+
table: {
58+
fontSize: 'inherit',
59+
},
60+
'thead th': {
61+
fontSize: 'inherit',
62+
fontWeight: '600',
63+
},
64+
'tbody td': {
65+
fontSize: 'inherit',
66+
},
67+
},
68+
},
69+
},
1470
colors: {
1571
border: "hsl(var(--border))",
1672
input: "hsl(var(--input))",

0 commit comments

Comments
 (0)