blume 0.6.7 → 0.8.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 +618 -0
- package/LICENSE +21 -0
- package/README.md +107 -0
- package/dist/cli/index.js +2609 -1041
- package/dist/cli/index.js.map +110 -103
- package/dist/types/ai/component-markdown.d.ts +34 -0
- package/dist/types/components/content/youtube.d.ts +18 -0
- package/dist/types/core/base-path.d.ts +47 -0
- package/dist/types/core/config-input.d.ts +110 -12
- package/dist/types/core/config.d.ts +6 -4
- package/dist/types/core/data.d.ts +4 -0
- package/dist/types/core/i18n-ui.d.ts +477 -135
- package/dist/types/core/schema.d.ts +309 -195
- package/dist/types/core/sources/types.d.ts +2 -0
- package/dist/types/core/types.d.ts +6 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/openapi/references.d.ts +60 -0
- package/docs/01-quickstart.mdx +5 -2
- package/docs/02-deployment.mdx +24 -9
- package/docs/03-faq.mdx +46 -16
- package/docs/advanced/custom-pages.mdx +1 -1
- package/docs/advanced/skills.mdx +1 -1
- package/docs/configuration/ai.mdx +49 -10
- package/docs/configuration/customization.mdx +11 -0
- package/docs/configuration/index.mdx +33 -3
- package/docs/configuration/seo.mdx +2 -2
- package/docs/content/components.mdx +30 -3
- package/docs/content/i18n.mdx +1 -1
- package/docs/content/islands.mdx +8 -0
- package/docs/content/navigation.mdx +3 -3
- package/docs/content/sources.mdx +1 -1
- package/docs/content/syntax.mdx +17 -2
- package/docs/index.mdx +2 -2
- package/docs/reference/cli.mdx +8 -6
- package/package.json +15 -4
- package/skills/blume/SKILL.md +5 -3
- package/skills/blume-update-docs/SKILL.md +3 -2
- package/src/ai/agent-readability.ts +11 -5
- package/src/ai/ask-context.ts +7 -2
- package/src/ai/ask-data.ts +3 -0
- package/src/ai/ask.ts +12 -7
- package/src/ai/component-markdown.ts +461 -0
- package/src/ai/llms.ts +143 -23
- package/src/ai/markdown.ts +35 -6
- package/src/ai/mcp/data.ts +33 -8
- package/src/ai/mcp/discovery.ts +10 -3
- package/src/ai/mcp/server.ts +24 -7
- package/src/ai/visibility.ts +74 -0
- package/src/astro/component-slots.ts +16 -4
- package/src/astro/examples.ts +12 -7
- package/src/astro/generate.ts +393 -189
- package/src/astro/index.ts +5 -1
- package/src/astro/integration.ts +9 -5
- package/src/astro/islands.ts +11 -5
- package/src/astro/markdown-negotiation.ts +2 -2
- package/src/astro/pages.ts +89 -22
- package/src/astro/templates.ts +259 -25
- package/src/blume-modules.d.ts +8 -0
- package/src/cli/commands/build.ts +131 -38
- package/src/cli/commands/check.ts +1 -1
- package/src/cli/commands/dev.ts +71 -17
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/eject.ts +47 -19
- package/src/cli/commands/init.ts +120 -180
- package/src/cli/commands/preview.ts +4 -1
- package/src/cli/commands/validate.ts +44 -2
- package/src/cli/dev-lock.ts +34 -19
- package/src/cli/eject-scripts.ts +72 -0
- package/src/cli/env.ts +15 -5
- package/src/cli/init/questions.ts +158 -0
- package/src/cli/init/scaffold.ts +380 -0
- package/src/cli/required-secrets.ts +2 -1
- package/src/components/content/AccordionItem.astro +23 -4
- package/src/components/content/Badge.astro +3 -1
- package/src/components/content/Card.astro +4 -2
- package/src/components/content/CodeBlock.astro +3 -0
- package/src/components/content/Component.astro +30 -16
- package/src/components/content/Diff.astro +3 -1
- package/src/components/content/Step.astro +10 -1
- package/src/components/content/Tabs.astro +15 -3
- package/src/components/content/Tile.astro +2 -1
- package/src/components/content/Tooltip.astro +3 -1
- package/src/components/content/Update.astro +9 -2
- package/src/components/content/auto-type-table.ts +25 -9
- package/src/components/content/base-href.ts +33 -0
- package/src/components/content/changelog-element.ts +9 -2
- package/src/components/content/diff.ts +12 -6
- package/src/components/content/mermaid-element.ts +10 -2
- package/src/components/index.ts +23 -1
- package/src/components/islands/AskAI.astro +5 -2
- package/src/components/islands/ask-ai.tsx +68 -12
- package/src/components/islands/base-path.ts +28 -0
- package/src/components/islands/hooks.ts +44 -9
- package/src/components/layout/Banner.astro +12 -3
- package/src/components/layout/Breadcrumbs.astro +2 -1
- package/src/components/layout/Favicon.astro +3 -2
- package/src/components/layout/Header.astro +15 -5
- package/src/components/layout/LanguageSwitcher.astro +2 -1
- package/src/components/layout/Logo.astro +13 -4
- package/src/components/layout/NavSelector.astro +2 -1
- package/src/components/layout/NavTree.astro +22 -7
- package/src/components/layout/PageActions.astro +25 -10
- package/src/components/layout/PageFeedback.astro +4 -1
- package/src/components/layout/PageLayout.astro +51 -9
- package/src/components/layout/Pagination.astro +3 -2
- package/src/components/layout/ReferenceLayout.astro +8 -1
- package/src/components/layout/RootLayout.astro +74 -13
- package/src/components/layout/Search.astro +107 -27
- package/src/components/layout/nav-utils.ts +18 -10
- package/src/components/layout/search/algolia.ts +11 -2
- package/src/components/layout/search/endpoint.ts +11 -5
- package/src/components/layout/search/orama-cloud.ts +8 -2
- package/src/components/layout/search/pagefind.ts +3 -0
- package/src/components/layout/search/types.ts +5 -1
- package/src/components/layout/search/typesense.ts +4 -1
- package/src/components/layout/toc-element.ts +8 -2
- package/src/components/openapi/ApiTagOperations.astro +2 -1
- package/src/components/openapi/Operation.astro +47 -40
- package/src/components/openapi/RequestPanel.astro +8 -2
- package/src/components/openapi/helpers.ts +71 -3
- package/src/components/openapi/panel.ts +1 -1
- package/src/components/openapi/snippets.ts +25 -11
- package/src/core/base-path.ts +94 -0
- package/src/core/builtin-tags.ts +2 -0
- package/src/core/component-overrides.ts +103 -74
- package/src/core/config-input.ts +118 -17
- package/src/core/config.ts +8 -5
- package/src/core/content.ts +2 -0
- package/src/core/data.ts +4 -0
- package/src/core/diagnostics.ts +54 -34
- package/src/core/gitignore.ts +4 -1
- package/src/core/graph.ts +166 -88
- package/src/core/i18n-ui.ts +63 -3
- package/src/core/last-modified.ts +15 -6
- package/src/core/links.ts +69 -25
- package/src/core/manifest.ts +62 -45
- package/src/core/nav-diagnostics.ts +1 -1
- package/src/core/navigation.ts +144 -58
- package/src/core/package-json.ts +17 -2
- package/src/core/project-graph.ts +25 -15
- package/src/core/schema.ts +605 -620
- package/src/core/sources/assets.ts +6 -1
- package/src/core/sources/filesystem.ts +4 -0
- package/src/core/sources/github-releases.ts +2 -1
- package/src/core/sources/mdx-remote.ts +76 -63
- package/src/core/sources/normalize.ts +236 -91
- package/src/core/sources/notion.ts +27 -18
- package/src/core/sources/types.ts +2 -0
- package/src/core/tsconfig-aliases.ts +59 -30
- package/src/core/types.ts +6 -1
- package/src/core/ui-packs/ar.ts +1 -0
- package/src/core/ui-packs/bg.ts +1 -0
- package/src/core/ui-packs/bn.ts +1 -0
- package/src/core/ui-packs/ca.ts +1 -0
- package/src/core/ui-packs/cs.ts +1 -0
- package/src/core/ui-packs/da.ts +1 -0
- package/src/core/ui-packs/de.ts +1 -0
- package/src/core/ui-packs/el.ts +1 -0
- package/src/core/ui-packs/es.ts +1 -0
- package/src/core/ui-packs/fa.ts +1 -0
- package/src/core/ui-packs/fi.ts +1 -0
- package/src/core/ui-packs/fr.ts +2 -1
- package/src/core/ui-packs/he.ts +1 -0
- package/src/core/ui-packs/hi.ts +1 -0
- package/src/core/ui-packs/hr.ts +1 -0
- package/src/core/ui-packs/hu.ts +1 -0
- package/src/core/ui-packs/id.ts +1 -0
- package/src/core/ui-packs/it.ts +1 -0
- package/src/core/ui-packs/ja.ts +1 -0
- package/src/core/ui-packs/ko.ts +1 -0
- package/src/core/ui-packs/nl.ts +1 -0
- package/src/core/ui-packs/no.ts +1 -0
- package/src/core/ui-packs/pl.ts +1 -0
- package/src/core/ui-packs/pt-br.ts +1 -0
- package/src/core/ui-packs/pt.ts +1 -0
- package/src/core/ui-packs/ro.ts +1 -0
- package/src/core/ui-packs/ru.ts +1 -0
- package/src/core/ui-packs/sk.ts +1 -0
- package/src/core/ui-packs/sr.ts +1 -0
- package/src/core/ui-packs/sv.ts +1 -0
- package/src/core/ui-packs/th.ts +1 -0
- package/src/core/ui-packs/tr.ts +1 -0
- package/src/core/ui-packs/uk.ts +1 -0
- package/src/core/ui-packs/vi.ts +1 -0
- package/src/core/ui-packs/zh-tw.ts +1 -0
- package/src/core/ui-packs/zh.ts +1 -0
- package/src/deploy/adapter-output.ts +18 -8
- package/src/deploy/redirects.ts +25 -2
- package/src/deploy/robots.ts +6 -1
- package/src/deploy/rss.ts +10 -3
- package/src/deploy/sitemap.ts +59 -13
- package/src/index.ts +5 -0
- package/src/markdown/base-links.ts +60 -0
- package/src/markdown/code-title.ts +11 -14
- package/src/markdown/index.ts +46 -9
- package/src/markdown/inline-code.ts +14 -4
- package/src/markdown/package-commands.ts +10 -4
- package/src/markdown/themes.ts +24 -0
- package/src/openapi/model.ts +15 -5
- package/src/openapi/parse.ts +21 -0
- package/src/openapi/references.ts +75 -21
- package/src/openapi/render-mdx.ts +11 -6
- package/src/openapi/scalar.ts +32 -16
- package/src/openapi/source.ts +59 -10
- package/src/registry/eject.ts +247 -19
- package/src/registry/registry.ts +0 -3
- package/src/search/build.ts +3 -0
- package/src/search/documents.ts +36 -4
- package/src/search/sync/typesense.ts +6 -4
- package/src/seo/jsonld.ts +28 -17
- package/src/theme/entry.ts +85 -20
|
@@ -5,14 +5,18 @@
|
|
|
5
5
|
// `<Component path="forms/login" />` for `examples/forms/login.tsx`.
|
|
6
6
|
//
|
|
7
7
|
// Blume discovers `examples/` at build time and generates `blume:examples`: a
|
|
8
|
-
// map of path → { Component (a static hydration wrapper), code, lang }
|
|
9
|
-
//
|
|
10
|
-
//
|
|
8
|
+
// map of path → { Component (a static hydration wrapper), code, lang } plus
|
|
9
|
+
// the route base preview frames live under. React, Vue, Svelte, and Astro
|
|
10
|
+
// examples are all supported. The source is highlighted with the same Shiki
|
|
11
|
+
// setup as ordinary code fences.
|
|
12
|
+
import data from "blume:data";
|
|
13
|
+
|
|
11
14
|
import { highlightCode } from "../../markdown/index.ts";
|
|
15
|
+
import { withBase } from "../islands/base-path.ts";
|
|
12
16
|
import Tab from "./Tab.astro";
|
|
13
17
|
import Tabs from "./Tabs.astro";
|
|
14
18
|
// Generated per project; resolves to an empty map when there are no examples.
|
|
15
|
-
import { examples } from "blume:examples";
|
|
19
|
+
import { examples, examplesBase } from "blume:examples";
|
|
16
20
|
|
|
17
21
|
interface Props {
|
|
18
22
|
path: string;
|
|
@@ -20,14 +24,27 @@ interface Props {
|
|
|
20
24
|
|
|
21
25
|
const { path } = Astro.props;
|
|
22
26
|
const entry = examples[path];
|
|
23
|
-
|
|
27
|
+
|
|
28
|
+
// The live preview renders in its own generated route, embedded as an iframe:
|
|
29
|
+
// the frame boundary is what keeps the docs CSS (prose typography, component
|
|
30
|
+
// chrome) out of the example entirely, so users can preview components styled
|
|
31
|
+
// by their own design system (e.g. shadcn, via the configured `examples.css`).
|
|
32
|
+
// The frame syncs its theme with this document before first paint.
|
|
33
|
+
const previewSrc = entry
|
|
34
|
+
? withBase(
|
|
35
|
+
`${examplesBase}/${path.split("/").map(encodeURIComponent).join("/")}`
|
|
36
|
+
)
|
|
37
|
+
: undefined;
|
|
24
38
|
|
|
25
39
|
// Highlight the source with the shared helper so it matches Markdown fences.
|
|
26
40
|
// `blume-source` makes the inner `<code>` (not the `<pre>`) the scroll/height-
|
|
27
41
|
// capped box — see the theme entry — so the copy button, pinned to the `<pre>`,
|
|
28
42
|
// stays put. This pane already lives inside `.prose`, so the theme styles it.
|
|
29
43
|
const codeHtml = entry
|
|
30
|
-
? await highlightCode(entry.code, entry.lang, {
|
|
44
|
+
? await highlightCode(entry.code, entry.lang, {
|
|
45
|
+
className: "blume-source",
|
|
46
|
+
themes: data.config.codeThemes,
|
|
47
|
+
})
|
|
31
48
|
: undefined;
|
|
32
49
|
|
|
33
50
|
// Both tabs share one height so toggling them never shifts the layout. Size it
|
|
@@ -45,16 +62,13 @@ const paneStyle = `height:${paneHeight}px`;
|
|
|
45
62
|
// `sync={false}`: each preview's Preview/Code tabs are independent — unlike
|
|
46
63
|
// CodeGroup, switching one Component must not switch the others.
|
|
47
64
|
<Tabs hash={false} sync={false}>
|
|
48
|
-
<Tab
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
title="Preview"
|
|
56
|
-
>
|
|
57
|
-
<Preview />
|
|
65
|
+
<Tab class="overflow-hidden p-0!" style={paneStyle} title="Preview">
|
|
66
|
+
<iframe
|
|
67
|
+
class="h-full w-full"
|
|
68
|
+
loading="lazy"
|
|
69
|
+
src={previewSrc}
|
|
70
|
+
title={`Preview of ${path}`}
|
|
71
|
+
/>
|
|
58
72
|
</Tab>
|
|
59
73
|
<Tab class="overflow-hidden" style={paneStyle} title="Code">
|
|
60
74
|
<Fragment set:html={codeHtml} />
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
//
|
|
7
7
|
// Inputs (one of): `patch`/`src` (unified patch), `before`+`after` (file paths),
|
|
8
8
|
// or `old`+`new` (inline strings, with optional `lang`).
|
|
9
|
+
import data from "blume:data";
|
|
10
|
+
|
|
9
11
|
import { renderDiff } from "./diff.ts";
|
|
10
12
|
|
|
11
13
|
interface Props {
|
|
@@ -22,7 +24,7 @@ let html: string | undefined;
|
|
|
22
24
|
let error: string | undefined;
|
|
23
25
|
|
|
24
26
|
try {
|
|
25
|
-
html = await renderDiff(Astro.props);
|
|
27
|
+
html = await renderDiff({ ...Astro.props, theme: data.config.codeThemes });
|
|
26
28
|
} catch (cause) {
|
|
27
29
|
error = cause instanceof Error ? cause.message : String(cause);
|
|
28
30
|
}
|
|
@@ -26,7 +26,16 @@ const canRenderIcon =
|
|
|
26
26
|
<span class:list={[markerClass, !canRenderIcon && "before:[content:counter(blume-step)]"]}>
|
|
27
27
|
{canRenderIcon && <Icon name={icon} size={14} />}
|
|
28
28
|
</span>
|
|
29
|
-
{
|
|
29
|
+
{
|
|
30
|
+
// Consumes the `titleSize` variables set by <Steps>; the fallbacks match
|
|
31
|
+
// its "p" default (and the prose-inherited sizing) so a bare <Step>
|
|
32
|
+
// renders identically.
|
|
33
|
+
title && (
|
|
34
|
+
<p class="mb-1.5 font-semibold text-[length:var(--blume-step-title-size,1rem)] leading-[var(--blume-step-title-line-height,1.75rem)]">
|
|
35
|
+
{title}
|
|
36
|
+
</p>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
30
39
|
<div class="[&>:first-child]:mt-0! [&>:last-child]:mb-0!">
|
|
31
40
|
<slot />
|
|
32
41
|
</div>
|
|
@@ -51,6 +51,10 @@ const hashEnabled = String(hash) !== "false";
|
|
|
51
51
|
"w-full rounded-blume border border-border bg-background px-3 py-2 font-medium text-foreground text-sm";
|
|
52
52
|
const SYNC_EVENT = "blume-tabs-change";
|
|
53
53
|
|
|
54
|
+
// Disambiguates generated trigger/panel ids when the same tab labels appear
|
|
55
|
+
// in several <Tabs> instances on one page.
|
|
56
|
+
let instanceCount = 0;
|
|
57
|
+
|
|
54
58
|
const panelTitle = (panel: HTMLElement, index: number) =>
|
|
55
59
|
panel.dataset.title ?? `Tab ${index + 1}`;
|
|
56
60
|
const panelId = (panel: HTMLElement) => panel.dataset.tabId ?? panel.id;
|
|
@@ -119,7 +123,8 @@ const hashEnabled = String(hash) !== "false";
|
|
|
119
123
|
if (this.dataset.dropdown === "true") {
|
|
120
124
|
this.#select = document.createElement("select");
|
|
121
125
|
this.#select.className = SELECT_CLASS;
|
|
122
|
-
|
|
126
|
+
// Neutral default: tab sets aren't necessarily code examples.
|
|
127
|
+
this.#select.setAttribute("aria-label", "Select tab");
|
|
123
128
|
panels.forEach((panel, index) => {
|
|
124
129
|
const option = document.createElement("option");
|
|
125
130
|
option.value = String(index);
|
|
@@ -131,6 +136,7 @@ const hashEnabled = String(hash) !== "false";
|
|
|
131
136
|
});
|
|
132
137
|
list.appendChild(this.#select);
|
|
133
138
|
} else {
|
|
139
|
+
const instance = ++instanceCount;
|
|
134
140
|
panels.forEach((panel, index) => {
|
|
135
141
|
const trigger = document.createElement("button");
|
|
136
142
|
trigger.type = "button";
|
|
@@ -138,9 +144,15 @@ const hashEnabled = String(hash) !== "false";
|
|
|
138
144
|
trigger.className = TRIGGER_CLASS;
|
|
139
145
|
appendTriggerContent(trigger, panel, index);
|
|
140
146
|
trigger.tabIndex = index === 0 ? 0 : -1;
|
|
141
|
-
|
|
142
|
-
|
|
147
|
+
// Wire tab and panel both ways. An author-supplied panel id is kept;
|
|
148
|
+
// otherwise derive unique ids from the label slug per instance.
|
|
149
|
+
const slug = panelId(panel) || `tab-${index + 1}`;
|
|
150
|
+
if (!panel.id) {
|
|
151
|
+
panel.id = `blume-tabpanel-${slug}-${instance}`;
|
|
143
152
|
}
|
|
153
|
+
trigger.id = `blume-tab-${slug}-${instance}`;
|
|
154
|
+
trigger.setAttribute("aria-controls", panel.id);
|
|
155
|
+
panel.setAttribute("aria-labelledby", trigger.id);
|
|
144
156
|
trigger.setAttribute(
|
|
145
157
|
"aria-selected",
|
|
146
158
|
index === 0 ? "true" : "false"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import Icon from "../Icon.astro";
|
|
3
|
+
import { contentHref } from "./base-href.ts";
|
|
3
4
|
|
|
4
5
|
const { description, href, title } = Astro.props;
|
|
5
6
|
const Tag = href ? "a" : "div";
|
|
@@ -11,7 +12,7 @@ const previewPattern =
|
|
|
11
12
|
<Tag
|
|
12
13
|
class="not-prose block overflow-hidden rounded-blume border border-border bg-background text-inherit no-underline! transition-colors hover:no-underline! hover:[&[href]]:border-accent"
|
|
13
14
|
data-blume-tile
|
|
14
|
-
href={href}
|
|
15
|
+
href={href === undefined ? undefined : contentHref(href)}
|
|
15
16
|
rel={external ? "noreferrer" : undefined}
|
|
16
17
|
target={external ? "_blank" : undefined}
|
|
17
18
|
>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
|
|
4
|
+
import { contentHref } from "./base-href.ts";
|
|
5
|
+
|
|
4
6
|
interface Props {
|
|
5
7
|
cta?: string;
|
|
6
8
|
headline?: string;
|
|
@@ -43,7 +45,7 @@ const labelHtml = Astro.slots.has("default")
|
|
|
43
45
|
cta && href && (
|
|
44
46
|
<a
|
|
45
47
|
class="mt-2 inline-flex font-medium text-accent text-xs hover:underline"
|
|
46
|
-
href={href}
|
|
48
|
+
href={contentHref(href)}
|
|
47
49
|
rel={external ? "noreferrer" : undefined}
|
|
48
50
|
target={external ? "_blank" : undefined}
|
|
49
51
|
>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { contentHref } from "./base-href.ts";
|
|
3
|
+
|
|
2
4
|
interface RssMetadata {
|
|
3
5
|
description?: string;
|
|
4
6
|
title?: string;
|
|
@@ -6,7 +8,12 @@ interface RssMetadata {
|
|
|
6
8
|
|
|
7
9
|
interface Props {
|
|
8
10
|
description?: string;
|
|
9
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Link the heading to a dedicated page; falls back to the in-page anchor.
|
|
13
|
+
* A root-relative route gains the composed `deployment.base` + `basePath`
|
|
14
|
+
* prefix at emit time (like a markdown link); anchors and external URLs
|
|
15
|
+
* pass through.
|
|
16
|
+
*/
|
|
10
17
|
href?: string;
|
|
11
18
|
id?: string;
|
|
12
19
|
label?: string;
|
|
@@ -47,7 +54,7 @@ const tagList = Array.isArray(tags) ? tags : tags ? [tags] : [];
|
|
|
47
54
|
<header class="md:border-border md:border-e md:pe-4">
|
|
48
55
|
<a
|
|
49
56
|
class="font-semibold text-foreground text-sm no-underline hover:text-accent"
|
|
50
|
-
href={href ?? `#${id}`}
|
|
57
|
+
href={contentHref(href ?? `#${id}`)}
|
|
51
58
|
>
|
|
52
59
|
{updateLabel}
|
|
53
60
|
</a>
|
|
@@ -9,7 +9,17 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import nodePath from "node:path";
|
|
11
11
|
|
|
12
|
-
import type
|
|
12
|
+
import type {
|
|
13
|
+
CompilerHost,
|
|
14
|
+
CompilerOptions,
|
|
15
|
+
DeclarationStatement,
|
|
16
|
+
Program,
|
|
17
|
+
} from "typescript";
|
|
18
|
+
|
|
19
|
+
// `typeof import(...)` is the only way to name the whole lazily-imported
|
|
20
|
+
// `typescript` module without a value import (see components/props.ts).
|
|
21
|
+
// oxlint-disable-next-line typescript/consistent-type-imports
|
|
22
|
+
type TypeScriptApi = typeof import("typescript");
|
|
13
23
|
|
|
14
24
|
/** A single documented property — one generated row of a type table. */
|
|
15
25
|
export interface TypeTableProperty {
|
|
@@ -36,10 +46,10 @@ const VIRTUAL_FILE = "__blume_auto_type_table__.ts";
|
|
|
36
46
|
|
|
37
47
|
/** Build a compiler host that serves a single in-memory file plus the real libs. */
|
|
38
48
|
const inMemoryHost = (
|
|
39
|
-
ts:
|
|
40
|
-
options:
|
|
49
|
+
ts: TypeScriptApi,
|
|
50
|
+
options: CompilerOptions,
|
|
41
51
|
source: string
|
|
42
|
-
):
|
|
52
|
+
): CompilerHost => {
|
|
43
53
|
const host = ts.createCompilerHost(options, true);
|
|
44
54
|
const getSourceFile = host.getSourceFile.bind(host);
|
|
45
55
|
host.getSourceFile = (requested, languageVersion, onError, shouldCreate) =>
|
|
@@ -65,9 +75,9 @@ export const extractTypeTable = async (
|
|
|
65
75
|
): Promise<TypeTableProperty[]> => {
|
|
66
76
|
const { name, path, root = process.cwd(), source } = options;
|
|
67
77
|
const tsModule = await import("typescript");
|
|
68
|
-
const ts = (tsModule.default ?? tsModule) as
|
|
78
|
+
const ts = (tsModule.default ?? tsModule) as TypeScriptApi;
|
|
69
79
|
|
|
70
|
-
const compilerOptions:
|
|
80
|
+
const compilerOptions: CompilerOptions = {
|
|
71
81
|
allowJs: true,
|
|
72
82
|
module: ts.ModuleKind.ESNext,
|
|
73
83
|
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
@@ -78,7 +88,7 @@ export const extractTypeTable = async (
|
|
|
78
88
|
};
|
|
79
89
|
|
|
80
90
|
let fileName: string;
|
|
81
|
-
let program:
|
|
91
|
+
let program: Program;
|
|
82
92
|
if (source === undefined) {
|
|
83
93
|
if (path === undefined) {
|
|
84
94
|
throw new Error("AutoTypeTable needs a `path` or inline `type` source.");
|
|
@@ -100,7 +110,7 @@ export const extractTypeTable = async (
|
|
|
100
110
|
}
|
|
101
111
|
|
|
102
112
|
const declaration = sourceFile.statements.find(
|
|
103
|
-
(statement): statement is
|
|
113
|
+
(statement): statement is DeclarationStatement =>
|
|
104
114
|
(ts.isInterfaceDeclaration(statement) ||
|
|
105
115
|
ts.isTypeAliasDeclaration(statement)) &&
|
|
106
116
|
statement.name?.text === name
|
|
@@ -134,7 +144,13 @@ export const extractTypeTable = async (
|
|
|
134
144
|
default: defaultValue || undefined,
|
|
135
145
|
description: description || undefined,
|
|
136
146
|
name: symbol.getName(),
|
|
137
|
-
|
|
147
|
+
// Optionality lives on the checker's symbol, not the backing
|
|
148
|
+
// declaration: mapped/utility types synthesize members whose
|
|
149
|
+
// declaration has no question token (`Partial<Base>` would read as
|
|
150
|
+
// all-required), and `Required<Base>` strips optionality while the
|
|
151
|
+
// declaration keeps its `?` — the token is wrong in both directions.
|
|
152
|
+
// oxlint-disable-next-line no-bitwise -- SymbolFlags is a bitfield
|
|
153
|
+
required: (symbol.flags & ts.SymbolFlags.Optional) === 0,
|
|
138
154
|
type: typeText,
|
|
139
155
|
};
|
|
140
156
|
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import data from "blume:data";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
isInternalPath,
|
|
5
|
+
normalizeBasePath,
|
|
6
|
+
withComposedBasePath,
|
|
7
|
+
} from "../../core/base-path.ts";
|
|
8
|
+
|
|
9
|
+
// Mirrors `markdown/base-links.ts`: a path whose final segment carries a file
|
|
10
|
+
// extension is a `public/` asset, served at the site root and never moved
|
|
11
|
+
// under `basePath`.
|
|
12
|
+
const ASSET_PATH = /\.[a-z0-9]+$/iu;
|
|
13
|
+
|
|
14
|
+
/** Strip any `#fragment`/`?query` so only the path is extension-tested. */
|
|
15
|
+
const pathOf = (url: string): string => url.replace(/[#?].*$/u, "");
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Rebase a component-emitted `href` the way `markdown/base-links.ts` rebases
|
|
19
|
+
* `[x](/guide)`: a root-relative internal page link gains the composed
|
|
20
|
+
* `deployment.base` + `basePath` prefix, so authors write component links
|
|
21
|
+
* (`<Card href="/guide">`) under the same "as if mounted at root" contract as
|
|
22
|
+
* markdown links. Idempotent per layer (a hand-written `/docs/x` isn't
|
|
23
|
+
* double-prefixed) and inert for external URLs, fragments, relative paths, and
|
|
24
|
+
* asset links.
|
|
25
|
+
*/
|
|
26
|
+
export const contentHref = (href: string): string =>
|
|
27
|
+
isInternalPath(href) && !ASSET_PATH.test(pathOf(href))
|
|
28
|
+
? withComposedBasePath(
|
|
29
|
+
normalizeBasePath(import.meta.env.BASE_URL),
|
|
30
|
+
data.config.basePath,
|
|
31
|
+
href
|
|
32
|
+
)
|
|
33
|
+
: href;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Client
|
|
2
|
+
* Client behavior for the `<blume-changelog>` custom element wrapping the
|
|
3
3
|
* generated changelog timeline when its releases are semver-versioned. The
|
|
4
4
|
* newest major line stays visible; every older major is collapsed into a group
|
|
5
5
|
* revealed one major at a time by the "Show N.x releases" button at the bottom.
|
|
@@ -24,6 +24,10 @@ class BlumeChangelog extends HTMLElement {
|
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// Localized button template from the generated page markup (the
|
|
28
|
+
// data-attribute channel); `{version}` is replaced with the major line.
|
|
29
|
+
const template = this.dataset.i18nMore || "Show {version} releases";
|
|
30
|
+
|
|
27
31
|
for (const group of groups) {
|
|
28
32
|
group.hidden = true;
|
|
29
33
|
// Focusable only programmatically, so revealing a group can move focus to
|
|
@@ -35,7 +39,10 @@ class BlumeChangelog extends HTMLElement {
|
|
|
35
39
|
const sync = () => {
|
|
36
40
|
const next = groups[revealed];
|
|
37
41
|
if (next) {
|
|
38
|
-
button.textContent =
|
|
42
|
+
button.textContent = template.replace(
|
|
43
|
+
"{version}",
|
|
44
|
+
next.dataset.changelogLabel ?? ""
|
|
45
|
+
);
|
|
39
46
|
button.hidden = false;
|
|
40
47
|
} else {
|
|
41
48
|
button.hidden = true;
|
|
@@ -13,6 +13,9 @@ import { readFile } from "node:fs/promises";
|
|
|
13
13
|
import { preloadDiffHTML, preloadPatchDiff } from "@pierre/diffs/ssr";
|
|
14
14
|
import { isAbsolute, join } from "pathe";
|
|
15
15
|
|
|
16
|
+
import { DEFAULT_CODE_THEMES } from "../../markdown/themes.ts";
|
|
17
|
+
import type { CodeThemes } from "../../markdown/themes.ts";
|
|
18
|
+
|
|
16
19
|
export interface DiffOptions {
|
|
17
20
|
/** Path to the "after" file, resolved relative to {@link DiffOptions.root}. */
|
|
18
21
|
after?: string;
|
|
@@ -30,11 +33,13 @@ export interface DiffOptions {
|
|
|
30
33
|
root?: string;
|
|
31
34
|
/** Path to a `.patch`/`.diff` file, resolved relative to {@link DiffOptions.root}. */
|
|
32
35
|
src?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Light/dark Shiki themes (`markdown.codeBlocks.theme`). Defaults to the same
|
|
38
|
+
* github pair Blume's code blocks use, keeping diffs in lockstep.
|
|
39
|
+
*/
|
|
40
|
+
theme?: CodeThemes;
|
|
33
41
|
}
|
|
34
42
|
|
|
35
|
-
/** Dual-theme config mirroring Blume's Shiki convention (see `templates.ts`). */
|
|
36
|
-
const THEME = { dark: "github-dark", light: "github-light" } as const;
|
|
37
|
-
|
|
38
43
|
const resolvePath = (path: string, root: string): string =>
|
|
39
44
|
isAbsolute(path) ? path : join(root, path);
|
|
40
45
|
|
|
@@ -56,12 +61,13 @@ export const renderDiff = async (options: DiffOptions): Promise<string> => {
|
|
|
56
61
|
patch,
|
|
57
62
|
root = process.cwd(),
|
|
58
63
|
src,
|
|
64
|
+
theme = DEFAULT_CODE_THEMES,
|
|
59
65
|
} = options;
|
|
60
66
|
|
|
61
67
|
if (patch !== undefined || src !== undefined) {
|
|
62
68
|
const text = patch ?? (await readText(src as string, root));
|
|
63
69
|
const result = await preloadPatchDiff({
|
|
64
|
-
options: { theme
|
|
70
|
+
options: { theme },
|
|
65
71
|
patch: text,
|
|
66
72
|
});
|
|
67
73
|
return result.prerenderedHTML;
|
|
@@ -74,7 +80,7 @@ export const renderDiff = async (options: DiffOptions): Promise<string> => {
|
|
|
74
80
|
return await preloadDiffHTML({
|
|
75
81
|
newFile: { contents: await readText(after, root), name: after },
|
|
76
82
|
oldFile: { contents: await readText(before, root), name: before },
|
|
77
|
-
options: { theme
|
|
83
|
+
options: { theme },
|
|
78
84
|
});
|
|
79
85
|
}
|
|
80
86
|
|
|
@@ -85,7 +91,7 @@ export const renderDiff = async (options: DiffOptions): Promise<string> => {
|
|
|
85
91
|
return await preloadDiffHTML({
|
|
86
92
|
newFile: { contents: newText, lang, name: "snippet" },
|
|
87
93
|
oldFile: { contents: old, lang, name: "snippet" },
|
|
88
|
-
options: { disableFileHeader: true, theme
|
|
94
|
+
options: { disableFileHeader: true, theme },
|
|
89
95
|
});
|
|
90
96
|
}
|
|
91
97
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Client
|
|
2
|
+
* Client behavior for the `<blume-mermaid>` custom element emitted by the
|
|
3
3
|
* Mermaid markdown plugin. Mermaid is lazy-loaded — it needs a DOM and is large,
|
|
4
4
|
* so the dependency only downloads on pages that actually contain a diagram —
|
|
5
5
|
* and each diagram re-renders when the color theme flips so it tracks light/dark.
|
|
@@ -55,10 +55,18 @@ class BlumeMermaid extends HTMLElement {
|
|
|
55
55
|
// A newer render (rapid theme toggles) superseded this one — dropping
|
|
56
56
|
// the stale result keeps the diagram in the latest theme.
|
|
57
57
|
if (token === this.#renderToken) {
|
|
58
|
+
// Mermaid's own render output is SVG markup; it must be injected as
|
|
59
|
+
// HTML, not text. securityLevel "strict" (see initialize) sanitizes it.
|
|
60
|
+
// oxlint-disable-next-line github/no-inner-html -- Mermaid-generated SVG must be injected as HTML
|
|
58
61
|
output.innerHTML = svg;
|
|
59
62
|
}
|
|
60
63
|
} catch {
|
|
61
|
-
|
|
64
|
+
// Localized message stamped on <body> by RootLayout's markup (the
|
|
65
|
+
// data-attribute channel); English fallback when the attribute is
|
|
66
|
+
// missing (a stale snapshot or a custom layout).
|
|
67
|
+
output.textContent =
|
|
68
|
+
document.body.dataset.i18nDiagramError ||
|
|
69
|
+
"Could not render this diagram.";
|
|
62
70
|
}
|
|
63
71
|
output.removeAttribute("aria-busy");
|
|
64
72
|
};
|
package/src/components/index.ts
CHANGED
|
@@ -11,4 +11,26 @@ export type {
|
|
|
11
11
|
IslandDescriptor,
|
|
12
12
|
} from "../core/define-components.ts";
|
|
13
13
|
export type { HydrationMode } from "../core/schema.ts";
|
|
14
|
-
export type
|
|
14
|
+
export type {
|
|
15
|
+
AccordionItemProps,
|
|
16
|
+
AccordionProps,
|
|
17
|
+
BadgeProps,
|
|
18
|
+
CalloutProps,
|
|
19
|
+
CardGroupProps,
|
|
20
|
+
CardProps,
|
|
21
|
+
CodeGroupProps,
|
|
22
|
+
ColumnProps,
|
|
23
|
+
ColumnsProps,
|
|
24
|
+
ExpandableProps,
|
|
25
|
+
FrameProps,
|
|
26
|
+
IconProps,
|
|
27
|
+
PanelProps,
|
|
28
|
+
PromptProps,
|
|
29
|
+
StepProps,
|
|
30
|
+
StepsProps,
|
|
31
|
+
TabProps,
|
|
32
|
+
TabsProps,
|
|
33
|
+
TileProps,
|
|
34
|
+
TooltipProps,
|
|
35
|
+
YouTubeProps,
|
|
36
|
+
} from "./props.ts";
|
|
@@ -63,13 +63,16 @@ const icons = {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/* The table of contents only shows at xl; reclaim its column for the article
|
|
66
|
-
while the panel is open so the shrunken content still has room to breathe.
|
|
66
|
+
while the panel is open so the shrunken content still has room to breathe.
|
|
67
|
+
Scoped to grids that actually have a TOC column: a "bare" layout (the
|
|
68
|
+
changelog index) is single-column, and forcing the sidebar tracks onto it
|
|
69
|
+
would squeeze its only item — <main> — into the 17.5rem track. */
|
|
67
70
|
@media (min-width: 1280px) {
|
|
68
71
|
body[data-blume-ask="open"] [data-blume-toc] {
|
|
69
72
|
display: none;
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
body[data-blume-ask="open"] [data-blume-doc-grid] {
|
|
75
|
+
body[data-blume-ask="open"] [data-blume-doc-grid]:has([data-blume-toc]) {
|
|
73
76
|
grid-template-columns: 17.5rem minmax(0, 1fr);
|
|
74
77
|
}
|
|
75
78
|
}
|