hd-garden 0.3.7 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env.example CHANGED
@@ -8,11 +8,14 @@ ADMIN_URL=https://admin.example.com
8
8
  # 可选:反向代理或多域名后台时,显式指定 GitHub OAuth 回调地址。
9
9
  # GITHUB_OAUTH_REDIRECT_URI=https://admin.example.com/api/auth/github/callback
10
10
 
11
- # Giscus:在 https://giscus.app 配置后复制以下四项。
12
- GISCUS_REPO=owner/repository
13
- GISCUS_REPO_ID=R_xxxxxxxxxx
14
- GISCUS_CATEGORY=Announcements
15
- GISCUS_CATEGORY_ID=DIC_xxxxxxxxxx
11
+ # 评论默认关闭。打开 https://giscus.app/zh-CN 安装后,去掉注释并填入真实值。
12
+ # 仓库必须是 owner/name 这种公开仓库,且已开启 Discussions。
13
+ # 线上还要把同名四项写进 GitHub Actions Variables。留空会让发布失败。
14
+ # 确定不开放评论时,把 Actions Variable GISCUS_DISABLED 设为 1。
15
+ # GISCUS_REPO=
16
+ # GISCUS_REPO_ID=
17
+ # GISCUS_CATEGORY=
18
+ # GISCUS_CATEGORY_ID=
16
19
 
17
20
  # Webmention.io:注册正式域名后填写域名,不含 https://。
18
21
  WEBMENTION_IO_DOMAIN=example.com
package/Makefile CHANGED
@@ -26,7 +26,6 @@ skill:
26
26
 
27
27
  prepare:
28
28
  @if [ ! -d node_modules ]; then npm ci; fi
29
- @if [ ! -f .env.local ]; then cp .env.example .env.local; fi
30
29
 
31
30
  dev: prepare
32
31
  node bin/garden.mjs dev
package/README.md CHANGED
@@ -55,11 +55,11 @@ make update # 检查、提交并触发自动发布
55
55
  make update MESSAGE="新增一篇文章"
