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
|
@@ -1,33 +1,9 @@
|
|
|
1
|
-
import Link from "next/link";
|
|
2
|
-
import type { Metadata } from "next";
|
|
3
1
|
import { notFound } from "next/navigation";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
gardenDocs,
|
|
10
|
-
} from "../../lib/content";
|
|
11
|
-
import { FEED_ALTERNATES } from "../../lib/page-alternates";
|
|
12
|
-
import { absoluteUrl, getSiteUrl } from "../../lib/site";
|
|
13
|
-
import { siteHref } from "../../lib/site-href";
|
|
14
|
-
import { ArticleMeasure } from "../../components/ArticleMeasure";
|
|
15
|
-
import { ArticleReferences } from "../../components/ArticleReferences";
|
|
16
|
-
import { ArticleTitle } from "../../components/ArticleTitle";
|
|
17
|
-
import { MarkdownArticle } from "../../components/MarkdownArticle";
|
|
18
|
-
import { TableOfContents } from "../../components/TableOfContents";
|
|
19
|
-
import { GardenDocsNav } from "../../components/GardenDocsNav";
|
|
20
|
-
import {
|
|
21
|
-
COMPONENT_BADGE,
|
|
22
|
-
HANDBOOK_SECTIONS,
|
|
23
|
-
orderedHandbookDocs,
|
|
24
|
-
toHandbookNavItems,
|
|
25
|
-
} from "../../lib/garden-docs-meta";
|
|
26
|
-
import {
|
|
27
|
-
isStaticPlaceholder,
|
|
28
|
-
STATIC_PLACEHOLDER,
|
|
29
|
-
staticParams,
|
|
30
|
-
} from "../../../ui/garden/static-params.mjs";
|
|
2
|
+
import { HandbookDoc, handbookDocMetadata } from "../../components/HandbookView";
|
|
3
|
+
import { gardenDocs } from "../../lib/content";
|
|
4
|
+
import { isStaticPlaceholder, STATIC_PLACEHOLDER, staticParams } from "../../../ui/garden/static-params.mjs";
|
|
5
|
+
|
|
6
|
+
const find = (slug: string[]) => gardenDocs.find((doc) => doc.path === slug.join("/"));
|
|
31
7
|
|
|
32
8
|
export function generateStaticParams() {
|
|
33
9
|
return staticParams(
|
|
@@ -36,113 +12,17 @@ export function generateStaticParams() {
|
|
|
36
12
|
);
|
|
37
13
|
}
|
|
38
14
|
|
|
39
|
-
export async function generateMetadata({
|
|
40
|
-
params,
|
|
41
|
-
}: {
|
|
42
|
-
params: Promise<{ slug: string[] }>;
|
|
43
|
-
}) {
|
|
15
|
+
export async function generateMetadata({ params }: { params: Promise<{ slug: string[] }> }) {
|
|
44
16
|
const { slug } = await params;
|
|
45
|
-
|
|
46
|
-
if (!doc) return {};
|
|
47
|
-
const baseUrl = await getSiteUrl();
|
|
48
|
-
const pathname = gardenDocHref(doc);
|
|
49
|
-
return {
|
|
50
|
-
title: `${doc.title} · Garden 使用说明`,
|
|
51
|
-
description: doc.description,
|
|
52
|
-
alternates: { canonical: pathname, types: FEED_ALTERNATES },
|
|
53
|
-
openGraph: {
|
|
54
|
-
type: "article",
|
|
55
|
-
url: absoluteUrl(pathname, baseUrl),
|
|
56
|
-
title: `${doc.title} · Garden 使用说明`,
|
|
57
|
-
description: doc.description,
|
|
58
|
-
},
|
|
59
|
-
} satisfies Metadata;
|
|
17
|
+
return handbookDocMetadata("zh", find(slug));
|
|
60
18
|
}
|
|
61
19
|
|
|
62
|
-
export default async function GardenDocPage({
|
|
63
|
-
params,
|
|
64
|
-
}: {
|
|
65
|
-
params: Promise<{ slug: string[] }>;
|
|
66
|
-
}) {
|
|
20
|
+
export default async function GardenDocPage({ params }: { params: Promise<{ slug: string[] }> }) {
|
|
67
21
|
const { slug } = await params;
|
|
68
|
-
const doc =
|
|
22
|
+
const doc = find(slug);
|
|
69
23
|
if (!doc) {
|
|
70
24
|
if (isStaticPlaceholder(slug)) return null;
|
|
71
25
|
notFound();
|
|
72
26
|
}
|
|
73
|
-
|
|
74
|
-
const ordered = orderedHandbookDocs(gardenDocs);
|
|
75
|
-
const navItems = toHandbookNavItems(gardenDocs);
|
|
76
|
-
const index = ordered.findIndex((item) => item.path === doc.path);
|
|
77
|
-
const prev = index > 0 ? ordered[index - 1] : null;
|
|
78
|
-
const next = index >= 0 && index < ordered.length - 1 ? ordered[index + 1] : null;
|
|
79
|
-
const headings = extractHeadings(doc.content);
|
|
80
|
-
const sectionMeta = HANDBOOK_SECTIONS.find((sec) => sec.id === doc.section);
|
|
81
|
-
const componentBadge = COMPONENT_BADGE[doc.path];
|
|
82
|
-
|
|
83
|
-
return (
|
|
84
|
-
<main className="column-reading-page garden-doc-page">
|
|
85
|
-
<GardenDocsNav docs={navItems} currentPath={doc.path} />
|
|
86
|
-
|
|
87
|
-
<article className="article-column garden-doc-article h-entry">
|
|
88
|
-
<header className="gdoc-article-head">
|
|
89
|
-
<div className="gdoc-eyebrow">
|
|
90
|
-
<Link href={siteHref("/garden")} className="gdoc-eyebrow-root">
|
|
91
|
-
Garden 使用说明
|
|
92
|
-
</Link>
|
|
93
|
-
<span aria-hidden="true">/</span>
|
|
94
|
-
<span>{sectionMeta?.title ?? doc.sectionTitle ?? "指南"}</span>
|
|
95
|
-
{componentBadge ? (
|
|
96
|
-
<>
|
|
97
|
-
<code className="gdoc-eyebrow-fence">{`garden ${componentBadge}`}</code>
|
|
98
|
-
</>
|
|
99
|
-
) : null}
|
|
100
|
-
</div>
|
|
101
|
-
<ArticleTitle title={doc.title} />
|
|
102
|
-
{doc.description ? (
|
|
103
|
-
<p className="gdoc-article-deck p-summary">{doc.description}</p>
|
|
104
|
-
) : null}
|
|
105
|
-
</header>
|
|
106
|
-
|
|
107
|
-
<ArticleMeasure />
|
|
108
|
-
|
|
109
|
-
<div className="e-content">
|
|
110
|
-
<MarkdownArticle content={doc.content} />
|
|
111
|
-
</div>
|
|
112
|
-
|
|
113
|
-
<ArticleReferences references={doc.references} />
|
|
114
|
-
|
|
115
|
-
<nav className="gdoc-pager" aria-label="说明书上下篇导航">
|
|
116
|
-
{prev ? (
|
|
117
|
-
<Link href={gardenDocHref(prev)} className="gdoc-pager-card is-prev">
|
|
118
|
-
<span className="gdoc-pager-dir">
|
|
119
|
-
<ArrowLeft size={14} aria-hidden="true" />
|
|
120
|
-
上一篇
|
|
121
|
-
</span>
|
|
122
|
-
<strong>{prev.title}</strong>
|
|
123
|
-
</Link>
|
|
124
|
-
) : (
|
|
125
|
-
<Link href={siteHref("/garden")} className="gdoc-pager-card is-prev">
|
|
126
|
-
<span className="gdoc-pager-dir">
|
|
127
|
-
<ArrowLeft size={14} aria-hidden="true" />
|
|
128
|
-
返回总览
|
|
129
|
-
</span>
|
|
130
|
-
<strong>Garden 使用说明首页</strong>
|
|
131
|
-
</Link>
|
|
132
|
-
)}
|
|
133
|
-
{next ? (
|
|
134
|
-
<Link href={gardenDocHref(next)} className="gdoc-pager-card is-next">
|
|
135
|
-
<span className="gdoc-pager-dir">
|
|
136
|
-
下一篇
|
|
137
|
-
<ArrowRight size={14} aria-hidden="true" />
|
|
138
|
-
</span>
|
|
139
|
-
<strong>{next.title}</strong>
|
|
140
|
-
</Link>
|
|
141
|
-
) : null}
|
|
142
|
-
</nav>
|
|
143
|
-
</article>
|
|
144
|
-
|
|
145
|
-
<TableOfContents headings={headings} label="本页目录" />
|
|
146
|
-
</main>
|
|
147
|
-
);
|
|
27
|
+
return <HandbookDoc lang="zh" doc={doc} />;
|
|
148
28
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { notFound } from "next/navigation";
|
|
2
|
+
import { HandbookDoc, handbookDocMetadata } from "../../../components/HandbookView";
|
|
3
|
+
import { gardenDocsEn } from "../../../lib/content";
|
|
4
|
+
import { isStaticPlaceholder, STATIC_PLACEHOLDER, staticParams } from "../../../../ui/garden/static-params.mjs";
|
|
5
|
+
|
|
6
|
+
const find = (slug: string[]) => gardenDocsEn.find((doc) => doc.path === slug.join("/"));
|
|
7
|
+
|
|
8
|
+
export function generateStaticParams() {
|
|
9
|
+
return staticParams(
|
|
10
|
+
gardenDocsEn.map((doc) => ({ slug: doc.path.split("/") })),
|
|
11
|
+
{ slug: [STATIC_PLACEHOLDER] },
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function generateMetadata({ params }: { params: Promise<{ slug: string[] }> }) {
|
|
16
|
+
const { slug } = await params;
|
|
17
|
+
return handbookDocMetadata("en", find(slug));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default async function GardenEnDocPage({ params }: { params: Promise<{ slug: string[] }> }) {
|
|
21
|
+
const { slug } = await params;
|
|
22
|
+
const doc = find(slug);
|
|
23
|
+
if (!doc) {
|
|
24
|
+
if (isStaticPlaceholder(slug)) return null;
|
|
25
|
+
notFound();
|
|
26
|
+
}
|
|
27
|
+
return <HandbookDoc lang="en" doc={doc} />;
|
|
28
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { notFound } from "next/navigation";
|
|
2
|
+
import { HandbookOverview, handbookLanguages } from "../../components/HandbookView";
|
|
3
|
+
import { pageAlternates } from "../../lib/page-alternates";
|
|
4
|
+
import { HANDBOOK_UI } from "../../lib/garden-docs-meta";
|
|
5
|
+
import { gardenIndexDocEn } from "../../lib/content";
|
|
6
|
+
|
|
7
|
+
export const metadata = {
|
|
8
|
+
title: HANDBOOK_UI.en.name,
|
|
9
|
+
description: gardenIndexDocEn?.description ?? "",
|
|
10
|
+
alternates: pageAlternates("/garden/en", handbookLanguages("en", "index")),
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default function GardenEnPage() {
|
|
14
|
+
if (!gardenIndexDocEn) notFound();
|
|
15
|
+
return <HandbookOverview lang="en" />;
|
|
16
|
+
}
|
package/app/garden/page.tsx
CHANGED
|
@@ -1,81 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { ArrowRight } from "@phosphor-icons/react/ssr";
|
|
3
|
-
import { ArticleMeasure } from "../components/ArticleMeasure";
|
|
4
|
-
import { ArticleReferences } from "../components/ArticleReferences";
|
|
5
|
-
import { ArticleTitle } from "../components/ArticleTitle";
|
|
6
|
-
import { GardenDocsNav } from "../components/GardenDocsNav";
|
|
7
|
-
import { GardenMediaGuide } from "../components/GardenMediaGuide";
|
|
8
|
-
import { MarkdownArticle } from "../components/MarkdownArticle";
|
|
9
|
-
import { TableOfContents } from "../components/TableOfContents";
|
|
10
|
-
import {
|
|
11
|
-
extractHeadings,
|
|
12
|
-
gardenDocHref,
|
|
13
|
-
gardenDocs,
|
|
14
|
-
gardenIndexDoc,
|
|
15
|
-
} from "../lib/content";
|
|
16
|
-
import {
|
|
17
|
-
orderedHandbookDocs,
|
|
18
|
-
toHandbookNavItems,
|
|
19
|
-
} from "../lib/garden-docs-meta";
|
|
1
|
+
import { HandbookOverview, handbookLanguages } from "../components/HandbookView";
|
|
20
2
|
import { pageAlternates } from "../lib/page-alternates";
|
|
21
|
-
import {
|
|
3
|
+
import { HANDBOOK_UI } from "../lib/garden-docs-meta";
|
|
4
|
+
import { gardenIndexDoc } from "../lib/content";
|
|
22
5
|
|
|
23
6
|
export const metadata = {
|
|
24
|
-
title:
|
|
25
|
-
description:
|
|
26
|
-
|
|
27
|
-
alternates: pageAlternates("/garden"),
|
|
7
|
+
title: HANDBOOK_UI.zh.name,
|
|
8
|
+
description: gardenIndexDoc?.description ?? "",
|
|
9
|
+
alternates: pageAlternates("/garden", handbookLanguages("zh", "index")),
|
|
28
10
|
};
|
|
29
11
|
|
|
30
12
|
export default function GardenPage() {
|
|
31
|
-
|
|
32
|
-
const ordered = orderedHandbookDocs(gardenDocs);
|
|
33
|
-
const firstDoc = ordered[0] ?? null;
|
|
34
|
-
const content = gardenIndexDoc?.content ?? "";
|
|
35
|
-
const headings = extractHeadings(content);
|
|
36
|
-
const configuredTitle = siteConfig.pages.garden.title?.trim() || gardenIndexDoc?.title || "Garden 使用说明";
|
|
37
|
-
const configuredSubtitle = siteConfig.pages.garden.subtitle?.trim() || gardenIndexDoc?.description || "";
|
|
38
|
-
void GardenMediaGuide;
|
|
39
|
-
|
|
40
|
-
return (
|
|
41
|
-
<main className="column-reading-page garden-doc-page">
|
|
42
|
-
<GardenDocsNav docs={navItems} currentPath="" />
|
|
43
|
-
|
|
44
|
-
<article className="article-column garden-doc-article h-entry">
|
|
45
|
-
<header className="gdoc-article-head">
|
|
46
|
-
<div className="gdoc-eyebrow">
|
|
47
|
-
<span>Garden 使用说明</span>
|
|
48
|
-
<span aria-hidden="true">/</span>
|
|
49
|
-
<span>总览</span>
|
|
50
|
-
</div>
|
|
51
|
-
<ArticleTitle title={configuredTitle} />
|
|
52
|
-
{configuredSubtitle ? (
|
|
53
|
-
<p className="gdoc-article-deck p-summary">{configuredSubtitle}</p>
|
|
54
|
-
) : null}
|
|
55
|
-
</header>
|
|
56
|
-
|
|
57
|
-
<ArticleMeasure />
|
|
58
|
-
|
|
59
|
-
<div className="e-content">
|
|
60
|
-
<MarkdownArticle content={content} />
|
|
61
|
-
</div>
|
|
62
|
-
|
|
63
|
-
<ArticleReferences references={gardenIndexDoc?.references} />
|
|
64
|
-
|
|
65
|
-
{firstDoc ? (
|
|
66
|
-
<nav className="gdoc-pager" aria-label="说明书上下篇导航">
|
|
67
|
-
<Link href={gardenDocHref(firstDoc)} className="gdoc-pager-card is-next">
|
|
68
|
-
<span className="gdoc-pager-dir">
|
|
69
|
-
下一篇
|
|
70
|
-
<ArrowRight size={14} aria-hidden="true" />
|
|
71
|
-
</span>
|
|
72
|
-
<strong>{firstDoc.title}</strong>
|
|
73
|
-
</Link>
|
|
74
|
-
</nav>
|
|
75
|
-
) : null}
|
|
76
|
-
</article>
|
|
77
|
-
|
|
78
|
-
<TableOfContents headings={headings} label="本页目录" />
|
|
79
|
-
</main>
|
|
80
|
-
);
|
|
13
|
+
return <HandbookOverview lang="zh" />;
|
|
81
14
|
}
|
package/app/garden-modules.d.ts
CHANGED
|
@@ -60,9 +60,12 @@ declare module "@garden/generated-content" {
|
|
|
60
60
|
references?: { title: string; url?: string; domain?: string }[];
|
|
61
61
|
sourcePath: string;
|
|
62
62
|
content: string;
|
|
63
|
+
lang?: "zh" | "en";
|
|
63
64
|
};
|
|
64
65
|
export const gardenDocs: GardenDocEntry[];
|
|
65
66
|
export const gardenIndexDoc: GardenDocEntry | null;
|
|
67
|
+
export const gardenDocsEn: GardenDocEntry[];
|
|
68
|
+
export const gardenIndexDocEn: GardenDocEntry | null;
|
|
66
69
|
export const a2WordCounts: Record<string, number>;
|
|
67
70
|
export type LinkCardMeta = {
|
|
68
71
|
title: string;
|
package/app/layout.tsx
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { githubUrl, siteConfig } from "./site.config";
|
|
14
14
|
import { brand } from "@garden/site-config";
|
|
15
15
|
import { FEED_ALTERNATES } from "./lib/page-alternates";
|
|
16
|
+
import { htmlLang, siteLang } from "./lib/i18n";
|
|
16
17
|
|
|
17
18
|
export async function generateMetadata(): Promise<Metadata> {
|
|
18
19
|
const siteUrl = await getSiteUrl();
|
|
@@ -75,7 +76,7 @@ export default function RootLayout({
|
|
|
75
76
|
"https://plausible.io/js/script.js";
|
|
76
77
|
|
|
77
78
|
return (
|
|
78
|
-
<html lang=
|
|
79
|
+
<html lang={htmlLang(siteLang)} suppressHydrationWarning>
|
|
79
80
|
<head>
|
|
80
81
|
<script
|
|
81
82
|
dangerouslySetInnerHTML={{
|
|
@@ -93,9 +94,6 @@ export default function RootLayout({
|
|
|
93
94
|
)}
|
|
94
95
|
</head>
|
|
95
96
|
<body>
|
|
96
|
-
<a className="skip-link" href="#main-content">
|
|
97
|
-
跳到正文
|
|
98
|
-
</a>
|
|
99
97
|
<SiteChrome>{children}</SiteChrome>
|
|
100
98
|
</body>
|
|
101
99
|
</html>
|
package/app/lib/content.ts
CHANGED
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
columns,
|
|
4
4
|
gardenDocs,
|
|
5
5
|
gardenIndexDoc,
|
|
6
|
+
gardenDocsEn,
|
|
7
|
+
gardenIndexDocEn,
|
|
6
8
|
posts,
|
|
7
9
|
thoughts,
|
|
8
10
|
type ContentEntry,
|
|
@@ -19,8 +21,9 @@ import { unified } from "unified";
|
|
|
19
21
|
import { visit } from "unist-util-visit";
|
|
20
22
|
import { articleUsesHeadingNumbers, autoNumbersHeadings, splitHeadingNumber } from "./heading-number";
|
|
21
23
|
import { siteHref } from "./site-href";
|
|
24
|
+
import { ui } from "./i18n";
|
|
22
25
|
|
|
23
|
-
export { columns, gardenDocs, gardenIndexDoc, posts, thoughts };
|
|
26
|
+
export { columns, gardenDocs, gardenDocsEn, gardenIndexDoc, gardenIndexDocEn, posts, thoughts };
|
|
24
27
|
export type { ContentEntry, GardenDocEntry, MediaItem };
|
|
25
28
|
|
|
26
29
|
export const postHref = (post: Pick<ContentEntry, "path">) => siteHref(`/blog/${post.path}`);
|
|
@@ -45,7 +48,7 @@ export const formatDate = (value: string, withTime = false) => {
|
|
|
45
48
|
const canonical = canonicalSiteDate(value) ?? value;
|
|
46
49
|
const date = new Date(canonical);
|
|
47
50
|
if (Number.isNaN(date.getTime())) return value;
|
|
48
|
-
return new Intl.DateTimeFormat(
|
|
51
|
+
return new Intl.DateTimeFormat(ui().dateLocale, {
|
|
49
52
|
timeZone: "Asia/Shanghai",
|
|
50
53
|
year: "numeric",
|
|
51
54
|
month: "2-digit",
|
|
@@ -135,6 +138,13 @@ export const findThought = (slug: string): ContentEntry | undefined =>
|
|
|
135
138
|
export const findGardenDoc = (path: string): GardenDocEntry | undefined =>
|
|
136
139
|
gardenDocs.find((doc) => doc.path === path);
|
|
137
140
|
|
|
141
|
+
/** Handbook pages and the overview of one language. */
|
|
142
|
+
export const handbookDocs = (lang: "zh" | "en") => (lang === "en" ? gardenDocsEn : gardenDocs);
|
|
143
|
+
export const handbookIndexDoc = (lang: "zh" | "en") => (lang === "en" ? gardenIndexDocEn : gardenIndexDoc);
|
|
144
|
+
/** Whether a handbook page (or "index") exists in a language. */
|
|
145
|
+
export const hasHandbookPage = (lang: "zh" | "en", path: string) =>
|
|
146
|
+
path === "index" || path === "" ? Boolean(handbookIndexDoc(lang)) : handbookDocs(lang).some((doc) => doc.path === path);
|
|
147
|
+
|
|
138
148
|
export const findColumnEntry = (
|
|
139
149
|
column: string,
|
|
140
150
|
path: string,
|
|
@@ -4,19 +4,72 @@ export type GardenDocNavItem = {
|
|
|
4
4
|
description?: string;
|
|
5
5
|
section?: string;
|
|
6
6
|
order?: number;
|
|
7
|
+
navTitle?: string;
|
|
7
8
|
};
|
|
8
9
|
|
|
10
|
+
export type HandbookLang = "zh" | "en";
|
|
11
|
+
|
|
9
12
|
export type HandbookSectionMeta = {
|
|
10
13
|
id: string;
|
|
11
14
|
title: string;
|
|
12
|
-
|
|
15
|
+
/** English sidebar group heading; falls back to the Chinese one until translated. */
|
|
16
|
+
titleEn?: string;
|
|
13
17
|
};
|
|
14
18
|
|
|
19
|
+
/** Where each language of the handbook lives. English mirrors the Chinese paths. */
|
|
20
|
+
export const handbookBase = (lang: HandbookLang) => (lang === "en" ? "/garden/en" : "/garden");
|
|
21
|
+
export const handbookPath = (lang: HandbookLang, docPath = "") =>
|
|
22
|
+
docPath && docPath !== "index" ? `${handbookBase(lang)}/${docPath}` : handbookBase(lang);
|
|
23
|
+
export const sectionTitle = (section: HandbookSectionMeta | undefined, lang: HandbookLang) =>
|
|
24
|
+
section ? (lang === "en" ? section.titleEn ?? section.title : section.title) : undefined;
|
|
25
|
+
/** The handbook remembers the reader's choice under this localStorage key. */
|
|
26
|
+
export const HANDBOOK_LANG_KEY = "garden-handbook-lang";
|
|
27
|
+
|
|
28
|
+
/** Handbook chrome labels. Page text lives in docs/handbook and docs/handbook/en. */
|
|
29
|
+
export const HANDBOOK_UI = {
|
|
30
|
+
zh: {
|
|
31
|
+
name: "Garden 使用说明",
|
|
32
|
+
overview: "总览",
|
|
33
|
+
overviewHint: "总览与快速入口",
|
|
34
|
+
guide: "指南",
|
|
35
|
+
pager: "说明书上下篇导航",
|
|
36
|
+
prev: "上一篇",
|
|
37
|
+
next: "下一篇",
|
|
38
|
+
backToOverview: "返回总览",
|
|
39
|
+
overviewDocTitle: "Garden 使用说明首页",
|
|
40
|
+
toc: "本页目录",
|
|
41
|
+
sidebar: "Garden 使用说明目录",
|
|
42
|
+
chapters: "章节导航",
|
|
43
|
+
closeSidebar: "关闭说明目录",
|
|
44
|
+
openSidebar: "打开说明目录",
|
|
45
|
+
sidebarShort: "说明目录",
|
|
46
|
+
mobileBar: "说明书目录栏",
|
|
47
|
+
},
|
|
48
|
+
en: {
|
|
49
|
+
name: "Garden Handbook",
|
|
50
|
+
overview: "Overview",
|
|
51
|
+
overviewHint: "Overview and quick links",
|
|
52
|
+
guide: "Guide",
|
|
53
|
+
pager: "Handbook pages",
|
|
54
|
+
prev: "Previous",
|
|
55
|
+
next: "Next",
|
|
56
|
+
backToOverview: "Back to overview",
|
|
57
|
+
overviewDocTitle: "Garden Handbook home",
|
|
58
|
+
toc: "On this page",
|
|
59
|
+
sidebar: "Garden Handbook contents",
|
|
60
|
+
chapters: "Chapters",
|
|
61
|
+
closeSidebar: "Close contents",
|
|
62
|
+
openSidebar: "Open contents",
|
|
63
|
+
sidebarShort: "Contents",
|
|
64
|
+
mobileBar: "Handbook contents bar",
|
|
65
|
+
},
|
|
66
|
+
} as const;
|
|
67
|
+
|
|
15
68
|
export const HANDBOOK_SECTIONS: HandbookSectionMeta[] = [
|
|
16
|
-
{ id: "getting-started", title: "快速上手",
|
|
17
|
-
{ id: "writing-and-reading", title: "写作与排版",
|
|
18
|
-
{ id: "components", title: "内置组件",
|
|
19
|
-
{ id: "workflow", title: "检查与补全",
|
|
69
|
+
{ id: "getting-started", title: "快速上手", titleEn: "Getting Started" },
|
|
70
|
+
{ id: "writing-and-reading", title: "写作与排版", titleEn: "Writing & Layout" },
|
|
71
|
+
{ id: "components", title: "内置组件", titleEn: "Built-in Components" },
|
|
72
|
+
{ id: "workflow", title: "检查与补全", titleEn: "Checks & Shortcuts" },
|
|
20
73
|
];
|
|
21
74
|
|
|
22
75
|
export const COMPONENT_BADGE: Record<string, string> = {
|
|
@@ -33,6 +86,7 @@ export const COMPONENT_BADGE: Record<string, string> = {
|
|
|
33
86
|
|
|
34
87
|
export const NAV_SHORT_TITLE: Record<string, string> = {
|
|
35
88
|
start: "快速开始",
|
|
89
|
+
"ai-writing": "用 AI 写作",
|
|
36
90
|
config: "站点与专栏配置",
|
|
37
91
|
deploy: "发布到 GitHub Pages",
|
|
38
92
|
writing: "写文章与随想",
|
|
@@ -79,5 +133,6 @@ export function toHandbookNavItems(docs: GardenDocNavItem[]): GardenDocNavItem[]
|
|
|
79
133
|
description: doc.description,
|
|
80
134
|
section: doc.section,
|
|
81
135
|
order: doc.order,
|
|
136
|
+
...(doc.navTitle ? { navTitle: doc.navTitle } : {}),
|
|
82
137
|
}));
|
|
83
138
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The same handbook page in the other language, worked out from the address
|
|
3
|
+
* in the browser so a basePath (/repo/garden/...) stays in front.
|
|
4
|
+
* /garden/x/ ⇄ /garden/en/x/. Returns null outside the handbook.
|
|
5
|
+
*/
|
|
6
|
+
export function swapHandbookLang(pathname: string, target: "zh" | "en"): string | null {
|
|
7
|
+
const match = /^(.*?\/garden)(\/en)?(\/.*)?$/u.exec(pathname);
|
|
8
|
+
if (!match) return null;
|
|
9
|
+
const rest = match[3] ?? "/";
|
|
10
|
+
return target === "en" ? `${match[1]}/en${rest}` : `${match[1]}${rest}`;
|
|
11
|
+
}
|