hd-garden 0.7.0 → 0.8.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-ui/SKILL.md +26 -19
- package/README.en.md +57 -0
- package/README.md +31 -46
- package/app/blog/[...path]/page.tsx +9 -1
- package/app/columns/[column]/[...path]/page.tsx +10 -12
- package/app/components/ArticlePager.tsx +32 -0
- package/app/components/Comments.tsx +2 -1
- package/app/components/FeedMore.tsx +70 -0
- package/app/components/GardenDocsNav.tsx +25 -20
- package/app/components/HandbookLangRedirect.tsx +30 -0
- package/app/components/HandbookLangSwitch.tsx +49 -0
- package/app/components/HandbookView.tsx +187 -0
- package/app/components/MarkdownArticle.tsx +11 -5
- package/app/components/MarkdownImage.tsx +6 -4
- package/app/components/SiteChrome.tsx +11 -2
- package/app/components/SiteFooter.tsx +5 -3
- package/app/components/SiteHeader.tsx +27 -23
- package/app/components/TableOfContents.tsx +15 -9
- package/app/components/ThoughtFeed.tsx +41 -0
- package/app/components/UiLang.tsx +18 -0
- package/app/components/Webmentions.tsx +3 -2
- package/app/components/garden/FilePreview.tsx +13 -11
- package/app/components/garden/GardenBlock.tsx +3 -1
- package/app/components/garden/GardenFile.tsx +9 -7
- package/app/components/garden/blocks.tsx +34 -24
- package/app/components/garden/chrome.tsx +20 -13
- package/app/garden/[...slug]/page.tsx +10 -130
- package/app/garden/en/[...slug]/page.tsx +28 -0
- package/app/garden/en/page.tsx +16 -0
- package/app/garden/page.tsx +7 -74
- package/app/garden-modules.d.ts +3 -0
- package/app/layout.tsx +2 -4
- package/app/lib/content.ts +12 -2
- package/app/lib/garden-docs-meta.ts +60 -5
- package/app/lib/handbook-path.ts +11 -0
- package/app/lib/i18n.ts +222 -0
- package/app/lib/page-alternates.ts +2 -2
- package/app/not-found.tsx +5 -3
- package/app/site.config.ts +1 -1
- package/app/styles/a2.css +101 -32
- package/app/styles/shell.css +6 -0
- package/app/thoughts/page/[n]/page.tsx +44 -0
- package/app/thoughts/page.tsx +2 -6
- package/bin/garden.mjs +4 -0
- package/docs/handbook/ai-writing.md +99 -0
- package/docs/handbook/components/card.md +2 -0
- package/docs/handbook/components/chart.md +2 -0
- package/docs/handbook/components/compare.md +10 -12
- package/docs/handbook/components/figure.md +2 -0
- package/docs/handbook/components/file.md +2 -0
- package/docs/handbook/components/html.md +2 -0
- package/docs/handbook/components/interactive.md +2 -0
- package/docs/handbook/components/stat.md +2 -0
- package/docs/handbook/components/timeline.md +2 -0
- package/docs/handbook/components.md +1 -1
- package/docs/handbook/config.md +2 -2
- package/docs/handbook/deploy.md +1 -1
- package/docs/handbook/design.md +3 -3
- package/docs/handbook/en/ai-writing.md +99 -0
- package/docs/handbook/en/components/card.md +46 -0
- package/docs/handbook/en/components/chart.md +54 -0
- package/docs/handbook/en/components/compare.md +38 -0
- package/docs/handbook/en/components/figure.md +72 -0
- package/docs/handbook/en/components/file/checklist.md +17 -0
- package/docs/handbook/en/components/file.md +34 -0
- package/docs/handbook/en/components/html.md +69 -0
- package/docs/handbook/en/components/interactive.md +67 -0
- package/docs/handbook/en/components/stat.md +57 -0
- package/docs/handbook/en/components/timeline.md +52 -0
- package/docs/handbook/en/components.md +30 -0
- package/docs/handbook/en/config.md +125 -0
- package/docs/handbook/en/deploy.md +273 -0
- package/docs/handbook/en/design.md +81 -0
- package/docs/handbook/en/index.md +192 -0
- package/docs/handbook/en/start.md +92 -0
- package/docs/handbook/en/workflow.md +81 -0
- package/docs/handbook/en/writing.md +156 -0
- package/docs/handbook/index.md +98 -3
- package/docs/handbook/writing.md +18 -10
- package/package.json +1 -1
- package/scripts/build-content.mjs +23 -3
- package/scripts/handbook-facts.mjs +34 -0
- package/scripts/html-lang.mjs +36 -0
- package/scripts/static-404.mjs +25 -0
- package/scripts/ui-cli.mjs +14 -8
- package/templates/site/site.config.yaml +2 -0
- package/ui/core/apply.mjs +22 -4
- package/ui/core/cli-locale.mjs +67 -0
- package/ui/core/fence.mjs +18 -3
- package/ui/core/fix-prompt.mjs +18 -15
- package/ui/core/garden-fence.mjs +64 -0
- package/ui/core/scan.mjs +19 -3
- package/ui/garden/adjacent.mjs +21 -0
- package/ui/garden/check.mjs +6 -0
- package/ui/garden/html-lang.mjs +16 -0
- package/ui/garden/paging.mjs +30 -0
- package/ui/preview/dist/garden-ui.css +9 -9
- package/ui/preview/dist/garden-ui.esm.js +169 -169
- package/ui/preview/dist/garden-ui.iife.js +169 -169
- package/ui/react/components/chart.tsx +7 -4
- package/ui/react/components/display.tsx +6 -3
- package/ui/react/components/inputs.tsx +6 -3
- package/ui/react/components/layout.tsx +6 -4
- package/ui/react/components/media.tsx +7 -4
- package/ui/react/components/sandbox.tsx +3 -1
- package/ui/react/index.tsx +11 -7
- package/ui/react/lang.tsx +85 -0
- package/ui/react/runtime.tsx +3 -1
- package/ui/react/ui.css +9 -9
- package/worker/index.ts +8 -1
package/app/lib/i18n.ts
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { siteConfig } from "../site.config";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Framework chrome strings. The site language is chosen at build time with
|
|
5
|
+
* `locale` in site.config.yaml (zh_CN, en_US, ...); there is no runtime switch
|
|
6
|
+
* for the blog. The /garden/ handbook is the one bilingual area: its pages pass
|
|
7
|
+
* their own language explicitly.
|
|
8
|
+
*/
|
|
9
|
+
export type UiLang = "zh" | "en";
|
|
10
|
+
|
|
11
|
+
export const langOf = (locale: string | undefined): UiLang => (/^en/i.test(locale ?? "") ? "en" : "zh");
|
|
12
|
+
export const siteLang: UiLang = langOf(siteConfig.locale);
|
|
13
|
+
export const htmlLang = (lang: UiLang) => (lang === "en" ? "en" : "zh-CN");
|
|
14
|
+
|
|
15
|
+
const zh = {
|
|
16
|
+
toc: "本篇目录",
|
|
17
|
+
tocShort: "目录",
|
|
18
|
+
tocPage: "本页目录",
|
|
19
|
+
open: "打开",
|
|
20
|
+
close: "关闭",
|
|
21
|
+
currentSection: ",当前章节:",
|
|
22
|
+
prev: "上一篇",
|
|
23
|
+
next: "下一篇",
|
|
24
|
+
pagerLabel: "上一篇和下一篇",
|
|
25
|
+
columnPagerLabel: "专栏里的上一篇和下一篇",
|
|
26
|
+
comments: "评论",
|
|
27
|
+
webmentions: "来自开放网络的回应",
|
|
28
|
+
webmentionsLead: "引用、喜欢、转发和站外回复会通过 Webmention 回到这里。",
|
|
29
|
+
notFoundTitle: "找不到这一页",
|
|
30
|
+
notFoundDeck: "地址可能写错了,或者这篇已经不在了。",
|
|
31
|
+
backHome: "返回首页",
|
|
32
|
+
builtWith: ["基于 ", " 构建"],
|
|
33
|
+
search: "搜索",
|
|
34
|
+
searchArticles: "搜索文章",
|
|
35
|
+
toLight: "切换到浅色模式",
|
|
36
|
+
toDark: "切换到深色模式",
|
|
37
|
+
wordCount: (n: number) => `约 ${n.toLocaleString("en-US")} 字`,
|
|
38
|
+
dateLocale: "zh-CN",
|
|
39
|
+
handbook: "Garden 使用说明",
|
|
40
|
+
overview: "总览",
|
|
41
|
+
langSwitch: "切换语言",
|
|
42
|
+
nextPage: "下一页",
|
|
43
|
+
prevPage: "上一页",
|
|
44
|
+
loadingMore: "正在加载…",
|
|
45
|
+
loadFailed: "没能加载,点这里重试",
|
|
46
|
+
feedEnd: "已经到底了",
|
|
47
|
+
pageOf: (page: number, total: number) => `第 ${page} 页,共 ${total} 页`,
|
|
48
|
+
skipToContent: "跳到正文",
|
|
49
|
+
copyCode: "复制代码",
|
|
50
|
+
copied: "已复制",
|
|
51
|
+
copiedLive: "已复制到剪贴板",
|
|
52
|
+
diffAdded: "(新增)",
|
|
53
|
+
diffRemoved: "(删除)",
|
|
54
|
+
codeTabs: ["代码", "对比上一版"] as string[],
|
|
55
|
+
codeTabsLabel: "代码",
|
|
56
|
+
demo: "演示",
|
|
57
|
+
fullscreen: "全屏",
|
|
58
|
+
exitFullscreen: "退出全屏",
|
|
59
|
+
chart: "图表",
|
|
60
|
+
dataTable: "查看数据表",
|
|
61
|
+
item: "项目",
|
|
62
|
+
value: "数值",
|
|
63
|
+
part: (n: number) => `部分 ${n}`,
|
|
64
|
+
pickPart: "选择一个部分",
|
|
65
|
+
diagram: "结构图",
|
|
66
|
+
pickAnswer: "先选一个答案。",
|
|
67
|
+
correct: "✓ 答对了",
|
|
68
|
+
wrong: "✗ 再想想",
|
|
69
|
+
bytes: "字节",
|
|
70
|
+
htmlAt: (src: string) => `HTML 在 ${src}。发布时由同名目录读入。`,
|
|
71
|
+
interactive: "交互",
|
|
72
|
+
ideaLabel: "想法 · 发布前编译",
|
|
73
|
+
unknownBlock: (name: string) => `未知 Garden 块:${name}`,
|
|
74
|
+
file: "文件",
|
|
75
|
+
openInTab: "在新标签页打开",
|
|
76
|
+
openNamedInTab: (name: string) => `在新标签页打开 ${name}`,
|
|
77
|
+
download: "下载",
|
|
78
|
+
downloadNamed: (name: string) => `下载 ${name}`,
|
|
79
|
+
downloadFile: "下载文件",
|
|
80
|
+
closePreview: "关闭预览",
|
|
81
|
+
previewFailed: "预览没能载入。",
|
|
82
|
+
retry: "重试",
|
|
83
|
+
previewLoading: "正在载入预览…",
|
|
84
|
+
escClose: "Esc 关闭",
|
|
85
|
+
preview: "预览",
|
|
86
|
+
previewNamed: (name: string) => `预览 ${name}`,
|
|
87
|
+
viewImage: "查看原图",
|
|
88
|
+
zoomImage: (d: string) => `放大查看:${d}`,
|
|
89
|
+
closeImage: "关闭图片预览",
|
|
90
|
+
tableScroll: "表格,可横向滚动",
|
|
91
|
+
tocToggle: (collapsed: boolean, text: string) => `${collapsed ? "展开" : "收起"}「${text}」的子目录`,
|
|
92
|
+
siteNav: "站点导航",
|
|
93
|
+
siteNavMobile: "站点导航(移动端)",
|
|
94
|
+
openMenu: "打开菜单",
|
|
95
|
+
closeMenu: "关闭菜单",
|
|
96
|
+
darkMode: "深色模式",
|
|
97
|
+
searchTitle: "搜索站内内容",
|
|
98
|
+
searchPlaceholder: "搜索标题、主题或正文…",
|
|
99
|
+
searchInput: "模糊搜索",
|
|
100
|
+
closeSearch: "关闭搜索",
|
|
101
|
+
searchResults: "搜索结果",
|
|
102
|
+
resultCount: (n: number) => `${n} 篇相关文章`,
|
|
103
|
+
recent: "最近内容",
|
|
104
|
+
searchFailed: "搜索暂时打不开,请稍后再试。",
|
|
105
|
+
loading: "正在载入…",
|
|
106
|
+
fuzzy: "支持模糊匹配",
|
|
107
|
+
escToClose: "关闭",
|
|
108
|
+
noResults: (q: string) => (q ? `没有找到和「${q}」相关的文章` : "还没有可以搜索的内容"),
|
|
109
|
+
navLabels: { "/blog/": "文章", "/thoughts/": "随想", "/columns/": "专栏", "/tags/": "标签", "/about/": "关于" } as Record<string, string>,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const en: typeof zh = {
|
|
113
|
+
toc: "Contents",
|
|
114
|
+
tocShort: "Contents",
|
|
115
|
+
tocPage: "On this page",
|
|
116
|
+
open: "Open ",
|
|
117
|
+
close: "Close ",
|
|
118
|
+
currentSection: ", current section: ",
|
|
119
|
+
prev: "Previous",
|
|
120
|
+
next: "Next",
|
|
121
|
+
pagerLabel: "Previous and next post",
|
|
122
|
+
columnPagerLabel: "Previous and next in this column",
|
|
123
|
+
comments: "Comments",
|
|
124
|
+
webmentions: "Responses from the open web",
|
|
125
|
+
webmentionsLead: "Mentions, likes, reposts and replies from other sites arrive here through Webmention.",
|
|
126
|
+
notFoundTitle: "Page not found",
|
|
127
|
+
notFoundDeck: "The address may be wrong, or the page has moved.",
|
|
128
|
+
backHome: "Back to home",
|
|
129
|
+
builtWith: ["Built with ", ""],
|
|
130
|
+
search: "Search",
|
|
131
|
+
searchArticles: "Search posts",
|
|
132
|
+
toLight: "Switch to light mode",
|
|
133
|
+
toDark: "Switch to dark mode",
|
|
134
|
+
wordCount: (n: number) => `${n.toLocaleString("en-US")} words`,
|
|
135
|
+
dateLocale: "en-US",
|
|
136
|
+
handbook: "Garden Handbook",
|
|
137
|
+
overview: "Overview",
|
|
138
|
+
langSwitch: "Switch language",
|
|
139
|
+
nextPage: "Next page",
|
|
140
|
+
prevPage: "Previous page",
|
|
141
|
+
loadingMore: "Loading…",
|
|
142
|
+
loadFailed: "Could not load. Tap to retry",
|
|
143
|
+
feedEnd: "That is everything",
|
|
144
|
+
pageOf: (page: number, total: number) => `Page ${page} of ${total}`,
|
|
145
|
+
skipToContent: "Skip to content",
|
|
146
|
+
copyCode: "Copy code",
|
|
147
|
+
copied: "Copied",
|
|
148
|
+
copiedLive: "Copied to clipboard",
|
|
149
|
+
diffAdded: " (added)",
|
|
150
|
+
diffRemoved: " (removed)",
|
|
151
|
+
codeTabs: ["Code", "Diff from previous"],
|
|
152
|
+
codeTabsLabel: "Code",
|
|
153
|
+
demo: "Demo",
|
|
154
|
+
fullscreen: "Full screen",
|
|
155
|
+
exitFullscreen: "Exit full screen",
|
|
156
|
+
chart: "Chart",
|
|
157
|
+
dataTable: "Show data table",
|
|
158
|
+
item: "Item",
|
|
159
|
+
value: "Value",
|
|
160
|
+
part: (n: number) => `Part ${n}`,
|
|
161
|
+
pickPart: "Choose a part",
|
|
162
|
+
diagram: "Diagram",
|
|
163
|
+
pickAnswer: "Pick an answer first.",
|
|
164
|
+
correct: "✓ Correct",
|
|
165
|
+
wrong: "✗ Try again",
|
|
166
|
+
bytes: "bytes",
|
|
167
|
+
htmlAt: (src: string) => `HTML lives in ${src}. It is read from the folder of the same name at publish time.`,
|
|
168
|
+
interactive: "Interactive",
|
|
169
|
+
ideaLabel: "Idea · compiled before publishing",
|
|
170
|
+
unknownBlock: (name: string) => `Unknown Garden block: ${name}`,
|
|
171
|
+
file: "File",
|
|
172
|
+
openInTab: "Open in new tab",
|
|
173
|
+
openNamedInTab: (name: string) => `Open ${name} in a new tab`,
|
|
174
|
+
download: "Download",
|
|
175
|
+
downloadNamed: (name: string) => `Download ${name}`,
|
|
176
|
+
downloadFile: "Download file",
|
|
177
|
+
closePreview: "Close preview",
|
|
178
|
+
previewFailed: "The preview could not load.",
|
|
179
|
+
retry: "Retry",
|
|
180
|
+
previewLoading: "Loading preview…",
|
|
181
|
+
escClose: "Esc to close",
|
|
182
|
+
preview: "Preview",
|
|
183
|
+
previewNamed: (name: string) => `Preview ${name}`,
|
|
184
|
+
viewImage: "View full image",
|
|
185
|
+
zoomImage: (d: string) => `Enlarge: ${d}`,
|
|
186
|
+
closeImage: "Close image preview",
|
|
187
|
+
tableScroll: "Table, scrolls horizontally",
|
|
188
|
+
tocToggle: (collapsed: boolean, text: string) => `${collapsed ? "Expand" : "Collapse"} subsections of “${text}”`,
|
|
189
|
+
siteNav: "Site navigation",
|
|
190
|
+
siteNavMobile: "Site navigation (mobile)",
|
|
191
|
+
openMenu: "Open menu",
|
|
192
|
+
closeMenu: "Close menu",
|
|
193
|
+
darkMode: "Dark mode",
|
|
194
|
+
searchTitle: "Search this site",
|
|
195
|
+
searchPlaceholder: "Search titles, topics or text…",
|
|
196
|
+
searchInput: "Fuzzy search",
|
|
197
|
+
closeSearch: "Close search",
|
|
198
|
+
searchResults: "Search results",
|
|
199
|
+
resultCount: (n: number) => `${n} matching ${n === 1 ? "post" : "posts"}`,
|
|
200
|
+
recent: "Recent",
|
|
201
|
+
searchFailed: "Search is unavailable right now. Please try again later.",
|
|
202
|
+
loading: "Loading…",
|
|
203
|
+
fuzzy: "Fuzzy matching",
|
|
204
|
+
escToClose: "to close",
|
|
205
|
+
noResults: (q: string) => (q ? `No posts match “${q}”` : "Nothing to search yet"),
|
|
206
|
+
navLabels: { "/blog/": "Posts", "/thoughts/": "Thoughts", "/columns/": "Columns", "/tags/": "Tags", "/about/": "About" },
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export const UI_STRINGS = { zh, en } as const;
|
|
210
|
+
export const ui = (lang: UiLang = siteLang) => UI_STRINGS[lang];
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Chrome language for a route. The handbook follows its own URL (/garden/en/
|
|
214
|
+
* is English, the rest of /garden/ is Chinese); every other page uses the site
|
|
215
|
+
* language. `pathname` is the in-app path (no basePath), as usePathname gives.
|
|
216
|
+
*/
|
|
217
|
+
export function routeLang(pathname: string | null | undefined): UiLang {
|
|
218
|
+
const path = pathname || "/";
|
|
219
|
+
if (/^\/garden\/en(\/|$)/u.test(path)) return "en";
|
|
220
|
+
if (/^\/garden(\/|$)/u.test(path)) return "zh";
|
|
221
|
+
return siteLang;
|
|
222
|
+
}
|
|
@@ -9,6 +9,6 @@ export const FEED_ALTERNATES = { "application/rss+xml": "/rss.xml" } as const;
|
|
|
9
9
|
* resolved against metadataBase, plus the feed. A page that sets `alternates`
|
|
10
10
|
* replaces the layout's, so the feed link is repeated here.
|
|
11
11
|
*/
|
|
12
|
-
export function pageAlternates(pathname: string) {
|
|
13
|
-
return { canonical: siteHref(pathname), types: FEED_ALTERNATES };
|
|
12
|
+
export function pageAlternates(pathname: string, languages?: Record<string, string>) {
|
|
13
|
+
return { canonical: siteHref(pathname), types: FEED_ALTERNATES, ...(languages ? { languages } : {}) };
|
|
14
14
|
}
|
package/app/not-found.tsx
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import Link from "next/link";
|
|
2
|
+
import { ui } from "./lib/i18n";
|
|
2
3
|
|
|
3
4
|
export default function NotFound() {
|
|
5
|
+
const t = ui();
|
|
4
6
|
return (
|
|
5
7
|
<main className="page-shell">
|
|
6
8
|
<header className="sh-head">
|
|
7
|
-
<h1
|
|
8
|
-
<p className="sh-deck"
|
|
9
|
+
<h1>{t.notFoundTitle}</h1>
|
|
10
|
+
<p className="sh-deck">{t.notFoundDeck}</p>
|
|
9
11
|
</header>
|
|
10
|
-
<p><Link href="/"
|
|
12
|
+
<p><Link href="/">{t.backHome}</Link></p>
|
|
11
13
|
</main>
|
|
12
14
|
);
|
|
13
15
|
}
|
package/app/site.config.ts
CHANGED
package/app/styles/a2.css
CHANGED
|
@@ -702,7 +702,7 @@ html[data-theme="dark"] .markdown-body > p > img:not([class]) {
|
|
|
702
702
|
}
|
|
703
703
|
|
|
704
704
|
.markdown-body .fig-card {
|
|
705
|
-
background:
|
|
705
|
+
background: transparent;
|
|
706
706
|
border: 1px solid var(--rule);
|
|
707
707
|
border-radius: 16px;
|
|
708
708
|
padding: 12px;
|
|
@@ -886,7 +886,7 @@ html[data-theme="dark"] .markdown-body > p > img:not([class]) {
|
|
|
886
886
|
border: 1px solid var(--rule);
|
|
887
887
|
border-radius: var(--r);
|
|
888
888
|
padding: 12px 14px 14px;
|
|
889
|
-
background:
|
|
889
|
+
background: transparent;
|
|
890
890
|
max-width: 100%;
|
|
891
891
|
}
|
|
892
892
|
|
|
@@ -964,7 +964,7 @@ html[data-theme="dark"] .markdown-body > p > img:not([class]) {
|
|
|
964
964
|
.g-html {
|
|
965
965
|
border: 1px solid var(--rule);
|
|
966
966
|
border-radius: var(--r);
|
|
967
|
-
background:
|
|
967
|
+
background: transparent;
|
|
968
968
|
}
|
|
969
969
|
|
|
970
970
|
.g-card,
|
|
@@ -1111,7 +1111,7 @@ html[data-theme="dark"] .markdown-body > p > img:not([class]) {
|
|
|
1111
1111
|
width: 10px;
|
|
1112
1112
|
height: 10px;
|
|
1113
1113
|
border-radius: 50%;
|
|
1114
|
-
background: var(--
|
|
1114
|
+
background: var(--paper);
|
|
1115
1115
|
border: 2px solid var(--blue);
|
|
1116
1116
|
}
|
|
1117
1117
|
|
|
@@ -1238,6 +1238,37 @@ html[data-theme="dark"] .markdown-body > p > img:not([class]) {
|
|
|
1238
1238
|
background: transparent;
|
|
1239
1239
|
}
|
|
1240
1240
|
|
|
1241
|
+
/* One diff line per row; the marker column stays out of the copied text. */
|
|
1242
|
+
.g-diff code > span {
|
|
1243
|
+
display: block;
|
|
1244
|
+
padding: 0 12px 0 0;
|
|
1245
|
+
white-space: pre;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
.g-diff .d-m {
|
|
1249
|
+
display: inline-block;
|
|
1250
|
+
width: 2ch;
|
|
1251
|
+
color: var(--muted);
|
|
1252
|
+
user-select: none;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
.g-diff .d-add {
|
|
1256
|
+
background: var(--tint);
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
.g-diff .d-add .d-m {
|
|
1260
|
+
color: var(--ink);
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
.g-diff .d-del {
|
|
1264
|
+
background: var(--red-tint);
|
|
1265
|
+
color: var(--soft);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
.g-diff .d-del .d-m {
|
|
1269
|
+
color: var(--red-text);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1241
1272
|
.ux-btn {
|
|
1242
1273
|
display: inline-grid;
|
|
1243
1274
|
place-items: center;
|
|
@@ -1246,7 +1277,7 @@ html[data-theme="dark"] .markdown-body > p > img:not([class]) {
|
|
|
1246
1277
|
padding: 0;
|
|
1247
1278
|
border: 1px solid var(--rule);
|
|
1248
1279
|
border-radius: 8px;
|
|
1249
|
-
background:
|
|
1280
|
+
background: transparent;
|
|
1250
1281
|
color: var(--muted);
|
|
1251
1282
|
cursor: pointer;
|
|
1252
1283
|
font: 500 12px / 1 var(--sans);
|
|
@@ -1585,14 +1616,14 @@ html[data-theme="dark"] .hljs-params {
|
|
|
1585
1616
|
.g-quiz label { display: flex; gap: 10px; align-items: center; padding: 8px 0; cursor: pointer; min-height: 44px; }
|
|
1586
1617
|
.g-quiz input { accent-color: var(--blue); width: 18px; height: 18px; margin: 0; }
|
|
1587
1618
|
.g-quiz-row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; margin-top: 12px; }
|
|
1588
|
-
.g-quiz button { font: 600 14px/1 var(--sans); padding: 11px 16px; border-radius: 8px; border: 1px solid var(--rule-strong); background:
|
|
1619
|
+
.g-quiz button { font: 600 14px/1 var(--sans); padding: 11px 16px; border-radius: 8px; border: 1px solid var(--rule-strong); background: transparent; color: var(--ink); cursor: pointer; min-height: 44px; }
|
|
1589
1620
|
.g-quiz-out { margin: 0; font-size: 14px; }
|
|
1590
1621
|
.g-quiz-out.ok { color: #2d6a3e; }
|
|
1591
1622
|
.g-quiz-out.no { color: #b4213a; }
|
|
1592
1623
|
html[data-theme="dark"] .g-quiz-out.ok { color: #8fd1a0; }
|
|
1593
1624
|
html[data-theme="dark"] .g-quiz-out.no { color: #f29aab; }
|
|
1594
1625
|
.g-quiz-ex { margin-top: 8px; font-size: 15px; color: var(--soft); }
|
|
1595
|
-
.g-play { border: 1px solid var(--rule); border-radius: var(--r); padding: 14px 16px; margin: 24px 0; background:
|
|
1626
|
+
.g-play { border: 1px solid var(--rule); border-radius: var(--r); padding: 14px 16px; margin: 24px 0; background: transparent; }
|
|
1596
1627
|
.g-play-f { margin: 0 0 10px; font-size: 14px; color: var(--soft); }
|
|
1597
1628
|
.g-range { display: grid; grid-template-columns: 110px minmax(0, 1fr) 64px; gap: 12px; align-items: center; font-size: 14px; color: var(--muted); margin: 6px 0; }
|
|
1598
1629
|
.g-range input { width: 100%; accent-color: var(--blue); min-height: 32px; }
|
|
@@ -1604,20 +1635,20 @@ html[data-theme="dark"] .g-quiz-out.no { color: #f29aab; }
|
|
|
1604
1635
|
.kv-seg legend { float: left; font-size: 14px; color: var(--soft); margin-right: 10px; min-width: 3.2em; padding: 0; }
|
|
1605
1636
|
.kv-seg label { position: relative; cursor: pointer; }
|
|
1606
1637
|
.kv-seg input { position: absolute; opacity: 0; inset: 0; }
|
|
1607
|
-
.kv-seg span { display: inline-flex; align-items: center; min-height: 44px; padding: 5px 10px; border: 1px solid var(--rule-strong); border-radius: 8px; font-size: 14px; background:
|
|
1638
|
+
.kv-seg span { display: inline-flex; align-items: center; min-height: 44px; padding: 5px 10px; border: 1px solid var(--rule-strong); border-radius: 8px; font-size: 14px; background: transparent; }
|
|
1608
1639
|
.kv-seg input:checked + span { border-color: var(--blue); background: var(--blue-tint); color: var(--blue); }
|
|
1609
1640
|
.kv-seg input:focus-visible + span { outline: 2px solid var(--blue); outline-offset: 2px; }
|
|
1610
1641
|
.dx-tok { font: 500 14px ui-monospace, Menlo, monospace; fill: var(--ink); }
|
|
1611
1642
|
.dx-l { font: 600 13px var(--sans); fill: var(--ink); }
|
|
1612
1643
|
.dx-cap { font: 12px var(--sans); fill: var(--muted); }
|
|
1613
|
-
.dx-k, .dx-v, .dx-q, .dx-o { fill:
|
|
1644
|
+
.dx-k, .dx-v, .dx-q, .dx-o { fill: transparent; stroke: var(--rule-strong); }
|
|
1614
1645
|
.dx-k, .dx-v { fill: var(--blue-tint); stroke: var(--blue-line); }
|
|
1615
|
-
.dx-new { fill:
|
|
1646
|
+
.dx-new { fill: transparent; stroke: var(--blue); stroke-dasharray: 4 3; }
|
|
1616
1647
|
.dx-cache { fill: none; stroke: var(--blue); stroke-width: 1.5; stroke-dasharray: 6 4; }
|
|
1617
1648
|
.dx-attn line { stroke: var(--muted); stroke-width: 1; }
|
|
1618
1649
|
.dx-q { stroke: var(--blue); stroke-width: 2; }
|
|
1619
1650
|
.g-flow .fl-group { fill: none; stroke: var(--rule-strong); stroke-dasharray: 5 4; }
|
|
1620
|
-
.g-flow .fl-n { fill:
|
|
1651
|
+
.g-flow .fl-n { fill: transparent; stroke: var(--rule-strong); stroke-width: 1.2; }
|
|
1621
1652
|
.g-flow .fl-m { fill: var(--blue-tint); stroke: var(--blue-line); }
|
|
1622
1653
|
.g-flow .fl-e line, .g-flow .fl-e1 { stroke: var(--muted); stroke-width: 1.3; fill: none; }
|
|
1623
1654
|
.g-flow .fl-ah { fill: var(--muted); }
|
|
@@ -1659,7 +1690,7 @@ html[data-theme="dark"] .g-quiz-out.no { color: #f29aab; }
|
|
|
1659
1690
|
}
|
|
1660
1691
|
|
|
1661
1692
|
/* ---- garden file: a download card, a group of rows, a preview drawer ---- */
|
|
1662
|
-
.gf { margin: 24px 0; border: 1px solid var(--rule); border-radius: 12px; background:
|
|
1693
|
+
.gf { margin: 24px 0; border: 1px solid var(--rule); border-radius: 12px; background: transparent; font-family: var(--sans); }
|
|
1663
1694
|
.gf-title { padding: 14px 16px 0; font-size: 14px; font-weight: 600; line-height: 1.4; color: var(--muted); }
|
|
1664
1695
|
.gf-row { display: flex; align-items: center; gap: 14px; padding: 14px 16px; }
|
|
1665
1696
|
.gf-row + .gf-row { border-top: 1px solid var(--rule); }
|
|
@@ -1686,7 +1717,7 @@ html[data-theme="dark"] .gf-scrim { background: rgb(0 0 0 / 45%); }
|
|
|
1686
1717
|
.gf-dh { display: flex; align-items: center; gap: 12px; min-height: 64px; padding: 12px 12px 12px 20px; border-bottom: 1px solid var(--rule); }
|
|
1687
1718
|
.gf-dh .gf-act { gap: 2px; }
|
|
1688
1719
|
.gf-db { flex: 1; min-height: 0; overflow: auto; padding: 28px 32px 40px; font-size: 15px; line-height: 1.75; overscroll-behavior: contain; }
|
|
1689
|
-
.gf-db-frame { padding: 0; overflow: hidden; background:
|
|
1720
|
+
.gf-db-frame { padding: 0; overflow: hidden; background: transparent; }
|
|
1690
1721
|
.gf-frame { display: block; width: 100%; height: 100%; border: 0; background: #fff; }
|
|
1691
1722
|
.gf-drawer[data-preview="pdf"] .gf-frame { background: var(--paper-deep); }
|
|
1692
1723
|
/* Focus catchers around the iframe: reachable by Tab, never seen. */
|
|
@@ -1833,25 +1864,10 @@ html[data-theme="dark"] .gf-scrim { background: rgb(0 0 0 / 45%); }
|
|
|
1833
1864
|
}
|
|
1834
1865
|
|
|
1835
1866
|
.gdoc-nav-group-head {
|
|
1836
|
-
|
|
1837
|
-
align-items: baseline;
|
|
1838
|
-
justify-content: space-between;
|
|
1839
|
-
gap: 8px;
|
|
1867
|
+
margin: 0 0 6px;
|
|
1840
1868
|
padding: 0 10px;
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
.gdoc-nav-group-head h2 {
|
|
1845
|
-
margin: 0;
|
|
1846
|
-
font: 600 12px/1.4 var(--sans);
|
|
1847
|
-
letter-spacing: 0.02em;
|
|
1848
|
-
color: var(--muted);
|
|
1849
|
-
}
|
|
1850
|
-
|
|
1851
|
-
.gdoc-nav-group-head span {
|
|
1852
|
-
font: 400 11px/1.3 var(--mono);
|
|
1853
|
-
color: var(--muted);
|
|
1854
|
-
opacity: 0.8;
|
|
1869
|
+
font: 600 15px/1.45 var(--sans);
|
|
1870
|
+
color: var(--ink);
|
|
1855
1871
|
}
|
|
1856
1872
|
|
|
1857
1873
|
.gdoc-nav-list {
|
|
@@ -2008,7 +2024,7 @@ html[data-theme="dark"] .gf-scrim { background: rgb(0 0 0 / 45%); }
|
|
|
2008
2024
|
padding: 16px 18px;
|
|
2009
2025
|
border: 1px solid var(--rule);
|
|
2010
2026
|
border-radius: 12px;
|
|
2011
|
-
background:
|
|
2027
|
+
background: transparent;
|
|
2012
2028
|
text-decoration: none;
|
|
2013
2029
|
transition: border-color 0.15s ease, background-color 0.15s ease;
|
|
2014
2030
|
}
|
|
@@ -2033,6 +2049,10 @@ html[data-theme="dark"] .gf-scrim { background: rgb(0 0 0 / 45%); }
|
|
|
2033
2049
|
}
|
|
2034
2050
|
|
|
2035
2051
|
.gdoc-pager-card strong {
|
|
2052
|
+
display: -webkit-box;
|
|
2053
|
+
overflow: hidden;
|
|
2054
|
+
-webkit-box-orient: vertical;
|
|
2055
|
+
-webkit-line-clamp: 2;
|
|
2036
2056
|
font-size: 15px;
|
|
2037
2057
|
font-weight: 600;
|
|
2038
2058
|
line-height: 1.45;
|
|
@@ -2229,3 +2249,52 @@ html[data-theme="dark"] .gf-scrim { background: rgb(0 0 0 / 45%); }
|
|
|
2229
2249
|
}
|
|
2230
2250
|
}
|
|
2231
2251
|
|
|
2252
|
+
|
|
2253
|
+
/* English handbook pages: same 740px column, tighter leading than Chinese. */
|
|
2254
|
+
.garden-doc-page:lang(en) .markdown-body,
|
|
2255
|
+
.garden-doc-page:lang(en) .gdoc-article-deck {
|
|
2256
|
+
line-height: 1.6;
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
/* Article pager: no rule above (the Webmention block ends the section); the comments keep theirs. */
|
|
2260
|
+
.gdoc-pager.article-pager {
|
|
2261
|
+
border-top: 0;
|
|
2262
|
+
padding-top: 0;
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
/* 随想 feed: the 下一页 link doubles as the loading trigger. */
|
|
2266
|
+
.feed-more {
|
|
2267
|
+
display: flex;
|
|
2268
|
+
justify-content: center;
|
|
2269
|
+
margin: 32px 0 8px;
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
.feed-more a {
|
|
2273
|
+
display: inline-flex;
|
|
2274
|
+
align-items: center;
|
|
2275
|
+
min-height: 40px;
|
|
2276
|
+
padding: 0 18px;
|
|
2277
|
+
border: 1px solid var(--rule);
|
|
2278
|
+
border-radius: 999px;
|
|
2279
|
+
color: var(--soft);
|
|
2280
|
+
font: 400 14px/1 var(--sans);
|
|
2281
|
+
text-decoration: none;
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
.feed-more a:hover {
|
|
2285
|
+
border-color: var(--rule-strong);
|
|
2286
|
+
color: var(--ink);
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
.feed-end {
|
|
2290
|
+
color: var(--muted);
|
|
2291
|
+
font: 400 13px/1 var(--sans);
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
.feed-prev {
|
|
2295
|
+
display: flex;
|
|
2296
|
+
justify-content: space-between;
|
|
2297
|
+
margin: 24px 0 0;
|
|
2298
|
+
color: var(--muted);
|
|
2299
|
+
font: 400 13px/1.4 var(--sans);
|
|
2300
|
+
}
|
package/app/styles/shell.css
CHANGED
|
@@ -437,3 +437,9 @@ html[data-theme="dark"] .sh-repos > li.sk::after{background:linear-gradient(90de
|
|
|
437
437
|
@media (prefers-reduced-motion:reduce){
|
|
438
438
|
.sh-col,.sh-repos > li,.sh-tagchip{transition:none}
|
|
439
439
|
}
|
|
440
|
+
/* Handbook language switch: plain text 「中 / EN」, the current one in ink. */
|
|
441
|
+
.sh-lang{display:inline-flex;align-items:center;height:34px;padding:0 8px;color:var(--muted);font:400 14px/1 var(--sans);text-decoration:none;white-space:nowrap;border-radius:8px}
|
|
442
|
+
.sh-lang [aria-current]{color:var(--ink);font-weight:600}
|
|
443
|
+
a.sh-lang:hover{color:var(--ink)}
|
|
444
|
+
a.sh-lang:focus-visible{outline:2px solid var(--blue);outline-offset:2px}
|
|
445
|
+
.sh-lang.is-disabled{opacity:.55;cursor:default}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { notFound } from "next/navigation";
|
|
2
|
+
import { PageIntro } from "../../../components/PageIntro";
|
|
3
|
+
import { ThoughtFeed, thoughtsPageCount, thoughtsPageSize } from "../../../components/ThoughtFeed";
|
|
4
|
+
import { getPublishedThoughts } from "../../../lib/public-thoughts";
|
|
5
|
+
import { ui } from "../../../lib/i18n";
|
|
6
|
+
import { pageAlternates } from "../../../lib/page-alternates";
|
|
7
|
+
import { siteConfig } from "../../../site.config";
|
|
8
|
+
import { extraPages, pagePath } from "../../../../ui/garden/paging.mjs";
|
|
9
|
+
import { isStaticPlaceholder, STATIC_PLACEHOLDER, staticParams } from "../../../../ui/garden/static-params.mjs";
|
|
10
|
+
|
|
11
|
+
export function generateStaticParams() {
|
|
12
|
+
return staticParams(
|
|
13
|
+
extraPages(getPublishedThoughts().length, thoughtsPageSize()).map((page: number) => ({ n: String(page) })),
|
|
14
|
+
{ n: STATIC_PLACEHOLDER },
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const pageNumber = (value: string) => (/^\d+$/u.test(value) ? Number(value) : NaN);
|
|
19
|
+
|
|
20
|
+
export async function generateMetadata({ params }: { params: Promise<{ n: string }> }) {
|
|
21
|
+
const { n } = await params;
|
|
22
|
+
const page = pageNumber(n);
|
|
23
|
+
if (!(page >= 2 && page <= thoughtsPageCount())) return {};
|
|
24
|
+
return {
|
|
25
|
+
title: `${siteConfig.pages.thoughts.title} · ${ui().pageOf(page, thoughtsPageCount())}`,
|
|
26
|
+
description: siteConfig.pages.thoughts.subtitle || siteConfig.description,
|
|
27
|
+
alternates: pageAlternates(pagePath("/thoughts", page)),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default async function ThoughtsPage({ params }: { params: Promise<{ n: string }> }) {
|
|
32
|
+
const { n } = await params;
|
|
33
|
+
const page = pageNumber(n);
|
|
34
|
+
if (!(page >= 2 && page <= thoughtsPageCount())) {
|
|
35
|
+
if (isStaticPlaceholder(n)) return null;
|
|
36
|
+
notFound();
|
|
37
|
+
}
|
|
38
|
+
return (
|
|
39
|
+
<main className="page-shell thoughts-page">
|
|
40
|
+
<PageIntro eyebrow="THOUGHTS / 随想" title={siteConfig.pages.thoughts.title} subtitle={siteConfig.pages.thoughts.subtitle} />
|
|
41
|
+
<ThoughtFeed page={page} />
|
|
42
|
+
</main>
|
|
43
|
+
);
|
|
44
|
+
}
|
package/app/thoughts/page.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getPublishedThoughts } from "../lib/public-thoughts";
|
|
2
|
-
import {
|
|
2
|
+
import { ThoughtFeed } from "../components/ThoughtFeed";
|
|
3
3
|
import { ContentEmptyState } from "../components/ContentEmptyState";
|
|
4
4
|
import { PageIntro } from "../components/PageIntro";
|
|
5
5
|
import { siteConfig, siteDocumentTitle } from "../site.config";
|
|
@@ -22,11 +22,7 @@ export default function ThoughtsPage() {
|
|
|
22
22
|
subtitle={siteConfig.pages.thoughts.subtitle}
|
|
23
23
|
/>
|
|
24
24
|
{thoughts.length > 0 ? (
|
|
25
|
-
<
|
|
26
|
-
{thoughts.map((thought) => (
|
|
27
|
-
<ThoughtCard key={thought.slug} thought={thought} />
|
|
28
|
-
))}
|
|
29
|
-
</div>
|
|
25
|
+
<ThoughtFeed page={1} />
|
|
30
26
|
) : (
|
|
31
27
|
<ContentEmptyState kind="thoughts" />
|
|
32
28
|
)}
|
package/bin/garden.mjs
CHANGED
|
@@ -398,6 +398,10 @@ switch (command) {
|
|
|
398
398
|
// What the export wrote for empty dynamic routes (ui/garden/static-params.mjs).
|
|
399
399
|
runNode("scripts/static-placeholders.mjs");
|
|
400
400
|
runNode("scripts/redirects.mjs");
|
|
401
|
+
// <html lang="en"> on the English handbook pages.
|
|
402
|
+
runNode("scripts/html-lang.mjs");
|
|
403
|
+
// 404.html also under basePath when the host serves that folder alone.
|
|
404
|
+
runNode("scripts/static-404.mjs");
|
|
401
405
|
}
|
|
402
406
|
break;
|
|
403
407
|
case "links":
|