56
56
  ```
57
57
 
58
- `make dev` 会在首次运行时自动安装依赖并创建 `.env.local`。`make update` 会先检查,再提交并推送工作区中的所有改动。不想发布的文件请先移出仓库或加入 `.gitignore`。
58
+ `make dev` 会在首次运行时自动安装依赖并创建 `.env.local`,同时说明如何接上评论。线上发布要求 Actions Variables 里有 `GISCUS_REPO`、`GISCUS_REPO_ID`、`GISCUS_CATEGORY`、`GISCUS_CATEGORY_ID`;留空会让构建失败。确定不开放评论时设置 `GISCUS_DISABLED=1`。`make update` 会先检查,再提交并推送工作区中的所有改动。不想发布的文件请先移出仓库或加入 `.gitignore`。
59
59
 
60
60
  ## 进一步配置
61
61
 
62
- 首次跑通以后,再按需查看:
62
+ 评论接法见[从零开始](docs/quick-start.md)。跑通以后,再按需查看:
63
63
 
64
64
  - [评论、Webmentions 与内容分发](docs/integrations.md)
65
65
  - [随想管理后台](docs/admin-service.md)
package/app/blog/page.tsx CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Suspense } from "react";
2
2
  import { siteConfig, siteDocumentTitle } from "../site.config";
3
3
  import { BlogArchive } from "../components/BlogArchive";
4
+ import { PageIntro } from "../components/PageIntro";
4
5
 
5
6
  export const metadata = {
6
7
  title: siteDocumentTitle,
@@ -10,11 +11,11 @@ export const metadata = {
10
11
  export default function BlogIndex() {
11
12
  return (
12
13
  <main className="page-shell index-page">
13
- <header className="page-intro">
14
- <p className="eyebrow">POSTS / 博文</p>
15
- <h1>{siteConfig.pages.blog.title}</h1>
16
- <p>{siteConfig.pages.blog.subtitle}</p>
17
- </header>
14
+ <PageIntro
15
+ eyebrow="POSTS / 博文"
16
+ title={siteConfig.pages.blog.title}
17
+ subtitle={siteConfig.pages.blog.subtitle}
18
+ />
18
19
  <Suspense>
19
20
  <BlogArchive />
20
21
  </Suspense>
@@ -53,7 +53,7 @@ export default function ColumnsPage() {
53
53
  <p>
54
54
  {first.columnDescription ??
55
55
  first.description ??
56
- "沿着一条清晰的阅读路径,把一个主题持续写深。"}
56
+ "按顺序组织的专题文章系列。"}
57
57
  </p>
58
58
  <strong>
59
59
  从第一篇开始
@@ -1,29 +1,9 @@
1
1
  import { ChatCircle, GithubLogo } from "@phosphor-icons/react/ssr";
2
- import {
3
- GiscusComments,
4
- type GiscusConfig,
5
- } from "./GiscusComments";
6
-
7
- function giscusConfig(): GiscusConfig | undefined {
8
- const config = {
9
- repo:
10
- process.env.GISCUS_REPO ?? process.env.NEXT_PUBLIC_GISCUS_REPO,
11
- repoId:
12
- process.env.GISCUS_REPO_ID ?? process.env.NEXT_PUBLIC_GISCUS_REPO_ID,
13
- category:
14
- process.env.GISCUS_CATEGORY ??
15
- process.env.NEXT_PUBLIC_GISCUS_CATEGORY,
16
- categoryId:
17
- process.env.GISCUS_CATEGORY_ID ??
18
- process.env.NEXT_PUBLIC_GISCUS_CATEGORY_ID,
19
- };
20
- return Object.values(config).every(Boolean)
21
- ? (config as GiscusConfig)
22
- : undefined;
23
- }
2
+ import { readGiscusConfig } from "../lib/giscus-config";
3
+ import { GiscusComments } from "./GiscusComments";
24
4
 
25
5
  export function Comments({ slug }: { slug: string }) {
26
- const config = giscusConfig();
6
+ const config = readGiscusConfig();
27
7
 
28
8
  return (
29
9
  <section className="comments" aria-labelledby={`comments-heading-${slug}`}>
@@ -34,7 +14,7 @@ export function Comments({ slug }: { slug: string }) {
34
14
  <p>
35
15
  {config
36
16
  ? "评论由 GitHub Discussions 保存和管理。"
37
- : "评论接入已准备好,补充 GitHub Discussions 配置后即可开放。"}
17
+ : "评论还没开放。配好下面四项后,这里才会出现评论框。"}
38
18
  </p>
39
19
  </div>
40
20
  </div>
@@ -45,8 +25,14 @@ export function Comments({ slug }: { slug: string }) {
45
25
  <div className="integration-notice">
46
26
  <GithubLogo size={20} weight="fill" />
47
27
  <p>
48
- 需要一个公开 GitHub 仓库,并为它启用 Discussions、安装 Giscus
49
- App、选择评论分类。
28
+ 打开{" "}
29
+ <a href="https://giscus.app/zh-CN" target="_blank" rel="noreferrer">
30
+ Giscus
31
+ </a>
32
+ ,安装到一个已开启 Discussions 的公开仓库,把仓库、仓库 ID、分类、分类 ID
33
+ 写入 <code>.env.local</code> 和 GitHub Actions Variables:
34
+ <code>GISCUS_REPO</code>、<code>GISCUS_REPO_ID</code>、
35
+ <code>GISCUS_CATEGORY</code>、<code>GISCUS_CATEGORY_ID</code>。
50
36
  </p>
51
37
  </div>
52
38
  )}
@@ -7,24 +7,24 @@ const emptyContent = {
7
7
  posts: {
8
8
  icon: Article,
9
9
  eyebrow: "NO POSTS YET / 暂无博文",
10
- title: "这里还没有博文。",
11
- description: "第一篇长文发布后,会从这里开始形成可检索、可归档的写作记录。",
10
+ title: "暂无博文。",
11
+ description: "发布文章后会在这里按主题与时间归档。",
12
12
  actionHref: "/",
13
13
  actionLabel: "返回首页",
14
14
  },
15
15
  thoughts: {
16
16
  icon: Quotes,
17
17
  eyebrow: "NO THOUGHTS YET / 暂无随想",
18
- title: "随想还没有开始。",
19
- description: "短句、照片、声音和临时冒出的念头,之后都会沿着时间线落在这里。",
18
+ title: "暂无随想。",
19
+ description: "日常的碎片记录、短笔记与链接会落在这里。",
20
20
  actionHref: "/blog",
21
21
  actionLabel: "先读博文",
22
22
  },
23
23
  columns: {
24
24
  icon: Books,
25
25
  eyebrow: "NO COLUMNS YET / 暂无专栏",
26
- title: "专栏正在形成。",
27
- description: "当同一主题积累出清晰的阅读顺序,这里会出现第一条连续的阅读路径。",
26
+ title: "暂无专栏。",
27
+ description: "文章积累成系列后会在这里按顺序呈现。",
28
28
  actionHref: "/blog",
29
29
  actionLabel: "浏览全部博文",
30
30
  },
@@ -2,12 +2,9 @@
2
2
 
3
3
  import { useEffect, useRef } from "react";
4
4
 
5
- export type GiscusConfig = {
6
- repo: string;
7
- repoId: string;
8
- category: string;
9
- categoryId: string;
10
- };
5
+ import type { GiscusConfig } from "../lib/giscus-config";
6
+
7
+ export type { GiscusConfig };
11
8
 
12
9
  function currentGiscusTheme() {
13
10
  return document.documentElement.dataset.theme === "dark" ? "dark" : "light";
package/app/globals.css CHANGED
@@ -2934,6 +2934,17 @@ button {
2934
2934
  font-size: 11px;
2935
2935
  }
2936
2936
 
2937
+ .integration-notice a,
2938
+ .integration-notice code {
2939
+ text-decoration: underline;
2940
+ text-underline-offset: 0.18em;
2941
+ }
2942
+
2943
+ .integration-notice code {
2944
+ text-decoration: none;
2945
+ font-family: var(--font-mono), ui-monospace, monospace;
2946
+ }
2947
+
2937
2948
  .integration-status {
2938
2949
  margin: 22px 0 0;
2939
2950
  color: var(--muted);
@@ -11,38 +11,38 @@ export const siteConfig = {
11
11
  },
12
12
  pages: {
13
13
  home: {
14
- title: "把复杂的事,\n慢慢想明白。",
15
- subtitle: "在这里记录产品、工程、学习与日常生活中值得慢慢想明白的事。",
14
+ title: "记一些正在做的事,\n和想清楚的问题。",
15
+ subtitle: "关注软件工程、系统设计与技术实践,也记录偶发的探索和工作笔记。",
16
16
  },
17
17
  blog: {
18
- title: "长期写下去,偶尔回头整理。",
19
- subtitle: "这里是完整文章:有明确主题、有持续论证,也允许自己在更新里修正旧判断。",
18
+ title: "博文",
19
+ subtitle: "阶段性的完整梳理与技术总结。",
20
20
  },
21
21
  thoughts: {
22
- title: "不够写成文章的,也值得留下。",
23
- subtitle: "私人朋友圈式的轻量记录:短文字、照片、声音、影像,以及偶然遇见的链接。",
22
+ title: "随想",
23
+ subtitle: "碎片的念头、偶遇的灵感、短笔记与链接。",
24
24
  },
25
25
  columns: {
26
- title: "把一个主题,写到足够深入。",
27
- subtitle: "专栏不是标签集合,而是一条有顺序的阅读路径。左侧章节导航会在阅读时始终陪着你。",
26
+ title: "专栏",
27
+ subtitle: "按顺序组织的专题系列,沿着清晰的脉络连点成线。",
28
28
  },
29
29
  about: {
30
30
  title: "你好,我是 Your Name。",
31
- subtitle: "在这里写下你关心的问题、长期的方向,以及你希望如何与世界保持联系。",
32
- heading: "这个网站为什么存在",
31
+ subtitle: "欢迎来到我的个人网站。",
32
+ heading: "关于我与这里",
33
33
  paragraphs: [
34
- "我需要一个不被信息流推着走的地方。长文可以慢慢展开,随想可以轻轻放下;同一个主题也可以沿着清晰的顺序持续生长。",
35
- "所有文章都从本地 Markdown 文件开始。这意味着我可以继续在 Typora 里写作,也意味着内容不会被某个编辑器或平台锁住。",
34
+ "一名工程师 / 创作者。平时主要关注系统开发、技术探索与软件设计。",
35
+ "这里是我的公开工作台与数字自留地。比起社交网络上的碎片动态,我更倾向于把探索过程沉淀为有据可查的代码、设计与长篇笔记。",
36
36
  ],
37
- quote: "写作不是把已经想明白的东西记下来,而是在句子之间发现自己还没有想明白什么。",
37
+ quote: "",
38
38
  focus: [
39
- "将自己的关注方向写在这里",
40
- "用文章积累可迁移的知识",
41
- "为长期写作留出稳定的空间",
39
+ "软件工程与系统架构",
40
+ "原生工具与高质量交互",
41
+ "个人长期知识沉淀",
42
42
  ],
43
43
  cta: {
44
44
  href: "/blog",
45
- label: "从博文开始",
45
+ label: "浏览博文",
46
46
  },
47
47
  },
48
48
  garden: {
@@ -50,7 +50,7 @@ export const siteConfig = {
50
50
  subtitle: "",
51
51
  },
52
52
  },
53
- footer: "保持独立,持续写作。",
53
+ footer: "持续记录与构建。",
54
54
  };
55
55
 
56
56
  export const githubUrl = `https://github.com/${siteConfig.author.github}`;
