@brandon_m_behring/book-scaffold-astro 3.0.0-alpha.7 → 3.0.0-alpha.9
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/components/VersionSelector.tsx +3 -1
- package/dist/components/ToolFilter.d.ts +5 -0
- package/dist/components/ToolFilter.mjs +118 -0
- package/dist/components/VersionSelector.d.ts +5 -0
- package/dist/components/VersionSelector.mjs +58 -0
- package/dist/index.mjs +3 -0
- package/layouts/Base.astro +3 -6
- package/package.json +9 -3
|
@@ -50,7 +50,9 @@ export default function VersionSelector() {
|
|
|
50
50
|
};
|
|
51
51
|
}, [open]);
|
|
52
52
|
|
|
53
|
-
const
|
|
53
|
+
// STUB_VERSIONS is a const literal with >=1 entry so the fallback is
|
|
54
|
+
// always defined; the bang silences TS's noUncheckedIndexedAccess.
|
|
55
|
+
const current = STUB_VERSIONS.find((v) => v.current) ?? STUB_VERSIONS[0]!;
|
|
54
56
|
|
|
55
57
|
return (
|
|
56
58
|
<div class="version-selector" ref={ref}>
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// components/ToolFilter.tsx
|
|
2
|
+
import { useState, useRef, useEffect } from "preact/hooks";
|
|
3
|
+
import { jsx, jsxs } from "preact/jsx-runtime";
|
|
4
|
+
var STORAGE_KEY = "book:tool-filter";
|
|
5
|
+
var EVENT_NAME = "book:tool-filter:change";
|
|
6
|
+
var FILTERABLE_TOOLS = ["claude-code", "gemini-cli", "codex-cli"];
|
|
7
|
+
function readSaved() {
|
|
8
|
+
try {
|
|
9
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
10
|
+
if (!raw) return [];
|
|
11
|
+
const parsed = JSON.parse(raw);
|
|
12
|
+
if (!Array.isArray(parsed)) return [];
|
|
13
|
+
return parsed.filter((s) => typeof s === "string");
|
|
14
|
+
} catch {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function writeSaved(selected) {
|
|
19
|
+
try {
|
|
20
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(selected));
|
|
21
|
+
} catch {
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function announce(selected) {
|
|
25
|
+
window.dispatchEvent(
|
|
26
|
+
new CustomEvent(EVENT_NAME, { detail: { selected } })
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
function ToolFilter() {
|
|
30
|
+
const [open, setOpen] = useState(false);
|
|
31
|
+
const [selected, setSelected] = useState([]);
|
|
32
|
+
const ref = useRef(null);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const saved = readSaved();
|
|
35
|
+
setSelected(saved);
|
|
36
|
+
announce(saved);
|
|
37
|
+
}, []);
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (!open) return;
|
|
40
|
+
const onClick = (e) => {
|
|
41
|
+
if (ref.current && !ref.current.contains(e.target)) {
|
|
42
|
+
setOpen(false);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const onKey = (e) => {
|
|
46
|
+
if (e.key === "Escape") setOpen(false);
|
|
47
|
+
};
|
|
48
|
+
window.addEventListener("click", onClick);
|
|
49
|
+
window.addEventListener("keydown", onKey);
|
|
50
|
+
return () => {
|
|
51
|
+
window.removeEventListener("click", onClick);
|
|
52
|
+
window.removeEventListener("keydown", onKey);
|
|
53
|
+
};
|
|
54
|
+
}, [open]);
|
|
55
|
+
function toggle(tool) {
|
|
56
|
+
const next = selected.includes(tool) ? selected.filter((s) => s !== tool) : [...selected, tool];
|
|
57
|
+
setSelected(next);
|
|
58
|
+
writeSaved(next);
|
|
59
|
+
announce(next);
|
|
60
|
+
}
|
|
61
|
+
function clearAll() {
|
|
62
|
+
setSelected([]);
|
|
63
|
+
writeSaved([]);
|
|
64
|
+
announce([]);
|
|
65
|
+
}
|
|
66
|
+
const activeCount = selected.length;
|
|
67
|
+
const buttonLabel = activeCount === 0 ? "Filter chapters by tool (showing all)" : `Filter chapters by tool (${activeCount} active)`;
|
|
68
|
+
return /* @__PURE__ */ jsxs("div", { class: "tool-filter", ref, children: [
|
|
69
|
+
/* @__PURE__ */ jsxs(
|
|
70
|
+
"button",
|
|
71
|
+
{
|
|
72
|
+
type: "button",
|
|
73
|
+
class: "chrome-button tool-filter-trigger",
|
|
74
|
+
"aria-label": buttonLabel,
|
|
75
|
+
title: buttonLabel,
|
|
76
|
+
"aria-expanded": open,
|
|
77
|
+
"aria-haspopup": "true",
|
|
78
|
+
onClick: () => setOpen((v) => !v),
|
|
79
|
+
children: [
|
|
80
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2690" }),
|
|
81
|
+
activeCount > 0 && /* @__PURE__ */ jsx("span", { class: "tool-filter-count", "aria-hidden": "true", children: activeCount })
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
),
|
|
85
|
+
open && /* @__PURE__ */ jsxs("div", { class: "tool-filter-panel", role: "group", "aria-label": "Tool filters", children: [
|
|
86
|
+
/* @__PURE__ */ jsx("p", { class: "tool-filter-heading", children: "Show chapters for" }),
|
|
87
|
+
/* @__PURE__ */ jsx("ul", { class: "tool-filter-chips", children: FILTERABLE_TOOLS.map((tool) => {
|
|
88
|
+
const on = selected.includes(tool);
|
|
89
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
90
|
+
"button",
|
|
91
|
+
{
|
|
92
|
+
type: "button",
|
|
93
|
+
class: `tool-filter-chip${on ? " tool-filter-chip-on" : ""}`,
|
|
94
|
+
"aria-pressed": on,
|
|
95
|
+
onClick: () => toggle(tool),
|
|
96
|
+
children: tool
|
|
97
|
+
}
|
|
98
|
+
) });
|
|
99
|
+
}) }),
|
|
100
|
+
/* @__PURE__ */ jsxs("div", { class: "tool-filter-footer", children: [
|
|
101
|
+
/* @__PURE__ */ jsx(
|
|
102
|
+
"button",
|
|
103
|
+
{
|
|
104
|
+
type: "button",
|
|
105
|
+
class: "tool-filter-clear",
|
|
106
|
+
onClick: clearAll,
|
|
107
|
+
disabled: activeCount === 0,
|
|
108
|
+
children: "Clear"
|
|
109
|
+
}
|
|
110
|
+
),
|
|
111
|
+
/* @__PURE__ */ jsx("span", { class: "tool-filter-note", children: "Cross-tool chapters stay visible under any filter." })
|
|
112
|
+
] })
|
|
113
|
+
] })
|
|
114
|
+
] });
|
|
115
|
+
}
|
|
116
|
+
export {
|
|
117
|
+
ToolFilter as default
|
|
118
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// components/VersionSelector.tsx
|
|
2
|
+
import { useState, useRef, useEffect } from "preact/hooks";
|
|
3
|
+
import { jsx, jsxs } from "preact/jsx-runtime";
|
|
4
|
+
var STUB_VERSIONS = [
|
|
5
|
+
{ id: "", label: "Latest (main)", date: "2026-04-17", current: true },
|
|
6
|
+
{ id: "v1.0", label: "v1.0", date: "2026-05-01", current: false }
|
|
7
|
+
];
|
|
8
|
+
function VersionSelector() {
|
|
9
|
+
const [open, setOpen] = useState(false);
|
|
10
|
+
const ref = useRef(null);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (!open) return;
|
|
13
|
+
const onClick = (e) => {
|
|
14
|
+
if (ref.current && !ref.current.contains(e.target)) {
|
|
15
|
+
setOpen(false);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const onKey = (e) => {
|
|
19
|
+
if (e.key === "Escape") setOpen(false);
|
|
20
|
+
};
|
|
21
|
+
window.addEventListener("click", onClick);
|
|
22
|
+
window.addEventListener("keydown", onKey);
|
|
23
|
+
return () => {
|
|
24
|
+
window.removeEventListener("click", onClick);
|
|
25
|
+
window.removeEventListener("keydown", onKey);
|
|
26
|
+
};
|
|
27
|
+
}, [open]);
|
|
28
|
+
const current = STUB_VERSIONS.find((v) => v.current) ?? STUB_VERSIONS[0];
|
|
29
|
+
return /* @__PURE__ */ jsxs("div", { class: "version-selector", ref, children: [
|
|
30
|
+
/* @__PURE__ */ jsx(
|
|
31
|
+
"button",
|
|
32
|
+
{
|
|
33
|
+
type: "button",
|
|
34
|
+
class: "chrome-button version-selector-trigger",
|
|
35
|
+
"aria-label": "Select book version",
|
|
36
|
+
title: `Current: ${current.label} (${current.date})`,
|
|
37
|
+
"aria-expanded": open,
|
|
38
|
+
"aria-haspopup": "listbox",
|
|
39
|
+
onClick: () => setOpen((v) => !v),
|
|
40
|
+
children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "v" })
|
|
41
|
+
}
|
|
42
|
+
),
|
|
43
|
+
open && /* @__PURE__ */ jsx("ul", { class: "version-selector-menu", role: "listbox", children: STUB_VERSIONS.map((v) => /* @__PURE__ */ jsx("li", { role: "option", "aria-selected": v.current, children: /* @__PURE__ */ jsxs(
|
|
44
|
+
"a",
|
|
45
|
+
{
|
|
46
|
+
href: v.id ? `/${v.id}/` : "/",
|
|
47
|
+
class: v.current ? "version-current" : "",
|
|
48
|
+
children: [
|
|
49
|
+
/* @__PURE__ */ jsx("span", { class: "version-label", children: v.label }),
|
|
50
|
+
/* @__PURE__ */ jsx("span", { class: "version-date", children: v.date })
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
) })) })
|
|
54
|
+
] });
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
VersionSelector as default
|
|
58
|
+
};
|
package/dist/index.mjs
CHANGED
|
@@ -173,6 +173,9 @@ function bookScaffoldIntegration(opts) {
|
|
|
173
173
|
for (const sheet of styles) {
|
|
174
174
|
injectScript("page-ssr", `import '${PACKAGE_NAME}/styles/${sheet}';`);
|
|
175
175
|
}
|
|
176
|
+
if (profile === "academic") {
|
|
177
|
+
injectScript("page-ssr", "import 'katex/dist/katex.min.css';");
|
|
178
|
+
}
|
|
176
179
|
const routes = profile === "tools" ? [...DEFAULT_ROUTES_ALL, ...DEFAULT_ROUTES_TOOLS] : [...DEFAULT_ROUTES_ALL];
|
|
177
180
|
for (const route of routes) {
|
|
178
181
|
injectRoute({
|
package/layouts/Base.astro
CHANGED
|
@@ -31,12 +31,9 @@
|
|
|
31
31
|
*/
|
|
32
32
|
import '@fontsource-variable/roboto';
|
|
33
33
|
import '@fontsource-variable/source-code-pro';
|
|
34
|
-
// KaTeX
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
// rehype-katex (the JS pipeline) is profile-gated in astro.config.mjs;
|
|
38
|
-
// only the academic profile produces katex-classed DOM nodes at build.
|
|
39
|
-
import 'katex/dist/katex.min.css';
|
|
34
|
+
// KaTeX CSS is injected by bookScaffoldIntegration for academic profile
|
|
35
|
+
// only (since v3.0 alpha.8) — tools/minimal profiles don't install katex
|
|
36
|
+
// as a peer dep, so the import would fail at the consumer's build.
|
|
40
37
|
import '../styles/tokens.css';
|
|
41
38
|
import '../styles/typography.css';
|
|
42
39
|
import '../styles/layout.css';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brandon_m_behring/book-scaffold-astro",
|
|
3
3
|
"description": "Astro 6 + MDX toolkit for long-form technical books. Profile-aware (academic / tools / minimal); ships Tufte typography, KaTeX, BibTeX citations, Pagefind, Cloudflare Workers deploy. See PACKAGE_DESIGN.md for the API contract.",
|
|
4
|
-
"version": "3.0.0-alpha.
|
|
4
|
+
"version": "3.0.0-alpha.9",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Brandon Behring",
|
|
@@ -73,9 +73,15 @@
|
|
|
73
73
|
"./components/Tag.astro": "./components/Tag.astro",
|
|
74
74
|
"./components/Theorem.astro": "./components/Theorem.astro",
|
|
75
75
|
"./components/TipBox.astro": "./components/TipBox.astro",
|
|
76
|
-
"./components/ToolFilter":
|
|
76
|
+
"./components/ToolFilter": {
|
|
77
|
+
"types": "./dist/components/ToolFilter.d.ts",
|
|
78
|
+
"import": "./dist/components/ToolFilter.mjs"
|
|
79
|
+
},
|
|
77
80
|
"./components/TryThis.astro": "./components/TryThis.astro",
|
|
78
|
-
"./components/VersionSelector":
|
|
81
|
+
"./components/VersionSelector": {
|
|
82
|
+
"types": "./dist/components/VersionSelector.d.ts",
|
|
83
|
+
"import": "./dist/components/VersionSelector.mjs"
|
|
84
|
+
},
|
|
79
85
|
"./components/WarnBox.astro": "./components/WarnBox.astro",
|
|
80
86
|
"./components/WeekRef.astro": "./components/WeekRef.astro",
|
|
81
87
|
"./components/XRef.astro": "./components/XRef.astro",
|