Skip to content

Commit 7a7a5b5

Browse files
committed
build: fix build error
1 parent deb9388 commit 7a7a5b5

9 files changed

Lines changed: 2142 additions & 770 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 7 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,34 +25,23 @@ jobs:
2525
with:
2626
fetch-depth: 0
2727

28-
- name: 🔧 Setup Node.js
29-
uses: actions/setup-node@v4
30-
with:
31-
node-version: "24"
32-
3328
- name: 📦 Setup pnpm
3429
uses: pnpm/action-setup@v4
3530
with:
3631
version: latest
3732
run_install: false
3833

39-
- name: 📦 Get pnpm store directory
40-
shell: bash
41-
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
42-
43-
- name: 📦 Setup pnpm cache
44-
uses: actions/cache@v4
34+
- name: 🔧 Setup Node.js
35+
uses: actions/setup-node@v4
4536
with:
46-
path: ${{ env.STORE_PATH }}
47-
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
48-
restore-keys: |
49-
${{ runner.os }}-pnpm-store-
37+
node-version: 22
38+
cache: pnpm
5039

5140
- name: 📦 Install dependencies
52-
run: pnpm install --no-frozen-lockfile
41+
run: pnpm install --frozen-lockfile
5342

5443
- name: 🏗️ Build VitePress
55-
run: NODE_OPTIONS=--max-old-space-size=8192 pnpm docs:build
44+
run: pnpm docs:build
5645

5746
- name: 📤 Upload artifact
5847
uses: actions/upload-pages-artifact@v3
@@ -62,21 +51,10 @@ jobs:
6251
deploy:
6352
needs: build
6453
runs-on: ubuntu-latest
65-
6654
environment:
6755
name: github-pages
6856
url: ${{ steps.deployment.outputs.page_url }}
69-
7057
steps:
71-
- name: 🚀 Deploy to GitHub Pages
58+
- name: Deploy to GitHub Pages
7259
id: deployment
7360
uses: actions/deploy-pages@v4
74-
75-
- name: 📢 Deployment summary
76-
run: |
77-
echo "## 🚀 部署完成" >> $GITHUB_STEP_SUMMARY
78-
echo "" >> $GITHUB_STEP_SUMMARY
79-
echo "**部署地址**: [${{ steps.deployment.outputs.page_url }}](${{ steps.deployment.outputs.page_url }})" >> $GITHUB_STEP_SUMMARY
80-
echo "" >> $GITHUB_STEP_SUMMARY
81-
echo "**构建环境**: Node.js 20" >> $GITHUB_STEP_SUMMARY
82-
echo "**部署分支**: main" >> $GITHUB_STEP_SUMMARY

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
.DS_Store
22
Thumbs.db
33
node_modules/
4-
.vitepress
54
docs/.vitepress/dist/
65
docs/.vitepress/cache/
76
*.log

