hd-garden 0.4.1 → 0.5.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.
- package/.agents/skills/garden-interactive-chart/SKILL.md +5 -5
- package/Makefile +2 -3
- package/app/a2/[slug]/page.tsx +0 -1
- package/app/a2/page.tsx +1 -1
- package/app/about/page.tsx +8 -12
- package/app/admin/page.tsx +2 -2
- package/app/blog/[...path]/page.tsx +2 -12
- package/app/blog/page.tsx +2 -2
- package/app/columns/[column]/[...path]/page.tsx +2 -3
- package/app/columns/page.tsx +2 -2
- package/app/components/AdminDashboard.tsx +3 -3
- package/app/components/ArticleMeasure.tsx +114 -42
- package/app/components/ArticleTags.tsx +17 -0
- package/app/components/BlogArchive.tsx +85 -33
- package/app/components/ContentEmptyState.tsx +6 -6
- package/app/components/GardenAdminGuide.tsx +1 -1
- package/app/components/GardenContentGuide.tsx +1 -2
- package/app/components/GardenMediaGuide.tsx +3 -3
- package/app/components/GithubProfile.tsx +1 -1
- package/app/components/MarkdownImage.tsx +4 -2
- package/app/components/SiteFooter.tsx +5 -5
- package/app/components/SiteHeader.tsx +1 -5
- package/app/components/TagRedirect.tsx +17 -0
- package/app/garden/page.tsx +6 -6
- package/app/garden-modules.d.ts +0 -1
- package/app/globals.css +19 -101
- package/app/lib/a2-meta.ts +10 -0
- package/app/lib/a2-preview.ts +0 -2
- package/app/lib/content.ts +10 -7
- package/app/lib/fallback-site-config.ts +15 -15
- package/app/lib/generated-content.ts +58 -47
- package/app/lib/tag-filter.ts +114 -0
- package/app/not-found.tsx +13 -0
- package/app/page.tsx +9 -26
- package/app/styles/a2.css +96 -15
- package/app/styles/shell.css +54 -59
- package/app/styles/tokens.css +35 -22
- package/app/tags/page.tsx +20 -9
- package/package.json +1 -1
- package/scripts/build-content.mjs +3 -3
- package/scripts/check-post.mjs +0 -3
- package/scripts/new-post.mjs +1 -3
- package/site.config.yaml +15 -15
- package/templates/site/Makefile +2 -3
- package/templates/site/site.config.yaml +15 -15
- package/ui/garden/check.mjs +2 -4
- package/ui/garden/enhance.mjs +57 -0
- package/ui/garden/title.mjs +5 -8
- package/ui/host.mjs +13 -13
- package/ui/preview/dist/garden-ui.css +11 -11
- package/ui/preview/dist/garden-ui.esm.js +6 -6
- package/ui/preview/dist/garden-ui.iife.js +7 -7
- package/ui/react/components/media.tsx +2 -2
- package/ui/react/ui.css +11 -11
- package/app/components/TagIndex.tsx +0 -97
package/scripts/new-post.mjs
CHANGED
|
@@ -13,7 +13,6 @@ async function main() {
|
|
|
13
13
|
const titleSlug = title.toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-|-$/gu, "");
|
|
14
14
|
const slug = option("SLUG") || titleSlug || `post-${date.slice(0, 10)}-${randomUUID().slice(0, 8)}`;
|
|
15
15
|
const directory = option("DIR");
|
|
16
|
-
const topic = option("TOPIC") || "未分类";
|
|
17
16
|
|
|
18
17
|
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(slug)) {
|
|
19
18
|
throw new Error("SLUG 只能包含小写字母、数字和连字符,不含 .md 后缀。");
|
|
@@ -43,7 +42,6 @@ async function main() {
|
|
|
43
42
|
`title: ${JSON.stringify(title)}`,
|
|
44
43
|
`date: ${date}`,
|
|
45
44
|
`description: ${JSON.stringify("请填写一句话摘要。")}`,
|
|
46
|
-
`topic: ${JSON.stringify(topic)}`,
|
|
47
45
|
"tags: []",
|
|
48
46
|
"draft: true",
|
|
49
47
|
"---",
|
|
@@ -67,7 +65,7 @@ async function main() {
|
|
|
67
65
|
console.log(`已创建草稿:${path.relative(root, destination)}`);
|
|
68
66
|
console.log(`文章路径:/blog/${[...segments, slug].join("/")}/`);
|
|
69
67
|
console.log("预览:CONTENT_INCLUDE_DRAFTS=1 make dev");
|
|
70
|
-
console.log("
|
|
68
|
+
console.log("发布前请完善摘要、标签与正文,并将 draft 改为 false,再运行 make update。");
|
|
71
69
|
}
|
|
72
70
|
|
|
73
71
|
main().catch((error) => {
|
package/site.config.yaml
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
name: Hedon Garden
|
|
2
|
-
tagline: 写作 ·
|
|
3
|
-
description:
|
|
2
|
+
tagline: 写作 · 记录 · 保持好奇
|
|
3
|
+
description: 文章、随想和专栏。
|
|
4
4
|
locale: zh_CN
|
|
5
5
|
|
|
6
6
|
author:
|
|
7
7
|
name: Your Name
|
|
8
8
|
github: your-github-id
|
|
9
|
-
githubBio:
|
|
9
|
+
githubBio: 一句自我介绍。
|
|
10
10
|
# 可选:本地没有 GITHUB_TOKEN 时用作置顶仓库降级数据。
|
|
11
11
|
githubPinned: []
|
|
12
12
|
|
|
@@ -16,28 +16,28 @@ pages:
|
|
|
16
16
|
title: |-
|
|
17
17
|
记一些正在做的事,
|
|
18
18
|
和想清楚的问题。
|
|
19
|
-
subtitle:
|
|
19
|
+
subtitle: 软件、笔记,和偶尔的工作记录。
|
|
20
20
|
blog:
|
|
21
21
|
title: 博文
|
|
22
|
-
subtitle:
|
|
22
|
+
subtitle: 写完的长文。
|
|
23
23
|
thoughts:
|
|
24
24
|
title: 随想
|
|
25
|
-
subtitle:
|
|
25
|
+
subtitle: 短笔记、图片和链接。
|
|
26
26
|
columns:
|
|
27
27
|
title: 专栏
|
|
28
|
-
subtitle:
|
|
28
|
+
subtitle: 按顺序读的一组文章。
|
|
29
29
|
about:
|
|
30
30
|
title: 你好,我是 Your Name。
|
|
31
|
-
subtitle:
|
|
32
|
-
heading:
|
|
31
|
+
subtitle: 文章和笔记都在这里。
|
|
32
|
+
heading: 关于
|
|
33
33
|
paragraphs:
|
|
34
|
-
-
|
|
35
|
-
-
|
|
34
|
+
- 一名工程师。平时写代码,也把想清楚的问题记下来。
|
|
35
|
+
- 这里放文章、随想和专栏。
|
|
36
36
|
quote: ""
|
|
37
37
|
focus:
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
38
|
+
- 软件
|
|
39
|
+
- 笔记
|
|
40
|
+
- 日常
|
|
41
41
|
cta:
|
|
42
42
|
href: /blog
|
|
43
43
|
label: 浏览博文
|
|
@@ -45,4 +45,4 @@ pages:
|
|
|
45
45
|
title: "用 Markdown 写。\n推送之后,网站自己更新。"
|
|
46
46
|
subtitle: ""
|
|
47
47
|
|
|
48
|
-
footer:
|
|
48
|
+
footer: 持续记录。
|
package/templates/site/Makefile
CHANGED
|
@@ -4,15 +4,14 @@ MESSAGE ?= 更新博客
|
|
|
4
4
|
|
|
5
5
|
.PHONY: help new embed skill prepare dev check build update
|
|
6
6
|
|
|
7
|
-
unexport TITLE SLUG DIR
|
|
7
|
+
unexport TITLE SLUG DIR URL
|
|
8
8
|
export GARDEN_NEW_TITLE := $(value TITLE)
|
|
9
9
|
export GARDEN_NEW_SLUG := $(value SLUG)
|
|
10
10
|
export GARDEN_NEW_DIR := $(value DIR)
|
|
11
|
-
export GARDEN_NEW_TOPIC := $(value TOPIC)
|
|
12
11
|
export GARDEN_EMBED_URL := $(value URL)
|
|
13
12
|
|
|
14
13
|
help:
|
|
15
|
-
@printf "%s\n" "Garden 常用命令:" " make new 用引擎创建文章草稿" ' make new TITLE="文章标题" SLUG=my-post DIR=writing
|
|
14
|
+
@printf "%s\n" "Garden 常用命令:" " make new 用引擎创建文章草稿" ' make new TITLE="文章标题" SLUG=my-post DIR=writing' ' make embed URL="https://example.com" 拉取外部网页摘要' " make skill 安装可交互图表 skill" " make dev 安装依赖并本地预览" " make build 构建公开站点" " make update 检查、提交并触发自动发布"
|
|
16
15
|
|
|
17
16
|
new: prepare
|
|
18
17
|
npx garden new
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
name: Garden
|
|
2
|
-
tagline: 写作 ·
|
|
3
|
-
description:
|
|
2
|
+
tagline: 写作 · 记录 · 保持好奇
|
|
3
|
+
description: 文章、随想和专栏。
|
|
4
4
|
locale: zh_CN
|
|
5
5
|
|
|
6
6
|
author:
|
|
7
7
|
name: Your Name
|
|
8
8
|
github: your-github-id
|
|
9
|
-
githubBio:
|
|
9
|
+
githubBio: 一句自我介绍。
|
|
10
10
|
githubPinned: []
|
|
11
11
|
|
|
12
12
|
pages:
|
|
@@ -14,28 +14,28 @@ pages:
|
|
|
14
14
|
title: |-
|
|
15
15
|
记一些正在做的事,
|
|
16
16
|
和想清楚的问题。
|
|
17
|
-
subtitle:
|
|
17
|
+
subtitle: 软件、笔记,和偶尔的工作记录。
|
|
18
18
|
blog:
|
|
19
19
|
title: 博文
|
|
20
|
-
subtitle:
|
|
20
|
+
subtitle: 写完的长文。
|
|
21
21
|
thoughts:
|
|
22
22
|
title: 随想
|
|
23
|
-
subtitle:
|
|
23
|
+
subtitle: 短笔记、图片和链接。
|
|
24
24
|
columns:
|
|
25
25
|
title: 专栏
|
|
26
|
-
subtitle:
|
|
26
|
+
subtitle: 按顺序读的一组文章。
|
|
27
27
|
about:
|
|
28
28
|
title: 你好,我是 Your Name。
|
|
29
|
-
subtitle:
|
|
30
|
-
heading:
|
|
29
|
+
subtitle: 文章和笔记都在这里。
|
|
30
|
+
heading: 关于
|
|
31
31
|
paragraphs:
|
|
32
|
-
-
|
|
33
|
-
-
|
|
32
|
+
- 一名工程师。平时写代码,也把想清楚的问题记下来。
|
|
33
|
+
- 这里放文章、随想和专栏。
|
|
34
34
|
quote: ""
|
|
35
35
|
focus:
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
36
|
+
- 软件
|
|
37
|
+
- 笔记
|
|
38
|
+
- 日常
|
|
39
39
|
cta:
|
|
40
40
|
href: /blog
|
|
41
41
|
label: 浏览博文
|
|
@@ -43,4 +43,4 @@ pages:
|
|
|
43
43
|
title: "用 Markdown 写。\n推送之后,网站自己更新。"
|
|
44
44
|
subtitle: ""
|
|
45
45
|
|
|
46
|
-
footer:
|
|
46
|
+
footer: 持续记录。
|
package/ui/garden/check.mjs
CHANGED
|
@@ -34,10 +34,8 @@ export function checkGardenMarkdown(markdown, { draft = false, title = "", title
|
|
|
34
34
|
|
|
35
35
|
if (title) {
|
|
36
36
|
const split = splitTitle(title);
|
|
37
|
-
//
|
|
38
|
-
if (split.issue === "
|
|
39
|
-
push(titleLine, "title-split", "标题里有不止一个分隔符,无法分成正好两行。", "只留一个 丨、| 或 \" | \"。", "warning");
|
|
40
|
-
} else if (split.issue === "empty") {
|
|
37
|
+
// More than one separator still splits at the first. Only an empty side stays one line.
|
|
38
|
+
if (split.issue === "empty") {
|
|
41
39
|
push(titleLine, "title-split", "分隔符两侧都要有文字。", "写成「主题丨副题」。", "warning");
|
|
42
40
|
}
|
|
43
41
|
}
|
package/ui/garden/enhance.mjs
CHANGED
|
@@ -74,6 +74,63 @@ export function tableBreakout({
|
|
|
74
74
|
return { left: margin, right: margin };
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/**
|
|
78
|
+
* The width used to decide whether an image widens past the column: its width
|
|
79
|
+
* attribute when it has one, otherwise half its pixels (screenshots are
|
|
80
|
+
* usually 2x). 0 while the image has not loaded. Never more than the image
|
|
81
|
+
* itself, so nothing is enlarged.
|
|
82
|
+
*/
|
|
83
|
+
export function imageDisplayWidth({ naturalWidth = 0, attrWidth = 0, density = 2 }) {
|
|
84
|
+
if (attrWidth > 0) return naturalWidth > 0 ? Math.min(attrWidth, naturalWidth) : attrWidth;
|
|
85
|
+
if (!(naturalWidth > 0)) return 0;
|
|
86
|
+
return Math.round(naturalWidth / density);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The inline width an article image gets, or 0 to leave it to CSS
|
|
91
|
+
* (width:auto; max-width:100%), which shows it at its natural size and caps
|
|
92
|
+
* it at the column, never stretched and never halved. A widened image takes
|
|
93
|
+
* its display width; an image in the column only follows its width attribute,
|
|
94
|
+
* never past its own pixels.
|
|
95
|
+
*/
|
|
96
|
+
export function figureImageWidth({ naturalWidth = 0, attrWidth = 0, widened = false, density = 2 }) {
|
|
97
|
+
if (widened) return imageDisplayWidth({ naturalWidth, attrWidth, density });
|
|
98
|
+
if (attrWidth > 0) return naturalWidth > 0 ? Math.min(attrWidth, naturalWidth) : attrWidth;
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* How far a figure steps out on each side. Screenshots are usually 2x, so an
|
|
104
|
+
* image displays at naturalWidth / density unless it states its own width.
|
|
105
|
+
* A figure widens only when that display width is wider than the column, and
|
|
106
|
+
* never past the display width, maxWidth in total, or the room on screen.
|
|
107
|
+
*/
|
|
108
|
+
export function figureBreakout({
|
|
109
|
+
viewport,
|
|
110
|
+
articleLeft,
|
|
111
|
+
articleRight,
|
|
112
|
+
figureClient,
|
|
113
|
+
imageClient = figureClient,
|
|
114
|
+
naturalWidth = 0,
|
|
115
|
+
attrWidth = 0,
|
|
116
|
+
density = 2,
|
|
117
|
+
maxWidth = 1040,
|
|
118
|
+
minViewport = 900,
|
|
119
|
+
edge = 24,
|
|
120
|
+
}) {
|
|
121
|
+
if (viewport < minViewport) return { left: 0, right: 0 };
|
|
122
|
+
const display = imageDisplayWidth({ naturalWidth, attrWidth, density });
|
|
123
|
+
// Wider than the text column, or it stays in the column.
|
|
124
|
+
if (!(display > figureClient)) return { left: 0, right: 0 };
|
|
125
|
+
const need = Math.floor(display - imageClient);
|
|
126
|
+
if (!(need > 1)) return { left: 0, right: 0 };
|
|
127
|
+
const target = Math.min(figureClient + need, Math.max(maxWidth, figureClient));
|
|
128
|
+
const room = Math.max(0, Math.min(articleLeft - edge, viewport - edge - articleRight));
|
|
129
|
+
const side = Math.min(room, Math.floor((target - figureClient) / 2));
|
|
130
|
+
if (side <= 0) return { left: 0, right: 0 };
|
|
131
|
+
return { left: side, right: side };
|
|
132
|
+
}
|
|
133
|
+
|
|
77
134
|
/**
|
|
78
135
|
* Slide the TOC right of a wide element measured against the TOC's original
|
|
79
136
|
* position. Hide it when the shifted TOC would not fit on screen.
|
package/ui/garden/title.mjs
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
const SEPARATOR = /丨||| \| /gu;
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* A title splits into
|
|
5
|
-
*
|
|
4
|
+
* A title splits into two lines at the first 丨, | or " | ".
|
|
5
|
+
* Later separators stay on the second line. An empty side stays one line.
|
|
6
|
+
* The separator stays in the accessible name.
|
|
6
7
|
*/
|
|
7
8
|
export function splitTitle(title) {
|
|
8
9
|
const text = String(title ?? "");
|
|
9
|
-
const
|
|
10
|
-
if (
|
|
11
|
-
if (matches.length > 1) {
|
|
12
|
-
return { split: false, title: text, lines: [text], separator: null, issue: "multiple" };
|
|
13
|
-
}
|
|
14
|
-
const match = matches[0];
|
|
10
|
+
const match = [...text.matchAll(SEPARATOR)][0];
|
|
11
|
+
if (!match) return { split: false, title: text, lines: [text], separator: null, issue: null };
|
|
15
12
|
const before = text.slice(0, match.index).trim();
|
|
16
13
|
const after = text.slice(match.index + match[0].length).trim();
|
|
17
14
|
const separator = match[0] === " | " ? " | " : match[0];
|
package/ui/host.mjs
CHANGED
|
@@ -61,11 +61,11 @@ const THEMES = {
|
|
|
61
61
|
rule: "#E6E3DC",
|
|
62
62
|
ruleStrong: "#D6D2C9",
|
|
63
63
|
card: "#FFFFFF",
|
|
64
|
-
paper: "#
|
|
65
|
-
well: "#
|
|
66
|
-
blue: "#
|
|
67
|
-
blueTint: "#
|
|
68
|
-
blueLine: "rgba(
|
|
64
|
+
paper: "#FAF9F5",
|
|
65
|
+
well: "#F0EEE6",
|
|
66
|
+
blue: "#30302E",
|
|
67
|
+
blueTint: "#F0EEE6",
|
|
68
|
+
blueLine: "rgba(20,20,19,.22)",
|
|
69
69
|
},
|
|
70
70
|
dark: {
|
|
71
71
|
ink: "#EEECE7",
|
|
@@ -73,14 +73,14 @@ const THEMES = {
|
|
|
73
73
|
soft: "#BDB9B0",
|
|
74
74
|
muted: "#A09B91",
|
|
75
75
|
grey: "#A09B91",
|
|
76
|
-
rule: "#
|
|
77
|
-
ruleStrong: "#
|
|
78
|
-
card: "#
|
|
79
|
-
paper: "#
|
|
80
|
-
well: "#
|
|
81
|
-
blue: "#
|
|
82
|
-
blueTint: "
|
|
83
|
-
blueLine: "rgba(
|
|
76
|
+
rule: "#3D3D3A",
|
|
77
|
+
ruleStrong: "#4D4C48",
|
|
78
|
+
card: "#262624",
|
|
79
|
+
paper: "#1F1E1D",
|
|
80
|
+
well: "#262624",
|
|
81
|
+
blue: "#EEECE7",
|
|
82
|
+
blueTint: "#30302E",
|
|
83
|
+
blueLine: "rgba(238,236,231,.25)",
|
|
84
84
|
},
|
|
85
85
|
};
|
|
86
86
|
|
|
@@ -22,23 +22,23 @@
|
|
|
22
22
|
--gui-control-border: var(--rule-strong, #d6d2c9);
|
|
23
23
|
--gui-check-border: var(--rule-strong, #d6d2c9);
|
|
24
24
|
--gui-control-bg: var(--card, #fff);
|
|
25
|
-
--gui-surface: var(--well, #
|
|
26
|
-
--gui-chip: var(--well, #
|
|
27
|
-
--gui-hover: var(--well, #
|
|
25
|
+
--gui-surface: var(--well, #f0eee6);
|
|
26
|
+
--gui-chip: var(--well, #f0eee6);
|
|
27
|
+
--gui-hover: var(--well, #f0eee6);
|
|
28
28
|
--gui-solid: var(--ink, #1b1a18);
|
|
29
29
|
--gui-solid-hover: var(--soft, #4a4844);
|
|
30
|
-
--gui-on-solid: var(--paper, #
|
|
31
|
-
--gui-segment: var(--well, #
|
|
30
|
+
--gui-on-solid: var(--paper, #faf9f5);
|
|
31
|
+
--gui-segment: var(--well, #f0eee6);
|
|
32
32
|
--gui-segment-selected: var(--card, #fff);
|
|
33
33
|
--gui-segment-shadow: none;
|
|
34
|
-
--gui-progress: var(--blue, #
|
|
35
|
-
--gui-progress-track: var(--well, #
|
|
36
|
-
--gui-focus: var(--blue, #
|
|
37
|
-
--gui-bubble: var(--blue-tint, #
|
|
34
|
+
--gui-progress: var(--blue, #30302e);
|
|
35
|
+
--gui-progress-track: var(--well, #f0eee6);
|
|
36
|
+
--gui-focus: var(--blue, #30302e);
|
|
37
|
+
--gui-bubble: var(--blue-tint, #f0eee6);
|
|
38
38
|
--gui-popover-border: var(--rule, #e6e3dc);
|
|
39
39
|
--gui-shadow: none;
|
|
40
|
-
--gui-code-bg: var(--well, #
|
|
41
|
-
--gui-accent: var(--blue, #
|
|
40
|
+
--gui-code-bg: var(--well, #f0eee6);
|
|
41
|
+
--gui-accent: var(--blue, #30302e);
|
|
42
42
|
--gui-series-plain: var(--ink, #1b1a18);
|
|
43
43
|
--gui-series-accent: var(--blue);
|
|
44
44
|
--gui-series-new: #00a240;
|