@brandon_m_behring/book-scaffold-astro 4.23.0 → 4.25.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/CLAUDE.md +9 -1
- package/components/ChapterNav.astro +3 -2
- package/components/ChapterTOC.astro +8 -1
- package/components/Cite.astro +2 -1
- package/components/Epigraph.astro +28 -0
- package/components/EvidenceTag.astro +102 -0
- package/components/MarginFigure.astro +49 -0
- package/components/Newthought.astro +18 -0
- package/components/PartReview.astro +2 -1
- package/components/Rationale.astro +1 -1
- package/components/SectionMap.astro +56 -0
- package/components/SectionMap.tsx +170 -0
- package/components/Sidebar.astro +10 -7
- package/components/Term.astro +2 -1
- package/components/TipsCard.astro +2 -1
- package/dist/components/ExamRunner.d.ts +2 -2
- package/dist/components/Flashcards.d.ts +2 -2
- package/dist/components/SectionMap.d.ts +10 -0
- package/dist/components/SectionMap.mjs +107 -0
- package/dist/{exam-manifest-DbTHo90M.d.ts → exam-manifest-DnqyzZ9I.d.ts} +1 -1
- package/dist/{flashcards-DPFFQhP8.d.ts → flashcards-okekZcl8.d.ts} +1 -1
- package/dist/index.d.ts +71 -8
- package/dist/index.mjs +38 -5
- package/dist/{schemas-DDWDRUxs.d.ts → schemas-CKipJ5Ie.d.ts} +22 -1
- package/dist/schemas.d.ts +2 -2
- package/dist/schemas.mjs +15 -5
- package/dist/{types-CGN95mrX.d.ts → types-Bn7rKhgP.d.ts} +1 -1
- package/layouts/Base.astro +4 -3
- package/layouts/Chapter.astro +36 -1
- package/package.json +11 -1
- package/recipes/04-component-library.md +6 -2
- package/recipes/16-tikz-figures.md +39 -0
- package/src/lib/section-map.ts +88 -0
- package/src/profiles/academic.ts +1 -1
- package/src/profiles/course-notes.ts +1 -1
- package/src/profiles/minimal.ts +1 -1
- package/src/profiles/research-portfolio.ts +1 -1
- package/src/profiles/tools.ts +1 -0
- package/src/schemas.ts +15 -0
- package/styles/layout.css +64 -0
- package/styles/section-map.css +130 -0
- package/styles/typography.css +51 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as preact from 'preact';
|
|
2
|
+
import { MarkdownHeading } from 'astro';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
/** TOC headings (tocHeadings output) — slug/depth/text only, serializable. */
|
|
6
|
+
headings: MarkdownHeading[];
|
|
7
|
+
}
|
|
8
|
+
declare function SectionMap({ headings }: Props): preact.JSX.Element;
|
|
9
|
+
|
|
10
|
+
export { SectionMap as default };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// components/SectionMap.tsx
|
|
2
|
+
import { useEffect, useRef } from "preact/hooks";
|
|
3
|
+
|
|
4
|
+
// src/lib/section-map.ts
|
|
5
|
+
function pickActive(visible, prev) {
|
|
6
|
+
if (visible.length === 0) return prev;
|
|
7
|
+
let bestNonNeg = null;
|
|
8
|
+
let bestAbove = null;
|
|
9
|
+
for (const h of visible) {
|
|
10
|
+
if (h.top >= 0) {
|
|
11
|
+
if (bestNonNeg === null || h.top < bestNonNeg.top) bestNonNeg = h;
|
|
12
|
+
} else {
|
|
13
|
+
if (bestAbove === null || h.top > bestAbove.top) bestAbove = h;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
if (bestNonNeg !== null) return bestNonNeg.slug;
|
|
17
|
+
if (bestAbove !== null) return bestAbove.slug;
|
|
18
|
+
return prev;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// components/SectionMap.tsx
|
|
22
|
+
import { jsx } from "preact/jsx-runtime";
|
|
23
|
+
function SectionMap({ headings }) {
|
|
24
|
+
const ref = useRef(null);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const root = ref.current?.closest("[data-section-map-root]");
|
|
27
|
+
if (!root) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
"SectionMap: no [data-section-map-root] ancestor \u2014 mount the island inside the <nav> that contains its links (see the DOM contract in SectionMap.tsx)."
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
const tracked = [];
|
|
33
|
+
for (const h of headings) {
|
|
34
|
+
const el = document.getElementById(h.slug);
|
|
35
|
+
if (el) tracked.push({ slug: h.slug, el });
|
|
36
|
+
}
|
|
37
|
+
if (tracked.length === 0) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`SectionMap: manifest/DOM drift \u2014 none of the ${headings.length} heading id(s) resolve to an element (#${headings.map((h) => h.slug).join(", #")}).`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
const lastTracked = tracked[tracked.length - 1];
|
|
43
|
+
function linkFor(slug) {
|
|
44
|
+
return root.querySelector(
|
|
45
|
+
`a[data-section-id="${CSS.escape(slug)}"]`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
const inView = /* @__PURE__ */ new Set();
|
|
49
|
+
let active = null;
|
|
50
|
+
function setActive(next) {
|
|
51
|
+
if (next === active) return;
|
|
52
|
+
if (active !== null) {
|
|
53
|
+
const prevLink = linkFor(active);
|
|
54
|
+
if (prevLink) {
|
|
55
|
+
prevLink.classList.remove("active");
|
|
56
|
+
prevLink.removeAttribute("aria-current");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (next !== null) {
|
|
60
|
+
const nextLink = linkFor(next);
|
|
61
|
+
if (nextLink) {
|
|
62
|
+
nextLink.classList.add("active");
|
|
63
|
+
nextLink.setAttribute("aria-current", "page");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
active = next;
|
|
67
|
+
}
|
|
68
|
+
function recompute() {
|
|
69
|
+
const visible = [];
|
|
70
|
+
for (const t of tracked) {
|
|
71
|
+
if (inView.has(t.el)) {
|
|
72
|
+
visible.push({ slug: t.slug, top: t.el.getBoundingClientRect().top });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
setActive(pickActive(visible, active));
|
|
76
|
+
}
|
|
77
|
+
const observer = new IntersectionObserver(
|
|
78
|
+
(entries) => {
|
|
79
|
+
for (const entry of entries) {
|
|
80
|
+
if (entry.isIntersecting) inView.add(entry.target);
|
|
81
|
+
else inView.delete(entry.target);
|
|
82
|
+
}
|
|
83
|
+
recompute();
|
|
84
|
+
},
|
|
85
|
+
// A negative bottom margin biases "active" toward the heading nearest the
|
|
86
|
+
// top of the viewport (the section you're reading INTO), not the last one
|
|
87
|
+
// peeking in at the bottom.
|
|
88
|
+
{ rootMargin: "0px 0px -70% 0px", threshold: 0 }
|
|
89
|
+
);
|
|
90
|
+
for (const t of tracked) observer.observe(t.el);
|
|
91
|
+
function onScroll() {
|
|
92
|
+
const atBottom = window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 2;
|
|
93
|
+
if (atBottom) setActive(lastTracked.slug);
|
|
94
|
+
}
|
|
95
|
+
window.addEventListener("scroll", onScroll, { passive: true });
|
|
96
|
+
window.addEventListener("resize", onScroll, { passive: true });
|
|
97
|
+
return () => {
|
|
98
|
+
observer.disconnect();
|
|
99
|
+
window.removeEventListener("scroll", onScroll);
|
|
100
|
+
window.removeEventListener("resize", onScroll);
|
|
101
|
+
};
|
|
102
|
+
}, [headings]);
|
|
103
|
+
return /* @__PURE__ */ jsx("span", { ref, class: "section-map-mount", hidden: true, "aria-hidden": "true" });
|
|
104
|
+
}
|
|
105
|
+
export {
|
|
106
|
+
SectionMap as default
|
|
107
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { AstroUserConfig, AstroIntegration } from 'astro';
|
|
2
|
-
import { c as BookConfigOptions, f as BookScaffoldIntegrationOptions, h as ChaptersRenderer, l as Style } from './types-
|
|
3
|
-
export { B as BOOK_PRESETS, a as BOOK_PROFILES, b as BookConfigError, d as BookPreset, e as BookProfile, g as BookSchemasOptions, C as ChapterFor, F as FreshnessAffordance, i as FrontmatterRouteConfig, P as PartKey, j as PartialRouteToggles, k as ProfileDefinition, R as RouteToggles, S as StatusBadge, m as StyleInput, V as VolatilityBadge, n as composeStyles, o as defineProfile, p as defineStyle, q as normalizeFrontmatterConfig, r as resolvePreset, s as resolveProfile } from './types-
|
|
4
|
-
import {
|
|
5
|
-
export { A as AcademicChapter, B as BloomLevel, C as CourseNotesChapter, G as GlossaryTerm, M as MinimalChapter, P as Provenance, a as QuestionType, R as ResearchPortfolioChapter, T as ToolsChapter, b as academicChapterSchema, d as bloomLevels, e as changeKinds, f as changelogSchema, g as chapterStatus, h as citationBackstops, i as courseNotesChapterSchema, j as glossarySchema, m as minimalChapterSchema, p as patternCategories, k as patternsSchema,
|
|
1
|
+
import { AstroUserConfig, AstroIntegration, MarkdownHeading } from 'astro';
|
|
2
|
+
import { c as BookConfigOptions, f as BookScaffoldIntegrationOptions, h as ChaptersRenderer, l as Style } from './types-Bn7rKhgP.js';
|
|
3
|
+
export { B as BOOK_PRESETS, a as BOOK_PROFILES, b as BookConfigError, d as BookPreset, e as BookProfile, g as BookSchemasOptions, C as ChapterFor, F as FreshnessAffordance, i as FrontmatterRouteConfig, P as PartKey, j as PartialRouteToggles, k as ProfileDefinition, R as RouteToggles, S as StatusBadge, m as StyleInput, V as VolatilityBadge, n as composeStyles, o as defineProfile, p as defineStyle, q as normalizeFrontmatterConfig, r as resolvePreset, s as resolveProfile } from './types-Bn7rKhgP.js';
|
|
4
|
+
import { E as volatilityLevels, c as academicParts, Q as Question } from './schemas-CKipJ5Ie.js';
|
|
5
|
+
export { A as AcademicChapter, B as BloomLevel, C as CourseNotesChapter, G as GlossaryTerm, M as MinimalChapter, P as Provenance, a as QuestionType, R as ResearchPortfolioChapter, T as ToolsChapter, b as academicChapterSchema, d as bloomLevels, e as changeKinds, f as changelogSchema, g as chapterStatus, h as citationBackstops, i as courseNotesChapterSchema, j as glossarySchema, l as layoutModes, m as minimalChapterSchema, p as patternCategories, k as patternsSchema, n as provenanceObject, o as provenanceSchema, q as questionDifficulties, r as questionSchema, s as questionTypes, t as refineQuestion, u as refinedQuestionSchema, v as researchPortfolioChapterSchema, w as sourceTiers, x as sourceTiersResearch, y as sourcesSchema, z as toolSlugs, D as toolsChapterSchema } from './schemas-CKipJ5Ie.js';
|
|
6
6
|
export { KIND_LABEL, ResolvedTheoremLabel, THEOREM_KINDS, TheoremKind, TheoremLabelProps, resolveTheoremNumber, theoremLabel } from './lib/theorem-label.js';
|
|
7
|
-
export { D as DomainScore, E as ExamBlueprint, a as ExamQuestion, b as ExamResult, R as RoutingChapter, c as buildExamManifest, d as deriveDomainRouting, s as sampleExam, e as scoreExam, f as shuffle, g as spreadBlueprint } from './exam-manifest-
|
|
8
|
-
export { F as FlashcardRef, b as buildFlashcardDeck } from './flashcards-
|
|
7
|
+
export { D as DomainScore, E as ExamBlueprint, a as ExamQuestion, b as ExamResult, R as RoutingChapter, c as buildExamManifest, d as deriveDomainRouting, s as sampleExam, e as scoreExam, f as shuffle, g as spreadBlueprint } from './exam-manifest-DnqyzZ9I.js';
|
|
8
|
+
export { F as FlashcardRef, b as buildFlashcardDeck } from './flashcards-okekZcl8.js';
|
|
9
9
|
import 'astro/zod';
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -360,6 +360,69 @@ declare function distinctChaptersSorted<T extends {
|
|
|
360
360
|
data: Pick<Question, 'chapter'>;
|
|
361
361
|
}>(entries: readonly T[]): string[];
|
|
362
362
|
|
|
363
|
+
/**
|
|
364
|
+
* section-map.ts — PURE heading-selection + scrollspy logic for the right-gutter
|
|
365
|
+
* "On this page" section map (#section-map).
|
|
366
|
+
*
|
|
367
|
+
* No DOM, no Preact: this is the testable core under both ChapterTOC.astro (the
|
|
368
|
+
* collapsed mobile fallback) and the SectionMap island (the sticky gutter nav).
|
|
369
|
+
* Two pure, total functions:
|
|
370
|
+
*
|
|
371
|
+
* - tocHeadings: the ONE filter (h2+h3) that both the fallback TOC and the
|
|
372
|
+
* gutter map share — a single source of truth so the two
|
|
373
|
+
* never disagree about which headings are "on this page".
|
|
374
|
+
* - pickActive: given the headings currently intersecting the viewport (slug
|
|
375
|
+
* + viewport-relative top), choose which one is "active". The
|
|
376
|
+
* island feeds it IntersectionObserver state; node:test feeds
|
|
377
|
+
* it plain arrays. Browser geometry stays OUT of this file.
|
|
378
|
+
*
|
|
379
|
+
* Both are unit-tested in tests/section-map.test.mjs (node:test, no browser),
|
|
380
|
+
* mirroring exam-engine.ts.
|
|
381
|
+
*/
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Filter a chapter's headings to the TOC set: depth 2–3 only. h1 is the chapter
|
|
385
|
+
* title (rendered by ChapterHeader, not the body) and h4+ is noise in an anchor
|
|
386
|
+
* list. This is the shared contract — ChapterTOC.astro and SectionMap.astro both
|
|
387
|
+
* call it so the fallback and the gutter map carry IDENTICAL entries. Pure: it
|
|
388
|
+
* copies (filter) rather than mutating the input.
|
|
389
|
+
*/
|
|
390
|
+
declare function tocHeadings(headings: MarkdownHeading[]): MarkdownHeading[];
|
|
391
|
+
/**
|
|
392
|
+
* One visible heading, as the island measures it: its slug and its
|
|
393
|
+
* viewport-relative top (CSS pixels — `getBoundingClientRect().top`). A negative
|
|
394
|
+
* `top` means the heading has scrolled above the top of the viewport.
|
|
395
|
+
*/
|
|
396
|
+
interface VisibleHeading {
|
|
397
|
+
slug: string;
|
|
398
|
+
/** Viewport-relative top in px (negative = scrolled above the fold). */
|
|
399
|
+
top: number;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Choose the active section slug from the headings currently intersecting the
|
|
403
|
+
* viewport.
|
|
404
|
+
*
|
|
405
|
+
* Rule (a stable, total scrollspy):
|
|
406
|
+
* 1. Prefer the topmost heading at or below the top of the viewport — the
|
|
407
|
+
* visible heading with the SMALLEST non-negative `top`. That's the section
|
|
408
|
+
* the reader has just scrolled to / is reading into.
|
|
409
|
+
* 2. If every visible heading is above the fold (all `top` negative — e.g. a
|
|
410
|
+
* heading STRADDLING the top edge: its top is slightly negative but its box
|
|
411
|
+
* still overlaps the top zone, so it's intersecting yet has no non-negative
|
|
412
|
+
* sibling below it), fall back to the one nearest the fold from above: the
|
|
413
|
+
* GREATEST `top` (closest to 0 from the negative side). That keeps the
|
|
414
|
+
* enclosing section lit instead of going dark mid-section. (A heading whose
|
|
415
|
+
* box FULLY scrolled above the top has left `inView` entirely — it isn't in
|
|
416
|
+
* `visible` at all, and is handled by the empty-set → `prev` branch below.)
|
|
417
|
+
* 3. If NOTHING is visible (the observer reports an empty set — between two
|
|
418
|
+
* sparse intersections, or scrolled past the last heading), retain `prev`
|
|
419
|
+
* so the highlight is sticky rather than flickering off.
|
|
420
|
+
*
|
|
421
|
+
* Pure + total: no DOM, no throw, deterministic. Ties (equal `top`) resolve to
|
|
422
|
+
* the first in iteration order, which the island passes in document order.
|
|
423
|
+
*/
|
|
424
|
+
declare function pickActive(visible: ReadonlyArray<VisibleHeading>, prev: string | null): string | null;
|
|
425
|
+
|
|
363
426
|
/** One exercise as emitted by `book-scaffold build-exercises` (src/data/exercises.json). */
|
|
364
427
|
interface ReviewExercise {
|
|
365
428
|
id: string;
|
|
@@ -488,4 +551,4 @@ type TipsConfigInput = Omit<TipsConfig, typeof TipsConfigBrand | '__tipsConfigVe
|
|
|
488
551
|
*/
|
|
489
552
|
declare function defineTips(opts: TipsConfigInput): TipsConfig;
|
|
490
553
|
|
|
491
|
-
export { ACADEMIC_PART_NAMES, BRANDON_PORTFOLIO_DEFAULT, BUILTIN_STYLES, BookConfigOptions, BookScaffoldIntegrationOptions, ChaptersRenderer, DEFAULT_GITHUB_BRANCH, type Freshness, type FreshnessStatus, type PartReviewGroup, type PartReviewSelection, Question, type ReviewChapter, type ReviewExercise, Style, type TipsConfig, type TipsConfigInput, UNKNOWN_PART_ORDINAL, type VolatilityLevel, academicChaptersRenderer, academicPartHeading, academicPartName, academicPartOrdinal, academicParts, academicStyle, assertEnumProp, assertKnownDomain, bookScaffoldIntegration, buildGithubUrl, chapterLabel, chapterSortKey, courseNotesStyle, defineBookConfig, defineMdxComponents, defineTips, deriveObjectiveMap, distinctChaptersSorted, fallbackChaptersRenderer, freshnessLabel, getFreshness, groupByChapter, groupByDomain, minimalStyle, originUrlFromGitConfig, parseRepoSlug, researchPortfolioStyle, resolveBookHref, resolveGithubRepo, selectPartExercises, sortQuestions, toolsChaptersRenderer, toolsStyle, volatilityLevels };
|
|
554
|
+
export { ACADEMIC_PART_NAMES, BRANDON_PORTFOLIO_DEFAULT, BUILTIN_STYLES, BookConfigOptions, BookScaffoldIntegrationOptions, ChaptersRenderer, DEFAULT_GITHUB_BRANCH, type Freshness, type FreshnessStatus, type PartReviewGroup, type PartReviewSelection, Question, type ReviewChapter, type ReviewExercise, Style, type TipsConfig, type TipsConfigInput, UNKNOWN_PART_ORDINAL, type VisibleHeading, type VolatilityLevel, academicChaptersRenderer, academicPartHeading, academicPartName, academicPartOrdinal, academicParts, academicStyle, assertEnumProp, assertKnownDomain, bookScaffoldIntegration, buildGithubUrl, chapterLabel, chapterSortKey, courseNotesStyle, defineBookConfig, defineMdxComponents, defineTips, deriveObjectiveMap, distinctChaptersSorted, fallbackChaptersRenderer, freshnessLabel, getFreshness, groupByChapter, groupByDomain, minimalStyle, originUrlFromGitConfig, parseRepoSlug, pickActive, researchPortfolioStyle, resolveBookHref, resolveGithubRepo, selectPartExercises, sortQuestions, tocHeadings, toolsChaptersRenderer, toolsStyle, volatilityLevels };
|
package/dist/index.mjs
CHANGED
|
@@ -168,6 +168,7 @@ var chapterStatus = [
|
|
|
168
168
|
"scaffolded",
|
|
169
169
|
"planned"
|
|
170
170
|
];
|
|
171
|
+
var layoutModes = ["default", "wide"];
|
|
171
172
|
var citationBackstops = ["research-kb", "manual", "unverified"];
|
|
172
173
|
var provenanceObject = z.object({
|
|
173
174
|
ai_tools: z.array(z.string()).default([]),
|
|
@@ -186,6 +187,8 @@ var academicChapterSchema = z.object({
|
|
|
186
187
|
title: z.string().min(1),
|
|
187
188
|
slug: z.string().optional(),
|
|
188
189
|
// v4.9.0: explicit URL slug override (else filename → entry.id)
|
|
190
|
+
layout: z.enum(layoutModes).optional(),
|
|
191
|
+
// 1d: per-page width knob (default measure | 'wide'); threaded as data-layout
|
|
189
192
|
status: z.enum(chapterStatus),
|
|
190
193
|
roadmap_lines: z.tuple([z.number().int(), z.number().int()]).optional(),
|
|
191
194
|
code_path: z.string().optional(),
|
|
@@ -207,6 +210,8 @@ var toolsChapterSchema = z.object({
|
|
|
207
210
|
title: z.string().min(1),
|
|
208
211
|
slug: z.string().optional(),
|
|
209
212
|
// v4.9.0: explicit URL slug override (else filename → entry.id)
|
|
213
|
+
layout: z.enum(layoutModes).optional(),
|
|
214
|
+
// 1d: per-page width knob (default measure | 'wide'); threaded as data-layout
|
|
210
215
|
part: z.number().int().min(0).max(10),
|
|
211
216
|
chapter: z.number().int().min(0).max(99),
|
|
212
217
|
volatility: z.enum(volatilityLevels),
|
|
@@ -232,6 +237,8 @@ var courseNotesChapterSchema = z.object({
|
|
|
232
237
|
title: z.string().min(1),
|
|
233
238
|
slug: z.string().optional(),
|
|
234
239
|
// v4.9.0: explicit URL slug override (else filename → entry.id)
|
|
240
|
+
layout: z.enum(layoutModes).optional(),
|
|
241
|
+
// 1d: per-page width knob (default measure | 'wide'); threaded as data-layout
|
|
235
242
|
chapter: z.number().int().min(0).max(99),
|
|
236
243
|
part: z.number().int().min(0).max(20).default(1),
|
|
237
244
|
description: z.string().optional(),
|
|
@@ -268,6 +275,8 @@ var researchPortfolioChapterSchema = z.object({
|
|
|
268
275
|
title: z.string().min(1),
|
|
269
276
|
slug: z.string().optional(),
|
|
270
277
|
// explicit slug override (otherwise filename)
|
|
278
|
+
layout: z.enum(layoutModes).optional(),
|
|
279
|
+
// 1d: per-page width knob (default measure | 'wide'); threaded as data-layout
|
|
271
280
|
description: z.string().optional(),
|
|
272
281
|
// Hierarchy — accept either academic-style or tools-style; all optional.
|
|
273
282
|
// The academic 'part' field is a string enum; tools 'part' is a number.
|
|
@@ -576,7 +585,7 @@ var academicProfile = defineProfile({
|
|
|
576
585
|
landing: true
|
|
577
586
|
// v4.5.0: auto-inject minimal root landing; consumers override via src/pages/index.astro
|
|
578
587
|
},
|
|
579
|
-
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
|
|
588
|
+
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css", "section-map.css"],
|
|
580
589
|
katex: true,
|
|
581
590
|
chaptersRenderer: academicChaptersRenderer,
|
|
582
591
|
// v3.7.0 (#35) — owns /chapters semantics if consumer opts in via routes.chapters
|
|
@@ -715,7 +724,8 @@ var toolsProfile = defineProfile({
|
|
|
715
724
|
"typography.css",
|
|
716
725
|
"print.css",
|
|
717
726
|
"convergence.css",
|
|
718
|
-
"tool-filter.css"
|
|
727
|
+
"tool-filter.css",
|
|
728
|
+
"section-map.css"
|
|
719
729
|
],
|
|
720
730
|
chaptersRenderer: toolsChaptersRenderer
|
|
721
731
|
// v3.7.0 (#35) — owns /chapters semantics for tools shape
|
|
@@ -800,7 +810,7 @@ var minimalProfile = defineProfile({
|
|
|
800
810
|
landing: true
|
|
801
811
|
// v4.5.0: auto-inject minimal root landing
|
|
802
812
|
},
|
|
803
|
-
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
|
|
813
|
+
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css", "section-map.css"],
|
|
804
814
|
// v3.7.0 (#35): minimal aliases tools schema; fallback renderer field-dispatches if a consumer opts into routes.chapters
|
|
805
815
|
chaptersRenderer: fallbackChaptersRenderer
|
|
806
816
|
});
|
|
@@ -833,7 +843,7 @@ var courseNotesProfile = defineProfile({
|
|
|
833
843
|
landing: true
|
|
834
844
|
// v4.5.0: auto-inject minimal root landing
|
|
835
845
|
},
|
|
836
|
-
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
|
|
846
|
+
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css", "section-map.css"],
|
|
837
847
|
// v3.7.0 (#35): course-notes schema has tools-style fields (chapter, volatility, sources) — fallback renderer dispatches via tools renderer
|
|
838
848
|
chaptersRenderer: fallbackChaptersRenderer,
|
|
839
849
|
// v4.6.0 (#76 Secondary): exclude /print/ from sitemap — print-friendly
|
|
@@ -870,7 +880,7 @@ var researchPortfolioProfile = defineProfile({
|
|
|
870
880
|
landing: true
|
|
871
881
|
// v4.5.0: auto-inject minimal root landing
|
|
872
882
|
},
|
|
873
|
-
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
|
|
883
|
+
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css", "section-map.css"],
|
|
874
884
|
katex: true,
|
|
875
885
|
// math is common in research content
|
|
876
886
|
// v3.7.0 (#35): portfolio schema is a union of academic + tools shapes — fallback renderer dispatches per chapter via field presence
|
|
@@ -1749,6 +1759,26 @@ function buildFlashcardDeck(entries) {
|
|
|
1749
1759
|
return entries.filter((e) => !e.data.draft).map((e) => ({ id: e.id, front: e.data.term })).sort((a, b) => a.front.localeCompare(b.front));
|
|
1750
1760
|
}
|
|
1751
1761
|
|
|
1762
|
+
// src/lib/section-map.ts
|
|
1763
|
+
function tocHeadings(headings) {
|
|
1764
|
+
return headings.filter((h) => h.depth >= 2 && h.depth <= 3);
|
|
1765
|
+
}
|
|
1766
|
+
function pickActive(visible, prev) {
|
|
1767
|
+
if (visible.length === 0) return prev;
|
|
1768
|
+
let bestNonNeg = null;
|
|
1769
|
+
let bestAbove = null;
|
|
1770
|
+
for (const h of visible) {
|
|
1771
|
+
if (h.top >= 0) {
|
|
1772
|
+
if (bestNonNeg === null || h.top < bestNonNeg.top) bestNonNeg = h;
|
|
1773
|
+
} else {
|
|
1774
|
+
if (bestAbove === null || h.top > bestAbove.top) bestAbove = h;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
if (bestNonNeg !== null) return bestNonNeg.slug;
|
|
1778
|
+
if (bestAbove !== null) return bestAbove.slug;
|
|
1779
|
+
return prev;
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1752
1782
|
// src/lib/part-review.ts
|
|
1753
1783
|
function selectPartExercises(chapters, byChapter, part) {
|
|
1754
1784
|
const want = String(part);
|
|
@@ -1844,6 +1874,7 @@ export {
|
|
|
1844
1874
|
glossarySchema,
|
|
1845
1875
|
groupByChapter,
|
|
1846
1876
|
groupByDomain,
|
|
1877
|
+
layoutModes,
|
|
1847
1878
|
minimalChapterSchema,
|
|
1848
1879
|
minimalStyle,
|
|
1849
1880
|
normalizeFrontmatterConfig,
|
|
@@ -1851,6 +1882,7 @@ export {
|
|
|
1851
1882
|
parseRepoSlug,
|
|
1852
1883
|
patternCategories,
|
|
1853
1884
|
patternsSchema,
|
|
1885
|
+
pickActive,
|
|
1854
1886
|
provenanceObject,
|
|
1855
1887
|
provenanceSchema,
|
|
1856
1888
|
questionDifficulties,
|
|
@@ -1875,6 +1907,7 @@ export {
|
|
|
1875
1907
|
sourcesSchema,
|
|
1876
1908
|
spreadBlueprint,
|
|
1877
1909
|
theoremLabel,
|
|
1910
|
+
tocHeadings,
|
|
1878
1911
|
toolSlugs,
|
|
1879
1912
|
toolsChapterSchema,
|
|
1880
1913
|
toolsChaptersRenderer,
|
|
@@ -25,6 +25,7 @@ declare const changeKinds: readonly ["added", "removed", "changed", "deprecated"
|
|
|
25
25
|
declare const patternCategories: readonly ["safety", "scale", "context", "interaction", "extension", "other"];
|
|
26
26
|
declare const academicParts: readonly ["foundations", "ssm-core", "beyond-ssm", "integration", "synthesis"];
|
|
27
27
|
declare const chapterStatus: readonly ["implemented", "chapter_only", "reading_only", "prose_only", "code_only", "scaffolded", "planned"];
|
|
28
|
+
declare const layoutModes: readonly ["default", "wide"];
|
|
28
29
|
declare const citationBackstops: readonly ["research-kb", "manual", "unverified"];
|
|
29
30
|
declare const provenanceObject: z.ZodObject<{
|
|
30
31
|
ai_tools: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
@@ -67,6 +68,10 @@ declare const academicChapterSchema: z.ZodObject<{
|
|
|
67
68
|
}>;
|
|
68
69
|
title: z.ZodString;
|
|
69
70
|
slug: z.ZodOptional<z.ZodString>;
|
|
71
|
+
layout: z.ZodOptional<z.ZodEnum<{
|
|
72
|
+
default: "default";
|
|
73
|
+
wide: "wide";
|
|
74
|
+
}>>;
|
|
70
75
|
status: z.ZodEnum<{
|
|
71
76
|
implemented: "implemented";
|
|
72
77
|
chapter_only: "chapter_only";
|
|
@@ -106,6 +111,10 @@ declare const academicChapterSchema: z.ZodObject<{
|
|
|
106
111
|
declare const toolsChapterSchema: z.ZodObject<{
|
|
107
112
|
title: z.ZodString;
|
|
108
113
|
slug: z.ZodOptional<z.ZodString>;
|
|
114
|
+
layout: z.ZodOptional<z.ZodEnum<{
|
|
115
|
+
default: "default";
|
|
116
|
+
wide: "wide";
|
|
117
|
+
}>>;
|
|
109
118
|
part: z.ZodNumber;
|
|
110
119
|
chapter: z.ZodNumber;
|
|
111
120
|
volatility: z.ZodEnum<{
|
|
@@ -148,6 +157,10 @@ declare const toolsChapterSchema: z.ZodObject<{
|
|
|
148
157
|
declare const minimalChapterSchema: z.ZodObject<{
|
|
149
158
|
title: z.ZodString;
|
|
150
159
|
slug: z.ZodOptional<z.ZodString>;
|
|
160
|
+
layout: z.ZodOptional<z.ZodEnum<{
|
|
161
|
+
default: "default";
|
|
162
|
+
wide: "wide";
|
|
163
|
+
}>>;
|
|
151
164
|
part: z.ZodNumber;
|
|
152
165
|
chapter: z.ZodNumber;
|
|
153
166
|
volatility: z.ZodEnum<{
|
|
@@ -205,6 +218,10 @@ declare const sourceTiersResearch: readonly ["T1", "T2", "T3", "T4"];
|
|
|
205
218
|
declare const courseNotesChapterSchema: z.ZodObject<{
|
|
206
219
|
title: z.ZodString;
|
|
207
220
|
slug: z.ZodOptional<z.ZodString>;
|
|
221
|
+
layout: z.ZodOptional<z.ZodEnum<{
|
|
222
|
+
default: "default";
|
|
223
|
+
wide: "wide";
|
|
224
|
+
}>>;
|
|
208
225
|
chapter: z.ZodNumber;
|
|
209
226
|
part: z.ZodDefault<z.ZodNumber>;
|
|
210
227
|
description: z.ZodOptional<z.ZodString>;
|
|
@@ -269,6 +286,10 @@ declare const courseNotesChapterSchema: z.ZodObject<{
|
|
|
269
286
|
declare const researchPortfolioChapterSchema: z.ZodObject<{
|
|
270
287
|
title: z.ZodString;
|
|
271
288
|
slug: z.ZodOptional<z.ZodString>;
|
|
289
|
+
layout: z.ZodOptional<z.ZodEnum<{
|
|
290
|
+
default: "default";
|
|
291
|
+
wide: "wide";
|
|
292
|
+
}>>;
|
|
272
293
|
description: z.ZodOptional<z.ZodString>;
|
|
273
294
|
part: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
|
|
274
295
|
week: z.ZodOptional<z.ZodNumber>;
|
|
@@ -488,4 +509,4 @@ declare const glossarySchema: z.ZodObject<{
|
|
|
488
509
|
}, z.core.$strict>;
|
|
489
510
|
type GlossaryTerm = z.infer<typeof glossarySchema>;
|
|
490
511
|
|
|
491
|
-
export { type AcademicChapter as A, type BloomLevel as B, type CourseNotesChapter as C,
|
|
512
|
+
export { type AcademicChapter as A, type BloomLevel as B, type CourseNotesChapter as C, toolsChapterSchema as D, volatilityLevels as E, type GlossaryTerm as G, type MinimalChapter as M, type Provenance as P, type Question as Q, type ResearchPortfolioChapter as R, type ToolsChapter as T, type QuestionType as a, academicChapterSchema as b, academicParts as c, bloomLevels as d, changeKinds as e, changelogSchema as f, chapterStatus as g, citationBackstops as h, courseNotesChapterSchema as i, glossarySchema as j, patternsSchema as k, layoutModes as l, minimalChapterSchema as m, provenanceObject as n, provenanceSchema as o, patternCategories as p, questionDifficulties as q, questionSchema as r, questionTypes as s, refineQuestion as t, refinedQuestionSchema as u, researchPortfolioChapterSchema as v, sourceTiers as w, sourceTiersResearch as x, sourcesSchema as y, toolSlugs as z };
|
package/dist/schemas.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineCollection } from 'astro:content';
|
|
2
|
-
import { g as BookSchemasOptions } from './types-
|
|
2
|
+
import { g as BookSchemasOptions } from './types-Bn7rKhgP.js';
|
|
3
3
|
import 'astro';
|
|
4
|
-
import './schemas-
|
|
4
|
+
import './schemas-CKipJ5Ie.js';
|
|
5
5
|
import 'astro/zod';
|
|
6
6
|
|
|
7
7
|
/**
|
package/dist/schemas.mjs
CHANGED
|
@@ -52,6 +52,7 @@ var chapterStatus = [
|
|
|
52
52
|
"scaffolded",
|
|
53
53
|
"planned"
|
|
54
54
|
];
|
|
55
|
+
var layoutModes = ["default", "wide"];
|
|
55
56
|
var citationBackstops = ["research-kb", "manual", "unverified"];
|
|
56
57
|
var provenanceObject = z.object({
|
|
57
58
|
ai_tools: z.array(z.string()).default([]),
|
|
@@ -70,6 +71,8 @@ var academicChapterSchema = z.object({
|
|
|
70
71
|
title: z.string().min(1),
|
|
71
72
|
slug: z.string().optional(),
|
|
72
73
|
// v4.9.0: explicit URL slug override (else filename → entry.id)
|
|
74
|
+
layout: z.enum(layoutModes).optional(),
|
|
75
|
+
// 1d: per-page width knob (default measure | 'wide'); threaded as data-layout
|
|
73
76
|
status: z.enum(chapterStatus),
|
|
74
77
|
roadmap_lines: z.tuple([z.number().int(), z.number().int()]).optional(),
|
|
75
78
|
code_path: z.string().optional(),
|
|
@@ -91,6 +94,8 @@ var toolsChapterSchema = z.object({
|
|
|
91
94
|
title: z.string().min(1),
|
|
92
95
|
slug: z.string().optional(),
|
|
93
96
|
// v4.9.0: explicit URL slug override (else filename → entry.id)
|
|
97
|
+
layout: z.enum(layoutModes).optional(),
|
|
98
|
+
// 1d: per-page width knob (default measure | 'wide'); threaded as data-layout
|
|
94
99
|
part: z.number().int().min(0).max(10),
|
|
95
100
|
chapter: z.number().int().min(0).max(99),
|
|
96
101
|
volatility: z.enum(volatilityLevels),
|
|
@@ -116,6 +121,8 @@ var courseNotesChapterSchema = z.object({
|
|
|
116
121
|
title: z.string().min(1),
|
|
117
122
|
slug: z.string().optional(),
|
|
118
123
|
// v4.9.0: explicit URL slug override (else filename → entry.id)
|
|
124
|
+
layout: z.enum(layoutModes).optional(),
|
|
125
|
+
// 1d: per-page width knob (default measure | 'wide'); threaded as data-layout
|
|
119
126
|
chapter: z.number().int().min(0).max(99),
|
|
120
127
|
part: z.number().int().min(0).max(20).default(1),
|
|
121
128
|
description: z.string().optional(),
|
|
@@ -152,6 +159,8 @@ var researchPortfolioChapterSchema = z.object({
|
|
|
152
159
|
title: z.string().min(1),
|
|
153
160
|
slug: z.string().optional(),
|
|
154
161
|
// explicit slug override (otherwise filename)
|
|
162
|
+
layout: z.enum(layoutModes).optional(),
|
|
163
|
+
// 1d: per-page width knob (default measure | 'wide'); threaded as data-layout
|
|
155
164
|
description: z.string().optional(),
|
|
156
165
|
// Hierarchy — accept either academic-style or tools-style; all optional.
|
|
157
166
|
// The academic 'part' field is a string enum; tools 'part' is a number.
|
|
@@ -454,7 +463,7 @@ var academicProfile = defineProfile({
|
|
|
454
463
|
landing: true
|
|
455
464
|
// v4.5.0: auto-inject minimal root landing; consumers override via src/pages/index.astro
|
|
456
465
|
},
|
|
457
|
-
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
|
|
466
|
+
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css", "section-map.css"],
|
|
458
467
|
katex: true,
|
|
459
468
|
chaptersRenderer: academicChaptersRenderer,
|
|
460
469
|
// v3.7.0 (#35) — owns /chapters semantics if consumer opts in via routes.chapters
|
|
@@ -593,7 +602,8 @@ var toolsProfile = defineProfile({
|
|
|
593
602
|
"typography.css",
|
|
594
603
|
"print.css",
|
|
595
604
|
"convergence.css",
|
|
596
|
-
"tool-filter.css"
|
|
605
|
+
"tool-filter.css",
|
|
606
|
+
"section-map.css"
|
|
597
607
|
],
|
|
598
608
|
chaptersRenderer: toolsChaptersRenderer
|
|
599
609
|
// v3.7.0 (#35) — owns /chapters semantics for tools shape
|
|
@@ -678,7 +688,7 @@ var minimalProfile = defineProfile({
|
|
|
678
688
|
landing: true
|
|
679
689
|
// v4.5.0: auto-inject minimal root landing
|
|
680
690
|
},
|
|
681
|
-
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
|
|
691
|
+
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css", "section-map.css"],
|
|
682
692
|
// v3.7.0 (#35): minimal aliases tools schema; fallback renderer field-dispatches if a consumer opts into routes.chapters
|
|
683
693
|
chaptersRenderer: fallbackChaptersRenderer
|
|
684
694
|
});
|
|
@@ -711,7 +721,7 @@ var courseNotesProfile = defineProfile({
|
|
|
711
721
|
landing: true
|
|
712
722
|
// v4.5.0: auto-inject minimal root landing
|
|
713
723
|
},
|
|
714
|
-
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
|
|
724
|
+
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css", "section-map.css"],
|
|
715
725
|
// v3.7.0 (#35): course-notes schema has tools-style fields (chapter, volatility, sources) — fallback renderer dispatches via tools renderer
|
|
716
726
|
chaptersRenderer: fallbackChaptersRenderer,
|
|
717
727
|
// v4.6.0 (#76 Secondary): exclude /print/ from sitemap — print-friendly
|
|
@@ -748,7 +758,7 @@ var researchPortfolioProfile = defineProfile({
|
|
|
748
758
|
landing: true
|
|
749
759
|
// v4.5.0: auto-inject minimal root landing
|
|
750
760
|
},
|
|
751
|
-
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
|
|
761
|
+
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css", "section-map.css"],
|
|
752
762
|
katex: true,
|
|
753
763
|
// math is common in research content
|
|
754
764
|
// v3.7.0 (#35): portfolio schema is a union of academic + tools shapes — fallback renderer dispatches per chapter via field presence
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AstroIntegration, AstroUserConfig } from 'astro';
|
|
2
|
-
import { A as AcademicChapter, T as ToolsChapter, M as MinimalChapter, C as CourseNotesChapter, R as ResearchPortfolioChapter } from './schemas-
|
|
2
|
+
import { A as AcademicChapter, T as ToolsChapter, M as MinimalChapter, C as CourseNotesChapter, R as ResearchPortfolioChapter } from './schemas-CKipJ5Ie.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* src/lib/chapters-renderer.ts — per-profile strategy interface for the
|
package/layouts/Base.astro
CHANGED
|
@@ -99,13 +99,14 @@ const absoluteOgImage = resolvedOgImage
|
|
|
99
99
|
const twitterHandle = bookConfig.seo?.twitterHandle ?? null;
|
|
100
100
|
const ogSiteName = bookConfig.title ?? title;
|
|
101
101
|
const ogDescription = description ?? bookConfig.description ?? '';
|
|
102
|
+
const baseUrl = import.meta.env.BASE_URL ?? '/';
|
|
102
103
|
---
|
|
103
104
|
|
|
104
105
|
<!doctype html>
|
|
105
106
|
<html lang={lang}>
|
|
106
107
|
<head>
|
|
107
108
|
<meta charset="utf-8" />
|
|
108
|
-
<link rel="icon" type="image/svg+xml" href=
|
|
109
|
+
<link rel="icon" type="image/svg+xml" href={`${baseUrl}favicon.svg`} />
|
|
109
110
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
110
111
|
<meta name="color-scheme" content="light dark" />
|
|
111
112
|
<meta name="generator" content={Astro.generator} />
|
|
@@ -119,7 +120,7 @@ const ogDescription = description ?? bookConfig.description ?? '';
|
|
|
119
120
|
OG tags). sitemap link points at @astrojs/sitemap's auto-emitted
|
|
120
121
|
index. */}
|
|
121
122
|
<link rel="canonical" href={canonicalURL} />
|
|
122
|
-
<link rel="sitemap" type="application/xml" href=
|
|
123
|
+
<link rel="sitemap" type="application/xml" href={`${baseUrl}sitemap-index.xml`} />
|
|
123
124
|
<meta property="og:title" content={title} />
|
|
124
125
|
{ogDescription && <meta property="og:description" content={ogDescription} />}
|
|
125
126
|
<meta property="og:url" content={canonicalURL} />
|
|
@@ -150,7 +151,7 @@ const ogDescription = description ?? bookConfig.description ?? '';
|
|
|
150
151
|
{showToolsChrome && <ToolFilter client:idle />}
|
|
151
152
|
{showToolsChrome && <VersionSelector client:idle />}
|
|
152
153
|
<a
|
|
153
|
-
href=
|
|
154
|
+
href={`${baseUrl}search/`}
|
|
154
155
|
class="chrome-button"
|
|
155
156
|
aria-label="Search the book"
|
|
156
157
|
title="Search"
|
package/layouts/Chapter.astro
CHANGED
|
@@ -25,6 +25,7 @@ import Base from './Base.astro';
|
|
|
25
25
|
import bookConfig from 'virtual:book-scaffold/book-config';
|
|
26
26
|
import ChapterHeader from '../components/ChapterHeader.astro';
|
|
27
27
|
import ChapterTOC from '../components/ChapterTOC.astro';
|
|
28
|
+
import SectionMap from '../components/SectionMap.astro';
|
|
28
29
|
import ChapterNav from '../components/ChapterNav.astro';
|
|
29
30
|
|
|
30
31
|
interface Props {
|
|
@@ -44,6 +45,39 @@ const articlePublished = (entry.data as { published?: Date }).published;
|
|
|
44
45
|
const articleUpdated = (entry.data as { updated?: Date }).updated;
|
|
45
46
|
const articleTags = ((entry.data as { tags?: string[] }).tags ?? []) as string[];
|
|
46
47
|
const chapterImage = (entry.data as { image?: string }).image;
|
|
48
|
+
|
|
49
|
+
// (#section-map): best-effort context label for the gutter section map, e.g.
|
|
50
|
+
// "Part 2 · Ch 4" or "Week 1". All hierarchy fields are profile-dependent and
|
|
51
|
+
// Zod-optional (academic carries part+week; tools/research-portfolio carry
|
|
52
|
+
// part+chapter; minimal carries none), so each piece is included only when
|
|
53
|
+
// present — the label is omitted entirely (undefined) when nothing is derivable.
|
|
54
|
+
const dataAny = entry.data as {
|
|
55
|
+
part?: string | number;
|
|
56
|
+
chapter?: number;
|
|
57
|
+
week?: number;
|
|
58
|
+
layout?: 'default' | 'wide';
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// 1d: per-page width knob. `layout` is Zod-optional on every profile schema
|
|
62
|
+
// (closed enum 'default'|'wide'). Emit `data-layout` ONLY for a non-default
|
|
63
|
+
// value so existing chapters (no `layout`, or `layout: default`) render the
|
|
64
|
+
// attribute-free <article> byte-identically — `undefined` makes Astro drop the
|
|
65
|
+
// attribute, and only `.prose[data-layout="wide"]` carries a rule in layout.css.
|
|
66
|
+
const layoutMode =
|
|
67
|
+
dataAny.layout && dataAny.layout !== 'default' ? dataAny.layout : undefined;
|
|
68
|
+
const labelParts: string[] = [];
|
|
69
|
+
if (dataAny.part !== undefined && dataAny.part !== '') {
|
|
70
|
+
// Numeric part → "Part N"; string-enum part (academic, e.g. "foundations")
|
|
71
|
+
// → title-case it as-is.
|
|
72
|
+
labelParts.push(
|
|
73
|
+
typeof dataAny.part === 'number'
|
|
74
|
+
? `Part ${dataAny.part}`
|
|
75
|
+
: dataAny.part.charAt(0).toUpperCase() + dataAny.part.slice(1),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (dataAny.chapter !== undefined) labelParts.push(`Ch ${dataAny.chapter}`);
|
|
79
|
+
else if (dataAny.week !== undefined) labelParts.push(`Week ${dataAny.week}`);
|
|
80
|
+
const sectionMapLabel = labelParts.length > 0 ? labelParts.join(' · ') : undefined;
|
|
47
81
|
---
|
|
48
82
|
|
|
49
83
|
<Base
|
|
@@ -62,9 +96,10 @@ const chapterImage = (entry.data as { image?: string }).image;
|
|
|
62
96
|
)}
|
|
63
97
|
{articleTags.map((t) => <meta property="article:tag" content={t} />)}
|
|
64
98
|
</Fragment>
|
|
65
|
-
<article class="prose">
|
|
99
|
+
<article class="prose" data-layout={layoutMode}>
|
|
66
100
|
<ChapterHeader data={entry.data} />
|
|
67
101
|
<ChapterTOC headings={headings} />
|
|
102
|
+
<SectionMap headings={headings} label={sectionMapLabel} />
|
|
68
103
|
<slot />
|
|
69
104
|
<ChapterNav currentId={entry.id} />
|
|
70
105
|
</article>
|