hd-garden 0.14.1 → 0.14.2
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/app/admin/preview/[...path]/page.tsx +1 -1
- package/docs/handbook/en/workflow.md +1 -1
- package/docs/handbook/en/writing.md +1 -1
- package/docs/handbook/workflow.md +1 -1
- package/docs/handbook/writing.md +1 -1
- package/editors/dist/vscode/{garden-ui-0.1.1.vsix → garden-ui-0.14.2.vsix} +0 -0
- package/package.json +3 -2
- package/scripts/build-content.mjs +3 -1
- package/scripts/check-post.mjs +2 -2
- package/scripts/pack-editors.mjs +12 -1
- package/scripts/sync-editor-version.mjs +28 -0
|
@@ -75,7 +75,7 @@ export default async function DraftPreviewPage({ params }: { params: Promise<{ p
|
|
|
75
75
|
草稿预览 · {post}{draft ? " · 草稿" : ""} · <Link href="/admin/ui/blocks">回到交互块</Link>
|
|
76
76
|
</p>
|
|
77
77
|
<h1 className="ui-admin-preview-title">{typeof data.title === "string" ? data.title : post}</h1>
|
|
78
|
-
{typeof data.description === "string" ? <p className="ui-studio-meta">{data.description}</p> : null}
|
|
78
|
+
{typeof data.description === "string" && data.description.trim() ? <p className="ui-studio-meta">{data.description}</p> : null}
|
|
79
79
|
<article className="article-body">
|
|
80
80
|
<MarkdownArticle content={proxyAssets(content, post)} numbering={data.numbering === false ? false : undefined} />
|
|
81
81
|
</article>
|
|
@@ -38,7 +38,7 @@ Three commands check your content at different scopes:
|
|
|
38
38
|
|
|
39
39
|
| Code | Severity | Cause and Fix |
|
|
40
40
|
| :--- | :--- | :--- |
|
|
41
|
-
| `missing-title` / `
|
|
41
|
+
| `missing-title` / `invalid-description` | Error | The title is missing or empty, or description is neither a string nor null; a blank description is valid |
|
|
42
42
|
| `unexpected-slug` | Error | Front Matter includes a manual `slug` field (remove `slug`; the filename is the slug) |
|
|
43
43
|
| `date-format` | Error | `date` is not a valid `YYYY-MM-DD`, `YYYY-MM-DD HH:mm:ss`, or ISO timestamp with timezone |
|
|
44
44
|
| `missing-cover` / `missing-asset` | Error | A local file referenced by `cover`, a Markdown image, or Compose `file` does not exist |
|
|
@@ -36,7 +36,7 @@ references:
|
|
|
36
36
|
| :--- | :--- | :--- | :--- |
|
|
37
37
|
| `title` | Yes | String | Post title. Write ` \| ` (with spaces on both sides) to split the heading into two lines and scale the font size to fit |
|
|
38
38
|
| `date` | Yes | Date string | Publication date (such as `2026-10-18` or an ISO timestamp with timezone) |
|
|
39
|
-
| `description` |
|
|
39
|
+
| `description` | No | String | Summary shown in the article deck, archive lists, and search engine descriptions. Empty (`description: ""`), omitted, null, or whitespace-only values hide the summary; Garden does not substitute an excerpt from the body |
|
|
40
40
|
| `updated` | No | Date string | Last updated date, shown in the article meta bar and used for RSS sorting |
|
|
41
41
|
| `publishAt` | No | Date string | Scheduled publish time; included only when earlier than the build time (or when `CONTENT_INCLUDE_DRAFTS=1`) |
|
|
42
42
|
| `tags` | No | String array | Tags for the post; clicking a tag filters `/blog?t=<tag>` |
|
|
@@ -38,7 +38,7 @@ order: 40
|
|
|
38
38
|
|
|
39
39
|
| 诊断码 | 级别 | 触发原因与修复方法 |
|
|
40
40
|
| :--- | :--- | :--- |
|
|
41
|
-
| `missing-title` / `
|
|
41
|
+
| `missing-title` / `invalid-description` | 错误 | `title` 缺失或为空,或 `description` 不是字符串/空值;摘要可以留空 |
|
|
42
42
|
| `unexpected-slug` | 错误 | Front Matter 中手写了 `slug` 字段(文件名即标识,删掉 `slug`) |
|
|
43
43
|
| `date-format` | 错误 | `date` 不是合法的 `YYYY-MM-DD`、`YYYY-MM-DD HH:mm:ss` 或带时区的 ISO 时间 |
|
|
44
44
|
| `missing-cover` / `missing-asset` | 错误 | `cover` 封面图、正文图片或 Compose `file` 引用的本地文件不存在 |
|
package/docs/handbook/writing.md
CHANGED
|
@@ -36,7 +36,7 @@ references:
|
|
|
36
36
|
| :--- | :--- | :--- | :--- |
|
|
37
37
|
| `title` | 是 | 字符串 | 文章标题。中间写 `丨`、`|` 或 ` \| ` 会分成两行并按字数缩放字号 |
|
|
38
38
|
| `date` | 是 | 日期字符串 | 发布日期(如 `2026-10-18` 或带时区的 ISO 时间) |
|
|
39
|
-
| `description` |
|
|
39
|
+
| `description` | 否 | 字符串 | 文章摘要,显示在正文开头导语、列表页与搜索引擎摘要中;留空(如 `description: ""`)、省略、YAML `null` 或只写空白时,不渲染摘要,也不会自动截取正文替代 |
|
|
40
40
|
| `updated` | 否 | 日期字符串 | 最后更新日期,显示在文章元信息栏并用于 RSS 排序 |
|
|
41
41
|
| `publishAt` | 否 | 日期字符串 | 定时发布时间;早于当前构建时间(或设置 `CONTENT_INCLUDE_DRAFTS=1`)才会发布 |
|
|
42
42
|
| `tags` | 否 | 字符串数组 | 文章标签列表,点击标签会跳转到 `/blog?t=<标签>` 筛选 |
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hd-garden",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "用 Markdown 写作、用 GitHub Pages 发布的个人博客引擎。",
|
|
6
6
|
"license": "MIT",
|
|
@@ -71,7 +71,8 @@
|
|
|
71
71
|
"editors:build": "node editors/vscode/build.mjs && node editors/obsidian/build.mjs && node editors/typora/build.mjs",
|
|
72
72
|
"editors:pack": "node scripts/pack-editors.mjs",
|
|
73
73
|
"prepack": "node scripts/pack-editors.mjs",
|
|
74
|
-
"lint": "node bin/garden.mjs sync && eslint . --ignore-pattern dist --ignore-pattern .next --ignore-pattern .garden --ignore-pattern templates"
|
|
74
|
+
"lint": "node bin/garden.mjs sync && eslint . --ignore-pattern dist --ignore-pattern .next --ignore-pattern .garden --ignore-pattern templates",
|
|
75
|
+
"version": "node scripts/sync-editor-version.mjs && git add editors/vscode/package.json editors/vscode/package-lock.json"
|
|
75
76
|
},
|
|
76
77
|
"dependencies": {
|
|
77
78
|
"@cloudflare/vite-plugin": "1.37.1",
|
|
@@ -409,7 +409,9 @@ function validateAndNormalize(data, content, markdownPath, kind, sourcePath, lin
|
|
|
409
409
|
}
|
|
410
410
|
|
|
411
411
|
if (kind === "post") {
|
|
412
|
-
|
|
412
|
+
const description = optionalString(data, "description", sourcePath);
|
|
413
|
+
// An explicit blank is intentional: never replace it with an excerpt from the body.
|
|
414
|
+
if (!description) data.description = "";
|
|
413
415
|
}
|
|
414
416
|
if (kind === "thought" && data.mediaType) {
|
|
415
417
|
if (!["image", "audio", "video", "link", "text"].includes(data.mediaType)) {
|
package/scripts/check-post.mjs
CHANGED
|
@@ -215,8 +215,8 @@ const checkFile = (filePath) => {
|
|
|
215
215
|
if (typeof data.title !== "string" || !data.title.trim()) {
|
|
216
216
|
error(errors, "missing-title", 1, `${sourcePath}: front matter 缺少有效的 title`);
|
|
217
217
|
}
|
|
218
|
-
if (typeof data.description !== "string"
|
|
219
|
-
error(errors, "
|
|
218
|
+
if (data.description != null && typeof data.description !== "string") {
|
|
219
|
+
error(errors, "invalid-description", 1, `${sourcePath}: description 必须是字符串或留空`);
|
|
220
220
|
}
|
|
221
221
|
if (data.slug !== undefined) {
|
|
222
222
|
error(errors, "unexpected-slug", 1, `${sourcePath}: 不要写 slug,文件名就是标识`);
|
package/scripts/pack-editors.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import zlib from "node:zlib";
|
|
6
|
+
import { syncEditorVersion } from "./sync-editor-version.mjs";
|
|
6
7
|
|
|
7
8
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
9
|
const distDir = path.join(root, "editors", "dist");
|
|
@@ -16,7 +17,8 @@ function distReady() {
|
|
|
16
17
|
const vsixDir = path.join(distDir, "vscode");
|
|
17
18
|
const manifest = path.join(distDir, "typora", "manifest.json");
|
|
18
19
|
if (!fs.existsSync(manifest) || !fs.existsSync(vsixDir)) return false;
|
|
19
|
-
|
|
20
|
+
const version = syncEditorVersion({ check: true });
|
|
21
|
+
return fs.existsSync(path.join(vsixDir, `garden-ui-${version}.vsix`));
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
function withPackLock(fn) {
|
|
@@ -166,6 +168,7 @@ function vsixManifest(manifest) {
|
|
|
166
168
|
<Identity Language="en-US" Id="${xmlEscape(manifest.name)}" Version="${xmlEscape(manifest.version)}" Publisher="${xmlEscape(manifest.publisher)}"/>
|
|
167
169
|
<DisplayName>${xmlEscape(manifest.displayName)}</DisplayName>
|
|
168
170
|
<Description xml:space="preserve">${xmlEscape(manifest.description)}</Description>
|
|
171
|
+
<Icon>extension/${xmlEscape(manifest.icon)}</Icon>
|
|
169
172
|
<Tags>${xmlEscape(tags)}</Tags>
|
|
170
173
|
<Categories>${xmlEscape(categories)}</Categories>
|
|
171
174
|
<GalleryFlags>Public</GalleryFlags>
|
|
@@ -179,6 +182,7 @@ function vsixManifest(manifest) {
|
|
|
179
182
|
</Installation>
|
|
180
183
|
<Dependencies/>
|
|
181
184
|
<Assets>
|
|
185
|
+
<Asset Type="Microsoft.VisualStudio.Services.Icons.Default" Path="extension/${xmlEscape(manifest.icon)}" Addressable="true"/>
|
|
182
186
|
<Asset Type="Microsoft.VisualStudio.Code.Manifest" Path="extension/package.json" Addressable="true"/>
|
|
183
187
|
</Assets>
|
|
184
188
|
</PackageManifest>
|
|
@@ -188,6 +192,13 @@ function vsixManifest(manifest) {
|
|
|
188
192
|
function packageVsix() {
|
|
189
193
|
const source = path.join(root, "editors", "vscode");
|
|
190
194
|
const manifest = JSON.parse(fs.readFileSync(path.join(source, "package.json"), "utf8"));
|
|
195
|
+
syncEditorVersion({ check: true });
|
|
196
|
+
if (manifest.icon !== "dist/icon.png") throw new Error("VS Code extension icon must be dist/icon.png");
|
|
197
|
+
const icon = fs.readFileSync(path.join(source, manifest.icon));
|
|
198
|
+
if (icon.subarray(0, 8).toString("hex") !== "89504e470d0a1a0a" || icon.readUInt32BE(16) < 128 || icon.readUInt32BE(20) < 128)
|
|
199
|
+
throw new Error("VS Code extension icon must be PNG, at least 128 × 128.");
|
|
200
|
+
if (!icon.equals(fs.readFileSync(path.join(root, "public/apple-touch-icon.png"))))
|
|
201
|
+
throw new Error("VS Code extension icon must reuse Garden's canonical public/apple-touch-icon.png.");
|
|
191
202
|
const entries = [];
|
|
192
203
|
const license = path.join(root, "LICENSE");
|
|
193
204
|
if (fs.existsSync(license)) entries.push({ name: "extension/LICENSE", data: fs.readFileSync(license) });
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const defaultRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
6
|
+
|
|
7
|
+
/** Root Garden version is authoritative. npm version runs this before its release commit. */
|
|
8
|
+
export function syncEditorVersion({ root = defaultRoot, check = false } = {}) {
|
|
9
|
+
const version = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")).version;
|
|
10
|
+
if (!/^\d+\.\d+\.\d+$/u.test(version)) throw new Error("Garden editor releases require a stable major.minor.patch version.");
|
|
11
|
+
for (const name of ["package.json", "package-lock.json"]) {
|
|
12
|
+
const file = path.join(root, "editors/vscode", name);
|
|
13
|
+
const value = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
14
|
+
const versions = name === "package-lock.json" ? [value, value.packages[""]] : [value];
|
|
15
|
+
if (versions.every((item) => item.version === version)) continue;
|
|
16
|
+
if (check) throw new Error(`${file} must match Garden ${version}; run node scripts/sync-editor-version.mjs and commit both editor manifests.`);
|
|
17
|
+
for (const item of versions) item.version = version;
|
|
18
|
+
fs.writeFileSync(file, JSON.stringify(value, null, 2) + "\n");
|
|
19
|
+
}
|
|
20
|
+
return version;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
24
|
+
const version = syncEditorVersion({ check: process.argv.includes("--check") });
|
|
25
|
+
if (process.env.GITHUB_REF_TYPE === "tag" && process.env.GITHUB_REF_NAME !== `v${version}`)
|
|
26
|
+
throw new Error(`Release tag ${process.env.GITHUB_REF_NAME} does not match Garden v${version}`);
|
|
27
|
+
process.stdout.write(`Garden / VS Code extension version: ${version}\n`);
|
|
28
|
+
}
|