@@ -0,0 +1,39 @@
1
+ export type GiscusConfig = {
2
+ repo: string;
3
+ repoId: string;
4
+ category: string;
5
+ categoryId: string;
6
+ };
7
+
8
+ const PLACEHOLDERS = new Set([
9
+ "owner/repository",
10
+ "R_xxxxxxxxxx",
11
+ "DIC_xxxxxxxxxx",
12
+ ]);
13
+
14
+ function configuredValue(
15
+ env: Record<string, string | undefined>,
16
+ primary: string,
17
+ fallback: string,
18
+ ) {
19
+ const value = (env[primary] ?? env[fallback] ?? "").trim();
20
+ if (!value || PLACEHOLDERS.has(value)) return undefined;
21
+ return value;
22
+ }
23
+
24
+ export function readGiscusConfig(
25
+ env: Record<string, string | undefined> = process.env,
26
+ ): GiscusConfig | undefined {
27
+ const repo = configuredValue(env, "GISCUS_REPO", "NEXT_PUBLIC_GISCUS_REPO");
28
+ const repoId = configuredValue(env, "GISCUS_REPO_ID", "NEXT_PUBLIC_GISCUS_REPO_ID");
29
+ const category = configuredValue(env, "GISCUS_CATEGORY", "NEXT_PUBLIC_GISCUS_CATEGORY");
30
+ const categoryId = configuredValue(
31
+ env,
32
+ "GISCUS_CATEGORY_ID",
33
+ "NEXT_PUBLIC_GISCUS_CATEGORY_ID",
34
+ );
35
+ if (!repo || !repoId || !category || !categoryId) return undefined;
36
+ if (!/^[^/\s]+\/[^/\s]+$/.test(repo)) return undefined;
37
+ if (!repoId.startsWith("R_") || !categoryId.startsWith("DIC_")) return undefined;
38
+ return { repo, repoId, category, categoryId };
39
+ }
package/bin/garden.mjs CHANGED
@@ -88,11 +88,25 @@ function ensurePublic() {
88
88
  }
