explico 0.13.1 → 0.14.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/CHANGELOG.md +879 -1
- package/README.md +200 -30
- package/dist/compiler/astro.d.ts +15 -2
- package/dist/compiler/astro.js +40 -11
- package/dist/compiler/cli-main.d.ts +43 -0
- package/dist/compiler/cli-main.js +392 -0
- package/dist/compiler/cli.d.ts +424 -9
- package/dist/compiler/cli.js +1289 -8
- package/dist/compiler/collections.d.ts +88 -1
- package/dist/compiler/collections.js +170 -25
- package/dist/compiler/course-config.d.ts +1 -1
- package/dist/compiler/course-config.js +105 -1
- package/dist/compiler/decks.d.ts +161 -0
- package/dist/compiler/decks.js +235 -0
- package/dist/compiler/exports/combined-html.d.ts +5 -1
- package/dist/compiler/exports/deck-fit.d.ts +55 -0
- package/dist/compiler/exports/deck-fit.js +90 -0
- package/dist/compiler/exports/export-chapters.d.ts +37 -6
- package/dist/compiler/exports/export-chapters.js +64 -5
- package/dist/compiler/exports/export-pages.d.ts +30 -3
- package/dist/compiler/exports/export-pages.js +24 -1
- package/dist/compiler/exports/latex/beamerthemeexplico.sty +124 -0
- package/dist/compiler/exports/latex/cache.d.ts +78 -0
- package/dist/compiler/exports/latex/cache.js +129 -0
- package/dist/compiler/exports/latex/capture.d.ts +10 -1
- package/dist/compiler/exports/latex/capture.js +3 -1
- package/dist/compiler/exports/latex/compile.d.ts +13 -2
- package/dist/compiler/exports/latex/compile.js +12 -1
- package/dist/compiler/exports/latex/deck.d.ts +92 -0
- package/dist/compiler/exports/latex/deck.js +349 -0
- package/dist/compiler/exports/latex/document.d.ts +51 -6
- package/dist/compiler/exports/latex/document.js +71 -12
- package/dist/compiler/exports/latex/explico-common.sty +198 -0
- package/dist/compiler/exports/latex/explico.cls +13 -183
- package/dist/compiler/exports/latex/serialize.d.ts +60 -4
- package/dist/compiler/exports/latex/serialize.js +4 -2
- package/dist/compiler/exports/latex-export.d.ts +24 -2
- package/dist/compiler/exports/latex-export.js +107 -26
- package/dist/compiler/exports/markdown/serialize.d.ts +24 -4
- package/dist/compiler/exports/markdown/serialize.js +85 -8
- package/dist/compiler/exports/markdown-export.d.ts +6 -1
- package/dist/compiler/exports/markdown-export.js +50 -11
- package/dist/compiler/exports/standalone-html.d.ts +10 -5
- package/dist/compiler/exports/standalone-html.js +3 -3
- package/dist/compiler/exports/static-tree.d.ts +38 -1
- package/dist/compiler/exports/static-tree.js +120 -4
- package/dist/compiler/knowledge-version.d.ts +26 -2
- package/dist/compiler/knowledge-version.js +27 -17
- package/dist/compiler/lesson-derivation.d.ts +143 -6
- package/dist/compiler/lesson-derivation.js +145 -13
- package/dist/compiler/manifest.d.ts +133 -4
- package/dist/compiler/manifest.js +359 -58
- package/dist/compiler/page-versions.d.ts +160 -0
- package/dist/compiler/page-versions.js +198 -0
- package/dist/compiler/render-diagnostics.d.ts +5 -1
- package/dist/compiler/resolution-report.d.ts +24 -6
- package/dist/compiler/resolution-report.js +22 -4
- package/dist/compiler/validate-content.d.ts +7 -0
- package/dist/compiler/validate-content.js +160 -0
- package/dist/{course-config-rRtME5UV.d.ts → course-config-BLtIdkei.d.ts} +164 -1
- package/dist/curriculum/competency-graph.d.ts +54 -0
- package/dist/curriculum/competency-graph.js +41 -0
- package/dist/reference/base-library.d.mts +8 -0
- package/dist/reference/base-library.mjs +19 -0
- package/dist/reference/consistency.d.ts +8 -4
- package/dist/reference/consistency.js +76 -26
- package/dist/reference/deck-remark.d.mts +123 -0
- package/dist/reference/deck-remark.mjs +600 -0
- package/dist/reference/deck-source.d.ts +107 -0
- package/dist/reference/deck-source.js +140 -0
- package/dist/reference/decks-validate.d.ts +133 -0
- package/dist/reference/decks-validate.js +359 -0
- package/dist/reference/derived-entries.d.ts +5 -1
- package/dist/reference/diagram-fit.d.ts +1 -1
- package/dist/reference/figure-symbols.d.ts +23 -6
- package/dist/reference/figure-symbols.js +51 -15
- package/dist/reference/float-name.d.ts +43 -0
- package/dist/reference/float-name.js +58 -0
- package/dist/reference/folds-validate.d.ts +1 -1
- package/dist/reference/gate-abbreviations.d.ts +1 -1
- package/dist/reference/gate-indices.js +5 -4
- package/dist/reference/gate-math.d.ts +29 -2
- package/dist/reference/gate-math.js +99 -18
- package/dist/reference/gate-part-pages.d.ts +1 -1
- package/dist/reference/lab-katex-validate.d.ts +10 -6
- package/dist/reference/lab-katex-validate.js +17 -9
- package/dist/reference/lab-math-scope.js +29 -4
- package/dist/reference/math-glyphs.d.mts +25 -0
- package/dist/reference/math-glyphs.mjs +189 -25
- package/dist/reference/page-notation.d.ts +22 -3
- package/dist/reference/page-notation.js +32 -10
- package/dist/reference/references.d.ts +14 -1
- package/dist/reference/references.js +13 -0
- package/dist/reference/registry-scope.d.mts +33 -0
- package/dist/reference/registry-scope.mjs +34 -0
- package/dist/reference/registry.js +29 -10
- package/dist/reference/rehype-deck.d.mts +6 -0
- package/dist/reference/rehype-deck.mjs +286 -0
- package/dist/reference/rehype-heading-anchors.d.mts +20 -0
- package/dist/reference/rehype-heading-anchors.mjs +48 -6
- package/dist/reference/remark-notation.mjs +249 -40
- package/dist/reference/source-lines.d.mts +12 -0
- package/dist/reference/source-lines.mjs +65 -0
- package/dist/reference/types.d.ts +28 -0
- package/dist/rules/catalog.d.ts +21 -4
- package/dist/rules/catalog.js +221 -2
- package/dist/rules/course-rules.d.ts +5 -1
- package/dist/rules/course-rules.js +17 -3
- package/dist/rules/course-rules.schema.json +12 -0
- package/dist/rules/in-effect.d.ts +13 -3
- package/dist/rules/in-effect.js +30 -7
- package/dist/rules/rules.schema.json +259 -1
- package/dist/rules/settings.d.ts +1 -1
- package/dist/session/PreferenceStore.d.ts +6 -0
- package/dist/session/PreferenceStore.js +18 -1
- package/dist/session/deck-memory.d.ts +116 -0
- package/dist/session/deck-memory.js +143 -0
- package/dist/session/reading-memory.d.ts +96 -0
- package/dist/session/reading-memory.js +133 -0
- package/docs/VERSIONING.md +21 -18
- package/docs/authoring/README.md +29 -9
- package/docs/authoring/agents.md +63 -16
- package/docs/authoring/components.md +15 -0
- package/docs/authoring/deck.md +588 -0
- package/docs/authoring/migrating.md +1045 -16
- package/docs/authoring/notation.md +49 -6
- package/docs/authoring/structure.md +80 -1
- package/docs/authoring/versioning.md +54 -2
- package/docs/authoring/visualizations.md +7 -2
- package/docs/course-agent/AGENTS.md +37 -0
- package/docs/course-agent/agents/rules-reviewer.md +33 -0
- package/docs/course-agent/skills/draft-lesson/SKILL.md +37 -0
- package/docs/course-agent/skills/migrate-course/SKILL.md +31 -0
- package/docs/course-agent/skills/release-content/SKILL.md +24 -0
- package/docs/diagram-scaling.md +148 -0
- package/docs/privacy.md +59 -0
- package/package.json +8 -2
- package/src/compiler/astro.ts +60 -21
- package/src/compiler/cli-main.ts +469 -0
- package/src/compiler/cli.ts +1905 -16
- package/src/compiler/collections.ts +284 -25
- package/src/compiler/course-config.ts +267 -0
- package/src/compiler/decks.ts +455 -0
- package/src/compiler/exports/deck-fit.ts +127 -0
- package/src/compiler/exports/export-chapters.ts +105 -6
- package/src/compiler/exports/export-pages.ts +46 -2
- package/src/compiler/exports/latex/beamerthemeexplico.sty +124 -0
- package/src/compiler/exports/latex/cache.ts +183 -0
- package/src/compiler/exports/latex/capture.ts +5 -2
- package/src/compiler/exports/latex/compile.ts +18 -0
- package/src/compiler/exports/latex/deck.ts +419 -0
- package/src/compiler/exports/latex/document.ts +117 -21
- package/src/compiler/exports/latex/explico-common.sty +198 -0
- package/src/compiler/exports/latex/explico.cls +13 -183
- package/src/compiler/exports/latex/serialize.ts +25 -21
- package/src/compiler/exports/latex/texlive-packages.txt +6 -2
- package/src/compiler/exports/latex-export.ts +187 -45
- package/src/compiler/exports/markdown/serialize.ts +118 -9
- package/src/compiler/exports/markdown-export.ts +56 -11
- package/src/compiler/exports/standalone-html.ts +8 -7
- package/src/compiler/exports/static-tree.ts +175 -3
- package/src/compiler/knowledge-version.ts +56 -21
- package/src/compiler/lesson-derivation.ts +282 -16
- package/src/compiler/manifest.ts +558 -63
- package/src/compiler/page-versions.ts +359 -0
- package/src/compiler/resolution-report.ts +38 -7
- package/src/compiler/validate-content.ts +212 -0
- package/src/components/CourseContents.astro +124 -6
- package/src/components/CurriculumMap.astro +49 -1
- package/src/components/FloatIndex.astro +1 -1
- package/src/components/PartOverview.astro +51 -5
- package/src/components/assessments/AssessmentSet.astro +41 -15
- package/src/components/block-heading.ts +15 -6
- package/src/components/card-carry.ts +7 -4
- package/src/components/card-manager.ts +14 -2
- package/src/components/card-memory.ts +13 -9
- package/src/components/deck/DeckEnhancer.astro +13 -0
- package/src/components/deck/deck-context.ts +104 -0
- package/src/components/deck/deck-controller.ts +1287 -0
- package/src/components/deck/deck-map.ts +91 -0
- package/src/components/deck-lane.ts +163 -0
- package/src/components/diagram/Diagram.astro +131 -86
- package/src/components/disclosure/persisted-disclosure.ts +9 -1
- package/src/components/figure/Figure.astro +7 -2
- package/src/components/figure/FloatCaption.astro +5 -3
- package/src/components/fold/Fold.astro +2 -2
- package/src/components/notation/NotationFormula.astro +81 -11
- package/src/components/notation/NotationGlossary.astro +132 -1
- package/src/components/notation/NotationLayer.astro +15 -7
- package/src/components/starlight/AboutPanel.astro +186 -3
- package/src/components/starlight/CollapsibleSections.astro +24 -3
- package/src/components/starlight/DiagramEnhancer.astro +5 -1
- package/src/components/starlight/HeadingAnchorEnhancer.astro +1 -1
- package/src/components/starlight/LayoutHeader.astro +17 -0
- package/src/components/starlight/LayoutPageTitle.astro +110 -0
- package/src/components/starlight/LayoutSidebar.astro +807 -21
- package/src/components/starlight/LayoutSocialIcons.astro +62 -6
- package/src/components/starlight/LessonExportActions.astro +27 -9
- package/src/components/starlight/LessonFooter.astro +59 -0
- package/src/components/starlight/ReadingMemoryNotice.astro +114 -0
- package/src/components/starlight/TextSizeControl.astro +24 -13
- package/src/components/starlight/page-marks.ts +139 -0
- package/src/components/starlight/section-summary.ts +1 -1
- package/src/components/starlight/sidebar-editions.ts +245 -0
- package/src/components/table/Table.astro +15 -7
- package/src/content-config.ts +87 -1
- package/src/curriculum/competency-graph.ts +95 -0
- package/src/reference/base-library.mjs +19 -0
- package/src/reference/consistency.ts +126 -39
- package/src/reference/deck-remark.mjs +600 -0
- package/src/reference/deck-source.ts +273 -0
- package/src/reference/decks-validate.ts +604 -0
- package/src/reference/diagram-layout.ts +3 -1
- package/src/reference/figure-symbols.ts +108 -31
- package/src/reference/folds-validate.ts +1 -1
- package/src/reference/gate-indices.ts +5 -3
- package/src/reference/gate-math.ts +169 -25
- package/src/reference/lab-katex-validate.ts +30 -15
- package/src/reference/lab-math-scope.ts +34 -3
- package/src/reference/math-glyphs.mjs +189 -25
- package/src/reference/page-notation.ts +69 -15
- package/src/reference/references.ts +28 -0
- package/src/reference/registry-scope.mjs +34 -0
- package/src/reference/registry.ts +41 -11
- package/src/reference/rehype-deck.mjs +286 -0
- package/src/reference/rehype-heading-anchors.mjs +48 -6
- package/src/reference/remark-notation.mjs +249 -40
- package/src/reference/source-lines.mjs +65 -0
- package/src/reference/types.ts +28 -0
- package/src/rules/catalog.ts +257 -4
- package/src/rules/course-rules.ts +21 -1
- package/src/rules/in-effect.ts +54 -8
- package/src/session/PreferenceStore.ts +23 -0
- package/src/session/deck-memory.ts +263 -0
- package/src/session/reading-memory.ts +243 -0
- package/src/styles/deck.css +1150 -0
- package/src/styles/global.css +958 -27
- package/src/styles/zones.css +277 -0
- /package/src/{components → reference}/float-name.ts +0 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The slide map on a deck's study page (`docs/authoring/deck.md`, "Present
|
|
3
|
+
* mode and the study page"): every frame as a numbered square, pinned under
|
|
4
|
+
* the page's header while the deck scrolls. A deck too long for the map's
|
|
5
|
+
* line keeps its first few frames, the frames around the place to continue —
|
|
6
|
+
* a few before it, shown already, and some after it, not shown yet — the
|
|
7
|
+
* frames around the one in view, and its last few, with a gap for each run
|
|
8
|
+
* it leaves out. How many of each is what the line has room for.
|
|
9
|
+
*
|
|
10
|
+
* Pure: `deck-controller.ts` draws the map and measures its room;
|
|
11
|
+
* `tests/unit/deck-map.test.ts` checks this in Node.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** One place on the map: a frame's square, or a gap standing for the frames from `from` to `to`. */
|
|
15
|
+
export type SlideMapItem =
|
|
16
|
+
| { readonly kind: 'slide'; readonly number: number }
|
|
17
|
+
| { readonly kind: 'gap'; readonly from: number; readonly to: number };
|
|
18
|
+
|
|
19
|
+
/** The frames the map keeps in sight, by number. */
|
|
20
|
+
export interface SlideMapFocus {
|
|
21
|
+
/** The frame to continue at, or else the first one not shown yet. */
|
|
22
|
+
readonly anchor?: number;
|
|
23
|
+
/** The frame in view on the study page. */
|
|
24
|
+
readonly current?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** How much a shortened map keeps: frames at each end, before and after the anchor, and on each side of the one in view. */
|
|
28
|
+
export interface SlideMapKeep {
|
|
29
|
+
readonly ends: number;
|
|
30
|
+
readonly before: number;
|
|
31
|
+
readonly after: number;
|
|
32
|
+
readonly around: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The least a shortened map keeps. */
|
|
36
|
+
const LEAST: SlideMapKeep = { ends: 1, before: 1, after: 1, around: 0 };
|
|
37
|
+
|
|
38
|
+
/** What a shortened map widens first, while the line has room: what comes next, then the ends, then what came before. */
|
|
39
|
+
const WIDEN: readonly (keyof SlideMapKeep)[] = ['after', 'ends', 'before', 'around'];
|
|
40
|
+
|
|
41
|
+
const slide = (number: number): SlideMapItem => ({ kind: 'slide', number });
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A deck of `total` frames, numbered from 1, shortened to what `keep` keeps
|
|
45
|
+
* of its ends and of `focus`. A gap never stands for one frame alone: that
|
|
46
|
+
* frame is shown in its place.
|
|
47
|
+
*/
|
|
48
|
+
export function slideMapKeeping(total: number, focus: SlideMapFocus, keep: SlideMapKeep): SlideMapItem[] {
|
|
49
|
+
const kept = new Set<number>();
|
|
50
|
+
const add = (from: number, to: number) => {
|
|
51
|
+
for (let number = Math.max(1, from); number <= Math.min(total, to); number += 1) kept.add(number);
|
|
52
|
+
};
|
|
53
|
+
add(1, keep.ends);
|
|
54
|
+
add(total - keep.ends + 1, total);
|
|
55
|
+
if (focus.anchor !== undefined) add(focus.anchor - keep.before, focus.anchor + keep.after);
|
|
56
|
+
if (focus.current !== undefined) add(focus.current - keep.around, focus.current + keep.around);
|
|
57
|
+
const items: SlideMapItem[] = [];
|
|
58
|
+
let last = 0;
|
|
59
|
+
for (const number of [...kept].sort((a, b) => a - b)) {
|
|
60
|
+
if (number - last === 2) items.push(slide(number - 1));
|
|
61
|
+
else if (number - last > 2) items.push({ kind: 'gap', from: last + 1, to: number - 1 });
|
|
62
|
+
items.push(slide(number));
|
|
63
|
+
last = number;
|
|
64
|
+
}
|
|
65
|
+
return items;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The map of a deck of `total` frames in a line with room for `room` places:
|
|
70
|
+
* every frame when they fit, else the least a shortened map keeps, widened
|
|
71
|
+
* ({@link WIDEN}, a frame at a time, round and round) while the line has
|
|
72
|
+
* room. Past the least, nothing fits: the map wraps.
|
|
73
|
+
*/
|
|
74
|
+
export function slideMap(total: number, focus: SlideMapFocus, room = Number.POSITIVE_INFINITY): SlideMapItem[] {
|
|
75
|
+
if (total <= room) return Array.from({ length: total }, (_, index) => slide(index + 1));
|
|
76
|
+
let keep = LEAST;
|
|
77
|
+
let best = slideMapKeeping(total, focus, keep);
|
|
78
|
+
for (let widened = true; widened; ) {
|
|
79
|
+
widened = false;
|
|
80
|
+
for (const field of WIDEN) {
|
|
81
|
+
const wider = { ...keep, [field]: keep[field] + 1 };
|
|
82
|
+
const items = slideMapKeeping(total, focus, wider);
|
|
83
|
+
if (items.length <= room && items.length > best.length) {
|
|
84
|
+
keep = wider;
|
|
85
|
+
best = items;
|
|
86
|
+
widened = true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return best;
|
|
91
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The slides column of a contents list (`CourseContents.astro`,
|
|
3
|
+
* `PartOverview.astro`): each lesson's deck at its row's end, as long as what
|
|
4
|
+
* it covers. A lesson's own deck is a **tile** on its title line; a part's
|
|
5
|
+
* deck is a **bar**, its covered chapters' cells joined into one shape from
|
|
6
|
+
* the first covered title to the last, each cell a link to that chapter's
|
|
7
|
+
* section of the deck, named as the sidebar names it (`sidebar-editions.ts`).
|
|
8
|
+
*
|
|
9
|
+
* Pure: the two components draw the cells, and `tests/unit/deck-lane.test.ts`
|
|
10
|
+
* checks this in Node.
|
|
11
|
+
*/
|
|
12
|
+
import { editionLabel, sectionDetail } from './starlight/sidebar-editions';
|
|
13
|
+
|
|
14
|
+
/** One row's cell in one lane of the slides column. */
|
|
15
|
+
export interface LaneCell {
|
|
16
|
+
/** A tile is a whole deck on one row; a bar is a `start`, any `middle`s and an `end`. */
|
|
17
|
+
readonly kind: 'tile' | 'start' | 'middle' | 'end';
|
|
18
|
+
/** The glyph the cell always shows: on a tile and on a bar's start. */
|
|
19
|
+
readonly glyph?: 'deck' | 'part-deck';
|
|
20
|
+
/** Where it links, without the site's base: `/motion/slides/#slide-17`. */
|
|
21
|
+
readonly href: string;
|
|
22
|
+
/** The accessible name, the tooltip's first line: `Slides: Projectile Range`. */
|
|
23
|
+
readonly name: string;
|
|
24
|
+
/** The tooltip's second line, the link's description: `Slides 17–25 of the part's slides`. */
|
|
25
|
+
readonly detail?: string;
|
|
26
|
+
/**
|
|
27
|
+
* A slides-only lesson's tile: its title opens the same page, so the tile
|
|
28
|
+
* is left out of the tab order and hidden from assistive technology.
|
|
29
|
+
*/
|
|
30
|
+
readonly duplicate?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A lesson as the column reads it from the manifest. */
|
|
34
|
+
export interface LaneLesson {
|
|
35
|
+
readonly id: string;
|
|
36
|
+
readonly slug: string;
|
|
37
|
+
readonly title: string;
|
|
38
|
+
readonly editions: readonly ('chapter' | 'deck')[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A deck as the column reads it from the manifest (`ManifestDeck`). */
|
|
42
|
+
export interface LaneDeck {
|
|
43
|
+
/** Its route, `motion/slides`. */
|
|
44
|
+
readonly slug: string;
|
|
45
|
+
/** A lesson's deck: the lesson's id. */
|
|
46
|
+
readonly lessonId?: string;
|
|
47
|
+
/** A part's deck: the part's directory. */
|
|
48
|
+
readonly part?: string;
|
|
49
|
+
/** A part's deck: each chapter it covers, by lesson slug, and the frame its section opens on. */
|
|
50
|
+
readonly covers: readonly { readonly chapter: string; readonly frame: number }[];
|
|
51
|
+
/** On a part's deck, each frame's chapter, by lesson slug. */
|
|
52
|
+
readonly frames: readonly { readonly number: number; readonly chapter?: string }[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The frames a part's deck holds of one chapter, as the sidebar writes them
|
|
57
|
+
* (`withPartDeckFrames`): `2–16`, or `5` for one; none when no frame is the
|
|
58
|
+
* chapter's.
|
|
59
|
+
*/
|
|
60
|
+
function sectionRange(deck: LaneDeck, chapter: string): string | undefined {
|
|
61
|
+
const numbers = deck.frames.filter((frame) => frame.chapter === chapter).map((frame) => frame.number);
|
|
62
|
+
if (numbers.length === 0) return undefined;
|
|
63
|
+
const first = Math.min(...numbers);
|
|
64
|
+
const last = Math.max(...numbers);
|
|
65
|
+
return first === last ? String(first) : `${first}–${last}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The slides column of one part: `lanes[lane][row]`, a cell or null, one
|
|
70
|
+
* row per lesson in `lessons`' order. Lane 0 holds each lesson's own deck,
|
|
71
|
+
* and the part's deck when no row it covers has a deck of its own; when one
|
|
72
|
+
* does, the part's deck takes lane 1. A part with no deck has no lanes.
|
|
73
|
+
*/
|
|
74
|
+
export function deckLanes(input: {
|
|
75
|
+
readonly lessons: readonly LaneLesson[];
|
|
76
|
+
readonly decks: readonly LaneDeck[];
|
|
77
|
+
readonly part: { readonly dir: string };
|
|
78
|
+
}): (LaneCell | null)[][] {
|
|
79
|
+
const { lessons, decks, part } = input;
|
|
80
|
+
const own: (LaneCell | null)[] = lessons.map((lesson) => {
|
|
81
|
+
const deck = decks.find((candidate) => candidate.lessonId === lesson.id);
|
|
82
|
+
if (deck === undefined) return null;
|
|
83
|
+
return {
|
|
84
|
+
kind: 'tile',
|
|
85
|
+
glyph: 'deck',
|
|
86
|
+
href: `/${deck.slug}/`,
|
|
87
|
+
name: editionLabel('slides', lesson.title, 'other'),
|
|
88
|
+
...(lesson.editions.includes('chapter') ? {} : { duplicate: true }),
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const partDeck = decks.find((deck) => deck.part === part.dir);
|
|
93
|
+
const covered: (LaneCell | null)[] = lessons.map(() => null);
|
|
94
|
+
if (partDeck !== undefined) {
|
|
95
|
+
const frameOf = new Map(partDeck.covers.map((cover) => [cover.chapter, cover.frame]));
|
|
96
|
+
lessons.forEach((lesson, row) => {
|
|
97
|
+
const frame = frameOf.get(lesson.slug);
|
|
98
|
+
if (frame === undefined) return;
|
|
99
|
+
const range = sectionRange(partDeck, lesson.slug);
|
|
100
|
+
const above = row > 0 && frameOf.has(lessons[row - 1]!.slug);
|
|
101
|
+
const below = row < lessons.length - 1 && frameOf.has(lessons[row + 1]!.slug);
|
|
102
|
+
const kind = above ? (below ? 'middle' : 'end') : below ? 'start' : 'tile';
|
|
103
|
+
covered[row] = {
|
|
104
|
+
kind,
|
|
105
|
+
...(above ? {} : { glyph: 'part-deck' as const }),
|
|
106
|
+
href: `/${partDeck.slug}/#slide-${frame}`,
|
|
107
|
+
name: editionLabel('slides', lesson.title, 'other'),
|
|
108
|
+
...(range === undefined ? {} : { detail: sectionDetail(range) }),
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const hasOwn = own.some((cell) => cell !== null);
|
|
114
|
+
const hasPart = covered.some((cell) => cell !== null);
|
|
115
|
+
if (!hasOwn && !hasPart) return [];
|
|
116
|
+
if (!hasPart) return [own];
|
|
117
|
+
if (!hasOwn) return [covered];
|
|
118
|
+
const clash = covered.some((cell, row) => cell !== null && own[row] !== null);
|
|
119
|
+
if (clash) return [own, covered];
|
|
120
|
+
return [own.map((cell, row) => cell ?? covered[row]!)];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* A cell's link attributes, as both lists draw it: its kind and glyph as
|
|
125
|
+
* classes, its lane as the column counted from the row's end (the lanes are
|
|
126
|
+
* the row's last `lanes` columns, wherever the list puts its others), the
|
|
127
|
+
* tooltip's two lines in `title`, the second as `aria-description` too. A
|
|
128
|
+
* duplicate is out of the tab order and hidden from assistive technology.
|
|
129
|
+
* The link holds the name in a visually hidden span.
|
|
130
|
+
*/
|
|
131
|
+
export function laneLink(cell: LaneCell, lane: number, lanes: number, base: string): Record<string, string | number> {
|
|
132
|
+
return {
|
|
133
|
+
class: ['deck-lane', `deck-lane--${cell.kind}`, ...(cell.glyph ? [`deck-lane--${cell.glyph}`] : [])].join(' '),
|
|
134
|
+
href: `${base}${cell.href}`,
|
|
135
|
+
style: `grid-column: ${lane - lanes - 1}`,
|
|
136
|
+
title: cell.detail === undefined ? cell.name : `${cell.name}\n${cell.detail}`,
|
|
137
|
+
...(cell.detail === undefined ? {} : { 'aria-description': cell.detail }),
|
|
138
|
+
...(cell.duplicate ? { 'aria-hidden': 'true', tabindex: -1 } : {}),
|
|
139
|
+
'data-pagefind-ignore': '',
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** The glyph before a lesson's title: what it opens, its chapter or, for a slides-only lesson, its deck. */
|
|
144
|
+
export function titleGlyph(lesson: Pick<LaneLesson, 'editions'>): 'chapter' | 'deck' {
|
|
145
|
+
return lesson.editions.includes('chapter') ? 'chapter' : 'deck';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** The glyphs a contents list uses, in the order its key names them. */
|
|
149
|
+
export type ContentsGlyph = 'chapter' | 'deck' | 'part-deck';
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* The key's entries: `chapter` when a lesson has a chapter, `deck` when a
|
|
153
|
+
* lesson has a deck of its own, `part-deck` when a part has one. Empty when
|
|
154
|
+
* the course has no deck: the list then draws no glyph at all.
|
|
155
|
+
*/
|
|
156
|
+
export function contentsKey(lessons: readonly LaneLesson[], decks: readonly LaneDeck[]): ContentsGlyph[] {
|
|
157
|
+
if (decks.length === 0) return [];
|
|
158
|
+
return [
|
|
159
|
+
...(lessons.some((lesson) => lesson.editions.includes('chapter')) ? (['chapter'] as const) : []),
|
|
160
|
+
...(decks.some((deck) => deck.lessonId !== undefined) ? (['deck'] as const) : []),
|
|
161
|
+
...(decks.some((deck) => deck.part !== undefined) ? (['part-deck'] as const) : []),
|
|
162
|
+
];
|
|
163
|
+
}
|
|
@@ -41,8 +41,15 @@
|
|
|
41
41
|
* overflows its column (`../starlight/DiagramEnhancer.astro`). To let a
|
|
42
42
|
* reader fold a diagram away, wrap it in a `<Fold>` or put it in a
|
|
43
43
|
* collapsible section.
|
|
44
|
+
*
|
|
45
|
+
* The whole drawing, boxes and text together, follows the reader's text size
|
|
46
|
+
* on a touch screen (`../starlight/TextSizeControl.astro`), so a diagram can
|
|
47
|
+
* be made to fit a phone: see `.diagram-frame` and `.diagram-canvas` below,
|
|
48
|
+
* and `docs/diagram-scaling.md` for why it is done this way and what not to
|
|
49
|
+
* change.
|
|
44
50
|
*/
|
|
45
51
|
import { fitDiagramLayout, FORMULA_FONT_PX } from '../../reference/diagram-layout';
|
|
52
|
+
import { deckFloat } from '../deck/deck-context';
|
|
46
53
|
import { measureFormula } from '../../reference/katex-measure';
|
|
47
54
|
import { diagramTooWideMessage, diagramWidthLimit } from '../../reference/diagram-fit';
|
|
48
55
|
import { loadManifest } from '../../compiler/manifest';
|
|
@@ -93,6 +100,7 @@ export interface Props {
|
|
|
93
100
|
number?: string;
|
|
94
101
|
/** Computed by `remark-notation.mjs` for a top-level lesson diagram. */
|
|
95
102
|
anchorId?: string;
|
|
103
|
+
|
|
96
104
|
/**
|
|
97
105
|
* The widest this diagram may lay out, in px, before the build warns
|
|
98
106
|
* (`dia-too-wide`, rule V8). Default: the course's `rules.V8.columnWidth`,
|
|
@@ -113,6 +121,9 @@ const {
|
|
|
113
121
|
anchorId,
|
|
114
122
|
warnAboveWidth,
|
|
115
123
|
} = Astro.props;
|
|
124
|
+
// On a deck, the deck's own diagram carries no number and a recalled one's
|
|
125
|
+
// number links to its chapter (rule D8).
|
|
126
|
+
const { onDeck, numberHref } = await deckFloat(Astro.locals, anchorId, import.meta.env.BASE_URL);
|
|
116
127
|
|
|
117
128
|
const idPattern = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
118
129
|
if (!idPattern.test(id)) {
|
|
@@ -141,7 +152,7 @@ if (short !== undefined && (typeof short !== 'string' || !short.trim())) {
|
|
|
141
152
|
if (nodes.length === 0) {
|
|
142
153
|
throw new Error(`Diagram "${id}" has no nodes.`);
|
|
143
154
|
}
|
|
144
|
-
if ((number && !anchorId) || (!number && anchorId)) {
|
|
155
|
+
if ((number && !anchorId) || (!number && anchorId && !onDeck)) {
|
|
145
156
|
throw new Error(
|
|
146
157
|
`Diagram "${id}" must receive both computed \`number\` and \`anchorId\`, or neither.`,
|
|
147
158
|
);
|
|
@@ -306,91 +317,96 @@ function pathFrom(points: readonly { x: number; y: number }[]): string {
|
|
|
306
317
|
aria-labelledby={captionTextId}
|
|
307
318
|
>
|
|
308
319
|
<div
|
|
309
|
-
class="diagram-
|
|
310
|
-
style={`--diagram-
|
|
320
|
+
class="diagram-frame"
|
|
321
|
+
style={`--diagram-width: ${layout.width}; --diagram-height: ${layout.height};`}
|
|
311
322
|
>
|
|
312
|
-
<
|
|
313
|
-
|
|
314
|
-
width
|
|
315
|
-
height="100%"
|
|
323
|
+
<div
|
|
324
|
+
class="diagram-canvas"
|
|
325
|
+
style={`--diagram-print-ratio: ${layout.width} / ${layout.height}; width: ${layout.width}px; height: ${layout.height}px;`}
|
|
316
326
|
>
|
|
317
|
-
<
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
327
|
+
<svg
|
|
328
|
+
viewBox={`0 0 ${layout.width} ${layout.height}`}
|
|
329
|
+
width="100%"
|
|
330
|
+
height="100%"
|
|
331
|
+
>
|
|
332
|
+
<defs>
|
|
333
|
+
<marker
|
|
334
|
+
id={markerId}
|
|
335
|
+
viewBox="0 0 10 10"
|
|
336
|
+
refX="9"
|
|
337
|
+
refY="5"
|
|
338
|
+
markerWidth="7"
|
|
339
|
+
markerHeight="7"
|
|
340
|
+
orient="auto-start-reverse"
|
|
341
|
+
>
|
|
342
|
+
<path
|
|
343
|
+
d="M 0 0 L 10 5 L 0 10 z"
|
|
344
|
+
class="diagram-arrowhead"
|
|
345
|
+
/>
|
|
346
|
+
</marker>
|
|
347
|
+
</defs>
|
|
348
|
+
|
|
349
|
+
{
|
|
350
|
+
layout.edges.map((edge) => (
|
|
351
|
+
<path
|
|
352
|
+
d={pathFrom(edge.points)}
|
|
353
|
+
class="diagram-edge"
|
|
354
|
+
marker-end={`url(#${markerId})`}
|
|
355
|
+
/>
|
|
356
|
+
))
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
{
|
|
360
|
+
layout.nodes.map((position) => {
|
|
361
|
+
const node = nodeById.get(position.id)!;
|
|
362
|
+
// Position each node with x/y attributes, never a
|
|
363
|
+
// `transform` on the group: WebKit (every iOS browser)
|
|
364
|
+
// paints a `foreignObject` descendant that gets its own
|
|
365
|
+
// render layer — every KaTeX `position: relative` span —
|
|
366
|
+
// without the ancestor transform, so formulas pile up at
|
|
367
|
+
// the SVG origin while their boxes stay put.
|
|
368
|
+
return (
|
|
369
|
+
<g class="diagram-node">
|
|
370
|
+
<rect
|
|
371
|
+
x={position.x}
|
|
372
|
+
y={position.y}
|
|
373
|
+
width={position.width}
|
|
374
|
+
height={position.height}
|
|
375
|
+
rx="8"
|
|
376
|
+
ry="8"
|
|
377
|
+
/>
|
|
378
|
+
<foreignObject
|
|
379
|
+
x={position.x}
|
|
380
|
+
y={position.y}
|
|
381
|
+
width={position.width}
|
|
382
|
+
height={position.height}
|
|
372
383
|
>
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
384
|
+
<div
|
|
385
|
+
xmlns="http://www.w3.org/1999/xhtml"
|
|
386
|
+
class="diagram-node-content"
|
|
387
|
+
>
|
|
388
|
+
{
|
|
389
|
+
node.text && (
|
|
390
|
+
<span class="diagram-node-text">{node.text}</span>
|
|
391
|
+
)
|
|
392
|
+
}
|
|
393
|
+
{
|
|
394
|
+
node.mathNodes && (
|
|
395
|
+
<span class="diagram-node-formula notation-math">
|
|
396
|
+
{node.mathNodes.map((mathNode) => (
|
|
397
|
+
<SafeMathNode node={mathNode} />
|
|
398
|
+
))}
|
|
399
|
+
</span>
|
|
400
|
+
)
|
|
401
|
+
}
|
|
402
|
+
</div>
|
|
403
|
+
</foreignObject>
|
|
404
|
+
</g>
|
|
405
|
+
);
|
|
406
|
+
})
|
|
407
|
+
}
|
|
408
|
+
</svg>
|
|
409
|
+
</div>
|
|
394
410
|
</div>
|
|
395
411
|
</div>
|
|
396
412
|
</div>
|
|
@@ -403,6 +419,7 @@ function pathFrom(points: readonly { x: number; y: number }[]): string {
|
|
|
403
419
|
short={short}
|
|
404
420
|
anchorId={renderedAnchorId}
|
|
405
421
|
number={number}
|
|
422
|
+
numberHref={numberHref}
|
|
406
423
|
textId={captionTextId}
|
|
407
424
|
>
|
|
408
425
|
<button
|
|
@@ -479,6 +496,33 @@ function pathFrom(points: readonly { x: number; y: number }[]): string {
|
|
|
479
496
|
flex: none;
|
|
480
497
|
}
|
|
481
498
|
|
|
499
|
+
/* The reader's text size on a touch screen (`TextSizeControl.astro`)
|
|
500
|
+
shrinks the whole drawing, boxes and text, so that a wide diagram
|
|
501
|
+
fits a phone. It does so the way print does (`global.css`): the SVG
|
|
502
|
+
stays at its build-time size, and the HTML canvas around it is scaled
|
|
503
|
+
with a transform — never the `viewBox`, whose scale WebKit (every iOS
|
|
504
|
+
browser) does not apply to the HTML in a `foreignObject`. A transform
|
|
505
|
+
moves no layout box, so the frame takes the scaled size: it is what
|
|
506
|
+
the scroller measures (`scrollWidth`) and what sets the height. The
|
|
507
|
+
frame clips its content, which the scaled canvas just fills, so the
|
|
508
|
+
canvas's unscaled layout box adds nothing to the scroll width.
|
|
509
|
+
`--explico-text-scale` is 0.5 to 1; with no size chosen, or off a touch
|
|
510
|
+
screen, none of this applies. Only the lesson's own diagrams (also
|
|
511
|
+
when expanded) shrink: a card keeps the 12px floor on its text.
|
|
512
|
+
`docs/diagram-scaling.md` has the reasons and the rules for changing it. */
|
|
513
|
+
@media screen and (hover: none) {
|
|
514
|
+
:global(:root[data-text-size]) :global(.sl-markdown-content) .diagram-frame {
|
|
515
|
+
width: calc(var(--diagram-width) * 1px * var(--explico-text-scale, 1));
|
|
516
|
+
height: calc(var(--diagram-height) * 1px * var(--explico-text-scale, 1));
|
|
517
|
+
overflow: hidden;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
:global(:root[data-text-size]) :global(.sl-markdown-content) .diagram-canvas {
|
|
521
|
+
transform: scale(var(--explico-text-scale, 1));
|
|
522
|
+
transform-origin: 0 0;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
482
526
|
.diagram.dia-ref-highlight {
|
|
483
527
|
outline: 3px solid var(--sl-color-accent);
|
|
484
528
|
outline-offset: 0.25rem;
|
|
@@ -494,9 +538,10 @@ function pathFrom(points: readonly { x: number; y: number }[]): string {
|
|
|
494
538
|
box from these values; change them together. The size is in px, not
|
|
495
539
|
rem: the boxes are measured at build time for 13.6px text, so text that
|
|
496
540
|
followed a reader's larger default font size (20px, Chrome's "Large")
|
|
497
|
-
outgrew its box and was cut off. Scaling the
|
|
498
|
-
way out — WebKit on iOS scales the SVG but not the HTML in a
|
|
499
|
-
`foreignObject`. Browser zoom still enlarges it with everything else
|
|
541
|
+
outgrew its box and was cut off. Scaling the drawing through the SVG
|
|
542
|
+
is no way out — WebKit on iOS scales the SVG but not the HTML in a
|
|
543
|
+
`foreignObject`. Browser zoom still enlarges it with everything else,
|
|
544
|
+
and the text-size menu shrinks it as a whole (`.diagram-frame`). */
|
|
500
545
|
.diagram-node-content {
|
|
501
546
|
display: flex;
|
|
502
547
|
height: 100%;
|
|
@@ -43,10 +43,18 @@ export function wirePersistedDetails(
|
|
|
43
43
|
): void {
|
|
44
44
|
const fullKey = storageKey(key);
|
|
45
45
|
const saved = store.get(fullKey);
|
|
46
|
-
|
|
46
|
+
// The state the page shows without the reader having touched it. Setting
|
|
47
|
+
// it below fires a `toggle` of its own when the markup started the other
|
|
48
|
+
// way; that one is not the reader's, so it stores nothing. Only a state
|
|
49
|
+
// the reader moved the element to is saved: a page that is merely opened
|
|
50
|
+
// writes nothing to the browser.
|
|
51
|
+
let shown = saved === null ? defaultOpen : saved === 'true';
|
|
52
|
+
details.open = shown;
|
|
47
53
|
|
|
48
54
|
details.addEventListener('toggle', () => {
|
|
49
55
|
if (details.hasAttribute('data-explico-temporary-open')) return;
|
|
56
|
+
if (details.open === shown) return;
|
|
57
|
+
shown = details.open;
|
|
50
58
|
store.set(fullKey, String(details.open));
|
|
51
59
|
});
|
|
52
60
|
}
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
* tools never print. A figure with no `tools` slot has no button and no tray.
|
|
40
40
|
*/
|
|
41
41
|
import FloatCaption from './FloatCaption.astro';
|
|
42
|
+
import { deckFloat } from '../deck/deck-context';
|
|
42
43
|
import { courseDisclosure } from '../disclosure/course-disclosure';
|
|
43
44
|
|
|
44
45
|
export interface Props {
|
|
@@ -70,6 +71,9 @@ const {
|
|
|
70
71
|
number,
|
|
71
72
|
anchorId,
|
|
72
73
|
} = Astro.props;
|
|
74
|
+
// On a deck, the deck's own figure carries no number and a recalled one's
|
|
75
|
+
// number links to its chapter (rule D8).
|
|
76
|
+
const { onDeck, numberHref } = await deckFloat(Astro.locals, anchorId, import.meta.env.BASE_URL);
|
|
73
77
|
|
|
74
78
|
if ('title' in Astro.props) {
|
|
75
79
|
throw new Error(
|
|
@@ -82,7 +86,7 @@ if (typeof caption !== 'string' || !caption.trim()) {
|
|
|
82
86
|
if (short !== undefined && (typeof short !== 'string' || !short.trim())) {
|
|
83
87
|
throw new Error('Figure `short` must be a non-empty string when given.');
|
|
84
88
|
}
|
|
85
|
-
if (!number || !anchorId) {
|
|
89
|
+
if ((!number && !onDeck) || !anchorId) {
|
|
86
90
|
throw new Error(
|
|
87
91
|
`Figure is missing its computed \`number\`/\`anchorId\`. remark-notation numbers a <Figure> only on a lesson page, at its top level or in a <Memo>, <ShortNote> or <Fold>: elsewhere (a worked example) show the content without <Figure>, and on a page that is not a chapter (rule S10) link the chapter's figure. At a lesson's top level, this page is not running remark-notation with the manifest's \`figures\` (see astro.config.mjs).`,
|
|
88
92
|
);
|
|
@@ -91,7 +95,7 @@ if (!number || !anchorId) {
|
|
|
91
95
|
const hasTools = Astro.slots.has('tools');
|
|
92
96
|
const toolsId = `${anchorId}-tools`;
|
|
93
97
|
const toolsOpen = hasTools && (defaultOpen ?? (await courseDisclosure()).figures.defaultOpen);
|
|
94
|
-
const name = `Figure ${number}
|
|
98
|
+
const name = number ? `Figure ${number}` : 'Figure';
|
|
95
99
|
---
|
|
96
100
|
|
|
97
101
|
<figure
|
|
@@ -113,6 +117,7 @@ const name = `Figure ${number}`;
|
|
|
113
117
|
short={short}
|
|
114
118
|
anchorId={anchorId}
|
|
115
119
|
number={number}
|
|
120
|
+
numberHref={numberHref}
|
|
116
121
|
live
|
|
117
122
|
>
|
|
118
123
|
{
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* `.float-caption__text`, where a lab states the case its plot shows
|
|
28
28
|
* (`./figure-state.ts`).
|
|
29
29
|
*/
|
|
30
|
-
import { captionTitle } from '
|
|
30
|
+
import { captionTitle } from '../../reference/float-name';
|
|
31
31
|
|
|
32
32
|
export interface Props {
|
|
33
33
|
/** The reader-facing kind word: `Figure`, `Diagram`. */
|
|
@@ -42,13 +42,15 @@ export interface Props {
|
|
|
42
42
|
anchorId: string;
|
|
43
43
|
/** Its number; an unnumbered float shows none. */
|
|
44
44
|
number?: string;
|
|
45
|
+
/** Where its number links: the float itself by default, a deck's recalled one to its chapter. */
|
|
46
|
+
numberHref?: string;
|
|
45
47
|
/** An id for the caption text, for a control inside the body to be named by. */
|
|
46
48
|
textId?: string;
|
|
47
49
|
/** Give the line a live end a lab writes into (`./figure-state.ts`). */
|
|
48
50
|
live?: boolean;
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
const { kind, prefix, caption, short, anchorId, number, textId, live = false } = Astro.props;
|
|
53
|
+
const { kind, prefix, caption, short, anchorId, number, numberHref, textId, live = false } = Astro.props;
|
|
52
54
|
const name = number ? `${kind} ${number}` : kind.toLowerCase();
|
|
53
55
|
const tools = Astro.slots.has('default') ? (await Astro.slots.render('default')).trim() : '';
|
|
54
56
|
---
|
|
@@ -58,7 +60,7 @@ const tools = Astro.slots.has('default') ? (await Astro.slots.render('default'))
|
|
|
58
60
|
number && (
|
|
59
61
|
<a
|
|
60
62
|
class:list={['float-caption__number', `${prefix}__number`]}
|
|
61
|
-
href={`#${anchorId}`}
|
|
63
|
+
href={numberHref ?? `#${anchorId}`}
|
|
62
64
|
aria-label={name}
|
|
63
65
|
>
|
|
64
66
|
{name}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*
|
|
20
20
|
* The bar is the `label`, set at body-text size. Without one it names the
|
|
21
21
|
* numbered floats inside, "Diagram 2.1.1 Steps of the range derivation"
|
|
22
|
-
* (
|
|
22
|
+
* (`../../reference/float-name.ts`), read from the rendered content; with neither it
|
|
23
23
|
* throws, as `fold-unnamed` reports at `validate:content`. A bar that names
|
|
24
24
|
* the floats (`data-names-floats`) is their name here: their caption lines
|
|
25
25
|
* drop the bold short-caption title that would repeat it, and a folded
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* page.
|
|
32
32
|
*/
|
|
33
33
|
import { courseDisclosure } from '../disclosure/course-disclosure';
|
|
34
|
-
import { floatName, floatsInHtml } from '
|
|
34
|
+
import { floatName, floatsInHtml } from '../../reference/float-name';
|
|
35
35
|
import { foldId, foldSlug } from './fold-id';
|
|
36
36
|
|
|
37
37
|
export interface Props {
|