blume 0.6.0 → 0.6.2
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/dist/cli/index.js +6070 -5718
- package/dist/cli/index.js.map +41 -40
- package/dist/types/core/config-input.d.ts +749 -0
- package/dist/types/core/config.d.ts +126 -3
- package/dist/types/core/schema.d.ts +10 -27
- package/dist/types/core/sources/types.d.ts +6 -0
- package/dist/types/index.d.ts +2 -1
- package/docs/advanced/changelog.mdx +10 -2
- package/docs/configuration/index.mdx +0 -2
- package/docs/content/syntax.mdx +4 -8
- package/package.json +1 -1
- package/src/astro/generate.ts +59 -24
- package/src/astro/markdown-negotiation.ts +12 -3
- package/src/astro/templates.ts +94 -12
- package/src/cli/commands/build.ts +26 -1
- package/src/cli/commands/dev.ts +30 -14
- package/src/cli/commands/doctor.ts +35 -7
- package/src/cli/commands/sync.ts +14 -2
- package/src/cli/dev-lock.ts +40 -10
- package/src/cli/env.ts +5 -1
- package/src/components/content/Update.astro +12 -2
- package/src/components/content/changelog-element.ts +62 -0
- package/src/components/islands/ask-ai.tsx +3 -1
- package/src/components/islands/hooks.ts +5 -1
- package/src/components/layout/Header.astro +10 -2
- package/src/components/layout/NavSelector.astro +5 -3
- package/src/components/layout/PageLayout.astro +2 -1
- package/src/components/layout/ReferenceLayout.astro +1 -0
- package/src/components/layout/RootLayout.astro +47 -10
- package/src/components/layout/Search.astro +8 -3
- package/src/components/layout/nav-utils.ts +7 -3
- package/src/core/config-input.ts +923 -0
- package/src/core/config.ts +126 -3
- package/src/core/i18n.ts +6 -5
- package/src/core/links.ts +16 -1
- package/src/core/meta.ts +112 -52
- package/src/core/navigation.ts +15 -5
- package/src/core/project-graph.ts +68 -2
- package/src/core/schema.ts +8 -14
- package/src/core/sources/assets.ts +21 -5
- package/src/core/sources/cache.ts +19 -1
- package/src/core/sources/github-releases.ts +9 -3
- package/src/core/sources/mdx-remote.ts +14 -4
- package/src/core/sources/normalize.ts +13 -1
- package/src/core/sources/notion.ts +43 -7
- package/src/core/sources/resolve.ts +44 -1
- package/src/core/sources/sanity.ts +9 -3
- package/src/core/sources/types.ts +6 -0
- package/src/deploy/adapter-output.ts +82 -0
- package/src/deploy/rss.ts +3 -1
- package/src/index.ts +1 -1
- package/src/markdown/code-title.ts +11 -4
- package/src/markdown/index.ts +28 -30
- package/src/markdown/math.ts +3 -2
- package/src/markdown/package-commands.ts +13 -0
- package/src/og/card.ts +3 -1
- package/src/openapi/model.ts +2 -1
- package/src/openapi/parse.ts +9 -1
- package/src/openapi/references.ts +11 -1
- package/src/openapi/render-mdx.ts +30 -3
- package/src/openapi/source.ts +3 -1
- package/src/registry/eject.ts +21 -14
- package/src/search/documents.ts +4 -1
- package/src/theme/entry.ts +10 -3
- package/src/theme/icons.ts +7 -11
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// <details>/<summary> like the language switcher.
|
|
6
6
|
import type { NavSelector } from "../../core/types.ts";
|
|
7
7
|
import Icon from "../Icon.astro";
|
|
8
|
+
import { isUnderPath } from "./nav-utils.ts";
|
|
8
9
|
|
|
9
10
|
interface Props {
|
|
10
11
|
selector: NavSelector;
|
|
@@ -13,12 +14,13 @@ interface Props {
|
|
|
13
14
|
|
|
14
15
|
const { selector, route } = Astro.props;
|
|
15
16
|
|
|
16
|
-
// The active item is the deepest path
|
|
17
|
-
//
|
|
17
|
+
// The active item is the deepest path the current route sits under (on a path
|
|
18
|
+
// boundary, so `/api` never claims `/api-reference` routes), falling back to
|
|
19
|
+
// the first item so the summary always shows something meaningful.
|
|
18
20
|
const active =
|
|
19
21
|
selector.items.find((item) => item.path === route) ??
|
|
20
22
|
selector.items
|
|
21
|
-
.filter((item) => route
|
|
23
|
+
.filter((item) => isUnderPath(route, item.path))
|
|
22
24
|
.toSorted((a, b) => b.path.length - a.path.length)[0] ??
|
|
23
25
|
selector.items[0];
|
|
24
26
|
|
|
@@ -32,6 +32,7 @@ import Favicon from "./Favicon.astro";
|
|
|
32
32
|
import Fonts from "./Fonts.astro";
|
|
33
33
|
import { bannerInitScript, themeInitScript } from "./head-scripts.ts";
|
|
34
34
|
import Header from "./Header.astro";
|
|
35
|
+
import { isUnderPath } from "./nav-utils.ts";
|
|
35
36
|
|
|
36
37
|
interface Props {
|
|
37
38
|
site: { title: string; description?: string };
|
|
@@ -209,7 +210,7 @@ const bannerScript = banner?.dismissible
|
|
|
209
210
|
<a
|
|
210
211
|
aria-current={
|
|
211
212
|
route === tab.path ||
|
|
212
|
-
(tab.path !== "/" && route
|
|
213
|
+
(tab.path !== "/" && isUnderPath(route, tab.path))
|
|
213
214
|
? "page"
|
|
214
215
|
: undefined
|
|
215
216
|
}
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
findBreadcrumbs,
|
|
24
24
|
flattenPages,
|
|
25
25
|
getPagination,
|
|
26
|
+
isUnderPath,
|
|
26
27
|
sidebarForRoute,
|
|
27
28
|
} from "./nav-utils.ts";
|
|
28
29
|
import NavTree from "./NavTree.astro";
|
|
@@ -126,6 +127,12 @@ interface Props {
|
|
|
126
127
|
clientData?: BlumeClientData | null;
|
|
127
128
|
/** Table-of-contents settings (`toc` config): visibility + heading range. */
|
|
128
129
|
toc?: { enabled: boolean; maxLevel: number; minLevel: number };
|
|
130
|
+
/**
|
|
131
|
+
* Content-column preset. `"bare"` (the generated changelog index) drops both
|
|
132
|
+
* the sidebar and the table of contents and centers a single wide column;
|
|
133
|
+
* everything else uses the standard sidebar + content + TOC grid.
|
|
134
|
+
*/
|
|
135
|
+
contentLayout?: "default" | "bare";
|
|
129
136
|
}
|
|
130
137
|
|
|
131
138
|
const {
|
|
@@ -169,6 +176,7 @@ const {
|
|
|
169
176
|
layout = {},
|
|
170
177
|
clientData,
|
|
171
178
|
toc = { enabled: true, maxLevel: 3, minLevel: 2 },
|
|
179
|
+
contentLayout = "default",
|
|
172
180
|
} = Astro.props;
|
|
173
181
|
|
|
174
182
|
// Serialized once for island hooks; `<` escaped so content can't break the tag.
|
|
@@ -205,16 +213,28 @@ const searchLocale =
|
|
|
205
213
|
// API operation pages own a two-column body (docs + request panel) and their own
|
|
206
214
|
// right rail, so they drop the table of contents and widen the content column.
|
|
207
215
|
const isApiOperation = pageType === "openapi-operation";
|
|
216
|
+
// A "bare" page (the changelog index) is a standalone landing: no sidebar, no
|
|
217
|
+
// TOC, just a wider centered column of content.
|
|
218
|
+
const isBare = contentLayout === "bare";
|
|
219
|
+
const showSidebar = !isBare;
|
|
220
|
+
const showToc = !(isApiOperation || isBare);
|
|
208
221
|
const tocHeadings =
|
|
209
|
-
toc.enabled &&
|
|
222
|
+
toc.enabled && showToc
|
|
210
223
|
? headings.filter((h) => h.depth >= toc.minLevel && h.depth <= toc.maxLevel)
|
|
211
224
|
: [];
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
225
|
+
let gridClass =
|
|
226
|
+
"lg:grid-cols-[17.5rem_minmax(0,1fr)] xl:grid-cols-[17.5rem_minmax(0,1fr)_17.5rem]";
|
|
227
|
+
if (isBare) {
|
|
228
|
+
gridClass = "";
|
|
229
|
+
} else if (isApiOperation) {
|
|
230
|
+
gridClass = "lg:grid-cols-[17.5rem_minmax(0,1fr)]";
|
|
231
|
+
}
|
|
232
|
+
let articleClass = "prose mx-auto max-w-[42rem]";
|
|
233
|
+
if (isBare) {
|
|
234
|
+
articleClass = "prose mx-auto max-w-[54rem]";
|
|
235
|
+
} else if (isApiOperation) {
|
|
236
|
+
articleClass = "prose max-w-none";
|
|
237
|
+
}
|
|
218
238
|
const pageTitle = page.title ? `${page.title} - ${site.title}` : site.title;
|
|
219
239
|
const description = page.description ?? site.description;
|
|
220
240
|
|
|
@@ -347,6 +367,8 @@ const bannerScript = banner?.dismissible
|
|
|
347
367
|
class:list={["mx-auto grid grid-cols-1 items-start", gridClass]}
|
|
348
368
|
data-blume-doc-grid
|
|
349
369
|
>
|
|
370
|
+
{
|
|
371
|
+
showSidebar && (
|
|
350
372
|
<aside
|
|
351
373
|
aria-label="Primary"
|
|
352
374
|
class="fixed top-[var(--blume-drawer-top,4rem)] start-0 z-[35] h-[calc(100dvh-var(--blume-drawer-top,4rem))] w-64 max-w-[80vw] -translate-x-[105%] overflow-y-auto border-border border-e bg-background px-5 pt-4 pb-6 transition-transform rtl:translate-x-[105%] [:where([data-blume-nav-open])_&]:translate-x-0! lg:sticky lg:top-16 lg:z-auto lg:h-[calc(100dvh-4rem)] lg:w-auto lg:max-w-none lg:translate-x-0! lg:border-e-0 lg:bg-transparent lg:px-4"
|
|
@@ -406,7 +428,7 @@ const bannerScript = banner?.dismissible
|
|
|
406
428
|
<a
|
|
407
429
|
aria-current={
|
|
408
430
|
page.route === tab.path ||
|
|
409
|
-
(tab.path !== "/" && page.route
|
|
431
|
+
(tab.path !== "/" && isUnderPath(page.route, tab.path))
|
|
410
432
|
? "page"
|
|
411
433
|
: undefined
|
|
412
434
|
}
|
|
@@ -438,6 +460,8 @@ const bannerScript = banner?.dismissible
|
|
|
438
460
|
}
|
|
439
461
|
</nav>
|
|
440
462
|
</aside>
|
|
463
|
+
)
|
|
464
|
+
}
|
|
441
465
|
<main class="px-6 pt-6 pb-10 lg:px-8 xl:px-10" id="blume-content">
|
|
442
466
|
<BreadcrumbsSlot crumbs={crumbs} wide={isApiOperation} />
|
|
443
467
|
<TableOfContentsSlot
|
|
@@ -469,7 +493,7 @@ const bannerScript = banner?.dismissible
|
|
|
469
493
|
}
|
|
470
494
|
</main>
|
|
471
495
|
{
|
|
472
|
-
|
|
496
|
+
showToc && (
|
|
473
497
|
<aside
|
|
474
498
|
aria-label={strings.toc.title}
|
|
475
499
|
class="sticky top-16 hidden h-[calc(100dvh-4rem)] overflow-y-auto px-4 pt-6 pb-10 text-sm xl:block"
|
|
@@ -578,7 +602,20 @@ const bannerScript = banner?.dismissible
|
|
|
578
602
|
button.setAttribute("aria-label", "Copy code");
|
|
579
603
|
button.innerHTML = svg("copy");
|
|
580
604
|
button.addEventListener("click", async () => {
|
|
581
|
-
const
|
|
605
|
+
const code = pre.querySelector("code");
|
|
606
|
+
let text = code?.textContent ?? "";
|
|
607
|
+
// Twoslash nests each hover popup's type signature and docs inside
|
|
608
|
+
// the <code>; copying textContent verbatim would interleave them
|
|
609
|
+
// with the source. Strip the popups from a clone first.
|
|
610
|
+
if (code?.querySelector(".twoslash-popup-container")) {
|
|
611
|
+
const clone = code.cloneNode(true) as HTMLElement;
|
|
612
|
+
for (const popup of clone.querySelectorAll(
|
|
613
|
+
".twoslash-popup-container"
|
|
614
|
+
)) {
|
|
615
|
+
popup.remove();
|
|
616
|
+
}
|
|
617
|
+
text = clone.textContent ?? "";
|
|
618
|
+
}
|
|
582
619
|
try {
|
|
583
620
|
await navigator.clipboard.writeText(text);
|
|
584
621
|
} catch {
|
|
@@ -133,7 +133,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
133
133
|
<script
|
|
134
134
|
data-blume-search-popular
|
|
135
135
|
is:inline
|
|
136
|
-
set:html={JSON.stringify(popular)}
|
|
136
|
+
set:html={JSON.stringify(popular).replaceAll("<", "\\u003c")}
|
|
137
137
|
type="application/json"
|
|
138
138
|
/>
|
|
139
139
|
|
|
@@ -190,6 +190,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
190
190
|
selectables: Selectable[] = [];
|
|
191
191
|
selectedIndex = -1;
|
|
192
192
|
activeSection: string | null = null;
|
|
193
|
+
renderGeneration = 0;
|
|
193
194
|
previewOn = true;
|
|
194
195
|
devOnlyMsg = "Search is available in the production build.";
|
|
195
196
|
noResultsMsg = "No results found.";
|
|
@@ -339,6 +340,8 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
339
340
|
|
|
340
341
|
async render() {
|
|
341
342
|
const query = this.input.value.trim();
|
|
343
|
+
this.renderGeneration += 1;
|
|
344
|
+
const generation = this.renderGeneration;
|
|
342
345
|
this.selectables = [];
|
|
343
346
|
this.selectedIndex = -1;
|
|
344
347
|
this.results.replaceChildren();
|
|
@@ -364,8 +367,10 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
364
367
|
locale: localeFilter,
|
|
365
368
|
section: this.activeSection ?? undefined,
|
|
366
369
|
});
|
|
367
|
-
//
|
|
368
|
-
|
|
370
|
+
// Any newer render — a keystroke, a section pill, a locale toggle —
|
|
371
|
+
// supersedes this one mid-await, even for the same query text;
|
|
372
|
+
// appending the stale hits would duplicate rows and desync selection.
|
|
373
|
+
if (generation !== this.renderGeneration) {
|
|
369
374
|
return;
|
|
370
375
|
}
|
|
371
376
|
|
|
@@ -71,9 +71,13 @@ export const findBreadcrumbs = (nodes: NavNode[], route: string): Crumb[] => {
|
|
|
71
71
|
return search(nodes, []) ?? [];
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
-
/**
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Whether `route` is the section root `base` or nested beneath it. Requires a
|
|
76
|
+
* path boundary, so `/api-reference` is not under `/api`. The root `/` spans
|
|
77
|
+
* every route.
|
|
78
|
+
*/
|
|
79
|
+
export const isUnderPath = (route: string, base: string): boolean =>
|
|
80
|
+
base === "/" || route === base || route.startsWith(`${base}/`);
|
|
77
81
|
|
|
78
82
|
/**
|
|
79
83
|
* The tab whose `path` is the longest prefix of `route`, mirroring the header's
|