blume 1.6.5 → 1.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/CHANGELOG.md +39 -0
- package/bin/blume.mjs +3 -2
- package/dist/cli/chunk-0qhq7b8q.js +111 -0
- package/dist/cli/chunk-0qhq7b8q.js.map +11 -0
- package/dist/cli/chunk-18tjv4f7.js +96 -0
- package/dist/cli/chunk-18tjv4f7.js.map +10 -0
- package/dist/cli/chunk-27gtm2ym.js +69 -0
- package/dist/cli/chunk-27gtm2ym.js.map +11 -0
- package/dist/cli/chunk-2aj8ddew.js +72 -0
- package/dist/cli/chunk-2aj8ddew.js.map +10 -0
- package/dist/cli/chunk-3r94j3tc.js +221 -0
- package/dist/cli/chunk-3r94j3tc.js.map +10 -0
- package/dist/cli/chunk-4trphnvy.js +102 -0
- package/dist/cli/chunk-4trphnvy.js.map +11 -0
- package/dist/cli/chunk-4xyggvgf.js +21 -0
- package/dist/cli/chunk-4xyggvgf.js.map +10 -0
- package/dist/cli/chunk-5d4q7121.js +4064 -0
- package/dist/cli/chunk-5d4q7121.js.map +40 -0
- package/dist/cli/chunk-5hs6gb7n.js +32 -0
- package/dist/cli/chunk-5hs6gb7n.js.map +10 -0
- package/dist/cli/chunk-6kzzpsx8.js +26 -0
- package/dist/cli/chunk-6kzzpsx8.js.map +10 -0
- package/dist/cli/chunk-8gnpdsn1.js +952 -0
- package/dist/cli/chunk-8gnpdsn1.js.map +12 -0
- package/dist/cli/chunk-9qs6acpw.js +176 -0
- package/dist/cli/chunk-9qs6acpw.js.map +10 -0
- package/dist/cli/chunk-agy5rzxy.js +2453 -0
- package/dist/cli/chunk-agy5rzxy.js.map +15 -0
- package/dist/cli/chunk-bcy492zc.js +16 -0
- package/dist/cli/chunk-bcy492zc.js.map +10 -0
- package/dist/cli/chunk-btfr9yvw.js +41 -0
- package/dist/cli/chunk-btfr9yvw.js.map +10 -0
- package/dist/cli/chunk-cbjnx4s8.js +73 -0
- package/dist/cli/chunk-cbjnx4s8.js.map +10 -0
- package/dist/cli/chunk-cfw6x4rm.js +1967 -0
- package/dist/cli/chunk-cfw6x4rm.js.map +34 -0
- package/dist/cli/chunk-ckh3a410.js +277 -0
- package/dist/cli/chunk-ckh3a410.js.map +11 -0
- package/dist/cli/chunk-drke6t0h.js +259 -0
- package/dist/cli/chunk-drke6t0h.js.map +11 -0
- package/dist/cli/chunk-ev67ycx0.js +15 -0
- package/dist/cli/chunk-ev67ycx0.js.map +10 -0
- package/dist/cli/chunk-ey89bjj1.js +209 -0
- package/dist/cli/chunk-ey89bjj1.js.map +11 -0
- package/dist/cli/chunk-j6pxe0dt.js +69 -0
- package/dist/cli/chunk-j6pxe0dt.js.map +11 -0
- package/dist/cli/chunk-jk1zwka1.js +387 -0
- package/dist/cli/chunk-jk1zwka1.js.map +12 -0
- package/dist/cli/chunk-jtb45atp.js +467 -0
- package/dist/cli/chunk-jtb45atp.js.map +14 -0
- package/dist/cli/chunk-jxkxjsc1.js +76 -0
- package/dist/cli/chunk-jxkxjsc1.js.map +10 -0
- package/dist/cli/chunk-kwx90v78.js +81 -0
- package/dist/cli/chunk-kwx90v78.js.map +10 -0
- package/dist/cli/chunk-n0nyat6g.js +30 -0
- package/dist/cli/chunk-n0nyat6g.js.map +10 -0
- package/dist/cli/chunk-pxj10x8y.js +35 -0
- package/dist/cli/chunk-pxj10x8y.js.map +10 -0
- package/dist/cli/chunk-qq9nm3qd.js +1141 -0
- package/dist/cli/chunk-qq9nm3qd.js.map +19 -0
- package/dist/cli/chunk-s102bysw.js +5170 -0
- package/dist/cli/chunk-s102bysw.js.map +47 -0
- package/dist/cli/chunk-s5dsk8bj.js +769 -0
- package/dist/cli/chunk-s5dsk8bj.js.map +13 -0
- package/dist/cli/chunk-s5e5jt53.js +227 -0
- package/dist/cli/chunk-s5e5jt53.js.map +11 -0
- package/dist/cli/chunk-sbdqrjbb.js +81 -0
- package/dist/cli/chunk-sbdqrjbb.js.map +10 -0
- package/dist/cli/chunk-tnskyrej.js +117 -0
- package/dist/cli/chunk-tnskyrej.js.map +10 -0
- package/dist/cli/chunk-v2ymm99c.js +1016 -0
- package/dist/cli/chunk-v2ymm99c.js.map +13 -0
- package/dist/cli/chunk-v5mm027v.js +185 -0
- package/dist/cli/chunk-v5mm027v.js.map +11 -0
- package/dist/cli/chunk-vt8fgygt.js +23 -0
- package/dist/cli/chunk-vt8fgygt.js.map +10 -0
- package/dist/cli/chunk-vxv4x1n8.js +17 -0
- package/dist/cli/chunk-vxv4x1n8.js.map +10 -0
- package/dist/cli/chunk-wd27zjcz.js +60 -0
- package/dist/cli/chunk-wd27zjcz.js.map +10 -0
- package/dist/cli/chunk-x66c5yjn.js +23 -0
- package/dist/cli/chunk-x66c5yjn.js.map +10 -0
- package/dist/cli/chunk-xv91q4nm.js +5314 -0
- package/dist/cli/chunk-xv91q4nm.js.map +58 -0
- package/dist/cli/chunk-y3g15rvv.js +679 -0
- package/dist/cli/chunk-y3g15rvv.js.map +15 -0
- package/dist/cli/chunk-ye9zdkgv.js +136 -0
- package/dist/cli/chunk-ye9zdkgv.js.map +10 -0
- package/dist/cli/chunk-ynacq3ev.js +1062 -0
- package/dist/cli/chunk-ynacq3ev.js.map +25 -0
- package/dist/cli/chunk-zr3ygrq3.js +54 -0
- package/dist/cli/chunk-zr3ygrq3.js.map +10 -0
- package/dist/cli/index.js +55 -27597
- package/dist/cli/index.js.map +5 -243
- package/dist/types/ai/ask-context.d.ts +26 -0
- package/dist/types/components/layout/nav-utils.d.ts +33 -1
- package/dist/types/core/code-fences.d.ts +11 -0
- package/dist/types/core/package-root.d.ts +1 -1
- package/dist/types/core/schema.d.ts +70 -0
- package/dist/types/theme/fonts.d.ts +22 -22
- package/docs/02-deployment.mdx +22 -1
- package/docs/configuration/ask-ai.mdx +1 -1
- package/docs/configuration/customization.mdx +2 -9
- package/docs/content/navigation.mdx +2 -0
- package/docs/content/syntax.mdx +1 -1
- package/docs/discoverability/open-graph.mdx +4 -0
- package/docs/reference/cli.mdx +1 -1
- package/package.json +4 -2
- package/src/ai/api/handlers.ts +4 -7
- package/src/ai/api/paths.ts +8 -0
- package/src/ai/api/spec.ts +2 -1
- package/src/ai/ask-context.ts +378 -22
- package/src/astro/generate.ts +161 -28
- package/src/astro/include-hmr.ts +10 -13
- package/src/astro/include-refresh.ts +0 -0
- package/src/astro/index.ts +6 -1
- package/src/astro/integration.ts +280 -53
- package/src/astro/module-types.ts +83 -0
- package/src/astro/templates.ts +256 -108
- package/src/audit/image-size.ts +10 -8
- package/src/cli/command-meta.ts +77 -0
- package/src/cli/commands/add.ts +2 -4
- package/src/cli/commands/audit.ts +2 -4
- package/src/cli/commands/build.ts +70 -346
- package/src/cli/commands/check.ts +2 -4
- package/src/cli/commands/dev.ts +31 -42
- package/src/cli/commands/doctor.ts +2 -4
- package/src/cli/commands/eject.ts +3 -41
- package/src/cli/commands/eval.ts +2 -5
- package/src/cli/commands/init.ts +2 -4
- package/src/cli/commands/mcp-stdio.ts +2 -5
- package/src/cli/commands/preview.ts +3 -5
- package/src/cli/commands/sync.ts +2 -4
- package/src/cli/commands/translate.ts +2 -5
- package/src/cli/commands/validate.ts +2 -4
- package/src/cli/commands/version.ts +2 -4
- package/src/cli/eject-scripts.ts +0 -45
- package/src/cli/host-args.ts +16 -0
- package/src/cli/index.ts +84 -35
- package/src/cli/lazy-command.ts +47 -0
- package/src/components/Icon.astro +24 -0
- package/src/components/content/GithubInfo.astro +4 -1
- package/src/components/icon-sprite-middleware.ts +41 -0
- package/src/components/icon-sprite.ts +93 -0
- package/src/components/layout/IconSprite.astro +11 -0
- package/src/components/layout/NavTree.astro +156 -188
- package/src/components/layout/NavTreeCache.astro +45 -0
- package/src/components/layout/NavTreeScript.astro +256 -0
- package/src/components/layout/PageActions.astro +11 -5
- package/src/components/layout/PageLayout.astro +21 -3
- package/src/components/layout/ReferenceLayout.astro +21 -4
- package/src/components/layout/RootLayout.astro +44 -6
- package/src/components/layout/nav-cache.ts +49 -0
- package/src/components/layout/nav-utils.ts +69 -1
- package/src/components/layout/page-locale.ts +29 -0
- package/src/core/api-name.ts +18 -0
- package/src/core/code-fences.ts +48 -0
- package/src/core/content-assets.ts +3 -7
- package/src/core/includes.ts +3 -7
- package/src/core/package-root.ts +1 -1
- package/src/core/schema.ts +19 -0
- package/src/core/sources/normalize.ts +2 -37
- package/src/core/sources/obsidian.ts +3 -2
- package/src/core/svg-dimensions.ts +97 -0
- package/src/core/version-cut.ts +2 -2
- package/src/deploy/artifacts.ts +370 -0
- package/src/deploy/cloudflare-negotiation.ts +97 -32
- package/src/deploy/function-bundle.ts +66 -20
- package/src/deploy/sitemap.ts +6 -0
- package/src/deploy/vercel-negotiation.ts +8 -30
- package/src/markdown/language-icon.ts +64 -20
- package/src/markdown/mermaid.ts +11 -0
- package/src/og/cache.ts +236 -0
- package/src/og/card.ts +18 -16
- package/src/og/index.ts +8 -1
- package/src/openapi/render-mdx.ts +9 -5
- package/src/registry/eject.ts +23 -10
- package/src/theme/entry.ts +41 -7
- package/src/theme/fonts.ts +30 -23
|
@@ -6,15 +6,20 @@ import { methodColor } from "../colors.ts";
|
|
|
6
6
|
import Icon from "../Icon.astro";
|
|
7
7
|
import { withBase } from "../islands/base-path.ts";
|
|
8
8
|
import Self from "./NavTree.astro";
|
|
9
|
+
import NavTreeCache from "./NavTreeCache.astro";
|
|
10
|
+
import NavTreeScript from "./NavTreeScript.astro";
|
|
9
11
|
|
|
10
12
|
interface PagePanel {
|
|
11
13
|
id: string;
|
|
12
14
|
parentId: string;
|
|
15
|
+
/** Nesting level in the panel stack (a root-level page group is 1). */
|
|
16
|
+
depth: number;
|
|
13
17
|
label: string;
|
|
14
18
|
route?: string;
|
|
15
19
|
icon?: string;
|
|
16
20
|
children: NavNode[];
|
|
17
21
|
active: boolean;
|
|
22
|
+
node: NavNode;
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
interface Props {
|
|
@@ -27,6 +32,20 @@ interface Props {
|
|
|
27
32
|
root?: boolean;
|
|
28
33
|
/** Localized labels ("Back", the deprecated badge); English when omitted. */
|
|
29
34
|
strings?: UIStrings["nav"];
|
|
35
|
+
/**
|
|
36
|
+
* Stable ids for the full sidebar's groups (`navGroupIds`), so panel and
|
|
37
|
+
* fragment ids match across the scoped views the layout renders. Absent,
|
|
38
|
+
* ids fall back to positions within this render.
|
|
39
|
+
*/
|
|
40
|
+
ids?: Map<NavNode, string>;
|
|
41
|
+
/**
|
|
42
|
+
* URL prefix of the deferred-section fragments (`/blume-nav/<version>/
|
|
43
|
+
* <locale>`). When set, a collapsed group's children and an inactive
|
|
44
|
+
* drill-in panel's contents are left out of the page and fetched from
|
|
45
|
+
* `<fragmentBase>/<id>` on first open (see NavTreeScript); absent, every
|
|
46
|
+
* section renders in full.
|
|
47
|
+
*/
|
|
48
|
+
fragmentBase?: string;
|
|
30
49
|
}
|
|
31
50
|
|
|
32
51
|
const {
|
|
@@ -36,8 +55,18 @@ const {
|
|
|
36
55
|
idPrefix = "n",
|
|
37
56
|
root = depth === 0,
|
|
38
57
|
strings,
|
|
58
|
+
ids,
|
|
59
|
+
fragmentBase,
|
|
39
60
|
} = Astro.props;
|
|
40
61
|
|
|
62
|
+
/** A group's id: its stable full-tree id, else its position in this render. */
|
|
63
|
+
const idOf = (node: NavNode, positional: string): string =>
|
|
64
|
+
ids?.get(node) ?? positional;
|
|
65
|
+
|
|
66
|
+
/** The fragment a deferred section loads from, when sections are deferred. */
|
|
67
|
+
const fragmentFor = (id: string): string | undefined =>
|
|
68
|
+
fragmentBase ? `${fragmentBase}/${id}` : undefined;
|
|
69
|
+
|
|
41
70
|
// Merge over the English defaults so a label missing from a translation (or
|
|
42
71
|
// from a not-yet-regenerated snapshot) still renders instead of coming out
|
|
43
72
|
// blank — the PageActions pattern.
|
|
@@ -66,27 +95,30 @@ const hasActivePage = (item: NavNode): boolean =>
|
|
|
66
95
|
const collectPanels = (
|
|
67
96
|
nodes: NavNode[],
|
|
68
97
|
prefix: string,
|
|
69
|
-
parentId: string
|
|
98
|
+
parentId: string,
|
|
99
|
+
level = 1
|
|
70
100
|
): PagePanel[] => {
|
|
71
101
|
const out: PagePanel[] = [];
|
|
72
102
|
nodes.forEach((node, index) => {
|
|
73
103
|
if (node.kind !== "group") {
|
|
74
104
|
return;
|
|
75
105
|
}
|
|
76
|
-
const id = `${prefix}.${index}
|
|
106
|
+
const id = idOf(node, `${prefix}.${index}`);
|
|
77
107
|
if ((node.display ?? "flat") === "page") {
|
|
78
108
|
out.push({
|
|
79
109
|
active: hasActivePage(node),
|
|
80
110
|
children: node.children,
|
|
111
|
+
depth: level,
|
|
81
112
|
icon: node.icon,
|
|
82
113
|
id,
|
|
83
114
|
label: node.label,
|
|
115
|
+
node,
|
|
84
116
|
parentId,
|
|
85
117
|
route: node.route,
|
|
86
118
|
});
|
|
87
|
-
out.push(...collectPanels(node.children, id, id));
|
|
119
|
+
out.push(...collectPanels(node.children, id, id, level + 1));
|
|
88
120
|
} else {
|
|
89
|
-
out.push(...collectPanels(node.children, id, parentId));
|
|
121
|
+
out.push(...collectPanels(node.children, id, parentId, level));
|
|
90
122
|
}
|
|
91
123
|
});
|
|
92
124
|
return out;
|
|
@@ -98,27 +130,33 @@ const panels = root ? collectPanels(items, idPrefix, "root") : [];
|
|
|
98
130
|
const activePanels = panels.filter((panel) => panel.active);
|
|
99
131
|
const initialId =
|
|
100
132
|
activePanels.length > 0
|
|
101
|
-
? activePanels.reduce((a, b) =>
|
|
102
|
-
b.id.split(".").length > a.id.split(".").length ? b : a
|
|
103
|
-
).id
|
|
133
|
+
? activePanels.reduce((a, b) => (b.depth > a.depth ? b : a)).id
|
|
104
134
|
: "root";
|
|
105
135
|
---
|
|
106
136
|
|
|
137
|
+
{root && (panels.length > 0 || fragmentBase) && <NavTreeScript />}
|
|
107
138
|
{
|
|
108
139
|
root && panels.length > 0 ? (
|
|
109
140
|
<blume-nav class="block" data-initial={initialId}>
|
|
110
|
-
<div data-nav-panel="root" hidden={initialId !== "root"}>
|
|
141
|
+
<div data-nav-depth="0" data-nav-panel="root" hidden={initialId !== "root"}>
|
|
111
142
|
<Self
|
|
112
143
|
currentRoute={currentRoute}
|
|
113
144
|
depth={0}
|
|
145
|
+
fragmentBase={fragmentBase}
|
|
114
146
|
idPrefix={idPrefix}
|
|
147
|
+
ids={ids}
|
|
115
148
|
items={items}
|
|
116
149
|
root={false}
|
|
117
150
|
strings={n}
|
|
118
151
|
/>
|
|
119
152
|
</div>
|
|
120
153
|
{panels.map((panel) => (
|
|
121
|
-
<div
|
|
154
|
+
<div
|
|
155
|
+
data-nav-depth={panel.depth}
|
|
156
|
+
data-nav-panel={panel.id}
|
|
157
|
+
data-nav-src={panel.active ? undefined : fragmentFor(panel.id)}
|
|
158
|
+
hidden={panel.id !== initialId}
|
|
159
|
+
>
|
|
122
160
|
{/* The title is the only sidebar link to the section's own page, so
|
|
123
161
|
a routed panel keeps it as a link; without a route the whole row
|
|
124
162
|
becomes the back button. Either way every part of the row is
|
|
@@ -156,14 +194,34 @@ const initialId =
|
|
|
156
194
|
<span class="flex-1 truncate">{panel.label}</span>
|
|
157
195
|
</button>
|
|
158
196
|
)}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
197
|
+
{/* An inactive panel's contents are deferred (fetched into this
|
|
198
|
+
element on drill-in) when fragments are on; otherwise the
|
|
199
|
+
build-time cache renders them once. */}
|
|
200
|
+
{panel.active ? (
|
|
201
|
+
<Self
|
|
202
|
+
currentRoute={currentRoute}
|
|
203
|
+
depth={1}
|
|
204
|
+
fragmentBase={fragmentBase}
|
|
205
|
+
idPrefix={panel.id}
|
|
206
|
+
ids={ids}
|
|
207
|
+
items={panel.children}
|
|
208
|
+
root={false}
|
|
209
|
+
strings={n}
|
|
210
|
+
/>
|
|
211
|
+
) : fragmentBase ? null : (
|
|
212
|
+
<NavTreeCache node={panel.node} variant={`${panel.id}|${n.back}|${n.deprecated}`}>
|
|
213
|
+
<Self
|
|
214
|
+
currentRoute={currentRoute}
|
|
215
|
+
depth={1}
|
|
216
|
+
fragmentBase={fragmentBase}
|
|
217
|
+
idPrefix={panel.id}
|
|
218
|
+
ids={ids}
|
|
219
|
+
items={panel.children}
|
|
220
|
+
root={false}
|
|
221
|
+
strings={n}
|
|
222
|
+
/>
|
|
223
|
+
</NavTreeCache>
|
|
224
|
+
)}
|
|
167
225
|
</div>
|
|
168
226
|
))}
|
|
169
227
|
</blume-nav>
|
|
@@ -171,11 +229,28 @@ const initialId =
|
|
|
171
229
|
<ul class="m-0 list-none space-y-px p-0">
|
|
172
230
|
{items.map((item, index) => {
|
|
173
231
|
if (item.kind === "page") {
|
|
232
|
+
const current = item.route === currentRoute ? "page" : undefined;
|
|
233
|
+
// A bare row (no icon, badge, or deprecation) is the common case and
|
|
234
|
+
// the bulk of every page's HTML on a large site, so it renders as a
|
|
235
|
+
// single truncating link with no inner row markup.
|
|
236
|
+
if (!(item.icon || item.badge || item.deprecated)) {
|
|
237
|
+
return (
|
|
238
|
+
<li>
|
|
239
|
+
<a
|
|
240
|
+
aria-current={current}
|
|
241
|
+
class="blume-nav-link truncate"
|
|
242
|
+
href={withBase(item.route)}
|
|
243
|
+
>
|
|
244
|
+
{item.label}
|
|
245
|
+
</a>
|
|
246
|
+
</li>
|
|
247
|
+
);
|
|
248
|
+
}
|
|
174
249
|
return (
|
|
175
250
|
<li>
|
|
176
251
|
<a
|
|
177
|
-
aria-current={
|
|
178
|
-
class="
|
|
252
|
+
aria-current={current}
|
|
253
|
+
class="blume-nav-link"
|
|
179
254
|
href={withBase(item.route)}
|
|
180
255
|
>
|
|
181
256
|
<span class="flex items-center gap-2">
|
|
@@ -198,7 +273,7 @@ const initialId =
|
|
|
198
273
|
}
|
|
199
274
|
|
|
200
275
|
const display = item.display ?? "flat";
|
|
201
|
-
const id = `${idPrefix}.${index}
|
|
276
|
+
const id = idOf(item, `${idPrefix}.${index}`);
|
|
202
277
|
// Flat groups are section headers and want breathing room; collapsible
|
|
203
278
|
// `group`/`page` rows are single rows and should stack tightly.
|
|
204
279
|
const spacing =
|
|
@@ -209,7 +284,7 @@ const initialId =
|
|
|
209
284
|
return (
|
|
210
285
|
<li class={spacing}>
|
|
211
286
|
<button
|
|
212
|
-
class="
|
|
287
|
+
class="blume-nav-drill"
|
|
213
288
|
data-nav-to={id}
|
|
214
289
|
type="button"
|
|
215
290
|
>
|
|
@@ -241,13 +316,13 @@ const initialId =
|
|
|
241
316
|
return (
|
|
242
317
|
<li class={spacing}>
|
|
243
318
|
<details open={open}>
|
|
244
|
-
<summary class="
|
|
319
|
+
<summary class="blume-nav-summary">
|
|
245
320
|
{item.route ? (
|
|
246
321
|
<a
|
|
247
322
|
aria-current={
|
|
248
323
|
item.route === currentRoute ? "page" : undefined
|
|
249
324
|
}
|
|
250
|
-
class="-
|
|
325
|
+
class="blume-nav-summary-link"
|
|
251
326
|
href={withBase(item.route)}
|
|
252
327
|
>
|
|
253
328
|
{item.icon && (
|
|
@@ -285,15 +360,37 @@ const initialId =
|
|
|
285
360
|
<Icon name="chevron-right" size={13} />
|
|
286
361
|
</span>
|
|
287
362
|
</summary>
|
|
288
|
-
<div
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
363
|
+
<div
|
|
364
|
+
class="space-y-0.5 border-border border-l pl-3"
|
|
365
|
+
data-nav-src={open ? undefined : fragmentFor(id)}
|
|
366
|
+
>
|
|
367
|
+
{/* A closed group's children are deferred (fetched into this
|
|
368
|
+
element on first open) when fragments are on. */}
|
|
369
|
+
{!open && fragmentBase ? null : active ? (
|
|
370
|
+
<Self
|
|
371
|
+
currentRoute={currentRoute}
|
|
372
|
+
depth={depth + 1}
|
|
373
|
+
fragmentBase={fragmentBase}
|
|
374
|
+
idPrefix={id}
|
|
375
|
+
ids={ids}
|
|
376
|
+
items={item.children}
|
|
377
|
+
root={false}
|
|
378
|
+
strings={n}
|
|
379
|
+
/>
|
|
380
|
+
) : (
|
|
381
|
+
<NavTreeCache node={item} variant={`${id}|${n.back}|${n.deprecated}`}>
|
|
382
|
+
<Self
|
|
383
|
+
currentRoute={currentRoute}
|
|
384
|
+
depth={depth + 1}
|
|
385
|
+
fragmentBase={fragmentBase}
|
|
386
|
+
idPrefix={id}
|
|
387
|
+
ids={ids}
|
|
388
|
+
items={item.children}
|
|
389
|
+
root={false}
|
|
390
|
+
strings={n}
|
|
391
|
+
/>
|
|
392
|
+
</NavTreeCache>
|
|
393
|
+
)}
|
|
297
394
|
</div>
|
|
298
395
|
</details>
|
|
299
396
|
</li>
|
|
@@ -303,11 +400,11 @@ const initialId =
|
|
|
303
400
|
// `flat` (default): a non-collapsible header with its items beneath.
|
|
304
401
|
return (
|
|
305
402
|
<li class={spacing}>
|
|
306
|
-
<p class="
|
|
403
|
+
<p class="blume-nav-heading">
|
|
307
404
|
{item.route ? (
|
|
308
405
|
<a
|
|
309
406
|
aria-current={item.route === currentRoute ? "page" : undefined}
|
|
310
|
-
class="-
|
|
407
|
+
class="blume-nav-heading-link"
|
|
311
408
|
href={withBase(item.route)}
|
|
312
409
|
>
|
|
313
410
|
{item.icon && (
|
|
@@ -339,14 +436,31 @@ const initialId =
|
|
|
339
436
|
)}
|
|
340
437
|
</p>
|
|
341
438
|
<div class="space-y-0.5">
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
439
|
+
{hasActivePage(item) ? (
|
|
440
|
+
<Self
|
|
441
|
+
currentRoute={currentRoute}
|
|
442
|
+
depth={depth + 1}
|
|
443
|
+
fragmentBase={fragmentBase}
|
|
444
|
+
idPrefix={id}
|
|
445
|
+
ids={ids}
|
|
446
|
+
items={item.children}
|
|
447
|
+
root={false}
|
|
448
|
+
strings={n}
|
|
449
|
+
/>
|
|
450
|
+
) : (
|
|
451
|
+
<NavTreeCache node={item} variant={`${id}|${n.back}|${n.deprecated}`}>
|
|
452
|
+
<Self
|
|
453
|
+
currentRoute={currentRoute}
|
|
454
|
+
depth={depth + 1}
|
|
455
|
+
fragmentBase={fragmentBase}
|
|
456
|
+
idPrefix={id}
|
|
457
|
+
ids={ids}
|
|
458
|
+
items={item.children}
|
|
459
|
+
root={false}
|
|
460
|
+
strings={n}
|
|
461
|
+
/>
|
|
462
|
+
</NavTreeCache>
|
|
463
|
+
)}
|
|
350
464
|
</div>
|
|
351
465
|
</li>
|
|
352
466
|
);
|
|
@@ -355,149 +469,3 @@ const initialId =
|
|
|
355
469
|
)
|
|
356
470
|
}
|
|
357
471
|
|
|
358
|
-
<script>
|
|
359
|
-
const DURATION = 260;
|
|
360
|
-
const EASING = "cubic-bezier(0.33, 1, 0.68, 1)";
|
|
361
|
-
|
|
362
|
-
class BlumeNav extends HTMLElement {
|
|
363
|
-
connectedCallback() {
|
|
364
|
-
if (this.dataset.ready) {
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
this.dataset.ready = "1";
|
|
368
|
-
this.addEventListener("click", (event) => {
|
|
369
|
-
const target = event.target;
|
|
370
|
-
if (!(target instanceof Element)) {
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
const drill = target.closest("[data-nav-to]");
|
|
374
|
-
if (drill && this.contains(drill)) {
|
|
375
|
-
const id = drill.getAttribute("data-nav-to");
|
|
376
|
-
if (id && this.panel(id)) {
|
|
377
|
-
event.preventDefault();
|
|
378
|
-
this.show(id, true);
|
|
379
|
-
}
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
const back = target.closest("[data-nav-back]");
|
|
383
|
-
if (back && this.contains(back)) {
|
|
384
|
-
event.preventDefault();
|
|
385
|
-
this.show(back.getAttribute("data-nav-back") || "root", true);
|
|
386
|
-
}
|
|
387
|
-
});
|
|
388
|
-
// Initial panel is route-driven, so it snaps into place without a slide.
|
|
389
|
-
this.show(this.dataset.initial || "root", false);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
panel(id) {
|
|
393
|
-
return this.querySelector(`[data-nav-panel="${id}"]`);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
depth(id) {
|
|
397
|
-
return id === "root" ? 0 : id.split(".").length;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
reduced() {
|
|
401
|
-
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
show(id, animate) {
|
|
405
|
-
const next = this.panel(id) || this.panel("root");
|
|
406
|
-
if (!next) {
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
const resolved = next.getAttribute("data-nav-panel");
|
|
410
|
-
const prev = this.active;
|
|
411
|
-
if (prev === next) {
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
// Settle any in-flight slide before starting the next one.
|
|
415
|
-
if (this.finishSlide) {
|
|
416
|
-
this.finishSlide();
|
|
417
|
-
}
|
|
418
|
-
this.active = next;
|
|
419
|
-
this.dataset.active = resolved;
|
|
420
|
-
|
|
421
|
-
if (!(animate && prev) || this.reduced()) {
|
|
422
|
-
for (const panel of this.querySelectorAll("[data-nav-panel]")) {
|
|
423
|
-
panel.hidden = panel !== next;
|
|
424
|
-
}
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const forward =
|
|
429
|
-
this.depth(resolved) > this.depth(prev.getAttribute("data-nav-panel"));
|
|
430
|
-
this.slide(prev, next, forward);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
slide(out, into, forward) {
|
|
434
|
-
const rtl = getComputedStyle(this).direction === "rtl" ? -1 : 1;
|
|
435
|
-
const sign = (forward ? -1 : 1) * rtl;
|
|
436
|
-
|
|
437
|
-
const fromHeight = out.offsetHeight;
|
|
438
|
-
this.style.position = "relative";
|
|
439
|
-
this.style.overflow = "hidden";
|
|
440
|
-
this.style.height = `${fromHeight}px`;
|
|
441
|
-
into.hidden = false;
|
|
442
|
-
for (const panel of [out, into]) {
|
|
443
|
-
panel.style.position = "absolute";
|
|
444
|
-
panel.style.top = "0";
|
|
445
|
-
panel.style.insetInlineStart = "0";
|
|
446
|
-
panel.style.width = "100%";
|
|
447
|
-
}
|
|
448
|
-
const toHeight = into.offsetHeight;
|
|
449
|
-
this.style.height = `${toHeight}px`;
|
|
450
|
-
|
|
451
|
-
const options = { duration: DURATION, easing: EASING };
|
|
452
|
-
const animations = [
|
|
453
|
-
out.animate(
|
|
454
|
-
[
|
|
455
|
-
{ transform: "translateX(0)" },
|
|
456
|
-
{ transform: `translateX(${sign * 100}%)` },
|
|
457
|
-
],
|
|
458
|
-
options
|
|
459
|
-
),
|
|
460
|
-
into.animate(
|
|
461
|
-
[
|
|
462
|
-
{ transform: `translateX(${-sign * 100}%)` },
|
|
463
|
-
{ transform: "translateX(0)" },
|
|
464
|
-
],
|
|
465
|
-
options
|
|
466
|
-
),
|
|
467
|
-
this.animate(
|
|
468
|
-
[{ height: `${fromHeight}px` }, { height: `${toHeight}px` }],
|
|
469
|
-
options
|
|
470
|
-
),
|
|
471
|
-
];
|
|
472
|
-
|
|
473
|
-
let done = false;
|
|
474
|
-
const finish = () => {
|
|
475
|
-
if (done) {
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
done = true;
|
|
479
|
-
for (const animation of animations) {
|
|
480
|
-
animation.cancel();
|
|
481
|
-
}
|
|
482
|
-
out.hidden = true;
|
|
483
|
-
for (const panel of [out, into]) {
|
|
484
|
-
panel.style.position = "";
|
|
485
|
-
panel.style.top = "";
|
|
486
|
-
panel.style.insetInlineStart = "";
|
|
487
|
-
panel.style.width = "";
|
|
488
|
-
panel.style.transform = "";
|
|
489
|
-
}
|
|
490
|
-
this.style.position = "";
|
|
491
|
-
this.style.overflow = "";
|
|
492
|
-
this.style.height = "";
|
|
493
|
-
this.finishSlide = null;
|
|
494
|
-
};
|
|
495
|
-
this.finishSlide = finish;
|
|
496
|
-
animations[0].finished.then(finish, finish);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
if (!customElements.get("blume-nav")) {
|
|
501
|
-
customElements.define("blume-nav", BlumeNav);
|
|
502
|
-
}
|
|
503
|
-
</script>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Renders its slot — a sidebar subtree with no active page in it — through the
|
|
3
|
+
// build-time subtree cache (see nav-cache.ts), so a large sidebar is rendered
|
|
4
|
+
// once per group instead of once per page. Off in dev, where component edits
|
|
5
|
+
// must show on the next request. The cached HTML references the page's icon
|
|
6
|
+
// sprite (see icon-sprite.ts), so the symbols it used ride along and are
|
|
7
|
+
// registered into whichever page reuses it.
|
|
8
|
+
import type { NavNode } from "../../core/types.ts";
|
|
9
|
+
import { iconSpriteFor, referencedIconSymbols } from "../icon-sprite.ts";
|
|
10
|
+
import type { IconSymbol } from "../icon-sprite.ts";
|
|
11
|
+
import { cachedNavSubtree } from "./nav-cache.ts";
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
node: NavNode;
|
|
15
|
+
variant: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const { node, variant } = Astro.props;
|
|
19
|
+
const sprite = iconSpriteFor(Astro.locals);
|
|
20
|
+
const { html, icons } = await cachedNavSubtree(
|
|
21
|
+
node,
|
|
22
|
+
variant,
|
|
23
|
+
async () => {
|
|
24
|
+
const rendered = await Astro.slots.render("default");
|
|
25
|
+
const used: [string, IconSymbol][] = [];
|
|
26
|
+
for (const id of referencedIconSymbols(rendered)) {
|
|
27
|
+
const symbol = sprite?.symbols.get(id);
|
|
28
|
+
if (symbol) {
|
|
29
|
+
used.push([id, symbol]);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { html: rendered, icons: used };
|
|
33
|
+
},
|
|
34
|
+
!import.meta.env.DEV
|
|
35
|
+
);
|
|
36
|
+
if (sprite) {
|
|
37
|
+
for (const [id, symbol] of icons) {
|
|
38
|
+
if (!sprite.symbols.has(id)) {
|
|
39
|
+
sprite.symbols.set(id, symbol);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
<Fragment set:html={html} />
|