blume 0.5.4 → 0.6.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/dist/cli/index.js +759 -406
- package/dist/cli/index.js.map +27 -25
- package/dist/types/core/config-input.d.ts +759 -0
- package/dist/types/core/config.d.ts +126 -3
- package/dist/types/core/data.d.ts +4 -0
- package/dist/types/core/i18n-ui.d.ts +50 -0
- package/dist/types/core/schema.d.ts +334 -62
- package/dist/types/core/types.d.ts +8 -0
- package/dist/types/index.d.ts +2 -1
- package/docs/advanced/changelog.mdx +10 -2
- package/docs/configuration/ai.mdx +56 -0
- package/docs/configuration/index.mdx +0 -2
- package/docs/configuration/seo.mdx +59 -1
- package/docs/configuration/theming.mdx +14 -9
- package/docs/content/meta.mdx +3 -17
- package/docs/content/navigation.mdx +41 -4
- package/docs/content/syntax.mdx +4 -8
- package/package.json +3 -1
- package/src/ai/agent-readability.ts +97 -0
- package/src/ai/ask-context.ts +131 -8
- package/src/ai/ask-data.ts +4 -1
- package/src/astro/generate.ts +40 -11
- package/src/astro/templates.ts +90 -10
- package/src/cli/commands/build.ts +41 -1
- package/src/cli/commands/dev.ts +31 -14
- package/src/cli/dev-lock.ts +94 -21
- package/src/components/content/GithubInfo.astro +11 -10
- package/src/components/content/TypeTable.astro +8 -3
- package/src/components/content/Update.astro +12 -2
- package/src/components/content/changelog-element.ts +62 -0
- package/src/components/islands/AskAI.astro +66 -2
- package/src/components/islands/ask-ai.tsx +289 -53
- package/src/components/layout/Header.astro +1 -1
- package/src/components/layout/NavTree.astro +1 -1
- package/src/components/layout/PageActions.astro +73 -30
- package/src/components/layout/RootLayout.astro +79 -10
- package/src/core/config-input.ts +933 -0
- package/src/core/config.ts +126 -3
- package/src/core/data.ts +4 -0
- package/src/core/graph.ts +7 -2
- package/src/core/i18n-ui.ts +5 -0
- package/src/core/nav-diagnostics.ts +7 -0
- package/src/core/navigation.ts +38 -12
- package/src/core/schema.ts +130 -22
- package/src/core/sources/filesystem.ts +5 -1
- package/src/core/sources/watch.ts +43 -12
- package/src/core/types.ts +9 -0
- package/src/deploy/adapter-output.ts +82 -0
- package/src/deploy/robots.ts +37 -4
- package/src/index.ts +1 -1
- package/src/markdown/index.ts +28 -30
- package/src/markdown/math.ts +3 -2
- package/src/openapi/scalar.ts +1 -1
- package/src/registry/eject.ts +21 -14
- package/src/search/documents.ts +9 -2
- package/src/theme/entry.ts +7 -3
- package/src/theme/palette.ts +21 -14
package/src/cli/dev-lock.ts
CHANGED
|
@@ -14,26 +14,48 @@ import { logger } from "./log.ts";
|
|
|
14
14
|
/**
|
|
15
15
|
* A best-effort PID lock in the shared `.blume/` runtime dir. `blume dev`
|
|
16
16
|
* regenerates and serves `.blume` continuously, so a concurrent `build`,
|
|
17
|
-
* `eject`, or
|
|
17
|
+
* `eject`, or second `dev` that regenerates or deletes it out from under the
|
|
18
18
|
* running Vite server corrupts the dev session. The lock lets those commands
|
|
19
|
-
* detect a live dev server and refuse
|
|
19
|
+
* detect a live dev server and refuse — and, because it records the server's
|
|
20
|
+
* port, point the caller (often an agent that just tried to start its own
|
|
21
|
+
* server) at the URL to reuse instead.
|
|
20
22
|
*/
|
|
21
23
|
|
|
24
|
+
export interface DevLockInfo {
|
|
25
|
+
pid: number;
|
|
26
|
+
/** Port the dev server is bound to, when known. */
|
|
27
|
+
port?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
22
30
|
const lockPath = (outDir: string): string => join(outDir, "dev.lock");
|
|
23
31
|
|
|
32
|
+
const isValidPid = (pid: unknown): pid is number =>
|
|
33
|
+
typeof pid === "number" && Number.isInteger(pid) && pid > 0;
|
|
34
|
+
|
|
24
35
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
36
|
+
* Parse a lock file body. Current locks are JSON (`{"pid":123,"port":3001}`);
|
|
37
|
+
* a bare integer (the pre-port format) still parses as a pid-only lock.
|
|
27
38
|
*/
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
const parseLock = (raw: string): DevLockInfo | null => {
|
|
40
|
+
let data: unknown;
|
|
41
|
+
try {
|
|
42
|
+
data = JSON.parse(raw.trim());
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
32
45
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return false;
|
|
46
|
+
if (isValidPid(data)) {
|
|
47
|
+
return { pid: data };
|
|
36
48
|
}
|
|
49
|
+
if (typeof data === "object" && data !== null) {
|
|
50
|
+
const { pid, port } = data as { pid?: unknown; port?: unknown };
|
|
51
|
+
if (isValidPid(pid)) {
|
|
52
|
+
return typeof port === "number" ? { pid, port } : { pid };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const isProcessAlive = (pid: number): boolean => {
|
|
37
59
|
try {
|
|
38
60
|
// Signal 0 probes liveness without actually signaling the process.
|
|
39
61
|
process.kill(pid, 0);
|
|
@@ -45,15 +67,53 @@ export const isDevLocked = (outDir: string): boolean => {
|
|
|
45
67
|
}
|
|
46
68
|
};
|
|
47
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Read the lock on `outDir` held by a live `blume dev`, or null. A lock left
|
|
72
|
+
* by a process that has since exited (stale) is treated as absent.
|
|
73
|
+
*/
|
|
74
|
+
export const readDevLock = (outDir: string): DevLockInfo | null => {
|
|
75
|
+
const path = lockPath(outDir);
|
|
76
|
+
if (!existsSync(path)) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const lock = parseLock(readFileSync(path, "utf-8"));
|
|
80
|
+
return lock && isProcessAlive(lock.pid) ? lock : null;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/** Whether another live `blume dev` holds the lock on `outDir`. */
|
|
84
|
+
export const isDevLocked = (outDir: string): boolean =>
|
|
85
|
+
readDevLock(outDir) !== null;
|
|
86
|
+
|
|
87
|
+
const writeLock = (outDir: string, port?: number): void => {
|
|
88
|
+
writeFileSync(
|
|
89
|
+
lockPath(outDir),
|
|
90
|
+
JSON.stringify({
|
|
91
|
+
pid: process.pid,
|
|
92
|
+
...(port === undefined ? {} : { port }),
|
|
93
|
+
})
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const ownsLock = (outDir: string): boolean => {
|
|
98
|
+
const path = lockPath(outDir);
|
|
99
|
+
if (!existsSync(path)) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
return parseLock(readFileSync(path, "utf-8"))?.pid === process.pid;
|
|
104
|
+
} catch {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
48
109
|
/**
|
|
49
110
|
* Write the current process's dev lock into `outDir` and return a release
|
|
50
111
|
* function. The release only removes the file if it's still ours, so a newer
|
|
51
112
|
* dev server's lock is never clobbered.
|
|
52
113
|
*/
|
|
53
|
-
export const acquireDevLock = (outDir: string): (() => void) => {
|
|
54
|
-
const path = lockPath(outDir);
|
|
114
|
+
export const acquireDevLock = (outDir: string, port?: number): (() => void) => {
|
|
55
115
|
mkdirSync(outDir, { recursive: true });
|
|
56
|
-
|
|
116
|
+
writeLock(outDir, port);
|
|
57
117
|
let released = false;
|
|
58
118
|
return () => {
|
|
59
119
|
if (released) {
|
|
@@ -61,11 +121,8 @@ export const acquireDevLock = (outDir: string): (() => void) => {
|
|
|
61
121
|
}
|
|
62
122
|
released = true;
|
|
63
123
|
try {
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
readFileSync(path, "utf-8").trim() === String(process.pid)
|
|
67
|
-
) {
|
|
68
|
-
rmSync(path, { force: true });
|
|
124
|
+
if (ownsLock(outDir)) {
|
|
125
|
+
rmSync(lockPath(outDir), { force: true });
|
|
69
126
|
}
|
|
70
127
|
} catch {
|
|
71
128
|
// Best-effort cleanup; a stale lock is handled by the liveness check.
|
|
@@ -73,6 +130,21 @@ export const acquireDevLock = (outDir: string): (() => void) => {
|
|
|
73
130
|
};
|
|
74
131
|
};
|
|
75
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Rewrite this process's lock with the port the server actually bound (the
|
|
135
|
+
* lock is acquired before the server starts, and Vite may bump a busy port).
|
|
136
|
+
* A lock owned by another process is left alone.
|
|
137
|
+
*/
|
|
138
|
+
export const updateDevLockPort = (outDir: string, port: number): void => {
|
|
139
|
+
if (ownsLock(outDir)) {
|
|
140
|
+
writeLock(outDir, port);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/** Human-readable location of a locked dev server, e.g. " at http://localhost:3001". */
|
|
145
|
+
export const describeDevLock = (lock: DevLockInfo): string =>
|
|
146
|
+
lock.port === undefined ? "" : ` at http://localhost:${lock.port}`;
|
|
147
|
+
|
|
76
148
|
/**
|
|
77
149
|
* Exit with an error when a live `blume dev` owns the runtime dir under `root`.
|
|
78
150
|
* `action` names the operation being refused (e.g. "building"). `runtimeDir`
|
|
@@ -85,9 +157,10 @@ export const refuseIfDevRunning = (
|
|
|
85
157
|
action: string,
|
|
86
158
|
runtimeDir?: string
|
|
87
159
|
): void => {
|
|
88
|
-
|
|
160
|
+
const lock = readDevLock(resolveRuntimeDir(root, runtimeDir));
|
|
161
|
+
if (lock) {
|
|
89
162
|
logger.error(
|
|
90
|
-
`A \`blume dev\` server is running
|
|
163
|
+
`A \`blume dev\` server is running${describeDevLock(lock)}; ${action} would corrupt its .blume runtime. Reuse that server, stop it first, or re-run with --isolated to build/verify against .blume-verify without touching it.`
|
|
91
164
|
);
|
|
92
165
|
process.exit(1);
|
|
93
166
|
}
|
|
@@ -3,10 +3,17 @@
|
|
|
3
3
|
// at build time (no client JS). Pass `owner`/`repo`, or omit them to use the
|
|
4
4
|
// repo from blume.config. A `GITHUB_TOKEN` env var lifts the API rate limit.
|
|
5
5
|
// If the API is unreachable the card still renders, just without counts.
|
|
6
|
+
import { resolveIcon } from "../../theme/icons.ts";
|
|
6
7
|
import data from "blume:data";
|
|
7
8
|
import { GITHUB_MARK } from "../github-mark.ts";
|
|
8
9
|
import { fetchRepositoryInfo } from "./github-info.ts";
|
|
9
10
|
|
|
11
|
+
// Star/fork glyphs resolve from Lucide at build time; the `<svg>` wrappers below
|
|
12
|
+
// supply size and the shared `statIcon` class. (The GitHub octocat above is a
|
|
13
|
+
// brand mark, not a Lucide icon, so it stays as GITHUB_MARK.)
|
|
14
|
+
const starIcon = resolveIcon("star")?.body ?? "";
|
|
15
|
+
const forkIcon = resolveIcon("git-fork")?.body ?? "";
|
|
16
|
+
|
|
10
17
|
interface Props {
|
|
11
18
|
owner?: string;
|
|
12
19
|
repo?: string;
|
|
@@ -77,9 +84,8 @@ const statIcon =
|
|
|
77
84
|
stroke-width="2"
|
|
78
85
|
viewBox="0 0 24 24"
|
|
79
86
|
width="14"
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
</svg>
|
|
87
|
+
set:html={starIcon}
|
|
88
|
+
/>
|
|
83
89
|
<span>{numbers.format(info.stars)}</span>
|
|
84
90
|
</span>
|
|
85
91
|
<span class="flex items-center gap-1.5">
|
|
@@ -94,13 +100,8 @@ const statIcon =
|
|
|
94
100
|
stroke-width="2"
|
|
95
101
|
viewBox="0 0 24 24"
|
|
96
102
|
width="14"
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
<circle cx="6" cy="6" r="3" />
|
|
100
|
-
<circle cx="18" cy="6" r="3" />
|
|
101
|
-
<path d="M18 9v2c0 .6-.4 1-1 1H7c-.6 0-1-.4-1-1V9" />
|
|
102
|
-
<path d="M12 12v3" />
|
|
103
|
-
</svg>
|
|
103
|
+
set:html={forkIcon}
|
|
104
|
+
/>
|
|
104
105
|
<span>{numbers.format(info.forks)}</span>
|
|
105
106
|
</span>
|
|
106
107
|
</span>
|
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
// the accent-colored prop name (with a `?` suffix when optional) and its type;
|
|
5
5
|
// expanded, the row lifts into a card showing the description and a Type/Default
|
|
6
6
|
// detail grid. Uses native <details>/<summary> so the core stays React-free.
|
|
7
|
+
import { resolveIcon } from "../../theme/icons.ts";
|
|
8
|
+
|
|
9
|
+
// The row disclosure caret; resolved from Lucide at build time and injected into
|
|
10
|
+
// the summary's `<svg>` (which supplies size and the rotate-on-open transition).
|
|
11
|
+
const chevronDown = resolveIcon("chevron-down")?.body ?? "";
|
|
12
|
+
|
|
7
13
|
interface TypeEntry {
|
|
8
14
|
default?: string;
|
|
9
15
|
description?: string;
|
|
@@ -64,9 +70,8 @@ const columns = "grid grid-cols-[1fr_2fr_auto] items-center gap-4";
|
|
|
64
70
|
stroke-width="2"
|
|
65
71
|
viewBox="0 0 24 24"
|
|
66
72
|
width="16"
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
</svg>
|
|
73
|
+
set:html={chevronDown}
|
|
74
|
+
/>
|
|
70
75
|
</summary>
|
|
71
76
|
|
|
72
77
|
<div class="border-border border-t px-3 py-3 text-xs">
|
|
@@ -6,6 +6,8 @@ interface RssMetadata {
|
|
|
6
6
|
|
|
7
7
|
interface Props {
|
|
8
8
|
description?: string;
|
|
9
|
+
/** Link the heading to a dedicated page; falls back to the in-page anchor. */
|
|
10
|
+
href?: string;
|
|
9
11
|
id?: string;
|
|
10
12
|
label?: string;
|
|
11
13
|
rss?: RssMetadata;
|
|
@@ -13,7 +15,15 @@ interface Props {
|
|
|
13
15
|
title?: string;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
const {
|
|
18
|
+
const {
|
|
19
|
+
description,
|
|
20
|
+
href,
|
|
21
|
+
id: providedId,
|
|
22
|
+
label,
|
|
23
|
+
rss,
|
|
24
|
+
tags,
|
|
25
|
+
title,
|
|
26
|
+
} = Astro.props;
|
|
17
27
|
const slugify = (text: string): string =>
|
|
18
28
|
text
|
|
19
29
|
.toLowerCase()
|
|
@@ -37,7 +47,7 @@ const tagList = Array.isArray(tags) ? tags : tags ? [tags] : [];
|
|
|
37
47
|
<header class="md:border-border md:border-e md:pe-4">
|
|
38
48
|
<a
|
|
39
49
|
class="font-semibold text-foreground text-sm no-underline hover:text-accent"
|
|
40
|
-
href={`#${id}`}
|
|
50
|
+
href={href ?? `#${id}`}
|
|
41
51
|
>
|
|
42
52
|
{updateLabel}
|
|
43
53
|
</a>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client behaviour for the `<blume-changelog>` custom element wrapping the
|
|
3
|
+
* generated changelog timeline when its releases are semver-versioned. The
|
|
4
|
+
* newest major line stays visible; every older major is collapsed into a group
|
|
5
|
+
* revealed one major at a time by the "Show N.x releases" button at the bottom.
|
|
6
|
+
*
|
|
7
|
+
* Pure progressive enhancement: the server renders every release in document
|
|
8
|
+
* order, and this element hides the older-major groups on connect — so a no-JS
|
|
9
|
+
* visitor (or a crawler) still sees the complete history, and the button does
|
|
10
|
+
* nothing until the script upgrades it.
|
|
11
|
+
*
|
|
12
|
+
* Imported for its side effect (registers the element) from the changelog page.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
class BlumeChangelog extends HTMLElement {
|
|
16
|
+
connectedCallback() {
|
|
17
|
+
const groups = [
|
|
18
|
+
...this.querySelectorAll<HTMLElement>("[data-changelog-major]"),
|
|
19
|
+
];
|
|
20
|
+
const button = this.querySelector<HTMLButtonElement>(
|
|
21
|
+
"[data-changelog-more]"
|
|
22
|
+
);
|
|
23
|
+
if (groups.length === 0 || !button) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
for (const group of groups) {
|
|
28
|
+
group.hidden = true;
|
|
29
|
+
// Focusable only programmatically, so revealing a group can move focus to
|
|
30
|
+
// it for keyboard and screen-reader users without adding a tab stop.
|
|
31
|
+
group.tabIndex = -1;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let revealed = 0;
|
|
35
|
+
const sync = () => {
|
|
36
|
+
const next = groups[revealed];
|
|
37
|
+
if (next) {
|
|
38
|
+
button.textContent = `Show ${next.dataset.changelogLabel} releases`;
|
|
39
|
+
button.hidden = false;
|
|
40
|
+
} else {
|
|
41
|
+
button.hidden = true;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
button.addEventListener("click", () => {
|
|
46
|
+
const next = groups[revealed];
|
|
47
|
+
if (!next) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
next.hidden = false;
|
|
51
|
+
revealed += 1;
|
|
52
|
+
sync();
|
|
53
|
+
next.focus();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
sync();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!customElements.get("blume-changelog")) {
|
|
61
|
+
customElements.define("blume-changelog", BlumeChangelog);
|
|
62
|
+
}
|
|
@@ -1,12 +1,76 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { UIStrings } from "../../core/i18n-ui.ts";
|
|
3
|
+
import { resolveIcon } from "../../theme/icons.ts";
|
|
3
4
|
import AskAI from "./ask-ai.tsx";
|
|
4
5
|
|
|
6
|
+
interface Suggestion {
|
|
7
|
+
icon?: string;
|
|
8
|
+
label: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
5
11
|
interface Props {
|
|
6
12
|
strings?: UIStrings["ask"];
|
|
13
|
+
suggestions?: Suggestion[];
|
|
7
14
|
}
|
|
8
15
|
|
|
9
|
-
const { strings } = Astro.props;
|
|
16
|
+
const { strings, suggestions = [] } = Astro.props;
|
|
17
|
+
|
|
18
|
+
// Icons resolve to inline SVG here (server-only module), so the client island
|
|
19
|
+
// gets ready-to-render markup rather than a name it can't resolve. Bare Lucide
|
|
20
|
+
// bodies carry no styling, so wrap them in a stroke-styled root like Icon.astro.
|
|
21
|
+
const svgFor = (name: string | undefined): string | null => {
|
|
22
|
+
const resolved = resolveIcon(name ?? "sparkles") ?? resolveIcon("sparkles");
|
|
23
|
+
return resolved
|
|
24
|
+
? `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${resolved.viewBox}" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${resolved.body}</svg>`
|
|
25
|
+
: null;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const items = suggestions.map((suggestion) => ({
|
|
29
|
+
icon: svgFor(suggestion.icon),
|
|
30
|
+
label: suggestion.label,
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
// The panel's own chrome glyphs resolve here too, so the client island carries
|
|
34
|
+
// no icon data. Each value is the bare Lucide body; the island's <Glyph> wraps
|
|
35
|
+
// it in a sized, stroke-styled <svg>. Keyed by role → Lucide icon name.
|
|
36
|
+
const bodyFor = (name: string): string => resolveIcon(name)?.body ?? "";
|
|
37
|
+
const icons = {
|
|
38
|
+
arrowUp: bodyFor("arrow-up"),
|
|
39
|
+
chat: bodyFor("message-circle"),
|
|
40
|
+
clear: bodyFor("trash-2"),
|
|
41
|
+
close: bodyFor("chevrons-right"),
|
|
42
|
+
copy: bodyFor("copy"),
|
|
43
|
+
};
|
|
10
44
|
---
|
|
11
45
|
|
|
12
|
-
<AskAI client:load strings={strings} />
|
|
46
|
+
<AskAI client:load icons={icons} strings={strings} suggestions={items} />
|
|
47
|
+
|
|
48
|
+
<style is:global>
|
|
49
|
+
:root {
|
|
50
|
+
--blume-ask-width: min(30rem, 100vw);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Desktop docks the panel and shrinks the page to fit, like vercel.com/docs.
|
|
54
|
+
On smaller screens the panel is a full-width overlay (no push). */
|
|
55
|
+
@media (min-width: 1024px) {
|
|
56
|
+
body {
|
|
57
|
+
transition: padding-inline-end 0.2s ease-out;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
body[data-blume-ask="open"] {
|
|
61
|
+
padding-inline-end: var(--blume-ask-width);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
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. */
|
|
67
|
+
@media (min-width: 1280px) {
|
|
68
|
+
body[data-blume-ask="open"] [data-blume-toc] {
|
|
69
|
+
display: none;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
body[data-blume-ask="open"] [data-blume-doc-grid] {
|
|
73
|
+
grid-template-columns: 17.5rem minmax(0, 1fr);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
</style>
|