89
89
  }
90
90
 
91
+ function printGiscusGuide() {
92
+ process.stdout.write(`评论默认关闭。发布前要接上自己的 Giscus,留空会让 GitHub Actions 构建失败:
93
+ 1. 准备一个公开 GitHub 仓库,并开启 Discussions。公开源码仓库可以直接用;私有源码要另建一个公开仓库。
94
+ 2. 打开 https://giscus.app/zh-CN ,安装 Giscus App,只授权这个仓库,并选一个分类。
95
+ 3. 把仓库、仓库 ID、分类、分类 ID 写入 .env.local 的 GISCUS_REPO、GISCUS_REPO_ID、GISCUS_CATEGORY、GISCUS_CATEGORY_ID。
96
+ 4. 在源码仓库 Settings → Secrets and variables → Actions → Variables 写入同样四项。
97
+ 示例值不会被当成已配置。确定不开放评论时,把 Actions Variable GISCUS_DISABLED 设为 1。
98
+ `);
99
+ }
100
+
91
101
  function ensureEnv() {
102
+ // CI injects the real values. Copying .env.example there would fill any
103
+ // unset key with a sample, which used to turn comments on for owner/repository.
104
+ if (process.env.GITHUB_ACTIONS === "true") return;
92
105
  const example = path.join(gardenPackageRoot, ".env.example");
93
106
  const local = path.join(siteRoot, ".env.local");
94
107
  if (!fs.existsSync(local) && fs.existsSync(example)) {
95
108
  fs.copyFileSync(example, local);
109
+ printGiscusGuide();
96
110
  }
97
111
  }
