返回 slidev
sidebar-gen.ts
根目录 / docs / .vitepress / sidebar-gen.ts
1 import type { DefaultTheme } from 'vitepress'
2 import { join } from 'node:path'
3 import { fileURLToPath } from 'node:url'
4 import fg from 'fast-glob'
5 import graymatter from 'gray-matter'
6
7 const root = fileURLToPath(new URL('../../', import.meta.url))
8
9 const RE_HEADING1 = /^#\s+(.*)/m
10 const RE_HASH_FRAGMENT = /#.*$/
11
12 interface ParsedFile {
13 filepath: string
14 path: string
15 matter: graymatter.GrayMatterFile<string>
16 title: string
17 }
18
19 function parseFile(file: string) {
20 const filepath = join(root, file)
21 const path = file.replace('docs/', '').replace('.md', '')
22 const matter = graymatter.read(filepath)
23 const title = matter.data.title || matter.content.match(RE_HEADING1)?.[1] || path
24 return {
25 filepath,
26 path,
27 matter,
28 title,
29 }
30 }
31
32 export async function getSidebarObject() {
33 const map: Record<string, DefaultTheme.SidebarItem[]> = {}
34
35 const parsedFeatures: ParsedFile[] = await fg([
36 'docs/features/*.md',
37 ], {
38 onlyFiles: true,
39 cwd: root,
40 })
41 .then(files => files.map(parseFile))
42
43 const parsedGuides: ParsedFile[] = await fg([
44 'docs/guide/*.md',
45 ], {
46 onlyFiles: true,
47 cwd: root,
48 })
49 .then(files => files.map(parseFile))
50
51 const parsedCustoms: ParsedFile[] = await fg([
52 'docs/custom/*.md',
53 ], {
54 onlyFiles: true,
55 cwd: root,
56 })
57 .then(files => files.map(parseFile))
58
59 parsedFeatures.forEach(({ matter, path }) => {
60 const items: DefaultTheme.SidebarItem[] = [
61 {
62 text: 'Back to',
63 items: [
64 {
65 text: 'All Features',
66 link: '/features',
67 },
68 ],
69 },
70 ]
71
72 function findParsed(related: string) {
73 related = related.replace(RE_HASH_FRAGMENT, '')
74 const feature = parsedFeatures.find(file => file.path === related)
75 if (feature) {
76 return {
77 type: 'features',
78 item: feature,
79 }
80 }
81 const guide = parsedGuides.find(file => file.path === related)
82 if (guide) {
83 return {
84 type: 'guide',
85 item: guide,
86 }
87 }
88 const custom = parsedCustoms.find(file => file.path === related)
89 if (custom) {
90 return {
91 type: 'custom',
92 item: custom,
93 }
94 }
95 return undefined
96 }
97
98 function frontmatterToSidebarItem(path: string | Record<string, string>): DefaultTheme.SidebarItem[] {
99 if (typeof path === 'string') {
100 const match = findParsed(path)
101 if (match?.type === 'features') {
102 return [{
103 text: `✨ ${match.item.title}`,
104 link: `/${path}`,
105 }]
106 }
107 if (match?.type === 'guide') {
108 return [{
109 text: `📖 ${match.item.title}`,
110 link: `/${path}`,
111 }]
112 }
113 if (match?.type === 'custom') {
114 return [{
115 text: `🛠️ ${match.item.title}`,
116 link: `/${path}`,
117 }]
118 }
119 console.warn(`Dependent file not found: ${path}`)
120 return [{
121 text: path,
122 link: `/${path}`,
123 }]
124 }
125 else {
126 return Object.entries(path).map(([text, link]) => ({
127 text,
128 link,
129 }))
130 }
131 }
132
133 if (matter.data.depends) {
134 items.push({
135 text: 'Depends on',
136 items: matter.data.depends.flatMap(frontmatterToSidebarItem),
137 })
138 }
139
140 if (matter.data.relates) {
141 items.push({
142 text: 'Related to',
143 items: matter.data.relates.flatMap(frontmatterToSidebarItem),
144 })
145 }
146
147 const derives = matter.data.derives
148 ?? parsedFeatures.filter(f => f.matter.data.depends?.includes(path)).map(f => f.path)
149
150 if (derives.length) {
151 items.push({
152 text: 'Derives',
153 items: derives.flatMap(frontmatterToSidebarItem),
154 })
155 }
156
157 map[`/${path}`] = items
158 })
159
160 return map
161 }
162
162 lines TYPESCRIPT