hd-garden 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/garden-ui/SKILL.md +2 -2
- package/.env.en.example +67 -0
- package/README.en.md +2 -2
- package/README.md +2 -2
- 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 +7 -6
- package/app/blog/page.tsx +4 -2
- package/app/columns/[column]/[...path]/page.tsx +8 -7
- package/app/columns/page.tsx +12 -8
- package/app/components/AdminUiShell.tsx +4 -3
- package/app/components/ArticleMeasure.tsx +6 -3
- 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 +16 -10
- package/app/components/ContentEmptyState.tsx +29 -28
- package/app/components/ExternalEmbed.tsx +15 -14
- package/app/components/GardenContentGuide.tsx +10 -2
- package/app/components/GardenDocsNav.tsx +4 -2
- package/app/components/GiscusComments.tsx +2 -1
- package/app/components/GithubLiveOverview.tsx +10 -8
- package/app/components/GithubProfile.tsx +31 -21
- package/app/components/HandbookLangSwitch.tsx +8 -4
- package/app/components/HandbookView.tsx +92 -9
- package/app/components/MarkdownArticle.tsx +31 -7
- package/app/components/MermaidDiagram.tsx +4 -3
- package/app/components/SiteChrome.tsx +3 -3
- package/app/components/SiteFooter.tsx +1 -1
- package/app/components/SiteHeader.tsx +13 -4
- package/app/components/ThoughtCard.tsx +16 -14
- package/app/components/UiBlock.tsx +3 -2
- package/app/components/Webmentions.tsx +11 -11
- package/app/components/WidgetChart.tsx +8 -6
- package/app/components/garden/FilePreview.tsx +2 -2
- package/app/components/garden/GardenBlock.tsx +7 -0
- package/app/components/garden/GardenFile.tsx +2 -2
- package/app/components/garden/RefDemo.tsx +62 -0
- package/app/components/garden/blocks.tsx +56 -8
- package/app/components/garden/patterns.tsx +497 -0
- package/app/garden/[...slug]/page.tsx +3 -2
- package/app/garden/en/page.tsx +7 -7
- package/app/garden/page.tsx +3 -2
- package/app/globals.css +49 -1
- package/app/lib/a2-meta.ts +26 -3
- package/app/lib/content.ts +2 -2
- package/app/lib/garden-docs-meta.ts +24 -0
- package/app/lib/github-id.mjs +8 -0
- package/app/lib/handbook-switch.mjs +12 -0
- package/app/lib/i18n.ts +401 -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 +32 -4
- package/app/page.tsx +17 -14
- package/app/rss.xml/route.ts +2 -1
- package/app/styles/a2.css +338 -0
- package/app/styles/shell.css +15 -2
- package/app/tags/page.tsx +7 -5
- package/app/thoughts/[slug]/page.tsx +2 -1
- package/app/thoughts/page/[n]/page.tsx +1 -1
- package/app/thoughts/page.tsx +2 -1
- package/app/ui-parity/page.tsx +38 -22
- package/bin/garden.mjs +129 -19
- package/docs/handbook/ai-writing.md +2 -2
- package/docs/handbook/assets/reading-layout-en.svg +26 -0
- package/docs/handbook/components/card.md +43 -22
- package/docs/handbook/components/chart.md +10 -26
- package/docs/handbook/components/choice.md +42 -0
- package/docs/handbook/components/compare.md +32 -17
- package/docs/handbook/components/figure.md +33 -46
- package/docs/handbook/components/file.md +10 -15
- package/docs/handbook/components/html.md +10 -30
- package/docs/handbook/components/interactive.md +9 -35
- package/docs/handbook/components/list.md +65 -0
- package/docs/handbook/components/relations.md +95 -0
- package/docs/handbook/components/stat.md +10 -28
- package/docs/handbook/components/timeline.md +13 -28
- package/docs/handbook/components.md +210 -19
- package/docs/handbook/config.md +5 -3
- package/docs/handbook/deploy.md +1 -1
- package/docs/handbook/design.md +20 -12
- package/docs/handbook/en/ai-writing.md +2 -2
- package/docs/handbook/en/components/card.md +42 -21
- package/docs/handbook/en/components/chart.md +10 -26
- package/docs/handbook/en/components/choice.md +42 -0
- package/docs/handbook/en/components/compare.md +30 -15
- package/docs/handbook/en/components/figure.md +29 -42
- package/docs/handbook/en/components/file.md +9 -14
- package/docs/handbook/en/components/html.md +9 -29
- package/docs/handbook/en/components/interactive.md +8 -34
- 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 +9 -27
- package/docs/handbook/en/components/timeline.md +9 -24
- package/docs/handbook/en/components.md +211 -19
- package/docs/handbook/en/config.md +32 -30
- package/docs/handbook/en/deploy.md +12 -12
- package/docs/handbook/en/design.md +19 -12
- package/docs/handbook/en/index.md +2 -2
- 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 +2 -2
- package/docs/handbook/en/workflow.md +6 -5
- package/docs/handbook/en/writing.md +3 -2
- package/docs/handbook/index.md +1 -1
- 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 +3 -2
- 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 +37 -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 +305 -11
- 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/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/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/garden/catalog.mjs +205 -4
- package/ui/garden/check.mjs +6 -0
- package/ui/garden/file-block.mjs +8 -7
- package/ui/garden/html-lang.mjs +73 -1
- package/ui/garden/markdown-links.mjs +7 -4
- 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.esm.js +41 -4
- package/ui/preview/dist/garden-ui.iife.js +41 -4
- package/ui/prompt/rules.md +28 -1
- package/ui/react/components/chart.tsx +1 -1
- package/ui/react/components/display.tsx +1 -1
- package/ui/react/lang.tsx +4 -0
- package/ui/react/parity.tsx +8 -6
- package/worker/index.ts +8 -2
|
@@ -17,11 +17,13 @@ import {
|
|
|
17
17
|
HANDBOOK_SECTIONS,
|
|
18
18
|
HANDBOOK_UI,
|
|
19
19
|
handbookPath,
|
|
20
|
+
isReferencePath,
|
|
20
21
|
orderedHandbookDocs,
|
|
21
22
|
sectionTitle,
|
|
22
23
|
toHandbookNavItems,
|
|
23
24
|
type HandbookLang,
|
|
24
25
|
} from "../lib/garden-docs-meta";
|
|
26
|
+
import { UI_STRINGS, siteLang } from "../lib/i18n";
|
|
25
27
|
import { siteConfig } from "../site.config";
|
|
26
28
|
|
|
27
29
|
const other = (lang: HandbookLang): HandbookLang => (lang === "en" ? "zh" : "en");
|
|
@@ -29,6 +31,7 @@ const hreflang = (lang: HandbookLang) => (lang === "en" ? "en" : "zh-CN");
|
|
|
29
31
|
|
|
30
32
|
/** canonical + hreflang links; the counterpart is listed only when it exists. */
|
|
31
33
|
export function handbookLanguages(lang: HandbookLang, docPath: string) {
|
|
34
|
+
if (siteLang === "en") return undefined;
|
|
32
35
|
if (!hasHandbookPage(other(lang), docPath)) return undefined;
|
|
33
36
|
const zh = siteHref(handbookPath("zh", docPath));
|
|
34
37
|
const en = siteHref(handbookPath("en", docPath));
|
|
@@ -38,21 +41,101 @@ export function handbookLanguages(lang: HandbookLang, docPath: string) {
|
|
|
38
41
|
export const handbookAlternates = (lang: HandbookLang, docPath: string) =>
|
|
39
42
|
pageAlternates(handbookPath(lang, docPath), handbookLanguages(lang, docPath));
|
|
40
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
|
+
|
|
41
82
|
export async function handbookDocMetadata(lang: HandbookLang, doc: GardenDocEntry | undefined) {
|
|
42
|
-
if (!doc)
|
|
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
|
+
}
|
|
43
111
|
const t = HANDBOOK_UI[lang];
|
|
44
112
|
const baseUrl = await getSiteUrl();
|
|
45
113
|
const pathname = siteHref(handbookPath(lang, doc.path));
|
|
114
|
+
const docTitle = `${doc.title} · ${t.name}`;
|
|
46
115
|
return {
|
|
47
|
-
title:
|
|
116
|
+
title: docTitle,
|
|
48
117
|
description: doc.description,
|
|
49
118
|
alternates: handbookAlternates(lang, doc.path),
|
|
50
119
|
openGraph: {
|
|
51
120
|
type: "article",
|
|
52
121
|
url: absoluteUrl(pathname, baseUrl),
|
|
53
|
-
title:
|
|
122
|
+
title: docTitle,
|
|
54
123
|
description: doc.description,
|
|
55
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"],
|
|
56
139
|
},
|
|
57
140
|
} satisfies Metadata;
|
|
58
141
|
}
|
|
@@ -66,7 +149,7 @@ export function HandbookOverview({ lang }: { lang: HandbookLang }) {
|
|
|
66
149
|
const content = indexDoc?.content ?? "";
|
|
67
150
|
const headings = extractHeadings(content);
|
|
68
151
|
// The site config title/subtitle is written in the site's own language.
|
|
69
|
-
const configured = lang ===
|
|
152
|
+
const configured = lang === siteLang ? siteConfig.pages.garden : { title: "", subtitle: "" };
|
|
70
153
|
const title = configured.title?.trim() || indexDoc?.title || t.name;
|
|
71
154
|
const subtitle = configured.subtitle?.trim() || indexDoc?.description || "";
|
|
72
155
|
|
|
@@ -89,10 +172,10 @@ export function HandbookOverview({ lang }: { lang: HandbookLang }) {
|
|
|
89
172
|
<ArticleMeasure />
|
|
90
173
|
|
|
91
174
|
<div className="e-content">
|
|
92
|
-
<MarkdownArticle content={content} lang={lang} />
|
|
175
|
+
<MarkdownArticle content={content} lang={lang} fieldTables handbook />
|
|
93
176
|
</div>
|
|
94
177
|
|
|
95
|
-
<ArticleReferences references={indexDoc?.references} />
|
|
178
|
+
<ArticleReferences references={indexDoc?.references} lang={lang} />
|
|
96
179
|
|
|
97
180
|
{firstDoc ? (
|
|
98
181
|
<nav className="gdoc-pager" aria-label={t.pager}>
|
|
@@ -125,7 +208,7 @@ export function HandbookDoc({ lang, doc }: { lang: HandbookLang; doc: GardenDocE
|
|
|
125
208
|
const componentBadge = COMPONENT_BADGE[doc.path];
|
|
126
209
|
|
|
127
210
|
return (
|
|
128
|
-
<main className=
|
|
211
|
+
<main className={`column-reading-page garden-doc-page${isReferencePath(doc.path) ? " is-reference" : ""}`} lang={hreflang(lang)}>
|
|
129
212
|
<HandbookLangRedirect lang={lang} />
|
|
130
213
|
<GardenDocsNav docs={navItems} currentPath={doc.path} lang={lang} />
|
|
131
214
|
|
|
@@ -146,10 +229,10 @@ export function HandbookDoc({ lang, doc }: { lang: HandbookLang; doc: GardenDocE
|
|
|
146
229
|
<ArticleMeasure />
|
|
147
230
|
|
|
148
231
|
<div className="e-content">
|
|
149
|
-
<MarkdownArticle content={doc.content} lang={lang} />
|
|
232
|
+
<MarkdownArticle content={doc.content} lang={lang} fieldTables handbook refDemos={isReferencePath(doc.path)} />
|
|
150
233
|
</div>
|
|
151
234
|
|
|
152
|
-
<ArticleReferences references={doc.references} />
|
|
235
|
+
<ArticleReferences references={doc.references} lang={lang} />
|
|
153
236
|
|
|
154
237
|
<nav className="gdoc-pager" aria-label={t.pager}>
|
|
155
238
|
{prev ? (
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { UiLangProvider } from "./UiLang";
|
|
2
|
-
import { ui, type UiLang } from "../lib/i18n";
|
|
2
|
+
import { siteLang, ui, type UiLang } from "../lib/i18n";
|
|
3
3
|
import { markdownPlugins } from "../lib/markdown-plugins";
|
|
4
4
|
import { remarkA2 } from "../lib/remark-a2.mjs";
|
|
5
5
|
import { remarkLinkCard } from "../lib/remark-link-card.mjs";
|
|
@@ -24,6 +24,8 @@ import { CodeCopy } from "./garden/chrome";
|
|
|
24
24
|
import { CodeDiff } from "./garden/chrome";
|
|
25
25
|
import { GardenBlock } from "./garden/GardenBlock";
|
|
26
26
|
import { GardenFile } from "./garden/GardenFile";
|
|
27
|
+
import { RefDemo } from "./garden/RefDemo";
|
|
28
|
+
import { remarkRefDemos } from "../lib/remark-ref-demos.mjs";
|
|
27
29
|
import { readGardenPayload } from "../lib/remark-garden.mjs";
|
|
28
30
|
import { imageSizes, linkAssets } from "@garden/generated-content";
|
|
29
31
|
import { imageDimensions, lookupImageSize } from "../../ui/garden/image-dims.mjs";
|
|
@@ -33,6 +35,8 @@ import { columnRedirects, internalLinkResolver, readRedirects } from "../../ui/g
|
|
|
33
35
|
import { columnHref, columns, gardenDocHref, gardenDocs, gardenDocsEn, postHref, posts, thoughts } from "../lib/content";
|
|
34
36
|
import { LinkCardRefresh } from "./LinkCardRefresh";
|
|
35
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";
|
|
36
40
|
import { cardDate } from "../../ui/garden/card-refresh.mjs";
|
|
37
41
|
import { siteHref } from "../lib/site-href";
|
|
38
42
|
import { codeLanguageLabel, fenceLanguage } from "../../ui/garden/code-language.mjs";
|
|
@@ -42,7 +46,7 @@ const sameSite = sameSiteMatcher(siteLinks?.address ?? "", siteLinks?.basePath ?
|
|
|
42
46
|
|
|
43
47
|
// Links to other pages of the site render as Link. A link to an old address listed under
|
|
44
48
|
// redirects is rewritten to the page it moved to; a link that lands on no known page stays a plain anchor.
|
|
45
|
-
const
|
|
49
|
+
const siteRoute = internalLinkResolver({
|
|
46
50
|
redirects: [
|
|
47
51
|
...readRedirects(siteConfig),
|
|
48
52
|
...columnRedirects({ columns: [...new Set(columns.map((entry) => entry.column).filter(Boolean))].map((slug) => ({ slug })) }),
|
|
@@ -60,6 +64,18 @@ const internalRoute = internalLinkResolver({
|
|
|
60
64
|
format: siteHref,
|
|
61
65
|
});
|
|
62
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
|
+
|
|
63
79
|
/** The site's favicon, downloaded at build time; nothing when there is none. */
|
|
64
80
|
function Favicon({ href }: { href: string }) {
|
|
65
81
|
const src = linkAssets?.favicons?.[linkHost(href)];
|
|
@@ -211,7 +227,8 @@ const headingText = (children: ReactNode) => {
|
|
|
211
227
|
return parts.join("");
|
|
212
228
|
};
|
|
213
229
|
|
|
214
|
-
export function MarkdownArticle({ content, numbering, lang }: { content: string; numbering?: boolean; lang?: UiLang }) {
|
|
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;
|
|
215
232
|
const t = ui(lang);
|
|
216
233
|
// Optional layer 2: a Worker that refreshes card text. Without it, cards stay as built.
|
|
217
234
|
const cardWorker = content.includes("{card}") ? siteConfig.linkPreview?.worker : undefined;
|
|
@@ -255,6 +272,7 @@ export function MarkdownArticle({ content, numbering, lang }: { content: string;
|
|
|
255
272
|
if (block?.name === "file" && !block.error) return <GardenFile props={(block.props ?? {}) as Record<string, unknown>} lang={lang} />;
|
|
256
273
|
return <GardenBlock dataPayload={payload} />;
|
|
257
274
|
},
|
|
275
|
+
"garden-refdemo": (props: { children?: ReactNode; "data-name"?: string }) => <RefDemo lang={lang === "en" ? "en" : "zh"} name={props["data-name"]}>{props.children}</RefDemo>,
|
|
258
276
|
"garden-codediff": (props: {
|
|
259
277
|
children?: ReactNode;
|
|
260
278
|
dataDiff?: string;
|
|
@@ -271,9 +289,10 @@ export function MarkdownArticle({ content, numbering, lang }: { content: string;
|
|
|
271
289
|
};
|
|
272
290
|
return (
|
|
273
291
|
<UiLangProvider lang={lang}>
|
|
292
|
+
<FaviconProvider favicons={linkAssets?.favicons}>
|
|
274
293
|
<div className="markdown-body prose">
|
|
275
294
|
<ReactMarkdown
|
|
276
|
-
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 }]]}
|
|
277
296
|
rehypePlugins={[
|
|
278
297
|
rehypeRaw,
|
|
279
298
|
rehypeKatex,
|
|
@@ -281,6 +300,8 @@ export function MarkdownArticle({ content, numbering, lang }: { content: string;
|
|
|
281
300
|
// Not rehype-autolink-headings "wrap": a heading with a link in it would nest <a> in <a> (React #418).
|
|
282
301
|
[rehypeHeadingAnchors, { className: ["h-anchor"] }],
|
|
283
302
|
rehypeHighlight,
|
|
303
|
+
// Handbook only: field tables become cards on narrow screens.
|
|
304
|
+
...(fieldTables ? [rehypeFieldTables] : []),
|
|
284
305
|
]}
|
|
285
306
|
components={{
|
|
286
307
|
p: ({ children, ...props }) => {
|
|
@@ -312,10 +333,12 @@ export function MarkdownArticle({ content, numbering, lang }: { content: string;
|
|
|
312
333
|
href={href}
|
|
313
334
|
{...domProps}
|
|
314
335
|
{...(external
|
|
315
|
-
? { className: "ext", target: "_blank", rel: "noopener noreferrer" }
|
|
336
|
+
? { ...(domProps.className ? { className: `ext ${domProps.className}` } : { className: "ext" }), target: "_blank", rel: "noopener noreferrer" }
|
|
316
337
|
: {})}
|
|
317
338
|
>
|
|
318
|
-
{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}</>}
|
|
319
342
|
</a>
|
|
320
343
|
);
|
|
321
344
|
},
|
|
@@ -325,7 +348,7 @@ export function MarkdownArticle({ content, numbering, lang }: { content: string;
|
|
|
325
348
|
return <MarkdownImage {...props} {...dims} style={{ ...props.style, ...dims.style }} />;
|
|
326
349
|
},
|
|
327
350
|
table: ({ children, ...props }) => (
|
|
328
|
-
<div className="table-wrap" role="region" aria-label={t.tableScroll} tabIndex={0}>
|
|
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}>
|
|
329
352
|
<table {...props}>{children}</table>
|
|
330
353
|
</div>
|
|
331
354
|
),
|
|
@@ -361,6 +384,7 @@ export function MarkdownArticle({ content, numbering, lang }: { content: string;
|
|
|
361
384
|
</ReactMarkdown>
|
|
362
385
|
{cardWorker ? <LinkCardRefresh endpoint={cardWorker} /> : null}
|
|
363
386
|
</div>
|
|
387
|
+
</FaviconProvider>
|
|
364
388
|
</UiLangProvider>
|
|
365
389
|
);
|
|
366
390
|
}
|
|
@@ -25,10 +25,11 @@ function renderMermaid(source: string, id: string) {
|
|
|
25
25
|
themeVariables: {
|
|
26
26
|
darkMode: dark,
|
|
27
27
|
background: token("--paper"),
|
|
28
|
-
primaryColor: token("--
|
|
29
|
-
primaryBorderColor: token("--
|
|
28
|
+
primaryColor: token("--paper"),
|
|
29
|
+
primaryBorderColor: token("--ink"),
|
|
30
30
|
primaryTextColor: token("--ink"),
|
|
31
|
-
|
|
31
|
+
textColor: token("--ink"),
|
|
32
|
+
lineColor: token("--ink"),
|
|
32
33
|
secondaryColor: token("--well"),
|
|
33
34
|
tertiaryColor: token("--well"),
|
|
34
35
|
fontSize: "14px",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { usePathname } from "next/navigation";
|
|
4
|
-
import { routeLang, ui } from "../lib/i18n";
|
|
4
|
+
import { inAppPath, isHandbookPath, routeLang, ui } from "../lib/i18n";
|
|
5
5
|
import { SiteFooter } from "./SiteFooter";
|
|
6
6
|
import { SiteHeader } from "./SiteHeader";
|
|
7
7
|
|
|
@@ -14,14 +14,14 @@ export function SiteChrome({ children }: { children: React.ReactNode }) {
|
|
|
14
14
|
{ui(lang).skipToContent}
|
|
15
15
|
</a>
|
|
16
16
|
);
|
|
17
|
-
if (pathname
|
|
17
|
+
if (inAppPath(pathname).startsWith("/admin")) return <>{skip}{children}</>;
|
|
18
18
|
|
|
19
19
|
return (
|
|
20
20
|
<>
|
|
21
21
|
{skip}
|
|
22
22
|
<SiteHeader />
|
|
23
23
|
<div id="main-content">{children}</div>
|
|
24
|
-
<SiteFooter docs={pathname
|
|
24
|
+
<SiteFooter docs={isHandbookPath(pathname)} lang={lang} />
|
|
25
25
|
</>
|
|
26
26
|
);
|
|
27
27
|
}
|
|
@@ -23,7 +23,7 @@ export function SiteFooter({ docs = false, lang }: { docs?: boolean; lang?: UiLa
|
|
|
23
23
|
<GithubLogo size={16} aria-hidden="true" />
|
|
24
24
|
GitHub
|
|
25
25
|
</a>
|
|
26
|
-
<a href="/rss.xml
|
|
26
|
+
<a href={`${(process.env.NEXT_PUBLIC_BASE_PATH ?? "").replace(/\/+$/u, "")}/rss.xml`}>
|
|
27
27
|
<Rss size={16} aria-hidden="true" />
|
|
28
28
|
RSS
|
|
29
29
|
</a>
|
|
@@ -17,7 +17,7 @@ import { createSearch } from "../../ui/garden/search-match.mjs";
|
|
|
17
17
|
import { cropMatchedText, highlightParts } from "../../ui/garden/search-preview.mjs";
|
|
18
18
|
import { resolveDark, themeSwitchProps } from "../../ui/garden/theme.mjs";
|
|
19
19
|
import { SITE_NAV, isCurrentNav } from "../../ui/garden/site-nav.mjs";
|
|
20
|
-
import { routeLang, ui } from "../lib/i18n";
|
|
20
|
+
import { isHandbookPath, routeLang, ui } from "../lib/i18n";
|
|
21
21
|
import { HandbookLangSwitch } from "./HandbookLangSwitch";
|
|
22
22
|
import { createIdbStore, loadFullIndex, saveDataOn, whenIdleAfterLoad } from "../../ui/garden/search-full.mjs";
|
|
23
23
|
import { siteConfig } from "../site.config";
|
|
@@ -192,7 +192,12 @@ export function SiteHeader() {
|
|
|
192
192
|
useEffect(() => {
|
|
193
193
|
const system = window.matchMedia("(prefers-color-scheme: dark)");
|
|
194
194
|
const apply = () => {
|
|
195
|
-
|
|
195
|
+
let stored: string | null = null;
|
|
196
|
+
try {
|
|
197
|
+
stored = window.localStorage.getItem("theme");
|
|
198
|
+
} catch {
|
|
199
|
+
// Storage blocked (cookies off, opaque origin): follow the system.
|
|
200
|
+
}
|
|
196
201
|
const useDark = resolveDark(stored, system.matches);
|
|
197
202
|
document.documentElement.dataset.theme = useDark ? "dark" : "light";
|
|
198
203
|
setDark(useDark);
|
|
@@ -298,7 +303,11 @@ export function SiteHeader() {
|
|
|
298
303
|
const next = !dark;
|
|
299
304
|
setDark(next);
|
|
300
305
|
document.documentElement.dataset.theme = next ? "dark" : "light";
|
|
301
|
-
|
|
306
|
+
try {
|
|
307
|
+
window.localStorage.setItem("theme", next ? "dark" : "light");
|
|
308
|
+
} catch {
|
|
309
|
+
// Storage blocked: the choice lasts for this page only.
|
|
310
|
+
}
|
|
302
311
|
};
|
|
303
312
|
|
|
304
313
|
const current = (href: string) => isCurrentNav(pathname, href);
|
|
@@ -338,7 +347,7 @@ export function SiteHeader() {
|
|
|
338
347
|
<path d="M13.3 13.3 17 17" />
|
|
339
348
|
</svg>
|
|
340
349
|
</button>
|
|
341
|
-
{pathname
|
|
350
|
+
{isHandbookPath(pathname) ? <HandbookLangSwitch /> : null}
|
|
342
351
|
<button className="sh-icon sh-theme" type="button" onClick={toggleTheme} aria-label={dark ? t.toLight : t.toDark}>
|
|
343
352
|
<svg className="i-moon" viewBox="0 0 20 20" aria-hidden="true">
|
|
344
353
|
<path d="M16.5 12.2A6.8 6.8 0 0 1 7.8 3.5a6.8 6.8 0 1 0 8.7 8.7Z" />
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import { githubUrl, siteConfig } from "../site.config";
|
|
16
16
|
import { MarkdownArticle } from "./MarkdownArticle";
|
|
17
17
|
import { siteHref } from "../lib/site-href";
|
|
18
|
+
import { siteLang, ui } from "../lib/i18n";
|
|
18
19
|
|
|
19
20
|
const icons = {
|
|
20
21
|
image: ImageSquare,
|
|
@@ -66,11 +67,12 @@ export function ThoughtCard({
|
|
|
66
67
|
const isMixed = media.length > 1;
|
|
67
68
|
const Icon = isMixed ? SquaresFour : icons[thought.mediaType ?? "text"];
|
|
68
69
|
const mediaGroups = groupMedia(media);
|
|
70
|
+
const t = ui();
|
|
69
71
|
|
|
70
72
|
return (
|
|
71
73
|
<article id={thought.slug} className="thought-card h-entry">
|
|
72
74
|
<Link className="u-url sr-only" href={siteHref(`/thoughts/${thought.slug}`)}>
|
|
73
|
-
{thought.title}
|
|
75
|
+
{siteLang === "en" ? t.permalinkLabel(thought.title) : <>{thought.title} 永久链接</>}
|
|
74
76
|
</Link>
|
|
75
77
|
<a
|
|
76
78
|
className="p-author h-card sr-only"
|
|
@@ -83,16 +85,16 @@ export function ThoughtCard({
|
|
|
83
85
|
<span className="thought-kind">
|
|
84
86
|
<Icon size={16} />
|
|
85
87
|
{isMixed
|
|
86
|
-
?
|
|
88
|
+
? t.thoughtMixedLabel(media.length)
|
|
87
89
|
: thought.mediaType === "image"
|
|
88
|
-
?
|
|
90
|
+
? t.thoughtImageLabel
|
|
89
91
|
: thought.mediaType === "audio"
|
|
90
|
-
?
|
|
92
|
+
? t.thoughtAudioLabel
|
|
91
93
|
: thought.mediaType === "video"
|
|
92
|
-
?
|
|
94
|
+
? t.thoughtVideoLabel
|
|
93
95
|
: thought.mediaType === "link"
|
|
94
|
-
?
|
|
95
|
-
:
|
|
96
|
+
? t.thoughtLinkLabel
|
|
97
|
+
: t.thoughtTextLabel}
|
|
96
98
|
</span>
|
|
97
99
|
<time className="dt-published" dateTime={thought.date}>
|
|
98
100
|
{formatDate(thought.date, true)}
|
|
@@ -112,7 +114,7 @@ export function ThoughtCard({
|
|
|
112
114
|
<div
|
|
113
115
|
className={`thought-gallery thought-gallery-${Math.min(group.items.length, 3)}`}
|
|
114
116
|
key={`images-${groupIndex}`}
|
|
115
|
-
aria-label={
|
|
117
|
+
aria-label={t.thoughtGalleryLabel(group.items.length)}
|
|
116
118
|
>
|
|
117
119
|
{group.items.map((item, index) => (
|
|
118
120
|
// eslint-disable-next-line @next/next/no-img-element
|
|
@@ -120,7 +122,7 @@ export function ThoughtCard({
|
|
|
120
122
|
key={`${item.src}-${index}`}
|
|
121
123
|
className="thought-image u-photo"
|
|
122
124
|
src={item.src}
|
|
123
|
-
alt={item.alt ??
|
|
125
|
+
alt={item.alt ?? t.thoughtPhotoAlt(thought.title, index + 1)}
|
|
124
126
|
loading="lazy"
|
|
125
127
|
/>
|
|
126
128
|
))}
|
|
@@ -132,14 +134,14 @@ export function ThoughtCard({
|
|
|
132
134
|
<div className="audio-embed" key={`${group.item.src}-${groupIndex}`}>
|
|
133
135
|
<MusicNotes size={28} />
|
|
134
136
|
<div>
|
|
135
|
-
<strong>{group.item.title ??
|
|
137
|
+
<strong>{group.item.title ?? t.thoughtNowPlaying(thought.title)}</strong>
|
|
136
138
|
<audio
|
|
137
139
|
className="u-audio"
|
|
138
140
|
controls
|
|
139
141
|
preload="none"
|
|
140
142
|
src={group.item.src}
|
|
141
143
|
>
|
|
142
|
-
|
|
144
|
+
{t.audioFallback}
|
|
143
145
|
</audio>
|
|
144
146
|
</div>
|
|
145
147
|
</div>
|
|
@@ -194,14 +196,14 @@ export function ThoughtCard({
|
|
|
194
196
|
<div className="audio-embed">
|
|
195
197
|
<MusicNotes size={28} />
|
|
196
198
|
<div>
|
|
197
|
-
<strong
|
|
199
|
+
<strong>{t.thoughtNowPlaying(thought.title)}</strong>
|
|
198
200
|
<audio
|
|
199
201
|
className="u-audio"
|
|
200
202
|
controls
|
|
201
203
|
preload="none"
|
|
202
204
|
src={thought.media}
|
|
203
205
|
>
|
|
204
|
-
|
|
206
|
+
{t.audioFallback}
|
|
205
207
|
</audio>
|
|
206
208
|
</div>
|
|
207
209
|
</div>
|
|
@@ -234,7 +236,7 @@ export function ThoughtCard({
|
|
|
234
236
|
|
|
235
237
|
{showPermalink && (
|
|
236
238
|
<Link className="thought-permalink" href={siteHref(`/thoughts/${thought.slug}`)}>
|
|
237
|
-
|
|
239
|
+
{t.thoughtPermalinkShort}
|
|
238
240
|
<ArrowUpRight size={15} />
|
|
239
241
|
</Link>
|
|
240
242
|
)}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { readPayload } from "../../ui/core/compile.mjs";
|
|
2
2
|
import { IntentCard, UiErrorCard, UiFigure, type Diagnostic, type UiPayload } from "../../ui/react";
|
|
3
|
+
import { ui, type UiLang } from "../lib/i18n";
|
|
3
4
|
|
|
4
|
-
export function UiBlock({ source }: { source: string }) {
|
|
5
|
+
export function UiBlock({ source, lang }: { source: string; lang?: UiLang }) {
|
|
5
6
|
const payload = readPayload(source) as UiPayload | null;
|
|
6
|
-
if (!payload) return <p className="external-embed-error"
|
|
7
|
+
if (!payload) return <p className="external-embed-error">{ui(lang).uiBlockCorrupt}</p>;
|
|
7
8
|
return <UiFigure payload={payload} />;
|
|
8
9
|
}
|
|
9
10
|
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
Quotes,
|
|
9
9
|
Repeat,
|
|
10
10
|
} from "@phosphor-icons/react";
|
|
11
|
-
import { ui } from "../lib/i18n";
|
|
11
|
+
import { siteLang, ui } from "../lib/i18n";
|
|
12
12
|
|
|
13
13
|
type WebmentionEntry = {
|
|
14
14
|
author?: {
|
|
@@ -102,26 +102,26 @@ export function Webmentions({
|
|
|
102
102
|
{!endpoint && (
|
|
103
103
|
<div className="integration-notice">
|
|
104
104
|
<Quotes size={20} weight="fill" />
|
|
105
|
-
<p
|
|
105
|
+
<p>{ui().webmentionIdleNote}</p>
|
|
106
106
|
</div>
|
|
107
107
|
)}
|
|
108
108
|
|
|
109
109
|
{endpoint && loading && (
|
|
110
110
|
<p className="integration-status" role="status">
|
|
111
|
-
|
|
111
|
+
{ui().webmentionLoading}
|
|
112
112
|
</p>
|
|
113
113
|
)}
|
|
114
114
|
|
|
115
115
|
{endpoint && failed && (
|
|
116
116
|
<p className="integration-status" role="status">
|
|
117
|
-
|
|
117
|
+
{ui().webmentionUnavailable}
|
|
118
118
|
</p>
|
|
119
119
|
)}
|
|
120
120
|
|
|
121
121
|
{endpoint && !loading && !failed && entries.length === 0 && (
|
|
122
122
|
<div className="integration-notice">
|
|
123
123
|
<Quotes size={20} weight="fill" />
|
|
124
|
-
<p
|
|
124
|
+
<p>{ui().webmentionEmpty}</p>
|
|
125
125
|
</div>
|
|
126
126
|
)}
|
|
127
127
|
|
|
@@ -130,15 +130,15 @@ export function Webmentions({
|
|
|
130
130
|
<span>
|
|
131
131
|
<Heart size={16} weight="fill" />
|
|
132
132
|
<Repeat size={16} />
|
|
133
|
-
{reactions.length}
|
|
133
|
+
{ui().webmentionReactionCount(reactions.length)}
|
|
134
134
|
</span>
|
|
135
|
-
<div className="webmention-facepile" aria-label=
|
|
135
|
+
<div className="webmention-facepile" aria-label={ui().webmentionRespondersLabel}>
|
|
136
136
|
{reactions.slice(0, 16).map((entry, index) => (
|
|
137
137
|
<a
|
|
138
138
|
href={entry.url}
|
|
139
139
|
target="_blank"
|
|
140
140
|
rel="noopener noreferrer"
|
|
141
|
-
aria-label={entry.author?.name ??
|
|
141
|
+
aria-label={entry.author?.name ?? ui().webmentionViewResponse}
|
|
142
142
|
key={`${entry.url}-${index}`}
|
|
143
143
|
>
|
|
144
144
|
{entry.author?.photo ? (
|
|
@@ -165,10 +165,10 @@ export function Webmentions({
|
|
|
165
165
|
return (
|
|
166
166
|
<article key={`${entry.url}-${index}`}>
|
|
167
167
|
<header>
|
|
168
|
-
<strong>{entry.author?.name ??
|
|
168
|
+
<strong>{entry.author?.name ?? ui().webmentionAuthorFallback}</strong>
|
|
169
169
|
{received && (
|
|
170
170
|
<time dateTime={received}>
|
|
171
|
-
{new Intl.DateTimeFormat("zh-CN", {
|
|
171
|
+
{new Intl.DateTimeFormat(siteLang === "en" ? "en-US" : "zh-CN", {
|
|
172
172
|
year: "numeric",
|
|
173
173
|
month: "2-digit",
|
|
174
174
|
day: "2-digit",
|
|
@@ -182,7 +182,7 @@ export function Webmentions({
|
|
|
182
182
|
target="_blank"
|
|
183
183
|
rel="noopener noreferrer"
|
|
184
184
|
>
|
|
185
|
-
|
|
185
|
+
{ui().webmentionViewOriginal}
|
|
186
186
|
<ArrowSquareOut size={14} />
|
|
187
187
|
</a>
|
|
188
188
|
</article>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { ArrowUpRight, Browser, CaretDown, FilePdf } from "@phosphor-icons/react";
|
|
4
4
|
import { useEffect, useRef, useState } from "react";
|
|
5
5
|
import { parse } from "yaml";
|
|
6
|
+
import { ui, type UiLang } from "../lib/i18n";
|
|
6
7
|
|
|
7
8
|
type WidgetKind = "chart" | "html" | "pdf";
|
|
8
9
|
|
|
@@ -79,7 +80,8 @@ function DocumentThumb({ kind }: { kind: "html" | "pdf" }) {
|
|
|
79
80
|
);
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
export function WidgetChart({ source }: { source: string }) {
|
|
83
|
+
export function WidgetChart({ source, lang }: { source: string; lang?: UiLang }) {
|
|
84
|
+
const s = ui(lang);
|
|
83
85
|
const data = readWidgetData(source);
|
|
84
86
|
const src = data?.src;
|
|
85
87
|
const kind = data?.kind;
|
|
@@ -132,7 +134,7 @@ export function WidgetChart({ source }: { source: string }) {
|
|
|
132
134
|
if (!data) {
|
|
133
135
|
return (
|
|
134
136
|
<p className="external-embed-error">
|
|
135
|
-
|
|
137
|
+
{s.widgetParseError}
|
|
136
138
|
</p>
|
|
137
139
|
);
|
|
138
140
|
}
|
|
@@ -156,7 +158,7 @@ export function WidgetChart({ source }: { source: string }) {
|
|
|
156
158
|
<figcaption>
|
|
157
159
|
<span>{data.caption}</span>
|
|
158
160
|
<a href={data.src} target="_blank" rel="noopener noreferrer">
|
|
159
|
-
|
|
161
|
+
{s.widgetOpenPage}
|
|
160
162
|
</a>
|
|
161
163
|
</figcaption>
|
|
162
164
|
<noscript>
|
|
@@ -167,8 +169,8 @@ export function WidgetChart({ source }: { source: string }) {
|
|
|
167
169
|
}
|
|
168
170
|
|
|
169
171
|
const Icon = data.kind === "pdf" ? FilePdf : Browser;
|
|
170
|
-
const kindLabel = data.kind === "pdf" ? "PDF" :
|
|
171
|
-
const openLabel = data.kind === "pdf" ?
|
|
172
|
+
const kindLabel = data.kind === "pdf" ? "PDF" : s.widgetPageKind;
|
|
173
|
+
const openLabel = data.kind === "pdf" ? s.widgetOpenPdf : s.widgetOpenPage;
|
|
172
174
|
|
|
173
175
|
return (
|
|
174
176
|
<figure
|
|
@@ -197,7 +199,7 @@ export function WidgetChart({ source }: { source: string }) {
|
|
|
197
199
|
<strong>{data.caption}</strong>
|
|
198
200
|
</span>
|
|
199
201
|
<span className="garden-doc-action">
|
|
200
|
-
{expanded ?
|
|
202
|
+
{expanded ? s.widgetCollapse : s.widgetExpand}
|
|
201
203
|
<CaretDown size={14} className="garden-doc-caret" />
|
|
202
204
|
</span>
|
|
203
205
|
</button>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useCallback, useEffect, useId, useRef, useState, type FocusEvent, type MouseEvent } from "react";
|
|
4
4
|
import { createPortal } from "react-dom";
|
|
5
|
-
import { ui, type UiLang } from "../../lib/i18n";
|
|
5
|
+
import { siteLang, ui, type UiLang } from "../../lib/i18n";
|
|
6
6
|
import { previewBody, previewFrameProps } from "../../../ui/garden/file-block.mjs";
|
|
7
7
|
import { ActionGlyph, FileGlyph } from "./file-icons";
|
|
8
8
|
|
|
@@ -119,7 +119,7 @@ export function FilePreview({ file, compact, lang }: { file: PreviewFile; compac
|
|
|
119
119
|
if (file.preview === "md" && loaded.state !== "ready" && loaded.state !== "loading") load();
|
|
120
120
|
};
|
|
121
121
|
|
|
122
|
-
const frame = previewFrameProps(file) as { src: string; title: string; referrerPolicy: "no-referrer"; sandbox?: string } | null;
|
|
122
|
+
const frame = previewFrameProps(file, lang ?? siteLang) as { src: string; title: string; referrerPolicy: "no-referrer"; sandbox?: string } | null;
|
|
123
123
|
const drawer = open
|
|
124
124
|
? createPortal(
|
|
125
125
|
<div className="gf-layer">
|