docia 0.0.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/README.md +71 -0
- package/package.json +35 -0
- package/src/book/index.ts +11 -0
- package/src/book/summary.ts +327 -0
- package/src/book/types.ts +48 -0
- package/src/build/build-site.ts +277 -0
- package/src/build/client-assets.ts +109 -0
- package/src/build/index.ts +12 -0
- package/src/build/seo.ts +114 -0
- package/src/cli-types.ts +16 -0
- package/src/cli.ts +111 -0
- package/src/client/main.ts +28 -0
- package/src/client/router.ts +244 -0
- package/src/client/search.ts +619 -0
- package/src/client/styles.css +811 -0
- package/src/commands/build.ts +194 -0
- package/src/commands/check.ts +208 -0
- package/src/commands/dev.ts +33 -0
- package/src/commands/index.ts +59 -0
- package/src/commands/init.ts +80 -0
- package/src/commands/new.ts +125 -0
- package/src/commands/serve.ts +69 -0
- package/src/config/defaults.ts +42 -0
- package/src/config/define-config.ts +5 -0
- package/src/config/load-config.ts +473 -0
- package/src/config/types.ts +76 -0
- package/src/dev/index.ts +1 -0
- package/src/dev/start-dev-server.ts +213 -0
- package/src/errors.ts +16 -0
- package/src/index.ts +13 -0
- package/src/markdown/engine.ts +277 -0
- package/src/markdown/index.ts +7 -0
- package/src/render/index.ts +3 -0
- package/src/render/layout.tsx +616 -0
- package/src/search/index.ts +40 -0
- package/src/search/types.ts +7 -0
- package/src/server/static.ts +191 -0
- package/src/templates/init-template.ts +87 -0
- package/src/utils/args.ts +148 -0
- package/src/utils/html.ts +39 -0
- package/src/utils/process.ts +23 -0
- package/src/utils/strings.ts +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# docia
|
|
2
|
+
|
|
3
|
+
SEO-first static docs generator in Bun, inspired by mdBook.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun install
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## CLI
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bun run src/cli.ts --help
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Documentation
|
|
18
|
+
|
|
19
|
+
Project usage docs are written with `docia` itself in `docs/book`.
|
|
20
|
+
|
|
21
|
+
Run the docs locally:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bun run src/cli.ts dev --config docs/docia.config.ts
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Build docs output:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
bun run src/cli.ts build --config docs/docia.config.ts
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Commands
|
|
34
|
+
|
|
35
|
+
- `bun run src/cli.ts init`
|
|
36
|
+
- `bun run src/cli.ts build`
|
|
37
|
+
- `bun run src/cli.ts dev`
|
|
38
|
+
- `bun run src/cli.ts serve`
|
|
39
|
+
- `bun run src/cli.ts check`
|
|
40
|
+
- `bun run src/cli.ts new <chapter-name>`
|
|
41
|
+
|
|
42
|
+
`dev` runs an initial build, serves `dist/`, and rebuilds on file changes.
|
|
43
|
+
|
|
44
|
+
## Example Project
|
|
45
|
+
|
|
46
|
+
See `examples/team-handbook` for a complete docs sample.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
bun run src/cli.ts build --config examples/team-handbook/docia.config.ts
|
|
50
|
+
bun run src/cli.ts serve --config examples/team-handbook/docia.config.ts --build
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Status
|
|
54
|
+
|
|
55
|
+
Foundation is in progress:
|
|
56
|
+
|
|
57
|
+
- CLI command skeleton
|
|
58
|
+
- Config loading and validation (`docia.config.ts`)
|
|
59
|
+
- Project scaffolding via `init`
|
|
60
|
+
- `SUMMARY.md` parser + chapter graph (nesting, prev/next)
|
|
61
|
+
- Bun-native Markdown rendering (`Bun.markdown.html/render`)
|
|
62
|
+
- Client assets bundled with `Bun.build` (JS + CSS loaders)
|
|
63
|
+
- Static HTML build output with sidebar, TOC, and pagination
|
|
64
|
+
- SPA-style client routing after first page load
|
|
65
|
+
- Shiki build-time syntax highlighting for code blocks
|
|
66
|
+
- Build-time JSX page rendering (Preact SSR, no hydration required)
|
|
67
|
+
- Basic SEO output (`canonical`, meta description, JSON-LD, `robots.txt`, `llms.txt`)
|
|
68
|
+
- LLM-friendly page actions (copy markdown, view markdown, open in ChatGPT/Claude)
|
|
69
|
+
- Sidebar socials, "Powered by docsia", and optional GitHub edit links
|
|
70
|
+
- `dev` and `serve` on `Bun.serve()`
|
|
71
|
+
- `check` validations for missing files, duplicate routes/output paths, broken markdown links, and orphan markdown files
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "docia",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"module": "src/cli.ts",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"docia": "./src/cli.ts"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"src",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"docia": "bun run src/cli.ts",
|
|
15
|
+
"build": "bun run src/cli.ts build",
|
|
16
|
+
"dev": "bun run src/cli.ts dev",
|
|
17
|
+
"serve": "bun run src/cli.ts serve",
|
|
18
|
+
"check": "bun run src/cli.ts check",
|
|
19
|
+
"test": "bun test"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/bun": "latest",
|
|
23
|
+
"@types/react": "^19.2.13",
|
|
24
|
+
"@types/react-dom": "^19.2.3"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"typescript": "^5"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"picocolors": "^1.1.1",
|
|
31
|
+
"react": "^19.2.4",
|
|
32
|
+
"react-dom": "^19.2.4",
|
|
33
|
+
"shiki": "^3.22.0"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { posix, resolve } from "node:path";
|
|
2
|
+
import type { ResolvedConfig } from "../config/types";
|
|
3
|
+
import { CliError } from "../errors";
|
|
4
|
+
import type {
|
|
5
|
+
SummaryChapterEntry,
|
|
6
|
+
SummaryEntry,
|
|
7
|
+
SummaryGraph,
|
|
8
|
+
SummaryLinkEntry,
|
|
9
|
+
SummarySectionEntry,
|
|
10
|
+
} from "./types";
|
|
11
|
+
|
|
12
|
+
const MARKDOWN_PATH_PATTERN = /\.(md|markdown|mdown)$/i;
|
|
13
|
+
const LIST_ITEM_PATTERN = /^(\s*)[-*+]\s+(.+)$/;
|
|
14
|
+
const MARKDOWN_LINK_PATTERN = /^\[([^\]]+)\]\((.+)\)\s*$/;
|
|
15
|
+
const EXTERNAL_LINK_PATTERN = /^(?:[a-zA-Z][a-zA-Z0-9+.-]*:|#|\/\/)/;
|
|
16
|
+
|
|
17
|
+
interface ParsedSummaryItem {
|
|
18
|
+
depth: number;
|
|
19
|
+
line: number;
|
|
20
|
+
title: string;
|
|
21
|
+
href?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface EntryStackItem {
|
|
25
|
+
depth: number;
|
|
26
|
+
entry: SummaryEntry;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function toLeadingSpaces(input: string): string {
|
|
30
|
+
return input.replaceAll("\t", " ");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseSummaryItems(text: string): ParsedSummaryItem[] {
|
|
34
|
+
const lines = text.split(/\r?\n/);
|
|
35
|
+
const items: ParsedSummaryItem[] = [];
|
|
36
|
+
|
|
37
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
38
|
+
const line = lines[index];
|
|
39
|
+
const match = LIST_ITEM_PATTERN.exec(line ?? "");
|
|
40
|
+
if (!match) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const indent = toLeadingSpaces(match[1] ?? "");
|
|
45
|
+
const raw = (match[2] ?? "").trim();
|
|
46
|
+
if (raw.length === 0) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const depth = Math.floor(indent.length / 2);
|
|
51
|
+
const linkMatch = MARKDOWN_LINK_PATTERN.exec(raw);
|
|
52
|
+
if (linkMatch) {
|
|
53
|
+
const title = (linkMatch[1] ?? "").trim();
|
|
54
|
+
const href = (linkMatch[2] ?? "").trim();
|
|
55
|
+
|
|
56
|
+
if (title.length === 0) {
|
|
57
|
+
throw new CliError(`Invalid SUMMARY entry at line ${index + 1}: missing title.`);
|
|
58
|
+
}
|
|
59
|
+
if (href.length === 0) {
|
|
60
|
+
throw new CliError(`Invalid SUMMARY entry at line ${index + 1}: missing href.`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
items.push({
|
|
64
|
+
depth,
|
|
65
|
+
line: index + 1,
|
|
66
|
+
title,
|
|
67
|
+
href,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
items.push({
|
|
74
|
+
depth,
|
|
75
|
+
line: index + 1,
|
|
76
|
+
title: raw,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return items;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function normalizeSourcePath(href: string, line: number): string {
|
|
84
|
+
const trimmed = href.trim();
|
|
85
|
+
const withoutHash = trimmed.split("#", 1)[0] ?? "";
|
|
86
|
+
const withoutQuery = withoutHash.split("?", 1)[0] ?? "";
|
|
87
|
+
|
|
88
|
+
let normalized = withoutQuery.replaceAll("\\", "/").replace(/^\.?\//, "");
|
|
89
|
+
if (normalized.startsWith("/")) {
|
|
90
|
+
normalized = normalized.slice(1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
normalized = posix.normalize(normalized);
|
|
94
|
+
|
|
95
|
+
if (normalized.length === 0 || normalized === ".") {
|
|
96
|
+
throw new CliError(`Invalid SUMMARY link at line ${line}: empty file path.`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (normalized === ".." || normalized.startsWith("../")) {
|
|
100
|
+
throw new CliError(
|
|
101
|
+
`Invalid SUMMARY link at line ${line}: path cannot traverse outside source dir.`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return normalized;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function escapeRegExp(input: string): string {
|
|
109
|
+
return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function stripMarkdownExtension(pathValue: string): string {
|
|
113
|
+
return pathValue.replace(MARKDOWN_PATH_PATTERN, "");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function trimReadmeSuffix(pathValue: string): string {
|
|
117
|
+
const readmePattern = new RegExp(`(?:^|/)${escapeRegExp("README")}$`, "i");
|
|
118
|
+
return pathValue.replace(readmePattern, "");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function toRoutePath(sourcePath: string, prettyUrls: boolean): string {
|
|
122
|
+
const normalized = stripMarkdownExtension(sourcePath);
|
|
123
|
+
const withoutReadme = trimReadmeSuffix(normalized);
|
|
124
|
+
const cleaned = withoutReadme.replace(/^\//, "").replace(/\/$/, "");
|
|
125
|
+
|
|
126
|
+
if (prettyUrls) {
|
|
127
|
+
if (cleaned.length === 0) {
|
|
128
|
+
return "/";
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return `/${cleaned}/`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (cleaned.length === 0) {
|
|
135
|
+
return "/index.html";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return `/${cleaned}.html`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function toOutputPath(sourcePath: string, prettyUrls: boolean): string {
|
|
142
|
+
const normalized = stripMarkdownExtension(sourcePath);
|
|
143
|
+
const withoutReadme = trimReadmeSuffix(normalized);
|
|
144
|
+
const cleaned = withoutReadme.replace(/^\//, "").replace(/\/$/, "");
|
|
145
|
+
|
|
146
|
+
if (prettyUrls) {
|
|
147
|
+
if (cleaned.length === 0) {
|
|
148
|
+
return "index.html";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return `${cleaned}/index.html`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (cleaned.length === 0) {
|
|
155
|
+
return "index.html";
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return `${cleaned}.html`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function createEntryId(prefix: string, line: number): string {
|
|
162
|
+
return `${prefix}-${line}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function isExternalLink(href: string): boolean {
|
|
166
|
+
return EXTERNAL_LINK_PATTERN.test(href.trim());
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function isMarkdownHref(href: string): boolean {
|
|
170
|
+
const pathPart = href.split("#", 1)[0]?.split("?", 1)[0] ?? "";
|
|
171
|
+
return MARKDOWN_PATH_PATTERN.test(pathPart);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function flattenChapters(entries: SummaryEntry[]): SummaryChapterEntry[] {
|
|
175
|
+
const chapters: SummaryChapterEntry[] = [];
|
|
176
|
+
|
|
177
|
+
const visit = (entryList: SummaryEntry[]): void => {
|
|
178
|
+
for (const entry of entryList) {
|
|
179
|
+
if (entry.kind === "chapter") {
|
|
180
|
+
chapters.push(entry);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (entry.children.length > 0) {
|
|
184
|
+
visit(entry.children);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
visit(entries);
|
|
190
|
+
return chapters;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export async function loadSummaryGraph(config: ResolvedConfig): Promise<SummaryGraph> {
|
|
194
|
+
const summaryPath = resolve(config.srcDirAbsolute, "SUMMARY.md");
|
|
195
|
+
const summaryFile = Bun.file(summaryPath);
|
|
196
|
+
|
|
197
|
+
if (!(await summaryFile.exists())) {
|
|
198
|
+
throw new CliError(
|
|
199
|
+
`Could not find SUMMARY.md in source directory: ${config.srcDirAbsolute}`,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const summaryText = await summaryFile.text();
|
|
204
|
+
const items = parseSummaryItems(summaryText);
|
|
205
|
+
|
|
206
|
+
if (items.length === 0) {
|
|
207
|
+
throw new CliError(`SUMMARY.md does not include any chapter entries: ${summaryPath}`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const rootEntries: SummaryEntry[] = [];
|
|
211
|
+
const stack: EntryStackItem[] = [];
|
|
212
|
+
const entryById = new Map<string, SummaryEntry>();
|
|
213
|
+
const chapterBySourcePath = new Map<string, SummaryChapterEntry>();
|
|
214
|
+
|
|
215
|
+
for (const item of items) {
|
|
216
|
+
while (stack.length > 0) {
|
|
217
|
+
const top = stack[stack.length - 1];
|
|
218
|
+
if (top && top.depth >= item.depth) {
|
|
219
|
+
stack.pop();
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const parent = stack[stack.length - 1]?.entry;
|
|
227
|
+
if (!parent && item.depth > 0) {
|
|
228
|
+
throw new CliError(
|
|
229
|
+
`Invalid SUMMARY nesting at line ${item.line}: indentation starts before a parent entry.`,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (parent && item.depth > parent.depth + 1) {
|
|
234
|
+
throw new CliError(
|
|
235
|
+
`Invalid SUMMARY nesting at line ${item.line}: indentation jumps more than one level.`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let entry: SummaryEntry;
|
|
240
|
+
|
|
241
|
+
if (item.href === undefined) {
|
|
242
|
+
const sectionEntry: SummarySectionEntry = {
|
|
243
|
+
id: createEntryId("section", item.line),
|
|
244
|
+
kind: "section",
|
|
245
|
+
title: item.title,
|
|
246
|
+
depth: item.depth,
|
|
247
|
+
line: item.line,
|
|
248
|
+
parentId: parent?.id ?? null,
|
|
249
|
+
children: [],
|
|
250
|
+
};
|
|
251
|
+
entry = sectionEntry;
|
|
252
|
+
} else if (isExternalLink(item.href) || !isMarkdownHref(item.href)) {
|
|
253
|
+
const linkEntry: SummaryLinkEntry = {
|
|
254
|
+
id: createEntryId("link", item.line),
|
|
255
|
+
kind: "link",
|
|
256
|
+
title: item.title,
|
|
257
|
+
href: item.href,
|
|
258
|
+
external: isExternalLink(item.href),
|
|
259
|
+
depth: item.depth,
|
|
260
|
+
line: item.line,
|
|
261
|
+
parentId: parent?.id ?? null,
|
|
262
|
+
children: [],
|
|
263
|
+
};
|
|
264
|
+
entry = linkEntry;
|
|
265
|
+
} else {
|
|
266
|
+
const sourcePath = normalizeSourcePath(item.href, item.line);
|
|
267
|
+
const sourceAbsolutePath = resolve(config.srcDirAbsolute, sourcePath);
|
|
268
|
+
|
|
269
|
+
if (chapterBySourcePath.has(sourcePath)) {
|
|
270
|
+
const existing = chapterBySourcePath.get(sourcePath);
|
|
271
|
+
throw new CliError(
|
|
272
|
+
`Duplicate SUMMARY chapter path \`${sourcePath}\` at line ${item.line}. First defined at line ${existing?.line}.`,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const chapterEntry: SummaryChapterEntry = {
|
|
277
|
+
id: createEntryId("chapter", item.line),
|
|
278
|
+
kind: "chapter",
|
|
279
|
+
title: item.title,
|
|
280
|
+
href: item.href,
|
|
281
|
+
sourcePath,
|
|
282
|
+
sourceAbsolutePath,
|
|
283
|
+
routePath: toRoutePath(sourcePath, config.prettyUrls),
|
|
284
|
+
outputPath: toOutputPath(sourcePath, config.prettyUrls),
|
|
285
|
+
depth: item.depth,
|
|
286
|
+
line: item.line,
|
|
287
|
+
parentId: parent?.id ?? null,
|
|
288
|
+
children: [],
|
|
289
|
+
order: -1,
|
|
290
|
+
previousChapterId: null,
|
|
291
|
+
nextChapterId: null,
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
chapterBySourcePath.set(sourcePath, chapterEntry);
|
|
295
|
+
entry = chapterEntry;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (parent) {
|
|
299
|
+
parent.children.push(entry);
|
|
300
|
+
} else {
|
|
301
|
+
rootEntries.push(entry);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
entryById.set(entry.id, entry);
|
|
305
|
+
stack.push({ depth: item.depth, entry });
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const chapters = flattenChapters(rootEntries);
|
|
309
|
+
chapters.forEach((chapter, index) => {
|
|
310
|
+
const previous = chapters[index - 1];
|
|
311
|
+
const next = chapters[index + 1];
|
|
312
|
+
|
|
313
|
+
chapter.order = index;
|
|
314
|
+
chapter.previousChapterId = previous?.id ?? null;
|
|
315
|
+
chapter.nextChapterId = next?.id ?? null;
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
summaryPath,
|
|
320
|
+
entries: rootEntries,
|
|
321
|
+
chapters,
|
|
322
|
+
chapterBySourcePath,
|
|
323
|
+
entryById,
|
|
324
|
+
firstChapterId: chapters[0]?.id ?? null,
|
|
325
|
+
lastChapterId: chapters[chapters.length - 1]?.id ?? null,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type SummaryEntryKind = "section" | "chapter" | "link";
|
|
2
|
+
|
|
3
|
+
export interface SummaryBaseEntry {
|
|
4
|
+
id: string;
|
|
5
|
+
kind: SummaryEntryKind;
|
|
6
|
+
title: string;
|
|
7
|
+
depth: number;
|
|
8
|
+
line: number;
|
|
9
|
+
parentId: string | null;
|
|
10
|
+
children: SummaryEntry[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SummarySectionEntry extends SummaryBaseEntry {
|
|
14
|
+
kind: "section";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface SummaryLinkEntry extends SummaryBaseEntry {
|
|
18
|
+
kind: "link";
|
|
19
|
+
href: string;
|
|
20
|
+
external: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SummaryChapterEntry extends SummaryBaseEntry {
|
|
24
|
+
kind: "chapter";
|
|
25
|
+
href: string;
|
|
26
|
+
sourcePath: string;
|
|
27
|
+
sourceAbsolutePath: string;
|
|
28
|
+
routePath: string;
|
|
29
|
+
outputPath: string;
|
|
30
|
+
order: number;
|
|
31
|
+
previousChapterId: string | null;
|
|
32
|
+
nextChapterId: string | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type SummaryEntry =
|
|
36
|
+
| SummarySectionEntry
|
|
37
|
+
| SummaryLinkEntry
|
|
38
|
+
| SummaryChapterEntry;
|
|
39
|
+
|
|
40
|
+
export interface SummaryGraph {
|
|
41
|
+
summaryPath: string;
|
|
42
|
+
entries: SummaryEntry[];
|
|
43
|
+
chapters: SummaryChapterEntry[];
|
|
44
|
+
chapterBySourcePath: Map<string, SummaryChapterEntry>;
|
|
45
|
+
entryById: Map<string, SummaryEntry>;
|
|
46
|
+
firstChapterId: string | null;
|
|
47
|
+
lastChapterId: string | null;
|
|
48
|
+
}
|