blume 0.1.5 → 0.3.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/dist/cli/index.js +2123 -555
- package/dist/cli/index.js.map +39 -25
- package/dist/types/core/data.d.ts +16 -0
- package/dist/types/core/define-components.d.ts +9 -2
- package/dist/types/core/diagnostics.d.ts +5 -0
- package/dist/types/core/schema.d.ts +136 -508
- package/dist/types/core/types.d.ts +2 -2
- package/docs/02-deployment.mdx +21 -2
- package/docs/advanced/changelog.mdx +28 -1
- package/docs/advanced/custom-pages.mdx +63 -2
- package/docs/configuration/ai.mdx +20 -3
- package/docs/configuration/customization.mdx +103 -5
- package/docs/configuration/index.mdx +25 -11
- package/docs/configuration/search.mdx +13 -1
- package/docs/configuration/seo.mdx +5 -0
- package/docs/configuration/theming.mdx +51 -0
- package/docs/content/components.mdx +18 -0
- package/docs/content/islands.mdx +73 -0
- package/docs/content/navigation.mdx +25 -0
- package/docs/content/sources.mdx +43 -0
- package/docs/content/syntax.mdx +1 -1
- package/docs/index.mdx +3 -12
- package/docs/reference/cli.mdx +49 -1
- package/docs/reference/frontmatter.mdx +9 -1
- package/package.json +3 -1
- package/src/ai/ask-context.ts +131 -0
- package/src/ai/ask-data.ts +25 -0
- package/src/astro/component-slots.ts +165 -0
- package/src/astro/generate.ts +162 -26
- package/src/astro/integration.ts +59 -0
- package/src/astro/pages.ts +5 -12
- package/src/astro/templates.ts +102 -45
- package/src/blume-modules.d.ts +25 -0
- package/src/cli/commands/build.ts +186 -1
- package/src/cli/commands/check.ts +62 -0
- package/src/cli/commands/dev.ts +21 -1
- package/src/cli/commands/doctor.ts +23 -6
- package/src/cli/commands/init.ts +163 -15
- package/src/cli/commands/validate.ts +16 -2
- package/src/cli/env.ts +84 -0
- package/src/cli/index.ts +20 -0
- package/src/cli/internal-error.ts +63 -0
- package/src/cli/log.ts +30 -1
- package/src/cli/prepare.ts +22 -3
- package/src/cli/required-secrets.ts +44 -0
- package/src/components/BlumePage.astro +107 -0
- package/src/components/content/CodeBlock.astro +7 -2
- package/src/components/index.ts +3 -3
- package/src/components/islands/ask-ai.tsx +15 -1
- package/src/components/islands/hooks.ts +188 -0
- package/src/components/layout/Empty.astro +6 -0
- package/src/components/layout/Header.astro +24 -39
- package/src/components/layout/Logo.astro +50 -0
- package/src/components/layout/NavSelector.astro +75 -0
- package/src/components/layout/PageLayout.astro +38 -2
- package/src/components/layout/RootLayout.astro +70 -4
- package/src/components/layout/hydration-hint.ts +30 -0
- package/src/components/layout/overrides.ts +6 -4
- package/src/components/props.ts +68 -0
- package/src/core/builtin-tags.ts +39 -0
- package/src/core/component-diagnostics.ts +44 -0
- package/src/core/component-overrides.ts +478 -0
- package/src/core/config.ts +8 -0
- package/src/core/data.ts +14 -0
- package/src/core/define-components.ts +9 -2
- package/src/core/diagnostics.ts +90 -1
- package/src/core/graph.ts +7 -0
- package/src/core/nav-diagnostics.ts +205 -0
- package/src/core/project-graph.ts +40 -1
- package/src/core/schema.ts +54 -96
- package/src/core/sources/github-releases.ts +200 -0
- package/src/core/sources/normalize.ts +51 -0
- package/src/core/sources/resolve.ts +16 -0
- package/src/core/types.ts +2 -2
- package/src/deploy/redirects.ts +43 -0
- package/src/markdown/index.ts +24 -0
- package/src/migrate/mintlify/config.ts +1 -176
- package/src/migrate/starlight/config.ts +0 -4
- package/src/og/card.ts +163 -38
- package/src/registry/eject.ts +39 -9
- package/src/registry/registry.ts +166 -0
- package/src/runtime/index.ts +61 -0
- package/src/vite-env.d.ts +14 -0
- package/docs/changelog/v0-1-0.mdx +0 -12
- package/docs/changelog/v0-2-0.mdx +0 -16
|
@@ -108,6 +108,8 @@ export interface PageRecord {
|
|
|
108
108
|
format: "md" | "mdx";
|
|
109
109
|
/** Internal/asset links discovered in the page (for validation). */
|
|
110
110
|
links: PageLink[];
|
|
111
|
+
/** Capitalized JSX component tags used in the body (`.mdx` only). */
|
|
112
|
+
componentsUsed?: string[];
|
|
111
113
|
/** Resolved "last updated" ISO date, when the feature is enabled. */
|
|
112
114
|
lastModified?: string;
|
|
113
115
|
}
|
|
@@ -168,8 +170,6 @@ export interface NavSidebarVariant {
|
|
|
168
170
|
export interface NavChromeVariant {
|
|
169
171
|
path: string;
|
|
170
172
|
banner?: ResolvedConfig["banner"];
|
|
171
|
-
footer?: ResolvedConfig["footer"];
|
|
172
|
-
navbar?: ResolvedConfig["navbar"];
|
|
173
173
|
}
|
|
174
174
|
/** The complete navigation model derived from the content graph. */
|
|
175
175
|
export interface Navigation {
|
package/docs/02-deployment.mdx
CHANGED
|
@@ -118,8 +118,27 @@ Map old URLs to new ones in `blume.config.ts`:
|
|
|
118
118
|
redirects: [{ from: "/old", to: "/new", status: 301 }];
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
-
`status` accepts `301`, `302`, `307`, or `308` (default `301`).
|
|
122
|
-
|
|
121
|
+
`status` accepts `301`, `302`, `307`, or `308` (default `301`). Server builds
|
|
122
|
+
handle redirects at request time. Static builds emit redirect pages **and**
|
|
123
|
+
platform files so your host issues a real HTTP redirect: `_redirects` (Netlify,
|
|
124
|
+
Cloudflare Pages), `vercel.json` (Vercel), and `blume-redirects.json` — a
|
|
125
|
+
structured manifest for anything else (nginx/Apache rules, an edge worker). A
|
|
126
|
+
`_redirects` or `vercel.json` you ship in `public/` is left untouched.
|
|
127
|
+
|
|
128
|
+
## Environment variables
|
|
129
|
+
|
|
130
|
+
When a feature needs a runtime secret, Blume warns at `blume dev`/`build` if it's
|
|
131
|
+
missing — so the problem surfaces early instead of at the first request:
|
|
132
|
+
|
|
133
|
+
| Feature | Variable |
|
|
134
|
+
| --------------------------------------- | ------------------------------------- |
|
|
135
|
+
| Ask AI (AI Gateway) | `AI_GATEWAY_API_KEY` (or Vercel OIDC) |
|
|
136
|
+
| Ask AI (OpenRouter / OpenAI-compatible) | the provider's `apiKeyEnv` |
|
|
137
|
+
| Mixedbread search | `MIXEDBREAD_API_KEY` |
|
|
138
|
+
|
|
139
|
+
Set them in `.env.local` for local dev and in your host's environment for
|
|
140
|
+
production. Build-time secrets for search-index sync (Algolia, Orama Cloud,
|
|
141
|
+
Typesense) are warned about separately during the sync step.
|
|
123
142
|
|
|
124
143
|
## Build summary
|
|
125
144
|
|
|
@@ -5,7 +5,8 @@ description: Author release notes as content, and Blume builds a timeline page a
|
|
|
5
5
|
|
|
6
6
|
Blume ships a changelog out of the box. Write each release as a normal content
|
|
7
7
|
file, mark it `type: changelog`, and Blume collects every entry into a generated
|
|
8
|
-
timeline page and an RSS feed — no layout to build, no list to maintain.
|
|
8
|
+
timeline page and an RSS feed — no layout to build, no list to maintain. Or skip
|
|
9
|
+
the files entirely and [source your changelog from GitHub Releases](#from-github-releases).
|
|
9
10
|
|
|
10
11
|
## Write an entry
|
|
11
12
|
|
|
@@ -72,6 +73,32 @@ replace it with your own design, add a [custom page](/docs/advanced/custom-pages
|
|
|
72
73
|
`pages/changelog.astro` — it takes over and Blume stops generating the default
|
|
73
74
|
timeline.
|
|
74
75
|
|
|
76
|
+
## From GitHub Releases
|
|
77
|
+
|
|
78
|
+
Rather than authoring entries by hand, point the built-in
|
|
79
|
+
[`github-releases` source](/docs/content/sources#github-releases) at a repo and
|
|
80
|
+
every release becomes a `type: changelog` entry — the same timeline and feed,
|
|
81
|
+
fed straight from the releases you already publish:
|
|
82
|
+
|
|
83
|
+
```ts blume.config.ts
|
|
84
|
+
content: {
|
|
85
|
+
sources: [
|
|
86
|
+
{ type: "filesystem", root: "content" },
|
|
87
|
+
{
|
|
88
|
+
type: "github-releases",
|
|
89
|
+
prefix: "changelog",
|
|
90
|
+
owner: "acme",
|
|
91
|
+
repo: "sdk",
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The release name becomes the title, its tag becomes `changelog.version`, and its
|
|
98
|
+
published date sorts the timeline. A private repo authenticates with the
|
|
99
|
+
`GITHUB_TOKEN` environment variable. See
|
|
100
|
+
[Content sources](/docs/content/sources#github-releases) for every option.
|
|
101
|
+
|
|
75
102
|
## The RSS feed
|
|
76
103
|
|
|
77
104
|
Blume also builds a changelog feed at **`/changelog/rss.xml`**, sorted
|
|
@@ -167,6 +167,51 @@ const posts = (await getCollection("docs"))
|
|
|
167
167
|
</ul>
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
+
## Runtime helpers
|
|
171
|
+
|
|
172
|
+
`blume/runtime` bundles the common data patterns so you don't reach into
|
|
173
|
+
`blume:data` internals.
|
|
174
|
+
|
|
175
|
+
**`getBlumeCollection(data, query?)`** selects content routes — filtered by
|
|
176
|
+
collection, locale, or path prefix, with drafts and hidden pages excluded and the
|
|
177
|
+
result sorted by path — which is exactly what a custom index needs:
|
|
178
|
+
|
|
179
|
+
```astro pages/blog/index.astro lineNumbers
|
|
180
|
+
---
|
|
181
|
+
import data from "blume:data";
|
|
182
|
+
import { getBlumeCollection } from "blume/runtime";
|
|
183
|
+
|
|
184
|
+
const posts = getBlumeCollection(data, { prefix: "/blog" });
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
<ul>
|
|
188
|
+
{posts.map((post) => (
|
|
189
|
+
<li><a href={post.path}>{post.title}</a></li>
|
|
190
|
+
))}
|
|
191
|
+
</ul>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**`<BlumePage>`** renders a content entry's body inside a custom page, with
|
|
195
|
+
Blume's built-in MDX components (callouts, cards, steps…) already wired in — for
|
|
196
|
+
featuring a doc on a landing page or building a bespoke index that shows real
|
|
197
|
+
content:
|
|
198
|
+
|
|
199
|
+
```astro pages/index.astro lineNumbers
|
|
200
|
+
---
|
|
201
|
+
import BlumePage from "blume/components/BlumePage.astro";
|
|
202
|
+
import data from "blume:data";
|
|
203
|
+
import { getBlumeCollection } from "blume/runtime";
|
|
204
|
+
|
|
205
|
+
const [intro] = getBlumeCollection(data, { prefix: "/docs" });
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
{intro && <BlumePage id={intro.entryId} />}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Pass `components` to add your own overrides or islands (which live in the
|
|
212
|
+
generated runtime and aren't imported by default), and `collection` to read from
|
|
213
|
+
a collection other than `"docs"`.
|
|
214
|
+
|
|
170
215
|
## Using the site layout
|
|
171
216
|
|
|
172
217
|
`RootLayout` gives a custom page the full docs chrome — header, sidebar, search,
|
|
@@ -211,7 +256,24 @@ generated `og:image` automatically: Blume renders an Open Graph card for every
|
|
|
211
256
|
static custom page — the home included, the most-shared URL — served at
|
|
212
257
|
`/og/<route>.png` (`/og/index.png` for `/`). The home card uses the site title
|
|
213
258
|
with the description as its eyebrow; a deeper page is titled from its last path
|
|
214
|
-
segment. Set `ogImage` or `canonical` explicitly to override.
|
|
259
|
+
segment. Set `ogImage` or `canonical` explicitly to override either. `ogImage`
|
|
260
|
+
takes a root-relative path — a file in `public/`, resolved against
|
|
261
|
+
[`deployment.site`](/docs/deployment) to the absolute URL crawlers need — or an
|
|
262
|
+
external URL, which passes through untouched:
|
|
263
|
+
|
|
264
|
+
```astro pages/index.astro lineNumbers
|
|
265
|
+
<PageLayout
|
|
266
|
+
siteUrl={config.site}
|
|
267
|
+
ogEnabled={config.og.enabled}
|
|
268
|
+
ogImage="/opengraph-image.png"
|
|
269
|
+
page={{ title: config.title }}
|
|
270
|
+
>
|
|
271
|
+
<!-- page content -->
|
|
272
|
+
</PageLayout>
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Only this page changes — every other route keeps its generated card — so it's how
|
|
276
|
+
you give the home page alone a bespoke share image.
|
|
215
277
|
|
|
216
278
|
`page.title` is used verbatim as the document title (no `- siteTitle` suffix),
|
|
217
279
|
since marketing pages usually set their own. To give a custom page the full docs
|
|
@@ -233,7 +295,6 @@ import data from "blume:data";
|
|
|
233
295
|
headings={[]}
|
|
234
296
|
themeMode={data.config.theme.mode}
|
|
235
297
|
searchEnabled={data.config.search.enabled}
|
|
236
|
-
searchProvider={data.config.search.provider}
|
|
237
298
|
indexable={true}
|
|
238
299
|
>
|
|
239
300
|
<h1>Pricing</h1>
|
|
@@ -84,6 +84,24 @@ ai: {
|
|
|
84
84
|
}
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
### Grounding
|
|
88
|
+
|
|
89
|
+
Ask AI is **grounded in your docs**. For each question it retrieves the most
|
|
90
|
+
relevant pages — using the same lexical [Orama](/docs/configuration/search) index
|
|
91
|
+
that powers on-page search — and injects them into the model's system prompt, so
|
|
92
|
+
answers come from your content instead of the model's own knowledge. The
|
|
93
|
+
assistant is told to answer only from the retrieved pages, to say when something
|
|
94
|
+
isn't covered, and to cite the pages it drew from.
|
|
95
|
+
|
|
96
|
+
The page the reader is currently on is added to the context first and used to
|
|
97
|
+
scope retrieval to that page's language, so answers stay relevant to where they
|
|
98
|
+
are in the docs. Retrieval runs at request time from a snapshot baked into the
|
|
99
|
+
build, so it works regardless of your [search](/docs/configuration/search)
|
|
100
|
+
provider — even when search is set to `none` — and needs no configuration.
|
|
101
|
+
|
|
102
|
+
Grounding is on for every backend except **[Inkeep](#backends)**, which runs its
|
|
103
|
+
own retrieval over the content you've indexed in its dashboard.
|
|
104
|
+
|
|
87
105
|
### Server output required
|
|
88
106
|
|
|
89
107
|
Ask AI is a server route (`POST /api/ask`), so it can't run on a static build.
|
|
@@ -151,9 +169,8 @@ at a different env var or proxy.
|
|
|
151
169
|
|
|
152
170
|
:::note
|
|
153
171
|
**Inkeep** answers from the content you've indexed in the Inkeep dashboard — it
|
|
154
|
-
runs its own retrieval —
|
|
155
|
-
|
|
156
|
-
to the model.
|
|
172
|
+
runs its own retrieval — so Blume leaves it ungrounded. Every other backend is
|
|
173
|
+
[grounded](#grounding) in this site's pages.
|
|
157
174
|
:::
|
|
158
175
|
|
|
159
176
|
Keys are read with `process.env`, which covers the Node, Vercel, and Netlify
|
|
@@ -25,6 +25,101 @@ export default defineComponents({
|
|
|
25
25
|
Keys are the names you write in MDX (`<Callout>`, `<Pricing>`). Use the `.tsx`
|
|
26
26
|
filename when you import React components.
|
|
27
27
|
|
|
28
|
+
### Reference form
|
|
29
|
+
|
|
30
|
+
Every override — in `mdx`, `layout`, or `islands` — accepts three forms:
|
|
31
|
+
|
|
32
|
+
```ts components.ts
|
|
33
|
+
import { defineComponents } from "blume";
|
|
34
|
+
import Callout from "./components/Callout.astro";
|
|
35
|
+
|
|
36
|
+
export default defineComponents({
|
|
37
|
+
mdx: {
|
|
38
|
+
Callout, // 1. an imported component
|
|
39
|
+
Note: "./components/Note.astro", // 2. a path string (resolved from the project root)
|
|
40
|
+
Chart: { component: "./components/Chart.tsx", client: "load" }, // 3. a descriptor
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The **descriptor** form adds a hydration mode so an interactive
|
|
46
|
+
React/Vue/Svelte component ships its JavaScript and comes alive on the client.
|
|
47
|
+
Without a `client` mode a framework component renders as static HTML — Blume
|
|
48
|
+
prints a build warning when it spots one, since that's usually a mistake.
|
|
49
|
+
|
|
50
|
+
| `client` | Hydrates |
|
|
51
|
+
| ----------- | ---------------------------------------------------------------- |
|
|
52
|
+
| `"load"` | Immediately on page load |
|
|
53
|
+
| `"idle"` | When the main thread is idle |
|
|
54
|
+
| `"visible"` | When scrolled into view |
|
|
55
|
+
| `"media"` | When a `media` query matches (add `media: "(min-width: 40rem)"`) |
|
|
56
|
+
| `"only"` | Client only, never server-rendered |
|
|
57
|
+
|
|
58
|
+
For interactive components you use across many pages, the [`islands`
|
|
59
|
+
group](/docs/content/islands#registering-islands-in-componentsts) is a shorthand
|
|
60
|
+
for the descriptor form with `client: "visible"`.
|
|
61
|
+
|
|
62
|
+
### Typing an override
|
|
63
|
+
|
|
64
|
+
When you replace a built-in, import its prop type from `blume/components` so your
|
|
65
|
+
component matches the contract — the types are derived from the components
|
|
66
|
+
themselves, so they never drift:
|
|
67
|
+
|
|
68
|
+
```tsx components/Callout.tsx
|
|
69
|
+
import type { CalloutProps } from "blume/components";
|
|
70
|
+
|
|
71
|
+
export default function Callout(props: CalloutProps) {
|
|
72
|
+
// …your own callout, same props as the built-in
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Prop types are exported for the content components (`CalloutProps`, `CardProps`,
|
|
77
|
+
`TabsProps`, `StepsProps`, `BadgeProps`, and more).
|
|
78
|
+
|
|
79
|
+
## Layout slots
|
|
80
|
+
|
|
81
|
+
The `layout` map replaces a piece of Blume's chrome with your own component. Each
|
|
82
|
+
override receives the same props as the built-in it replaces, so you can wrap the
|
|
83
|
+
default or start from scratch.
|
|
84
|
+
|
|
85
|
+
```ts components.ts
|
|
86
|
+
import { defineComponents } from "blume";
|
|
87
|
+
import Footer from "./components/Footer.astro";
|
|
88
|
+
import Logo from "./components/Logo.astro";
|
|
89
|
+
|
|
90
|
+
export default defineComponents({
|
|
91
|
+
layout: {
|
|
92
|
+
Logo, // brand mark + title in the header
|
|
93
|
+
Footer, // site-wide footer (no built-in — renders only when set)
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Wired slots:
|
|
99
|
+
|
|
100
|
+
| Slot | Replaces | Props |
|
|
101
|
+
| ----------------- | -------------------------------------------------------- | -------------------------------------------------------------- |
|
|
102
|
+
| `Layout` | The entire page shell (`RootLayout`) | Everything the built-in layout receives, plus the `layout` map |
|
|
103
|
+
| `Header` | The top navigation bar | `site`, `logo`, `navigation`, `route`, `searchEnabled`, … |
|
|
104
|
+
| `Logo` | The brand link (mark + title) in the header | `site`, `logo` |
|
|
105
|
+
| `Search` | The header search trigger + modal | `navigation`, `strings`, `locale`, `askEnabled` |
|
|
106
|
+
| `Sidebar` | The primary navigation tree | `items`, `currentRoute` |
|
|
107
|
+
| `MobileNav` | The nav inside the mobile drawer (defaults to `Sidebar`) | `items`, `currentRoute` |
|
|
108
|
+
| `Breadcrumbs` | The breadcrumb trail | `crumbs` |
|
|
109
|
+
| `TableOfContents` | The on-this-page outline | `headings`, `title`, `variant` |
|
|
110
|
+
| `Pagination` | The prev/next footer links | `prev`, `next`, `strings` |
|
|
111
|
+
| `PageHeader` | An injection point above the article (no built-in) | `page`, `headings`, `route` |
|
|
112
|
+
| `PageFooter` | An injection point below the article (no built-in) | `page`, `headings`, `route` |
|
|
113
|
+
| `Footer` | A site-wide footer after the content grid (no built-in) | `site`, `navigation`, `ui` |
|
|
114
|
+
|
|
115
|
+
`PageHeader`, `PageFooter`, and `Footer` have no built-in component — they render
|
|
116
|
+
nothing until you set them, which makes them handy injection points for a
|
|
117
|
+
promo banner, a "last updated" note, or a marketing footer.
|
|
118
|
+
|
|
119
|
+
Layout slots accept the same [three reference forms](#reference-form) as MDX
|
|
120
|
+
overrides, so a slot can be a path string or a hydrated descriptor
|
|
121
|
+
(`{ component, client }`) when you want an interactive header or footer.
|
|
122
|
+
|
|
28
123
|
## Interactive islands
|
|
29
124
|
|
|
30
125
|
For interactive UI (React, Vue, or Svelte), drop a component into an `islands/`
|
|
@@ -66,16 +161,19 @@ available:
|
|
|
66
161
|
blume add
|
|
67
162
|
```
|
|
68
163
|
|
|
69
|
-
Install a
|
|
70
|
-
|
|
164
|
+
Install a layout slot (header, sidebar, breadcrumbs, table of contents,
|
|
165
|
+
pagination, or feedback) or any content component (callout, card, tabs, steps,
|
|
166
|
+
accordion, and more):
|
|
71
167
|
|
|
72
168
|
```bash
|
|
169
|
+
blume add callout
|
|
73
170
|
blume add pagination
|
|
74
171
|
```
|
|
75
172
|
|
|
76
|
-
The copy imports the rest of the framework from `blume/*`, so it renders
|
|
77
|
-
|
|
78
|
-
|
|
173
|
+
The copy imports the rest of the framework from `blume/*`, so it renders exactly
|
|
174
|
+
like the built-in until you change it. `blume add` prints the `defineComponents`
|
|
175
|
+
snippet to register it — content components under `mdx`, layout pieces under
|
|
176
|
+
`layout`.
|
|
79
177
|
|
|
80
178
|
## Eject
|
|
81
179
|
|
|
@@ -20,7 +20,8 @@ export default defineConfig({
|
|
|
20
20
|
|
|
21
21
|
## A complete example
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
A broader example touching the most common options (see each feature's guide for
|
|
24
|
+
the rest):
|
|
24
25
|
|
|
25
26
|
```ts blume.config.ts lineNumbers
|
|
26
27
|
import { defineConfig } from "blume";
|
|
@@ -238,7 +239,7 @@ the full reference.
|
|
|
238
239
|
|
|
239
240
|
```ts blume.config.ts lineNumbers
|
|
240
241
|
seo: {
|
|
241
|
-
og: { enabled:
|
|
242
|
+
og: { enabled: true },
|
|
242
243
|
rss: { enabled: true, types: ["blog", "changelog"] },
|
|
243
244
|
sitemap: true,
|
|
244
245
|
robots: true,
|
|
@@ -246,18 +247,31 @@ seo: {
|
|
|
246
247
|
}
|
|
247
248
|
```
|
|
248
249
|
|
|
249
|
-
| Option | Default | Description
|
|
250
|
-
| ---------------- | ----------------------- |
|
|
251
|
-
| `og.enabled` |
|
|
252
|
-
| `rss.enabled` | `true` | Build feeds for blog and changelog content.
|
|
253
|
-
| `rss.types` | `["blog", "changelog"]` | Content types that each get a feed.
|
|
254
|
-
| `rss.limit` | `50` | Maximum items per feed.
|
|
255
|
-
| `sitemap` | `true` | Generate sitemap.xml (needs deployment.site).
|
|
256
|
-
| `robots` | `true` | Generate robots.txt with a Sitemap link.
|
|
257
|
-
| `structuredData` | `true` | Emit schema.org JSON-LD in each page's head.
|
|
250
|
+
| Option | Default | Description |
|
|
251
|
+
| ---------------- | ----------------------- | ------------------------------------------------------- |
|
|
252
|
+
| `og.enabled` | auto | Per-page Open Graph images — on when a site URL is set. |
|
|
253
|
+
| `rss.enabled` | `true` | Build feeds for blog and changelog content. |
|
|
254
|
+
| `rss.types` | `["blog", "changelog"]` | Content types that each get a feed. |
|
|
255
|
+
| `rss.limit` | `50` | Maximum items per feed. |
|
|
256
|
+
| `sitemap` | `true` | Generate sitemap.xml (needs deployment.site). |
|
|
257
|
+
| `robots` | `true` | Generate robots.txt with a Sitemap link. |
|
|
258
|
+
| `structuredData` | `true` | Emit schema.org JSON-LD in each page's head. |
|
|
258
259
|
|
|
259
260
|
These work best with an absolute [`deployment.site`](/docs/deployment) for full URLs.
|
|
260
261
|
|
|
262
|
+
## Table of contents
|
|
263
|
+
|
|
264
|
+
The on-this-page outline is on by default and lists `H2`–`H3` headings. Turn it
|
|
265
|
+
off, or change the heading range, with `toc`:
|
|
266
|
+
|
|
267
|
+
```ts blume.config.ts
|
|
268
|
+
export default defineConfig({
|
|
269
|
+
toc: false, // hide it everywhere
|
|
270
|
+
// …or narrow the range:
|
|
271
|
+
toc: { minHeadingLevel: 2, maxHeadingLevel: 4 },
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
261
275
|
## Feature options
|
|
262
276
|
|
|
263
277
|
Each of these has its own guide. The config field is the entry point:
|
|
@@ -17,7 +17,8 @@ so picking one backend never pulls in the others.
|
|
|
17
17
|
## Using search
|
|
18
18
|
|
|
19
19
|
Open search with <Badge variant="accent">⌘K</Badge> (or `Ctrl K`), or press `/`
|
|
20
|
-
when you're not typing in a field. `Esc` closes it
|
|
20
|
+
when you're not typing in a field. `Esc` closes it, and `⌘J` (or `Ctrl J`)
|
|
21
|
+
toggles the result preview pane.
|
|
21
22
|
|
|
22
23
|
Queries match page **titles**, **descriptions**, and **body text**, with title
|
|
23
24
|
matches ranked highest and descriptions above body.
|
|
@@ -29,6 +30,17 @@ to plain text — code blocks, images, and markup are stripped, so results stay
|
|
|
29
30
|
relevant. The index is built from your source files, so it's identical in dev and
|
|
30
31
|
production.
|
|
31
32
|
|
|
33
|
+
## Tags
|
|
34
|
+
|
|
35
|
+
Add `search.tags` to a page's frontmatter to group it under a filter in the
|
|
36
|
+
search dialog — readers can narrow results to a tag with a click. Tags also
|
|
37
|
+
become a facet on the hosted providers.
|
|
38
|
+
|
|
39
|
+
```yaml
|
|
40
|
+
search:
|
|
41
|
+
tags: [api, reference]
|
|
42
|
+
```
|
|
43
|
+
|
|
32
44
|
## Providers
|
|
33
45
|
|
|
34
46
|
The client-side providers are keyless and need no extra config. The hosted ones
|
|
@@ -114,6 +114,11 @@ OG rendering uses hex internally, so an `oklch` custom accent falls back to the
|
|
|
114
114
|
default. Use a named accent (`blue`, `teal`, …) or a hex value for custom cards.
|
|
115
115
|
:::
|
|
116
116
|
|
|
117
|
+
`seo.image` is frontmatter, so it only covers Markdown and MDX content. To give a
|
|
118
|
+
custom [`.astro` page](/docs/advanced/custom-pages) its own social image — a
|
|
119
|
+
marketing home or landing page, and the way to give the home page alone a bespoke
|
|
120
|
+
share image — pass the `ogImage` prop to `PageLayout`.
|
|
121
|
+
|
|
117
122
|
## RSS feeds
|
|
118
123
|
|
|
119
124
|
Blume builds an RSS feed for each content type in `rss.types` — `blog` and
|
|
@@ -98,6 +98,55 @@ Each value is a Google Fonts slug from the curated set below:
|
|
|
98
98
|
Need a font that isn't listed, or want to drop back to the system stack? Override
|
|
99
99
|
the `--blume-font-*` tokens directly in [`theme.css`](#theme-css).
|
|
100
100
|
|
|
101
|
+
### Dark-mode colors
|
|
102
|
+
|
|
103
|
+
By default light and dark share one `accent`. Set `accentDark` for a different
|
|
104
|
+
accent in dark mode, and `background` / `backgroundDark` to override the page
|
|
105
|
+
background per mode:
|
|
106
|
+
|
|
107
|
+
```ts blume.config.ts lineNumbers
|
|
108
|
+
theme: {
|
|
109
|
+
accent: "blue",
|
|
110
|
+
accentDark: "teal", // a different accent in dark mode
|
|
111
|
+
background: "#ffffff", // light-mode page background
|
|
112
|
+
backgroundDark: "#0a0a0a", // dark-mode page background
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Each takes a named preset or any CSS color, exactly like `accent`.
|
|
117
|
+
|
|
118
|
+
### Action color
|
|
119
|
+
|
|
120
|
+
`action` is a secondary accent for primary calls to action and the `action`
|
|
121
|
+
Tailwind utilities (`bg-action`, `text-action`). It defaults to your `accent`:
|
|
122
|
+
|
|
123
|
+
```ts blume.config.ts
|
|
124
|
+
theme: {
|
|
125
|
+
action: "#ff0066",
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Background decoration
|
|
130
|
+
|
|
131
|
+
Add a subtle, theme-aware pattern behind your content with `backgroundDecoration`
|
|
132
|
+
— `gradient`, `grid`, or `windows`:
|
|
133
|
+
|
|
134
|
+
```ts blume.config.ts
|
|
135
|
+
theme: {
|
|
136
|
+
backgroundDecoration: "grid",
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
For a full background image, set `backgroundImage` (and `backgroundImageDark` for
|
|
141
|
+
a dark variant) to a URL or a path under `public/`:
|
|
142
|
+
|
|
143
|
+
```ts blume.config.ts lineNumbers
|
|
144
|
+
theme: {
|
|
145
|
+
backgroundImage: "/bg-light.svg",
|
|
146
|
+
backgroundImageDark: "/bg-dark.svg",
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
101
150
|
## theme.css
|
|
102
151
|
|
|
103
152
|
Drop a `theme.css` in your project root to override any design token. It's the
|
|
@@ -128,6 +177,7 @@ dark mode.
|
|
|
128
177
|
| `--blume-border` | Borders and dividers |
|
|
129
178
|
| `--blume-accent` | Accent color |
|
|
130
179
|
| `--blume-accent-foreground` | Text and icons on an accent background |
|
|
180
|
+
| `--blume-action` | Secondary accent (defaults to accent) |
|
|
131
181
|
| `--blume-code-background` | Code block surface |
|
|
132
182
|
| `--blume-radius` | Corner radius |
|
|
133
183
|
| `--blume-font-display` | Heading font |
|
|
@@ -159,6 +209,7 @@ a utility, so your components track the theme automatically:
|
|
|
159
209
|
| `--blume-border` | `border-border` |
|
|
160
210
|
| `--blume-accent` | `bg-accent`, `text-accent` |
|
|
161
211
|
| `--blume-accent-foreground` | `text-accent-foreground` |
|
|
212
|
+
| `--blume-action` | `bg-action`, `text-action` |
|
|
162
213
|
| `--blume-radius` | `rounded-blume` |
|
|
163
214
|
| `--blume-font-display` | `font-display` |
|
|
164
215
|
| `--blume-font-body` | `font-sans` |
|
|
@@ -133,6 +133,24 @@ A negative or breaking state, such as a deprecation.
|
|
|
133
133
|
<Badge variant="danger">Deprecated</Badge>
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
+
## Icon
|
|
137
|
+
|
|
138
|
+
Render an icon from Blume's built-in set by name — the same set the `icon` props
|
|
139
|
+
on cards, tiles, tabs, and sidebar entries draw from. Names are lowercase and
|
|
140
|
+
kebab-cased (`rocket`, `book-open`, `chevron-right`).
|
|
141
|
+
|
|
142
|
+
<Icon icon="rocket" size={20} />
|
|
143
|
+
|
|
144
|
+
```astro
|
|
145
|
+
<Icon icon="rocket" size={20} />
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`icon` is the icon name; `size` sets the pixel size (default `16`) and `color`
|
|
149
|
+
tints it (any CSS color; defaults to `currentColor`). Pass a raw `<svg>` string,
|
|
150
|
+
an image URL, or a local image path in place of a name to render your own art,
|
|
151
|
+
and add a `label` to expose it to assistive tech — without one, the icon is
|
|
152
|
+
decorative.
|
|
153
|
+
|
|
136
154
|
## File tree
|
|
137
155
|
|
|
138
156
|
Illustrate a project or folder layout. Wrap a normal Markdown list and Blume
|
package/docs/content/islands.mdx
CHANGED
|
@@ -36,6 +36,35 @@ pages (a styled callout, a pricing table), use an [MDX
|
|
|
36
36
|
override](/docs/configuration/customization) instead — it ships no JavaScript.
|
|
37
37
|
:::
|
|
38
38
|
|
|
39
|
+
## Registering islands in `components.ts`
|
|
40
|
+
|
|
41
|
+
If you'd rather keep islands next to the rest of your components — or give them a
|
|
42
|
+
different name than the file — register them with `defineComponents`. The
|
|
43
|
+
`islands` group is exactly like the `islands/` folder: each entry is available in
|
|
44
|
+
every MDX page and hydrates (defaulting to `client: "visible"`).
|
|
45
|
+
|
|
46
|
+
```ts components.ts
|
|
47
|
+
import { defineComponents } from "blume";
|
|
48
|
+
import Counter from "./widgets/Counter.tsx";
|
|
49
|
+
|
|
50
|
+
export default defineComponents({
|
|
51
|
+
islands: {
|
|
52
|
+
Counter, // <Counter /> in any MDX page, hydrated
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Reference the component by import or by a path string, and set a hydration mode
|
|
58
|
+
per island with the descriptor form:
|
|
59
|
+
|
|
60
|
+
```ts components.ts
|
|
61
|
+
export default defineComponents({
|
|
62
|
+
islands: {
|
|
63
|
+
Chart: { component: "./widgets/Chart.tsx", client: "only" },
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
39
68
|
## Hydration
|
|
40
69
|
|
|
41
70
|
By default an island uses `client:visible`: it hydrates when the reader scrolls
|
|
@@ -92,3 +121,47 @@ and children (`<Counter>label</Counter>`) arrive as the default slot.
|
|
|
92
121
|
Islands hydrate on the client, so anything you pass as a prop must be
|
|
93
122
|
serializable — strings, numbers, plain objects, not functions.
|
|
94
123
|
:::
|
|
124
|
+
|
|
125
|
+
## Hooks
|
|
126
|
+
|
|
127
|
+
Islands hydrate on their own, so there's no React context to thread project data
|
|
128
|
+
through. Instead, `blume/hooks` reads a small snapshot the layout serializes into
|
|
129
|
+
the page — no props to drill:
|
|
130
|
+
|
|
131
|
+
```tsx islands/PageInfo.tsx lineNumbers
|
|
132
|
+
import { useBlume, usePage } from "blume/hooks";
|
|
133
|
+
|
|
134
|
+
export default function PageInfo() {
|
|
135
|
+
const blume = useBlume();
|
|
136
|
+
const page = usePage();
|
|
137
|
+
if (!(blume && page)) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
return (
|
|
141
|
+
<p>
|
|
142
|
+
You're reading <strong>{page.title}</strong> on {blume.config.title}.
|
|
143
|
+
</p>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
| Hook | Returns |
|
|
149
|
+
| ------------- | --------------------------------------------------------------------- |
|
|
150
|
+
| `useBlume()` | `{ config, navigation }` for the site, or `null` before mount |
|
|
151
|
+
| `usePage()` | `{ route, title }` for the current page, or `null` before mount |
|
|
152
|
+
| `useSearch()` | `{ search, results, loading }` — query the configured search provider |
|
|
153
|
+
| `useAskAI()` | `{ ask, messages, loading, reset }` — stream from the Ask AI endpoint |
|
|
154
|
+
|
|
155
|
+
`useBlume()` and `usePage()` return `null` until the island mounts (so server and
|
|
156
|
+
client render the same first frame) — guard for it. The snapshot is emitted only
|
|
157
|
+
on pages that ship React, so a fully static site pays nothing.
|
|
158
|
+
|
|
159
|
+
On [custom pages](/docs/advanced/custom-pages) built with `PageLayout`, pass
|
|
160
|
+
`clientData` so islands there can read it:
|
|
161
|
+
|
|
162
|
+
```astro
|
|
163
|
+
<PageLayout
|
|
164
|
+
clientData={{ config: data.config, navigation: data.navigation, page: { route: "/", title: "Home" } }}
|
|
165
|
+
{/* …other props… */}
|
|
166
|
+
/>
|
|
167
|
+
```
|
|
@@ -113,6 +113,31 @@ your loose top-level pages while the sectioned content stays behind its tab,
|
|
|
113
113
|
mirroring Fumadocs' root folders. If a route has no pages of its own to show
|
|
114
114
|
this way, the full tree is shown instead, so the sidebar is never left blank.
|
|
115
115
|
|
|
116
|
+
## Selectors
|
|
117
|
+
|
|
118
|
+
For switching between whole partitions of a site — a product, a version, or any
|
|
119
|
+
grouped set of destinations — add a `selector`. Each renders as a dropdown in the
|
|
120
|
+
header, showing the option whose `path` matches the current route:
|
|
121
|
+
|
|
122
|
+
```ts blume.config.ts lineNumbers
|
|
123
|
+
navigation: {
|
|
124
|
+
selectors: [
|
|
125
|
+
{
|
|
126
|
+
kind: "version",
|
|
127
|
+
label: "Version",
|
|
128
|
+
items: [
|
|
129
|
+
{ label: "v2 (latest)", path: "/v2", icon: "rocket" },
|
|
130
|
+
{ label: "v1", path: "/v1" },
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Each item takes a `label`, a `path`, and optional `icon`, `description`, and
|
|
138
|
+
`tag`. `kind` (`dropdown`, `product`, `version`, or `language`) is a hint for how
|
|
139
|
+
the selector is used; all render the same dropdown.
|
|
140
|
+
|
|
116
141
|
## Explicit sidebar
|
|
117
142
|
|
|
118
143
|
For full control, define `navigation.sidebar` in config. When it's set, Blume
|