hd-garden 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/admin/preview/[...path]/page.tsx +1 -1
- package/app/blog/[...path]/page.tsx +2 -2
- package/app/columns/[column]/[...path]/page.tsx +2 -2
- package/app/components/ArticleMeasure.tsx +39 -4
- package/app/components/GardenDocsNav.tsx +184 -0
- package/app/components/MarkdownArticle.tsx +7 -6
- package/app/components/SiteChrome.tsx +1 -1
- package/app/components/SiteFooter.tsx +10 -3
- package/app/garden/[...slug]/page.tsx +148 -0
- package/app/garden/page.tsx +61 -128
- package/app/garden-modules.d.ts +15 -0
- package/app/globals.css +10 -10
- package/app/lib/content.ts +16 -10
- package/app/lib/garden-docs-meta.ts +83 -0
- package/app/lib/heading-number.ts +13 -0
- package/app/styles/a2.css +531 -0
- package/docs/handbook/assets/reading-layout.svg +26 -0
- package/docs/handbook/components/card.md +44 -0
- package/docs/handbook/components/chart.md +52 -0
- package/docs/handbook/components/compare.md +40 -0
- package/docs/handbook/components/figure.md +70 -0
- package/docs/handbook/components/file/checklist.md +17 -0
- package/docs/handbook/components/file.md +32 -0
- package/docs/handbook/components/html.md +67 -0
- package/docs/handbook/components/interactive.md +65 -0
- package/docs/handbook/components/stat.md +55 -0
- package/docs/handbook/components/timeline.md +50 -0
- package/docs/handbook/components.md +30 -0
- package/docs/handbook/config.md +125 -0
- package/docs/handbook/deploy.md +273 -0
- package/docs/handbook/design.md +81 -0
- package/docs/handbook/index.md +92 -0
- package/docs/handbook/start.md +92 -0
- package/docs/handbook/workflow.md +81 -0
- package/docs/handbook/writing.md +148 -0
- package/package.json +2 -1
- package/scripts/build-content.mjs +124 -15
- package/scripts/handbook-allowlist.mjs +68 -0
- package/scripts/handbook-facts.mjs +1104 -0
- package/site.config.yaml +2 -2
- package/templates/garden-blocks.md +1 -1
- package/templates/site/package.json +1 -3
- package/ui/garden/catalog.mjs +1 -1
- package/ui/preview/dist/garden-ui.esm.js +1 -1
- package/ui/preview/dist/garden-ui.iife.js +1 -1
package/app/garden/page.tsx
CHANGED
|
@@ -1,148 +1,81 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import { ArrowRight } from "@phosphor-icons/react/ssr";
|
|
3
|
+
import { ArticleMeasure } from "../components/ArticleMeasure";
|
|
4
|
+
import { ArticleReferences } from "../components/ArticleReferences";
|
|
5
|
+
import { ArticleTitle } from "../components/ArticleTitle";
|
|
6
|
+
import { GardenDocsNav } from "../components/GardenDocsNav";
|
|
4
7
|
import { GardenMediaGuide } from "../components/GardenMediaGuide";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
8
|
+
import { MarkdownArticle } from "../components/MarkdownArticle";
|
|
9
|
+
import { TableOfContents } from "../components/TableOfContents";
|
|
10
|
+
import {
|
|
11
|
+
extractHeadings,
|
|
12
|
+
gardenDocHref,
|
|
13
|
+
gardenDocs,
|
|
14
|
+
gardenIndexDoc,
|
|
15
|
+
} from "../lib/content";
|
|
16
|
+
import {
|
|
17
|
+
orderedHandbookDocs,
|
|
18
|
+
toHandbookNavItems,
|
|
19
|
+
} from "../lib/garden-docs-meta";
|
|
7
20
|
import { pageAlternates } from "../lib/page-alternates";
|
|
21
|
+
import { siteConfig } from "../site.config";
|
|
8
22
|
|
|
9
23
|
export const metadata = {
|
|
10
|
-
title: "Garden",
|
|
11
|
-
description:
|
|
24
|
+
title: "Garden 使用说明",
|
|
25
|
+
description:
|
|
26
|
+
"用 Markdown 写文章、随想与专栏。自动处理标题断行、宽图宽表展开、代码对比与 9 个内置组件。",
|
|
12
27
|
alternates: pageAlternates("/garden"),
|
|
13
28
|
};
|
|
14
29
|
|
|
15
|
-
const jumps = [
|
|
16
|
-
["#write-post", "写文章"],
|
|
17
|
-
["#write-thought", "随想"],
|
|
18
|
-
["#make-column", "专栏"],
|
|
19
|
-
["#setup", "跑起来"],
|
|
20
|
-
["#media", "视频和链接"],
|
|
21
|
-
["#admin", "后台"],
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
const setupSteps = [
|
|
25
|
-
{
|
|
26
|
-
title: "创建站点",
|
|
27
|
-
body: (
|
|
28
|
-
<>
|
|
29
|
-
运行 <code>npx hd-garden init my-blog</code>。目录里留下 <code>content/</code> 和 <code>site.config.yaml</code>。仓库名叫 <code><owner>.github.io</code> 时,地址最直接。
|
|
30
|
-
</>
|
|
31
|
-
),
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
title: "在本地看",
|
|
35
|
-
command: "make dev",
|
|
36
|
-
body: "第一次会安装依赖。然后打开终端里的地址。",
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
title: "换成你的内容",
|
|
40
|
-
files: [
|
|
41
|
-
["site.config.yaml", "站名、作者、各页标题"],
|
|
42
|
-
["content/posts/", "长文和附件"],
|
|
43
|
-
["content/thoughts/", "随想"],
|
|
44
|
-
["content/columns.yaml", "专栏和阅读顺序"],
|
|
45
|
-
],
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
title: "发布",
|
|
49
|
-
command: "make update",
|
|
50
|
-
body: "先检查,再提交并推送。main 更新后,网站、搜索和 RSS 会一起更新。",
|
|
51
|
-
},
|
|
52
|
-
];
|
|
53
|
-
|
|
54
30
|
export default function GardenPage() {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
className="garden-primary-link"
|
|
64
|
-
href="https://github.com/hedon954/garden"
|
|
65
|
-
target="_blank"
|
|
66
|
-
rel="noreferrer"
|
|
67
|
-
>
|
|
68
|
-
<GithubLogo size={18} weight="fill" />
|
|
69
|
-
查看源码
|
|
70
|
-
<ArrowUpRight size={16} />
|
|
71
|
-
</a>
|
|
72
|
-
<a className="inline-cta" href="#setup">
|
|
73
|
-
在本地跑起来
|
|
74
|
-
<ArrowRight size={16} />
|
|
75
|
-
</a>
|
|
76
|
-
</div>
|
|
77
|
-
</PageIntro>
|
|
78
|
-
|
|
79
|
-
<nav className="garden-jumps" aria-label="本页">
|
|
80
|
-
{jumps.map(([href, label]) => (
|
|
81
|
-
<a key={href} href={href}>{label}</a>
|
|
82
|
-
))}
|
|
83
|
-
</nav>
|
|
31
|
+
const navItems = toHandbookNavItems(gardenDocs);
|
|
32
|
+
const ordered = orderedHandbookDocs(gardenDocs);
|
|
33
|
+
const firstDoc = ordered[0] ?? null;
|
|
34
|
+
const content = gardenIndexDoc?.content ?? "";
|
|
35
|
+
const headings = extractHeadings(content);
|
|
36
|
+
const configuredTitle = siteConfig.pages.garden.title?.trim() || gardenIndexDoc?.title || "Garden 使用说明";
|
|
37
|
+
const configuredSubtitle = siteConfig.pages.garden.subtitle?.trim() || gardenIndexDoc?.description || "";
|
|
38
|
+
void GardenMediaGuide;
|
|
84
39
|
|
|
85
|
-
|
|
40
|
+
return (
|
|
41
|
+
<main className="column-reading-page garden-doc-page">
|
|
42
|
+
<GardenDocsNav docs={navItems} currentPath="" />
|
|
86
43
|
|
|
87
|
-
<
|
|
88
|
-
<header className="
|
|
89
|
-
<
|
|
90
|
-
|
|
44
|
+
<article className="article-column garden-doc-article h-entry">
|
|
45
|
+
<header className="gdoc-article-head">
|
|
46
|
+
<div className="gdoc-eyebrow">
|
|
47
|
+
<span>Garden 使用说明</span>
|
|
48
|
+
<span aria-hidden="true">/</span>
|
|
49
|
+
<span>总览</span>
|
|
50
|
+
</div>
|
|
51
|
+
<ArticleTitle title={configuredTitle} />
|
|
52
|
+
{configuredSubtitle ? (
|
|
53
|
+
<p className="gdoc-article-deck p-summary">{configuredSubtitle}</p>
|
|
54
|
+
) : null}
|
|
91
55
|
</header>
|
|
92
56
|
|
|
93
|
-
<
|
|
94
|
-
{setupSteps.map((step, index) => (
|
|
95
|
-
<li key={step.title}>
|
|
96
|
-
<span>{index + 1}</span>
|
|
97
|
-
<div>
|
|
98
|
-
<h3>{step.title}</h3>
|
|
99
|
-
{"command" in step && step.command ? <pre><code>{step.command}</code></pre> : null}
|
|
100
|
-
{"files" in step && step.files ? (
|
|
101
|
-
<dl>
|
|
102
|
-
{step.files.map(([name, note]) => (
|
|
103
|
-
<div key={name}>
|
|
104
|
-
<dt>{name}</dt>
|
|
105
|
-
<dd>{note}</dd>
|
|
106
|
-
</div>
|
|
107
|
-
))}
|
|
108
|
-
</dl>
|
|
109
|
-
) : null}
|
|
110
|
-
{"body" in step && step.body ? <p>{step.body}</p> : null}
|
|
111
|
-
</div>
|
|
112
|
-
</li>
|
|
113
|
-
))}
|
|
114
|
-
</ol>
|
|
57
|
+
<ArticleMeasure />
|
|
115
58
|
|
|
116
|
-
<div className="
|
|
117
|
-
<
|
|
118
|
-
<strong>仓库可以是私有的。</strong>
|
|
119
|
-
公开仓库一种就够。想把草稿留在私有仓库时,用 <code>PAGES_REPOSITORY</code> 指向公开的页面仓库。两种都是 <code>make update</code>。
|
|
120
|
-
</p>
|
|
121
|
-
<p>
|
|
122
|
-
<strong>先看草稿。</strong>
|
|
123
|
-
<code>CONTENT_INCLUDE_DRAFTS=1 make dev</code> 会把草稿和还没到时间的文章也显示出来。
|
|
124
|
-
</p>
|
|
125
|
-
<p>
|
|
126
|
-
<strong>密钥不进仓库。</strong>
|
|
127
|
-
评论、统计和后台的密钥放在部署平台。
|
|
128
|
-
</p>
|
|
59
|
+
<div className="e-content">
|
|
60
|
+
<MarkdownArticle content={content} />
|
|
129
61
|
</div>
|
|
130
|
-
</section>
|
|
131
62
|
|
|
132
|
-
|
|
63
|
+
<ArticleReferences references={gardenIndexDoc?.references} />
|
|
133
64
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
65
|
+
{firstDoc ? (
|
|
66
|
+
<nav className="gdoc-pager" aria-label="说明书上下篇导航">
|
|
67
|
+
<Link href={gardenDocHref(firstDoc)} className="gdoc-pager-card is-next">
|
|
68
|
+
<span className="gdoc-pager-dir">
|
|
69
|
+
下一篇
|
|
70
|
+
<ArrowRight size={14} aria-hidden="true" />
|
|
71
|
+
</span>
|
|
72
|
+
<strong>{firstDoc.title}</strong>
|
|
73
|
+
</Link>
|
|
74
|
+
</nav>
|
|
75
|
+
) : null}
|
|
76
|
+
</article>
|
|
144
77
|
|
|
145
|
-
<
|
|
78
|
+
<TableOfContents headings={headings} label="本页目录" />
|
|
146
79
|
</main>
|
|
147
80
|
);
|
|
148
81
|
}
|
package/app/garden-modules.d.ts
CHANGED
|
@@ -48,6 +48,21 @@ declare module "@garden/generated-content" {
|
|
|
48
48
|
export const posts: ContentEntry[];
|
|
49
49
|
export const columns: ContentEntry[];
|
|
50
50
|
export const thoughts: ContentEntry[];
|
|
51
|
+
export type GardenDocEntry = {
|
|
52
|
+
title: string;
|
|
53
|
+
slug: string;
|
|
54
|
+
path: string;
|
|
55
|
+
description?: string;
|
|
56
|
+
section?: string;
|
|
57
|
+
sectionTitle?: string;
|
|
58
|
+
order?: number;
|
|
59
|
+
readingTime?: string;
|
|
60
|
+
references?: { title: string; url?: string; domain?: string }[];
|
|
61
|
+
sourcePath: string;
|
|
62
|
+
content: string;
|
|
63
|
+
};
|
|
64
|
+
export const gardenDocs: GardenDocEntry[];
|
|
65
|
+
export const gardenIndexDoc: GardenDocEntry | null;
|
|
51
66
|
export const a2WordCounts: Record<string, number>;
|
|
52
67
|
export type LinkCardMeta = {
|
|
53
68
|
title: string;
|
package/app/globals.css
CHANGED
|
@@ -4828,14 +4828,14 @@ html[data-theme="dark"] .search-footer {
|
|
|
4828
4828
|
.garden-page,
|
|
4829
4829
|
.admin-shell,
|
|
4830
4830
|
.admin-access-page {
|
|
4831
|
-
--garden-ink:
|
|
4832
|
-
--garden-muted:
|
|
4833
|
-
--garden-stage:
|
|
4834
|
-
--garden-link:
|
|
4835
|
-
--garden-button:
|
|
4836
|
-
--garden-button-text:
|
|
4831
|
+
--garden-ink: var(--ink);
|
|
4832
|
+
--garden-muted: var(--muted);
|
|
4833
|
+
--garden-stage: var(--paper-deep);
|
|
4834
|
+
--garden-link: var(--ink);
|
|
4835
|
+
--garden-button: var(--ink);
|
|
4836
|
+
--garden-button-text: var(--paper);
|
|
4837
4837
|
color: var(--garden-ink);
|
|
4838
|
-
font-family:
|
|
4838
|
+
font-family: var(--sans);
|
|
4839
4839
|
}
|
|
4840
4840
|
|
|
4841
4841
|
:root[data-theme="dark"] .garden-page,
|
|
@@ -4844,9 +4844,9 @@ html[data-theme="dark"] .search-footer {
|
|
|
4844
4844
|
--garden-ink: var(--ink);
|
|
4845
4845
|
--garden-muted: var(--muted);
|
|
4846
4846
|
--garden-stage: var(--paper-deep);
|
|
4847
|
-
--garden-link:
|
|
4848
|
-
--garden-button:
|
|
4849
|
-
--garden-button-text:
|
|
4847
|
+
--garden-link: var(--ink);
|
|
4848
|
+
--garden-button: var(--ink);
|
|
4849
|
+
--garden-button-text: var(--paper);
|
|
4850
4850
|
}
|
|
4851
4851
|
|
|
4852
4852
|
.garden-page pre,
|
package/app/lib/content.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { markdownPlugins } from "./markdown-plugins";
|
|
2
2
|
import {
|
|
3
3
|
columns,
|
|
4
|
+
gardenDocs,
|
|
5
|
+
gardenIndexDoc,
|
|
4
6
|
posts,
|
|
5
7
|
thoughts,
|
|
6
8
|
type ContentEntry,
|
|
9
|
+
type GardenDocEntry,
|
|
7
10
|
type MediaItem,
|
|
8
11
|
} from "@garden/generated-content";
|
|
9
12
|
import GithubSlugger from "github-slugger";
|
|
@@ -14,13 +17,14 @@ import { toString } from "mdast-util-to-string";
|
|
|
14
17
|
import remarkParse from "remark-parse";
|
|
15
18
|
import { unified } from "unified";
|
|
16
19
|
import { visit } from "unist-util-visit";
|
|
17
|
-
import { splitHeadingNumber } from "./heading-number";
|
|
20
|
+
import { articleUsesHeadingNumbers, autoNumbersHeadings, splitHeadingNumber } from "./heading-number";
|
|
18
21
|
import { siteHref } from "./site-href";
|
|
19
22
|
|
|
20
|
-
export { columns, posts, thoughts };
|
|
21
|
-
export type { ContentEntry, MediaItem };
|
|
23
|
+
export { columns, gardenDocs, gardenIndexDoc, posts, thoughts };
|
|
24
|
+
export type { ContentEntry, GardenDocEntry, MediaItem };
|
|
22
25
|
|
|
23
26
|
export const postHref = (post: Pick<ContentEntry, "path">) => siteHref(`/blog/${post.path}`);
|
|
27
|
+
export const gardenDocHref = (doc: Pick<GardenDocEntry, "path">) => siteHref(`/garden/${doc.path}`);
|
|
24
28
|
export const columnEntryPath = (entry: Pick<ContentEntry, "column" | "path">) => {
|
|
25
29
|
const prefix = entry.column ? `${entry.column}/` : "";
|
|
26
30
|
return prefix && entry.path.startsWith(prefix) ? entry.path.slice(prefix.length) : entry.path;
|
|
@@ -51,10 +55,8 @@ export const formatDate = (value: string, withTime = false) => {
|
|
|
51
55
|
}).format(date);
|
|
52
56
|
};
|
|
53
57
|
|
|
54
|
-
const NUMBERED_ARTICLE = /^#{2,3}[ \t]+\d/m;
|
|
55
|
-
|
|
56
58
|
/** An article that already writes its own heading numbers is not auto-numbered. */
|
|
57
|
-
export
|
|
59
|
+
export { articleUsesHeadingNumbers, autoNumbersHeadings };
|
|
58
60
|
|
|
59
61
|
/**
|
|
60
62
|
* Prototype render: an article with no `##` treats `###` as the top section.
|
|
@@ -76,11 +78,12 @@ export { splitHeadingNumber };
|
|
|
76
78
|
* rehype-slug. Every heading depth advances the slugger so headings outside
|
|
77
79
|
* the H2-H5 TOC range cannot shift the IDs shown in the directory.
|
|
78
80
|
* `no` follows the prototype: keep an author-written number, otherwise
|
|
79
|
-
* auto-number h2/h3 only when the article has no numbered headings
|
|
81
|
+
* auto-number h2/h3 only when the article has no numbered headings and its
|
|
82
|
+
* front matter does not say `numbering: false`.
|
|
80
83
|
*/
|
|
81
|
-
export const extractHeadings = (markdown: string) => {
|
|
84
|
+
export const extractHeadings = (markdown: string, { numbering }: { numbering?: boolean } = {}) => {
|
|
82
85
|
const slugger = new GithubSlugger();
|
|
83
|
-
const
|
|
86
|
+
const autoNumber = autoNumbersHeadings(markdown, numbering);
|
|
84
87
|
const base = articleHeadingBase(markdown);
|
|
85
88
|
const headings: Array<{ depth: number; text: string; id: string; no?: string }> = [];
|
|
86
89
|
const tree = unified().use(remarkParse).use(markdownPlugins).parse(markdown);
|
|
@@ -101,7 +104,7 @@ export const extractHeadings = (markdown: string) => {
|
|
|
101
104
|
text = split.text;
|
|
102
105
|
if (level === 2) { h2 += 1; h3 = 0; }
|
|
103
106
|
else h3 += 1;
|
|
104
|
-
} else if (
|
|
107
|
+
} else if (autoNumber) {
|
|
105
108
|
if (level === 2) { h2 += 1; h3 = 0; no = String(h2); }
|
|
106
109
|
else { h3 += 1; no = h2 ? `${h2}.${h3}` : String(h3); }
|
|
107
110
|
}
|
|
@@ -129,6 +132,9 @@ export const findPost = (path: string): ContentEntry | undefined =>
|
|
|
129
132
|
export const findThought = (slug: string): ContentEntry | undefined =>
|
|
130
133
|
thoughts.find((thought) => thought.slug === slug);
|
|
131
134
|
|
|
135
|
+
export const findGardenDoc = (path: string): GardenDocEntry | undefined =>
|
|
136
|
+
gardenDocs.find((doc) => doc.path === path);
|
|
137
|
+
|
|
132
138
|
export const findColumnEntry = (
|
|
133
139
|
column: string,
|
|
134
140
|
path: string,
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export type GardenDocNavItem = {
|
|
2
|
+
path: string;
|
|
3
|
+
title: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
section?: string;
|
|
6
|
+
order?: number;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type HandbookSectionMeta = {
|
|
10
|
+
id: string;
|
|
11
|
+
title: string;
|
|
12
|
+
note?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const HANDBOOK_SECTIONS: HandbookSectionMeta[] = [
|
|
16
|
+
{ id: "getting-started", title: "快速上手", note: "建站、配置与发布" },
|
|
17
|
+
{ id: "writing-and-reading", title: "写作与排版", note: "Markdown 与阅读版面" },
|
|
18
|
+
{ id: "components", title: "内置组件", note: "garden <name>" },
|
|
19
|
+
{ id: "workflow", title: "检查与补全", note: "语法检查与编辑器片段" },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export const COMPONENT_BADGE: Record<string, string> = {
|
|
23
|
+
"components/card": "card",
|
|
24
|
+
"components/stat": "stat",
|
|
25
|
+
"components/compare": "compare",
|
|
26
|
+
"components/timeline": "timeline",
|
|
27
|
+
"components/chart": "chart",
|
|
28
|
+
"components/figure": "figure",
|
|
29
|
+
"components/html": "html",
|
|
30
|
+
"components/interactive": "interactive",
|
|
31
|
+
"components/file": "file",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const NAV_SHORT_TITLE: Record<string, string> = {
|
|
35
|
+
start: "快速开始",
|
|
36
|
+
config: "站点与专栏配置",
|
|
37
|
+
deploy: "发布到 GitHub Pages",
|
|
38
|
+
writing: "写文章与随想",
|
|
39
|
+
design: "阅读排版是怎么工作的",
|
|
40
|
+
components: "组件总览",
|
|
41
|
+
"components/card": "知识卡片",
|
|
42
|
+
"components/stat": "数字指标",
|
|
43
|
+
"components/compare": "并排对比",
|
|
44
|
+
"components/timeline": "时间线",
|
|
45
|
+
"components/chart": "条形图表",
|
|
46
|
+
"components/figure": "分区结构图",
|
|
47
|
+
"components/html": "嵌入网页片段",
|
|
48
|
+
"components/interactive": "可调公式与参数",
|
|
49
|
+
"components/file": "文件下载与预览",
|
|
50
|
+
workflow: "语法检查与快捷输入",
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export function groupHandbookDocs<T extends GardenDocNavItem>(docs: T[]) {
|
|
54
|
+
const bySection = new Map<string, T[]>();
|
|
55
|
+
for (const section of HANDBOOK_SECTIONS) {
|
|
56
|
+
bySection.set(section.id, []);
|
|
57
|
+
}
|
|
58
|
+
for (const doc of docs) {
|
|
59
|
+
const key = doc.section && bySection.has(doc.section) ? doc.section : "getting-started";
|
|
60
|
+
bySection.get(key)!.push(doc);
|
|
61
|
+
}
|
|
62
|
+
for (const list of bySection.values()) {
|
|
63
|
+
list.sort((a, b) => (a.order ?? 999) - (b.order ?? 999) || a.path.localeCompare(b.path));
|
|
64
|
+
}
|
|
65
|
+
return HANDBOOK_SECTIONS.map((section) => ({
|
|
66
|
+
...section,
|
|
67
|
+
items: bySection.get(section.id) ?? [],
|
|
68
|
+
})).filter((section) => section.items.length > 0);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function orderedHandbookDocs<T extends GardenDocNavItem>(docs: T[]): T[] {
|
|
72
|
+
return groupHandbookDocs(docs).flatMap((section) => section.items);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function toHandbookNavItems(docs: GardenDocNavItem[]): GardenDocNavItem[] {
|
|
76
|
+
return orderedHandbookDocs(docs).map((doc) => ({
|
|
77
|
+
path: doc.path,
|
|
78
|
+
title: doc.title,
|
|
79
|
+
description: doc.description,
|
|
80
|
+
section: doc.section,
|
|
81
|
+
order: doc.order,
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
@@ -7,3 +7,16 @@ export const splitHeadingNumber = (text: string) => {
|
|
|
7
7
|
const match = /^(\d+(?:\.\d+)*\.?)[ \t]+([\s\S]+)$/u.exec(text.trim());
|
|
8
8
|
return match ? { no: match[1], text: match[2] } : null;
|
|
9
9
|
};
|
|
10
|
+
|
|
11
|
+
const NUMBERED_ARTICLE = /^#{2,3}[ \t]+\d/m;
|
|
12
|
+
|
|
13
|
+
/** True when the article writes its own section numbers (`## 2. …`). */
|
|
14
|
+
export const articleUsesHeadingNumbers = (markdown: string) => NUMBERED_ARTICLE.test(markdown);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Whether h2/h3 get automatic numbers. Off when the author writes numbers, or
|
|
18
|
+
* when the front matter says `numbering: false`. Author-written numbers are
|
|
19
|
+
* always kept.
|
|
20
|
+
*/
|
|
21
|
+
export const autoNumbersHeadings = (markdown: string, numbering?: boolean) =>
|
|
22
|
+
numbering !== false && !articleUsesHeadingNumbers(markdown);
|