hd-garden 0.9.4 → 0.10.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/app/about/page.tsx +1 -1
- package/app/blog/[...path]/page.tsx +9 -78
- package/app/blog/page.tsx +5 -4
- package/app/columns/[column]/[...path]/page.tsx +29 -91
- package/app/columns/page.tsx +6 -4
- package/app/components/PageIntro.tsx +9 -4
- package/app/components/PostReadingView.tsx +133 -0
- package/app/components/SeriesEdgeNav.tsx +119 -0
- package/app/components/SiteChrome.tsx +3 -17
- package/app/components/SiteHeader.tsx +3 -3
- package/app/garden-modules.d.ts +0 -6
- package/app/layout.tsx +1 -2
- package/app/lib/i18n.ts +8 -14
- package/app/page.tsx +3 -1
- package/app/sitemap.ts +0 -11
- package/app/styles/a2.css +204 -0
- package/app/styles/shell.css +0 -37
- package/app/thoughts/page/[n]/page.tsx +1 -1
- package/app/thoughts/page.tsx +1 -1
- package/docs/handbook/config.md +2 -59
- package/docs/handbook/en/config.md +2 -59
- package/package.json +1 -2
- package/scripts/build-content.mjs +1 -31
- package/scripts/handbook-facts.mjs +1 -2
- package/ui/garden/site-nav.mjs +3 -13
- package/vite.config.ts +3 -12
- package/app/bookshelf/[bookId]/page.tsx +0 -72
- package/app/bookshelf/load.mjs +0 -114
- package/app/bookshelf/load.ts +0 -16
- package/app/bookshelf/page.tsx +0 -52
- package/app/components/bookshelf/BookHub.tsx +0 -142
- package/app/components/bookshelf/Bookshelf.tsx +0 -141
- package/app/components/bookshelf/bookshelf.css +0 -1352
- package/app/components/bookshelf/catalog.mjs +0 -176
- package/app/components/bookshelf/catalog.ts +0 -27
- package/app/components/bookshelf/demo-data.ts +0 -93
- package/app/components/bookshelf/density.ts +0 -32
- package/app/components/bookshelf/engine.ts +0 -3057
- package/app/components/bookshelf/index.ts +0 -15
- package/app/components/bookshelf/model.ts +0 -16
- package/app/components/bookshelf/schema.mjs +0 -155
- package/app/components/bookshelf/schema.ts +0 -110
- package/app/components/bookshelf/types.ts +0 -94
- package/public/covers/accelerate.jpg +0 -0
- package/public/covers/clean-architecture.jpg +0 -0
- package/public/covers/clean-code.jpg +0 -0
- package/public/covers/continuous-delivery.jpg +0 -0
- package/public/covers/ddd.jpg +0 -0
- package/public/covers/ddia.jpg +0 -0
- package/public/covers/design-patterns.jpg +0 -0
- package/public/covers/eaa.jpg +0 -0
- package/public/covers/fundamentals-sa.jpg +0 -0
- package/public/covers/implementing-ddd.jpg +0 -0
- package/public/covers/lean-startup.jpg +0 -0
- package/public/covers/microservices.jpg +0 -0
- package/public/covers/mythical-man-month.jpg +0 -0
- package/public/covers/phoenix-project.jpg +0 -0
- package/public/covers/pragmatic-programmer.jpg +0 -0
- package/public/covers/refactoring.jpg +0 -0
- package/public/covers/release-it.jpg +0 -0
- package/public/covers/sre.jpg +0 -0
- package/public/covers/systems-performance.jpg +0 -0
- package/public/covers/tdd.jpg +0 -0
- package/public/covers/team-topologies.jpg +0 -0
- package/scripts/bookshelf-covers.mjs +0 -62
|
@@ -16,7 +16,7 @@ import { brand, searchFull, searchIndexUrl } from "@garden/site-config";
|
|
|
16
16
|
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
|
-
import {
|
|
19
|
+
import { SITE_NAV, isCurrentNav } from "../../ui/garden/site-nav.mjs";
|
|
20
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";
|
|
@@ -89,6 +89,7 @@ function loadSearchEngine() {
|
|
|
89
89
|
return engineLoad;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
const nav = SITE_NAV.map((item) => ({ ...item, key: item.href, href: siteHref(item.href) }));
|
|
92
93
|
|
|
93
94
|
function rangesFor(
|
|
94
95
|
matches: readonly SearchMatch[] | undefined,
|
|
@@ -139,8 +140,7 @@ function searchPreview(
|
|
|
139
140
|
return { prefix: "", text: item.description, suffix: "", ranges: [] };
|
|
140
141
|
}
|
|
141
142
|
|
|
142
|
-
export function SiteHeader(
|
|
143
|
-
const nav = siteNav({ shelf }).map((item) => ({ ...item, key: item.href, href: siteHref(item.href) }));
|
|
143
|
+
export function SiteHeader() {
|
|
144
144
|
const pathname = usePathname() || "/";
|
|
145
145
|
const t = ui(routeLang(pathname));
|
|
146
146
|
const [menuOpen, setMenuOpen] = useState(false);
|
package/app/garden-modules.d.ts
CHANGED
|
@@ -103,9 +103,3 @@ declare module "@garden/site-config" {
|
|
|
103
103
|
};
|
|
104
104
|
export const githubUrl: string;
|
|
105
105
|
}
|
|
106
|
-
|
|
107
|
-
declare module "@garden/bookshelf" {
|
|
108
|
-
import type { BookshelfMount } from "./components/bookshelf/catalog";
|
|
109
|
-
/** Site shelf from content/bookshelf.yaml, or null when that file is absent. */
|
|
110
|
-
export const bookshelf: BookshelfMount | null;
|
|
111
|
-
}
|
package/app/layout.tsx
CHANGED
|
@@ -4,7 +4,6 @@ import "./globals.css";
|
|
|
4
4
|
import "./styles/tokens.css";
|
|
5
5
|
import "./styles/a2.css";
|
|
6
6
|
import "./styles/shell.css";
|
|
7
|
-
import { bookshelf } from "@garden/bookshelf";
|
|
8
7
|
import { SiteChrome } from "./components/SiteChrome";
|
|
9
8
|
import {
|
|
10
9
|
getSiteUrl,
|
|
@@ -95,7 +94,7 @@ export default function RootLayout({
|
|
|
95
94
|
)}
|
|
96
95
|
</head>
|
|
97
96
|
<body>
|
|
98
|
-
<SiteChrome
|
|
97
|
+
<SiteChrome>{children}</SiteChrome>
|
|
99
98
|
</body>
|
|
100
99
|
</html>
|
|
101
100
|
);
|
package/app/lib/i18n.ts
CHANGED
|
@@ -131,7 +131,7 @@ const zh = {
|
|
|
131
131
|
fuzzy: "支持模糊匹配",
|
|
132
132
|
escToClose: "关闭",
|
|
133
133
|
noResults: (q: string) => (q ? `没有找到和「${q}」相关的文章` : "还没有可以搜索的内容"),
|
|
134
|
-
navLabels: { "/blog/": "文章", "/thoughts/": "随想", "/columns/": "专栏", "/tags/": "标签", "/
|
|
134
|
+
navLabels: { "/blog/": "文章", "/thoughts/": "随想", "/columns/": "专栏", "/tags/": "标签", "/about/": "关于" } as Record<string, string>,
|
|
135
135
|
// Home page
|
|
136
136
|
homeLatestBadge: "最新文章",
|
|
137
137
|
homeRecentHeading: "最近更新",
|
|
@@ -141,6 +141,7 @@ const zh = {
|
|
|
141
141
|
homeColumnCountStatus: (count: number, status: string) => `${count} 篇,${status}`,
|
|
142
142
|
columnDefaultStatus: "持续更新",
|
|
143
143
|
// Blog archive
|
|
144
|
+
blogEyebrow: "BLOG / 文章",
|
|
144
145
|
blogHeading: "文章",
|
|
145
146
|
blogDeck: (n: number) => `阶段性的完整沉淀,关于工程、架构与技术思考。共 ${n} 篇,按年份归档。`,
|
|
146
147
|
unknownYear: "未知",
|
|
@@ -170,6 +171,7 @@ const zh = {
|
|
|
170
171
|
seriesEyebrow: "主题专栏",
|
|
171
172
|
seriesCountStatus: (count: number, status: string) => `${count} 篇 · ${status}`,
|
|
172
173
|
// Columns index
|
|
174
|
+
columnsEyebrow: "COLUMNS / 专栏",
|
|
173
175
|
columnsFallbackTitle: "专栏",
|
|
174
176
|
columnsMeta: (count: number, status: string) => `阅读清单,${count} 篇,${status}`,
|
|
175
177
|
columnsFallbackDesc: "按顺序组织的专题文章系列。",
|
|
@@ -195,7 +197,7 @@ const zh = {
|
|
|
195
197
|
audioFallback: "你的浏览器不支持音频播放。",
|
|
196
198
|
thoughtPermalinkShort: "永久链接",
|
|
197
199
|
// About & GitHub profile
|
|
198
|
-
aboutEyebrow: "关于",
|
|
200
|
+
aboutEyebrow: "ABOUT / 关于",
|
|
199
201
|
aboutFallbackHeading: "关于",
|
|
200
202
|
aboutFallbackParagraphs: [
|
|
201
203
|
"一名工程师。平时写代码,也把想清楚的问题记下来。",
|
|
@@ -299,11 +301,6 @@ const zh = {
|
|
|
299
301
|
adminTitlePreview: "草稿预览",
|
|
300
302
|
a2IndexTitle: "A2 对照",
|
|
301
303
|
a2IndexDeck: "对照参考文章的版式。正文没有改字。",
|
|
302
|
-
bookHubCurrent: "本书",
|
|
303
|
-
bookHubNotes: "我的笔记",
|
|
304
|
-
bookHubOverview: "总览",
|
|
305
|
-
bookHubExcerpt: "摘抄",
|
|
306
|
-
bookHubPdfNote: "(仅分享,侵权删)",
|
|
307
304
|
};
|
|
308
305
|
|
|
309
306
|
const en: typeof zh = {
|
|
@@ -419,7 +416,7 @@ const en: typeof zh = {
|
|
|
419
416
|
fuzzy: "Fuzzy matching",
|
|
420
417
|
escToClose: "to close",
|
|
421
418
|
noResults: (q: string) => (q ? `No posts match “${q}”` : "Nothing to search yet"),
|
|
422
|
-
navLabels: { "/blog/": "Posts", "/thoughts/": "Thoughts", "/columns/": "Columns", "/tags/": "Tags", "/
|
|
419
|
+
navLabels: { "/blog/": "Posts", "/thoughts/": "Thoughts", "/columns/": "Columns", "/tags/": "Tags", "/about/": "About" },
|
|
423
420
|
// Home page
|
|
424
421
|
homeLatestBadge: "Latest post",
|
|
425
422
|
homeRecentHeading: "Recent updates",
|
|
@@ -429,6 +426,7 @@ const en: typeof zh = {
|
|
|
429
426
|
homeColumnCountStatus: (count: number, status: string) => `${count} ${count === 1 ? "post" : "posts"}, ${status}`,
|
|
430
427
|
columnDefaultStatus: "Ongoing",
|
|
431
428
|
// Blog archive
|
|
429
|
+
blogEyebrow: "BLOG",
|
|
432
430
|
blogHeading: "Posts",
|
|
433
431
|
blogDeck: (n: number) => `Essays on engineering, architecture, and systems thinking. ${n} ${n === 1 ? "post" : "posts"}, archived by year.`,
|
|
434
432
|
unknownYear: "Unknown",
|
|
@@ -458,6 +456,7 @@ const en: typeof zh = {
|
|
|
458
456
|
seriesEyebrow: "Column",
|
|
459
457
|
seriesCountStatus: (count: number, status: string) => `${count} ${count === 1 ? "post" : "posts"} · ${status}`,
|
|
460
458
|
// Columns index
|
|
459
|
+
columnsEyebrow: "COLUMNS",
|
|
461
460
|
columnsFallbackTitle: "Columns",
|
|
462
461
|
columnsMeta: (count: number, status: string) => `Reading list · ${count} ${count === 1 ? "post" : "posts"} · ${status}`,
|
|
463
462
|
columnsFallbackDesc: "A series of posts organized in reading order.",
|
|
@@ -483,7 +482,7 @@ const en: typeof zh = {
|
|
|
483
482
|
audioFallback: "Your browser does not support audio playback.",
|
|
484
483
|
thoughtPermalinkShort: "Permalink",
|
|
485
484
|
// About & GitHub profile
|
|
486
|
-
aboutEyebrow: "
|
|
485
|
+
aboutEyebrow: "ABOUT",
|
|
487
486
|
aboutFallbackHeading: "About",
|
|
488
487
|
aboutFallbackParagraphs: [
|
|
489
488
|
"An engineer who writes code and writes down problems once they are clear.",
|
|
@@ -587,11 +586,6 @@ const en: typeof zh = {
|
|
|
587
586
|
adminTitlePreview: "Draft Preview",
|
|
588
587
|
a2IndexTitle: "A2 Parity",
|
|
589
588
|
a2IndexDeck: "Layout comparison against reference articles. Body text is unchanged.",
|
|
590
|
-
bookHubCurrent: "This book",
|
|
591
|
-
bookHubNotes: "My notes",
|
|
592
|
-
bookHubOverview: "Overview",
|
|
593
|
-
bookHubExcerpt: "Excerpt",
|
|
594
|
-
bookHubPdfNote: "(shared only — removed if it infringes)",
|
|
595
589
|
};
|
|
596
590
|
|
|
597
591
|
export const UI_STRINGS = { zh, en } as const;
|
package/app/page.tsx
CHANGED
|
@@ -43,7 +43,9 @@ export default function Home() {
|
|
|
43
43
|
<main className="sh-main" id="content">
|
|
44
44
|
<section className="sh-intro">
|
|
45
45
|
<h1>{siteConfig.pages.home.title}</h1>
|
|
46
|
-
|
|
46
|
+
{siteConfig.pages.home.subtitle?.trim() ? (
|
|
47
|
+
<p>{siteConfig.pages.home.subtitle}</p>
|
|
48
|
+
) : null}
|
|
47
49
|
<div className="links">
|
|
48
50
|
<Link href={siteHref("/about")}>{t.homeAbout}</Link>
|
|
49
51
|
<a href={githubUrl} rel="me">GitHub</a>
|
package/app/sitemap.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import type { MetadataRoute } from "next";
|
|
2
|
-
import { bookshelf } from "@garden/bookshelf";
|
|
3
2
|
import { columnHref, columns, postHref, posts, thoughtHref } from "./lib/content";
|
|
4
3
|
import { absoluteUrl, getSiteUrl } from "./lib/site";
|
|
5
4
|
import { getPublishedThoughts } from "./lib/public-thoughts";
|
|
6
5
|
import { siteHref } from "./lib/site-href";
|
|
7
|
-
import type { BookshelfBook, BookshelfShelf } from "./components/bookshelf/types";
|
|
8
6
|
|
|
9
7
|
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
10
8
|
const baseUrl = await getSiteUrl();
|
|
@@ -16,15 +14,6 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
|
16
14
|
{ path: "/columns", priority: 0.8, changeFrequency: "weekly" as const },
|
|
17
15
|
{ path: "/about", priority: 0.5, changeFrequency: "monthly" as const },
|
|
18
16
|
{ path: "/garden", priority: 0.4, changeFrequency: "monthly" as const },
|
|
19
|
-
// Only a real shelf is a page. The package demo stays out of the sitemap.
|
|
20
|
-
...(bookshelf ? [{ path: "/bookshelf", priority: 0.6, changeFrequency: "weekly" as const }] : []),
|
|
21
|
-
...(bookshelf
|
|
22
|
-
? bookshelf.catalog.flatMap((row: BookshelfShelf) =>
|
|
23
|
-
row.books
|
|
24
|
-
.filter((book: BookshelfBook) => typeof book.hub === "string")
|
|
25
|
-
.map((book: BookshelfBook) => ({ path: book.hub ?? "/bookshelf", priority: 0.55, changeFrequency: "weekly" as const })),
|
|
26
|
-
)
|
|
27
|
-
: []),
|
|
28
17
|
].map((route) => ({
|
|
29
18
|
url: absoluteUrl(siteHref(route.path), baseUrl),
|
|
30
19
|
changeFrequency: route.changeFrequency,
|
package/app/styles/a2.css
CHANGED
|
@@ -2636,3 +2636,207 @@ html[data-theme="dark"] .ref-demo { --rd-s: #262624; --rd-p: #1f1e1d; --rd-i: #e
|
|
|
2636
2636
|
.ref-demo[data-name="figure"] .ref-pane[data-p="v"] { grid-column: 1; grid-row: 1; }
|
|
2637
2637
|
.ref-demo[data-name="figure"] .ref-pane[data-p="c"] { grid-column: 1; grid-row: 2; background: var(--rd-s); border-top: 1px solid var(--rd-r); }
|
|
2638
2638
|
}
|
|
2639
|
+
|
|
2640
|
+
/* ===== v0.10.1 tip keep (after git checkout of a2.css from 9a80f7e5) ===== */
|
|
2641
|
+
|
|
2642
|
+
/* List pages: PageIntro rides on .sh-head; keep eyebrow in the same vertical rhythm. */
|
|
2643
|
+
.page-intro.sh-head {
|
|
2644
|
+
padding: 72px 0 36px;
|
|
2645
|
+
max-width: var(--list);
|
|
2646
|
+
width: 100%;
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
.page-intro.sh-head .eyebrow {
|
|
2650
|
+
margin: 0 0 14px;
|
|
2651
|
+
color: var(--muted);
|
|
2652
|
+
font: 600 11px / 1.4 var(--sans);
|
|
2653
|
+
letter-spacing: 0.12em;
|
|
2654
|
+
text-transform: uppercase;
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
.page-intro.sh-head h1 {
|
|
2658
|
+
margin: 0;
|
|
2659
|
+
font: 700 var(--fs-display) / 1.2 var(--sans);
|
|
2660
|
+
color: var(--ink);
|
|
2661
|
+
letter-spacing: 0;
|
|
2662
|
+
text-wrap: balance;
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
.page-intro.sh-head .sh-deck {
|
|
2666
|
+
margin: 14px 0 0;
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2669
|
+
.page-intro.sh-head .page-intro-actions {
|
|
2670
|
+
margin-top: 22px;
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
.thoughts-page,
|
|
2674
|
+
.about-page {
|
|
2675
|
+
width: auto;
|
|
2676
|
+
max-width: none;
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
.about-page {
|
|
2680
|
+
padding-bottom: 120px;
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2683
|
+
/* Keep PageIntro at --list; focus card sticks to the right edge. */
|
|
2684
|
+
.about-page .about-grid {
|
|
2685
|
+
width: 100%;
|
|
2686
|
+
max-width: none;
|
|
2687
|
+
display: grid;
|
|
2688
|
+
grid-template-columns: minmax(0, var(--list)) minmax(12rem, 17rem);
|
|
2689
|
+
column-gap: 48px;
|
|
2690
|
+
row-gap: 36px;
|
|
2691
|
+
justify-content: space-between;
|
|
2692
|
+
align-items: start;
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
@media (max-width: 900px) {
|
|
2696
|
+
.about-page .about-grid {
|
|
2697
|
+
grid-template-columns: 1fr;
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
/* SeriesEdgeNav: fixed to viewport left on column posts that share PostReadingView.
|
|
2702
|
+
post-reading-page already lays out article + TOC; ticks are position:fixed so
|
|
2703
|
+
they do not need a grid track. */
|
|
2704
|
+
.post-reading-page > .series-edge {
|
|
2705
|
+
--edge-window-inset: 12px;
|
|
2706
|
+
position: fixed;
|
|
2707
|
+
top: calc(var(--hdr) + 12px);
|
|
2708
|
+
bottom: 16px;
|
|
2709
|
+
left: max(var(--edge-window-inset), env(safe-area-inset-left, 0px));
|
|
2710
|
+
z-index: 15;
|
|
2711
|
+
display: flex;
|
|
2712
|
+
align-items: center;
|
|
2713
|
+
pointer-events: none;
|
|
2714
|
+
transform: none;
|
|
2715
|
+
margin: 0;
|
|
2716
|
+
padding: 0;
|
|
2717
|
+
}
|
|
2718
|
+
|
|
2719
|
+
.series-edge-ticks {
|
|
2720
|
+
pointer-events: auto;
|
|
2721
|
+
appearance: none;
|
|
2722
|
+
border: 0;
|
|
2723
|
+
background: transparent;
|
|
2724
|
+
box-shadow: none;
|
|
2725
|
+
display: flex;
|
|
2726
|
+
flex-direction: column;
|
|
2727
|
+
justify-content: center;
|
|
2728
|
+
align-items: center;
|
|
2729
|
+
gap: 8px;
|
|
2730
|
+
padding: 12px 6px;
|
|
2731
|
+
margin: 0;
|
|
2732
|
+
cursor: pointer;
|
|
2733
|
+
color: inherit;
|
|
2734
|
+
font: inherit;
|
|
2735
|
+
border-radius: 0;
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2738
|
+
.series-edge-ticks i {
|
|
2739
|
+
display: block;
|
|
2740
|
+
width: 11px;
|
|
2741
|
+
height: 1.5px;
|
|
2742
|
+
border-radius: 1px;
|
|
2743
|
+
background: color-mix(in srgb, var(--ink) 32%, transparent);
|
|
2744
|
+
transition: background 0.15s ease, width 0.15s ease;
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
.series-edge-ticks i.hot {
|
|
2748
|
+
width: 15px;
|
|
2749
|
+
background: color-mix(in srgb, var(--ink) 70%, transparent);
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2752
|
+
.series-edge[data-open="true"] .series-edge-ticks i,
|
|
2753
|
+
.series-edge:focus-within .series-edge-ticks i {
|
|
2754
|
+
background: color-mix(in srgb, var(--ink) 52%, transparent);
|
|
2755
|
+
}
|
|
2756
|
+
|
|
2757
|
+
.series-edge[data-open="true"] .series-edge-ticks i.hot,
|
|
2758
|
+
.series-edge:focus-within .series-edge-ticks i.hot {
|
|
2759
|
+
background: var(--ink);
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2762
|
+
.series-edge-bridge {
|
|
2763
|
+
pointer-events: auto;
|
|
2764
|
+
position: absolute;
|
|
2765
|
+
top: 0;
|
|
2766
|
+
bottom: 0;
|
|
2767
|
+
left: 100%;
|
|
2768
|
+
width: 28px;
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
.series-edge-fly {
|
|
2772
|
+
pointer-events: none;
|
|
2773
|
+
position: absolute;
|
|
2774
|
+
top: 50%;
|
|
2775
|
+
left: calc(100% + 4px);
|
|
2776
|
+
transform: translate(0, -50%);
|
|
2777
|
+
width: 16rem;
|
|
2778
|
+
max-height: min(72vh, calc(100vh - var(--hdr) - 40px));
|
|
2779
|
+
margin: 0;
|
|
2780
|
+
padding: 0.7rem 0.85rem 0.75rem;
|
|
2781
|
+
border: 0;
|
|
2782
|
+
border-radius: 10px;
|
|
2783
|
+
background: var(--paper);
|
|
2784
|
+
box-shadow:
|
|
2785
|
+
0 1px 2px color-mix(in srgb, var(--ink) 6%, transparent),
|
|
2786
|
+
0 10px 28px color-mix(in srgb, var(--ink) 10%, transparent);
|
|
2787
|
+
opacity: 0;
|
|
2788
|
+
visibility: hidden;
|
|
2789
|
+
overflow: auto;
|
|
2790
|
+
transition: opacity 0.16s ease, visibility 0.16s ease;
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
.series-edge[data-open="true"],
|
|
2794
|
+
.series-edge:focus-within {
|
|
2795
|
+
z-index: 28;
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
.series-edge[data-open="true"] .series-edge-fly,
|
|
2799
|
+
.series-edge:focus-within .series-edge-fly {
|
|
2800
|
+
pointer-events: auto;
|
|
2801
|
+
opacity: 1;
|
|
2802
|
+
visibility: visible;
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2805
|
+
.series-edge-list {
|
|
2806
|
+
list-style: none;
|
|
2807
|
+
margin: 0;
|
|
2808
|
+
padding: 0;
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
.series-edge-list a {
|
|
2812
|
+
display: block;
|
|
2813
|
+
padding: 0.4rem 0.35rem;
|
|
2814
|
+
margin: 0 -0.35rem;
|
|
2815
|
+
border-radius: 7px;
|
|
2816
|
+
color: var(--soft);
|
|
2817
|
+
font-size: 13px;
|
|
2818
|
+
line-height: 1.35;
|
|
2819
|
+
text-decoration: none;
|
|
2820
|
+
white-space: nowrap;
|
|
2821
|
+
overflow: hidden;
|
|
2822
|
+
text-overflow: ellipsis;
|
|
2823
|
+
}
|
|
2824
|
+
|
|
2825
|
+
.series-edge-list a:hover {
|
|
2826
|
+
background: color-mix(in srgb, var(--ink) 4%, transparent);
|
|
2827
|
+
color: var(--ink);
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2830
|
+
.series-edge-list li.cur a {
|
|
2831
|
+
background: color-mix(in srgb, var(--ink) 6%, transparent);
|
|
2832
|
+
color: var(--ink);
|
|
2833
|
+
font-weight: 600;
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2836
|
+
@media (max-width: 900px) {
|
|
2837
|
+
/* The desktop rule is `.post-reading-page > .series-edge { display: flex }`.
|
|
2838
|
+
Match that specificity so the rail stays off small screens. */
|
|
2839
|
+
.post-reading-page > .series-edge {
|
|
2840
|
+
display: none;
|
|
2841
|
+
}
|
|
2842
|
+
}
|
package/app/styles/shell.css
CHANGED
|
@@ -434,43 +434,6 @@ html[data-theme="dark"] .sh-repos > li.sk::after{background:linear-gradient(90de
|
|
|
434
434
|
.sh-series.no-cover h2,.sh-series.no-cover .count{grid-column:1 / -1;grid-row:auto}
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
-
/* Book hub (本书), locked to tip book-multi-20261029c. Width is fixed; height follows the jacket. */
|
|
438
|
-
.book-hub{width:min(100%,740px);padding:40px 0 72px}
|
|
439
|
-
.book-hub-crumb{margin:0 0 28px;font-size:12px;color:var(--faint,#a09b91);letter-spacing:.06em}
|
|
440
|
-
.book-hub-crumb a{color:inherit;text-decoration:none}
|
|
441
|
-
.book-hub-crumb a:hover{color:var(--ink)}
|
|
442
|
-
.book-hub-crumb [aria-current="page"]{color:var(--faint,#a09b91)}
|
|
443
|
-
.book-hub-card{display:flex;gap:28px;align-items:flex-start;margin:0 0 40px}
|
|
444
|
-
.book-hub-cover{width:148px;flex-shrink:0;height:auto;min-height:0;aspect-ratio:auto;border-radius:0;overflow:visible;background:none;box-shadow:none;line-height:0;padding:0}
|
|
445
|
-
.book-hub-cover img{display:block;width:148px;max-width:none;height:auto;aspect-ratio:auto;object-fit:unset;background:none;border-radius:4px;box-shadow:0 10px 24px rgba(40,30,20,.10)}
|
|
446
|
-
.book-hub-meta{flex:1;min-width:0;padding-top:4px}
|
|
447
|
-
.book-hub-meta h1{margin:0 0 10px;font-family:var(--serif);font-size:clamp(26px,4vw,34px);font-weight:600;line-height:1.25;letter-spacing:-.01em;color:var(--ink)}
|
|
448
|
-
.book-hub-author{margin:0 0 18px;font-size:14px;color:var(--muted)}
|
|
449
|
-
.book-hub-sources{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
|
|
450
|
-
.book-hub-source{display:inline-flex;align-items:center;gap:2px}
|
|
451
|
-
.book-hub-chip{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border-radius:999px;background:#f3efe6;color:var(--muted);font-size:12px;line-height:1.4;text-decoration:none;border:1px solid transparent}
|
|
452
|
-
html[data-theme="dark"] .book-hub-chip{background:var(--chip)}
|
|
453
|
-
.book-hub-chip:hover{color:var(--ink);border-color:var(--rule)}
|
|
454
|
-
.book-hub-chip .ext{font-size:11px;opacity:.7}
|
|
455
|
-
.book-hub-source-note{font-size:11px;color:var(--faint,#a09b91);letter-spacing:.02em;line-height:1.4}
|
|
456
|
-
.book-hub-notes-head{margin:0 0 14px;font:400 12px/1.4 var(--sans);color:var(--faint,#a09b91);letter-spacing:.12em}
|
|
457
|
-
.book-hub-notes{display:flex;flex-direction:column;gap:12px;margin:0;padding:0;list-style:none}
|
|
458
|
-
.book-hub-note{display:block;width:100%;text-align:left;padding:18px 20px;background:#fffcf7;border:1px solid var(--rule);border-radius:10px;text-decoration:none;color:inherit}
|
|
459
|
-
html[data-theme="dark"] .book-hub-note{background:var(--card)}
|
|
460
|
-
@media (hover:hover){
|
|
461
|
-
.book-hub-note:hover{border-color:#d5d0c4;box-shadow:0 6px 16px rgba(40,30,20,.05)}
|
|
462
|
-
html[data-theme="dark"] .book-hub-note:hover{border-color:var(--rule-strong);box-shadow:none}
|
|
463
|
-
}
|
|
464
|
-
.book-hub-note-label{margin:0 0 6px;font-size:11px;color:var(--faint,#a09b91);letter-spacing:.08em}
|
|
465
|
-
.book-hub-note h3{margin:0 0 8px;font:500 16px/1.45 var(--sans);color:var(--ink)}
|
|
466
|
-
.book-hub-note-sum{margin:0 0 12px;font-size:13px;line-height:1.65;color:var(--muted)}
|
|
467
|
-
.book-hub-note-meta{margin:0;font-size:12px;color:var(--faint,#a09b91)}
|
|
468
|
-
@media (max-width:560px){
|
|
469
|
-
.book-hub{padding:28px 0 64px}
|
|
470
|
-
.book-hub-card{flex-direction:column;gap:18px}
|
|
471
|
-
.book-hub-cover,.book-hub-cover img{width:120px}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
437
|
.sh-row .incol{grid-column:2;justify-self:start;position:relative;z-index:1;margin-top:4px;font-size:var(--fs-meta);line-height:1.6;color:var(--muted);text-decoration:none}
|
|
475
438
|
@media (hover:hover){.sh-row .incol:hover{color:var(--blue);text-decoration:underline;text-underline-offset:3px}}
|
|
476
439
|
.is-compact .sh-row .incol{display:none}
|
|
@@ -36,7 +36,7 @@ export default async function ThoughtsPage({ params }: { params: Promise<{ n: st
|
|
|
36
36
|
notFound();
|
|
37
37
|
}
|
|
38
38
|
return (
|
|
39
|
-
<main className="
|
|
39
|
+
<main className="sh-main thoughts-page" id="content">
|
|
40
40
|
<PageIntro eyebrow={ui().thoughtsEyebrow} title={siteConfig.pages.thoughts.title} subtitle={siteConfig.pages.thoughts.subtitle} />
|
|
41
41
|
<ThoughtFeed page={page} />
|
|
42
42
|
</main>
|
package/app/thoughts/page.tsx
CHANGED
|
@@ -16,7 +16,7 @@ export default function ThoughtsPage() {
|
|
|
16
16
|
const thoughts = getPublishedThoughts();
|
|
17
17
|
|
|
18
18
|
return (
|
|
19
|
-
<main className="
|
|
19
|
+
<main className="sh-main thoughts-page" id="content">
|
|
20
20
|
<PageIntro
|
|
21
21
|
eyebrow={ui().thoughtsEyebrow}
|
|
22
22
|
title={siteConfig.pages.thoughts.title}
|
package/docs/handbook/config.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: 站点与专栏配置
|
|
3
3
|
navTitle: 站点与专栏配置
|
|
4
|
-
description: 配置 site.config.yaml
|
|
4
|
+
description: 配置 site.config.yaml 中的站点信息、各页标题、自定义品牌图标与 content/columns.yaml 专栏。
|
|
5
5
|
section: getting-started
|
|
6
6
|
order: 3
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
站点的全局文字与外观信息集中在根目录的 `site.config.yaml`,专栏目录集中在 `content/columns.yaml
|
|
9
|
+
站点的全局文字与外观信息集中在根目录的 `site.config.yaml`,专栏目录集中在 `content/columns.yaml`。修改后运行 `npx garden sync`(或直接 `make dev`)即可生效。
|
|
10
10
|
|
|
11
11
|
## 1. `site.config.yaml` 完整结构
|
|
12
12
|
|
|
@@ -125,60 +125,3 @@ columns:
|
|
|
125
125
|
| `status` | 否 | 状态标识:`default`(或不写,显示为「持续更新」)、`ongoing`(「连载中」)、`completed`(「已完结」);旧写法 `持续更新`、`连载中`、`已完结` 仍可识别,其他文字按原样显示 |
|
|
126
126
|
| `cover` | 否 | 专栏封面图路径(相对 `content/covers/`) |
|
|
127
127
|
| `posts` | 是 | 按阅读顺序排列的文章路径列表(相对于 `content/posts/`,不带 `.md` 后缀) |
|
|
128
|
-
|
|
129
|
-
## 3. 配置读书笔记书架(`content/bookshelf.yaml`)
|
|
130
|
-
|
|
131
|
-
`/bookshelf` 在站点写了 `content/bookshelf.yaml` 时渲染这里的书,并带上站点顶栏,和其他页面一样可以被索引。没有这个文件时,`/bookshelf` 只在引擎包内的中文验收里显示内置 demo(无顶栏、不收录);正式站要真书目,就放下这份文件。
|
|
132
|
-
|
|
133
|
-
架上每一条 `books` 是一本书。`post: note/ddia` 是只有一篇笔记时的简写。一本书有多篇笔记时写 `posts`,点书脊播放抽书动画,落到 `/bookshelf/<id>`(本书),不打开第一篇。从本书页回到书架播放合上动画。本书页左边是书衣(宽 148,高度跟图片走),右边是书名、作者,以及豆瓣和 PDF 两枚出处胶囊;PDF 后面有一行「(仅分享,侵权删)」。下面是「我的笔记」,点进去才是文章,这一步没有飞页。单篇笔记仍走原来的抽书和合上。
|
|
134
|
-
|
|
135
|
-
`cover` 是书衣,只给书架、抽书飞页和本书页用。文件放在 `content/covers/`,构建时会复制到 `/media/covers/`,即使没有任何博文的 front matter 引用这个文件名。豆瓣封面四周的白边在放进 `content/covers/` 之前裁好,构建不会再改像素。笔记页的横幅仍是各篇 front matter 的 `cover`,不要把那张图填进书衣。yaml 不写 `cover` 时,书衣才沿用第一篇的封面。`color`(`#rgb` 或 `#rrggbb`)画书脊和单篇打开后的纸页。
|
|
136
|
-
|
|
137
|
-
```yaml
|
|
138
|
-
title: 读书笔记
|
|
139
|
-
description: 读过的书,和留下的笔记。
|
|
140
|
-
eyebrow: 花园
|
|
141
|
-
shelves:
|
|
142
|
-
- id: recent
|
|
143
|
-
name: 最近在读
|
|
144
|
-
books:
|
|
145
|
-
- id: ddia
|
|
146
|
-
title: 数据密集型应用系统设计
|
|
147
|
-
author: Martin Kleppmann
|
|
148
|
-
cover: ddia.jpg
|
|
149
|
-
color: "#8b3a2f"
|
|
150
|
-
post: note/ddia
|
|
151
|
-
- id: unit-testing
|
|
152
|
-
title: Unit Testing
|
|
153
|
-
cover: unit-testing.jpg
|
|
154
|
-
color: "#1a2744"
|
|
155
|
-
sources:
|
|
156
|
-
- label: 豆瓣
|
|
157
|
-
href: https://book.douban.com/subject/00000000/
|
|
158
|
-
- label: PDF
|
|
159
|
-
href: https://example.com/unit-testing.pdf
|
|
160
|
-
posts:
|
|
161
|
-
- path: note/note-unit-testing
|
|
162
|
-
kind: overview
|
|
163
|
-
- path: note/note-unit-testing-excerpt
|
|
164
|
-
kind: excerpt
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
| 字段 | 必填 | 说明 |
|
|
168
|
-
| :--- | :--- | :--- |
|
|
169
|
-
| `title` | 是 | 书架页标题 |
|
|
170
|
-
| `description` | 否 | 页面简介 |
|
|
171
|
-
| `eyebrow` | 否 | 架面眉题。不写时用站点名 |
|
|
172
|
-
| `shelves` | 是 | 非空数组。每一层要有 `id`、`name`,以及非空的 `books` |
|
|
173
|
-
| `books[].id` | 是 | 整架唯一 |
|
|
174
|
-
| `books[].title` | 是 | 书名,显示在左页和笔记页 |
|
|
175
|
-
| `books[].author` | 否 | 作者 |
|
|
176
|
-
| `books[].spine` | 否 | 书脊文字,默认同 `title` |
|
|
177
|
-
| `books[].size` | 否 | `thin`、`s-short`、`s-mid`、`s-tall`。普通书默认 `s-mid`,占位薄册默认 `thin` |
|
|
178
|
-
| `books[].cover` | 否 | 书衣。文件名或完整 URL。写了就只用它 |
|
|
179
|
-
| `books[].color` | 否 | 书脊和单篇打开后的纸色 |
|
|
180
|
-
| `books[].filler` | 否 | `true`、`sand` 或 `cream`,占位薄册,不能打开 |
|
|
181
|
-
| `books[].post` | 否 | 单篇笔记的简写 |
|
|
182
|
-
| `books[].posts` | 否 | 同一本书下的笔记。`kind` 为 `overview`(总览)或 `excerpt`(摘抄)。两篇及以上打开本书页 |
|
|
183
|
-
| `books[].sources` | 否 | 出处。本书页只显示「豆瓣」和「PDF」 |
|
|
184
|
-
| `books[].note` | 否 | 没有笔记路径时直接写的 Markdown |
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Site & Column Config
|
|
3
3
|
navTitle: Site & Column Config
|
|
4
|
-
description: Configure site metadata, page headings, and brand icons in site.config.yaml,
|
|
4
|
+
description: Configure site metadata, page headings, and brand icons in site.config.yaml, and organize series in content/columns.yaml.
|
|
5
5
|
section: getting-started
|
|
6
6
|
order: 3
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
Global site copy and appearance settings live in `site.config.yaml` at the repository root, series definitions live in `content/columns.yaml
|
|
9
|
+
Global site copy and appearance settings live in `site.config.yaml` at the repository root, while series definitions live in `content/columns.yaml`. Run `npx garden sync` (or `make dev`) after editing to apply changes.
|
|
10
10
|
|
|
11
11
|
## 1. Full `site.config.yaml` Structure
|
|
12
12
|
|
|
@@ -125,60 +125,3 @@ columns:
|
|
|
125
125
|
| `status` | No | Status value: `default` (or omitted, shown as "Active"), `ongoing` ("Ongoing"), or `completed` ("Completed"). Legacy Chinese values are still accepted, and any other text is shown verbatim |
|
|
126
126
|
| `cover` | No | Cover image path relative to `content/covers/` |
|
|
127
127
|
| `posts` | Yes | Ordered array of post paths relative to `content/posts/`, without the `.md` extension |
|
|
128
|
-
|
|
129
|
-
## 3. Configuring the bookshelf (`content/bookshelf.yaml`)
|
|
130
|
-
|
|
131
|
-
When a site has `content/bookshelf.yaml`, `/bookshelf` renders those books inside the site header and is indexed with the other pages. Without that file, `/bookshelf` is only the package's built-in demo (Chinese click-test, no header, not indexed). A production site supplies the file.
|
|
132
|
-
|
|
133
|
-
Each `books` entry is one volume. `post: note/ddia` is the one-note shorthand. Several notes use `posts`. Clicking that spine plays the extract animation and lands on `/bookshelf/<id>`. It does not open the first note. Returning from that page to the shelf plays the close animation. The book page shows a 148px-wide jacket (height follows the image), the title, the author, and only the Douban and PDF chips. The PDF chip is followed by a short sharing note. My notes lists the notes; each card is a normal link to the article and does not play the flyer. A single note still uses the extract and return animation.
|
|
134
|
-
|
|
135
|
-
`cover` is the jacket for the shelf, the flyer, and the book page. The file lives in `content/covers/` and the build copies it to `/media/covers/`, even when no post front matter names that file. Crop a Douban scan's white margin before committing the file; the build does not change pixels. A note's hero stays that post's front matter `cover`. Omit the jacket and it falls back to the first note's cover. `color` (`#rgb` or `#rrggbb`) is the spine and the single-note open page.
|
|
136
|
-
|
|
137
|
-
```yaml
|
|
138
|
-
title: Reading notes
|
|
139
|
-
description: Books I have read, and the notes I kept.
|
|
140
|
-
eyebrow: Library
|
|
141
|
-
shelves:
|
|
142
|
-
- id: recent
|
|
143
|
-
name: Recent
|
|
144
|
-
books:
|
|
145
|
-
- id: ddia
|
|
146
|
-
title: Designing Data-Intensive Applications
|
|
147
|
-
author: Martin Kleppmann
|
|
148
|
-
cover: ddia.jpg
|
|
149
|
-
color: "#8b3a2f"
|
|
150
|
-
post: note/ddia
|
|
151
|
-
- id: unit-testing
|
|
152
|
-
title: Unit Testing
|
|
153
|
-
cover: unit-testing.jpg
|
|
154
|
-
color: "#1a2744"
|
|
155
|
-
sources:
|
|
156
|
-
- label: Douban
|
|
157
|
-
href: https://book.douban.com/subject/00000000/
|
|
158
|
-
- label: PDF
|
|
159
|
-
href: https://example.com/unit-testing.pdf
|
|
160
|
-
posts:
|
|
161
|
-
- path: note/note-unit-testing
|
|
162
|
-
kind: overview
|
|
163
|
-
- path: note/note-unit-testing-excerpt
|
|
164
|
-
kind: excerpt
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
| Field | Required | Description |
|
|
168
|
-
| :--- | :--- | :--- |
|
|
169
|
-
| `title` | Yes | Page title |
|
|
170
|
-
| `description` | No | Page summary |
|
|
171
|
-
| `eyebrow` | No | Small line above the title. Falls back to the site name |
|
|
172
|
-
| `shelves` | Yes | Non-empty list. Each shelf needs `id`, `name`, and a non-empty `books` list |
|
|
173
|
-
| `books[].id` | Yes | Unique across the shelf |
|
|
174
|
-
| `books[].title` | Yes | Title on the left page and the note view |
|
|
175
|
-
| `books[].author` | No | Author |
|
|
176
|
-
| `books[].spine` | No | Spine label. Defaults to `title` |
|
|
177
|
-
| `books[].size` | No | `thin`, `s-short`, `s-mid`, or `s-tall`. Real books default to `s-mid`; filler spines default to `thin` |
|
|
178
|
-
| `books[].cover` | No | Jacket. File name or full URL. When set, it is the only jacket |
|
|
179
|
-
| `books[].color` | No | Spine and single-note open-page color |
|
|
180
|
-
| `books[].filler` | No | `true`, `sand`, or `cream`. A thin spacer that does not open |
|
|
181
|
-
| `books[].post` | No | Shorthand for one note |
|
|
182
|
-
| `books[].posts` | No | Notes on this volume. `kind` is `overview` or `excerpt`. Two or more open the book page |
|
|
183
|
-
| `books[].sources` | No | Sources. The book page shows Douban and PDF only |
|
|
184
|
-
| `books[].note` | No | Markdown used only when the book names no posts |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hd-garden",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "用 Markdown 写作、用 GitHub Pages 发布的个人博客引擎。",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
"public/apple-touch-icon.png",
|
|
32
32
|
"public/og.png",
|
|
33
33
|
"public/images",
|
|
34
|
-
"public/covers",
|
|
35
34
|
"scripts",
|
|
36
35
|
"templates",
|
|
37
36
|
"ui",
|