docs/.vitepress/config.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { defineConfig } from "vitepress";
2+
3+
function escapeMustache(content: string): string {
4+
return content
5+
.replace(/\{\{/g, "{{")
6+
.replace(/\}\}/g, "}}");
7+
}
8+
9+
export default defineConfig({
10+
title: "前端成长记录",
11+
description: "一个前端工程师从 2018 年开始的学习与成长记录",
12+
lang: "zh-CN",
13+
themeConfig: {
14+
nav: [
15+
{ text: "首页", link: "/" },
16+
{ text: "最新文章", link: "/posts/" },
17+
{ text: "📚 归档 (2018-2025)", link: "/archive/" },
18+
],
19+
outline: {
20+
label: "本文目录",
21+
},
22+
docFooter: {
23+
prev: "上一篇",
24+
next: "下一篇",
25+
},
26+
darkModeSwitchLabel: "深色模式",
27+
sidebarMenuLabel: "菜单",
28+
returnToTopLabel: "返回顶部",
29+
footer: {
30+
message: "MIT Licensed",
31+
copyright: "前端成长记录 2018 - 2026",
32+
},
33+
},
34+
markdown: {
35+
theme: {
36+
light: "github-light",
37+
dark: "github-dark",
38+
},
39+
config(md) {
40+
// 只在普通文本 token 上转义 {{ }},代码块由渲染器后置处理
41+
md.core.ruler.push("escape-mustache-in-markdown", (state) => {
42+
for (const token of state.tokens) {
43+
// fence/code_block 由渲染器 override 处理,跳过
44+
if (token.type === "fence" || token.type === "code_block") continue;
45+
46+
if (
47+
typeof token.content === "string" &&
48+
token.content.includes("{{")
49+
) {
50+
token.content = escapeMustache(token.content);
51+
}
52+
53+
if (token.type === "inline" && token.children) {
54+
for (const child of token.children) {
55+
// code_inline 由渲染器 override 处理,跳过
56+
if (child.type === "code_inline") continue;
57+
58+
if (
59+
typeof child.content === "string" &&
60+
child.content.includes("{{")
61+
) {
62+
child.content = escapeMustache(child.content);
63+
}
64+
}
65+
}
66+
}
67+
});
68+
69+
// 在 shiki 高亮后的 HTML 输出中替换 {{ }}
70+
// 不能在原始 content 上改,否则 escapeHtml 会把 & 二次转义成 & 导致显示乱码
71+
const origFence = md.renderer.rules.fence;
72+
md.renderer.rules.fence = (...args) => {
73+
const html = origFence ? origFence(...args) : "";
74+
return html
75+
.replace(/\{\{/g, "{{")
76+
.replace(/\}\}/g, "}}");
77+
};
78+
79+
const origCodeInline = md.renderer.rules.code_inline;
80+
md.renderer.rules.code_inline = (...args) => {
81+
const html = origCodeInline ? origCodeInline(...args) : "";
82+
return html
83+
.replace(/\{\{/g, "{{")
84+
.replace(/\}\}/g, "}}");
85+
};
86+
},
87+
},
88+
});
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
<script setup lang="ts">
2+
import { data as allPosts } from "../../posts.data";
3+
import { computed } from "vue";
4+
import { useRoute } from "vitepress";
5+
6+
const route = useRoute();
7+
8+
// 从路由 /archive/2018/ 中提取年份
9+
const yearMatch = route.path.match(/\/archive\/(\d{4})\//);
10+
const year = yearMatch ? parseInt(yearMatch[1]) : null;
11+
12+
const yearPosts = computed(() => {
13+
if (!year) return [];
14+
return (allPosts as typeof allPosts).filter(
15+
(p) => new Date(p.date).getFullYear() === year,
16+
);
17+
});
18+
19+
const postsGroupedByMonth = computed(() => {
20+
const groups: Record<string, typeof yearPosts.value> = {};
21+
22+
yearPosts.value.forEach((post) => {
23+
const date = new Date(post.date);
24+
const month = String(date.getMonth() + 1).padStart(2, "0");
25+
const key = month;
26+
27+
if (!groups[key]) {
28+
groups[key] = [];
29+
}
30+
groups[key].push(post);
31+
});
32+
33+
return Object.entries(groups)
34+
.sort(([a], [b]) => b.localeCompare(a))
35+
.map(([month, posts]) => ({ month, posts }));
36+
});
37+
38+
function formatDate(d: string) {
39+
if (!d) return "";
40+
const dt = new Date(d.length === 10 ? d + "T00:00:00" : d);
41+
if (isNaN(dt.getTime())) return "";
42+
return `${dt.getFullYear()}-${String(dt.getMonth() + 1).padStart(2, "0")}-${String(dt.getDate()).padStart(2, "0")}`;
43+
}
44+
</script>
45+
46+
<template>
47+
<div class="archive-year-wrap" v-if="year && yearPosts.length > 0">
48+
<p class="year-summary">{{ year }} 年共 {{ yearPosts.length }} 篇文章</p>
49+
50+
<div class="months-list">
51+
<div
52+
v-for="group in postsGroupedByMonth"
53+
:key="group.month"
54+
class="month-group"
55+
>
56+
<h3 class="month-title">
57+
{{ group.month }} 月 ({{ group.posts.length }} 篇)
58+
</h3>
59+
<ul class="posts-in-month">
60+
<li v-for="post in group.posts" :key="post.url" class="post-item">
61+
<time class="post-date">{{ formatDate(post.date) }}</time>
62+
<a :href="post.url" class="post-title">{{ post.title }}</a>
63+
</li>
64+
</ul>
65+
</div>
66+
</div>
67+
68+
<div class="archive-nav">
69+
<a href="/archive/" class="nav-link">← 返回归档</a>
70+
<a href="/posts/" class="nav-link">查看所有文章 →</a>
71+
</div>
72+
</div>
73+
<div v-else class="no-posts">
74+
<p>{{ year ? `${year} 年没有文章` : "获取年份失败" }}</p>
75+
<a href="/archive/">返回归档 →</a>
76+
</div>
77+
</template>
78+
79+
<style scoped>
80+
.archive-year-wrap {
81+
max-width: 740px;
82+
padding: 1rem 0 4rem;
83+
}
84+
85+
.year-summary {
86+
font-size: 1.1rem;
87+
color: var(--vp-c-text-1);
88+
margin-bottom: 1.5rem;
89+
font-weight: 600;
90+
}
91+
92+
.months-list {
93+
display: flex;
94+
flex-direction: column;
95+
gap: 2rem;
96+
margin-bottom: 2rem;
97+
}
98+
99+
.month-group {
100+
border-left: 3px solid var(--vp-c-brand-1);
101+
padding-left: 1rem;
102+
}
103+
104+
.month-title {
105+
font-size: 1rem;
106+
color: var(--vp-c-brand-1);
107+
margin: 0 0 0.8rem 0;
108+
font-weight: 600;
109+
}
110+
111+
.posts-in-month {
112+
list-style: none;
113+
padding: 0;
114+
margin: 0;
115+
display: flex;
116+
flex-direction: column;
117+
gap: 0.6rem;
118+
}
119+
120+
.post-item {
121+
display: flex;
122+
gap: 1rem;
123+
align-items: center;
124+
}
125+
126+
.post-date {
127+
font-size: 0.9rem;
128+
color: var(--vp-c-text-3);
129+
min-width: 110px;
130+
flex-shrink: 0;
131+
}
132+
133+
.post-title {
134+
color: var(--vp-c-brand-1);
135+
text-decoration: none;
136+
flex: 1;
137+
transition: opacity 0.2s;
138+
}
139+
140+
.post-title:hover {
141+
opacity: 0.7;
142+
}
143+
144+
.archive-nav {
145+
display: flex;
146+
gap: 1rem;
147+
justify-content: center;
148+
padding-top: 1rem;
149+
border-top: 1px solid var(--vp-c-divider);
150+
}
151+
152+
.nav-link {
153+
color: var(--vp-c-brand-1);
154+
text-decoration: none;
155+
transition: opacity 0.2s;
156+
}
157+
158+
.nav-link:hover {
159+
opacity: 0.7;
160+
}
161+
162+
.no-posts {
163+
text-align: center;
164+
padding: 2rem;
165+
color: var(--vp-c-text-2);
166+
}
167+
168+
.no-posts a {
169+
color: var(--vp-c-brand-1);
170+
}
171+
</style>

0 commit comments

Comments
 (0)