blume 1.7.0 → 1.7.1
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 +10 -0
- package/dist/cli/{chunk-s102bysw.js → chunk-12dzsn9b.js} +140 -23
- package/dist/cli/{chunk-s102bysw.js.map → chunk-12dzsn9b.js.map} +6 -5
- package/dist/cli/{chunk-agy5rzxy.js → chunk-4ae4f395.js} +78 -48
- package/dist/cli/chunk-4ae4f395.js.map +15 -0
- package/dist/cli/{chunk-tnskyrej.js → chunk-52cwcqvp.js} +4 -4
- package/dist/cli/{chunk-drke6t0h.js → chunk-5gfw0q4j.js} +9 -9
- package/dist/cli/{chunk-ckh3a410.js → chunk-6mq7qkve.js} +3 -3
- package/dist/cli/{chunk-0qhq7b8q.js → chunk-82atea4k.js} +5 -5
- package/dist/cli/{chunk-ye9zdkgv.js → chunk-8p3xe5jv.js} +2 -2
- package/dist/cli/{chunk-kwx90v78.js → chunk-90pdhkpm.js} +11 -11
- package/dist/cli/{chunk-jk1zwka1.js → chunk-aqjvpd03.js} +4 -4
- package/dist/cli/{chunk-cfw6x4rm.js → chunk-h9ekmtz7.js} +5 -5
- package/dist/cli/{chunk-jxkxjsc1.js → chunk-he2zfgah.js} +10 -10
- package/dist/cli/{chunk-zr3ygrq3.js → chunk-j5f2wrj5.js} +2 -2
- package/dist/cli/{chunk-qq9nm3qd.js → chunk-k0v1f8bb.js} +3 -3
- package/dist/cli/{chunk-ynacq3ev.js → chunk-ka5k7cz9.js} +6 -19
- package/dist/cli/{chunk-ynacq3ev.js.map → chunk-ka5k7cz9.js.map} +3 -4
- package/dist/cli/{chunk-v5mm027v.js → chunk-kmx2mydj.js} +2 -2
- package/dist/cli/{chunk-9qs6acpw.js → chunk-mfm4sjwx.js} +11 -11
- package/dist/cli/{chunk-y3g15rvv.js → chunk-np8dmfb0.js} +6 -6
- package/dist/cli/{chunk-18tjv4f7.js → chunk-pdwg3q9g.js} +11 -11
- package/dist/cli/{chunk-v2ymm99c.js → chunk-q56730e0.js} +11 -11
- package/dist/cli/{chunk-j6pxe0dt.js → chunk-qvvpnwaz.js} +2 -2
- package/dist/cli/{chunk-xv91q4nm.js → chunk-r99hynxh.js} +3 -2
- package/dist/cli/{chunk-xv91q4nm.js.map → chunk-r99hynxh.js.map} +3 -3
- package/dist/cli/{chunk-s5dsk8bj.js → chunk-vyqj481z.js} +8 -7
- package/dist/cli/{chunk-s5dsk8bj.js.map → chunk-vyqj481z.js.map} +3 -3
- package/dist/cli/{chunk-5d4q7121.js → chunk-x1wvw7a8.js} +21 -11
- package/dist/cli/{chunk-5d4q7121.js.map → chunk-x1wvw7a8.js.map} +5 -5
- package/dist/cli/{chunk-n0nyat6g.js → chunk-ywn7t0pb.js} +2 -2
- package/dist/cli/index.js +13 -13
- package/dist/types/components/layout/nav-utils.d.ts +15 -2
- package/dist/types/core/config-input.d.ts +7 -0
- package/dist/types/core/schema.d.ts +2 -0
- package/docs/08-faq.mdx +21 -0
- package/docs/configuration/ask-ai.mdx +16 -0
- package/docs/content/sources.mdx +2 -2
- package/package.json +1 -1
- package/src/ai/ask.ts +31 -4
- package/src/astro/generate.ts +1 -0
- package/src/astro/templates.ts +104 -60
- package/src/components/content/YouTube.astro +1 -1
- package/src/components/layout/Header.astro +1 -1
- package/src/components/layout/Search.astro +11 -0
- package/src/components/layout/nav-utils.ts +30 -12
- package/src/core/config-input.ts +7 -0
- package/src/core/schema.ts +5 -0
- package/src/core/sources/assets.ts +162 -26
- package/src/core/sources/notion.ts +60 -1
- package/src/registry/eject.ts +1 -0
- package/src/theme/entry.ts +9 -0
- package/dist/cli/chunk-agy5rzxy.js.map +0 -15
- /package/dist/cli/{chunk-tnskyrej.js.map → chunk-52cwcqvp.js.map} +0 -0
- /package/dist/cli/{chunk-drke6t0h.js.map → chunk-5gfw0q4j.js.map} +0 -0
- /package/dist/cli/{chunk-ckh3a410.js.map → chunk-6mq7qkve.js.map} +0 -0
- /package/dist/cli/{chunk-0qhq7b8q.js.map → chunk-82atea4k.js.map} +0 -0
- /package/dist/cli/{chunk-ye9zdkgv.js.map → chunk-8p3xe5jv.js.map} +0 -0
- /package/dist/cli/{chunk-kwx90v78.js.map → chunk-90pdhkpm.js.map} +0 -0
- /package/dist/cli/{chunk-jk1zwka1.js.map → chunk-aqjvpd03.js.map} +0 -0
- /package/dist/cli/{chunk-cfw6x4rm.js.map → chunk-h9ekmtz7.js.map} +0 -0
- /package/dist/cli/{chunk-jxkxjsc1.js.map → chunk-he2zfgah.js.map} +0 -0
- /package/dist/cli/{chunk-zr3ygrq3.js.map → chunk-j5f2wrj5.js.map} +0 -0
- /package/dist/cli/{chunk-qq9nm3qd.js.map → chunk-k0v1f8bb.js.map} +0 -0
- /package/dist/cli/{chunk-v5mm027v.js.map → chunk-kmx2mydj.js.map} +0 -0
- /package/dist/cli/{chunk-9qs6acpw.js.map → chunk-mfm4sjwx.js.map} +0 -0
- /package/dist/cli/{chunk-y3g15rvv.js.map → chunk-np8dmfb0.js.map} +0 -0
- /package/dist/cli/{chunk-18tjv4f7.js.map → chunk-pdwg3q9g.js.map} +0 -0
- /package/dist/cli/{chunk-v2ymm99c.js.map → chunk-q56730e0.js.map} +0 -0
- /package/dist/cli/{chunk-j6pxe0dt.js.map → chunk-qvvpnwaz.js.map} +0 -0
- /package/dist/cli/{chunk-n0nyat6g.js.map → chunk-ywn7t0pb.js.map} +0 -0
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-wd27zjcz.js";
|
|
5
5
|
import {
|
|
6
6
|
needsPlaygroundProxy
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-r99hynxh.js";
|
|
8
8
|
|
|
9
9
|
// src/core/server-features.ts
|
|
10
10
|
var serverFeatures = (config) => {
|
|
@@ -27,4 +27,4 @@ var serverFeatures = (config) => {
|
|
|
27
27
|
export { serverFeatures };
|
|
28
28
|
|
|
29
29
|
//# debugId=0DFA9D31AEDBECCC64756E2164756E21
|
|
30
|
-
//# sourceMappingURL=chunk-
|
|
30
|
+
//# sourceMappingURL=chunk-ywn7t0pb.js.map
|
package/dist/cli/index.js
CHANGED
|
@@ -57,20 +57,20 @@ var main = defineCommand({
|
|
|
57
57
|
},
|
|
58
58
|
subCommands: {
|
|
59
59
|
add: lazyCommand(commandMeta.add, () => import("./chunk-4trphnvy.js"), "addCommand"),
|
|
60
|
-
audit: lazyCommand(commandMeta.audit, () => import("./chunk-
|
|
61
|
-
build: lazyCommand(commandMeta.build, () => import("./chunk-
|
|
62
|
-
check: lazyCommand(commandMeta.check, () => import("./chunk-
|
|
63
|
-
dev: lazyCommand(commandMeta.dev, () => import("./chunk-
|
|
64
|
-
doctor: lazyCommand(commandMeta.doctor, () => import("./chunk-
|
|
65
|
-
eject: lazyCommand(commandMeta.eject, () => import("./chunk-
|
|
66
|
-
eval: lazyCommand(commandMeta.eval, () => import("./chunk-
|
|
67
|
-
init: lazyCommand(commandMeta.init, () => import("./chunk-
|
|
60
|
+
audit: lazyCommand(commandMeta.audit, () => import("./chunk-h9ekmtz7.js"), "auditCommand"),
|
|
61
|
+
build: lazyCommand(commandMeta.build, () => import("./chunk-q56730e0.js"), "buildCommand"),
|
|
62
|
+
check: lazyCommand(commandMeta.check, () => import("./chunk-pdwg3q9g.js"), "checkCommand"),
|
|
63
|
+
dev: lazyCommand(commandMeta.dev, () => import("./chunk-mfm4sjwx.js"), "devCommand"),
|
|
64
|
+
doctor: lazyCommand(commandMeta.doctor, () => import("./chunk-52cwcqvp.js"), "doctorCommand"),
|
|
65
|
+
eject: lazyCommand(commandMeta.eject, () => import("./chunk-he2zfgah.js"), "ejectCommand"),
|
|
66
|
+
eval: lazyCommand(commandMeta.eval, () => import("./chunk-np8dmfb0.js"), "evalCommand"),
|
|
67
|
+
init: lazyCommand(commandMeta.init, () => import("./chunk-5gfw0q4j.js"), "initCommand"),
|
|
68
68
|
"mcp-stdio": lazyCommand(commandMeta["mcp-stdio"], () => import("./chunk-jtb45atp.js"), "mcpStdioCommand"),
|
|
69
|
-
preview: lazyCommand(commandMeta.preview, () => import("./chunk-
|
|
70
|
-
sync: lazyCommand(commandMeta.sync, () => import("./chunk-
|
|
71
|
-
translate: lazyCommand(commandMeta.translate, () => import("./chunk-
|
|
72
|
-
validate: lazyCommand(commandMeta.validate, () => import("./chunk-
|
|
73
|
-
version: lazyCommand(commandMeta.version, () => import("./chunk-
|
|
69
|
+
preview: lazyCommand(commandMeta.preview, () => import("./chunk-j5f2wrj5.js"), "previewCommand"),
|
|
70
|
+
sync: lazyCommand(commandMeta.sync, () => import("./chunk-90pdhkpm.js"), "syncCommand"),
|
|
71
|
+
translate: lazyCommand(commandMeta.translate, () => import("./chunk-k0v1f8bb.js"), "translateCommand"),
|
|
72
|
+
validate: lazyCommand(commandMeta.validate, () => import("./chunk-aqjvpd03.js"), "validateCommand"),
|
|
73
|
+
version: lazyCommand(commandMeta.version, () => import("./chunk-6mq7qkve.js"), "versionCommand")
|
|
74
74
|
}
|
|
75
75
|
});
|
|
76
76
|
loadEnvFiles(process.cwd());
|
|
@@ -78,15 +78,28 @@ export interface NavVariant {
|
|
|
78
78
|
locale: string;
|
|
79
79
|
navigation: Navigation;
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* The locale whose code must not appear as a fragment URL segment: the
|
|
83
|
+
* default locale while its URL prefix is hidden. Astro's i18n routing 404s any
|
|
84
|
+
* page URL carrying that locale's code as a segment (it expects the default
|
|
85
|
+
* locale to be unprefixed), so its trees are keyed `default` instead, the same
|
|
86
|
+
* way its page routes drop the prefix. `null` when every locale is prefixed
|
|
87
|
+
* or the site is single-locale.
|
|
88
|
+
*/
|
|
89
|
+
export declare const hiddenDefaultLocale: (i18n: {
|
|
90
|
+
defaultLocale: string;
|
|
91
|
+
hideDefaultLocalePrefix: boolean;
|
|
92
|
+
} | null) => string | null;
|
|
81
93
|
/**
|
|
82
94
|
* Every navigation tree the runtime data holds — the default, each locale's,
|
|
83
95
|
* and each archived version's per locale — keyed the way the deferred
|
|
84
96
|
* sidebar fragments' URLs are (`/blume-nav/<version>/<locale>/…`). An
|
|
85
97
|
* unlocalized version tree is keyed by `""` in the data; it maps to
|
|
86
|
-
* `default` here
|
|
98
|
+
* `default` here, as does the hidden-prefix default locale's (see
|
|
99
|
+
* `hiddenDefaultLocale`), whose current tree is `data.navigation` already.
|
|
87
100
|
*/
|
|
88
101
|
export declare const navVariants: (data: {
|
|
89
102
|
navigation: Navigation;
|
|
90
103
|
navigationByLocale: Record<string, Navigation>;
|
|
91
104
|
navigationByVersion: Record<string, Record<string, Navigation>>;
|
|
92
|
-
}) => NavVariant[];
|
|
105
|
+
}, hiddenDefault?: string | null) => NavVariant[];
|
|
@@ -628,6 +628,13 @@ export interface AskConfig {
|
|
|
628
628
|
* limiting, and streaming. Accepts an absolute URL or root-relative path.
|
|
629
629
|
*/
|
|
630
630
|
endpoint?: string;
|
|
631
|
+
/**
|
|
632
|
+
* Static request headers sent to the provider on every call — a
|
|
633
|
+
* caller-identifying header for a shared backend, for example. Values are
|
|
634
|
+
* written into the generated route as literals, so keep secrets in
|
|
635
|
+
* `apiKeyEnv` rather than here.
|
|
636
|
+
*/
|
|
637
|
+
headers?: Record<string, string>;
|
|
631
638
|
/**
|
|
632
639
|
* Extra system-prompt text appended to the built-in instructions — use it
|
|
633
640
|
* for identity, language, or tone. The built-in grounding behavior (answer
|
|
@@ -336,6 +336,7 @@ declare const aiConfigSchema: z.ZodObject<{
|
|
|
336
336
|
baseUrl: z.ZodOptional<z.ZodURL>;
|
|
337
337
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
338
338
|
endpoint: z.ZodOptional<z.ZodString>;
|
|
339
|
+
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
339
340
|
instructions: z.ZodOptional<z.ZodString>;
|
|
340
341
|
model: z.ZodDefault<z.ZodString>;
|
|
341
342
|
provider: z.ZodDefault<z.ZodEnum<{
|
|
@@ -577,6 +578,7 @@ export declare const blumeConfigSchema: z.ZodObject<{
|
|
|
577
578
|
baseUrl: z.ZodOptional<z.ZodURL>;
|
|
578
579
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
579
580
|
endpoint: z.ZodOptional<z.ZodString>;
|
|
581
|
+
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
580
582
|
instructions: z.ZodOptional<z.ZodString>;
|
|
581
583
|
model: z.ZodDefault<z.ZodString>;
|
|
582
584
|
provider: z.ZodDefault<z.ZodEnum<{
|
package/docs/08-faq.mdx
CHANGED
|
@@ -160,3 +160,24 @@ Patch oxfmt so it preserves the line break that sits directly against a `:::` fe
|
|
|
160
160
|
:::warning[Version-pinned]
|
|
161
161
|
The patch targets a specific oxfmt build — its diff references a file whose name is hashed per release (`dist/markdown-*.js`). When you bump oxfmt, regenerate the patch (e.g. `bun patch oxfmt`) or check whether the upstream fix has landed and the patch is no longer needed.
|
|
162
162
|
:::
|
|
163
|
+
|
|
164
|
+
## Why does Knip report my `blume.config.ts` dependencies as unused?
|
|
165
|
+
|
|
166
|
+
[Knip](https://knip.dev) only follows imports from files it knows are entry points, and it learns those from its built-in plugins. There is no Blume plugin yet, and Knip's Astro plugin doesn't switch on either: it looks for `astro` in your own `package.json`, but a Blume project depends on `blume`, and the generated `.blume/` Astro project is gitignored, so Knip never sees it. Nothing references `blume.config.ts`, so any package it imports gets reported as unused.
|
|
167
|
+
|
|
168
|
+
Register the files Blume loads from your project root as entries. In `knip.json`:
|
|
169
|
+
|
|
170
|
+
```json knip.json
|
|
171
|
+
{
|
|
172
|
+
"entry": [
|
|
173
|
+
"blume.config.{ts,mjs,js}",
|
|
174
|
+
"components.{ts,tsx}",
|
|
175
|
+
"islands/**/*.{ts,tsx}",
|
|
176
|
+
"pages/**/*"
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
In a monorepo, put the same `entry` list under the docs workspace in `workspaces` instead. Drop any line for a convention you don't use — `components.ts` for [component overrides](/docs/configuration/customization#component-overrides), `islands/` for [interactive islands](/docs/configuration/customization#interactive-islands), and `pages/` for [custom pages](/docs/configuration/customization#custom-pages) (adjust the last one if you've changed `content.pages`).
|
|
182
|
+
|
|
183
|
+
Knip can only follow real imports. A package that's only named inside a string — say, an [Astro integration](/docs/configuration/customization#astro-integrations) that calls `injectScript("page", "import('some-package')")` — still needs an `ignoreDependencies` entry.
|
|
@@ -166,6 +166,22 @@ ai: {
|
|
|
166
166
|
|
|
167
167
|
Set `apiKeyEnv` (and, for the named providers, `baseUrl`) on any backend to point at a different env var or proxy.
|
|
168
168
|
|
|
169
|
+
To send static request headers with every call — a caller-identifying header for a shared backend, say, so its own observability or rate limiting can tell your docs apart from other traffic — set `headers`. It works on every backend, including the gateway:
|
|
170
|
+
|
|
171
|
+
```ts blume.config.ts lineNumbers
|
|
172
|
+
ai: {
|
|
173
|
+
ask: {
|
|
174
|
+
enabled: true,
|
|
175
|
+
provider: "openai-compatible",
|
|
176
|
+
baseUrl: "https://llm.internal.example.com/v1",
|
|
177
|
+
apiKeyEnv: "INTERNAL_LLM_API_KEY",
|
|
178
|
+
headers: { "X-Caller-Id": "docs" },
|
|
179
|
+
},
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The values are written into the generated route as-is, so keep secrets in `apiKeyEnv` rather than in `headers`. The API key's `Authorization` header is applied first, so a custom header can't displace it.
|
|
184
|
+
|
|
169
185
|
:::note
|
|
170
186
|
**Inkeep** answers from the content you've indexed in the Inkeep dashboard — it runs its own retrieval — so Blume leaves it ungrounded. Every other backend is [grounded](#grounding) in this site's pages.
|
|
171
187
|
:::
|
package/docs/content/sources.mdx
CHANGED
|
@@ -163,7 +163,7 @@ A read token for a private dataset comes from the `SANITY_TOKEN` environment var
|
|
|
163
163
|
|
|
164
164
|
## Notion
|
|
165
165
|
|
|
166
|
-
The built-in `notion` source turns a Notion database into a collection: each row becomes a page, its properties become frontmatter, and its block tree becomes MDX. Callouts, toggles, columns, and code blocks map to the matching Blume components. `@notionhq/client` (v5 or later) is an optional peer dependency; Blume reads the database through its first data source.
|
|
166
|
+
The built-in `notion` source turns a Notion database into a collection: each row becomes a page, its properties become frontmatter, and its block tree becomes MDX. Callouts, toggles, columns, and code blocks map to the matching Blume components. Video blocks become a `<YouTube>` embed when they hold a YouTube link and a `<video>` player otherwise, with the block's caption as a `<Frame>` caption either way; a link to a video page rather than a media file (a Vimeo or Loom URL, say) is reported as a warning instead of embedded. `@notionhq/client` (v5 or later) is an optional peer dependency; Blume reads the database through its first data source.
|
|
167
167
|
|
|
168
168
|
```ts blume.config.ts
|
|
169
169
|
import { defineConfig } from "blume";
|
|
@@ -185,7 +185,7 @@ export default defineConfig({
|
|
|
185
185
|
});
|
|
186
186
|
```
|
|
187
187
|
|
|
188
|
-
The integration token comes from the `NOTION_TOKEN` environment variable (share the database with your integration). By default every page is imported; set `publishedValue` to make the `Status` property a publish gate — any other value then maps to `draft: true`, which production builds drop. **Notion image URLs are signed and expire**, so the adapter downloads them at build time into the site's assets and rewrites the references — a CMS
|
|
188
|
+
The integration token comes from the `NOTION_TOKEN` environment variable (share the database with your integration). By default every page is imported; set `publishedValue` to make the `Status` property a publish gate — any other value then maps to `draft: true`, which production builds drop. **Notion image and video URLs are signed and expire**, so the adapter downloads them at build time into the site's assets and rewrites the references — a CMS asset never rots a static build. API calls are paced through a small request pool (3 at a time, matching Notion's per-integration rate limit) so databases with hundreds of pages import without tripping `429` responses; set `concurrency` on the source to tune it.
|
|
189
189
|
|
|
190
190
|
## Preview and sync
|
|
191
191
|
|
package/package.json
CHANGED
package/src/ai/ask.ts
CHANGED
|
@@ -7,16 +7,31 @@ import type { AskAiConfig } from "../core/schema.ts";
|
|
|
7
7
|
* AI SDK's OpenAI-compatible provider.
|
|
8
8
|
*/
|
|
9
9
|
export type AskBackend =
|
|
10
|
-
| { kind: "gateway"; model: string }
|
|
11
|
-
| {
|
|
10
|
+
| { headers?: AskHeaders; kind: "gateway"; model: string }
|
|
11
|
+
| {
|
|
12
|
+
apiKeyEnv: string;
|
|
13
|
+
headers?: AskHeaders;
|
|
14
|
+
kind: "openrouter";
|
|
15
|
+
model: string;
|
|
16
|
+
}
|
|
12
17
|
| {
|
|
13
18
|
apiKeyEnv: string;
|
|
14
19
|
baseUrl: string;
|
|
20
|
+
headers?: AskHeaders;
|
|
15
21
|
kind: "openai-compatible";
|
|
16
22
|
model: string;
|
|
17
23
|
name: string;
|
|
18
24
|
};
|
|
19
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Static request headers (`ai.ask.headers`) every backend forwards to its
|
|
28
|
+
* provider factory. Every provider Blume generates against accepts the same
|
|
29
|
+
* `headers` option, so the map travels unchanged; the OpenAI-compatible
|
|
30
|
+
* provider applies them after the `Authorization` header it derives from the
|
|
31
|
+
* API key, so a custom header can't displace auth.
|
|
32
|
+
*/
|
|
33
|
+
export type AskHeaders = Record<string, string>;
|
|
34
|
+
|
|
20
35
|
interface AskPreset {
|
|
21
36
|
apiKeyEnv: string;
|
|
22
37
|
baseUrl?: string;
|
|
@@ -69,17 +84,28 @@ const ASK_PRESETS: AskPresetRegistry = {
|
|
|
69
84
|
|
|
70
85
|
const DEFAULT_MODEL = "openai/gpt-5.5";
|
|
71
86
|
|
|
87
|
+
/**
|
|
88
|
+
* The `headers` field every backend variant shares. An empty map is dropped so
|
|
89
|
+
* the generated route only carries a `headers` option when there is something
|
|
90
|
+
* to send.
|
|
91
|
+
*/
|
|
92
|
+
const askHeadersField = (ask?: AskAiConfig): { headers?: AskHeaders } =>
|
|
93
|
+
ask?.headers && Object.keys(ask.headers).length > 0
|
|
94
|
+
? { headers: ask.headers }
|
|
95
|
+
: {};
|
|
96
|
+
|
|
72
97
|
/** Resolve the `ai.ask` config into the backend the endpoint is built against. */
|
|
73
98
|
export const resolveAskBackend = (ask?: AskAiConfig): AskBackend => {
|
|
74
99
|
const provider = ask?.provider ?? "gateway";
|
|
75
100
|
const model = ask?.model ?? DEFAULT_MODEL;
|
|
101
|
+
const headers = askHeadersField(ask);
|
|
76
102
|
if (provider === "gateway") {
|
|
77
|
-
return { kind: "gateway", model };
|
|
103
|
+
return { ...headers, kind: "gateway", model };
|
|
78
104
|
}
|
|
79
105
|
const preset = ASK_PRESETS[provider];
|
|
80
106
|
const apiKeyEnv = ask?.apiKeyEnv ?? preset?.apiKeyEnv ?? "API_KEY";
|
|
81
107
|
if (provider === "openrouter") {
|
|
82
|
-
return { apiKeyEnv, kind: "openrouter", model };
|
|
108
|
+
return { apiKeyEnv, ...headers, kind: "openrouter", model };
|
|
83
109
|
}
|
|
84
110
|
// `llmgateway`, `inkeep`, and the generic `openai-compatible` provider all
|
|
85
111
|
// stream through the AI SDK's OpenAI-compatible provider. The schema requires
|
|
@@ -87,6 +113,7 @@ export const resolveAskBackend = (ask?: AskAiConfig): AskBackend => {
|
|
|
87
113
|
return {
|
|
88
114
|
apiKeyEnv,
|
|
89
115
|
baseUrl: ask?.baseUrl ?? preset?.baseUrl ?? "",
|
|
116
|
+
...headers,
|
|
90
117
|
kind: OPENAI_COMPATIBLE,
|
|
91
118
|
model,
|
|
92
119
|
name: preset?.name ?? OPENAI_COMPATIBLE,
|
package/src/astro/generate.ts
CHANGED
package/src/astro/templates.ts
CHANGED
|
@@ -1080,16 +1080,23 @@ export const askEndpointTemplate = (
|
|
|
1080
1080
|
];
|
|
1081
1081
|
let setup = "";
|
|
1082
1082
|
let modelExpr = JSON.stringify(backend.model);
|
|
1083
|
+
// `ai.ask.headers`, inlined as literals: every provider factory below takes
|
|
1084
|
+
// the same `headers` option, so one line serves all three.
|
|
1085
|
+
const headersField = backend.headers
|
|
1086
|
+
? `\n headers: ${JSON.stringify(backend.headers)},`
|
|
1087
|
+
: "";
|
|
1083
1088
|
if (backend.kind === "gateway") {
|
|
1084
|
-
setup = `\nconst gateway = createGateway({
|
|
1089
|
+
setup = `\nconst gateway = createGateway({
|
|
1090
|
+
apiKey: getSecret("AI_GATEWAY_API_KEY"),${headersField}
|
|
1091
|
+
});\n`;
|
|
1085
1092
|
modelExpr = `gateway(${JSON.stringify(backend.model)})`;
|
|
1086
1093
|
} else if (backend.kind === "openrouter") {
|
|
1087
1094
|
imports.push(
|
|
1088
1095
|
'import { createOpenRouter } from "@openrouter/ai-sdk-provider";'
|
|
1089
1096
|
);
|
|
1090
|
-
setup = `\nconst openrouter = createOpenRouter({
|
|
1091
|
-
|
|
1092
|
-
|
|
1097
|
+
setup = `\nconst openrouter = createOpenRouter({
|
|
1098
|
+
apiKey: getSecret(${JSON.stringify(backend.apiKeyEnv)}),${headersField}
|
|
1099
|
+
});\n`;
|
|
1093
1100
|
modelExpr = `openrouter(${JSON.stringify(backend.model)})`;
|
|
1094
1101
|
} else if (backend.kind === "openai-compatible") {
|
|
1095
1102
|
imports.push(
|
|
@@ -1097,7 +1104,7 @@ export const askEndpointTemplate = (
|
|
|
1097
1104
|
);
|
|
1098
1105
|
setup = `\nconst provider = createOpenAICompatible({
|
|
1099
1106
|
apiKey: getSecret(${JSON.stringify(backend.apiKeyEnv)}),
|
|
1100
|
-
baseURL: ${JSON.stringify(backend.baseUrl)}
|
|
1107
|
+
baseURL: ${JSON.stringify(backend.baseUrl)},${headersField}
|
|
1101
1108
|
name: ${JSON.stringify(backend.name)},
|
|
1102
1109
|
});\n`;
|
|
1103
1110
|
modelExpr = `provider(${JSON.stringify(backend.model)})`;
|
|
@@ -1692,15 +1699,22 @@ export function GET({ props }: { props: { section: string } }) {
|
|
|
1692
1699
|
* every collapsed section on every page. Ids are `navGroupIds` over the full
|
|
1693
1700
|
* tree, the same ids the pages' sidebars use; the `version`/`locale`
|
|
1694
1701
|
* segments pick the navigation variant (`current`/`default` for the
|
|
1695
|
-
* unversioned, unlocalized tree
|
|
1696
|
-
*
|
|
1702
|
+
* unversioned, unlocalized tree, and `default` again for the default locale
|
|
1703
|
+
* while its URL prefix is hidden: Astro's i18n routing 404s a page URL that
|
|
1704
|
+
* carries the default locale's code as a segment). Rendered with no current
|
|
1705
|
+
* route — a section that isn't on the active path has no active row by
|
|
1706
|
+
* definition.
|
|
1697
1707
|
*/
|
|
1698
1708
|
export const navFragmentTemplate = (): string =>
|
|
1699
1709
|
`---
|
|
1700
1710
|
// Generated by Blume. Do not edit.
|
|
1701
1711
|
import NavTree from "blume/components/layout/NavTree.astro";
|
|
1702
1712
|
import { withBase } from "blume/components/islands/base-path.ts";
|
|
1703
|
-
import {
|
|
1713
|
+
import {
|
|
1714
|
+
hiddenDefaultLocale,
|
|
1715
|
+
navGroupIds,
|
|
1716
|
+
navVariants,
|
|
1717
|
+
} from "blume/components/layout/nav-utils.ts";
|
|
1704
1718
|
import type { NavNode } from "blume/core/types.ts";
|
|
1705
1719
|
import data from "blume:data";
|
|
1706
1720
|
|
|
@@ -1711,15 +1725,16 @@ export const partial = true;
|
|
|
1711
1725
|
// Only imports are in scope here (Astro hoists getStaticPaths), which is why
|
|
1712
1726
|
// the variant walk lives in nav-utils.
|
|
1713
1727
|
export function getStaticPaths() {
|
|
1714
|
-
return navVariants(data).flatMap(
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1728
|
+
return navVariants(data, hiddenDefaultLocale(data.config.i18n)).flatMap(
|
|
1729
|
+
({ locale, navigation, version }) =>
|
|
1730
|
+
[...navGroupIds(navigation.sidebar)]
|
|
1731
|
+
.filter(([node]) => (node.display ?? "flat") !== "flat")
|
|
1732
|
+
.map(([, id]) => ({ params: { id, locale, version } }))
|
|
1718
1733
|
);
|
|
1719
1734
|
}
|
|
1720
1735
|
|
|
1721
1736
|
const { id, locale, version } = Astro.params;
|
|
1722
|
-
const variant = navVariants(data).find(
|
|
1737
|
+
const variant = navVariants(data, hiddenDefaultLocale(data.config.i18n)).find(
|
|
1723
1738
|
(entry) => entry.version === version && entry.locale === locale
|
|
1724
1739
|
);
|
|
1725
1740
|
const ids = variant
|
|
@@ -1933,37 +1948,22 @@ const htmlLang = i18n ? i18n.defaultLocale : "en";
|
|
|
1933
1948
|
</ReferenceLayout>
|
|
1934
1949
|
`;
|
|
1935
1950
|
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
needsReact: boolean;
|
|
1948
|
-
}): string => {
|
|
1949
|
-
const mathImport = options.mathEnabled
|
|
1951
|
+
/**
|
|
1952
|
+
* The component map every rendered entry body receives via
|
|
1953
|
+
* `<Content components={...} />`: Blume's content components, `<Math>` when
|
|
1954
|
+
* math is enabled, the island wrappers, and the user's `mdxComponents`
|
|
1955
|
+
* overrides. Shared by the catch-all page and the changelog index so a
|
|
1956
|
+
* `:::` directive (compiled to `<Callout>`) or an explicit `<Steps>` renders
|
|
1957
|
+
* wherever an entry is rendered — an MDX body throws "Expected component
|
|
1958
|
+
* `X` to be defined" the moment a page renders it without this map.
|
|
1959
|
+
*/
|
|
1960
|
+
const contentComponentsSource = (mathEnabled: boolean) => {
|
|
1961
|
+
const mathImport = mathEnabled
|
|
1950
1962
|
? 'import Math from "blume/components/content/Math.astro";\n'
|
|
1951
1963
|
: "";
|
|
1952
|
-
const mathEntry =
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
? "\n clientData={{ config: data.config, navigation, page: { route, title: seo.title ?? title } }}"
|
|
1956
|
-
: "";
|
|
1957
|
-
|
|
1958
|
-
return `---
|
|
1959
|
-
// Generated by Blume. Do not edit.
|
|
1960
|
-
import { getEntry, render } from "astro:content";
|
|
1961
|
-
import type { CollectionKey } from "astro:content";
|
|
1962
|
-
import RootLayout from "blume/components/layout/RootLayout.astro";
|
|
1963
|
-
import { withBase } from "blume/components/islands/base-path.ts";
|
|
1964
|
-
import { stripBasePath, withBasePath } from "blume/core/base-path.ts";
|
|
1965
|
-
import { resolveSlot } from "blume/components/layout/overrides.ts";
|
|
1966
|
-
import Accordion from "blume/components/content/Accordion.astro";
|
|
1964
|
+
const mathEntry = mathEnabled ? "Math,\n " : "";
|
|
1965
|
+
return {
|
|
1966
|
+
imports: `import Accordion from "blume/components/content/Accordion.astro";
|
|
1967
1967
|
import AccordionItem from "blume/components/content/AccordionItem.astro";
|
|
1968
1968
|
import AutoTypeTable from "blume/components/content/AutoTypeTable.astro";
|
|
1969
1969
|
import Badge from "blume/components/content/Badge.astro";
|
|
@@ -2003,19 +2003,8 @@ import ApiOverview from "blume/components/openapi/ApiOverview.astro";
|
|
|
2003
2003
|
import ApiTagOperations from "blume/components/openapi/ApiTagOperations.astro";
|
|
2004
2004
|
import Operation from "blume/components/openapi/Operation.astro";
|
|
2005
2005
|
${mathImport}import { mdxComponents as userMdx, layoutOverrides } from "../generated/components.ts";
|
|
2006
|
-
import { islandComponents } from "../generated/islands.ts"
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
const Color = Object.assign(ColorRoot, { Item: ColorItem, Row: ColorRow });
|
|
2010
|
-
const Tree = Object.assign(TreeRoot, { File: TreeFile, Folder: TreeFolder });
|
|
2011
|
-
|
|
2012
|
-
// Docs content is file-based and always prerendered, even in server output
|
|
2013
|
-
// (where only endpoints like /api/ask render on demand). Without this, server
|
|
2014
|
-
// builds would render this route on demand and ignore getStaticPaths, leaving
|
|
2015
|
-
// the entry id undefined.
|
|
2016
|
-
export const prerender = true;
|
|
2017
|
-
|
|
2018
|
-
const components = {
|
|
2006
|
+
import { islandComponents } from "../generated/islands.ts";`,
|
|
2007
|
+
map: `{
|
|
2019
2008
|
Accordion,
|
|
2020
2009
|
AccordionItem,
|
|
2021
2010
|
ApiOverview,
|
|
@@ -2052,8 +2041,52 @@ const components = {
|
|
|
2052
2041
|
YouTube,
|
|
2053
2042
|
${mathEntry}...islandComponents,
|
|
2054
2043
|
...userMdx,
|
|
2044
|
+
}`,
|
|
2045
|
+
};
|
|
2055
2046
|
};
|
|
2056
2047
|
|
|
2048
|
+
export const catchAllPageTemplate = (options: {
|
|
2049
|
+
exportEpub: boolean;
|
|
2050
|
+
exportPdf: boolean;
|
|
2051
|
+
mathEnabled: boolean;
|
|
2052
|
+
/**
|
|
2053
|
+
* Whether the sidebar defers collapsed sections to prerendered fragments
|
|
2054
|
+
* (see `navFragmentTemplate`); true when some group renders as a
|
|
2055
|
+
* disclosure or a drill-in panel.
|
|
2056
|
+
*/
|
|
2057
|
+
navFragments?: boolean;
|
|
2058
|
+
/** Serialize the island-hooks snapshot; only needed when React is enabled. */
|
|
2059
|
+
needsReact: boolean;
|
|
2060
|
+
}): string => {
|
|
2061
|
+
const { imports: componentImports, map: componentMap } =
|
|
2062
|
+
contentComponentsSource(options.mathEnabled);
|
|
2063
|
+
// The island-hooks snapshot (config + navigation + page) for `blume/hooks`.
|
|
2064
|
+
const clientData = options.needsReact
|
|
2065
|
+
? "\n clientData={{ config: data.config, navigation, page: { route, title: seo.title ?? title } }}"
|
|
2066
|
+
: "";
|
|
2067
|
+
|
|
2068
|
+
return `---
|
|
2069
|
+
// Generated by Blume. Do not edit.
|
|
2070
|
+
import { getEntry, render } from "astro:content";
|
|
2071
|
+
import type { CollectionKey } from "astro:content";
|
|
2072
|
+
import RootLayout from "blume/components/layout/RootLayout.astro";
|
|
2073
|
+
import { withBase } from "blume/components/islands/base-path.ts";
|
|
2074
|
+
import { stripBasePath, withBasePath } from "blume/core/base-path.ts";
|
|
2075
|
+
import { resolveSlot } from "blume/components/layout/overrides.ts";
|
|
2076
|
+
${componentImports}
|
|
2077
|
+
import data from "blume:data";
|
|
2078
|
+
|
|
2079
|
+
const Color = Object.assign(ColorRoot, { Item: ColorItem, Row: ColorRow });
|
|
2080
|
+
const Tree = Object.assign(TreeRoot, { File: TreeFile, Folder: TreeFolder });
|
|
2081
|
+
|
|
2082
|
+
// Docs content is file-based and always prerendered, even in server output
|
|
2083
|
+
// (where only endpoints like /api/ask render on demand). Without this, server
|
|
2084
|
+
// builds would render this route on demand and ignore getStaticPaths, leaving
|
|
2085
|
+
// the entry id undefined.
|
|
2086
|
+
export const prerender = true;
|
|
2087
|
+
|
|
2088
|
+
const components = ${componentMap};
|
|
2089
|
+
|
|
2057
2090
|
export function getStaticPaths() {
|
|
2058
2091
|
return data.routes.map((route) => ({
|
|
2059
2092
|
params: { slug: route.path === "/" ? undefined : route.path.slice(1) },
|
|
@@ -2324,7 +2357,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
|
|
|
2324
2357
|
exportEpub={${options.exportEpub}}${
|
|
2325
2358
|
options.navFragments
|
|
2326
2359
|
? `
|
|
2327
|
-
navFragmentBase={withBase(\`/blume-nav/\${version || "current"}/\${i18n ? locale : "default"}\`)}`
|
|
2360
|
+
navFragmentBase={withBase(\`/blume-nav/\${version || "current"}/\${i18n && localePrefix(locale) ? locale : "default"}\`)}`
|
|
2328
2361
|
: ""
|
|
2329
2362
|
}
|
|
2330
2363
|
openInChat={data.config.openInChat}
|
|
@@ -2355,6 +2388,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
|
|
|
2355
2388
|
export const changelogIndexTemplate = (options: {
|
|
2356
2389
|
exportEpub: boolean;
|
|
2357
2390
|
exportPdf: boolean;
|
|
2391
|
+
mathEnabled: boolean;
|
|
2358
2392
|
/** Serialize the island-hooks snapshot; only needed when React is enabled. */
|
|
2359
2393
|
needsReact: boolean;
|
|
2360
2394
|
/** Whether a `staged` collection exists (non-filesystem changelog sources). */
|
|
@@ -2368,6 +2402,8 @@ export const changelogIndexTemplate = (options: {
|
|
|
2368
2402
|
const stagedSpread = options.staged
|
|
2369
2403
|
? '\n ...(await getCollection("staged")),'
|
|
2370
2404
|
: "";
|
|
2405
|
+
const { imports: componentImports, map: componentMap } =
|
|
2406
|
+
contentComponentsSource(options.mathEnabled);
|
|
2371
2407
|
|
|
2372
2408
|
return `---
|
|
2373
2409
|
// Generated by Blume. Do not edit.
|
|
@@ -2377,11 +2413,19 @@ import Update from "blume/components/content/Update.astro";
|
|
|
2377
2413
|
import { withBase } from "blume/components/islands/base-path.ts";
|
|
2378
2414
|
import { resolveSlot } from "blume/components/layout/overrides.ts";
|
|
2379
2415
|
import { resolveDateFormatOptions } from "blume/core/date-format.ts";
|
|
2380
|
-
|
|
2416
|
+
${componentImports}
|
|
2381
2417
|
import data from "blume:data";
|
|
2382
2418
|
|
|
2419
|
+
const Color = Object.assign(ColorRoot, { Item: ColorItem, Row: ColorRow });
|
|
2420
|
+
const Tree = Object.assign(TreeRoot, { File: TreeFile, Folder: TreeFolder });
|
|
2421
|
+
|
|
2383
2422
|
export const prerender = true;
|
|
2384
2423
|
|
|
2424
|
+
// Entry bodies are MDX rendered outside the catch-all, so they need the same
|
|
2425
|
+
// component map: a \`:::\` callout or \`<Steps>\` in a release note otherwise
|
|
2426
|
+
// throws "Expected component ... to be defined" at build time.
|
|
2427
|
+
const components = ${componentMap};
|
|
2428
|
+
|
|
2385
2429
|
const entryDate = (entry: {
|
|
2386
2430
|
data: { date?: string | null; changelog?: { date?: string | null } | null };
|
|
2387
2431
|
}) => entry.data.date ?? entry.data.changelog?.date ?? null;
|
|
@@ -2580,7 +2624,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
|
|
|
2580
2624
|
>
|
|
2581
2625
|
{majorGroups[0].items.map(({ Content, href, id, label, date, tags }) => (
|
|
2582
2626
|
<Update description={date} href={href} id={id} label={label} tags={tags}>
|
|
2583
|
-
<Content />
|
|
2627
|
+
<Content components={components} />
|
|
2584
2628
|
</Update>
|
|
2585
2629
|
))}
|
|
2586
2630
|
{majorGroups.slice(1).map((group) => (
|
|
@@ -2591,7 +2635,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
|
|
|
2591
2635
|
>
|
|
2592
2636
|
{group.items.map(({ Content, href, id, label, date, tags }) => (
|
|
2593
2637
|
<Update description={date} href={href} id={id} label={label} tags={tags}>
|
|
2594
|
-
<Content />
|
|
2638
|
+
<Content components={components} />
|
|
2595
2639
|
</Update>
|
|
2596
2640
|
))}
|
|
2597
2641
|
</section>
|
|
@@ -2611,7 +2655,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
|
|
|
2611
2655
|
<div class="not-prose mt-8">
|
|
2612
2656
|
{items.map(({ Content, href, id, label, date, tags }) => (
|
|
2613
2657
|
<Update description={date} href={href} id={id} label={label} tags={tags}>
|
|
2614
|
-
<Content />
|
|
2658
|
+
<Content components={components} />
|
|
2615
2659
|
</Update>
|
|
2616
2660
|
))}
|
|
2617
2661
|
</div>
|
|
@@ -20,7 +20,7 @@ const src = videoId ? youtubeEmbedUrl(videoId, { start }) : null;
|
|
|
20
20
|
|
|
21
21
|
{
|
|
22
22
|
src && (
|
|
23
|
-
<div class="not-prose my-6 aspect-video overflow-hidden rounded-blume border border-border bg-muted/30">
|
|
23
|
+
<div class="not-prose my-6 aspect-video w-full overflow-hidden rounded-blume border border-border bg-muted/30">
|
|
24
24
|
<iframe
|
|
25
25
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
|
26
26
|
allowfullscreen
|
|
@@ -150,7 +150,7 @@ const ctaClass = [
|
|
|
150
150
|
// so the offset can't drift while open, and re-measure when a banner dismiss or
|
|
151
151
|
// a resize/rotation moves the header. Crossing into the desktop breakpoint
|
|
152
152
|
// (64rem, Tailwind `lg`) closes the drawer so the scroll lock can't outlive it.
|
|
153
|
-
const clickScript = `(()=>{const dr=()=>{const h=document.querySelector("[data-blume-header]");if(h){document.documentElement.style.setProperty("--blume-drawer-top",h.getBoundingClientRect().bottom+"px");}};document.addEventListener("click",(e)=>{const t=e.target.closest("[data-blume-theme-toggle]");if(t){const d=document.documentElement;const next=d.dataset.theme==="dark"?"light":"dark";const s=document.createElement("style");s.appendChild(document.createTextNode("*,*::before,*::after{transition:none!important}"));document.head.appendChild(s);d.dataset.theme=next;localStorage.setItem("blume-theme",next);window.getComputedStyle(d).opacity;setTimeout(()=>document.head.removeChild(s),1);}const n=e.target.closest("[data-blume-nav-toggle]");if(n){const d=document.documentElement;const open=d.toggleAttribute("data-blume-nav-open");if(open){dr();}
|
|
153
|
+
const clickScript = `(()=>{const dr=()=>{const h=document.querySelector("[data-blume-header]");if(h){document.documentElement.style.setProperty("--blume-drawer-top",h.getBoundingClientRect().bottom+"px");}};document.addEventListener("click",(e)=>{const t=e.target.closest("[data-blume-theme-toggle]");if(t){const d=document.documentElement;const next=d.dataset.theme==="dark"?"light":"dark";const s=document.createElement("style");s.appendChild(document.createTextNode("*,*::before,*::after{transition:none!important}"));document.head.appendChild(s);d.dataset.theme=next;localStorage.setItem("blume-theme",next);window.getComputedStyle(d).opacity;setTimeout(()=>document.head.removeChild(s),1);}const n=e.target.closest("[data-blume-nav-toggle]");if(n){const d=document.documentElement;const open=d.toggleAttribute("data-blume-nav-open");if(open){dr();}}const b=e.target.closest("[data-blume-banner-dismiss]");if(b){const bar=b.closest("[data-blume-banner]");const k=bar&&bar.getAttribute("data-banner-key");if(k){localStorage.setItem("blume-banner:"+k,"1");}document.documentElement.setAttribute("data-blume-banner-hidden","");requestAnimationFrame(dr);}});addEventListener("resize",()=>{const d=document.documentElement;if(!d.hasAttribute("data-blume-nav-open")){return;}if(matchMedia("(min-width:64rem)").matches){d.removeAttribute("data-blume-nav-open");}else{dr();}});})();`;
|
|
154
154
|
---
|
|
155
155
|
|
|
156
156
|
<header
|
|
@@ -445,6 +445,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
445
445
|
this.dialog.addEventListener("keydown", (event) =>
|
|
446
446
|
this.onKeydown(event)
|
|
447
447
|
);
|
|
448
|
+
this.dialog.addEventListener("close", () => this.unlockPageScroll());
|
|
448
449
|
this.dialog.addEventListener("click", (event) => {
|
|
449
450
|
if (event.target === this.dialog) {
|
|
450
451
|
this.dialog.close();
|
|
@@ -454,6 +455,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
454
455
|
|
|
455
456
|
disconnectedCallback() {
|
|
456
457
|
document.removeEventListener("keydown", this.#onDocumentKeydown);
|
|
458
|
+
this.unlockPageScroll();
|
|
457
459
|
}
|
|
458
460
|
|
|
459
461
|
isField(target: EventTarget | null): boolean {
|
|
@@ -473,6 +475,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
473
475
|
return;
|
|
474
476
|
}
|
|
475
477
|
this.dialog.showModal();
|
|
478
|
+
this.lockPageScroll();
|
|
476
479
|
this.input.focus();
|
|
477
480
|
this.input.select();
|
|
478
481
|
if (!this.loaded) {
|
|
@@ -498,6 +501,14 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
498
501
|
this.render();
|
|
499
502
|
}
|
|
500
503
|
|
|
504
|
+
lockPageScroll() {
|
|
505
|
+
document.documentElement.setAttribute("data-blume-search-dialog-open", "");
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
unlockPageScroll() {
|
|
509
|
+
document.documentElement.removeAttribute("data-blume-search-dialog-open");
|
|
510
|
+
}
|
|
511
|
+
|
|
501
512
|
onKeydown(event: KeyboardEvent) {
|
|
502
513
|
if (event.key === "ArrowDown") {
|
|
503
514
|
event.preventDefault();
|