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
package/app/lib/remark-a2.mjs
CHANGED
|
@@ -9,6 +9,14 @@ const ALERT_TITLE = {
|
|
|
9
9
|
caution: "注意",
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
const ALERT_TITLE_EN = {
|
|
13
|
+
note: "Note",
|
|
14
|
+
tip: "Tip",
|
|
15
|
+
important: "Important",
|
|
16
|
+
warning: "Warning",
|
|
17
|
+
caution: "Caution",
|
|
18
|
+
};
|
|
19
|
+
|
|
12
20
|
const LEGACY_KIND = {
|
|
13
21
|
info: "note",
|
|
14
22
|
success: "tip",
|
|
@@ -47,13 +55,14 @@ function isBlankParagraph(node) {
|
|
|
47
55
|
return node?.type === "paragraph" && toString(node).trim() === "" && meaningfulChildren(node).length === 0;
|
|
48
56
|
}
|
|
49
57
|
|
|
50
|
-
function alertTitle(kind, label) {
|
|
58
|
+
function alertTitle(kind, label, lang = "zh") {
|
|
59
|
+
const titles = lang === "en" ? ALERT_TITLE_EN : ALERT_TITLE;
|
|
51
60
|
return {
|
|
52
61
|
type: "paragraph",
|
|
53
62
|
data: { hName: "p", hProperties: { className: ["markdown-alert-title"] } },
|
|
54
63
|
children: [
|
|
55
64
|
{ type: "html", value: ALERT_ICON[kind] },
|
|
56
|
-
{ type: "text", value: label ||
|
|
65
|
+
{ type: "text", value: label || titles[kind] },
|
|
57
66
|
],
|
|
58
67
|
};
|
|
59
68
|
}
|
|
@@ -136,7 +145,7 @@ function strongOnlyText(node) {
|
|
|
136
145
|
return toString(children[0]).trim();
|
|
137
146
|
}
|
|
138
147
|
|
|
139
|
-
function rewriteAlert(node) {
|
|
148
|
+
function rewriteAlert(node, lang = "zh") {
|
|
140
149
|
const children = node.children ?? [];
|
|
141
150
|
const gfm = gfmAlert(children[0]);
|
|
142
151
|
const legacy = gfm ? null : legacyAlert(children[0]);
|
|
@@ -156,7 +165,7 @@ function rewriteAlert(node) {
|
|
|
156
165
|
}
|
|
157
166
|
}
|
|
158
167
|
while (rest.length && isBlankParagraph(rest[0])) rest.shift();
|
|
159
|
-
node.children = [alertTitle(kind, label), ...rest];
|
|
168
|
+
node.children = [alertTitle(kind, label, lang), ...rest];
|
|
160
169
|
node.data ??= {};
|
|
161
170
|
node.data.hProperties ??= {};
|
|
162
171
|
const existing = node.data.hProperties.className;
|
|
@@ -203,17 +212,18 @@ function asFigure(node, imageSize) {
|
|
|
203
212
|
};
|
|
204
213
|
}
|
|
205
214
|
|
|
206
|
-
function walk(node, imageSize) {
|
|
215
|
+
function walk(node, imageSize, lang = "zh") {
|
|
207
216
|
if (!node || typeof node !== "object" || !Array.isArray(node.children)) return;
|
|
208
|
-
if (node.type === "blockquote") rewriteAlert(node);
|
|
217
|
+
if (node.type === "blockquote") rewriteAlert(node, lang);
|
|
209
218
|
node.children = node.children.map((child) => asFigure(child, imageSize) ?? child);
|
|
210
|
-
for (const child of node.children) walk(child, imageSize);
|
|
219
|
+
for (const child of node.children) walk(child, imageSize, lang);
|
|
211
220
|
}
|
|
212
221
|
|
|
213
222
|
/** Demo alert cards and captioned figures. Article rendering only. */
|
|
214
223
|
export function remarkA2(options = {}) {
|
|
215
224
|
const imageSize = typeof options?.imageSize === "function" ? options.imageSize : () => null;
|
|
225
|
+
const lang = options?.lang === "en" ? "en" : "zh";
|
|
216
226
|
return (tree) => {
|
|
217
|
-
walk(tree, imageSize);
|
|
227
|
+
walk(tree, imageSize, lang);
|
|
218
228
|
};
|
|
219
229
|
}
|
|
@@ -76,10 +76,32 @@ function walk(parent, source, sameSite) {
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
const SOURCE_MARKER = "{source}";
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* `[research.google](https://research.google/...){source}` keeps the link in the
|
|
83
|
+
* sentence and draws it as a small source chip (site icon + the link text).
|
|
84
|
+
*/
|
|
85
|
+
function markSources(parent, source) {
|
|
86
|
+
const children = parent.children ?? [];
|
|
87
|
+
for (let index = 0; index < children.length; index += 1) {
|
|
88
|
+
const node = children[index];
|
|
89
|
+
if (node.type === "code" || node.type === "inlineCode" || node.type === "html") continue;
|
|
90
|
+
const next = children[index + 1];
|
|
91
|
+
if (node.type === "link" && next?.type === "text" && next.value.startsWith(SOURCE_MARKER) && isHttp(node.url) && writtenAsLink(node, source)) {
|
|
92
|
+
next.value = next.value.slice(SOURCE_MARKER.length);
|
|
93
|
+
node.data = { ...node.data, hProperties: { ...node.data?.hProperties, className: ["g-src-chip"], dataSource: "true" } };
|
|
94
|
+
}
|
|
95
|
+
if (node.children) markSources(node, source);
|
|
96
|
+
}
|
|
97
|
+
parent.children = children.filter((child) => !(child.type === "text" && child.value === ""));
|
|
98
|
+
}
|
|
99
|
+
|
|
79
100
|
export function remarkLinkCard(options = {}) {
|
|
80
101
|
const sameSite = typeof options?.sameSite === "function" ? options.sameSite : () => null;
|
|
81
102
|
return (tree, file) => {
|
|
82
103
|
const source = typeof file?.value === "string" ? file.value : String(file?.value ?? "");
|
|
104
|
+
markSources(tree, source);
|
|
83
105
|
walk(tree, source, sameSite);
|
|
84
106
|
};
|
|
85
107
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { classifyFence } from "../../ui/garden/parse.mjs";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Component reference pages: every garden fence becomes a demo that shows its
|
|
5
|
+
* source and the real rendered block side by side. Runs before remarkGarden,
|
|
6
|
+
* which then turns the second child into the block.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* The whole fence as the author writes it: info line (with any `#id`), the
|
|
10
|
+
* body, and the closing marker, so copying it reproduces the block. A `garden
|
|
11
|
+
* file` body was replaced by built `resolved:` data; show the original YAML.
|
|
12
|
+
*/
|
|
13
|
+
function fenceSource(child, block) {
|
|
14
|
+
const resolvedSource = block?.props?.resolved?.source;
|
|
15
|
+
const body = typeof resolvedSource === "string" ? resolvedSource : String(child.value ?? "");
|
|
16
|
+
const info = `${child.lang || ""} ${child.meta || ""}`.trim();
|
|
17
|
+
const longest = Math.max(2, ...(body.match(/`{3,}/gu) || []).map((run) => run.length));
|
|
18
|
+
const marker = "`".repeat(longest + 1);
|
|
19
|
+
return `${marker}${info}\n${body}\n${marker}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function remarkRefDemos() {
|
|
23
|
+
return (tree) => {
|
|
24
|
+
const walk = (node) => {
|
|
25
|
+
if (!Array.isArray(node?.children)) return;
|
|
26
|
+
node.children = node.children.map((child) => {
|
|
27
|
+
walk(child);
|
|
28
|
+
if (child?.type !== "code") return child;
|
|
29
|
+
const lang = String(child.lang || "").toLowerCase();
|
|
30
|
+
const meta = String(child.meta || "");
|
|
31
|
+
const kind = classifyFence({ lang, meta, info: `${child.lang || ""} ${meta}`.trim(), body: child.value });
|
|
32
|
+
if (kind.kind !== "garden" || !kind.block?.name) return child;
|
|
33
|
+
return {
|
|
34
|
+
type: "refDemo",
|
|
35
|
+
data: { hName: "garden-refdemo", hProperties: { dataName: kind.block.name } },
|
|
36
|
+
children: [{ type: "code", lang: "markdown", meta: null, value: fenceSource(child, kind.block) }, child],
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
walk(tree);
|
|
41
|
+
};
|
|
42
|
+
}
|
package/app/lib/tag-filter.ts
CHANGED
|
@@ -153,7 +153,14 @@ export function tagFilterHref(selected: readonly string[], pathname = "/blog") {
|
|
|
153
153
|
export const ARTICLE_TAG_LIST = "/blog/";
|
|
154
154
|
export const articleTagHref = (tag: string) => tagFilterHref([tag], ARTICLE_TAG_LIST);
|
|
155
155
|
|
|
156
|
-
/** 「共 N 篇」, or 「2 个标签,共 5 篇」 with a selection. */
|
|
157
|
-
export function tagSummary(selectedCount: number, resultCount: number) {
|
|
156
|
+
/** 「共 N 篇」, or 「2 个标签,共 5 篇」 with a selection (or English equivalent when lang === "en"). */
|
|
157
|
+
export function tagSummary(selectedCount: number, resultCount: number, lang: "zh" | "en" = "zh") {
|
|
158
|
+
if (lang === "en") {
|
|
159
|
+
const postsWord = resultCount === 1 ? "post" : "posts";
|
|
160
|
+
if (!selectedCount) return `${resultCount} ${postsWord}`;
|
|
161
|
+
const tagsWord = selectedCount === 1 ? "tag" : "tags";
|
|
162
|
+
return `${selectedCount} ${tagsWord}, ${resultCount} ${postsWord}`;
|
|
163
|
+
}
|
|
158
164
|
return selectedCount ? `${selectedCount} 个标签,共 ${resultCount} 篇` : `共 ${resultCount} 篇`;
|
|
159
165
|
}
|
|
166
|
+
|
package/app/not-found.tsx
CHANGED
|
@@ -1,15 +1,43 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import Link from "next/link";
|
|
2
|
-
import {
|
|
4
|
+
import { usePathname } from "next/navigation";
|
|
5
|
+
import { useLayoutEffect } from "react";
|
|
6
|
+
import { htmlLang, routeLang, ui } from "./lib/i18n";
|
|
7
|
+
import { siteConfig } from "./site.config";
|
|
3
8
|
|
|
4
9
|
export default function NotFound() {
|
|
5
|
-
const
|
|
10
|
+
const pathname = usePathname();
|
|
11
|
+
const lang = routeLang(pathname);
|
|
12
|
+
const t = ui(lang);
|
|
13
|
+
const fullTitle = `${t.notFoundTitle} · ${siteConfig.name}`;
|
|
14
|
+
const ogLocale = lang === "en" ? "en_US" : "zh_CN";
|
|
15
|
+
|
|
16
|
+
useLayoutEffect(() => {
|
|
17
|
+
document.documentElement.lang = htmlLang(lang);
|
|
18
|
+
document.title = fullTitle;
|
|
19
|
+
const setMeta = (attr: string, key: string, value: string) => {
|
|
20
|
+
const el = document.querySelector<HTMLMetaElement>(`meta[${attr}="${key}"]`);
|
|
21
|
+
if (el) el.content = value;
|
|
22
|
+
};
|
|
23
|
+
setMeta("name", "description", t.notFoundDeck);
|
|
24
|
+
setMeta("property", "og:title", fullTitle);
|
|
25
|
+
setMeta("property", "og:description", t.notFoundDeck);
|
|
26
|
+
setMeta("property", "og:locale", ogLocale);
|
|
27
|
+
setMeta("property", "og:image:alt", fullTitle);
|
|
28
|
+
setMeta("name", "twitter:title", fullTitle);
|
|
29
|
+
setMeta("name", "twitter:description", t.notFoundDeck);
|
|
30
|
+
}, [fullTitle, lang, ogLocale, t.notFoundDeck]);
|
|
31
|
+
|
|
6
32
|
return (
|
|
7
|
-
<main className="
|
|
33
|
+
<main className="sh-main" id="garden-not-found">
|
|
8
34
|
<header className="sh-head">
|
|
9
35
|
<h1>{t.notFoundTitle}</h1>
|
|
10
36
|
<p className="sh-deck">{t.notFoundDeck}</p>
|
|
11
37
|
</header>
|
|
12
|
-
<p
|
|
38
|
+
<p>
|
|
39
|
+
<Link href="/">{t.backHome}</Link>
|
|
40
|
+
</p>
|
|
13
41
|
</main>
|
|
14
42
|
);
|
|
15
43
|
}
|
package/app/page.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import Link from "next/link";
|
|
2
2
|
import { CoverImage } from "./components/CoverImage";
|
|
3
|
-
import { homeListDate } from "./lib/a2-meta";
|
|
4
|
-
import {
|
|
3
|
+
import { fullListDate, homeListDate } from "./lib/a2-meta";
|
|
4
|
+
import { columnStatusLabel, siteLang, ui } from "./lib/i18n";
|
|
5
|
+
import { columns, postHref, posts } from "./lib/content";
|
|
5
6
|
import { githubUrl, siteConfig } from "./site.config";
|
|
6
7
|
import { siteHref } from "./lib/site-href";
|
|
7
8
|
import { pageAlternates } from "./lib/page-alternates";
|
|
@@ -24,6 +25,7 @@ function day(value: string) {
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
export default function Home() {
|
|
28
|
+
const t = ui();
|
|
27
29
|
const recentAll = [...posts].sort((left, right) => Date.parse(right.date) - Date.parse(left.date));
|
|
28
30
|
const feature = recentAll[0];
|
|
29
31
|
const recent = recentAll.filter((post) => post.path !== feature?.path);
|
|
@@ -43,9 +45,9 @@ export default function Home() {
|
|
|
43
45
|
<h1>{siteConfig.pages.home.title}</h1>
|
|
44
46
|
<p>{siteConfig.pages.home.subtitle || siteConfig.description}</p>
|
|
45
47
|
<div className="links">
|
|
46
|
-
<Link href={siteHref("/about")}
|
|
48
|
+
<Link href={siteHref("/about")}>{t.homeAbout}</Link>
|
|
47
49
|
<a href={githubUrl} rel="me">GitHub</a>
|
|
48
|
-
<a href="/rss.xml
|
|
50
|
+
<a href={`${(process.env.NEXT_PUBLIC_BASE_PATH ?? "").replace(/\/+$/u, "")}/rss.xml`}>{t.homeRss}</a>
|
|
49
51
|
</div>
|
|
50
52
|
</section>
|
|
51
53
|
|
|
@@ -58,8 +60,8 @@ export default function Home() {
|
|
|
58
60
|
) : null}
|
|
59
61
|
<div>
|
|
60
62
|
<p className="kicker">
|
|
61
|
-
<span
|
|
62
|
-
<time className="num" dateTime={day(feature.date)}>{
|
|
63
|
+
<span>{t.homeLatestBadge}</span>
|
|
64
|
+
<time className="num" dateTime={day(feature.date)}>{fullListDate(feature.date, siteLang)}</time>
|
|
63
65
|
</p>
|
|
64
66
|
<h2 id="ft"><Link href={postHref(feature)}>{feature.title}</Link></h2>
|
|
65
67
|
{feature.description ? <p>{feature.description}</p> : null}
|
|
@@ -73,13 +75,14 @@ export default function Home() {
|
|
|
73
75
|
<div className="sh-home-grid">
|
|
74
76
|
<section className="sh-section" aria-labelledby="h-recent">
|
|
75
77
|
<div className="sh-sec-head">
|
|
76
|
-
<h2 id="h-recent"
|
|
77
|
-
<Link className="sh-more" href={siteHref("/blog")}
|
|
78
|
+
<h2 id="h-recent">{t.homeRecentHeading}</h2>
|
|
79
|
+
{posts.length > 0 ? <Link className="sh-more" href={siteHref("/blog")}>{t.homeAllPosts(posts.length)}</Link> : null}
|
|
78
80
|
</div>
|
|
81
|
+
{posts.length === 0 ? <p className="sh-empty">{t.emptyPostsTitle}</p> : null}
|
|
79
82
|
<ol className="sh-rows">
|
|
80
83
|
{recent.slice(0, 6).map((post) => (
|
|
81
84
|
<li className="sh-row" key={post.path}>
|
|
82
|
-
<time className="num" dateTime={day(post.date)}>{homeListDate(post.date)}</time>
|
|
85
|
+
<time className="num" dateTime={day(post.date)}>{homeListDate(post.date, undefined, siteLang)}</time>
|
|
83
86
|
<h3><Link href={postHref(post)}>{post.title}</Link></h3>
|
|
84
87
|
{post.description ? <p>{post.description}</p> : null}
|
|
85
88
|
</li>
|
|
@@ -88,12 +91,12 @@ export default function Home() {
|
|
|
88
91
|
</section>
|
|
89
92
|
{pinned.length > 0 ? (
|
|
90
93
|
<section className="sh-section" aria-labelledby="h-pin">
|
|
91
|
-
<div className="sh-sec-head section-heading accent-heading"><h2 id="h-pin"
|
|
94
|
+
<div className="sh-sec-head section-heading accent-heading"><h2 id="h-pin">{t.homePinned}</h2></div>
|
|
92
95
|
<ul className="sh-side-list">
|
|
93
96
|
{pinned.map((post) => (
|
|
94
97
|
<li key={post.path}>
|
|
95
98
|
<Link href={postHref(post)}>{post.title}</Link>
|
|
96
|
-
<span><time className="num" dateTime={day(post.date)}>{
|
|
99
|
+
<span><time className="num" dateTime={day(post.date)}>{fullListDate(post.date, siteLang)}</time></span>
|
|
97
100
|
</li>
|
|
98
101
|
))}
|
|
99
102
|
</ul>
|
|
@@ -104,8 +107,8 @@ export default function Home() {
|
|
|
104
107
|
{columns[0] && series.length ? (
|
|
105
108
|
<section className="sh-section" aria-labelledby="h-cols">
|
|
106
109
|
<div className="sh-sec-head">
|
|
107
|
-
<h2 id="h-cols"
|
|
108
|
-
<Link className="sh-more" href={siteHref("/columns")}
|
|
110
|
+
<h2 id="h-cols">{t.homeColumns}</h2>
|
|
111
|
+
<Link className="sh-more" href={siteHref("/columns")}>{t.homeAllColumns}</Link>
|
|
109
112
|
</div>
|
|
110
113
|
<div className="sh-cols">
|
|
111
114
|
{series.map(([slug, entries]) => {
|
|
@@ -120,7 +123,7 @@ export default function Home() {
|
|
|
120
123
|
</div>
|
|
121
124
|
<div className="body">
|
|
122
125
|
<h3><Link href={siteHref(`/columns/#${slug}`)}>{first.columnTitle}</Link></h3>
|
|
123
|
-
<p className="count">{entries.length
|
|
126
|
+
<p className="count">{t.columnCount(entries.length, columnStatusLabel(first.columnStatus))}</p>
|
|
124
127
|
<p>{first.columnDescription ?? first.description}</p>
|
|
125
128
|
</div>
|
|
126
129
|
</article>
|
package/app/rss.xml/route.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { configuredSiteUrl, siteDescription, siteName } from "../lib/site";
|
|
|
5
5
|
import { contentForFeed } from "../../ui/core/transform.mjs";
|
|
6
6
|
import { stripCardMarkers } from "../../ui/garden/markdown-links.mjs";
|
|
7
7
|
import { withTrailingSlash } from "../../ui/garden/site-href.mjs";
|
|
8
|
+
import { htmlLang, siteLang } from "../lib/i18n";
|
|
8
9
|
|
|
9
10
|
const escapeXml = (value: string) =>
|
|
10
11
|
value
|
|
@@ -72,7 +73,7 @@ export async function GET() {
|
|
|
72
73
|
<link>${origin}</link>
|
|
73
74
|
<atom:link href="${origin}/rss.xml" rel="self" type="application/rss+xml" />
|
|
74
75
|
<description>${escapeXml(siteDescription)}</description>
|
|
75
|
-
<language
|
|
76
|
+
<language>${htmlLang(siteLang)}</language>
|
|
76
77
|
<lastBuildDate>${lastBuildDate}</lastBuildDate>
|
|
77
78
|
${items}
|
|
78
79
|
</channel>
|