@ascribed/astro 0.3.3-next.24 → 0.4.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/README.md +4 -4
- package/dist/content.d.ts +40 -13
- package/dist/content.js +112 -30
- package/dist/content.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +7 -10
- package/dist/init.js +12 -51
- package/dist/init.js.map +1 -1
- package/dist/nav.d.ts +88 -13
- package/dist/nav.js +144 -37
- package/dist/nav.js.map +1 -1
- package/package.json +4 -5
- package/starter/src/layouts/Docs.astro +68 -12
- package/starter/src/nav.ts +0 -13
package/dist/nav.d.ts
CHANGED
|
@@ -4,15 +4,61 @@ export interface NavGroup {
|
|
|
4
4
|
/** Source files, relative to the content root, as `Guides/My Setup.md`. */
|
|
5
5
|
pages: string[];
|
|
6
6
|
}
|
|
7
|
-
/** A
|
|
7
|
+
/** A label in the site output's formatted form: HTML, with code spans as `<code>`. */
|
|
8
|
+
export interface Formatted {
|
|
9
|
+
label: string;
|
|
10
|
+
}
|
|
11
|
+
/** A page of the sidebar: its collection entry, its address, and its label. */
|
|
8
12
|
export interface SidebarPage<E> {
|
|
9
13
|
entry: E;
|
|
10
14
|
href: string;
|
|
15
|
+
/** Its label as plain text: its `nav-label`, else its title. */
|
|
16
|
+
label: string;
|
|
17
|
+
/** Its label as HTML, when it's read with inline markup, as the title is. */
|
|
18
|
+
formatted?: Formatted;
|
|
11
19
|
}
|
|
12
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* A group of the sidebar: a section of the navigation, or a group of a
|
|
22
|
+
* site's own list. `pages` starts with the section's own page when that's
|
|
23
|
+
* its `index.md`; an own page beside the folder (`checks/diagnostics.md`
|
|
24
|
+
* for `checks/diagnostics/`) is among the parent's pages instead, and the
|
|
25
|
+
* nested group sits right after it.
|
|
26
|
+
*/
|
|
13
27
|
export interface SidebarGroup<E> {
|
|
14
28
|
label: string;
|
|
29
|
+
/** Its label as HTML, when it's its own page's title, read with inline markup. */
|
|
30
|
+
formatted?: Formatted;
|
|
31
|
+
/** The folder, from the content root, ending in `/`; absent for a group of a site's own list. */
|
|
32
|
+
folder?: string;
|
|
33
|
+
/** Its own page, when the build offers one. */
|
|
34
|
+
page?: SidebarPage<E>;
|
|
15
35
|
pages: SidebarPage<E>[];
|
|
36
|
+
/** Its nested sections, in order; empty for a group of a site's own list. */
|
|
37
|
+
sections: SidebarGroup<E>[];
|
|
38
|
+
}
|
|
39
|
+
/** A page in `_ascribe/nav.json`. */
|
|
40
|
+
export interface NavPage {
|
|
41
|
+
/** Its path from the content root, as its record's `source`. */
|
|
42
|
+
source: string;
|
|
43
|
+
label: string;
|
|
44
|
+
formatted?: Formatted;
|
|
45
|
+
/** Its content type. */
|
|
46
|
+
type?: string;
|
|
47
|
+
}
|
|
48
|
+
/** A section in `_ascribe/nav.json`. */
|
|
49
|
+
export interface NavSection {
|
|
50
|
+
/** From the content root, ending in `/`; `""` for the root. */
|
|
51
|
+
folder: string;
|
|
52
|
+
label: string;
|
|
53
|
+
formatted?: Formatted;
|
|
54
|
+
page?: NavPage;
|
|
55
|
+
pages: NavPage[];
|
|
56
|
+
sections: NavSection[];
|
|
57
|
+
}
|
|
58
|
+
/** `_ascribe/nav.json`. */
|
|
59
|
+
export interface Navigation {
|
|
60
|
+
version: number;
|
|
61
|
+
root: NavSection;
|
|
16
62
|
}
|
|
17
63
|
/** What the check needs of a collection entry: its id, and the file it was loaded from. */
|
|
18
64
|
export interface NavEntry {
|
|
@@ -30,6 +76,41 @@ export declare function entryId(pagePath: string): string;
|
|
|
30
76
|
* plus its entry id, and the root page (`index`) at the base path itself.
|
|
31
77
|
*/
|
|
32
78
|
export declare function pageRoute(id: string, basePath: string, trailingSlash: "always" | "never"): string;
|
|
79
|
+
/**
|
|
80
|
+
* The sidebar's groups from the project's navigation: the root's own page
|
|
81
|
+
* and pages under its label, "Start" unless `ascribe.nav` gives another,
|
|
82
|
+
* when it has any, then each top section, each page with its entry and
|
|
83
|
+
* address. `source` gives an entry's page's source path, which the
|
|
84
|
+
* navigation names it by. Throws, naming each, when the navigation names a
|
|
85
|
+
* page the collection has no entry for.
|
|
86
|
+
*/
|
|
87
|
+
export declare function navigationGroups<E extends NavEntry>(navigation: Navigation, entries: readonly E[], route: (entry: E) => string, source: (entry: E) => string): SidebarGroup<E>[];
|
|
88
|
+
/**
|
|
89
|
+
* The navigation's pages in reading order, depth first: a section's own
|
|
90
|
+
* page, then its pages, each nested section whose own page is beside its
|
|
91
|
+
* folder right after that page, with its pages and nested sections, and the
|
|
92
|
+
* other nested sections after them, each from its own page. The content
|
|
93
|
+
* root reads as the sidebar groups it: its own page and its pages that
|
|
94
|
+
* aren't beside a folder, then each top section from its own page. Previous
|
|
95
|
+
* and next follow it, as an archive and `llms.txt` do.
|
|
96
|
+
*/
|
|
97
|
+
export declare function readingOrder(navigation: Navigation): NavPage[];
|
|
98
|
+
/** A page's previous and next, each with its entry, its address, and its label. */
|
|
99
|
+
export interface Neighbors<E> {
|
|
100
|
+
previous?: SidebarPage<E>;
|
|
101
|
+
next?: SidebarPage<E>;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The pages before and after `at` in `pages`, the order a reader goes in.
|
|
105
|
+
* Neither, for a page that isn't in it.
|
|
106
|
+
*/
|
|
107
|
+
export declare function neighborsIn<E>(pages: readonly SidebarPage<E>[], at: (page: SidebarPage<E>) => boolean): Neighbors<E>;
|
|
108
|
+
/**
|
|
109
|
+
* The navigation's pages in reading order, each with its entry, its
|
|
110
|
+
* address, and its label. `source` gives an entry's page's source path.
|
|
111
|
+
* A page the collection has no entry for is passed over.
|
|
112
|
+
*/
|
|
113
|
+
export declare function readingPages<E extends NavEntry>(navigation: Navigation, entries: readonly E[], route: (entry: E) => string, source: (entry: E) => string): SidebarPage<E>[];
|
|
33
114
|
/**
|
|
34
115
|
* The ways the list and the collection disagree, as sentences: a page the
|
|
35
116
|
* list leaves out, a file it names that isn't a page, and a page it names twice.
|
|
@@ -37,9 +118,10 @@ export declare function pageRoute(id: string, basePath: string, trailingSlash: "
|
|
|
37
118
|
*/
|
|
38
119
|
export declare function navProblems(nav: readonly NavGroup[], entries: readonly NavEntry[], source?: (entry: NavEntry) => string): string[];
|
|
39
120
|
/**
|
|
40
|
-
* The sidebar's groups, each page with its entry
|
|
41
|
-
*
|
|
42
|
-
*
|
|
121
|
+
* The sidebar's groups from a site's own list, each page with its entry, its
|
|
122
|
+
* address, and its label, which `label` gives, or else its entry id. Throws,
|
|
123
|
+
* naming each, when the list leaves out a page of the collection, names a
|
|
124
|
+
* file that isn't one, or names a page twice, so the list stays complete.
|
|
43
125
|
*
|
|
44
126
|
* A versioned build's collection has every line's pages, and the list is
|
|
45
127
|
* checked against them all: `id` gives an entry's id within its line, and
|
|
@@ -49,11 +131,4 @@ export declare function navProblems(nav: readonly NavGroup[], entries: readonly
|
|
|
49
131
|
export declare function sidebarGroups<E extends NavEntry>(nav: readonly NavGroup[], entries: readonly E[], route: (id: string) => string, source?: (entry: NavEntry) => string, lines?: {
|
|
50
132
|
id: (entry: E) => string;
|
|
51
133
|
shown: (entry: E) => boolean;
|
|
52
|
-
}): SidebarGroup<E>[];
|
|
53
|
-
/**
|
|
54
|
-
* The starter's list for a project's pages, by their paths in the site
|
|
55
|
-
* output: the pages at the content root in a group named "Start", the root
|
|
56
|
-
* page first, then a group for each folder, named as the folder is, in order
|
|
57
|
-
* of their names.
|
|
58
|
-
*/
|
|
59
|
-
export declare function navFor(pages: readonly string[]): NavGroup[];
|
|
134
|
+
}, label?: (entry: E) => string | undefined): SidebarGroup<E>[];
|
package/dist/nav.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
// A site's sidebar:
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// the list, and the check keeps it complete: `ascribe.toml` giving a site its
|
|
5
|
-
// navigation is the surface audit's finding 28, which has no design yet.
|
|
1
|
+
// A site's sidebar: the project's navigation, `_ascribe/nav.json` in a
|
|
2
|
+
// build's site output (the navigation contract), joined to the collection;
|
|
3
|
+
// or a site's own list of pages, `{ nav }`, checked against the collection.
|
|
6
4
|
import { slug } from "github-slugger";
|
|
7
5
|
/**
|
|
8
6
|
* A page's entry id, as Astro's `glob` loader computes it from the page's
|
|
@@ -26,6 +24,138 @@ export function pageRoute(id, basePath, trailingSlash) {
|
|
|
26
24
|
return trailingSlash === "never" && basePath !== "/" ? basePath.slice(0, -1) : basePath;
|
|
27
25
|
return `${basePath}${id}${trailingSlash === "always" ? "/" : ""}`;
|
|
28
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* The sidebar's groups from the project's navigation: the root's own page
|
|
29
|
+
* and pages under its label, "Start" unless `ascribe.nav` gives another,
|
|
30
|
+
* when it has any, then each top section, each page with its entry and
|
|
31
|
+
* address. `source` gives an entry's page's source path, which the
|
|
32
|
+
* navigation names it by. Throws, naming each, when the navigation names a
|
|
33
|
+
* page the collection has no entry for.
|
|
34
|
+
*/
|
|
35
|
+
export function navigationGroups(navigation, entries, route, source) {
|
|
36
|
+
const bySource = new Map(entries.map((entry) => [source(entry), entry]));
|
|
37
|
+
const missing = [];
|
|
38
|
+
const page = (nav) => {
|
|
39
|
+
const entry = bySource.get(nav.source);
|
|
40
|
+
if (entry === undefined) {
|
|
41
|
+
missing.push(nav.source);
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
return [
|
|
45
|
+
{
|
|
46
|
+
entry,
|
|
47
|
+
href: route(entry),
|
|
48
|
+
label: nav.label,
|
|
49
|
+
...(nav.formatted === undefined ? {} : { formatted: nav.formatted }),
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
};
|
|
53
|
+
const group = (section, parent) => {
|
|
54
|
+
const own = section.page === undefined ? [] : page(section.page);
|
|
55
|
+
// An own page beside the folder is one of the parent's pages.
|
|
56
|
+
const beside = section.page !== undefined &&
|
|
57
|
+
parent?.pages.some((other) => other.source === section.page?.source) === true;
|
|
58
|
+
return {
|
|
59
|
+
label: section.label,
|
|
60
|
+
...(section.formatted === undefined ? {} : { formatted: section.formatted }),
|
|
61
|
+
folder: section.folder,
|
|
62
|
+
...(own[0] === undefined ? {} : { page: own[0] }),
|
|
63
|
+
pages: [...(beside ? [] : own), ...section.pages.flatMap(page)],
|
|
64
|
+
sections: section.sections.map((nested) => group(nested, section)),
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
const { root } = navigation;
|
|
68
|
+
// A top section's own page beside its folder is in that section's group.
|
|
69
|
+
const top = {
|
|
70
|
+
...group({ ...root, pages: root.pages.filter((nav) => !beside(root, nav)) }, undefined),
|
|
71
|
+
sections: [],
|
|
72
|
+
};
|
|
73
|
+
const groups = [
|
|
74
|
+
...(top.pages.length === 0 ? [] : [top]),
|
|
75
|
+
...root.sections.map((section) => group(section, undefined)),
|
|
76
|
+
];
|
|
77
|
+
if (missing.length > 0) {
|
|
78
|
+
throw new Error(`@ascribed/astro: the project's navigation (_ascribe/nav.json) names pages the collection has no entry for:\n${[...new Set(missing)].map((file) => `- ${file}`).join("\n")}\nBuild the project again (\`ascribe build\`), so the navigation and the pages are from one build.`);
|
|
79
|
+
}
|
|
80
|
+
return groups;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The navigation's pages in reading order, depth first: a section's own
|
|
84
|
+
* page, then its pages, each nested section whose own page is beside its
|
|
85
|
+
* folder right after that page, with its pages and nested sections, and the
|
|
86
|
+
* other nested sections after them, each from its own page. The content
|
|
87
|
+
* root reads as the sidebar groups it: its own page and its pages that
|
|
88
|
+
* aren't beside a folder, then each top section from its own page. Previous
|
|
89
|
+
* and next follow it, as an archive and `llms.txt` do.
|
|
90
|
+
*/
|
|
91
|
+
export function readingOrder(navigation) {
|
|
92
|
+
const out = [];
|
|
93
|
+
const read = (section, own) => {
|
|
94
|
+
if (own && section.page !== undefined)
|
|
95
|
+
out.push(section.page);
|
|
96
|
+
const done = section.sections.map(() => false);
|
|
97
|
+
for (const page of section.pages) {
|
|
98
|
+
out.push(page);
|
|
99
|
+
section.sections.forEach((nested, at) => {
|
|
100
|
+
if (!done[at] && nested.page?.source === page.source) {
|
|
101
|
+
done[at] = true;
|
|
102
|
+
read(nested, false);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
section.sections.forEach((nested, at) => {
|
|
107
|
+
if (!done[at])
|
|
108
|
+
read(nested, true);
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
const { root } = navigation;
|
|
112
|
+
if (root.page !== undefined)
|
|
113
|
+
out.push(root.page);
|
|
114
|
+
out.push(...root.pages.filter((nav) => !beside(root, nav)));
|
|
115
|
+
for (const section of root.sections)
|
|
116
|
+
read(section, true);
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
/** Whether `page` is the own page of one of `section`'s nested sections. */
|
|
120
|
+
function beside(section, page) {
|
|
121
|
+
return section.sections.some((nested) => nested.page?.source === page.source);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The pages before and after `at` in `pages`, the order a reader goes in.
|
|
125
|
+
* Neither, for a page that isn't in it.
|
|
126
|
+
*/
|
|
127
|
+
export function neighborsIn(pages, at) {
|
|
128
|
+
const i = pages.findIndex(at);
|
|
129
|
+
if (i === -1)
|
|
130
|
+
return {};
|
|
131
|
+
const previous = pages[i - 1];
|
|
132
|
+
const next = pages[i + 1];
|
|
133
|
+
return {
|
|
134
|
+
...(previous === undefined ? {} : { previous }),
|
|
135
|
+
...(next === undefined ? {} : { next }),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* The navigation's pages in reading order, each with its entry, its
|
|
140
|
+
* address, and its label. `source` gives an entry's page's source path.
|
|
141
|
+
* A page the collection has no entry for is passed over.
|
|
142
|
+
*/
|
|
143
|
+
export function readingPages(navigation, entries, route, source) {
|
|
144
|
+
const bySource = new Map(entries.map((entry) => [source(entry), entry]));
|
|
145
|
+
return readingOrder(navigation).flatMap((nav) => {
|
|
146
|
+
const entry = bySource.get(nav.source);
|
|
147
|
+
if (entry === undefined)
|
|
148
|
+
return [];
|
|
149
|
+
return [
|
|
150
|
+
{
|
|
151
|
+
entry,
|
|
152
|
+
href: route(entry),
|
|
153
|
+
label: nav.label,
|
|
154
|
+
...(nav.formatted === undefined ? {} : { formatted: nav.formatted }),
|
|
155
|
+
},
|
|
156
|
+
];
|
|
157
|
+
});
|
|
158
|
+
}
|
|
29
159
|
/**
|
|
30
160
|
* The ways the list and the collection disagree, as sentences: a page the
|
|
31
161
|
* list leaves out, a file it names that isn't a page, and a page it names twice.
|
|
@@ -49,9 +179,10 @@ export function navProblems(nav, entries, source = (entry) => entry.id) {
|
|
|
49
179
|
return problems;
|
|
50
180
|
}
|
|
51
181
|
/**
|
|
52
|
-
* The sidebar's groups, each page with its entry
|
|
53
|
-
*
|
|
54
|
-
*
|
|
182
|
+
* The sidebar's groups from a site's own list, each page with its entry, its
|
|
183
|
+
* address, and its label, which `label` gives, or else its entry id. Throws,
|
|
184
|
+
* naming each, when the list leaves out a page of the collection, names a
|
|
185
|
+
* file that isn't one, or names a page twice, so the list stays complete.
|
|
55
186
|
*
|
|
56
187
|
* A versioned build's collection has every line's pages, and the list is
|
|
57
188
|
* checked against them all: `id` gives an entry's id within its line, and
|
|
@@ -61,7 +192,7 @@ export function navProblems(nav, entries, source = (entry) => entry.id) {
|
|
|
61
192
|
export function sidebarGroups(nav, entries, route, source, lines = {
|
|
62
193
|
id: (entry) => entry.id,
|
|
63
194
|
shown: () => true,
|
|
64
|
-
}) {
|
|
195
|
+
}, label) {
|
|
65
196
|
// A page in several lines is one page of the list, and one problem.
|
|
66
197
|
const checked = new Map(entries.map((entry) => [{ id: lines.id(entry), filePath: entry.filePath }, entry]));
|
|
67
198
|
const problems = navProblems(nav, [...checked.keys()], source === undefined ? undefined : (entry) => source(checked.get(entry) ?? entry)).filter((problem, i, all) => all.indexOf(problem) === i);
|
|
@@ -73,35 +204,11 @@ export function sidebarGroups(nav, entries, route, source, lines = {
|
|
|
73
204
|
label: group.label,
|
|
74
205
|
pages: group.pages.flatMap((file) => {
|
|
75
206
|
const entry = byId.get(entryId(file));
|
|
76
|
-
return entry === undefined
|
|
207
|
+
return entry === undefined
|
|
208
|
+
? []
|
|
209
|
+
: [{ entry, href: route(entry.id), label: label?.(entry) ?? entry.id }];
|
|
77
210
|
}),
|
|
211
|
+
sections: [],
|
|
78
212
|
}));
|
|
79
213
|
}
|
|
80
|
-
/**
|
|
81
|
-
* The starter's list for a project's pages, by their paths in the site
|
|
82
|
-
* output: the pages at the content root in a group named "Start", the root
|
|
83
|
-
* page first, then a group for each folder, named as the folder is, in order
|
|
84
|
-
* of their names.
|
|
85
|
-
*/
|
|
86
|
-
export function navFor(pages) {
|
|
87
|
-
const groups = new Map();
|
|
88
|
-
const sorted = pages.toSorted((a, b) => {
|
|
89
|
-
const rootA = a === "index.md";
|
|
90
|
-
const rootB = b === "index.md";
|
|
91
|
-
if (rootA !== rootB)
|
|
92
|
-
return rootA ? -1 : 1;
|
|
93
|
-
return a < b ? -1 : a > b ? 1 : 0;
|
|
94
|
-
});
|
|
95
|
-
for (const page of sorted) {
|
|
96
|
-
const slash = page.indexOf("/");
|
|
97
|
-
const label = slash === -1 ? "Start" : page.slice(0, slash);
|
|
98
|
-
groups.set(label, [...(groups.get(label) ?? []), page]);
|
|
99
|
-
}
|
|
100
|
-
const start = groups.get("Start");
|
|
101
|
-
groups.delete("Start");
|
|
102
|
-
return [
|
|
103
|
-
...(start === undefined ? [] : [{ label: "Start", pages: start }]),
|
|
104
|
-
...[...groups].map(([label, files]) => ({ label, pages: files })),
|
|
105
|
-
];
|
|
106
|
-
}
|
|
107
214
|
//# sourceMappingURL=nav.js.map
|
package/dist/nav.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nav.js","sourceRoot":"","sources":["../src/nav.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,6EAA6E;AAC7E,+EAA+E;AAC/E,8EAA8E;AAC9E,yEAAyE;AACzE,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AA2BtC;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,QAAgB;IACtC,OAAO,QAAQ;SACZ,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;SACpB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;SAC/B,IAAI,CAAC,GAAG,CAAC;SACT,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,EAAU,EAAE,QAAgB,EAAE,aAAiC;IACvF,IAAI,EAAE,KAAK,OAAO;QAChB,OAAO,aAAa,KAAK,OAAO,IAAI,QAAQ,KAAK,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC1F,OAAO,GAAG,QAAQ,GAAG,EAAE,GAAG,aAAa,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;AACpE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CACzB,GAAwB,EACxB,OAA4B,EAC5B,MAAM,GAAgC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE;IAEzD,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACnD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IACtD,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,QAAQ,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACtF,CAAC;IACD,KAAK,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;QACzC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACzB,QAAQ,CAAC,IAAI,CAAC,4BAA4B,IAAI,mCAAmC,CAAC,CAAC;aAChF,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YAC1E,QAAQ,CAAC,IAAI,CAAC,4BAA4B,IAAI,SAAS,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAC3B,GAAwB,EACxB,OAAqB,EACrB,KAA6B,EAC7B,MAAoC,EACpC,KAAK,GAA+D;IAClE,EAAE,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE;IACvB,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI;CAClB;IAED,oEAAoE;IACpE,MAAM,OAAO,GAAG,IAAI,GAAG,CACrB,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC,CAAC,CACnF,CAAC;IACF,MAAM,QAAQ,GAAG,WAAW,CAC1B,GAAG,EACH,CAAC,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,EACnB,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,CAClF,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IAC1D,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,qFAAqF,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC5I,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,GAAG,CAClB,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,CACvF,CAAC;IACF,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACzB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;YAClC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;YACtC,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACvE,CAAC,CAAC;KACH,CAAC,CAAC,CAAC;AACN,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,MAAM,CAAC,KAAwB;IAC7C,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC3C,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,CAAC,KAAK,UAAU,CAAC;QAC/B,MAAM,KAAK,GAAG,CAAC,KAAK,UAAU,CAAC;QAC/B,IAAI,KAAK,KAAK,KAAK;YAAE,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3C,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IACH,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;QAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAChC,MAAM,KAAK,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QAC5D,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;IAC1D,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAClC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACvB,OAAO;QACL,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QAClE,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;KAClE,CAAC;AACJ,CAAC","sourcesContent":["// A site's sidebar: its list of pages, which the starter writes as the site's\n// `src/nav.ts` (`npx @ascribed/astro init`), checked against the collection.\n// Ascribe's content model has no page order or grouping yet, so the site keeps\n// the list, and the check keeps it complete: `ascribe.toml` giving a site its\n// navigation is the surface audit's finding 28, which has no design yet.\nimport { slug } from \"github-slugger\";\n\n/** A group of the sidebar: a label, and its pages' source files, in order. */\nexport interface NavGroup {\n label: string;\n /** Source files, relative to the content root, as `Guides/My Setup.md`. */\n pages: string[];\n}\n\n/** A page of the sidebar: its collection entry, and its address. */\nexport interface SidebarPage<E> {\n entry: E;\n href: string;\n}\n\n/** A group of the sidebar, each of its pages with its entry. */\nexport interface SidebarGroup<E> {\n label: string;\n pages: SidebarPage<E>[];\n}\n\n/** What the check needs of a collection entry: its id, and the file it was loaded from. */\nexport interface NavEntry {\n id: string;\n filePath?: string | undefined;\n}\n\n/**\n * A page's entry id, as Astro's `glob` loader computes it from the page's\n * path in the site output: each segment slugged, without the extension, and\n * without a final `/index`.\n */\nexport function entryId(pagePath: string): string {\n return pagePath\n .replace(/\\.md$/, \"\")\n .split(\"/\")\n .map((segment) => slug(segment))\n .join(\"/\")\n .replace(/\\/index$/, \"\");\n}\n\n/**\n * A page's route, as Ascribe's router writes every link to it: the base path\n * plus its entry id, and the root page (`index`) at the base path itself.\n */\nexport function pageRoute(id: string, basePath: string, trailingSlash: \"always\" | \"never\"): string {\n if (id === \"index\")\n return trailingSlash === \"never\" && basePath !== \"/\" ? basePath.slice(0, -1) : basePath;\n return `${basePath}${id}${trailingSlash === \"always\" ? \"/\" : \"\"}`;\n}\n\n/**\n * The ways the list and the collection disagree, as sentences: a page the\n * list leaves out, a file it names that isn't a page, and a page it names twice.\n * `source` names an entry for a person, by its source file where it can.\n */\nexport function navProblems(\n nav: readonly NavGroup[],\n entries: readonly NavEntry[],\n source: (entry: NavEntry) => string = (entry) => entry.id,\n): string[] {\n const listed = nav.flatMap((group) => group.pages);\n const ids = new Set(entries.map((entry) => entry.id));\n const listedIds = new Set(listed.map(entryId));\n const problems: string[] = [];\n for (const entry of entries) {\n if (!listedIds.has(entry.id))\n problems.push(`${source(entry)} isn't in the sidebar's list. Add it to a group.`);\n }\n for (const [i, file] of listed.entries()) {\n if (!ids.has(entryId(file)))\n problems.push(`the sidebar's list names ${file}, which isn't a page of the site.`);\n else if (listed.findIndex((other) => entryId(other) === entryId(file)) !== i)\n problems.push(`the sidebar's list names ${file} twice.`);\n }\n return problems;\n}\n\n/**\n * The sidebar's groups, each page with its entry and address. Throws, naming\n * each, when the list leaves out a page of the collection, names a file that\n * isn't one, or names a page twice, so the list stays complete.\n *\n * A versioned build's collection has every line's pages, and the list is\n * checked against them all: `id` gives an entry's id within its line, and\n * the groups have the pages `shown` keeps, one line's, each listed page that\n * line has.\n */\nexport function sidebarGroups<E extends NavEntry>(\n nav: readonly NavGroup[],\n entries: readonly E[],\n route: (id: string) => string,\n source?: (entry: NavEntry) => string,\n lines: { id: (entry: E) => string; shown: (entry: E) => boolean } = {\n id: (entry) => entry.id,\n shown: () => true,\n },\n): SidebarGroup<E>[] {\n // A page in several lines is one page of the list, and one problem.\n const checked = new Map<NavEntry, E>(\n entries.map((entry) => [{ id: lines.id(entry), filePath: entry.filePath }, entry]),\n );\n const problems = navProblems(\n nav,\n [...checked.keys()],\n source === undefined ? undefined : (entry) => source(checked.get(entry) ?? entry),\n ).filter((problem, i, all) => all.indexOf(problem) === i);\n if (problems.length > 0) {\n throw new Error(\n `@ascribed/astro: the sidebar's list (src/nav.ts) doesn't match the site's pages:\\n${problems.map((problem) => `- ${problem}`).join(\"\\n\")}`,\n );\n }\n const byId = new Map(\n entries.filter((entry) => lines.shown(entry)).map((entry) => [lines.id(entry), entry]),\n );\n return nav.map((group) => ({\n label: group.label,\n pages: group.pages.flatMap((file) => {\n const entry = byId.get(entryId(file));\n return entry === undefined ? [] : [{ entry, href: route(entry.id) }];\n }),\n }));\n}\n\n/**\n * The starter's list for a project's pages, by their paths in the site\n * output: the pages at the content root in a group named \"Start\", the root\n * page first, then a group for each folder, named as the folder is, in order\n * of their names.\n */\nexport function navFor(pages: readonly string[]): NavGroup[] {\n const groups = new Map<string, string[]>();\n const sorted = pages.toSorted((a, b) => {\n const rootA = a === \"index.md\";\n const rootB = b === \"index.md\";\n if (rootA !== rootB) return rootA ? -1 : 1;\n return a < b ? -1 : a > b ? 1 : 0;\n });\n for (const page of sorted) {\n const slash = page.indexOf(\"/\");\n const label = slash === -1 ? \"Start\" : page.slice(0, slash);\n groups.set(label, [...(groups.get(label) ?? []), page]);\n }\n const start = groups.get(\"Start\");\n groups.delete(\"Start\");\n return [\n ...(start === undefined ? [] : [{ label: \"Start\", pages: start }]),\n ...[...groups].map(([label, files]) => ({ label, pages: files })),\n ];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"nav.js","sourceRoot":"","sources":["../src/nav.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,2EAA2E;AAC3E,4EAA4E;AAC5E,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AA6EtC;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,QAAgB;IACtC,OAAO,QAAQ;SACZ,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;SACpB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;SAC/B,IAAI,CAAC,GAAG,CAAC;SACT,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,EAAU,EAAE,QAAgB,EAAE,aAAiC;IACvF,IAAI,EAAE,KAAK,OAAO;QAChB,OAAO,aAAa,KAAK,OAAO,IAAI,QAAQ,KAAK,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC1F,OAAO,GAAG,QAAQ,GAAG,EAAE,GAAG,aAAa,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;AACpE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAC9B,UAAsB,EACtB,OAAqB,EACrB,KAA2B,EAC3B,MAA4B;IAE5B,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IACzE,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,IAAI,GAAG,CAAC,GAAY,EAAoB,EAAE;QAC9C,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACzB,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,OAAO;YACL;gBACE,KAAK;gBACL,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC;gBAClB,KAAK,EAAE,GAAG,CAAC,KAAK;gBAChB,GAAG,CAAC,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC;aACrE;SACF,CAAC;IACJ,CAAC,CAAC;IACF,MAAM,KAAK,GAAG,CAAC,OAAmB,EAAE,MAA8B,EAAmB,EAAE;QACrF,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACjE,8DAA8D;QAC9D,MAAM,MAAM,GACV,OAAO,CAAC,IAAI,KAAK,SAAS;YAC1B,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;QAChF,OAAO;YACL,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,GAAG,CAAC,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC;YAC5E,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACjD,KAAK,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC/D,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;SACnE,CAAC;IACJ,CAAC,CAAC;IACF,MAAM,EAAE,IAAI,EAAE,GAAG,UAAU,CAAC;IAC5B,yEAAyE;IACzE,MAAM,GAAG,GAAG;QACV,GAAG,KAAK,CAAC,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC;QACvF,QAAQ,EAAE,EAAE;KACb,CAAC;IACF,MAAM,MAAM,GAAG;QACb,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACxC,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;KAC7D,CAAC;IACF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACb,+GAA+G,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,oGAAoG,CAC/Q,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,UAAsB;IACjD,MAAM,GAAG,GAAc,EAAE,CAAC;IAC1B,MAAM,IAAI,GAAG,CAAC,OAAmB,EAAE,GAAY,EAAE,EAAE;QACjD,IAAI,GAAG,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS;YAAE,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC9D,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;QAC/C,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YACjC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACf,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE;gBACtC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;oBACrD,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;oBAChB,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;gBACtB,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE;YACtC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IACF,MAAM,EAAE,IAAI,EAAE,GAAG,UAAU,CAAC;IAC5B,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;QAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjD,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IAC5D,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ;QAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACzD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,4EAA4E;AAC5E,SAAS,MAAM,CAAC,OAAmB,EAAE,IAAa;IAChD,OAAO,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC,MAAM,CAAC,CAAC;AAChF,CAAC;AAQD;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,KAAgC,EAChC,EAAqC;IAErC,MAAM,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAC9B,IAAI,CAAC,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IACxB,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9B,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1B,OAAO;QACL,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC;QAC/C,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;KACxC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAC1B,UAAsB,EACtB,OAAqB,EACrB,KAA2B,EAC3B,MAA4B;IAE5B,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IACzE,OAAO,YAAY,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;QAC9C,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,EAAE,CAAC;QACnC,OAAO;YACL;gBACE,KAAK;gBACL,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC;gBAClB,KAAK,EAAE,GAAG,CAAC,KAAK;gBAChB,GAAG,CAAC,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC;aACrE;SACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CACzB,GAAwB,EACxB,OAA4B,EAC5B,MAAM,GAAgC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE;IAEzD,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACnD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IACtD,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,QAAQ,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACtF,CAAC;IACD,KAAK,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;QACzC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACzB,QAAQ,CAAC,IAAI,CAAC,4BAA4B,IAAI,mCAAmC,CAAC,CAAC;aAChF,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YAC1E,QAAQ,CAAC,IAAI,CAAC,4BAA4B,IAAI,SAAS,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAC3B,GAAwB,EACxB,OAAqB,EACrB,KAA6B,EAC7B,MAAoC,EACpC,KAAK,GAA+D;IAClE,EAAE,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE;IACvB,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI;CAClB,EACD,KAAwC;IAExC,oEAAoE;IACpE,MAAM,OAAO,GAAG,IAAI,GAAG,CACrB,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC,CAAC,CACnF,CAAC;IACF,MAAM,QAAQ,GAAG,WAAW,CAC1B,GAAG,EACH,CAAC,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,EACnB,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,CAClF,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IAC1D,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,qFAAqF,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC5I,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,GAAG,CAClB,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,CACvF,CAAC;IACF,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACzB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;YAClC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;YACtC,OAAO,KAAK,KAAK,SAAS;gBACxB,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5E,CAAC,CAAC;QACF,QAAQ,EAAE,EAAE;KACb,CAAC,CAAC,CAAC;AACN,CAAC","sourcesContent":["// A site's sidebar: the project's navigation, `_ascribe/nav.json` in a\n// build's site output (the navigation contract), joined to the collection;\n// or a site's own list of pages, `{ nav }`, checked against the collection.\nimport { slug } from \"github-slugger\";\n\n/** A group of the sidebar: a label, and its pages' source files, in order. */\nexport interface NavGroup {\n label: string;\n /** Source files, relative to the content root, as `Guides/My Setup.md`. */\n pages: string[];\n}\n\n/** A label in the site output's formatted form: HTML, with code spans as `<code>`. */\nexport interface Formatted {\n label: string;\n}\n\n/** A page of the sidebar: its collection entry, its address, and its label. */\nexport interface SidebarPage<E> {\n entry: E;\n href: string;\n /** Its label as plain text: its `nav-label`, else its title. */\n label: string;\n /** Its label as HTML, when it's read with inline markup, as the title is. */\n formatted?: Formatted;\n}\n\n/**\n * A group of the sidebar: a section of the navigation, or a group of a\n * site's own list. `pages` starts with the section's own page when that's\n * its `index.md`; an own page beside the folder (`checks/diagnostics.md`\n * for `checks/diagnostics/`) is among the parent's pages instead, and the\n * nested group sits right after it.\n */\nexport interface SidebarGroup<E> {\n label: string;\n /** Its label as HTML, when it's its own page's title, read with inline markup. */\n formatted?: Formatted;\n /** The folder, from the content root, ending in `/`; absent for a group of a site's own list. */\n folder?: string;\n /** Its own page, when the build offers one. */\n page?: SidebarPage<E>;\n pages: SidebarPage<E>[];\n /** Its nested sections, in order; empty for a group of a site's own list. */\n sections: SidebarGroup<E>[];\n}\n\n/** A page in `_ascribe/nav.json`. */\nexport interface NavPage {\n /** Its path from the content root, as its record's `source`. */\n source: string;\n label: string;\n formatted?: Formatted;\n /** Its content type. */\n type?: string;\n}\n\n/** A section in `_ascribe/nav.json`. */\nexport interface NavSection {\n /** From the content root, ending in `/`; `\"\"` for the root. */\n folder: string;\n label: string;\n formatted?: Formatted;\n page?: NavPage;\n pages: NavPage[];\n sections: NavSection[];\n}\n\n/** `_ascribe/nav.json`. */\nexport interface Navigation {\n version: number;\n root: NavSection;\n}\n\n/** What the check needs of a collection entry: its id, and the file it was loaded from. */\nexport interface NavEntry {\n id: string;\n filePath?: string | undefined;\n}\n\n/**\n * A page's entry id, as Astro's `glob` loader computes it from the page's\n * path in the site output: each segment slugged, without the extension, and\n * without a final `/index`.\n */\nexport function entryId(pagePath: string): string {\n return pagePath\n .replace(/\\.md$/, \"\")\n .split(\"/\")\n .map((segment) => slug(segment))\n .join(\"/\")\n .replace(/\\/index$/, \"\");\n}\n\n/**\n * A page's route, as Ascribe's router writes every link to it: the base path\n * plus its entry id, and the root page (`index`) at the base path itself.\n */\nexport function pageRoute(id: string, basePath: string, trailingSlash: \"always\" | \"never\"): string {\n if (id === \"index\")\n return trailingSlash === \"never\" && basePath !== \"/\" ? basePath.slice(0, -1) : basePath;\n return `${basePath}${id}${trailingSlash === \"always\" ? \"/\" : \"\"}`;\n}\n\n/**\n * The sidebar's groups from the project's navigation: the root's own page\n * and pages under its label, \"Start\" unless `ascribe.nav` gives another,\n * when it has any, then each top section, each page with its entry and\n * address. `source` gives an entry's page's source path, which the\n * navigation names it by. Throws, naming each, when the navigation names a\n * page the collection has no entry for.\n */\nexport function navigationGroups<E extends NavEntry>(\n navigation: Navigation,\n entries: readonly E[],\n route: (entry: E) => string,\n source: (entry: E) => string,\n): SidebarGroup<E>[] {\n const bySource = new Map(entries.map((entry) => [source(entry), entry]));\n const missing: string[] = [];\n const page = (nav: NavPage): SidebarPage<E>[] => {\n const entry = bySource.get(nav.source);\n if (entry === undefined) {\n missing.push(nav.source);\n return [];\n }\n return [\n {\n entry,\n href: route(entry),\n label: nav.label,\n ...(nav.formatted === undefined ? {} : { formatted: nav.formatted }),\n },\n ];\n };\n const group = (section: NavSection, parent: NavSection | undefined): SidebarGroup<E> => {\n const own = section.page === undefined ? [] : page(section.page);\n // An own page beside the folder is one of the parent's pages.\n const beside =\n section.page !== undefined &&\n parent?.pages.some((other) => other.source === section.page?.source) === true;\n return {\n label: section.label,\n ...(section.formatted === undefined ? {} : { formatted: section.formatted }),\n folder: section.folder,\n ...(own[0] === undefined ? {} : { page: own[0] }),\n pages: [...(beside ? [] : own), ...section.pages.flatMap(page)],\n sections: section.sections.map((nested) => group(nested, section)),\n };\n };\n const { root } = navigation;\n // A top section's own page beside its folder is in that section's group.\n const top = {\n ...group({ ...root, pages: root.pages.filter((nav) => !beside(root, nav)) }, undefined),\n sections: [],\n };\n const groups = [\n ...(top.pages.length === 0 ? [] : [top]),\n ...root.sections.map((section) => group(section, undefined)),\n ];\n if (missing.length > 0) {\n throw new Error(\n `@ascribed/astro: the project's navigation (_ascribe/nav.json) names pages the collection has no entry for:\\n${[...new Set(missing)].map((file) => `- ${file}`).join(\"\\n\")}\\nBuild the project again (\\`ascribe build\\`), so the navigation and the pages are from one build.`,\n );\n }\n return groups;\n}\n\n/**\n * The navigation's pages in reading order, depth first: a section's own\n * page, then its pages, each nested section whose own page is beside its\n * folder right after that page, with its pages and nested sections, and the\n * other nested sections after them, each from its own page. The content\n * root reads as the sidebar groups it: its own page and its pages that\n * aren't beside a folder, then each top section from its own page. Previous\n * and next follow it, as an archive and `llms.txt` do.\n */\nexport function readingOrder(navigation: Navigation): NavPage[] {\n const out: NavPage[] = [];\n const read = (section: NavSection, own: boolean) => {\n if (own && section.page !== undefined) out.push(section.page);\n const done = section.sections.map(() => false);\n for (const page of section.pages) {\n out.push(page);\n section.sections.forEach((nested, at) => {\n if (!done[at] && nested.page?.source === page.source) {\n done[at] = true;\n read(nested, false);\n }\n });\n }\n section.sections.forEach((nested, at) => {\n if (!done[at]) read(nested, true);\n });\n };\n const { root } = navigation;\n if (root.page !== undefined) out.push(root.page);\n out.push(...root.pages.filter((nav) => !beside(root, nav)));\n for (const section of root.sections) read(section, true);\n return out;\n}\n\n/** Whether `page` is the own page of one of `section`'s nested sections. */\nfunction beside(section: NavSection, page: NavPage): boolean {\n return section.sections.some((nested) => nested.page?.source === page.source);\n}\n\n/** A page's previous and next, each with its entry, its address, and its label. */\nexport interface Neighbors<E> {\n previous?: SidebarPage<E>;\n next?: SidebarPage<E>;\n}\n\n/**\n * The pages before and after `at` in `pages`, the order a reader goes in.\n * Neither, for a page that isn't in it.\n */\nexport function neighborsIn<E>(\n pages: readonly SidebarPage<E>[],\n at: (page: SidebarPage<E>) => boolean,\n): Neighbors<E> {\n const i = pages.findIndex(at);\n if (i === -1) return {};\n const previous = pages[i - 1];\n const next = pages[i + 1];\n return {\n ...(previous === undefined ? {} : { previous }),\n ...(next === undefined ? {} : { next }),\n };\n}\n\n/**\n * The navigation's pages in reading order, each with its entry, its\n * address, and its label. `source` gives an entry's page's source path.\n * A page the collection has no entry for is passed over.\n */\nexport function readingPages<E extends NavEntry>(\n navigation: Navigation,\n entries: readonly E[],\n route: (entry: E) => string,\n source: (entry: E) => string,\n): SidebarPage<E>[] {\n const bySource = new Map(entries.map((entry) => [source(entry), entry]));\n return readingOrder(navigation).flatMap((nav) => {\n const entry = bySource.get(nav.source);\n if (entry === undefined) return [];\n return [\n {\n entry,\n href: route(entry),\n label: nav.label,\n ...(nav.formatted === undefined ? {} : { formatted: nav.formatted }),\n },\n ];\n });\n}\n\n/**\n * The ways the list and the collection disagree, as sentences: a page the\n * list leaves out, a file it names that isn't a page, and a page it names twice.\n * `source` names an entry for a person, by its source file where it can.\n */\nexport function navProblems(\n nav: readonly NavGroup[],\n entries: readonly NavEntry[],\n source: (entry: NavEntry) => string = (entry) => entry.id,\n): string[] {\n const listed = nav.flatMap((group) => group.pages);\n const ids = new Set(entries.map((entry) => entry.id));\n const listedIds = new Set(listed.map(entryId));\n const problems: string[] = [];\n for (const entry of entries) {\n if (!listedIds.has(entry.id))\n problems.push(`${source(entry)} isn't in the sidebar's list. Add it to a group.`);\n }\n for (const [i, file] of listed.entries()) {\n if (!ids.has(entryId(file)))\n problems.push(`the sidebar's list names ${file}, which isn't a page of the site.`);\n else if (listed.findIndex((other) => entryId(other) === entryId(file)) !== i)\n problems.push(`the sidebar's list names ${file} twice.`);\n }\n return problems;\n}\n\n/**\n * The sidebar's groups from a site's own list, each page with its entry, its\n * address, and its label, which `label` gives, or else its entry id. Throws,\n * naming each, when the list leaves out a page of the collection, names a\n * file that isn't one, or names a page twice, so the list stays complete.\n *\n * A versioned build's collection has every line's pages, and the list is\n * checked against them all: `id` gives an entry's id within its line, and\n * the groups have the pages `shown` keeps, one line's, each listed page that\n * line has.\n */\nexport function sidebarGroups<E extends NavEntry>(\n nav: readonly NavGroup[],\n entries: readonly E[],\n route: (id: string) => string,\n source?: (entry: NavEntry) => string,\n lines: { id: (entry: E) => string; shown: (entry: E) => boolean } = {\n id: (entry) => entry.id,\n shown: () => true,\n },\n label?: (entry: E) => string | undefined,\n): SidebarGroup<E>[] {\n // A page in several lines is one page of the list, and one problem.\n const checked = new Map<NavEntry, E>(\n entries.map((entry) => [{ id: lines.id(entry), filePath: entry.filePath }, entry]),\n );\n const problems = navProblems(\n nav,\n [...checked.keys()],\n source === undefined ? undefined : (entry) => source(checked.get(entry) ?? entry),\n ).filter((problem, i, all) => all.indexOf(problem) === i);\n if (problems.length > 0) {\n throw new Error(\n `@ascribed/astro: the sidebar's list (src/nav.ts) doesn't match the site's pages:\\n${problems.map((problem) => `- ${problem}`).join(\"\\n\")}`,\n );\n }\n const byId = new Map(\n entries.filter((entry) => lines.shown(entry)).map((entry) => [lines.id(entry), entry]),\n );\n return nav.map((group) => ({\n label: group.label,\n pages: group.pages.flatMap((file) => {\n const entry = byId.get(entryId(file));\n return entry === undefined\n ? []\n : [{ entry, href: route(entry.id), label: label?.(entry) ?? entry.id }];\n }),\n sections: [],\n }));\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ascribed/astro",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "The Astro integration for Ascribe.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MPL-2.0",
|
|
@@ -58,9 +58,9 @@
|
|
|
58
58
|
"node": ">=24"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@ascribed/cli": "0.
|
|
62
|
-
"@ascribed/elements": "0.
|
|
63
|
-
"@ascribed/review": "0.
|
|
61
|
+
"@ascribed/cli": "0.4.0",
|
|
62
|
+
"@ascribed/elements": "0.4.0",
|
|
63
|
+
"@ascribed/review": "0.4.0",
|
|
64
64
|
"github-slugger": "^2.0.0",
|
|
65
65
|
"property-information": "^7.2.0",
|
|
66
66
|
"smol-toml": "^1.9.1"
|
|
@@ -96,7 +96,6 @@
|
|
|
96
96
|
"publishConfig": {
|
|
97
97
|
"access": "public"
|
|
98
98
|
},
|
|
99
|
-
"gitHead": "bd4c22cf71d61a205925fb81304dbec66bb400b4",
|
|
100
99
|
"scripts": {
|
|
101
100
|
"build": "tsc -p tsconfig.build.json",
|
|
102
101
|
"typecheck": "tsc --noEmit",
|
|
@@ -9,8 +9,13 @@ import Head from "@ascribed/astro/Head.astro";
|
|
|
9
9
|
// what it is and where the current one is.
|
|
10
10
|
import Versions from "@ascribed/astro/Versions.astro";
|
|
11
11
|
import VersionBanner from "@ascribed/astro/VersionBanner.astro";
|
|
12
|
-
import {
|
|
13
|
-
|
|
12
|
+
import {
|
|
13
|
+
neighbors,
|
|
14
|
+
sidebar,
|
|
15
|
+
type Formatted,
|
|
16
|
+
type SidebarGroup,
|
|
17
|
+
type SidebarPage,
|
|
18
|
+
} from "@ascribed/astro/content";
|
|
14
19
|
|
|
15
20
|
interface Props {
|
|
16
21
|
entry: CollectionEntry<"docs">;
|
|
@@ -31,15 +36,50 @@ interface Page {
|
|
|
31
36
|
|
|
32
37
|
const { entry, headings } = Astro.props;
|
|
33
38
|
const page = (of: CollectionEntry<"docs">) => of.data as Page;
|
|
39
|
+
const escape = (text: string) =>
|
|
40
|
+
text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
34
41
|
/** A page's title as HTML: its formatted form, or its plain text escaped. */
|
|
35
42
|
const titleHtml = (of: CollectionEntry<"docs">) =>
|
|
36
|
-
page(of).formatted?.title ??
|
|
37
|
-
|
|
43
|
+
page(of).formatted?.title ?? escape(page(of).title);
|
|
44
|
+
/** A sidebar label as HTML: its formatted form, or its plain text escaped. */
|
|
45
|
+
const labelHtml = (of: { label: string; formatted?: Formatted }) =>
|
|
46
|
+
of.formatted?.label ?? escape(of.label);
|
|
38
47
|
const { available } = page(entry);
|
|
39
|
-
// The sidebar:
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
|
|
48
|
+
// The sidebar: the project's navigation, in ascribe.nav's order or the
|
|
49
|
+
// default one, each page with its address and label, and a nested section's
|
|
50
|
+
// pages right after its own page, each with its depth. With a versioned build,
|
|
51
|
+
// it shows the pages of the line this page is in. Previous and next are the
|
|
52
|
+
// pages before and after this one in the navigation's reading order.
|
|
53
|
+
const entries = await getCollection("docs");
|
|
54
|
+
const groups = sidebar(entries, { entry });
|
|
55
|
+
const { previous, next } = neighbors(entry, entries);
|
|
56
|
+
|
|
57
|
+
/** A sidebar page, with how many sections deep it is: 0 in a top section. */
|
|
58
|
+
interface Item {
|
|
59
|
+
page: SidebarPage<CollectionEntry<"docs">>;
|
|
60
|
+
depth: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A group's pages in order, each nested section's right after its own page,
|
|
65
|
+
* or after the group's pages when its own page is its `index.md`.
|
|
66
|
+
*/
|
|
67
|
+
function items(group: SidebarGroup<CollectionEntry<"docs">>, depth = 0): Item[] {
|
|
68
|
+
const placed = new Set<SidebarGroup<CollectionEntry<"docs">>>();
|
|
69
|
+
const out: Item[] = [];
|
|
70
|
+
for (const page of group.pages) {
|
|
71
|
+
out.push({ page, depth });
|
|
72
|
+
for (const section of group.sections) {
|
|
73
|
+
if (section.page?.entry !== page.entry) continue;
|
|
74
|
+
placed.add(section);
|
|
75
|
+
out.push(...items(section, depth + 1));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
for (const section of group.sections) {
|
|
79
|
+
if (!placed.has(section)) out.push(...items(section, depth + 1));
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
43
83
|
---
|
|
44
84
|
|
|
45
85
|
<!doctype html>
|
|
@@ -55,15 +95,15 @@ const groups = sidebar(nav, await getCollection("docs"), { entry });
|
|
|
55
95
|
{
|
|
56
96
|
groups.map((group) => (
|
|
57
97
|
<section>
|
|
58
|
-
<h2
|
|
98
|
+
<h2 set:html={labelHtml(group)} />
|
|
59
99
|
<ul>
|
|
60
|
-
{group.
|
|
61
|
-
<li>
|
|
100
|
+
{items(group).map(({ page: item, depth }) => (
|
|
101
|
+
<li data-depth={depth}>
|
|
62
102
|
<a
|
|
63
103
|
href={item.href}
|
|
64
104
|
aria-current={item.entry.id === entry.id ? "page" : undefined}
|
|
65
105
|
>
|
|
66
|
-
<Fragment set:html={
|
|
106
|
+
<Fragment set:html={labelHtml(item)} />
|
|
67
107
|
</a>
|
|
68
108
|
</li>
|
|
69
109
|
))}
|
|
@@ -89,6 +129,22 @@ const groups = sidebar(nav, await getCollection("docs"), { entry });
|
|
|
89
129
|
</ul>
|
|
90
130
|
</nav>
|
|
91
131
|
<article><slot /></article>
|
|
132
|
+
<nav aria-label="Previous and next">
|
|
133
|
+
{
|
|
134
|
+
previous && (
|
|
135
|
+
<a rel="prev" href={previous.href}>
|
|
136
|
+
Previous: <Fragment set:html={labelHtml(previous)} />
|
|
137
|
+
</a>
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
{
|
|
141
|
+
next && (
|
|
142
|
+
<a rel="next" href={next.href}>
|
|
143
|
+
Next: <Fragment set:html={labelHtml(next)} />
|
|
144
|
+
</a>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
</nav>
|
|
92
148
|
</main>
|
|
93
149
|
</body>
|
|
94
150
|
</html>
|
package/starter/src/nav.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// The sidebar's list: its groups, in order, each with its pages' source files,
|
|
2
|
-
// relative to the content root. `npx @ascribed/astro init` wrote it from the
|
|
3
|
-
// pages there were then; it's yours to reorder, relabel, and add to. Ascribe's
|
|
4
|
-
// content model has no page order or grouping yet, so the site keeps the list,
|
|
5
|
-
// and the layout's `sidebar()` keeps it complete: the build fails on a page the
|
|
6
|
-
// list leaves out, and on a file it names that isn't a page.
|
|
7
|
-
import type { NavGroup } from "@ascribed/astro/content";
|
|
8
|
-
|
|
9
|
-
export const nav: NavGroup[] = [
|
|
10
|
-
{ label: "Start", pages: ["index.md"] },
|
|
11
|
-
{ label: "Guides", pages: ["Guides/My Setup.md"] },
|
|
12
|
-
{ label: "Reference", pages: ["Reference/Options.md"] },
|
|
13
|
-
];
|