@brandon_m_behring/book-scaffold-astro 4.2.0 → 4.4.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/bin/book-scaffold.mjs +5 -1
- package/components/Exercise.astro +24 -0
- package/components/ExerciseSolutions.astro +97 -0
- package/components/Practice.astro +30 -0
- package/components/Solution.astro +27 -0
- package/components/Tip.astro +28 -0
- package/components/TipsCard.astro +44 -0
- package/dist/index.d.ts +52 -3
- package/dist/index.mjs +48 -5
- package/dist/schemas.d.ts +1 -1
- package/dist/schemas.mjs +25 -5
- package/package.json +7 -1
- package/pages/chapters/[...slug].astro +40 -0
- package/pages/chapters.astro +1 -1
- package/pages/exercises.astro +65 -0
- package/pages/tips.astro +57 -0
- package/recipes/17-draft-chapter-workflow.md +88 -0
- package/scripts/build-exercises.mjs +125 -0
- package/scripts/build-tips.mjs +143 -0
- package/src/lib/define-tips.ts +56 -0
- package/src/profile-kit.ts +16 -0
- package/src/profiles/academic.ts +2 -0
- package/src/profiles/course-notes.ts +2 -0
- package/src/profiles/minimal.ts +2 -0
- package/src/profiles/research-portfolio.ts +2 -0
- package/src/profiles/tools.ts +2 -0
- package/styles/callouts.css +145 -0
package/bin/book-scaffold.mjs
CHANGED
|
@@ -17,6 +17,8 @@ const handlers = {
|
|
|
17
17
|
'build-labels': '../scripts/build-labels.mjs',
|
|
18
18
|
'build-bib': '../scripts/build-bib.mjs',
|
|
19
19
|
'build-figures': '../scripts/build-figures.mjs',
|
|
20
|
+
'build-tips': '../scripts/build-tips.mjs',
|
|
21
|
+
'build-exercises': '../scripts/build-exercises.mjs',
|
|
20
22
|
'render-notebooks': '../scripts/render-notebooks.mjs',
|
|
21
23
|
};
|
|
22
24
|
|
|
@@ -26,7 +28,9 @@ Sub-commands:
|
|
|
26
28
|
validate Pre-flight content validator (XRef ids, Cite keys, Figure srcs).
|
|
27
29
|
build-labels Emit src/data/labels.json for cross-references (Phase C).
|
|
28
30
|
build-bib BibTeX -> CSL JSON for the <Cite> component.
|
|
29
|
-
build-figures PDF -> SVG via pdftocairo / pdftoppm fallback.
|
|
31
|
+
build-figures PDF -> SVG via pdftocairo / pdftoppm fallback (+ TikZ in v4.2.0).
|
|
32
|
+
build-tips Scan chapters for <Tip> instances; emit src/data/tips.json (v4.3.0).
|
|
33
|
+
build-exercises Scan chapters for <Exercise> instances; emit src/data/exercises.json (v4.4.0).
|
|
30
34
|
render-notebooks ipynb -> HTML via Jupyter nbconvert.
|
|
31
35
|
|
|
32
36
|
--help, -h This message.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Exercise — inline at concept introduction per CS:APP precedent
|
|
4
|
+
* (v4.3.0, closes #71).
|
|
5
|
+
*
|
|
6
|
+
* Reader is expected to attempt while reading. Solutions live at the
|
|
7
|
+
* chapter end via id reference (paired via <Solution for="{id}"> inside
|
|
8
|
+
* <ExerciseSolutions>; manual pairing — no auto-collection).
|
|
9
|
+
*
|
|
10
|
+
* Light treatment: border-left in neutral color + "Exercise" label +
|
|
11
|
+
* anchor id (`#exercise-{id}`) for cross-linking from <Solution>.
|
|
12
|
+
*
|
|
13
|
+
* Family: book-genre (cross-profile).
|
|
14
|
+
*/
|
|
15
|
+
interface Props {
|
|
16
|
+
id: string;
|
|
17
|
+
}
|
|
18
|
+
const { id } = Astro.props;
|
|
19
|
+
const anchorId = `exercise-${id}`;
|
|
20
|
+
---
|
|
21
|
+
<aside class="exercise" id={anchorId} role="note">
|
|
22
|
+
<strong class="exercise-label">Exercise</strong>
|
|
23
|
+
<div class="exercise-body"><slot /></div>
|
|
24
|
+
</aside>
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* ExerciseSolutions — chapter-end wrapper (v4.3.0 #71 + v4.4.0 auto mode).
|
|
4
|
+
*
|
|
5
|
+
* Two modes:
|
|
6
|
+
*
|
|
7
|
+
* 1. Manual (default; v4.3.0): author places <Solution for="..."> elements
|
|
8
|
+
* inside the wrapper. Full control; works with no build script.
|
|
9
|
+
*
|
|
10
|
+
* <ExerciseSolutions>
|
|
11
|
+
* <Solution for="ex-1">Solution text.</Solution>
|
|
12
|
+
* </ExerciseSolutions>
|
|
13
|
+
*
|
|
14
|
+
* 2. Auto (v4.4.0): reads src/data/exercises.json (emitted by
|
|
15
|
+
* `book-scaffold build-exercises`), scopes by the current chapter
|
|
16
|
+
* (derived from Astro.url.pathname), and renders an auto-generated
|
|
17
|
+
* list of exercise problem statements with placeholder solution
|
|
18
|
+
* slots. Author fills solutions by hand-editing the rendered HTML
|
|
19
|
+
* OR by switching back to manual mode.
|
|
20
|
+
*
|
|
21
|
+
* <ExerciseSolutions auto />
|
|
22
|
+
*
|
|
23
|
+
* Auto mode requires:
|
|
24
|
+
* - `routes.chapters: true` (or a custom `/chapters/<slug>/` route pattern)
|
|
25
|
+
* - `book-scaffold build-exercises` run before astro build (typically wired
|
|
26
|
+
* into the `prebuild` script in package.json)
|
|
27
|
+
*
|
|
28
|
+
* Graceful skip: when exercises.json is missing OR no exercises found for
|
|
29
|
+
* the current chapter, auto mode renders an empty section with a hint.
|
|
30
|
+
*
|
|
31
|
+
* Family: book-genre (cross-profile).
|
|
32
|
+
*/
|
|
33
|
+
interface Props {
|
|
34
|
+
/** v4.4.0: if true, auto-collect exercises from src/data/exercises.json
|
|
35
|
+
* (scoped to the current chapter via Astro.url.pathname). Default false
|
|
36
|
+
* uses the manual-slot mode from v4.3.0. */
|
|
37
|
+
auto?: boolean;
|
|
38
|
+
}
|
|
39
|
+
const { auto = false } = Astro.props;
|
|
40
|
+
|
|
41
|
+
// Derive current chapter slug from URL when in auto mode. Astro.url.pathname
|
|
42
|
+
// for `/chapters/<slug>/` returns `/chapters/<slug>/`; strip prefix+suffix.
|
|
43
|
+
let chapterSlug = '';
|
|
44
|
+
let exercises: Array<{ id: string; problem: string }> = [];
|
|
45
|
+
let autoError: string | null = null;
|
|
46
|
+
|
|
47
|
+
if (auto) {
|
|
48
|
+
const path = Astro.url.pathname;
|
|
49
|
+
const m = path.match(/^\/chapters\/([^/]+)\//);
|
|
50
|
+
chapterSlug = m ? m[1] : '';
|
|
51
|
+
if (!chapterSlug) {
|
|
52
|
+
autoError = `<ExerciseSolutions auto /> requires a /chapters/<slug>/ URL; got ${path}.`;
|
|
53
|
+
} else {
|
|
54
|
+
// v4.4.0 fix: project-root-relative import via Vite's import.meta.glob
|
|
55
|
+
// (the previous `../../../src/data/exercises.json` failed in node_modules contexts).
|
|
56
|
+
const exModules = import.meta.glob<{ default: Record<string, Array<{ id: string; problem: string }>> }>(
|
|
57
|
+
'/src/data/exercises.json',
|
|
58
|
+
{ eager: true },
|
|
59
|
+
);
|
|
60
|
+
const exEntry = exModules['/src/data/exercises.json'];
|
|
61
|
+
if (!exEntry) {
|
|
62
|
+
autoError = `src/data/exercises.json not found — run \`npx book-scaffold build-exercises\` first.`;
|
|
63
|
+
} else {
|
|
64
|
+
exercises = exEntry.default[chapterSlug] ?? [];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
---
|
|
69
|
+
<section class="exercise-solutions" id="exercise-solutions">
|
|
70
|
+
<h2 class="exercise-solutions-heading">Exercise solutions</h2>
|
|
71
|
+
{!auto && (
|
|
72
|
+
<div class="exercise-solutions-body"><slot /></div>
|
|
73
|
+
)}
|
|
74
|
+
{auto && autoError && (
|
|
75
|
+
<p class="exercise-solutions-hint">{autoError}</p>
|
|
76
|
+
)}
|
|
77
|
+
{auto && !autoError && exercises.length === 0 && (
|
|
78
|
+
<p class="exercise-solutions-hint">
|
|
79
|
+
No <code><Exercise></code> instances found in chapter <code>{chapterSlug}</code>.
|
|
80
|
+
Run <code>npx book-scaffold build-exercises</code> after adding exercises.
|
|
81
|
+
</p>
|
|
82
|
+
)}
|
|
83
|
+
{auto && !autoError && exercises.length > 0 && (
|
|
84
|
+
<div class="exercise-solutions-body">
|
|
85
|
+
{exercises.map((ex) => (
|
|
86
|
+
<div class="solution" id={`solution-${ex.id}`}>
|
|
87
|
+
<div class="solution-header">
|
|
88
|
+
<strong class="solution-label">Exercise {ex.id}</strong>
|
|
89
|
+
<a href={`#exercise-${ex.id}`} class="solution-backlink">↑ Exercise</a>
|
|
90
|
+
</div>
|
|
91
|
+
<blockquote class="solution-problem">{ex.problem}</blockquote>
|
|
92
|
+
<p class="solution-placeholder"><em>Add your solution here.</em></p>
|
|
93
|
+
</div>
|
|
94
|
+
))}
|
|
95
|
+
</div>
|
|
96
|
+
)}
|
|
97
|
+
</section>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Practice — end-of-chapter problem with difficulty marker per CS:APP
|
|
4
|
+
* (v4.3.0, closes #71).
|
|
5
|
+
*
|
|
6
|
+
* Difficulty 1-4 rendered as ◆ count (filled diamonds out of 4). No
|
|
7
|
+
* inline solutions; instructor-manual style — separate from Exercise
|
|
8
|
+
* which has solutions at chapter end.
|
|
9
|
+
*
|
|
10
|
+
* Anchor id (`#practice-{id}`) for cross-references.
|
|
11
|
+
*
|
|
12
|
+
* Family: book-genre (cross-profile).
|
|
13
|
+
*/
|
|
14
|
+
type Difficulty = '1' | '2' | '3' | '4';
|
|
15
|
+
interface Props {
|
|
16
|
+
id: string;
|
|
17
|
+
difficulty: Difficulty;
|
|
18
|
+
}
|
|
19
|
+
const { id, difficulty } = Astro.props;
|
|
20
|
+
const anchorId = `practice-${id}`;
|
|
21
|
+
const filled = Number.parseInt(difficulty, 10);
|
|
22
|
+
const markers = '◆'.repeat(filled) + '◇'.repeat(4 - filled);
|
|
23
|
+
---
|
|
24
|
+
<aside class="practice" id={anchorId} role="note">
|
|
25
|
+
<div class="practice-header">
|
|
26
|
+
<strong class="practice-label">Practice</strong>
|
|
27
|
+
<span class="practice-difficulty" aria-label={`Difficulty ${difficulty} of 4`}>{markers}</span>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="practice-body"><slot /></div>
|
|
30
|
+
</aside>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Solution — paired by id with an <Exercise> at chapter end
|
|
4
|
+
* (v4.3.0, closes #71).
|
|
5
|
+
*
|
|
6
|
+
* Author writes `<Solution for="ch1-ex-2">solution text</Solution>` inside
|
|
7
|
+
* an <ExerciseSolutions> wrapper. The `for` attribute matches the
|
|
8
|
+
* corresponding <Exercise>'s `id`; the rendered solution links back to
|
|
9
|
+
* the inline exercise via `#exercise-{for}`.
|
|
10
|
+
*
|
|
11
|
+
* Manual pairing — no build-time auto-collection of Exercise content
|
|
12
|
+
* (deferred to v4.4.0+). Author maintains the id↔for mapping by hand.
|
|
13
|
+
*
|
|
14
|
+
* Family: book-genre (cross-profile).
|
|
15
|
+
*/
|
|
16
|
+
interface Props {
|
|
17
|
+
for: string;
|
|
18
|
+
}
|
|
19
|
+
const { for: forId } = Astro.props;
|
|
20
|
+
---
|
|
21
|
+
<div class="solution" id={`solution-${forId}`}>
|
|
22
|
+
<div class="solution-header">
|
|
23
|
+
<strong class="solution-label">Solution</strong>
|
|
24
|
+
<a href={`#exercise-${forId}`} class="solution-backlink">↑ Exercise</a>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="solution-body"><slot /></div>
|
|
27
|
+
</div>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Tip — numbered cross-volume tip per Pragmatic Programmer precedent
|
|
4
|
+
* (v4.3.0, closes #70).
|
|
5
|
+
*
|
|
6
|
+
* Author writes `<Tip n="14" title="Care About Your Craft">rule statement</Tip>`.
|
|
7
|
+
* Number is explicit (registry doesn't auto-number). Body slot is the
|
|
8
|
+
* pull-quotable single-sentence rule.
|
|
9
|
+
*
|
|
10
|
+
* Gold border (reuses --callout-insight); distinctive single-line layout
|
|
11
|
+
* + anchor id (`#tip-{n}`) for cross-referencing from later chapters.
|
|
12
|
+
*
|
|
13
|
+
* Family: book-genre (cross-profile; usable from any preset).
|
|
14
|
+
*/
|
|
15
|
+
interface Props {
|
|
16
|
+
n: string | number;
|
|
17
|
+
title: string;
|
|
18
|
+
}
|
|
19
|
+
const { n, title } = Astro.props;
|
|
20
|
+
const anchorId = `tip-${n}`;
|
|
21
|
+
---
|
|
22
|
+
<aside class="callout callout-tip-numbered" id={anchorId} role="note">
|
|
23
|
+
<div class="callout-tip-header">
|
|
24
|
+
<span class="callout-tip-number">Tip {n}</span>
|
|
25
|
+
<strong class="callout-tip-title">{title}</strong>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="callout-body"><slot /></div>
|
|
28
|
+
</aside>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* TipsCard — print-friendly pull-out card listing all numbered tips
|
|
4
|
+
* (v4.3.0, closes #70).
|
|
5
|
+
*
|
|
6
|
+
* Reads `src/data/tips.json` (emitted by `book-scaffold build-tips`).
|
|
7
|
+
* Author places it on a back-matter print route (e.g., `/print-tips`).
|
|
8
|
+
*
|
|
9
|
+
* Renders a single-column ordered list with page-break-inside: avoid
|
|
10
|
+
* so the card prints cleanly on one or more dedicated pages.
|
|
11
|
+
*
|
|
12
|
+
* Graceful skip: if tips.json doesn't exist or is empty, renders an
|
|
13
|
+
* empty container with a note (doesn't fail the build). Consumers who
|
|
14
|
+
* don't use the tips feature can include the component without prep.
|
|
15
|
+
*/
|
|
16
|
+
// v4.4.0 fix: project-root-relative import via Vite's import.meta.glob
|
|
17
|
+
// (the previous `../../../src/data/tips.json` failed in node_modules contexts).
|
|
18
|
+
const tipsModules = import.meta.glob<{ default: Array<{ n: number; title: string; chapter: string; preview: string }> }>(
|
|
19
|
+
'/src/data/tips.json',
|
|
20
|
+
{ eager: true },
|
|
21
|
+
);
|
|
22
|
+
const tips = tipsModules['/src/data/tips.json']?.default ?? [];
|
|
23
|
+
---
|
|
24
|
+
<aside class="tips-card" role="contentinfo">
|
|
25
|
+
<h2 class="tips-card-title">Tips</h2>
|
|
26
|
+
{tips.length === 0 && (
|
|
27
|
+
<p class="tips-card-empty">
|
|
28
|
+
No tips found. Run <code>book-scaffold build-tips</code> to generate
|
|
29
|
+
<code>src/data/tips.json</code> from <code><Tip></code> instances in chapters.
|
|
30
|
+
</p>
|
|
31
|
+
)}
|
|
32
|
+
{tips.length > 0 && (
|
|
33
|
+
<ol class="tips-card-list">
|
|
34
|
+
{tips.map((tip) => (
|
|
35
|
+
<li class="tips-card-item">
|
|
36
|
+
<a href={`/tips#tip-${tip.n}`} class="tips-card-link">
|
|
37
|
+
<span class="tips-card-number">{tip.n}.</span>
|
|
38
|
+
<strong class="tips-card-rule">{tip.title}</strong>
|
|
39
|
+
</a>
|
|
40
|
+
</li>
|
|
41
|
+
))}
|
|
42
|
+
</ol>
|
|
43
|
+
)}
|
|
44
|
+
</aside>
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AstroUserConfig, AstroIntegration } from 'astro';
|
|
2
|
-
import { c as BookConfigOptions, f as BookScaffoldIntegrationOptions, Q as volatilityLevels, h as ChaptersRenderer, n as Style } from './types-
|
|
3
|
-
export { A as AcademicChapter, B as BOOK_PRESETS, a as BOOK_PROFILES, b as BookConfigError, d as BookPreset, e as BookProfile, g as BookSchemasOptions, C as ChapterFor, i as CourseNotesChapter, F as FreshnessAffordance, j as FrontmatterRouteConfig, M as MinimalChapter, P as PartKey, k as PartialRouteToggles, l as ProfileDefinition, R as ResearchPortfolioChapter, m as RouteToggles, S as StatusBadge, o as StyleInput, T as ToolsChapter, V as VolatilityBadge, p as academicChapterSchema, q as academicParts, r as changeKinds, s as changelogSchema, t as chapterStatus, u as composeStyles, v as courseNotesChapterSchema, w as defineProfile, x as defineStyle, y as minimalChapterSchema, z as normalizeFrontmatterConfig, D as patternCategories, E as patternsSchema, G as researchPortfolioChapterSchema, H as resolvePreset, I as resolveProfile, J as sourceTiers, K as sourceTiersResearch, L as sourcesSchema, N as toolSlugs, O as toolsChapterSchema } from './types-
|
|
2
|
+
import { c as BookConfigOptions, f as BookScaffoldIntegrationOptions, Q as volatilityLevels, h as ChaptersRenderer, n as Style } from './types-BhlCranN.js';
|
|
3
|
+
export { A as AcademicChapter, B as BOOK_PRESETS, a as BOOK_PROFILES, b as BookConfigError, d as BookPreset, e as BookProfile, g as BookSchemasOptions, C as ChapterFor, i as CourseNotesChapter, F as FreshnessAffordance, j as FrontmatterRouteConfig, M as MinimalChapter, P as PartKey, k as PartialRouteToggles, l as ProfileDefinition, R as ResearchPortfolioChapter, m as RouteToggles, S as StatusBadge, o as StyleInput, T as ToolsChapter, V as VolatilityBadge, p as academicChapterSchema, q as academicParts, r as changeKinds, s as changelogSchema, t as chapterStatus, u as composeStyles, v as courseNotesChapterSchema, w as defineProfile, x as defineStyle, y as minimalChapterSchema, z as normalizeFrontmatterConfig, D as patternCategories, E as patternsSchema, G as researchPortfolioChapterSchema, H as resolvePreset, I as resolveProfile, J as sourceTiers, K as sourceTiersResearch, L as sourcesSchema, N as toolSlugs, O as toolsChapterSchema } from './types-BhlCranN.js';
|
|
4
4
|
import 'astro/zod';
|
|
5
5
|
|
|
6
6
|
declare function defineBookConfig(opts: BookConfigOptions): Promise<AstroUserConfig>;
|
|
@@ -195,4 +195,53 @@ declare const BUILTIN_STYLES: {
|
|
|
195
195
|
readonly 'research-portfolio': Style;
|
|
196
196
|
};
|
|
197
197
|
|
|
198
|
-
|
|
198
|
+
/**
|
|
199
|
+
* src/lib/define-tips.ts — `defineTips()` API for cross-volume tip registry
|
|
200
|
+
* (v4.3.0, closes #70).
|
|
201
|
+
*
|
|
202
|
+
* Pragmatic Programmer-style numbered tips can be distributed across multiple
|
|
203
|
+
* volumes (e.g., Handbook tips 1-25, Architect's Reference 26-40, Field-Guide
|
|
204
|
+
* 41-50). Authors write `<Tip n="14" ...>` with explicit numbers; defineTips()
|
|
205
|
+
* lets per-volume books offset their displayed numbers + label without
|
|
206
|
+
* renumbering source tags.
|
|
207
|
+
*
|
|
208
|
+
* Branded type follows the same convention as `defineStyle` (v4.0.0 D6):
|
|
209
|
+
* type-only `unique symbol` brand, closed shape, readonly fields, no public
|
|
210
|
+
* index signature. Consumer-side metadata goes in scoped `extra` if needed.
|
|
211
|
+
*/
|
|
212
|
+
declare const TipsConfigBrand: unique symbol;
|
|
213
|
+
interface TipsConfig {
|
|
214
|
+
/** Type-only brand for nominal typing. Set automatically by defineTips. */
|
|
215
|
+
readonly [TipsConfigBrand]: true;
|
|
216
|
+
/** Internal version marker; auto-set to 1 by defineTips. */
|
|
217
|
+
readonly __tipsConfigVersion: 1;
|
|
218
|
+
/** Display offset added to each `<Tip n="N">` for cross-volume coordination.
|
|
219
|
+
* Example: Vol B with volumeOffset=25 renders `<Tip n="1">` as "Tip 26". */
|
|
220
|
+
readonly volumeOffset?: number;
|
|
221
|
+
/** Optional label shown alongside tip numbers in the /tips index + TipsCard.
|
|
222
|
+
* Example: "Vol B" → "Vol B Tip 26". */
|
|
223
|
+
readonly volumeLabel?: string;
|
|
224
|
+
/** Scoped consumer-side metadata (matches defineStyle pattern). */
|
|
225
|
+
readonly extra?: Readonly<Record<string, unknown>>;
|
|
226
|
+
}
|
|
227
|
+
/** Input type for defineTips — omits the auto-set internal fields. */
|
|
228
|
+
type TipsConfigInput = Omit<TipsConfig, typeof TipsConfigBrand | '__tipsConfigVersion'>;
|
|
229
|
+
/**
|
|
230
|
+
* Identity helper that creates a typed, branded TipsConfig.
|
|
231
|
+
* Zero runtime overhead beyond an object spread + version marker.
|
|
232
|
+
*
|
|
233
|
+
* Usage:
|
|
234
|
+
*
|
|
235
|
+
* import { defineTips } from '@brandon_m_behring/book-scaffold-astro';
|
|
236
|
+
*
|
|
237
|
+
* export const tipsConfig = defineTips({
|
|
238
|
+
* volumeOffset: 25,
|
|
239
|
+
* volumeLabel: 'Vol B',
|
|
240
|
+
* });
|
|
241
|
+
*
|
|
242
|
+
* Consumed by `<Tip>` and `<TipsCard>` components + the auto-injected
|
|
243
|
+
* `/tips` route to compute display numbers from `<Tip n="N">` source tags.
|
|
244
|
+
*/
|
|
245
|
+
declare function defineTips(opts: TipsConfigInput): TipsConfig;
|
|
246
|
+
|
|
247
|
+
export { BUILTIN_STYLES, BookConfigOptions, BookScaffoldIntegrationOptions, ChaptersRenderer, type Freshness, type FreshnessStatus, Style, type TipsConfig, type TipsConfigInput, type VolatilityLevel, academicChaptersRenderer, academicStyle, bookScaffoldIntegration, chapterSortKey, courseNotesStyle, defineBookConfig, defineMdxComponents, defineTips, fallbackChaptersRenderer, freshnessLabel, getFreshness, minimalStyle, researchPortfolioStyle, toolsChaptersRenderer, toolsStyle, volatilityLevels };
|
package/dist/index.mjs
CHANGED
|
@@ -376,8 +376,12 @@ var academicProfile = defineProfile({
|
|
|
376
376
|
// academic consumers ship their own week-based /chapters listing
|
|
377
377
|
convergence: false,
|
|
378
378
|
// tools-profile-specific
|
|
379
|
-
frontmatter: false
|
|
379
|
+
frontmatter: false,
|
|
380
380
|
// opt-in per book; see #7
|
|
381
|
+
tips: false,
|
|
382
|
+
// v4.3.0 #70: opt-in per book; requires build-tips
|
|
383
|
+
exercises: false
|
|
384
|
+
// v4.4.0: opt-in per book; requires build-exercises
|
|
381
385
|
},
|
|
382
386
|
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
|
|
383
387
|
katex: true,
|
|
@@ -490,8 +494,12 @@ var toolsProfile = defineProfile({
|
|
|
490
494
|
// tools profile ships a flat chapter index
|
|
491
495
|
convergence: true,
|
|
492
496
|
// tools profile ships convergence dashboard
|
|
493
|
-
frontmatter: false
|
|
497
|
+
frontmatter: false,
|
|
494
498
|
// opt-in per book; see #7
|
|
499
|
+
tips: false,
|
|
500
|
+
// v4.3.0 #70: opt-in per book
|
|
501
|
+
exercises: false
|
|
502
|
+
// v4.4.0: opt-in per book
|
|
495
503
|
},
|
|
496
504
|
styles: [
|
|
497
505
|
"tokens.css",
|
|
@@ -568,8 +576,12 @@ var minimalProfile = defineProfile({
|
|
|
568
576
|
print: true,
|
|
569
577
|
chapters: false,
|
|
570
578
|
convergence: false,
|
|
571
|
-
frontmatter: false
|
|
579
|
+
frontmatter: false,
|
|
572
580
|
// opt-in per book; see #7
|
|
581
|
+
tips: false,
|
|
582
|
+
// v4.3.0 #70: opt-in per book
|
|
583
|
+
exercises: false
|
|
584
|
+
// v4.4.0: opt-in per book
|
|
573
585
|
},
|
|
574
586
|
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
|
|
575
587
|
// v3.7.0 (#35): minimal aliases tools schema; fallback renderer field-dispatches if a consumer opts into routes.chapters
|
|
@@ -587,8 +599,12 @@ var courseNotesProfile = defineProfile({
|
|
|
587
599
|
chapters: false,
|
|
588
600
|
// multi-book consumers route via [book]/[slug] themselves
|
|
589
601
|
convergence: false,
|
|
590
|
-
frontmatter: false
|
|
602
|
+
frontmatter: false,
|
|
591
603
|
// opt-in per book; see #7
|
|
604
|
+
tips: false,
|
|
605
|
+
// v4.3.0 #70: opt-in per book
|
|
606
|
+
exercises: false
|
|
607
|
+
// v4.4.0: opt-in per book
|
|
592
608
|
},
|
|
593
609
|
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
|
|
594
610
|
// v3.7.0 (#35): course-notes schema has tools-style fields (chapter, volatility, sources) — fallback renderer dispatches via tools renderer
|
|
@@ -607,8 +623,12 @@ var researchPortfolioProfile = defineProfile({
|
|
|
607
623
|
// portfolio books ship their own landing/index
|
|
608
624
|
convergence: false,
|
|
609
625
|
// tools-profile-specific
|
|
610
|
-
frontmatter: true
|
|
626
|
+
frontmatter: true,
|
|
611
627
|
// portfolios universally need title/disclosure/banner pages
|
|
628
|
+
tips: false,
|
|
629
|
+
// v4.3.0 #70: opt-in per book
|
|
630
|
+
exercises: false
|
|
631
|
+
// v4.4.0: opt-in per book
|
|
612
632
|
},
|
|
613
633
|
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
|
|
614
634
|
katex: true,
|
|
@@ -800,7 +820,19 @@ var ROUTE_REGISTRY = {
|
|
|
800
820
|
search: { pattern: "/search", file: "search.astro" },
|
|
801
821
|
print: { pattern: "/print", file: "print.astro" },
|
|
802
822
|
chapters: { pattern: "/chapters", file: "chapters.astro" },
|
|
823
|
+
// v4.3.0 (#69): per-chapter dynamic route auto-injected when
|
|
824
|
+
// routes.chapters: true. Mirrors the frontmatter pattern — toolkit ships
|
|
825
|
+
// BOTH the /chapters/ index AND the /chapters/<slug>/ dynamic route.
|
|
826
|
+
// Pre-v4.3.0 each consumer wrote this file by hand; all instances were
|
|
827
|
+
// mechanical copies of the same boilerplate.
|
|
828
|
+
chaptersSlug: { pattern: "/chapters/[...slug]", file: "chapters/[...slug].astro" },
|
|
803
829
|
convergence: { pattern: "/convergence", file: "convergence.astro" },
|
|
830
|
+
// v4.3.0 (#70): cross-volume numbered-tips index. Opt-in via
|
|
831
|
+
// routes.tips: true; pairs with build-tips script + <Tip> component.
|
|
832
|
+
tips: { pattern: "/tips", file: "tips.astro" },
|
|
833
|
+
// v4.4.0: exercises index by chapter. Opt-in via routes.exercises: true;
|
|
834
|
+
// pairs with build-exercises script + <ExerciseSolutions auto /> mode.
|
|
835
|
+
exercises: { pattern: "/exercises", file: "exercises.astro" },
|
|
804
836
|
// v3.4.0 (#7): consumer-collection-backed frontmatter route. Opt-in via
|
|
805
837
|
// routes: { frontmatter: true } AND content.config.ts defining the
|
|
806
838
|
// collection (use frontmatterCollection() helper from /schemas subpath).
|
|
@@ -841,8 +873,13 @@ function bookScaffoldIntegration(opts) {
|
|
|
841
873
|
if (def.katex) {
|
|
842
874
|
injectScript("page-ssr", "import 'katex/dist/katex.min.css';");
|
|
843
875
|
}
|
|
876
|
+
const routesToInject = [];
|
|
844
877
|
for (const [name, on] of Object.entries(enabledRoutes)) {
|
|
845
878
|
if (!on) continue;
|
|
879
|
+
routesToInject.push(name);
|
|
880
|
+
if (name === "chapters") routesToInject.push("chaptersSlug");
|
|
881
|
+
}
|
|
882
|
+
for (const name of routesToInject) {
|
|
846
883
|
const route = ROUTE_REGISTRY[name];
|
|
847
884
|
if (!route) continue;
|
|
848
885
|
const pattern = name === "frontmatter" ? frontmatterPatternFromPrefix(fmPrefix) : route.pattern;
|
|
@@ -1070,6 +1107,11 @@ var BUILTIN_STYLES = {
|
|
|
1070
1107
|
"course-notes": courseNotesStyle,
|
|
1071
1108
|
"research-portfolio": researchPortfolioStyle
|
|
1072
1109
|
};
|
|
1110
|
+
|
|
1111
|
+
// src/lib/define-tips.ts
|
|
1112
|
+
function defineTips(opts) {
|
|
1113
|
+
return { __tipsConfigVersion: 1, ...opts };
|
|
1114
|
+
}
|
|
1073
1115
|
export {
|
|
1074
1116
|
BOOK_PRESETS,
|
|
1075
1117
|
BOOK_PROFILES,
|
|
@@ -1091,6 +1133,7 @@ export {
|
|
|
1091
1133
|
defineMdxComponents,
|
|
1092
1134
|
defineProfile,
|
|
1093
1135
|
defineStyle,
|
|
1136
|
+
defineTips,
|
|
1094
1137
|
fallbackChaptersRenderer,
|
|
1095
1138
|
freshnessLabel,
|
|
1096
1139
|
getFreshness,
|
package/dist/schemas.d.ts
CHANGED
package/dist/schemas.mjs
CHANGED
|
@@ -261,8 +261,12 @@ var academicProfile = defineProfile({
|
|
|
261
261
|
// academic consumers ship their own week-based /chapters listing
|
|
262
262
|
convergence: false,
|
|
263
263
|
// tools-profile-specific
|
|
264
|
-
frontmatter: false
|
|
264
|
+
frontmatter: false,
|
|
265
265
|
// opt-in per book; see #7
|
|
266
|
+
tips: false,
|
|
267
|
+
// v4.3.0 #70: opt-in per book; requires build-tips
|
|
268
|
+
exercises: false
|
|
269
|
+
// v4.4.0: opt-in per book; requires build-exercises
|
|
266
270
|
},
|
|
267
271
|
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
|
|
268
272
|
katex: true,
|
|
@@ -375,8 +379,12 @@ var toolsProfile = defineProfile({
|
|
|
375
379
|
// tools profile ships a flat chapter index
|
|
376
380
|
convergence: true,
|
|
377
381
|
// tools profile ships convergence dashboard
|
|
378
|
-
frontmatter: false
|
|
382
|
+
frontmatter: false,
|
|
379
383
|
// opt-in per book; see #7
|
|
384
|
+
tips: false,
|
|
385
|
+
// v4.3.0 #70: opt-in per book
|
|
386
|
+
exercises: false
|
|
387
|
+
// v4.4.0: opt-in per book
|
|
380
388
|
},
|
|
381
389
|
styles: [
|
|
382
390
|
"tokens.css",
|
|
@@ -453,8 +461,12 @@ var minimalProfile = defineProfile({
|
|
|
453
461
|
print: true,
|
|
454
462
|
chapters: false,
|
|
455
463
|
convergence: false,
|
|
456
|
-
frontmatter: false
|
|
464
|
+
frontmatter: false,
|
|
457
465
|
// opt-in per book; see #7
|
|
466
|
+
tips: false,
|
|
467
|
+
// v4.3.0 #70: opt-in per book
|
|
468
|
+
exercises: false
|
|
469
|
+
// v4.4.0: opt-in per book
|
|
458
470
|
},
|
|
459
471
|
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
|
|
460
472
|
// v3.7.0 (#35): minimal aliases tools schema; fallback renderer field-dispatches if a consumer opts into routes.chapters
|
|
@@ -472,8 +484,12 @@ var courseNotesProfile = defineProfile({
|
|
|
472
484
|
chapters: false,
|
|
473
485
|
// multi-book consumers route via [book]/[slug] themselves
|
|
474
486
|
convergence: false,
|
|
475
|
-
frontmatter: false
|
|
487
|
+
frontmatter: false,
|
|
476
488
|
// opt-in per book; see #7
|
|
489
|
+
tips: false,
|
|
490
|
+
// v4.3.0 #70: opt-in per book
|
|
491
|
+
exercises: false
|
|
492
|
+
// v4.4.0: opt-in per book
|
|
477
493
|
},
|
|
478
494
|
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
|
|
479
495
|
// v3.7.0 (#35): course-notes schema has tools-style fields (chapter, volatility, sources) — fallback renderer dispatches via tools renderer
|
|
@@ -492,8 +508,12 @@ var researchPortfolioProfile = defineProfile({
|
|
|
492
508
|
// portfolio books ship their own landing/index
|
|
493
509
|
convergence: false,
|
|
494
510
|
// tools-profile-specific
|
|
495
|
-
frontmatter: true
|
|
511
|
+
frontmatter: true,
|
|
496
512
|
// portfolios universally need title/disclosure/banner pages
|
|
513
|
+
tips: false,
|
|
514
|
+
// v4.3.0 #70: opt-in per book
|
|
515
|
+
exercises: false
|
|
516
|
+
// v4.4.0: opt-in per book
|
|
497
517
|
},
|
|
498
518
|
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
|
|
499
519
|
katex: true,
|
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": "4.
|
|
4
|
+
"version": "4.4.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Brandon Behring",
|
|
@@ -57,6 +57,8 @@
|
|
|
57
57
|
"./components/Divergence.astro": "./components/Divergence.astro",
|
|
58
58
|
"./components/DynConnect.astro": "./components/DynConnect.astro",
|
|
59
59
|
"./components/ExampleBox.astro": "./components/ExampleBox.astro",
|
|
60
|
+
"./components/Exercise.astro": "./components/Exercise.astro",
|
|
61
|
+
"./components/ExerciseSolutions.astro": "./components/ExerciseSolutions.astro",
|
|
60
62
|
"./components/Figure.astro": "./components/Figure.astro",
|
|
61
63
|
"./components/InsightBox.astro": "./components/InsightBox.astro",
|
|
62
64
|
"./components/KeyIdea.astro": "./components/KeyIdea.astro",
|
|
@@ -68,17 +70,21 @@
|
|
|
68
70
|
"./components/Pitfall.astro": "./components/Pitfall.astro",
|
|
69
71
|
"./components/PocLayout.astro": "./components/PocLayout.astro",
|
|
70
72
|
"./components/PolicyRef.astro": "./components/PolicyRef.astro",
|
|
73
|
+
"./components/Practice.astro": "./components/Practice.astro",
|
|
71
74
|
"./components/PreReleaseBanner.astro": "./components/PreReleaseBanner.astro",
|
|
72
75
|
"./components/Recovery.astro": "./components/Recovery.astro",
|
|
73
76
|
"./components/ResultBox.astro": "./components/ResultBox.astro",
|
|
74
77
|
"./components/Sidebar.astro": "./components/Sidebar.astro",
|
|
75
78
|
"./components/Sidenote.astro": "./components/Sidenote.astro",
|
|
76
79
|
"./components/SkillBox.astro": "./components/SkillBox.astro",
|
|
80
|
+
"./components/Solution.astro": "./components/Solution.astro",
|
|
77
81
|
"./components/SourceArchive.astro": "./components/SourceArchive.astro",
|
|
78
82
|
"./components/StatusBadge.astro": "./components/StatusBadge.astro",
|
|
79
83
|
"./components/Tag.astro": "./components/Tag.astro",
|
|
80
84
|
"./components/Theorem.astro": "./components/Theorem.astro",
|
|
85
|
+
"./components/Tip.astro": "./components/Tip.astro",
|
|
81
86
|
"./components/TipBox.astro": "./components/TipBox.astro",
|
|
87
|
+
"./components/TipsCard.astro": "./components/TipsCard.astro",
|
|
82
88
|
"./components/ToolFilter": {
|
|
83
89
|
"types": "./dist/components/ToolFilter.d.ts",
|
|
84
90
|
"import": "./dist/components/ToolFilter.mjs"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Auto-injected per-chapter dynamic route (v4.3.0, closes #69).
|
|
4
|
+
*
|
|
5
|
+
* Gated on `routes.chapters: true` via bookScaffoldIntegration's
|
|
6
|
+
* injectRoute call. Mirrors the frontmatter route pattern (v3.4.0 #7):
|
|
7
|
+
* the toolkit ships BOTH the /chapters/ index AND the /chapters/<slug>/
|
|
8
|
+
* dynamic route, so consumers don't have to write the boilerplate.
|
|
9
|
+
*
|
|
10
|
+
* Layout switching by preset:
|
|
11
|
+
* - academic + research-portfolio → Chapter.astro (KaTeX + theorem chrome)
|
|
12
|
+
* - all others → Base.astro (lighter; for tools/minimal/course-notes)
|
|
13
|
+
*
|
|
14
|
+
* Pre-v4.3.0 each consumer wrote this file by hand; all instances were
|
|
15
|
+
* mechanical copies (see post_transformers/guides/web/src/pages/chapters/
|
|
16
|
+
* [...slug].astro and double_ml_time_series/web/src/pages/chapters/
|
|
17
|
+
* [...slug].astro for the canonical pattern).
|
|
18
|
+
*/
|
|
19
|
+
import { getCollection, render } from 'astro:content';
|
|
20
|
+
import Chapter from '../../layouts/Chapter.astro';
|
|
21
|
+
import Base from '../../layouts/Base.astro';
|
|
22
|
+
|
|
23
|
+
const BOOK_PRESET = import.meta.env.BOOK_PRESET ?? 'minimal';
|
|
24
|
+
const USE_CHAPTER_LAYOUT = ['academic', 'research-portfolio'].includes(BOOK_PRESET);
|
|
25
|
+
|
|
26
|
+
export async function getStaticPaths() {
|
|
27
|
+
const chapters = await getCollection('chapters', (entry) => !entry.data.draft);
|
|
28
|
+
return chapters.map((entry) => ({
|
|
29
|
+
params: { slug: entry.id },
|
|
30
|
+
props: { entry },
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const { entry } = Astro.props;
|
|
35
|
+
const { Content, headings } = await render(entry);
|
|
36
|
+
const Layout = USE_CHAPTER_LAYOUT ? Chapter : Base;
|
|
37
|
+
---
|
|
38
|
+
<Layout entry={entry} headings={headings}>
|
|
39
|
+
<Content />
|
|
40
|
+
</Layout>
|
package/pages/chapters.astro
CHANGED
|
@@ -112,7 +112,7 @@ for (const c of chapters) {
|
|
|
112
112
|
<ol class="chapter-list">
|
|
113
113
|
{cards.map((card) => (
|
|
114
114
|
<li class="chapter-card" data-tools={card.toolsAttr}>
|
|
115
|
-
<a href={
|
|
115
|
+
<a href={`/chapters/${card.c.id}/`} class="chapter-card-link">
|
|
116
116
|
<div class="chapter-card-meta">
|
|
117
117
|
<span class="chapter-card-number">{card.number}</span>
|
|
118
118
|
{card.volatility && (
|