hd-garden 0.6.1 → 0.7.0

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.
Files changed (45) hide show
  1. package/app/admin/preview/[...path]/page.tsx +1 -1
  2. package/app/blog/[...path]/page.tsx +2 -2
  3. package/app/columns/[column]/[...path]/page.tsx +2 -2
  4. package/app/components/ArticleMeasure.tsx +39 -4
  5. package/app/components/GardenDocsNav.tsx +184 -0
  6. package/app/components/MarkdownArticle.tsx +7 -6
  7. package/app/components/SiteChrome.tsx +1 -1
  8. package/app/components/SiteFooter.tsx +10 -3
  9. package/app/garden/[...slug]/page.tsx +148 -0
  10. package/app/garden/page.tsx +61 -128
  11. package/app/garden-modules.d.ts +15 -0
  12. package/app/globals.css +10 -10
  13. package/app/lib/content.ts +16 -10
  14. package/app/lib/garden-docs-meta.ts +83 -0
  15. package/app/lib/heading-number.ts +13 -0
  16. package/app/styles/a2.css +531 -0
  17. package/docs/handbook/assets/reading-layout.svg +26 -0
  18. package/docs/handbook/components/card.md +44 -0
  19. package/docs/handbook/components/chart.md +52 -0
  20. package/docs/handbook/components/compare.md +40 -0
  21. package/docs/handbook/components/figure.md +70 -0
  22. package/docs/handbook/components/file/checklist.md +17 -0
  23. package/docs/handbook/components/file.md +32 -0
  24. package/docs/handbook/components/html.md +67 -0
  25. package/docs/handbook/components/interactive.md +65 -0
  26. package/docs/handbook/components/stat.md +55 -0
  27. package/docs/handbook/components/timeline.md +50 -0
  28. package/docs/handbook/components.md +30 -0
  29. package/docs/handbook/config.md +125 -0
  30. package/docs/handbook/deploy.md +273 -0
  31. package/docs/handbook/design.md +81 -0
  32. package/docs/handbook/index.md +92 -0
  33. package/docs/handbook/start.md +92 -0
  34. package/docs/handbook/workflow.md +81 -0
  35. package/docs/handbook/writing.md +148 -0
  36. package/package.json +2 -1
  37. package/scripts/build-content.mjs +124 -15
  38. package/scripts/handbook-allowlist.mjs +68 -0
  39. package/scripts/handbook-facts.mjs +1104 -0
  40. package/site.config.yaml +2 -2
  41. package/templates/garden-blocks.md +1 -1
  42. package/templates/site/package.json +1 -3
  43. package/ui/garden/catalog.mjs +1 -1
  44. package/ui/preview/dist/garden-ui.esm.js +1 -1
  45. package/ui/preview/dist/garden-ui.iife.js +1 -1