98
112
 
@@ -241,8 +255,10 @@ function initSite() {
241
255
  process.stdout.write("下一步:\n");
242
256
  process.stdout.write(` cd ${destination}\n`);
243
257
  process.stdout.write(" npm install\n");
258
+ process.stdout.write(" 按下面四步接上评论,再写文章\n");
244
259
  process.stdout.write(" make new TITLE=\"你好,世界\" SLUG=hello\n");
245
260
  process.stdout.write(" make dev\n");
261
+ printGiscusGuide();
246
262
  }
247
263
 
248
264
  function packagedSkills() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hd-garden",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "private": false,
5
5
  "description": "用 Markdown 写作、用 GitHub Pages 发布的个人博客引擎。",
6
6
  "license": "MIT",
package/site.config.yaml CHANGED
@@ -14,35 +14,35 @@ author:
14
14
  pages:
15
15
  home:
16
16
  title: |-
17
- 把复杂的事,
18
- 慢慢想明白。
19
- subtitle: 在这里记录产品、工程、学习与日常生活中值得慢慢想明白的事。
17
+ 记一些正在做的事,
18
+ 和想清楚的问题。
19
+ subtitle: 关注软件工程、系统设计与技术实践,也记录偶发的探索和工作笔记。
20
20
  blog:
21
- title: 长期写下去,偶尔回头整理。
22
- subtitle: 这里是完整文章:有明确主题、有持续论证,也允许自己在更新里修正旧判断。
21
+ title: 博文
22
+ subtitle: 阶段性的完整梳理与技术总结。
23
23
  thoughts:
24
- title: 不够写成文章的,也值得留下。
25
- subtitle: 私人朋友圈式的轻量记录:短文字、照片、声音、影像,以及偶然遇见的链接。
24
+ title: 随想
25
+ subtitle: 碎片的念头、偶遇的灵感、短笔记与链接。
26
26
  columns:
27
- title: 把一个主题,写到足够深入。
28
- subtitle: 专栏不是标签集合,而是一条有顺序的阅读路径。左侧章节导航会在阅读时始终陪着你。
27
+ title: 专栏
28
+ subtitle: 按顺序组织的专题系列,沿着清晰的脉络连点成线。
29
29
  about:
30
30
  title: 你好,我是 Your Name。
31
- subtitle: 在这里写下你关心的问题、长期的方向,以及你希望如何与世界保持联系。
32
- heading: 这个网站为什么存在
31
+ subtitle: 欢迎来到我的个人网站。
32
+ heading: 关于我与这里
33
33
  paragraphs:
34
- - 我需要一个不被信息流推着走的地方。长文可以慢慢展开,随想可以轻轻放下;同一个主题也可以沿着清晰的顺序持续生长。
35
- - 所有文章都从本地 Markdown 文件开始。这意味着我可以继续在 Typora 里写作,也意味着内容不会被某个编辑器或平台锁住。
36
- quote: 写作不是把已经想明白的东西记下来,而是在句子之间发现自己还没有想明白什么。
34
+ - 一名工程师 / 创作者。平时主要关注系统开发、技术探索与软件设计。
35
+ - 这里是我的公开工作台与数字自留地。比起社交网络上的碎片动态,我更倾向于把探索过程沉淀为有据可查的代码、设计与长篇笔记。
36
+ quote: ""
37
37
  focus:
38
- - 将自己的关注方向写在这里
39
- - 用文章积累可迁移的知识
40
- - 为长期写作留出稳定的空间
38
+ - 软件工程与系统架构
39
+ - 原生工具与高质量交互
40
+ - 个人长期知识沉淀
41
41
  cta:
42
42
  href: /blog
43
- label: 从博文开始
43
+ label: 浏览博文
44
44
  garden:
45
45
  title: 一个基于 Markdown 的个人博客框架。
46
46
  subtitle: ""
47
47
 
48
- footer: 保持独立,持续写作。
48
+ footer: 持续记录与构建。
@@ -8,6 +8,8 @@ make new TITLE="你好,世界" SLUG=hello
8
8
  make dev
