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
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
---
|
|
2
|
+
// The sidebar's client behavior, split out of NavTree so a subtree rendered
|
|
3
|
+
// into the sidebar cache (see nav-cache.ts) never carries a copy of it: Astro
|
|
4
|
+
// emits a component's script where the component renders, and a cached
|
|
5
|
+
// subtree would replay the copy captured on its first render on every later
|
|
6
|
+
// page, next to the live root's own. Rendered by the root NavTree only, when
|
|
7
|
+
// a `page`-mode group gives the panel stack something to do or sections are
|
|
8
|
+
// deferred.
|
|
9
|
+
//
|
|
10
|
+
// Two jobs. The `<blume-nav>` drill-in panel stack; and deferred sections —
|
|
11
|
+
// a collapsed group's children or an inactive panel's contents left out of
|
|
12
|
+
// the page as an empty element with `data-nav-src`, fetched from the
|
|
13
|
+
// prerendered fragment at that URL on first open (and prefetched on hover or
|
|
14
|
+
// focus, so the open is usually instant). Fetched fragments are kept for the
|
|
15
|
+
// session, so a section opened once is free on every later page.
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
<script>
|
|
19
|
+
const DURATION = 260;
|
|
20
|
+
const EASING = "cubic-bezier(0.33, 1, 0.68, 1)";
|
|
21
|
+
|
|
22
|
+
/** Fragment URL → its HTML, once fetched (shared across router swaps). */
|
|
23
|
+
const fragments = new Map<string, Promise<string>>();
|
|
24
|
+
|
|
25
|
+
const loadFragment = (src: string): Promise<string> => {
|
|
26
|
+
let pending = fragments.get(src);
|
|
27
|
+
if (!pending) {
|
|
28
|
+
pending = fetch(src, { headers: { Accept: "text/html" } }).then(
|
|
29
|
+
(response) => {
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
throw new Error(`Sidebar fragment ${src} responded ${response.status}`);
|
|
32
|
+
}
|
|
33
|
+
return response.text();
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
fragments.set(src, pending);
|
|
37
|
+
// A failed fetch is retried on the next open rather than cached.
|
|
38
|
+
pending.catch(() => fragments.delete(src));
|
|
39
|
+
}
|
|
40
|
+
return pending;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Fill a deferred element from its fragment; a no-op once filled. */
|
|
44
|
+
const fillDeferred = async (slot: HTMLElement): Promise<void> => {
|
|
45
|
+
const src = slot.dataset.navSrc;
|
|
46
|
+
if (!src) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
slot.setAttribute("aria-busy", "true");
|
|
50
|
+
try {
|
|
51
|
+
slot.innerHTML = await loadFragment(src);
|
|
52
|
+
delete slot.dataset.navSrc;
|
|
53
|
+
} finally {
|
|
54
|
+
slot.removeAttribute("aria-busy");
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const deferredIn = (element: Element | null): HTMLElement | null =>
|
|
59
|
+
element instanceof HTMLElement && element.dataset.navSrc ? element : null;
|
|
60
|
+
|
|
61
|
+
// Delegated at the document so the handlers survive client-router swaps
|
|
62
|
+
// (the document persists; only its tree is replaced). Module scripts run
|
|
63
|
+
// once per real load, and the flag keeps a second copy of this module —
|
|
64
|
+
// a page that renders two trees — from doubling the listeners.
|
|
65
|
+
let installed = false;
|
|
66
|
+
const install = (): void => {
|
|
67
|
+
if (installed) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
installed = true;
|
|
71
|
+
document.addEventListener(
|
|
72
|
+
"toggle",
|
|
73
|
+
(event) => {
|
|
74
|
+
const details = event.target;
|
|
75
|
+
if (!(details instanceof HTMLDetailsElement) || !details.open) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const slot = deferredIn(details.querySelector(":scope > [data-nav-src]"));
|
|
79
|
+
if (slot) {
|
|
80
|
+
void fillDeferred(slot);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
true
|
|
84
|
+
);
|
|
85
|
+
// Prefetch on intent: a hovered or focused summary, or a drill-in row.
|
|
86
|
+
const prefetch = (event: Event): void => {
|
|
87
|
+
const target = event.target;
|
|
88
|
+
if (!(target instanceof Element)) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const summary = target.closest("summary");
|
|
92
|
+
const slot = summary
|
|
93
|
+
? deferredIn(summary.parentElement?.querySelector(":scope > [data-nav-src]") ?? null)
|
|
94
|
+
: null;
|
|
95
|
+
if (slot?.dataset.navSrc) {
|
|
96
|
+
void loadFragment(slot.dataset.navSrc).catch(() => undefined);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const drill = target.closest("[data-nav-to]");
|
|
100
|
+
const nav = drill?.closest("blume-nav");
|
|
101
|
+
if (drill && nav instanceof BlumeNav) {
|
|
102
|
+
const panel = deferredIn(nav.panel(drill.getAttribute("data-nav-to") ?? ""));
|
|
103
|
+
if (panel?.dataset.navSrc) {
|
|
104
|
+
void loadFragment(panel.dataset.navSrc).catch(() => undefined);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
document.addEventListener("pointerover", prefetch);
|
|
109
|
+
document.addEventListener("focusin", prefetch);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
class BlumeNav extends HTMLElement {
|
|
113
|
+
active: HTMLElement | null = null;
|
|
114
|
+
finishSlide: (() => void) | null = null;
|
|
115
|
+
|
|
116
|
+
connectedCallback() {
|
|
117
|
+
if (this.dataset.ready) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
this.dataset.ready = "1";
|
|
121
|
+
this.addEventListener("click", (event) => {
|
|
122
|
+
const target = event.target;
|
|
123
|
+
if (!(target instanceof Element)) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const drill = target.closest("[data-nav-to]");
|
|
127
|
+
if (drill && this.contains(drill)) {
|
|
128
|
+
const id = drill.getAttribute("data-nav-to");
|
|
129
|
+
if (id && this.panel(id)) {
|
|
130
|
+
event.preventDefault();
|
|
131
|
+
void this.show(id, true);
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const back = target.closest("[data-nav-back]");
|
|
136
|
+
if (back && this.contains(back)) {
|
|
137
|
+
event.preventDefault();
|
|
138
|
+
void this.show(back.getAttribute("data-nav-back") || "root", true);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
// Initial panel is route-driven, so it snaps into place without a slide.
|
|
142
|
+
void this.show(this.dataset.initial || "root", false);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
panel(id: string): HTMLElement | null {
|
|
146
|
+
return this.querySelector(`[data-nav-panel="${id}"]`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
depth(panel: HTMLElement): number {
|
|
150
|
+
return Number(panel.dataset.navDepth ?? "0");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
reduced(): boolean {
|
|
154
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async show(id: string, animate: boolean): Promise<void> {
|
|
158
|
+
const next = this.panel(id) || this.panel("root");
|
|
159
|
+
if (!next) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
// A deferred panel loads its contents before it slides in.
|
|
163
|
+
await fillDeferred(next);
|
|
164
|
+
const prev = this.active;
|
|
165
|
+
if (prev === next) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
// Settle any in-flight slide before starting the next one.
|
|
169
|
+
if (this.finishSlide) {
|
|
170
|
+
this.finishSlide();
|
|
171
|
+
}
|
|
172
|
+
this.active = next;
|
|
173
|
+
this.dataset.active = next.dataset.navPanel;
|
|
174
|
+
|
|
175
|
+
if (!(animate && prev) || this.reduced()) {
|
|
176
|
+
for (const panel of this.querySelectorAll<HTMLElement>("[data-nav-panel]")) {
|
|
177
|
+
panel.hidden = panel !== next;
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
this.slide(prev, next, this.depth(next) > this.depth(prev));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
slide(out: HTMLElement, into: HTMLElement, forward: boolean): void {
|
|
186
|
+
const rtl = getComputedStyle(this).direction === "rtl" ? -1 : 1;
|
|
187
|
+
const sign = (forward ? -1 : 1) * rtl;
|
|
188
|
+
|
|
189
|
+
const fromHeight = out.offsetHeight;
|
|
190
|
+
this.style.position = "relative";
|
|
191
|
+
this.style.overflow = "hidden";
|
|
192
|
+
this.style.height = `${fromHeight}px`;
|
|
193
|
+
into.hidden = false;
|
|
194
|
+
for (const panel of [out, into]) {
|
|
195
|
+
panel.style.position = "absolute";
|
|
196
|
+
panel.style.top = "0";
|
|
197
|
+
panel.style.insetInlineStart = "0";
|
|
198
|
+
panel.style.width = "100%";
|
|
199
|
+
}
|
|
200
|
+
const toHeight = into.offsetHeight;
|
|
201
|
+
this.style.height = `${toHeight}px`;
|
|
202
|
+
|
|
203
|
+
const options = { duration: DURATION, easing: EASING };
|
|
204
|
+
const animations = [
|
|
205
|
+
out.animate(
|
|
206
|
+
[
|
|
207
|
+
{ transform: "translateX(0)" },
|
|
208
|
+
{ transform: `translateX(${sign * 100}%)` },
|
|
209
|
+
],
|
|
210
|
+
options
|
|
211
|
+
),
|
|
212
|
+
into.animate(
|
|
213
|
+
[
|
|
214
|
+
{ transform: `translateX(${-sign * 100}%)` },
|
|
215
|
+
{ transform: "translateX(0)" },
|
|
216
|
+
],
|
|
217
|
+
options
|
|
218
|
+
),
|
|
219
|
+
this.animate(
|
|
220
|
+
[{ height: `${fromHeight}px` }, { height: `${toHeight}px` }],
|
|
221
|
+
options
|
|
222
|
+
),
|
|
223
|
+
];
|
|
224
|
+
|
|
225
|
+
let done = false;
|
|
226
|
+
const finish = () => {
|
|
227
|
+
if (done) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
done = true;
|
|
231
|
+
for (const animation of animations) {
|
|
232
|
+
animation.cancel();
|
|
233
|
+
}
|
|
234
|
+
out.hidden = true;
|
|
235
|
+
for (const panel of [out, into]) {
|
|
236
|
+
panel.style.position = "";
|
|
237
|
+
panel.style.top = "";
|
|
238
|
+
panel.style.insetInlineStart = "";
|
|
239
|
+
panel.style.width = "";
|
|
240
|
+
panel.style.transform = "";
|
|
241
|
+
}
|
|
242
|
+
this.style.position = "";
|
|
243
|
+
this.style.overflow = "";
|
|
244
|
+
this.style.height = "";
|
|
245
|
+
this.finishSlide = null;
|
|
246
|
+
};
|
|
247
|
+
this.finishSlide = finish;
|
|
248
|
+
animations[0]?.finished.then(finish, finish);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
install();
|
|
253
|
+
if (!customElements.get("blume-nav")) {
|
|
254
|
+
customElements.define("blume-nav", BlumeNav);
|
|
255
|
+
}
|
|
256
|
+
</script>
|
|
@@ -523,11 +523,17 @@ hr { border: 0; border-top: 1px solid #ddd; margin: 2em 0; }`;
|
|
|
523
523
|
epubLabel.textContent = generatingLabel;
|
|
524
524
|
}
|
|
525
525
|
try {
|
|
526
|
-
// Browser bundle: avoids Node built-ins and returns a Blob.
|
|
527
|
-
//
|
|
528
|
-
//
|
|
529
|
-
//
|
|
530
|
-
|
|
526
|
+
// Browser bundle: avoids Node built-ins and returns a Blob. Loaded
|
|
527
|
+
// through the generated feature loaders, so it's absent from the
|
|
528
|
+
// bundle altogether when export.epub is off (this button isn't
|
|
529
|
+
// rendered then either). The browserified UMD nests its callable
|
|
530
|
+
// under `.default`, which Vite's dev (esbuild) and build (rollup)
|
|
531
|
+
// interops expose at slightly different depths — unwrap both.
|
|
532
|
+
const { loadEpub } = await import("blume:features");
|
|
533
|
+
if (!loadEpub) {
|
|
534
|
+
throw new Error("EPUB export is not enabled for this site.");
|
|
535
|
+
}
|
|
536
|
+
const epubModule = await loadEpub();
|
|
531
537
|
const epub = epubModule.default?.default ?? epubModule.default;
|
|
532
538
|
// The article HTML already opens with the page's <h1>, so don't let the
|
|
533
539
|
// generator prepend a second chapter-title heading.
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
+
import IconSprite from "./IconSprite.astro";
|
|
3
|
+
import { createIconSprite } from "../icon-sprite.ts";
|
|
2
4
|
// A full-width page layout for landing pages, marketing pages, dashboards — any
|
|
3
5
|
// page that wants Blume's chrome (document shell, header, theme, fonts) without
|
|
4
6
|
// the docs sidebar + prose + TOC grid that RootLayout hard-codes. It renders the
|
|
@@ -35,6 +37,7 @@ import {
|
|
|
35
37
|
import { buildStructuredData } from "../../seo/jsonld.ts";
|
|
36
38
|
import { normalizeXHandle } from "../../seo/x-handle.ts";
|
|
37
39
|
import { withBase } from "../islands/base-path.ts";
|
|
40
|
+
import { pageDirection, pageLocale } from "./page-locale.ts";
|
|
38
41
|
import { searchLocaleFor } from "./search-locale.ts";
|
|
39
42
|
import "blume:theme";
|
|
40
43
|
import { ClientRouter } from "astro:transitions";
|
|
@@ -108,7 +111,11 @@ interface Props {
|
|
|
108
111
|
*/
|
|
109
112
|
x?: { creator?: string; handle?: string };
|
|
110
113
|
noindex?: boolean;
|
|
111
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* Active locale + direction for `<html lang>`/`<html dir>`. Default to the
|
|
116
|
+
* locale Astro resolved from the URL (`Astro.currentLocale`), then the site
|
|
117
|
+
* default, and to that locale's configured direction.
|
|
118
|
+
*/
|
|
112
119
|
locale?: string;
|
|
113
120
|
dir?: "ltr" | "rtl";
|
|
114
121
|
/** Resolved UI dictionary; English baseline when omitted. */
|
|
@@ -145,13 +152,19 @@ const {
|
|
|
145
152
|
structuredDataEnabled,
|
|
146
153
|
x,
|
|
147
154
|
noindex,
|
|
148
|
-
locale
|
|
149
|
-
dir
|
|
155
|
+
locale: localeProp,
|
|
156
|
+
dir: dirProp,
|
|
150
157
|
ui,
|
|
151
158
|
localeSwitch,
|
|
152
159
|
clientData,
|
|
153
160
|
} = Astro.props;
|
|
154
161
|
|
|
162
|
+
// The locale and direction: the page's own values when it passes them (the
|
|
163
|
+
// content catch-all always does), else what Astro's i18n routing resolved for
|
|
164
|
+
// this URL, else the site default (see `page-locale.ts`).
|
|
165
|
+
const locale = pageLocale(data.config.i18n, localeProp, Astro.currentLocale);
|
|
166
|
+
const dir = dirProp ?? pageDirection(data.config.i18n, locale);
|
|
167
|
+
|
|
155
168
|
const clientDataJson = clientData
|
|
156
169
|
? JSON.stringify(clientData).replaceAll("<", "\\u003c")
|
|
157
170
|
: null;
|
|
@@ -232,6 +245,10 @@ const structuredDataJson = structuredData
|
|
|
232
245
|
: null;
|
|
233
246
|
|
|
234
247
|
const bannerKey = banner?.dismissible ? banner.key : null;
|
|
248
|
+
|
|
249
|
+
// Start the page's icon sprite before anything renders an icon (see
|
|
250
|
+
// icon-sprite.ts); <IconSprite /> at the end of the body emits it.
|
|
251
|
+
createIconSprite(Astro.locals);
|
|
235
252
|
---
|
|
236
253
|
|
|
237
254
|
<!doctype html>
|
|
@@ -401,5 +418,6 @@ const bannerKey = banner?.dismissible ? banner.key : null;
|
|
|
401
418
|
|
|
402
419
|
syncDrawerInert();
|
|
403
420
|
</script>
|
|
421
|
+
<IconSprite />
|
|
404
422
|
</body>
|
|
405
423
|
</html>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
+
import IconSprite from "./IconSprite.astro";
|
|
3
|
+
import { createIconSprite } from "../icon-sprite.ts";
|
|
2
4
|
import "blume:theme";
|
|
3
5
|
import data from "blume:data";
|
|
4
6
|
import type { BlumeFavicon } from "../../core/data.ts";
|
|
@@ -17,6 +19,7 @@ import {
|
|
|
17
19
|
THEME_INIT_SCRIPT,
|
|
18
20
|
} from "./head-scripts.ts";
|
|
19
21
|
import Header from "./Header.astro";
|
|
22
|
+
import { pageDirection, pageLocale } from "./page-locale.ts";
|
|
20
23
|
import { searchLocaleFor } from "./search-locale.ts";
|
|
21
24
|
|
|
22
25
|
// A minimal shell for the Scalar API/AsyncAPI reference: Blume's banner + navbar
|
|
@@ -66,9 +69,12 @@ interface Props {
|
|
|
66
69
|
pageTitle: string;
|
|
67
70
|
/** Keep the reference route out of crawler indexes. */
|
|
68
71
|
noindex?: boolean;
|
|
69
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* Active locale code for `<html lang>`. Defaults to the locale Astro
|
|
74
|
+
* resolved from the URL (`Astro.currentLocale`), then the site default.
|
|
75
|
+
*/
|
|
70
76
|
locale?: string;
|
|
71
|
-
/** Text direction for `<html dir
|
|
77
|
+
/** Text direction for `<html dir>`; defaults to the locale's configured direction. */
|
|
72
78
|
dir?: "ltr" | "rtl";
|
|
73
79
|
/** Resolved UI dictionary; English baseline when omitted. */
|
|
74
80
|
ui?: UIStrings;
|
|
@@ -88,16 +94,26 @@ const {
|
|
|
88
94
|
searchEnabled,
|
|
89
95
|
pageTitle,
|
|
90
96
|
noindex = false,
|
|
91
|
-
locale
|
|
92
|
-
dir
|
|
97
|
+
locale: localeProp,
|
|
98
|
+
dir: dirProp,
|
|
93
99
|
ui,
|
|
94
100
|
} = Astro.props;
|
|
95
101
|
|
|
102
|
+
// The locale and direction: the page's own values when it passes them (the
|
|
103
|
+
// content catch-all always does), else what Astro's i18n routing resolved for
|
|
104
|
+
// this URL, else the site default (see `page-locale.ts`).
|
|
105
|
+
const locale = pageLocale(data.config.i18n, localeProp, Astro.currentLocale);
|
|
106
|
+
const dir = dirProp ?? pageDirection(data.config.i18n, locale);
|
|
107
|
+
|
|
96
108
|
const strings = ui ?? EN_UI;
|
|
97
109
|
// Scope search to the reference page's language on a multi-locale site.
|
|
98
110
|
const searchLocale = searchLocaleFor(data.config.i18n, locale);
|
|
99
111
|
|
|
100
112
|
const bannerKey = banner?.dismissible ? banner.key : null;
|
|
113
|
+
|
|
114
|
+
// Start the page's icon sprite before anything renders an icon (see
|
|
115
|
+
// icon-sprite.ts); <IconSprite /> at the end of the body emits it.
|
|
116
|
+
createIconSprite(Astro.locals);
|
|
101
117
|
---
|
|
102
118
|
|
|
103
119
|
<!doctype html>
|
|
@@ -142,5 +158,6 @@ const bannerKey = banner?.dismissible ? banner.key : null;
|
|
|
142
158
|
<script is:inline set:html={SCALAR_THEME_INIT_SCRIPT} />
|
|
143
159
|
</div>
|
|
144
160
|
<WebMcp />
|
|
161
|
+
<IconSprite />
|
|
145
162
|
</body>
|
|
146
163
|
</html>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
+
import IconSprite from "./IconSprite.astro";
|
|
3
|
+
import { createIconSprite } from "../icon-sprite.ts";
|
|
2
4
|
import data from "blume:data";
|
|
3
5
|
import { EN_UI } from "../../core/i18n-ui.ts";
|
|
4
6
|
import type { UIStrings } from "../../core/i18n-ui.ts";
|
|
@@ -54,10 +56,12 @@ import {
|
|
|
54
56
|
findBreadcrumbs,
|
|
55
57
|
flattenPages,
|
|
56
58
|
getPagination,
|
|
59
|
+
navGroupIds,
|
|
57
60
|
sidebarForRoute,
|
|
58
61
|
} from "./nav-utils.ts";
|
|
59
62
|
import NavTree from "./NavTree.astro";
|
|
60
63
|
import { resolveSlot } from "./overrides.ts";
|
|
64
|
+
import { pageDirection, pageLocale } from "./page-locale.ts";
|
|
61
65
|
import { searchLocaleFor } from "./search-locale.ts";
|
|
62
66
|
import PageActions from "./PageActions.astro";
|
|
63
67
|
import PageFeedback from "./PageFeedback.astro";
|
|
@@ -149,9 +153,12 @@ interface Props {
|
|
|
149
153
|
lastModified?: string | null;
|
|
150
154
|
noindex?: boolean;
|
|
151
155
|
structuredDataEnabled?: boolean;
|
|
152
|
-
/**
|
|
156
|
+
/**
|
|
157
|
+
* Active locale code for `<html lang>`. Defaults to the locale Astro
|
|
158
|
+
* resolved from the URL (`Astro.currentLocale`), then the site default.
|
|
159
|
+
*/
|
|
153
160
|
locale?: string;
|
|
154
|
-
/** Text direction for `<html dir
|
|
161
|
+
/** Text direction for `<html dir>`; defaults to the locale's configured direction. */
|
|
155
162
|
dir?: "ltr" | "rtl";
|
|
156
163
|
/**
|
|
157
164
|
* Direction of the page content's own language — differs from `dir` on a
|
|
@@ -177,6 +184,12 @@ interface Props {
|
|
|
177
184
|
} | null;
|
|
178
185
|
/** Viewed docs version for search filtering (`""` = current; `null`/absent = off). */
|
|
179
186
|
searchVersion?: string | null;
|
|
187
|
+
/**
|
|
188
|
+
* URL prefix of this page's deferred sidebar fragments (`/blume-nav/
|
|
189
|
+
* <version>/<locale>`), when the sidebar defers collapsed sections; absent,
|
|
190
|
+
* the sidebar renders every section in full. See NavTree.
|
|
191
|
+
*/
|
|
192
|
+
navFragmentBase?: string;
|
|
180
193
|
/**
|
|
181
194
|
* User layout-slot overrides from `components.ts` (`defineComponents`). Each
|
|
182
195
|
* key replaces the matching built-in; unknown keys are ignored. Wired slots:
|
|
@@ -242,8 +255,8 @@ const {
|
|
|
242
255
|
lastModified,
|
|
243
256
|
noindex,
|
|
244
257
|
structuredDataEnabled,
|
|
245
|
-
locale
|
|
246
|
-
dir
|
|
258
|
+
locale: localeProp,
|
|
259
|
+
dir: dirProp,
|
|
247
260
|
contentDir,
|
|
248
261
|
ui,
|
|
249
262
|
localeAlternates,
|
|
@@ -252,6 +265,7 @@ const {
|
|
|
252
265
|
versionSelector,
|
|
253
266
|
versionNotice,
|
|
254
267
|
searchVersion = null,
|
|
268
|
+
navFragmentBase,
|
|
255
269
|
layout = {},
|
|
256
270
|
clientData,
|
|
257
271
|
toc = { enabled: true, maxLevel: 3, minLevel: 2 },
|
|
@@ -259,6 +273,12 @@ const {
|
|
|
259
273
|
contentLayout = "default",
|
|
260
274
|
} = Astro.props;
|
|
261
275
|
|
|
276
|
+
// The locale and direction: the page's own values when it passes them (the
|
|
277
|
+
// content catch-all always does), else what Astro's i18n routing resolved for
|
|
278
|
+
// this URL, else the site default (see `page-locale.ts`).
|
|
279
|
+
const locale = pageLocale(data.config.i18n, localeProp, Astro.currentLocale);
|
|
280
|
+
const dir = dirProp ?? pageDirection(data.config.i18n, locale);
|
|
281
|
+
|
|
262
282
|
// Serialized once for island hooks; `<` escaped so content can't break the tag.
|
|
263
283
|
const clientDataJson = clientData
|
|
264
284
|
? JSON.stringify(clientData).replaceAll("<", "\\u003c")
|
|
@@ -386,6 +406,9 @@ const sidebar = sidebarForRoute(
|
|
|
386
406
|
page.route,
|
|
387
407
|
navigation.root
|
|
388
408
|
);
|
|
409
|
+
// Stable group ids over the full tree, so the scoped view above names its
|
|
410
|
+
// panels and deferred fragments the same way every other page does.
|
|
411
|
+
const navIds = navGroupIds(navigation.sidebar);
|
|
389
412
|
const activeTab = currentTabForRoute(
|
|
390
413
|
navigation.tabs,
|
|
391
414
|
page.route,
|
|
@@ -419,6 +442,10 @@ const structuredDataJson = structuredData
|
|
|
419
442
|
: null;
|
|
420
443
|
|
|
421
444
|
const bannerKey = banner?.dismissible ? banner.key : null;
|
|
445
|
+
|
|
446
|
+
// Start the page's icon sprite before anything renders an icon (see
|
|
447
|
+
// icon-sprite.ts); <IconSprite /> at the end of the body emits it.
|
|
448
|
+
createIconSprite(Astro.locals);
|
|
422
449
|
---
|
|
423
450
|
|
|
424
451
|
<!doctype html>
|
|
@@ -670,6 +697,8 @@ const bannerKey = banner?.dismissible ? banner.key : null;
|
|
|
670
697
|
<div class="lg:hidden">
|
|
671
698
|
<MobileNavSlot
|
|
672
699
|
currentRoute={page.route}
|
|
700
|
+
fragmentBase={navFragmentBase}
|
|
701
|
+
ids={navIds}
|
|
673
702
|
items={sidebar}
|
|
674
703
|
strings={navStrings}
|
|
675
704
|
/>
|
|
@@ -677,6 +706,8 @@ const bannerKey = banner?.dismissible ? banner.key : null;
|
|
|
677
706
|
<div class="hidden lg:block">
|
|
678
707
|
<SidebarSlot
|
|
679
708
|
currentRoute={page.route}
|
|
709
|
+
fragmentBase={navFragmentBase}
|
|
710
|
+
ids={navIds}
|
|
680
711
|
items={sidebar}
|
|
681
712
|
strings={navStrings}
|
|
682
713
|
/>
|
|
@@ -685,6 +716,8 @@ const bannerKey = banner?.dismissible ? banner.key : null;
|
|
|
685
716
|
) : (
|
|
686
717
|
<SidebarSlot
|
|
687
718
|
currentRoute={page.route}
|
|
719
|
+
fragmentBase={navFragmentBase}
|
|
720
|
+
ids={navIds}
|
|
688
721
|
items={sidebar}
|
|
689
722
|
strings={navStrings}
|
|
690
723
|
/>
|
|
@@ -782,8 +815,10 @@ const bannerKey = banner?.dismissible ? banner.key : null;
|
|
|
782
815
|
import { syncDrawerInert } from "./drawer-inert.ts";
|
|
783
816
|
import { chromeIcons as icons } from "../../theme/chrome-icons.ts";
|
|
784
817
|
// Registers the <blume-mermaid> custom element (emitted by ```mermaid
|
|
785
|
-
// fences)
|
|
786
|
-
|
|
818
|
+
// fences) when some page has one; Mermaid itself is lazy-loaded only on
|
|
819
|
+
// pages that use a diagram. The loader is null — and the element and
|
|
820
|
+
// library absent from the bundle — for a site with no diagrams.
|
|
821
|
+
import { loadMermaid } from "blume:features";
|
|
787
822
|
// Registers the <blume-toc> custom element: scrollspy for the table of
|
|
788
823
|
// contents, highlighting the section currently in view as you scroll.
|
|
789
824
|
import "./toc-element.ts";
|
|
@@ -791,6 +826,8 @@ const bannerKey = banner?.dismissible ? banner.key : null;
|
|
|
791
826
|
// Tree-shaken out of production builds.
|
|
792
827
|
import "./hydration-hint.ts";
|
|
793
828
|
|
|
829
|
+
loadMermaid?.();
|
|
830
|
+
|
|
794
831
|
// Runs once per real page load; re-syncs itself after client-router
|
|
795
832
|
// swaps (see drawer-inert.ts).
|
|
796
833
|
syncDrawerInert();
|
|
@@ -1044,5 +1081,6 @@ const bannerKey = banner?.dismissible ? banner.key : null;
|
|
|
1044
1081
|
}
|
|
1045
1082
|
</style>
|
|
1046
1083
|
<WebMcp />
|
|
1084
|
+
<IconSprite />
|
|
1047
1085
|
</body>
|
|
1048
1086
|
</html>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { NavNode } from "../../core/types.ts";
|
|
2
|
+
import type { IconSymbol } from "../icon-sprite.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Build-time cache of rendered sidebar subtrees. A group that does not contain
|
|
6
|
+
* the current page renders identically on every page (no `aria-current`, no
|
|
7
|
+
* forced-open ancestor), so its HTML is rendered once and reused across the
|
|
8
|
+
* build — keyed on the group node's identity (a regenerated navigation is a
|
|
9
|
+
* new object, so a stale tree is never served) and the render variant (the
|
|
10
|
+
* panel id prefix and localized labels). The in-flight promise is what's
|
|
11
|
+
* stored, so concurrent page renders share one render of the same subtree.
|
|
12
|
+
*/
|
|
13
|
+
/** A cached subtree: its HTML and the sprite symbols that HTML references. */
|
|
14
|
+
export interface CachedNavSubtree {
|
|
15
|
+
html: string;
|
|
16
|
+
/** Symbol id → symbol, so a later page can register them into its sprite. */
|
|
17
|
+
icons: [string, IconSymbol][];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const cache = new WeakMap<NavNode, Map<string, Promise<CachedNavSubtree>>>();
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The cached subtree of `node` for `variant` — its HTML plus the icon sprite
|
|
24
|
+
* symbols it references, since the HTML is reused on pages that never
|
|
25
|
+
* rendered those icons themselves — rendering it with `render` on the first
|
|
26
|
+
* request. `enabled: false` always renders (the dev
|
|
27
|
+
* server, where an edited component must show its change on the next request).
|
|
28
|
+
*/
|
|
29
|
+
export const cachedNavSubtree = (
|
|
30
|
+
node: NavNode,
|
|
31
|
+
variant: string,
|
|
32
|
+
render: () => Promise<CachedNavSubtree>,
|
|
33
|
+
enabled = true
|
|
34
|
+
): Promise<CachedNavSubtree> => {
|
|
35
|
+
if (!enabled) {
|
|
36
|
+
return render();
|
|
37
|
+
}
|
|
38
|
+
let byVariant = cache.get(node);
|
|
39
|
+
if (!byVariant) {
|
|
40
|
+
byVariant = new Map();
|
|
41
|
+
cache.set(node, byVariant);
|
|
42
|
+
}
|
|
43
|
+
let html = byVariant.get(variant);
|
|
44
|
+
if (!html) {
|
|
45
|
+
html = render();
|
|
46
|
+
byVariant.set(variant, html);
|
|
47
|
+
}
|
|
48
|
+
return html;
|
|
49
|
+
};
|