@@ -0,0 +1,273 @@
1
+ ---
2
+ title: 发布到 GitHub Pages
3
+ navTitle: 发布到 GitHub Pages
4
+ description: 配置 GitHub Actions 工作流,支持单个公开仓库或「私有源码仓库 + 公开页面仓库」两种发布方式。
5
+ section: getting-started
6
+ order: 3
7
+ ---
8
+
9
+ `npx hd-garden init` 不会自动创建 `.github/workflows/pages.yml`。发布到 GitHub Pages 时,在站点仓库新建这个文件。同一份工作流同时支持**单个公开仓库**和**私有源码仓库 + 公开页面仓库**两种模式,按 `PAGES_REPOSITORY` 变量是否为空自动切换。
10
+
11
+ 下面的工作流和 Garden 仓库自己的 `.github/workflows/pages.yml` 一致,改动只有两处:一是去掉只对 Garden 仓库有用的 `paths-ignore`、`npm run lint` 和 `npm test`;二是 `publish-repository` 任务先装依赖,再用 `npx garden pages:prepare static-site pages-repository` 写入公开仓库(Garden 仓库里直接调用的是同一个脚本 `scripts/prepare-pages-output.mjs`)。`npm ci` 需要仓库里有 `package-lock.json`,第一次 `npm install` 后记得一起提交。
12
+
13
+ `build` 任务只在三种情况下运行:仓库名是 `<owner>.github.io`、配置了 `SITE_URL` 变量,或配置了 `PAGES_REPOSITORY` 变量。
14
+
15
+ ## 1. 完整 `.github/workflows/pages.yml` 工作流
16
+
17
+ 在站点仓库新建 `.github/workflows/pages.yml`:
18
+
19
+ ```yaml
20
+ name: Verify and publish blog
21
+
22
+ on:
23
+ push:
24
+ branches: [main]
25
+ workflow_dispatch:
26
+
27
+ permissions:
28
+ contents: read
29
+
30
+ concurrency:
31
+ group: publish-blog
32
+ cancel-in-progress: true
33
+
34
+ jobs:
35
+ build:
36
+ if: github.event.repository.name == format('{0}.github.io', github.repository_owner) || vars.SITE_URL != '' || vars.PAGES_REPOSITORY != ''
37
+ runs-on: ubuntu-latest
38
+ steps:
39
+ - uses: actions/checkout@v7
40
+
41
+ - uses: actions/setup-node@v7
42
+ with:
43
+ node-version: 22
44
+ cache: npm
45
+
46
+ - run: npm ci
47
+
48
+ - name: Resolve the public URL
49
+ env:
50
+ SITE_URL_OVERRIDE: ${{ vars.SITE_URL }}
51
+ ADMIN_URL_OVERRIDE: ${{ vars.ADMIN_URL }}
52
+ GISCUS_REPO: ${{ vars.GISCUS_REPO }}
53
+ GISCUS_REPO_ID: ${{ vars.GISCUS_REPO_ID }}
54
+ GISCUS_CATEGORY: ${{ vars.GISCUS_CATEGORY }}
55
+ GISCUS_CATEGORY_ID: ${{ vars.GISCUS_CATEGORY_ID }}
56
+ GISCUS_DISABLED: ${{ vars.GISCUS_DISABLED }}
57
+ WEBMENTION_IO_DOMAIN: ${{ vars.WEBMENTION_IO_DOMAIN }}
58
+ ANALYTICS_DOMAIN: ${{ vars.ANALYTICS_DOMAIN }}
59
+ ANALYTICS_SCRIPT: ${{ vars.ANALYTICS_SCRIPT }}
60
+ run: |
61
+ owner="$GITHUB_REPOSITORY_OWNER"
62
+ default_url="https://$owner.github.io"
63
+ giscus_ready=1
64
+ if [ -z "$GISCUS_REPO" ] || [ "$GISCUS_REPO" = "owner/repository" ]; then giscus_ready=0; fi
65
+ if [ -z "$GISCUS_REPO_ID" ] || [ "$GISCUS_REPO_ID" = "R_xxxxxxxxxx" ]; then giscus_ready=0; fi
66
+ if [ -z "$GISCUS_CATEGORY" ]; then giscus_ready=0; fi
67
+ if [ -z "$GISCUS_CATEGORY_ID" ] || [ "$GISCUS_CATEGORY_ID" = "DIC_xxxxxxxxxx" ]; then giscus_ready=0; fi
68
+ if [ "$giscus_ready" != 1 ]; then
69
+ if [ "$GISCUS_DISABLED" = "1" ]; then
70
+ GISCUS_REPO=
71
+ GISCUS_REPO_ID=
72
+ GISCUS_CATEGORY=
73
+ GISCUS_CATEGORY_ID=
74
+ echo "GISCUS_DISABLED=1,本次构建不开放评论。"
75
+ else
76
+ echo "评论还没有配置,构建已停止。"
77
+ echo "1. 准备一个公开 GitHub 仓库,并开启 Discussions。"
78
+ echo "2. 打开 https://giscus.app/zh-CN ,安装 Giscus App,选中该仓库和分类。"
79
+ echo "3. 在 Settings → Secrets and variables → Actions → Variables 写入:"
80
+ echo " GISCUS_REPO、GISCUS_REPO_ID、GISCUS_CATEGORY、GISCUS_CATEGORY_ID"
81
+ echo "确定不开放评论时,把 GISCUS_DISABLED 设为 1。留空和示例值 owner/repository 都不会通过。"
82
+ exit 1
83
+ fi
84
+ fi
85
+ {
86
+ echo "SITE_URL=${SITE_URL_OVERRIDE:-$default_url}"
87
+ echo "NEXT_PUBLIC_SITE_URL=${SITE_URL_OVERRIDE:-$default_url}"
88
+ echo "ADMIN_URL=$ADMIN_URL_OVERRIDE"
89
+ echo "GISCUS_REPO=$GISCUS_REPO"
90
+ echo "GISCUS_REPO_ID=$GISCUS_REPO_ID"
91
+ echo "GISCUS_CATEGORY=$GISCUS_CATEGORY"
92
+ echo "GISCUS_CATEGORY_ID=$GISCUS_CATEGORY_ID"
93
+ echo "NEXT_PUBLIC_GISCUS_REPO=$GISCUS_REPO"
94
+ echo "NEXT_PUBLIC_GISCUS_REPO_ID=$GISCUS_REPO_ID"
95
+ echo "NEXT_PUBLIC_GISCUS_CATEGORY=$GISCUS_CATEGORY"
96
+ echo "NEXT_PUBLIC_GISCUS_CATEGORY_ID=$GISCUS_CATEGORY_ID"
97
+ echo "WEBMENTION_IO_DOMAIN=$WEBMENTION_IO_DOMAIN"
98
+ echo "NEXT_PUBLIC_ANALYTICS_DOMAIN=$ANALYTICS_DOMAIN"
99
+ echo "NEXT_PUBLIC_ANALYTICS_SCRIPT=$ANALYTICS_SCRIPT"
100
+ echo "STATIC_EXPORT=1"
101
+ } >> "$GITHUB_ENV"
102
+
103
+ - name: Build the public site
104
+ env:
105
+ GITHUB_TOKEN: ${{ github.token }}
106
+ run: npm run build
107
+
108
+ - name: Validate the static output
109
+ run: npm run pages:validate
110
+
111
+ - name: Configure GitHub Pages
112
+ if: ${{ vars.PAGES_REPOSITORY == '' }}
113
+ uses: actions/configure-pages@v6
114
+
115
+ - name: Upload the GitHub Pages artifact
116
+ if: ${{ vars.PAGES_REPOSITORY == '' }}
117
+ uses: actions/upload-pages-artifact@v5
118
+ with:
119
+ path: dist/client
120
+
121
+ - name: Upload the public-repository artifact
122
+ if: ${{ vars.PAGES_REPOSITORY != '' }}
123
+ uses: actions/upload-artifact@v7
124
+ with:
125
+ name: static-site
126
+ path: dist/client
127
+ include-hidden-files: true
128
+ if-no-files-found: error
129
+ retention-days: 1
130
+
131
+ deploy-pages:
132
+ if: ${{ vars.PAGES_REPOSITORY == '' }}
133
+ needs: build
134
+ permissions:
135
+ pages: write
136
+ id-token: write
137
+ environment:
138
+ name: github-pages
139
+ url: ${{ steps.deployment.outputs.page_url }}
140
+ runs-on: ubuntu-latest
141
+ steps:
142
+ - name: Deploy the GitHub Pages artifact
143
+ id: deployment
144
+ uses: actions/deploy-pages@v5
145
+
146
+ publish-repository:
147
+ if: ${{ vars.PAGES_REPOSITORY != '' }}
148
+ needs: build
149
+ runs-on: ubuntu-latest
150
+ steps:
151
+ - uses: actions/checkout@v7
152
+
153
+ - uses: actions/setup-node@v7
154
+ with:
155
+ node-version: 22
156
+ cache: npm
157
+
158
+ - run: npm ci
159
+
160
+ - name: Check the split-repository configuration
161
+ env:
162
+ PAGES_DEPLOY_KEY: ${{ secrets.PAGES_DEPLOY_KEY }}
163
+ TARGET_REPOSITORY: ${{ vars.PAGES_REPOSITORY }}
164
+ run: |
165
+ test -n "$PAGES_DEPLOY_KEY" || { echo "缺少 PAGES_DEPLOY_KEY Secret"; exit 1; }
166
+ [[ "$TARGET_REPOSITORY" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || {
167
+ echo "PAGES_REPOSITORY 格式无效"
168
+ exit 1
169
+ }
170
+
171
+ - uses: actions/download-artifact@v8
172
+ with:
173
+ name: static-site
174
+ path: static-site
175
+
176
+ - name: Check out the public output repository
177
+ uses: actions/checkout@v7
178
+ with:
179
+ repository: ${{ vars.PAGES_REPOSITORY }}
180
+ ssh-key: ${{ secrets.PAGES_DEPLOY_KEY }}
181
+ path: pages-repository
182
+ fetch-depth: 1
183
+
184
+ - name: Start a clean rolling deployment
185
+ working-directory: pages-repository
186
+ env:
187
+ TARGET_REPOSITORY: ${{ vars.PAGES_REPOSITORY }}
188
+ run: |
189
+ expected_remote="git@github.com:$TARGET_REPOSITORY.git"
190
+ actual_remote="$(git remote get-url origin)"
191
+ test "$actual_remote" = "$expected_remote" || {
192
+ echo "拒绝发布到非目标仓库:$actual_remote"
193
+ exit 1
194
+ }
195
+ echo "PAGES_PREVIOUS_TREE=$(git rev-parse 'HEAD^{tree}')" >> "$GITHUB_ENV"
196
+ git checkout --orphan pages-deploy
197
+ git rm -q -r -f .
198
+
199
+ - name: Prepare generated-only repository contents
200
+ run: npx garden pages:prepare static-site pages-repository
201
+
202
+ - name: Publish the rolling deployment commit
203
+ working-directory: pages-repository
204
+ run: |
205
+ git config user.name "github-actions[bot]"
206
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
207
+ git add -A
208
+ new_tree="$(git write-tree)"
209
+ if [ "$new_tree" = "$PAGES_PREVIOUS_TREE" ]; then
210
+ echo "静态产物没有变化,无需发布"
211
+ exit 0
212
+ fi
213
+ git commit -m "Deploy ${GITHUB_SHA}"
214
+ git push --force origin HEAD:main
215
+ ```
216
+
217
+ ## 2. 两种部署模式的设置步骤
218
+
219
+ ### 模式一:单个公开仓库(`PAGES_REPOSITORY` 留空)
220
+
221
+ 如果源码仓库本身是公开的,仓库名用 `<owner>.github.io`。用别的仓库名时,要给 Pages 配上自定义域名,并把 `SITE_URL` 变量设为这个域名,否则 `build` 任务不会运行(Garden 不支持部署在 `/仓库名/` 这样的子路径下):
222
+
223
+ 1. 打开仓库 **Settings → Pages**,将 **Source** 设为 **GitHub Actions**。
224
+ 2. 在 **Settings → Secrets and variables → Actions → Variables** 中新建 `GISCUS_DISABLED=1`(或者填入下方 4 个 `GISCUS_*` 变量)。
225
+ 3. 推送到 `main` 分支后,工作流会走 `deploy-pages` 任务直接发布。
226
+
227
+ ### 模式二:私有源码仓库 + 公开页面仓库
228
+
229
+ 如果不希望公开未发布的草稿(`draft: true`)或写作历史,可以把站点源码放在私有仓库(例如 `blog-source`),只把生成的静态文件推送到公开仓库 `<owner>.github.io`:
230
+
231
+ 1. **生成 SSH 部署密钥**:在本地运行命令生成一对专用密钥:
232
+ ```bash
233
+ ssh-keygen -t ed25519 -C "garden-pages-deploy" -f ./pages_deploy_key
234
+ ```
235
+ 2. **在公开仓库添加公钥**:打开公开仓库 `<owner>.github.io` 的 **Settings → Deploy keys → Add deploy key**,填入 `pages_deploy_key.pub` 的内容,并**勾选 Allow write access**。
236
+ 3. **设置公开仓库的 Pages 来源**:打开公开仓库 `<owner>.github.io` 的 **Settings → Pages**,将 **Source** 设为 **Deploy from a branch**,分支选择 `main`、目录选择 `/ (root)`。
237
+ 4. **在私有仓库配置变量与密钥**:打开私有源码仓库的 **Settings → Secrets and variables → Actions**:
238
+ - 在 **Variables** 中新建 `PAGES_REPOSITORY`,值为 `<owner>/<owner>.github.io`(同时配置 `GISCUS_DISABLED=1` 或 4 个 `GISCUS_*` 变量)。
239
+ - 在 **Secrets** 中新建 `PAGES_DEPLOY_KEY`,值为私钥文件 `pages_deploy_key` 的完整内容。
240
+ 5. 推送到私有仓库后,工作流中的 `publish-repository` 任务会使用 `PAGES_DEPLOY_KEY` 检出公开仓库,先检查 `PAGES_DEPLOY_KEY` 是否存在、`PAGES_REPOSITORY` 是否是 `owner/repo` 格式,再确认检出的远程地址就是目标仓库(不是就停止并提示「拒绝发布到非目标仓库」)。之后运行 `npx garden pages:prepare static-site pages-repository` 清空旧文件、写入静态产物、`.nojekyll` 和一份说明用的 `README.md`,最后强制推送到公开仓库的 `main` 分支。
241
+
242
+ ## 3. Giscus 评论配置说明
243
+
244
+ 文章底部的评论区基于 GitHub Discussions 和 [Giscus](https://giscus.app/zh-CN)。应用代码本身只读取 `GISCUS_REPO`、`GISCUS_REPO_ID`、`GISCUS_CATEGORY`、`GISCUS_CATEGORY_ID`(以及对应的 `NEXT_PUBLIC_GISCUS_*`);而上方 `pages.yml` 工作流里的 `Resolve the public URL` shell 步骤会在构建前检查这四项是否齐全:
245
+
246
+ - **暂不开启评论**:在 GitHub Actions Variables 中设置 `GISCUS_DISABLED=1`,工作流会清空 `GISCUS_*` 并跳过评论区。
247
+ - **开启评论**:在公开仓库启用 Discussions 并安装 Giscus App,把获取到的四个参数写入 GitHub Actions Variables。
248
+
249
+ > [!NOTE]
250
+ > `npx garden pages:validate` 负责检查 `dist/client` 静态产物目录(确认存在 `index.html`,且没有软链接、`.env` 文件、`node_modules`、`.git`、`app`、`content` 等源码目录,也没有 `.md`、`.map`、`.ts` 文件;`media/` 下作为附件发布的 `.md` 除外),它本身不会检查 Giscus 变量。
251
+
252
+ ## 4. 环境变量一览
253
+
254
+ 所有环境变量均可写在本地 `.env.local`(不要提交进 Git)或部署平台的 Variables / Secrets 中:
255
+
256
+ | 变量名 | 用途说明 |
257
+ | :--- | :--- |
258
+ | `SITE_URL` / `NEXT_PUBLIC_SITE_URL` | 站点公开根地址(如 `https://example.com`),用于生成 canonical、RSS 与 `sitemap.xml` |
259
+ | `NEXT_PUBLIC_BASE_PATH` | 子路径前缀(默认留空) |
260
+ | `STATIC_EXPORT` | 设为 `1` 时启用静态 HTML 导出,并自动开启站内链接末尾 `/` |
261
+ | `TRAILING_SLASH` | 服务端运行时设为 `1` 可开启站内链接末尾 `/`(`STATIC_EXPORT=1` 时已默认开启) |
262
+ | `CONTENT_INCLUDE_DRAFTS` | 设为 `1` 时将 `draft: true` 草稿和未来 `publishAt` 文章一并纳入构建 |
263
+ | `GITHUB_TOKEN` | 构建期拉取作者 GitHub 公开资料与置顶仓库信息(GitHub Actions 中可传 `${{ github.token }}`) |
264
+ | `GISCUS_DISABLED` | 供 `pages.yml` 工作流脚本识别:设为 `1` 时允许不填下方 4 个 `GISCUS_*` 变量 |
265
+ | `GISCUS_REPO` / `GISCUS_REPO_ID` | Giscus 绑定的公开仓库(`owner/repo`)及其 Repository ID |
266
+ | `GISCUS_CATEGORY` / `GISCUS_CATEGORY_ID` | Giscus 绑定的 Discussions 分类名及其 Category ID |
267
+ | `WEBMENTION_IO_DOMAIN` / `WEBMENTION_ENDPOINT` | 接收 Webmention 的域名(如 `example.com`)与自定义接收端点 |
268
+ | `ADMIN_URL` | 独立部署 `/admin` 后台时的地址 |
269
+ | `CONTENT_REPOSITORY` / `CONTENT_BRANCH` | `/admin` 后台写入的 GitHub 仓库(`owner/repo`)与分支名(默认 `main`) |
270
+ | `CONTENT_GITHUB_TOKEN` | `/admin` 后台向仓库提交随想和附件所用的 GitHub Fine-grained PAT |
271
+ | `GITHUB_OAUTH_CLIENT_ID` / `GITHUB_OAUTH_CLIENT_SECRET` | `/admin` 后台 GitHub OAuth 登录应用的 Client ID 与 Secret |
272
+ | `GITHUB_SESSION_SECRET` | `/admin` 会话签名的随机密钥字符串 |
273
+ | `ADMIN_GITHUB_LOGINS` | 允许登录 `/admin` 后台的 GitHub 用户名白名单(多个用逗号分隔) |
@@ -0,0 +1,81 @@
1
+ ---
2
+ title: 阅读排版是怎么工作的
3
+ navTitle: 阅读排版是怎么工作的
4
+ description: 740/1040/1340px 三级宽度计算、丨标题断行、图片防抖动、深色模式图片衬底与无品牌蓝配色。
5
+ section: writing-and-reading
6
+ order: 11
7
+ ---
8
+
9
+ Garden 的文章页面围绕长文阅读体验做了几项自动化处理。这些机制在博客文章、专栏文章和本说明书页面上使用同一套样式与测量脚本。
10
+
11
+ ## 1. 三级自适应宽度:`740px` / `1040px` / `1340px`
12
+
13
+ 文章页面的主体由居中正文列与右侧浮动目录(`200px`)组成。页面加载及窗口尺寸变化时,`ArticleMeasure` 脚本会测量正文中的图片与表格,按窗口两侧剩余空白对称向外拓宽:
14
+
15
+ | 内容元素 | 最大宽度 | 展开规则与边界条件 |
16
+ | :--- | :--- | :--- |
17
+ | 普通段落、列表、代码块、内置组件 | `740px` | 正文字号 `17px`、行高 `1.75`,每行约 38~42 个汉字,始终保持在阅读列内 |
18
+ | 宽幅配图(`.fig`) | `1040px` | 高分屏截图默认按像素宽度的一半(`naturalWidth / 2`)计算显示宽度;当显示宽度超过正文列且视口 `>= 900px` 时,向左右各最多延伸 `150px`(距窗口边缘至少留 `24px`;在说明书页面则限制在左侧导航与右侧目录之间的留白内) |
19
+ | 多列宽表格(`.table-wrap`) | `1340px` | 脚本先以 `max-content` 测量表格不折行时的真实宽度;超出 `740px` 且视口 `>= 900px` 时,向左右各最多延伸 `300px` |
20
+ | 右侧章节目录(`.toc`) | `200px` | 在独立文章页,当拓宽后的图片或表格右边缘靠近目录时,目录自动向右平移避让;若右侧空间不足以放下目录则自动隐藏,滚过宽元素后恢复 |
21
+
22
+ ## 2. 标题断行与字号缩放
23
+
24
+ 文章标题通过 `ArticleTitle` 组件处理,规则只有两条:
25
+
26
+ 1. **写了分隔符(`丨`、`|` 或前后带空格的 ` | `)**:在第一个分隔符处拆成上下两行,并按较长一行的字符宽度(汉字计 `1`、大写字母计 `0.68`、其余字符计 `0.58`、空格计 `0.3`)计算 `--l` 缩放变量,使较长一行刚好放进标题宽度而不再次折行。分隔符本身保留在屏幕阅读器可读文本中。
27
+ 2. **没有写分隔符**:保持单段 `<h1>`,仅使用 CSS `text-wrap: balance` 让浏览器均衡换行,**不会**自动在冒号、破折号或中间位置拆行,也不会自动改变字号。
28
+
29
+ > [!NOTE]
30
+ > 如果标题里写了 `丨`,但分隔符左边或右边为空(例如 `丨副标题`),`npx garden check` 会报 `title-split` 警告(提示 `分隔符两侧都要有文字`)。
31
+
32
+ ## 3. 构建期读取图片尺寸与点击放大
33
+
34
+ 在运行 `npx garden sync` 时,构建脚本会直接解析本地图片(PNG、JPEG、WebP、GIF、SVG)的像素宽高并写入 `.garden/generated-content.ts` 的 `imageSizes` 表:
35
+
36
+ ```garden figure
37
+ caption: 点击上方按钮查看图片从构建到渲染的三个阶段
38
+ svg: |
39
+ <svg viewBox="0 0 680 110" xmlns="http://www.w3.org/2000/svg" font-family="var(--sans)">
40
+ <rect data-p="sync" x="12" y="18" width="196" height="74" rx="10" fill="var(--card)" stroke="var(--rule-strong)" stroke-width="1.5"/>
41
+ <text data-p="sync" x="110" y="50" text-anchor="middle" font-size="14" font-weight="600" fill="var(--ink)">1. 构建期读取尺寸</text>
42
+ <text data-p="sync" x="110" y="72" text-anchor="middle" font-size="12" fill="var(--muted)">写入 width / height 与 --wl</text>
43
+ <path d="M214 55 H236" stroke="var(--muted)" stroke-width="1.5"/>
44
+ <rect data-p="measure" x="242" y="18" width="196" height="74" rx="10" fill="var(--card)" stroke="var(--rule-strong)" stroke-width="1.5"/>
45
+ <text data-p="measure" x="340" y="50" text-anchor="middle" font-size="14" font-weight="600" fill="var(--ink)">2. 客户端测量拓宽</text>
46
+ <text data-p="measure" x="340" y="72" text-anchor="middle" font-size="12" fill="var(--muted)">按当前视口调整宽图与目录</text>
47
+ <path d="M444 55 H466" stroke="var(--muted)" stroke-width="1.5"/>
48
+ <rect data-p="dark" x="472" y="18" width="196" height="74" rx="10" fill="var(--card)" stroke="var(--rule-strong)" stroke-width="1.5"/>
49
+ <text data-p="dark" x="570" y="50" text-anchor="middle" font-size="14" font-weight="600" fill="var(--ink)">3. 深色衬底与灯箱</text>
50
+ <text data-p="dark" x="570" y="72" text-anchor="middle" font-size="12" fill="var(--muted)">深色垫 #faf9f5 · 点击放大</text>
51
+ </svg>
52
+ parts:
53
+ - id: sync
54
+ label: 1. 构建期读取尺寸
55
+ text: 服务端生成的 HTML 已经带有图片的 `width`、`height`、`data-nw`、`data-bw` 以及宽图的初始 `--wl` / `--wr`,图片尚未下载完成时也不会把下方文字顶得跳动。
56
+ - id: measure
57
+ label: 2. 客户端测量拓宽
58
+ text: 浏览器加载后,`ArticleMeasure` 根据当前窗口的实际宽度重新计算拓宽边距,并在滚动时实时判断宽图是否与右侧目录重叠。
59
+ - id: dark
60
+ label: 3. 深色衬底与灯箱
61
+ text: "正文配图点击后可在当前页打开灯箱大图(按 `Esc` 或点击背景关闭);深色模式下自动为配图设置 `--image-backing: #faf9f5` 背景。"
62
+ ```
63
+
64
+ ## 4. 深色模式配图衬底与暖纸墨色
65
+
66
+ 技术文章里常有透明背景、深色线条的架构图或公式截图。切到深色模式后,如果直接放在深灰背景上,黑线会看不见;如果做反色滤镜(`filter: invert`),彩色截图又会失真。
67
+
68
+ Garden 的做法是在深色模式下把正文配图与灯箱大图的 `background` 设为浅色纸底变量 `var(--image-backing)`(即 `#faf9f5`),不加反色滤镜、不改内边距。透明底黑线图能直接看清,不透明的实心截图则完全不受影响。站点 Favicon 图标同样共用这一衬底变量,而文章封面、头像和链接卡片缩略图不会套用该规则。
69
+
70
+ 全站配色定义在 `app/styles/tokens.css` 中,不使用品牌蓝,主要语义色值如下:
71
+
72
+ | 颜色变量 | 浅色模式 | 深色模式 | 用途 |
73
+ | :--- | :--- | :--- | :--- |
74
+ | `--paper` | `#faf9f5` | `#1f1e1d` | 页面主背景色(深色模式下配图衬底 `--image-backing` 亦为 `#faf9f5`) |
75
+ | `--paper-deep` / `--well` | `#f0eee6` | `#262624` | 代码块底色与内嵌凹槽背景 |
76
+ | `--chip` | `#f0eee6` | `#30302e` | 标签胶囊与按钮底色 |
77
+ | `--card` | `#ffffff` | `#262624` | 独立卡片、图框与弹窗底色 |
78
+ | `--ink` | `#1b1a18` | `#eeece7` | 标题、正文强调与主交互色 |
79
+ | `--text` / `--soft` | `#2d2c29` / `#4a4844` | `#dcd9d2` / `#bdb9b0` | 正文段落与次级说明文字 |
80
+ | `--muted` | `#6e6b64` | `#a09b91` | 日期、图注、表头辅助信息(均满足 WCAG AA 4.5:1 对比度) |
81
+ | `--rule` / `--rule-strong` | `#e6e3dc` / `#d6d2c9` | `#3d3d3a` / `#4d4c48` | 1px 细分割线、卡片边框与强调边线 |
@@ -0,0 +1,92 @@
1
+ ---
2
+ title: 用 Markdown 写丨推送之后网站自己更新
3
+ description: 用 Markdown 写文章、随想和专栏,保存在自己的 Git 仓库里。
4
+ section: overview
5
+ order: 0
6
+ ---
7
+
8
+ 长篇技术文章除了段落,常常还有架构图、多列对比和改过的代码。在 Garden 里这些都用普通 Markdown 写,排版交给构建流程和页面脚本。下面每一节都附带实际效果。
9
+
10
+ ## 标题分行与 `740px` 正文列
11
+
12
+ 上方的标题在 `丨` 处分成两行,字号按较长一行自动计算。正文保持在 `740px` 阅读列内,宽图会在两侧留白里对称外扩,不会盖住导航和目录。
13
+
14
+ 下面这张透明底的线稿在深色模式下会自动垫上暖白底,不用另做一套配图:
15
+
16
+ ![740px 正文阅读列、1040px 宽图区与 1340px 宽表区示意(透明背景黑线 SVG,深色模式下自动衬上 #faf9f5 暖白纸底)。](./assets/reading-layout.svg)
17
+
18
+ ## 多列宽表格自动向外延伸
19
+
20
+ 表格不折行时比正文列宽,就会向两侧对称拓宽,文章页最宽 `1340px`。下面这张宽表在桌面端会伸出正文列:
21
+
22
+ | 内容形态 | 源文件位置 | 页面如何处理长文与媒体细节 | 适用场景 |
23
+ | :--- | :--- | :--- | :--- |
24
+ | 长篇技术文章 | `content/posts/<slug>.md` 及同名资源目录 | 自动测量本地配图宽高写入 HTML 防抖动,支持 `丨` 标题分行、宽图宽表外扩、LaTeX 公式与 Mermaid 图表 | 系统设计拆解、源码走读、排障复盘与长篇读书笔记 |
25
+ | 随想与日常记录 | `content/thoughts/<slug>.md` | 支持多图宫格、原生音视频播放(`image` / `audio` / `video`)以及外部网页链接卡片(`link`) | 几句话的灵感、终端截图、短音视频与日常链接摘录 |
26
+ | 主题系列专栏 | `content/columns.yaml` | 按顺序串联 `content/posts/` 下的多篇长文,在阅读页自动生成专栏进度与上下篇导航 | 连载教程、专题研究或按章节组织的系统笔记 |
27
+
28
+ ## 代码块与「对比上一版」切换
29
+
30
+ 把改好的完整代码块和一段 `diff` 紧挨着写,页面会把它们合成一个可以切换「代码」和「对比上一版」的代码框。复制按钮复制的总是新版完整代码。
31
+
32
+ ```yaml
33
+ pages:
34
+ garden:
35
+ title: "用 Markdown 写丨推送之后网站自己更新"
36
+ subtitle: "用 Markdown 写文章、随想和专栏,保存在自己的 Git 仓库里。"
37
+ ```
38
+
39
+ ```diff
40
+ pages:
41
+ garden:
42
+ - title: "用 Markdown 写。\n推送之后,网站自己更新。"
43
+ - subtitle: ""
44
+ + title: "用 Markdown 写丨推送之后网站自己更新"
45
+ + subtitle: "用 Markdown 写文章、随想和专栏,保存在自己的 Git 仓库里。"
46
+ ```
47
+
48
+ > [!NOTE]
49
+ > `diff` 代码块前面必须紧挨着一段完整代码块;如果单独放一段 `diff` 而前面没有完整代码,`npx garden check` 会报 `diff-orphan` 错误。
50
+
51
+ > [!TIP] 提示框支持自定标题
52
+ > 在 `> [!TIP]` 同一行后面直接写文字(或者下一行单独放一行粗体),会替换默认的「建议」标题。支持 `NOTE`、`TIP`、`IMPORTANT`、`WARNING`、`CAUTION` 等常见类型。
53
+
54
+ ## 在文章里嵌入结构化组件
55
+
56
+ 需要数字、对比、时间线或图表时,用 ```` ```garden <name> ```` 围栏插入内置组件。下面是一组 `garden stat`:
57
+
58
+ ```garden stat
59
+ items:
60
+ - label: 正文阅读列
61
+ value: "740"
62
+ unit: px
63
+ caption: 段落、代码块与列表的默认列宽
64
+ - label: 宽幅配图区
65
+ value: "1040"
66
+ unit: px
67
+ caption: 截图按像素宽度的一半显示,像素宽度约 2000px 以上时展开到这个宽度
68
+ - label: 多列宽表区
69
+ value: "1340"
70
+ unit: px
71
+ caption: 宽表格在桌面端向两侧对称外扩上限
72
+ ```
73
+
74
+ 9 个内置组件的完整参数与实时示例见左侧导航的 [组件总览](/garden/components)。
75
+
76
+ <h2 id="garden-admin-title">从初始化到日常写作</h2>
77
+
78
+ 站点仓库只放配置、文章和静态资源,渲染和构建由 `hd-garden` 包完成。本地建站并预览:
79
+
80
+ ```bash
81
+ npx hd-garden init my-blog
82
+ cd my-blog
83
+ npm install
84
+ make dev
85
+ ```
86
+
87
+ 写完运行 `make update`,检查通过后会提交并推送。细节见各章:
88
+
89
+ - **[快速开始](/garden/start)**:目录结构、`make new` 新建长文、草稿预览与日常命令。
90
+ - **[站点与专栏配置](/garden/config)**:`site.config.yaml` 各字段含义、自定义 SVG 图标与 `content/columns.yaml` 专栏编排。
91
+ - **[发布到 GitHub Pages](/garden/deploy)**:单个公开仓库或「私有源码仓库 + 公开页面仓库」的完整 Actions 工作流。
92
+ - **[写文章与随想](/garden/writing)** 与 **[阅读排版是怎么工作的](/garden/design)**:Front Matter 字段、外链卡片、三级自适应宽度与色彩变量。
@@ -0,0 +1,92 @@
1
+ ---
2
+ title: 快速开始
3
+ navTitle: 快速开始
4
+ description: 用 npx hd-garden init 初始化站点目录,在本地预览、新建文章并推送更新。
5
+ section: getting-started
6
+ order: 1
7
+ ---
8
+
9
+ Garden 是一个用 Markdown 写作、用 Git 管理的个人博客工具。初始化后的站点仓库只保留你的配置文件、文章和静态资源,渲染与构建代码由 `hd-garden` 包提供。
10
+
11
+ ## 1. 环境要求与初始化
12
+
13
+ 需要先安装 **Node.js `>= 22.13.0`**、**Git** 与 **GNU Make**。在终端运行以下命令创建站点目录:
14
+
15
+ ```bash
16
+ npx hd-garden init my-blog
17
+ cd my-blog
18
+ npm install
19
+ make dev
20
+ ```
21
+
22
+ `npx hd-garden init` 会把站点模板复制到 `my-blog` 目录,并写入 `.agents/skills/` 辅助技能文件。它不会自动执行 `npm install`,也不会自动创建 `.github/workflows/pages.yml`(GitHub Pages 工作流配置见 [发布到 GitHub Pages](/garden/deploy))。
23
+
24
+ 首次运行 `make dev` 时,如果还没有 `node_modules`,`Makefile` 中的 `make prepare` 会先自动执行 `npm install`,随后运行 `npx garden dev`(其内部会先执行内容同步)并启动本地开发服务器,默认地址为 `http://localhost:3000`。
25
+
26
+ ## 2. 站点目录结构
27
+
28
+ 初始化后的目录结构如下:
29
+
30
+ ```text
31
+ my-blog/
32
+ ├── site.config.yaml # 站点名称、作者信息与各一级页面的标题/副标题
33
+ ├── content/
34
+ │ ├── columns.yaml # 专栏定义与文章阅读顺序
35
+ │ ├── covers/ # 文章与专栏封面图
36
+ │ ├── posts/ # 长文(.md)及同名附件目录
37
+ │ └── thoughts/ # 随想短笔记(.md)及媒体文件
38
+ ├── public/ # 直接按原路径发布的静态资源(如图标)
39
+ ├── Makefile # 日常预览、新建文章、构建检查与提交命令
40
+ └── package.json # 仅依赖 hd-garden
41
+ ```
42
+
43
+ ## 3. 新建长文与随想
44
+
45
+ ### 新建长文(`make new`)
46
+
47
+ 使用 `make new` 在 `content/posts/` 下创建带日期、标题和组件速查注释的草稿文件:
48
+
49
+ ```bash
50
+ make new SLUG=tcp-bbr TITLE="TCP BBR 拥塞控制笔记"
51
+ ```
52
+
53
+ 如果希望把文章放进子目录(例如 `content/posts/network/tcp-bbr.md`),通过 `DIR` 指定子目录名:
54
+
55
+ ```bash
56
+ make new DIR=network SLUG=tcp-bbr TITLE="TCP BBR 拥塞控制笔记"
57
+ ```
58
+
59
+ > [!NOTE]
60
+ > `SLUG` 只能包含小写字母、数字和连字符(`a-z`、`0-9`、`-`),不能包含 `/`;子目录必须用 `DIR` 参数传入。`make new` 只读取 `SLUG`、`TITLE` 和 `DIR` 三个变量。
61
+
62
+ 新建的文件默认带有 `draft: true`。本地预览草稿或未来定时文章(`publishAt`)时,加上环境变量 `CONTENT_INCLUDE_DRAFTS=1`:
63
+
64
+ ```bash
65
+ CONTENT_INCLUDE_DRAFTS=1 make dev
66
+ ```
67
+
68
+ ### 新建随想
69
+
70
+ 随想存放在 `content/thoughts/` 目录下。直接在 `content/thoughts/` 里新建一个 `.md` 文件(例如 `content/thoughts/2026-10-19-note.md`),写上 `title` 和 `date` 即可;也可以在配置好 `/admin` 后台后从网页端发布。具体字段见 [写文章与随想](/garden/writing)。
71
+
72
+ ## 4. 常用命令与版本升级
73
+
74
+ 站点 `Makefile` 与 `garden` CLI 提供的日常命令如下:
75
+
76
+ | 命令 | 实际执行的操作 |
77
+ | :--- | :--- |
78
+ | `make dev` | 先执行 `make prepare` 检查依赖,再运行 `npx garden dev`(内部自动执行同步并启动开发服务器) |
79
+ | `CONTENT_INCLUDE_DRAFTS=1 make dev` | 连同 `draft: true` 草稿和未到 `publishAt` 时间的文章一起预览 |
80
+ | `make new SLUG=xxx TITLE="标题" DIR=子目录` | 调用 `npx garden new` 在 `content/posts/` 下生成新文章草稿(`DIR` 可省略) |
81
+ | `make check` | 在站点仓库中运行 `npx garden sync && npx garden build`,验证全站同步与生产构建 |
82
+ | `npx garden check content/posts/xxx.md` | 单独检查某篇 Markdown 文章的标题、组件字段、代码对比与本地附件路径 |
83
+ | `make update MESSAGE="提交说明"` | 先运行 `make check`,通过后执行 `git add -A`、`git commit -m "$(MESSAGE)"`(`MESSAGE` 默认值为 `更新博客`)和 `git push origin HEAD` |
84
+
85
+ ### 升级 `hd-garden` 版本
86
+
87
+ 需要升级引擎版本时,编辑站点根目录下的 `package.json`,将 `"hd-garden"` 的版本号改为目标版本,然后运行 `npm install`:
88
+
89
+ ```bash
90
+ npm install
91
+ make check
92
+ ```
@@ -0,0 +1,81 @@
1
+ ---
2
+ title: 语法检查与快捷输入
3
+ navTitle: 语法检查与快捷输入
4
+ description: 使用 npx garden check 检查文章诊断码,配置 Git 提交前检查钩子、编辑器代码片段与 Agent 技能。
5
+ section: workflow
6
+ order: 40
7
+ ---
8
+
9
+ 为了防止写错组件 YAML 缩进、漏掉必填字段或引用不存在的本地文件,Garden 提供了命令行检查工具、Git pre-commit 钩子、代码片段模板以及内置的 Agent 技能。
10
+
11
+ ## 1. 文章检查命令
12
+
13
+ 在站点仓库中,有三条不同粒度的检查命令:
14
+
15
+ - **检查单篇或多篇文章(`npx garden check`)**:
16
+ ```bash
17
+ npx garden check content/posts/my-post.md
18
+ # 也可以一次传入多篇 .md 文件
19
+ npx garden check content/posts/*.md
20
+ ```
21
+ - **检查交互块与组件语法(`npx garden ui check`)**:
22
+ ```bash
23
+ npx garden ui check content/posts/my-post.md
24
+ # 检查 Git 暂存区修改过的 Markdown 文件
25
+ npx garden ui check --staged
26
+ # 检查仓库内全部 Markdown 文件
27
+ npx garden ui check --all
28
+ ```
29
+ - **全站同步与构建检查(`make check`)**:
30
+ ```bash
31
+ make check
32
+ ```
33
+ 站点 `Makefile` 中的 `make check` 实际运行的是 `npx garden sync && npx garden build`,会完整执行全站内容同步、组件校验与生产构建。
34
+
35
+ ## 2. `npx garden check` 常见诊断码
36
+
37
+ `npx garden check`(内部调用 `scripts/check-post.mjs`、`ui/garden/check.mjs` 与 `ui/garden/file-paths.mjs`)会输出具体行号、诊断码和修复建议。下表是代码中实际检查的诊断项:
38
+
39
+ | 诊断码 | 级别 | 触发原因与修复方法 |
40
+ | :--- | :--- | :--- |
41
+ | `missing-title` / `missing-description` | 错误 | Front Matter 缺少有效的 `title` 或 `description` |
42
+ | `unexpected-slug` | 错误 | Front Matter 中手写了 `slug` 字段(文件名即标识,删掉 `slug`) |
43
+ | `date-format` | 错误 | `date` 不是合法的 `YYYY-MM-DD`、`YYYY-MM-DD HH:mm:ss` 或带时区的 ISO 时间 |
44
+ | `missing-cover` / `missing-asset` | 错误 | `cover` 封面图、正文图片或 `garden file` 引用的本地文件不存在 |
45
+ | `heading-h1` / `heading-skip` | 错误 | 正文使用了 `#` 一级标题,或标题层级跳级(例如从 `##` 直接跳到 `####`) |
46
+ | `reference-heading` | 错误 | 正文写了 `## 参考` 或 `## 参考资料` 标题(应改放到 Front Matter 的 `references` 列表) |
47
+ | `callout-type` | 错误 | `> [!TYPE]` 使用了不支持的提示框类型 |
48
+ | `title-split` | 警告 | 标题包含 `丨` 分隔符,但分隔符左边或右边为空(应写成「主题丨副题」) |
49
+ | `diff-orphan` | 错误 | `diff` 代码块前面没有紧挨着的完整代码块 |
50
+ | `legacy-fence` | 错误 | 使用了旧版围栏名(如 `garden:stat`、`widget`、`embed`、`html garden`),需改为 `garden <name>` |
51
+ | `garden-idea` | 错误 | 非草稿文章里还有未编译的空白 `garden` 想法块 |
52
+ | `garden-name` | 错误 | 未知的 `garden` 组件名(仅支持 `card`、`stat`、`compare`、`timeline`、`chart`、`figure`、`html`、`interactive`、`file`) |
53
+ | `garden-yaml` | 错误 | `garden` 围栏内 YAML 语法错误、顶层不是键值/列表,或包含该组件不支持的键名 |
54
+ | `garden-file` / `file-outside` | 错误 | `garden file` 手写了 `resolved`、缺少有效的 `src` / `files`,或本地路径越出了 `content/` 目录 |
55
+ | `file-large` | 警告 | `garden file` 引用的单个本地文件体积超过 `5 MB` |
56
+ | `compare-long` | 错误 | `garden compare` 单个对比格的正文超过约 60 字 |
57
+ | `interactive-trivial` / `steps-prose` | 错误 | `garden interactive` 只有一次简单乘法,或步骤只是散文而没有状态变化 |
58
+
59
+ ## 3. Git 提交前钩子与组件画廊
60
+
61
+ - **启用 pre-commit 钩子**:运行以下命令会在 `.git/hooks/pre-commit` 安装检查脚本,每次 `git commit` 时自动对暂存区的 Markdown 文件运行 `npx --no-install garden ui check --staged --quiet`:
62
+ ```bash
63
+ npx garden hooks
64
+ ```
65
+ - **生成本地组件画廊**:运行以下命令会在 `.garden/ui-catalog.html` 生成包含全部内置组件的静态预览页:
66
+ ```bash
67
+ npx garden ui catalog
68
+ ```
69
+
70
+ ## 4. 快速输入组件模板与 Agent 技能
71
+
72
+ Garden 通过以下三种方式提供组件模板与智能辅助:
73
+
74
+ 1. **新建文章自动附带速查表**:运行 `make new SLUG=xxx TITLE="..."` 创建文章时,生成的 `.md` 文件底部会自动带有一段 `<!-- 组件速查 ... -->` HTML 注释,列出想法块和 9 个组件的示例写法,复制需要的那块即可。这段注释不会显示在页面上,写完可以删掉。
75
+ 2. **代码片段模板文件**:`hd-garden` 包内的 `templates/` 目录自带两份模板文件:
76
+ - `templates/garden-blocks.md`:想法块和 9 个内置组件的可复制 Markdown 模板。
77
+ - `templates/garden-snippets.plist`:macOS 系统文本替换词条(可拖入「系统设置 → 键盘 → 文本替换」),触发词是 `;gidea`(也可以输入 `;garden想法`)、`;gcard`、`;gstat`、`;gcompare`、`;gtimeline`、`;gchart`、`;gfigure`、`;ghtml`、`;ginteractive`、`;gfile`,每个展开成对应的 `garden` 围栏。
78
+ 3. **安装 AI Agent 技能(`npx garden skill`)**:在站点目录运行 `npx garden skill`(`npx hd-garden init` 时也会自动执行),会把三个技能复制到站点根目录下的 `.agents/skills/`:
79
+ - `garden-ui`:把文章里的 `garden` 想法描述编译成具体的组件围栏。
80
+ - `garden-interactive-chart`:根据文章数据编写 `interactive`、`chart`、`figure` 与 `html` 组件。
81
+ - `garden-post-review`:按排版与组件规范审阅整篇文章。