9
9
  ```
10
10
 
11
- 第一次 `npx garden dev` 会补齐 Makefile 和 skill。Cursor / Codex 打开这个仓库后会读到 `.agents/skills/`。要更新 skill,再运行 `npx garden skill`。
11
+ 第一次 `npx garden dev` 会补齐 Makefile 和 skill,并创建 `.env.local`。Cursor / Codex 打开这个仓库后会读到 `.agents/skills/`。要更新 skill,再运行 `npx garden skill`。
12
+
13
+ 评论默认关闭。发布前打开 [Giscus](https://giscus.app/zh-CN),把仓库、仓库 ID、分类、分类 ID 写入 `.env.local`,并在 GitHub Actions Variables 设置同名的 `GISCUS_REPO`、`GISCUS_REPO_ID`、`GISCUS_CATEGORY`、`GISCUS_CATEGORY_ID`。留空会让发布失败。确定不开放评论时,把 `GISCUS_DISABLED` 设为 `1`。
12
14
 
13
15
  升级引擎:把 `package.json` 里的 `hd-garden` 改到新版本,例如 `^0.4.0`,再运行 `npm install`。
@@ -12,35 +12,35 @@ author:
12
12
  pages:
13
13
  home:
14
14
  title: |-
15
- 把复杂的事,
16
- 慢慢想明白。
17
- subtitle: 在这里记录产品、工程、学习与日常生活中值得慢慢想明白的事。
15
+ 记一些正在做的事,
16
+ 和想清楚的问题。
17
+ subtitle: 关注软件工程、系统设计与技术实践,也记录偶发的探索和工作笔记。
18
18
  blog:
19
- title: 长期写下去,偶尔回头整理。
20
- subtitle: 这里是完整文章:有明确主题、有持续论证,也允许自己在更新里修正旧判断。
19
+ title: 博文
20
+ subtitle: 阶段性的完整梳理与技术总结。
21
21
  thoughts:
22
- title: 不够写成文章的,也值得留下。
23
- subtitle: 私人朋友圈式的轻量记录:短文字、照片、声音、影像,以及偶然遇见的链接。
22
+ title: 随想
23
+ subtitle: 碎片的念头、偶遇的灵感、短笔记与链接。
24
24
  columns:
25
- title: 把一个主题,写到足够深入。
26
- subtitle: 专栏不是标签集合,而是一条有顺序的阅读路径。
25
+ title: 专栏
26
+ subtitle: 按顺序组织的专题系列,沿着清晰的脉络连点成线。
27
27
  about:
28
28
  title: 你好,我是 Your Name。
29
- subtitle: 在这里写下你关心的问题、长期的方向,以及你希望如何与世界保持联系。
30
- heading: 这个网站为什么存在
29
+ subtitle: 欢迎来到我的个人网站。
30
+ heading: 关于我与这里
31
31
  paragraphs:
32
- - 我需要一个不被信息流推着走的地方。长文可以慢慢展开,随想可以轻轻放下;同一个主题也可以沿着清晰的顺序持续生长。
33
- - 所有文章都从本地 Markdown 文件开始。这意味着我可以继续在 Typora 里写作,也意味着内容不会被某个编辑器或平台锁住。
34
- quote: 写作不是把已经想明白的东西记下来,而是在句子之间发现自己还没有想明白什么。
32
+ - 一名工程师 / 创作者。平时主要关注系统开发、技术探索与软件设计。
33
+ - 这里是我的公开工作台与数字自留地。比起社交网络上的碎片动态,我更倾向于把探索过程沉淀为有据可查的代码、设计与长篇笔记。
34
+ quote: ""
35
35
  focus:
36
- - 将自己的关注方向写在这里
37
- - 用文章积累可迁移的知识
38
- - 为长期写作留出稳定的空间
36
+ - 软件工程与系统架构
37
+ - 原生工具与高质量交互
38
+ - 个人长期知识沉淀
39
39
  cta:
40
40
  href: /blog
41
- label: 从博文开始
41
+ label: 浏览博文
42
42
  garden:
43
43
  title: 一个基于 Markdown 的个人博客框架。
44
44
  subtitle: ""
45
45
 
46
- footer: 保持独立,持续写作。
46
+ footer: 持续记录与构建。