hd-garden 0.7.0 → 0.8.1
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/.env.en.example +67 -0
- package/README.en.md +57 -0
- package/README.md +31 -46
- package/app/a2/[slug]/page.tsx +4 -3
- package/app/a2/page.tsx +4 -3
- package/app/about/page.tsx +8 -9
- package/app/admin/page.tsx +7 -6
- package/app/admin/preview/[...path]/page.tsx +2 -1
- package/app/admin/ui/blocks/page.tsx +2 -1
- package/app/admin/ui/catalog/page.tsx +2 -1
- package/app/admin/ui/evals/page.tsx +2 -1
- package/app/admin/ui/page.tsx +2 -1
- package/app/blog/[...path]/page.tsx +14 -5
- package/app/blog/page.tsx +4 -2
- package/app/columns/[column]/[...path]/page.tsx +17 -18
- package/app/columns/page.tsx +12 -8
- package/app/components/AdminUiShell.tsx +4 -3
- package/app/components/ArticlePager.tsx +32 -0
- package/app/components/ArticleReferences.tsx +4 -1
- package/app/components/ArticleTags.tsx +3 -2
- package/app/components/BlogArchive.tsx +20 -16
- package/app/components/Comments.tsx +17 -10
- package/app/components/ContentEmptyState.tsx +29 -28
- package/app/components/ExternalEmbed.tsx +15 -14
- package/app/components/FeedMore.tsx +70 -0
- package/app/components/GardenContentGuide.tsx +10 -2
- package/app/components/GardenDocsNav.tsx +29 -22
- package/app/components/GiscusComments.tsx +2 -1
- package/app/components/GithubLiveOverview.tsx +10 -8
- package/app/components/GithubProfile.tsx +31 -21
- package/app/components/HandbookLangRedirect.tsx +30 -0
- package/app/components/HandbookLangSwitch.tsx +53 -0
- package/app/components/HandbookView.tsx +270 -0
- package/app/components/MarkdownArticle.tsx +39 -9
- package/app/components/MarkdownImage.tsx +6 -4
- package/app/components/SiteChrome.tsx +11 -2
- package/app/components/SiteFooter.tsx +6 -4
- package/app/components/SiteHeader.tsx +38 -25
- package/app/components/TableOfContents.tsx +15 -9
- package/app/components/ThoughtCard.tsx +16 -14
- package/app/components/ThoughtFeed.tsx +41 -0
- package/app/components/UiBlock.tsx +3 -2
- package/app/components/UiLang.tsx +18 -0
- package/app/components/Webmentions.tsx +13 -12
- package/app/components/WidgetChart.tsx +8 -6
- package/app/components/garden/FilePreview.tsx +14 -12
- package/app/components/garden/GardenBlock.tsx +10 -1
- package/app/components/garden/GardenFile.tsx +10 -8
- package/app/components/garden/RefDemo.tsx +62 -0
- package/app/components/garden/blocks.tsx +90 -32
- package/app/components/garden/chrome.tsx +20 -13
- package/app/components/garden/patterns.tsx +497 -0
- package/app/garden/[...slug]/page.tsx +11 -130
- package/app/garden/en/[...slug]/page.tsx +28 -0
- package/app/garden/en/page.tsx +16 -0
- package/app/garden/page.tsx +8 -74
- package/app/garden-modules.d.ts +3 -0
- package/app/globals.css +10 -0
- package/app/layout.tsx +2 -4
- package/app/lib/a2-meta.ts +26 -3
- package/app/lib/content.ts +13 -3
- package/app/lib/garden-docs-meta.ts +84 -5
- package/app/lib/github-id.mjs +8 -0
- package/app/lib/handbook-path.ts +11 -0
- package/app/lib/handbook-switch.mjs +12 -0
- package/app/lib/i18n.ts +621 -0
- package/app/lib/page-alternates.ts +2 -2
- package/app/lib/rehype-field-tables.mjs +47 -0
- package/app/lib/remark-a2.mjs +18 -8
- package/app/lib/remark-link-card.mjs +22 -0
- package/app/lib/remark-ref-demos.mjs +42 -0
- package/app/lib/tag-filter.ts +9 -2
- package/app/not-found.tsx +34 -4
- package/app/page.tsx +17 -14
- package/app/rss.xml/route.ts +2 -1
- package/app/site.config.ts +1 -1
- package/app/styles/a2.css +439 -32
- package/app/styles/shell.css +21 -2
- package/app/tags/page.tsx +7 -5
- package/app/thoughts/[slug]/page.tsx +2 -1
- package/app/thoughts/page/[n]/page.tsx +44 -0
- package/app/thoughts/page.tsx +4 -7
- package/app/ui-parity/page.tsx +38 -22
- package/bin/garden.mjs +133 -19
- package/docs/handbook/ai-writing.md +99 -0
- package/docs/handbook/assets/reading-layout-en.svg +26 -0
- package/docs/handbook/components/card.md +43 -20
- package/docs/handbook/components/chart.md +10 -24
- package/docs/handbook/components/choice.md +42 -0
- package/docs/handbook/components/compare.md +34 -21
- package/docs/handbook/components/figure.md +33 -44
- package/docs/handbook/components/file.md +10 -13
- package/docs/handbook/components/html.md +10 -28
- package/docs/handbook/components/interactive.md +9 -33
- package/docs/handbook/components/list.md +65 -0
- package/docs/handbook/components/relations.md +95 -0
- package/docs/handbook/components/stat.md +10 -26
- package/docs/handbook/components/timeline.md +13 -26
- package/docs/handbook/components.md +210 -19
- package/docs/handbook/config.md +7 -5
- package/docs/handbook/deploy.md +2 -2
- package/docs/handbook/design.md +23 -15
- package/docs/handbook/en/ai-writing.md +99 -0
- package/docs/handbook/en/components/card.md +67 -0
- package/docs/handbook/en/components/chart.md +38 -0
- package/docs/handbook/en/components/choice.md +42 -0
- package/docs/handbook/en/components/compare.md +53 -0
- package/docs/handbook/en/components/figure.md +59 -0
- package/docs/handbook/en/components/file/checklist.md +17 -0
- package/docs/handbook/en/components/file.md +29 -0
- package/docs/handbook/en/components/html.md +49 -0
- package/docs/handbook/en/components/interactive.md +41 -0
- package/docs/handbook/en/components/list.md +65 -0
- package/docs/handbook/en/components/relations.md +95 -0
- package/docs/handbook/en/components/stat.md +39 -0
- package/docs/handbook/en/components/timeline.md +37 -0
- package/docs/handbook/en/components.md +222 -0
- package/docs/handbook/en/config.md +127 -0
- package/docs/handbook/en/deploy.md +273 -0
- package/docs/handbook/en/design.md +88 -0
- package/docs/handbook/en/index.md +192 -0
- package/docs/handbook/en/reference/card.md +85 -0
- package/docs/handbook/en/reference/chart.md +68 -0
- package/docs/handbook/en/reference/choice.md +69 -0
- package/docs/handbook/en/reference/compare.md +59 -0
- package/docs/handbook/en/reference/figure.md +84 -0
- package/docs/handbook/en/reference/file/checklist.md +17 -0
- package/docs/handbook/en/reference/file.md +50 -0
- package/docs/handbook/en/reference/html.md +62 -0
- package/docs/handbook/en/reference/interactive.md +71 -0
- package/docs/handbook/en/reference/list.md +72 -0
- package/docs/handbook/en/reference/relations.md +113 -0
- package/docs/handbook/en/reference/stat.md +60 -0
- package/docs/handbook/en/reference/timeline.md +56 -0
- package/docs/handbook/en/reference.md +26 -0
- package/docs/handbook/en/start.md +92 -0
- package/docs/handbook/en/workflow.md +82 -0
- package/docs/handbook/en/writing.md +157 -0
- package/docs/handbook/index.md +99 -4
- package/docs/handbook/reference/card.md +85 -0
- package/docs/handbook/reference/chart.md +68 -0
- package/docs/handbook/reference/choice.md +69 -0
- package/docs/handbook/reference/compare.md +59 -0
- package/docs/handbook/reference/figure.md +84 -0
- package/docs/handbook/reference/file/checklist.md +17 -0
- package/docs/handbook/reference/file.md +50 -0
- package/docs/handbook/reference/html.md +62 -0
- package/docs/handbook/reference/interactive.md +71 -0
- package/docs/handbook/reference/list.md +72 -0
- package/docs/handbook/reference/relations.md +113 -0
- package/docs/handbook/reference/stat.md +60 -0
- package/docs/handbook/reference/timeline.md +56 -0
- package/docs/handbook/reference.md +26 -0
- package/docs/handbook/start.md +1 -1
- package/docs/handbook/workflow.md +6 -5
- package/docs/handbook/writing.md +21 -12
- package/next.config.ts +6 -0
- package/package.json +2 -1
- package/public/images/garden/ref-card-book.svg +1 -0
- package/public/images/garden/ref-card-lamp.svg +1 -0
- package/scripts/build-content.mjs +57 -16
- package/scripts/favicon-ico.mjs +9 -6
- package/scripts/file-preview.mjs +10 -7
- package/scripts/handbook-allowlist.mjs +18 -0
- package/scripts/handbook-facts.mjs +339 -11
- package/scripts/html-lang.mjs +36 -0
- package/scripts/link-assets.mjs +7 -0
- package/scripts/new-post.mjs +18 -9
- package/scripts/prepare-pages-output.mjs +30 -18
- package/scripts/redirects.mjs +17 -6
- package/scripts/static-404.mjs +25 -0
- package/scripts/ui-cli.mjs +14 -8
- package/templates/garden-blocks.md +67 -0
- package/templates/garden-snippets.plist +61 -0
- package/templates/site/Makefile.en +45 -0
- package/templates/site/README.en.md +15 -0
- package/templates/site/site.config.en.yaml +55 -0
- package/templates/site/site.config.yaml +2 -0
- package/templates/skills-en/garden-interactive-chart/SKILL.md +84 -0
- package/templates/skills-en/garden-interactive-chart/references/shell.html +49 -0
- package/templates/skills-en/garden-interactive-chart/references/style.md +61 -0
- package/templates/skills-en/garden-post-review/SKILL.md +63 -0
- package/templates/skills-en/garden-ui/SKILL.md +118 -0
- package/templates/workflows/pages.en.yml +195 -0
- package/templates/workflows/pages.yml +195 -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/catalog.mjs +205 -4
- package/ui/garden/check.mjs +12 -0
- package/ui/garden/file-block.mjs +8 -7
- package/ui/garden/html-lang.mjs +88 -0
- package/ui/garden/markdown-links.mjs +7 -4
- package/ui/garden/paging.mjs +30 -0
- package/ui/garden/parse.mjs +45 -0
- package/ui/garden/relations.mjs +252 -0
- package/ui/garden/search-index.mjs +6 -4
- package/ui/garden/source-icon.mjs +54 -0
- package/ui/parity-fixtures.mjs +77 -0
- package/ui/preview/dist/garden-ui.css +9 -9
- package/ui/preview/dist/garden-ui.esm.js +206 -169
- package/ui/preview/dist/garden-ui.iife.js +206 -169
- package/ui/prompt/rules.md +28 -1
- package/ui/react/components/chart.tsx +8 -5
- package/ui/react/components/display.tsx +7 -4
- 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 +89 -0
- package/ui/react/parity.tsx +8 -6
- package/ui/react/runtime.tsx +3 -1
- package/ui/react/ui.css +9 -9
- package/worker/index.ts +14 -1
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import type { Metadata } from "next";
|
|
3
|
+
import { ArrowLeft, ArrowRight } from "@phosphor-icons/react/ssr";
|
|
4
|
+
import { ArticleMeasure } from "./ArticleMeasure";
|
|
5
|
+
import { ArticleReferences } from "./ArticleReferences";
|
|
6
|
+
import { ArticleTitle } from "./ArticleTitle";
|
|
7
|
+
import { GardenDocsNav } from "./GardenDocsNav";
|
|
8
|
+
import { HandbookLangRedirect } from "./HandbookLangRedirect";
|
|
9
|
+
import { MarkdownArticle } from "./MarkdownArticle";
|
|
10
|
+
import { TableOfContents } from "./TableOfContents";
|
|
11
|
+
import { extractHeadings, handbookDocs, handbookIndexDoc, hasHandbookPage, type GardenDocEntry } from "../lib/content";
|
|
12
|
+
import { pageAlternates } from "../lib/page-alternates";
|
|
13
|
+
import { absoluteUrl, getSiteUrl } from "../lib/site";
|
|
14
|
+
import { siteHref } from "../lib/site-href";
|
|
15
|
+
import {
|
|
16
|
+
COMPONENT_BADGE,
|
|
17
|
+
HANDBOOK_SECTIONS,
|
|
18
|
+
HANDBOOK_UI,
|
|
19
|
+
handbookPath,
|
|
20
|
+
isReferencePath,
|
|
21
|
+
orderedHandbookDocs,
|
|
22
|
+
sectionTitle,
|
|
23
|
+
toHandbookNavItems,
|
|
24
|
+
type HandbookLang,
|
|
25
|
+
} from "../lib/garden-docs-meta";
|
|
26
|
+
import { UI_STRINGS, siteLang } from "../lib/i18n";
|
|
27
|
+
import { siteConfig } from "../site.config";
|
|
28
|
+
|
|
29
|
+
const other = (lang: HandbookLang): HandbookLang => (lang === "en" ? "zh" : "en");
|
|
30
|
+
const hreflang = (lang: HandbookLang) => (lang === "en" ? "en" : "zh-CN");
|
|
31
|
+
|
|
32
|
+
/** canonical + hreflang links; the counterpart is listed only when it exists. */
|
|
33
|
+
export function handbookLanguages(lang: HandbookLang, docPath: string) {
|
|
34
|
+
if (siteLang === "en") return undefined;
|
|
35
|
+
if (!hasHandbookPage(other(lang), docPath)) return undefined;
|
|
36
|
+
const zh = siteHref(handbookPath("zh", docPath));
|
|
37
|
+
const en = siteHref(handbookPath("en", docPath));
|
|
38
|
+
return { "zh-CN": zh, en, "x-default": zh };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const handbookAlternates = (lang: HandbookLang, docPath: string) =>
|
|
42
|
+
pageAlternates(handbookPath(lang, docPath), handbookLanguages(lang, docPath));
|
|
43
|
+
|
|
44
|
+
export async function handbookOverviewMetadata(lang: HandbookLang, indexDoc: GardenDocEntry | null | undefined) {
|
|
45
|
+
const t = HANDBOOK_UI[lang];
|
|
46
|
+
const baseUrl = await getSiteUrl();
|
|
47
|
+
const pathname = siteHref(handbookPath(lang, "index"));
|
|
48
|
+
const description = indexDoc?.description ?? "";
|
|
49
|
+
const fullTitle = `${t.name} · ${siteConfig.name}`;
|
|
50
|
+
const imageAlt =
|
|
51
|
+
lang === "en"
|
|
52
|
+
? `${siteConfig.name} · ${indexDoc?.title || t.name}`
|
|
53
|
+
: `${siteConfig.name} · ${siteConfig.pages.home.title.replace("\n", "")}`;
|
|
54
|
+
return {
|
|
55
|
+
title: t.name,
|
|
56
|
+
description,
|
|
57
|
+
alternates: pageAlternates(handbookPath(lang), handbookLanguages(lang, "index")),
|
|
58
|
+
openGraph: {
|
|
59
|
+
type: "website",
|
|
60
|
+
url: absoluteUrl(pathname, baseUrl),
|
|
61
|
+
title: fullTitle,
|
|
62
|
+
description,
|
|
63
|
+
locale: lang === "en" ? "en_US" : "zh_CN",
|
|
64
|
+
images: [
|
|
65
|
+
{
|
|
66
|
+
url: "/og.png",
|
|
67
|
+
width: 1731,
|
|
68
|
+
height: 909,
|
|
69
|
+
alt: imageAlt,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
twitter: {
|
|
74
|
+
card: "summary_large_image",
|
|
75
|
+
title: fullTitle,
|
|
76
|
+
description,
|
|
77
|
+
images: ["/og.png"],
|
|
78
|
+
},
|
|
79
|
+
} satisfies Metadata;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function handbookDocMetadata(lang: HandbookLang, doc: GardenDocEntry | undefined) {
|
|
83
|
+
if (!doc) {
|
|
84
|
+
const copy = UI_STRINGS[lang];
|
|
85
|
+
const fullTitle = `${copy.notFoundTitle} · ${siteConfig.name}`;
|
|
86
|
+
return {
|
|
87
|
+
title: copy.notFoundTitle,
|
|
88
|
+
description: copy.notFoundDeck,
|
|
89
|
+
openGraph: {
|
|
90
|
+
type: "website",
|
|
91
|
+
title: fullTitle,
|
|
92
|
+
description: copy.notFoundDeck,
|
|
93
|
+
locale: lang === "en" ? "en_US" : "zh_CN",
|
|
94
|
+
images: [
|
|
95
|
+
{
|
|
96
|
+
url: "/og.png",
|
|
97
|
+
width: 1731,
|
|
98
|
+
height: 909,
|
|
99
|
+
alt: fullTitle,
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
twitter: {
|
|
104
|
+
card: "summary_large_image",
|
|
105
|
+
title: fullTitle,
|
|
106
|
+
description: copy.notFoundDeck,
|
|
107
|
+
images: ["/og.png"],
|
|
108
|
+
},
|
|
109
|
+
} satisfies Metadata;
|
|
110
|
+
}
|
|
111
|
+
const t = HANDBOOK_UI[lang];
|
|
112
|
+
const baseUrl = await getSiteUrl();
|
|
113
|
+
const pathname = siteHref(handbookPath(lang, doc.path));
|
|
114
|
+
const docTitle = `${doc.title} · ${t.name}`;
|
|
115
|
+
return {
|
|
116
|
+
title: docTitle,
|
|
117
|
+
description: doc.description,
|
|
118
|
+
alternates: handbookAlternates(lang, doc.path),
|
|
119
|
+
openGraph: {
|
|
120
|
+
type: "article",
|
|
121
|
+
url: absoluteUrl(pathname, baseUrl),
|
|
122
|
+
title: docTitle,
|
|
123
|
+
description: doc.description,
|
|
124
|
+
locale: lang === "en" ? "en_US" : "zh_CN",
|
|
125
|
+
images: [
|
|
126
|
+
{
|
|
127
|
+
url: "/og.png",
|
|
128
|
+
width: 1731,
|
|
129
|
+
height: 909,
|
|
130
|
+
alt: `${siteConfig.name} · ${docTitle}`,
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
twitter: {
|
|
135
|
+
card: "summary_large_image",
|
|
136
|
+
title: docTitle,
|
|
137
|
+
description: doc.description,
|
|
138
|
+
images: ["/og.png"],
|
|
139
|
+
},
|
|
140
|
+
} satisfies Metadata;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function HandbookOverview({ lang }: { lang: HandbookLang }) {
|
|
144
|
+
const t = HANDBOOK_UI[lang];
|
|
145
|
+
const docs = handbookDocs(lang);
|
|
146
|
+
const indexDoc = handbookIndexDoc(lang);
|
|
147
|
+
const navItems = toHandbookNavItems(docs);
|
|
148
|
+
const firstDoc = orderedHandbookDocs(docs)[0] ?? null;
|
|
149
|
+
const content = indexDoc?.content ?? "";
|
|
150
|
+
const headings = extractHeadings(content);
|
|
151
|
+
// The site config title/subtitle is written in the site's own language.
|
|
152
|
+
const configured = lang === siteLang ? siteConfig.pages.garden : { title: "", subtitle: "" };
|
|
153
|
+
const title = configured.title?.trim() || indexDoc?.title || t.name;
|
|
154
|
+
const subtitle = configured.subtitle?.trim() || indexDoc?.description || "";
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<main className="column-reading-page garden-doc-page" lang={hreflang(lang)}>
|
|
158
|
+
<HandbookLangRedirect lang={lang} />
|
|
159
|
+
<GardenDocsNav docs={navItems} currentPath="" lang={lang} />
|
|
160
|
+
|
|
161
|
+
<article className="article-column garden-doc-article h-entry">
|
|
162
|
+
<header className="gdoc-article-head">
|
|
163
|
+
<div className="gdoc-eyebrow">
|
|
164
|
+
<span>{t.name}</span>
|
|
165
|
+
<span aria-hidden="true">/</span>
|
|
166
|
+
<span>{t.overview}</span>
|
|
167
|
+
</div>
|
|
168
|
+
<ArticleTitle title={title} />
|
|
169
|
+
{subtitle ? <p className="gdoc-article-deck p-summary">{subtitle}</p> : null}
|
|
170
|
+
</header>
|
|
171
|
+
|
|
172
|
+
<ArticleMeasure />
|
|
173
|
+
|
|
174
|
+
<div className="e-content">
|
|
175
|
+
<MarkdownArticle content={content} lang={lang} fieldTables handbook />
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
<ArticleReferences references={indexDoc?.references} lang={lang} />
|
|
179
|
+
|
|
180
|
+
{firstDoc ? (
|
|
181
|
+
<nav className="gdoc-pager" aria-label={t.pager}>
|
|
182
|
+
<Link href={siteHref(handbookPath(lang, firstDoc.path))} className="gdoc-pager-card is-next">
|
|
183
|
+
<span className="gdoc-pager-dir">
|
|
184
|
+
{t.next}
|
|
185
|
+
<ArrowRight size={14} aria-hidden="true" />
|
|
186
|
+
</span>
|
|
187
|
+
<strong>{firstDoc.title}</strong>
|
|
188
|
+
</Link>
|
|
189
|
+
</nav>
|
|
190
|
+
) : null}
|
|
191
|
+
</article>
|
|
192
|
+
|
|
193
|
+
<TableOfContents headings={headings} label={t.toc} lang={lang} />
|
|
194
|
+
</main>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function HandbookDoc({ lang, doc }: { lang: HandbookLang; doc: GardenDocEntry }) {
|
|
199
|
+
const t = HANDBOOK_UI[lang];
|
|
200
|
+
const docs = handbookDocs(lang);
|
|
201
|
+
const ordered = orderedHandbookDocs(docs);
|
|
202
|
+
const navItems = toHandbookNavItems(docs);
|
|
203
|
+
const index = ordered.findIndex((item) => item.path === doc.path);
|
|
204
|
+
const prev = index > 0 ? ordered[index - 1] : null;
|
|
205
|
+
const next = index >= 0 && index < ordered.length - 1 ? ordered[index + 1] : null;
|
|
206
|
+
const headings = extractHeadings(doc.content);
|
|
207
|
+
const sectionMeta = HANDBOOK_SECTIONS.find((sec) => sec.id === doc.section);
|
|
208
|
+
const componentBadge = COMPONENT_BADGE[doc.path];
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<main className={`column-reading-page garden-doc-page${isReferencePath(doc.path) ? " is-reference" : ""}`} lang={hreflang(lang)}>
|
|
212
|
+
<HandbookLangRedirect lang={lang} />
|
|
213
|
+
<GardenDocsNav docs={navItems} currentPath={doc.path} lang={lang} />
|
|
214
|
+
|
|
215
|
+
<article className="article-column garden-doc-article h-entry">
|
|
216
|
+
<header className="gdoc-article-head">
|
|
217
|
+
<div className="gdoc-eyebrow">
|
|
218
|
+
<Link href={siteHref(handbookPath(lang))} className="gdoc-eyebrow-root">
|
|
219
|
+
{t.name}
|
|
220
|
+
</Link>
|
|
221
|
+
<span aria-hidden="true">/</span>
|
|
222
|
+
<span>{sectionTitle(sectionMeta, lang) ?? doc.sectionTitle ?? t.guide}</span>
|
|
223
|
+
{componentBadge ? <code className="gdoc-eyebrow-fence">{`garden ${componentBadge}`}</code> : null}
|
|
224
|
+
</div>
|
|
225
|
+
<ArticleTitle title={doc.title} />
|
|
226
|
+
{doc.description ? <p className="gdoc-article-deck p-summary">{doc.description}</p> : null}
|
|
227
|
+
</header>
|
|
228
|
+
|
|
229
|
+
<ArticleMeasure />
|
|
230
|
+
|
|
231
|
+
<div className="e-content">
|
|
232
|
+
<MarkdownArticle content={doc.content} lang={lang} fieldTables handbook refDemos={isReferencePath(doc.path)} />
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
<ArticleReferences references={doc.references} lang={lang} />
|
|
236
|
+
|
|
237
|
+
<nav className="gdoc-pager" aria-label={t.pager}>
|
|
238
|
+
{prev ? (
|
|
239
|
+
<Link href={siteHref(handbookPath(lang, prev.path))} className="gdoc-pager-card is-prev">
|
|
240
|
+
<span className="gdoc-pager-dir">
|
|
241
|
+
<ArrowLeft size={14} aria-hidden="true" />
|
|
242
|
+
{t.prev}
|
|
243
|
+
</span>
|
|
244
|
+
<strong>{prev.title}</strong>
|
|
245
|
+
</Link>
|
|
246
|
+
) : (
|
|
247
|
+
<Link href={siteHref(handbookPath(lang))} className="gdoc-pager-card is-prev">
|
|
248
|
+
<span className="gdoc-pager-dir">
|
|
249
|
+
<ArrowLeft size={14} aria-hidden="true" />
|
|
250
|
+
{t.backToOverview}
|
|
251
|
+
</span>
|
|
252
|
+
<strong>{t.overviewDocTitle}</strong>
|
|
253
|
+
</Link>
|
|
254
|
+
)}
|
|
255
|
+
{next ? (
|
|
256
|
+
<Link href={siteHref(handbookPath(lang, next.path))} className="gdoc-pager-card is-next">
|
|
257
|
+
<span className="gdoc-pager-dir">
|
|
258
|
+
{t.next}
|
|
259
|
+
<ArrowRight size={14} aria-hidden="true" />
|
|
260
|
+
</span>
|
|
261
|
+
<strong>{next.title}</strong>
|
|
262
|
+
</Link>
|
|
263
|
+
) : null}
|
|
264
|
+
</nav>
|
|
265
|
+
</article>
|
|
266
|
+
|
|
267
|
+
<TableOfContents headings={headings} label={t.toc} lang={lang} />
|
|
268
|
+
</main>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { UiLangProvider } from "./UiLang";
|
|
2
|
+
import { siteLang, ui, type UiLang } from "../lib/i18n";
|
|
1
3
|
import { markdownPlugins } from "../lib/markdown-plugins";
|
|
2
4
|
import { remarkA2 } from "../lib/remark-a2.mjs";
|
|
3
5
|
import { remarkLinkCard } from "../lib/remark-link-card.mjs";
|
|
@@ -22,15 +24,19 @@ import { CodeCopy } from "./garden/chrome";
|
|
|
22
24
|
import { CodeDiff } from "./garden/chrome";
|
|
23
25
|
import { GardenBlock } from "./garden/GardenBlock";
|
|
24
26
|
import { GardenFile } from "./garden/GardenFile";
|
|
27
|
+
import { RefDemo } from "./garden/RefDemo";
|
|
28
|
+
import { remarkRefDemos } from "../lib/remark-ref-demos.mjs";
|
|
25
29
|
import { readGardenPayload } from "../lib/remark-garden.mjs";
|
|
26
30
|
import { imageSizes, linkAssets } from "@garden/generated-content";
|
|
27
31
|
import { imageDimensions, lookupImageSize } from "../../ui/garden/image-dims.mjs";
|
|
28
32
|
import { siteConfig, siteLinks } from "@garden/site-config";
|
|
29
33
|
import { articleLink, sameSiteMatcher } from "../../ui/garden/same-site.mjs";
|
|
30
34
|
import { columnRedirects, internalLinkResolver, readRedirects } from "../../ui/garden/internal-links.mjs";
|
|
31
|
-
import { columnHref, columns, gardenDocHref, gardenDocs, postHref, posts, thoughts } from "../lib/content";
|
|
35
|
+
import { columnHref, columns, gardenDocHref, gardenDocs, gardenDocsEn, postHref, posts, thoughts } from "../lib/content";
|
|
32
36
|
import { LinkCardRefresh } from "./LinkCardRefresh";
|
|
33
37
|
import { linkHost } from "../../scripts/link-meta-core.mjs";
|
|
38
|
+
import { FaviconProvider, SourceMark } from "./garden/patterns";
|
|
39
|
+
import { rehypeFieldTables } from "../lib/rehype-field-tables.mjs";
|
|
34
40
|
import { cardDate } from "../../ui/garden/card-refresh.mjs";
|
|
35
41
|
import { siteHref } from "../lib/site-href";
|
|
36
42
|
import { codeLanguageLabel, fenceLanguage } from "../../ui/garden/code-language.mjs";
|
|
@@ -40,23 +46,36 @@ const sameSite = sameSiteMatcher(siteLinks?.address ?? "", siteLinks?.basePath ?
|
|
|
40
46
|
|
|
41
47
|
// Links to other pages of the site render as Link. A link to an old address listed under
|
|
42
48
|
// redirects is rewritten to the page it moved to; a link that lands on no known page stays a plain anchor.
|
|
43
|
-
const
|
|
49
|
+
const siteRoute = internalLinkResolver({
|
|
44
50
|
redirects: [
|
|
45
51
|
...readRedirects(siteConfig),
|
|
46
52
|
...columnRedirects({ columns: [...new Set(columns.map((entry) => entry.column).filter(Boolean))].map((slug) => ({ slug })) }),
|
|
47
53
|
],
|
|
48
54
|
routes: [
|
|
49
|
-
"/", "/blog", "/columns", "/thoughts", "/tags", "/about", "/garden",
|
|
55
|
+
"/", "/blog", "/columns", "/thoughts", "/tags", "/about", "/garden", "/garden/en",
|
|
50
56
|
...posts.map(postHref),
|
|
51
57
|
...columns.map(columnHref),
|
|
52
58
|
...thoughts.map((thought) => `/thoughts/${thought.slug}`),
|
|
53
59
|
...gardenDocs.map(gardenDocHref),
|
|
60
|
+
...gardenDocsEn.map((doc) => siteHref(`/garden/en/${doc.path}`)),
|
|
54
61
|
],
|
|
55
62
|
basePath: siteLinks?.basePath ?? "",
|
|
56
63
|
// Same shape as every other address in the site (the slash follows trailingSlash).
|
|
57
64
|
format: siteHref,
|
|
58
65
|
});
|
|
59
66
|
|
|
67
|
+
// The handbook ships with the framework and its links are written without
|
|
68
|
+
// any base path (/garden/…); Link adds the site's own prefix.
|
|
69
|
+
const handbookRoute = internalLinkResolver({
|
|
70
|
+
redirects: [],
|
|
71
|
+
routes: [
|
|
72
|
+
"/garden", "/garden/en",
|
|
73
|
+
...gardenDocs.map(gardenDocHref),
|
|
74
|
+
...gardenDocsEn.map((doc) => siteHref(`/garden/en/${doc.path}`)),
|
|
75
|
+
],
|
|
76
|
+
format: siteHref,
|
|
77
|
+
});
|
|
78
|
+
|
|
60
79
|
/** The site's favicon, downloaded at build time; nothing when there is none. */
|
|
61
80
|
function Favicon({ href }: { href: string }) {
|
|
62
81
|
const src = linkAssets?.favicons?.[linkHost(href)];
|
|
@@ -208,7 +227,9 @@ const headingText = (children: ReactNode) => {
|
|
|
208
227
|
return parts.join("");
|
|
209
228
|
};
|
|
210
229
|
|
|
211
|
-
export function MarkdownArticle({ content, numbering }: { content: string; numbering?: boolean }) {
|
|
230
|
+
export function MarkdownArticle({ content, numbering, lang, fieldTables, refDemos, handbook }: { content: string; numbering?: boolean; lang?: UiLang; fieldTables?: boolean; refDemos?: boolean; handbook?: boolean }) {
|
|
231
|
+
const internalRoute = handbook ? handbookRoute : siteRoute;
|
|
232
|
+
const t = ui(lang);
|
|
212
233
|
// Optional layer 2: a Worker that refreshes card text. Without it, cards stay as built.
|
|
213
234
|
const cardWorker = content.includes("{card}") ? siteConfig.linkPreview?.worker : undefined;
|
|
214
235
|
const numbers = { h2: 0, h3: 0 };
|
|
@@ -248,9 +269,10 @@ export function MarkdownArticle({ content, numbering }: { content: string; numbe
|
|
|
248
269
|
"garden-block": (props: { dataPayload?: string; "data-payload"?: string }) => {
|
|
249
270
|
const payload = props.dataPayload ?? props["data-payload"];
|
|
250
271
|
const block = readGardenPayload(payload);
|
|
251
|
-
if (block?.name === "file" && !block.error) return <GardenFile props={(block.props ?? {}) as Record<string, unknown>} />;
|
|
272
|
+
if (block?.name === "file" && !block.error) return <GardenFile props={(block.props ?? {}) as Record<string, unknown>} lang={lang} />;
|
|
252
273
|
return <GardenBlock dataPayload={payload} />;
|
|
253
274
|
},
|
|
275
|
+
"garden-refdemo": (props: { children?: ReactNode; "data-name"?: string }) => <RefDemo lang={lang === "en" ? "en" : "zh"} name={props["data-name"]}>{props.children}</RefDemo>,
|
|
254
276
|
"garden-codediff": (props: {
|
|
255
277
|
children?: ReactNode;
|
|
256
278
|
dataDiff?: string;
|
|
@@ -266,9 +288,11 @@ export function MarkdownArticle({ content, numbering }: { content: string; numbe
|
|
|
266
288
|
),
|
|
267
289
|
};
|
|
268
290
|
return (
|
|
291
|
+
<UiLangProvider lang={lang}>
|
|
292
|
+
<FaviconProvider favicons={linkAssets?.favicons}>
|
|
269
293
|
<div className="markdown-body prose">
|
|
270
294
|
<ReactMarkdown
|
|
271
|
-
remarkPlugins={[...markdownPlugins, [remarkLinkCard, { sameSite }], [remarkA2, { imageSize: (src: string) => lookupImageSize(imageSizes, src) }]]}
|
|
295
|
+
remarkPlugins={[...(refDemos ? [remarkRefDemos] : []), ...markdownPlugins, [remarkLinkCard, { sameSite }], [remarkA2, { imageSize: (src: string) => lookupImageSize(imageSizes, src), lang: lang ?? siteLang }]]}
|
|
272
296
|
rehypePlugins={[
|
|
273
297
|
rehypeRaw,
|
|
274
298
|
rehypeKatex,
|
|
@@ -276,6 +300,8 @@ export function MarkdownArticle({ content, numbering }: { content: string; numbe
|
|
|
276
300
|
// Not rehype-autolink-headings "wrap": a heading with a link in it would nest <a> in <a> (React #418).
|
|
277
301
|
[rehypeHeadingAnchors, { className: ["h-anchor"] }],
|
|
278
302
|
rehypeHighlight,
|
|
303
|
+
// Handbook only: field tables become cards on narrow screens.
|
|
304
|
+
...(fieldTables ? [rehypeFieldTables] : []),
|
|
279
305
|
]}
|
|
280
306
|
components={{
|
|
281
307
|
p: ({ children, ...props }) => {
|
|
@@ -307,10 +333,12 @@ export function MarkdownArticle({ content, numbering }: { content: string; numbe
|
|
|
307
333
|
href={href}
|
|
308
334
|
{...domProps}
|
|
309
335
|
{...(external
|
|
310
|
-
? { className: "ext", target: "_blank", rel: "noopener noreferrer" }
|
|
336
|
+
? { ...(domProps.className ? { className: `ext ${domProps.className}` } : { className: "ext" }), target: "_blank", rel: "noopener noreferrer" }
|
|
311
337
|
: {})}
|
|
312
338
|
>
|
|
313
|
-
{external && href
|
|
339
|
+
{external && href && /\bg-src-chip\b/u.test(String(domProps.className ?? ""))
|
|
340
|
+
? <><SourceMark href={href} /><span>{children}</span></>
|
|
341
|
+
: <>{external && href ? <WithFavicon href={href}>{children}</WithFavicon> : children}</>}
|
|
314
342
|
</a>
|
|
315
343
|
);
|
|
316
344
|
},
|
|
@@ -320,7 +348,7 @@ export function MarkdownArticle({ content, numbering }: { content: string; numbe
|
|
|
320
348
|
return <MarkdownImage {...props} {...dims} style={{ ...props.style, ...dims.style }} />;
|
|
321
349
|
},
|
|
322
350
|
table: ({ children, ...props }) => (
|
|
323
|
-
<div className="table-wrap" role="region" aria-label=
|
|
351
|
+
<div className={/\bfield-table\b/u.test(String(props.className ?? "")) ? "table-wrap field-wrap" : "table-wrap"} role="region" aria-label={t.tableScroll} tabIndex={0}>
|
|
324
352
|
<table {...props}>{children}</table>
|
|
325
353
|
</div>
|
|
326
354
|
),
|
|
@@ -356,5 +384,7 @@ export function MarkdownArticle({ content, numbering }: { content: string; numbe
|
|
|
356
384
|
</ReactMarkdown>
|
|
357
385
|
{cardWorker ? <LinkCardRefresh endpoint={cardWorker} /> : null}
|
|
358
386
|
</div>
|
|
387
|
+
</FaviconProvider>
|
|
388
|
+
</UiLangProvider>
|
|
359
389
|
);
|
|
360
390
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useUi } from "./UiLang";
|
|
3
4
|
import { useEffect, useState, type ComponentPropsWithoutRef } from "react";
|
|
4
5
|
|
|
5
6
|
// react-markdown also hands over its syntax-tree `node`; it must not reach the DOM.
|
|
@@ -8,7 +9,8 @@ type MarkdownImageProps = ComponentPropsWithoutRef<"img"> & { node?: unknown };
|
|
|
8
9
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
9
10
|
export function MarkdownImage({ alt = "", src, node, style, ...props }: MarkdownImageProps) {
|
|
10
11
|
const [open, setOpen] = useState(false);
|
|
11
|
-
const
|
|
12
|
+
const t = useUi();
|
|
13
|
+
const description = alt || t.viewImage;
|
|
12
14
|
|
|
13
15
|
useEffect(() => {
|
|
14
16
|
if (!open) return undefined;
|
|
@@ -33,7 +35,7 @@ export function MarkdownImage({ alt = "", src, node, style, ...props }: Markdown
|
|
|
33
35
|
type="button"
|
|
34
36
|
className="markdown-image-trigger"
|
|
35
37
|
onClick={() => setOpen(true)}
|
|
36
|
-
aria-label={
|
|
38
|
+
aria-label={t.zoomImage(description)}
|
|
37
39
|
>
|
|
38
40
|
{/* Native images preserve Typora-authored relative paths. */}
|
|
39
41
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
@@ -56,9 +58,9 @@ export function MarkdownImage({ alt = "", src, node, style, ...props }: Markdown
|
|
|
56
58
|
type="button"
|
|
57
59
|
className="markdown-image-lightbox-close"
|
|
58
60
|
onClick={() => setOpen(false)}
|
|
59
|
-
aria-label=
|
|
61
|
+
aria-label={t.closeImage}
|
|
60
62
|
>
|
|
61
|
-
|
|
63
|
+
{t.close.trim()}
|
|
62
64
|
</button>
|
|
63
65
|
</div>
|
|
64
66
|
</div>
|
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { usePathname } from "next/navigation";
|
|
4
|
+
import { inAppPath, isHandbookPath, routeLang, ui } from "../lib/i18n";
|
|
4
5
|
import { SiteFooter } from "./SiteFooter";
|
|
5
6
|
import { SiteHeader } from "./SiteHeader";
|
|
6
7
|
|
|
7
8
|
export function SiteChrome({ children }: { children: React.ReactNode }) {
|
|
8
9
|
const pathname = usePathname();
|
|
9
|
-
|
|
10
|
+
const lang = routeLang(pathname);
|
|
11
|
+
// The skip link follows the route language too (/garden/en/ is English).
|
|
12
|
+
const skip = (
|
|
13
|
+
<a className="skip-link" href="#main-content">
|
|
14
|
+
{ui(lang).skipToContent}
|
|
15
|
+
</a>
|
|
16
|
+
);
|
|
17
|
+
if (inAppPath(pathname).startsWith("/admin")) return <>{skip}{children}</>;
|
|
10
18
|
|
|
11
19
|
return (
|
|
12
20
|
<>
|
|
21
|
+
{skip}
|
|
13
22
|
<SiteHeader />
|
|
14
23
|
<div id="main-content">{children}</div>
|
|
15
|
-
<SiteFooter docs={pathname
|
|
24
|
+
<SiteFooter docs={isHandbookPath(pathname)} lang={lang} />
|
|
16
25
|
</>
|
|
17
26
|
);
|
|
18
27
|
}
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { GithubLogo, Rss } from "@phosphor-icons/react/ssr";
|
|
2
2
|
import { githubUrl, siteConfig } from "../site.config";
|
|
3
|
+
import { ui, type UiLang } from "../lib/i18n";
|
|
3
4
|
|
|
4
5
|
/** On the Garden docs pages the footer carries no site-owner line. */
|
|
5
|
-
export function SiteFooter({ docs = false }: { docs?: boolean }) {
|
|
6
|
+
export function SiteFooter({ docs = false, lang }: { docs?: boolean; lang?: UiLang }) {
|
|
7
|
+
const t = ui(lang);
|
|
6
8
|
return (
|
|
7
9
|
<footer className="sh-footer">
|
|
8
10
|
<div className="sh-foot">
|
|
9
11
|
<p>
|
|
10
12
|
{docs ? (
|
|
11
|
-
<span className="footer-framework"
|
|
13
|
+
<span className="footer-framework">{t.builtWith[0]}<a href="https://github.com/hedon954/garden" target="_blank" rel="noreferrer">Garden</a>{t.builtWith[1]}</span>
|
|
12
14
|
) : (
|
|
13
15
|
<>
|
|
14
16
|
© {new Date().getFullYear()} {siteConfig.author.name} · {siteConfig.footer}
|
|
15
|
-
<span className="footer-framework"> ·
|
|
17
|
+
<span className="footer-framework"> · {t.builtWith[0]}<a href="https://github.com/hedon954/garden" target="_blank" rel="noreferrer">Garden</a>{t.builtWith[1]}</span>
|
|
16
18
|
</>
|
|
17
19
|
)}
|
|
18
20
|
</p>
|
|
@@ -21,7 +23,7 @@ export function SiteFooter({ docs = false }: { docs?: boolean }) {
|
|
|
21
23
|
<GithubLogo size={16} aria-hidden="true" />
|
|
22
24
|
GitHub
|
|
23
25
|
</a>
|
|
24
|
-
<a href="/rss.xml
|
|
26
|
+
<a href={`${(process.env.NEXT_PUBLIC_BASE_PATH ?? "").replace(/\/+$/u, "")}/rss.xml`}>
|
|
25
27
|
<Rss size={16} aria-hidden="true" />
|
|
26
28
|
RSS
|
|
27
29
|
</a>
|