explico 0.8.0 → 0.11.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 +1239 -1
- package/README.md +60 -15
- package/dist/compiler/astro.d.ts +6 -1
- package/dist/compiler/cli.d.ts +46 -2
- package/dist/compiler/cli.js +70 -4
- package/dist/compiler/collections.d.ts +35 -2
- package/dist/compiler/collections.js +68 -5
- package/dist/compiler/course-config.d.ts +3 -194
- package/dist/compiler/course-config.js +78 -5
- package/dist/compiler/exports/standalone-html.d.ts +6 -1
- package/dist/compiler/knowledge-version.js +1 -1
- package/dist/compiler/lesson-derivation.d.ts +27 -10
- package/dist/compiler/lesson-derivation.js +23 -8
- package/dist/compiler/logging.d.ts +4 -1
- package/dist/compiler/logging.js +4 -1
- package/dist/compiler/manifest.d.ts +204 -31
- package/dist/compiler/manifest.js +270 -55
- package/dist/compiler/resolution-report.d.ts +6 -1
- package/dist/course-config-Dsnri9Tk.d.ts +464 -0
- package/dist/reference/base-accents.d.mts +26 -0
- package/dist/reference/base-accents.mjs +31 -0
- package/dist/reference/base-roman-labels.d.mts +2 -2
- package/dist/reference/base-roman-labels.mjs +2 -2
- package/dist/reference/consistency.d.ts +14 -2
- package/dist/reference/consistency.js +108 -4
- package/dist/reference/delimiter-size.d.ts +3 -2
- package/dist/reference/diagrams-validate.d.ts +7 -2
- package/dist/reference/diagrams-validate.js +9 -1
- package/dist/reference/equations-validate.d.ts +8 -5
- package/dist/reference/equations-validate.js +1 -0
- package/dist/reference/equations.d.mts +60 -31
- package/dist/reference/equations.mjs +83 -43
- package/dist/reference/examples-validate.d.ts +31 -0
- package/dist/reference/examples-validate.js +48 -0
- package/dist/reference/figures-validate.d.ts +1 -1
- package/dist/reference/figures-validate.js +4 -1
- package/dist/reference/folds-validate.d.ts +33 -0
- package/dist/reference/folds-validate.js +32 -0
- package/dist/reference/gate-abbreviations.d.ts +3 -1
- package/dist/reference/gate-abbreviations.js +1 -0
- package/dist/reference/gate-course-rules.d.ts +32 -0
- package/dist/reference/gate-course-rules.js +85 -0
- package/dist/reference/gate-heading-length.d.ts +5 -2
- package/dist/reference/gate-heading-length.js +45 -21
- package/dist/reference/gate-heading-structure.d.ts +41 -0
- package/dist/reference/gate-heading-structure.js +113 -0
- package/dist/reference/gate-math-punctuation.d.ts +71 -0
- package/dist/reference/gate-math-punctuation.js +251 -0
- package/dist/reference/gate-math.d.ts +22 -6
- package/dist/reference/gate-math.js +42 -8
- package/dist/reference/gate-part-pages.d.ts +50 -0
- package/dist/reference/gate-part-pages.js +118 -0
- package/dist/reference/gate-prose-notation.d.ts +21 -11
- package/dist/reference/gate-prose-notation.js +25 -7
- package/dist/reference/katex-delimiters.mjs +20 -7
- package/dist/reference/keyed-float-validate.d.ts +27 -10
- package/dist/reference/keyed-float-validate.js +15 -1
- package/dist/reference/keyed-jsx-floats.d.mts +11 -5
- package/dist/reference/keyed-jsx-floats.mjs +73 -20
- package/dist/reference/knowledge-checks-validate.d.ts +1 -1
- package/dist/reference/knowledge-checks.d.mts +3 -1
- package/dist/reference/knowledge-checks.mjs +9 -4
- package/dist/reference/lab-math-scope.d.ts +1 -0
- package/dist/reference/lab-math-slot.d.ts +34 -0
- package/dist/reference/lab-math-slot.js +6 -0
- package/dist/reference/math-glyphs.d.mts +80 -4
- package/dist/reference/math-glyphs.mjs +572 -37
- package/dist/reference/page-notation.d.ts +6 -1
- package/dist/reference/raw-floats-validate.js +1 -1
- package/dist/reference/raw-floats.mjs +1 -1
- package/dist/reference/rehype-heading-anchors.d.mts +14 -0
- package/dist/reference/rehype-heading-anchors.mjs +116 -17
- package/dist/reference/rehype-table-header-labels.d.mts +6 -1
- package/dist/reference/rehype-table-header-labels.mjs +21 -3
- package/dist/reference/remark-asides.d.mts +3 -0
- package/dist/reference/remark-asides.mjs +180 -0
- package/dist/reference/remark-memo.d.mts +3 -1
- package/dist/reference/remark-memo.mjs +42 -11
- package/dist/reference/remark-notation.d.mts +60 -10
- package/dist/reference/remark-notation.mjs +387 -36
- package/dist/reference/render-lab-math.d.ts +4 -26
- package/dist/reference/render-lab-math.js +4 -4
- package/dist/reference/sections-validate.d.ts +27 -0
- package/dist/reference/sections-validate.js +41 -0
- package/dist/reference/sections.d.mts +97 -0
- package/dist/reference/sections.mjs +306 -0
- package/dist/reference/tables-validate.d.ts +1 -1
- package/dist/reference/types.d.ts +17 -1
- package/dist/reference/unit-words.d.ts +37 -0
- package/dist/reference/unit-words.js +170 -0
- package/dist/rules/catalog.d.ts +176 -0
- package/dist/rules/catalog.js +983 -0
- package/dist/rules/course-rules.d.ts +115 -0
- package/dist/rules/course-rules.js +292 -0
- package/dist/rules/course-rules.schema.json +138 -0
- package/dist/rules/in-effect.d.ts +53 -0
- package/dist/rules/in-effect.js +142 -0
- package/dist/rules/rules.schema.json +1072 -0
- package/dist/rules/settings.d.ts +3 -0
- package/dist/rules/settings.js +290 -0
- package/dist/rules/waivers.d.ts +53 -0
- package/dist/rules/waivers.js +132 -0
- package/dist/session/PreferenceStore.d.ts +5 -0
- package/dist/session/PreferenceStore.js +9 -0
- package/docs/VERSIONING.md +2 -2
- package/docs/authoring/README.md +232 -40
- package/docs/authoring/agents.md +55 -9
- package/docs/authoring/components.md +178 -22
- package/docs/authoring/migrating.md +1096 -2
- package/docs/authoring/notation.md +576 -322
- package/docs/authoring/prose.md +551 -0
- package/docs/authoring/retired.md +35 -0
- package/docs/authoring/structure-rules.md +422 -40
- package/docs/authoring/structure.md +445 -62
- package/docs/authoring/versioning.md +20 -28
- package/docs/authoring/visualizations.md +142 -20
- package/package.json +2 -2
- package/src/compiler/cli.ts +116 -6
- package/src/compiler/collections.ts +111 -7
- package/src/compiler/course-config.ts +273 -34
- package/src/compiler/knowledge-version.ts +1 -1
- package/src/compiler/lesson-derivation.ts +54 -15
- package/src/compiler/logging.ts +8 -2
- package/src/compiler/manifest.ts +573 -104
- package/src/components/BlockHeading.astro +113 -0
- package/src/components/CourseContents.astro +221 -0
- package/src/components/CurriculumMap.astro +5 -6
- package/src/components/FloatIndex.astro +200 -0
- package/src/components/HoverPanel.astro +431 -79
- package/src/components/PartBridge.astro +27 -0
- package/src/components/PartOverview.astro +401 -0
- package/src/components/PartScope.astro +27 -0
- package/src/components/abbreviation/AbbreviationLayer.astro +49 -51
- package/src/components/assessments/AssessmentRunner.tsx +8 -5
- package/src/components/assessments/AssessmentSet.astro +23 -26
- package/src/components/block-heading.ts +146 -0
- package/src/components/card-arrow.ts +243 -0
- package/src/components/card-drag.ts +631 -0
- package/src/components/card-jump.ts +45 -0
- package/src/components/card-manager.ts +1589 -0
- package/src/components/card-memory.ts +177 -0
- package/src/components/card-placement.ts +504 -0
- package/src/components/card-side.ts +336 -0
- package/src/components/card-travel.ts +197 -0
- package/src/components/citation/CitationLayer.astro +77 -76
- package/src/components/diagram/Diagram.astro +161 -316
- package/src/components/disclosure/course-disclosure.ts +24 -0
- package/src/components/disclosure/hover-preview.ts +122 -42
- package/src/components/disclosure/persisted-disclosure.ts +3 -3
- package/src/components/disclosure/print-as-shown.ts +252 -0
- package/src/components/examples/CompactExample.astro +3 -1
- package/src/components/examples/CompactExamples.astro +101 -94
- package/src/components/figure/Figure.astro +174 -274
- package/src/components/figure/FloatCaption.astro +87 -0
- package/src/components/figure/figure-state.ts +58 -0
- package/src/components/figure/figure-tools.ts +87 -0
- package/src/components/float-name.ts +101 -0
- package/src/components/fold/Fold.astro +125 -0
- package/src/components/fold/fold-id.ts +40 -0
- package/src/components/memo/Memo.astro +162 -231
- package/src/components/memo/ShortNote.astro +6 -6
- package/src/components/notation/NotationFormula.astro +5 -1
- package/src/components/notation/NotationGlossary.astro +3 -4
- package/src/components/notation/NotationLayer.astro +169 -162
- package/src/components/notation/NotationMath.astro +10 -1
- package/src/components/reference/FloatRefLayer.astro +485 -423
- package/src/components/starlight/AboutPanel.astro +5 -4
- package/src/components/starlight/CollapsibleSections.astro +207 -93
- package/src/components/starlight/DiagramEnhancer.astro +15 -138
- package/src/components/starlight/HeadingAnchorEnhancer.astro +193 -1
- package/src/components/starlight/KeyedFloatEnhancer.astro +100 -9
- package/src/components/starlight/LayoutHeader.astro +298 -19
- package/src/components/starlight/LayoutPageSidebar.astro +99 -12
- package/src/components/starlight/LayoutPageTitle.astro +120 -0
- package/src/components/starlight/LayoutSidebar.astro +229 -11
- package/src/components/starlight/LessonExportActions.astro +53 -72
- package/src/components/starlight/LessonFooter.astro +26 -4
- package/src/components/starlight/section-parts.ts +15 -2
- package/src/components/starlight/section-summary.ts +181 -0
- package/src/components/table/Table.astro +95 -76
- package/src/components/table/TableOverflowExpander.astro +65 -28
- package/src/content-config.ts +40 -4
- package/src/reference/base-accents.mjs +31 -0
- package/src/reference/base-roman-labels.mjs +2 -2
- package/src/reference/consistency.ts +176 -5
- package/src/reference/delimiter-size.ts +3 -2
- package/src/reference/diagrams-validate.ts +17 -1
- package/src/reference/equations-validate.ts +10 -5
- package/src/reference/equations.mjs +83 -43
- package/src/reference/examples-validate.ts +97 -0
- package/src/reference/figures-validate.ts +6 -1
- package/src/reference/folds-validate.ts +65 -0
- package/src/reference/gate-abbreviations.ts +1 -0
- package/src/reference/gate-course-rules.ts +134 -0
- package/src/reference/gate-heading-length.ts +66 -35
- package/src/reference/gate-heading-structure.ts +193 -0
- package/src/reference/gate-math-punctuation.ts +381 -0
- package/src/reference/gate-math.ts +69 -10
- package/src/reference/gate-part-pages.ts +217 -0
- package/src/reference/gate-prose-notation.ts +59 -12
- package/src/reference/katex-delimiters.mjs +20 -7
- package/src/reference/keyed-float-validate.ts +45 -11
- package/src/reference/keyed-jsx-floats.mjs +73 -20
- package/src/reference/knowledge-checks-validate.ts +1 -1
- package/src/reference/knowledge-checks.mjs +9 -4
- package/src/reference/lab-math-slot.ts +36 -0
- package/src/reference/math-glyphs.mjs +572 -37
- package/src/reference/raw-floats-validate.ts +1 -1
- package/src/reference/raw-floats.mjs +1 -1
- package/src/reference/rehype-heading-anchors.mjs +116 -17
- package/src/reference/rehype-table-header-labels.mjs +21 -3
- package/src/reference/remark-asides.mjs +180 -0
- package/src/reference/remark-memo.mjs +42 -11
- package/src/reference/remark-notation.mjs +387 -36
- package/src/reference/render-lab-math.ts +16 -31
- package/src/reference/sections-validate.ts +81 -0
- package/src/reference/sections.mjs +306 -0
- package/src/reference/tables-validate.ts +2 -1
- package/src/reference/types.ts +17 -0
- package/src/reference/unit-words.ts +233 -0
- package/src/rules/catalog.ts +1298 -0
- package/src/rules/course-rules.ts +456 -0
- package/src/rules/in-effect.ts +235 -0
- package/src/rules/settings.ts +448 -0
- package/src/rules/waivers.ts +249 -0
- package/src/session/PreferenceStore.ts +15 -0
- package/src/styles/global.css +1471 -163
- package/src/styles/surfaces.css +182 -0
- package/src/components/hover-panel.ts +0 -339
- package/src/components/popup-stack.ts +0 -345
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* The `<h2>` a knowledge check (`AssessmentSet.astro`) and a worked-example
|
|
4
|
+
* set (`CompactExamples.astro`) draw where their tag stands
|
|
5
|
+
* (docs/plans/titles-and-containers.md Part 6, Q11). The author writes only
|
|
6
|
+
* the topic; the engine names the kind and the number:
|
|
7
|
+
*
|
|
8
|
+
* KNOWLEDGE CHECK 1.1.1
|
|
9
|
+
* ✓ Position and coordinates
|
|
10
|
+
*
|
|
11
|
+
* The kind's icon leads the title, which is one line of inline text, not a
|
|
12
|
+
* flex row, so it stays whole when it fits and wraps word by word when it
|
|
13
|
+
* does not. The badge — the kind's word and the number — is a small label in
|
|
14
|
+
* the card's top-right corner (`global.css`), out of the text flow, so the
|
|
15
|
+
* title keeps the whole line. Its source order still names the heading
|
|
16
|
+
* "Knowledge check 1.1.1 Position and coordinates". The word is for a screen
|
|
17
|
+
* reader only when the course sets `blockLabels: "icon"`. The number shows,
|
|
18
|
+
* and hides, by the section rules (`global.css`: on a wide screen, in print
|
|
19
|
+
* and on a folded bar; never when `numbering.sections.show` is off, which
|
|
20
|
+
* also leaves `data-‹kind›-number` off the heading, so no number-form alias
|
|
21
|
+
* resolves to it).
|
|
22
|
+
*
|
|
23
|
+
* The wrapper is the same `.sl-heading-wrapper` a Markdown heading gets
|
|
24
|
+
* (`rehype-heading-anchors.mjs`), chain icon included, so
|
|
25
|
+
* `CollapsibleSections.astro` folds and cards it and `HeadingAnchorEnhancer`
|
|
26
|
+
* copies its link. "On this page" lists it through the manifest's
|
|
27
|
+
* `titledBlocks` (`LayoutPageSidebar.astro`), by its title with the badge's
|
|
28
|
+
* icon — the same two icons as below, as CSS masks (`--block-kind-icon` in
|
|
29
|
+
* `global.css`).
|
|
30
|
+
*/
|
|
31
|
+
import { BLOCK_KINDS, blockHeadingSettings, type BlockKind } from './block-heading';
|
|
32
|
+
|
|
33
|
+
interface Props {
|
|
34
|
+
kind: BlockKind;
|
|
35
|
+
/** The heading's `id`: `check-‹id›` or `ex-‹id›`. */
|
|
36
|
+
anchorId: string;
|
|
37
|
+
/** The author's topical title. */
|
|
38
|
+
title: string;
|
|
39
|
+
/** The block's number (`1.1.1`), when it has one. */
|
|
40
|
+
number?: string | undefined;
|
|
41
|
+
/** Whether the section folds (`data-collapsible`). */
|
|
42
|
+
collapsible: boolean;
|
|
43
|
+
/** A folding section's first-visit state (`data-default-open`). */
|
|
44
|
+
defaultOpen: boolean;
|
|
45
|
+
/** Keep the reader's open/closed choice (`data-remember-state`). Default `true`. */
|
|
46
|
+
rememberState?: boolean;
|
|
47
|
+
/** `data-card`: `CollapsibleSections` cards heading and section in this tier. */
|
|
48
|
+
card?: 'accent' | undefined;
|
|
49
|
+
/** `data-section-to-end`: the section is the rest of the block's container. */
|
|
50
|
+
sectionToEnd?: boolean;
|
|
51
|
+
/** Extra classes on the wrapper. */
|
|
52
|
+
class?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const {
|
|
56
|
+
kind,
|
|
57
|
+
anchorId,
|
|
58
|
+
title,
|
|
59
|
+
number,
|
|
60
|
+
collapsible,
|
|
61
|
+
defaultOpen,
|
|
62
|
+
rememberState = true,
|
|
63
|
+
card,
|
|
64
|
+
sectionToEnd = false,
|
|
65
|
+
class: className,
|
|
66
|
+
} = Astro.props;
|
|
67
|
+
|
|
68
|
+
const { labels, showNumbers } = await blockHeadingSettings();
|
|
69
|
+
const { word, numberAttribute } = BLOCK_KINDS[kind];
|
|
70
|
+
const shown = showNumbers && number ? number : undefined;
|
|
71
|
+
const name = shown ? `${word} ${shown}: ${title}` : `${word}: ${title}`;
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
<div
|
|
75
|
+
class:list={['sl-heading-wrapper', 'level-h2', 'block-heading-wrapper', className]}
|
|
76
|
+
data-collapsible={String(collapsible)}
|
|
77
|
+
data-default-open={String(defaultOpen)}
|
|
78
|
+
data-remember-state={String(rememberState)}
|
|
79
|
+
data-card={card}
|
|
80
|
+
{...sectionToEnd ? { 'data-section-to-end': '' } : {}}
|
|
81
|
+
>
|
|
82
|
+
<h2
|
|
83
|
+
id={anchorId}
|
|
84
|
+
class="block-heading"
|
|
85
|
+
data-block-kind={kind}
|
|
86
|
+
{...shown ? { [numberAttribute]: shown } : {}}
|
|
87
|
+
>
|
|
88
|
+
<span class:list={['block-badge', `block-badge--${kind}`]} data-labels={labels}
|
|
89
|
+
><span class:list={['block-badge__word', { 'sr-only': labels === 'icon' }]}>{word}</span
|
|
90
|
+
>{shown && <span class="block-heading__number">{` ${shown}`}</span>}</span
|
|
91
|
+
>{' '}{
|
|
92
|
+
kind === 'check' ? (
|
|
93
|
+
<svg class="block-heading__icon" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
94
|
+
<path d="M2 8.5l4 4 8-9" />
|
|
95
|
+
</svg>
|
|
96
|
+
) : (
|
|
97
|
+
<svg class="block-heading__icon" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round">
|
|
98
|
+
<rect x="2" y="2" width="12" height="12" rx="2" />
|
|
99
|
+
<path d="M5 8h6M8 5v6" />
|
|
100
|
+
</svg>
|
|
101
|
+
)
|
|
102
|
+
}<span class="block-heading__title">{title}</span>
|
|
103
|
+
</h2>
|
|
104
|
+
<a class="sl-anchor-link" href={`#${anchorId}`}
|
|
105
|
+
><span aria-hidden="true" class="sl-anchor-icon"
|
|
106
|
+
><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"
|
|
107
|
+
><path
|
|
108
|
+
d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"
|
|
109
|
+
></path></svg
|
|
110
|
+
></span
|
|
111
|
+
><span class="sr-only" data-pagefind-ignore>Link to {name}</span></a
|
|
112
|
+
>
|
|
113
|
+
</div>
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Every chapter of the course, grouped under its part — the landing page's
|
|
4
|
+
* answer to "what is in here, and how long is it?".
|
|
5
|
+
*
|
|
6
|
+
* A hero that links only the first chapter hides the other eighteen, and a
|
|
7
|
+
* hand-written list of them goes stale the first time a chapter is renamed.
|
|
8
|
+
* This reads the manifest: parts in config order, chapters in reading order,
|
|
9
|
+
* each part heading linking its overview page where one exists, and study
|
|
10
|
+
* times totalled per part and for the course when every chapter states one.
|
|
11
|
+
*
|
|
12
|
+
* Usage, on a splash page:
|
|
13
|
+
*
|
|
14
|
+
* ```mdx
|
|
15
|
+
* import CourseContents from 'explico/components/CourseContents.astro';
|
|
16
|
+
*
|
|
17
|
+
* <CourseContents />
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
import { loadManifest } from '../compiler/manifest';
|
|
21
|
+
|
|
22
|
+
interface Props {
|
|
23
|
+
/** Heading level for each part's name. Defaults to `h2`. */
|
|
24
|
+
readonly headingLevel?: 2 | 3;
|
|
25
|
+
/** Set `false` to drop the per-chapter description column. */
|
|
26
|
+
readonly descriptions?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const { headingLevel = 2, descriptions = true } = Astro.props;
|
|
30
|
+
const Heading = `h${headingLevel}` as 'h2' | 'h3';
|
|
31
|
+
|
|
32
|
+
const manifest = await loadManifest();
|
|
33
|
+
const base = import.meta.env.BASE_URL.replace(/\/$/, '');
|
|
34
|
+
const lessonById = new Map(
|
|
35
|
+
manifest.lessons.map((lesson) => [lesson.id, lesson]),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const parts = manifest.parts
|
|
39
|
+
.map((part) => ({
|
|
40
|
+
...part,
|
|
41
|
+
chapters: part.lessons
|
|
42
|
+
.map((id) => lessonById.get(id))
|
|
43
|
+
.filter(
|
|
44
|
+
(lesson): lesson is NonNullable<typeof lesson> => lesson !== undefined,
|
|
45
|
+
),
|
|
46
|
+
}))
|
|
47
|
+
.filter((part) => part.chapters.length > 0);
|
|
48
|
+
|
|
49
|
+
const chapterCount = parts.reduce(
|
|
50
|
+
(total, part) => total + part.chapters.length,
|
|
51
|
+
0,
|
|
52
|
+
);
|
|
53
|
+
// Same rule as a part's own total: state one only when nothing is missing
|
|
54
|
+
// from it.
|
|
55
|
+
const courseMinutes = parts.every((part) => part.studyMinutes !== undefined)
|
|
56
|
+
? parts.reduce(
|
|
57
|
+
(total, part) => ({
|
|
58
|
+
min: total.min + part.studyMinutes!.min,
|
|
59
|
+
max: total.max + part.studyMinutes!.max,
|
|
60
|
+
}),
|
|
61
|
+
{ min: 0, max: 0 },
|
|
62
|
+
)
|
|
63
|
+
: undefined;
|
|
64
|
+
|
|
65
|
+
const minutes = (range: { min: number; max: number }) =>
|
|
66
|
+
range.min === range.max
|
|
67
|
+
? `${range.min} min`
|
|
68
|
+
: `${range.min}–${range.max} min`;
|
|
69
|
+
|
|
70
|
+
const hours = (range: { min: number; max: number }) => {
|
|
71
|
+
const low = Math.round((range.min / 60) * 10) / 10;
|
|
72
|
+
const high = Math.round((range.max / 60) * 10) / 10;
|
|
73
|
+
return low === high ? `${low} hours` : `${low}–${high} hours`;
|
|
74
|
+
};
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
<div class="course-contents not-content">
|
|
78
|
+
<p class="course-meta">
|
|
79
|
+
<span>{parts.length} parts</span>
|
|
80
|
+
<span>{chapterCount} chapters</span>
|
|
81
|
+
{courseMinutes && <span>{hours(courseMinutes)} of study</span>}
|
|
82
|
+
</p>
|
|
83
|
+
|
|
84
|
+
{
|
|
85
|
+
parts.map((part) => (
|
|
86
|
+
<section class="course-part">
|
|
87
|
+
<Heading id={`contents-${part.dir}`}>
|
|
88
|
+
{part.overview ? (
|
|
89
|
+
<a href={`${base}${part.link}`}>{part.label}</a>
|
|
90
|
+
) : (
|
|
91
|
+
part.label
|
|
92
|
+
)}
|
|
93
|
+
</Heading>
|
|
94
|
+
<p class="part-line">
|
|
95
|
+
<span>
|
|
96
|
+
Part {part.ordinal} · {part.chapters.length}{' '}
|
|
97
|
+
{part.chapters.length === 1 ? 'chapter' : 'chapters'}
|
|
98
|
+
</span>
|
|
99
|
+
{part.studyMinutes && <span>{minutes(part.studyMinutes)}</span>}
|
|
100
|
+
</p>
|
|
101
|
+
<ol aria-labelledby={`contents-${part.dir}`}>
|
|
102
|
+
{part.chapters.map((lesson) => (
|
|
103
|
+
<li>
|
|
104
|
+
<a href={`${base}/${lesson.slug}/`}>{lesson.title}</a>
|
|
105
|
+
{lesson.studyMinutes && (
|
|
106
|
+
<span class="chapter-time">{minutes(lesson.studyMinutes)}</span>
|
|
107
|
+
)}
|
|
108
|
+
{descriptions && <p>{lesson.description}</p>}
|
|
109
|
+
</li>
|
|
110
|
+
))}
|
|
111
|
+
</ol>
|
|
112
|
+
</section>
|
|
113
|
+
))
|
|
114
|
+
}
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<style>
|
|
118
|
+
.course-contents {
|
|
119
|
+
display: grid;
|
|
120
|
+
gap: 1.75rem;
|
|
121
|
+
margin-block-start: 1.5rem;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.course-meta,
|
|
125
|
+
.part-line {
|
|
126
|
+
display: flex;
|
|
127
|
+
flex-wrap: wrap;
|
|
128
|
+
gap: 0.25rem 1rem;
|
|
129
|
+
margin: 0;
|
|
130
|
+
color: var(--sl-color-gray-3);
|
|
131
|
+
font-size: var(--sl-text-xs);
|
|
132
|
+
letter-spacing: 0.04em;
|
|
133
|
+
text-transform: uppercase;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.course-part {
|
|
137
|
+
display: grid;
|
|
138
|
+
gap: 0.4rem;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.course-part :global(h2),
|
|
142
|
+
.course-part :global(h3) {
|
|
143
|
+
margin: 0;
|
|
144
|
+
font-size: var(--sl-text-lg);
|
|
145
|
+
line-height: 1.3;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.course-part :global(h2) a,
|
|
149
|
+
.course-part :global(h3) a {
|
|
150
|
+
text-decoration: none;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.course-part :global(h2) a:hover,
|
|
154
|
+
.course-part :global(h3) a:hover {
|
|
155
|
+
text-decoration: underline;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.course-part ol {
|
|
159
|
+
counter-reset: chapter;
|
|
160
|
+
display: grid;
|
|
161
|
+
gap: 0.1rem;
|
|
162
|
+
margin: 0.35rem 0 0;
|
|
163
|
+
padding: 0;
|
|
164
|
+
list-style: none;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.course-part li {
|
|
168
|
+
counter-increment: chapter;
|
|
169
|
+
display: grid;
|
|
170
|
+
grid-template-columns: 1.9rem minmax(0, 1fr) auto;
|
|
171
|
+
gap: 0 0.5rem;
|
|
172
|
+
margin: 0;
|
|
173
|
+
padding-block: 0.4rem;
|
|
174
|
+
border-block-start: 1px solid var(--sl-color-gray-6);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.course-part li::before {
|
|
178
|
+
content: counter(chapter, decimal-leading-zero);
|
|
179
|
+
color: var(--sl-color-gray-4);
|
|
180
|
+
font-size: var(--sl-text-xs);
|
|
181
|
+
font-variant-numeric: tabular-nums;
|
|
182
|
+
line-height: 1.7;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.course-part li > a {
|
|
186
|
+
text-decoration: none;
|
|
187
|
+
font-weight: 500;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.course-part li > a:hover {
|
|
191
|
+
text-decoration: underline;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.course-part li > .chapter-time {
|
|
195
|
+
color: var(--sl-color-gray-3);
|
|
196
|
+
font-size: var(--sl-text-xs);
|
|
197
|
+
font-variant-numeric: tabular-nums;
|
|
198
|
+
text-align: end;
|
|
199
|
+
white-space: nowrap;
|
|
200
|
+
line-height: 1.7;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.course-part li > p {
|
|
204
|
+
grid-column: 2 / -1;
|
|
205
|
+
margin: 0;
|
|
206
|
+
color: var(--sl-color-gray-3);
|
|
207
|
+
font-size: var(--sl-text-sm);
|
|
208
|
+
line-height: 1.45;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
@media (max-width: 40rem) {
|
|
212
|
+
.course-part li {
|
|
213
|
+
grid-template-columns: 1.9rem minmax(0, 1fr);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.course-part li > .chapter-time {
|
|
217
|
+
grid-column: 2;
|
|
218
|
+
text-align: start;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
</style>
|
|
@@ -65,7 +65,7 @@ const competencyEdges: DiagramEdge[] = competencies.flatMap(({ data }) =>
|
|
|
65
65
|
if (!lesson || !entry) return null;
|
|
66
66
|
return (
|
|
67
67
|
<li>
|
|
68
|
-
<article>
|
|
68
|
+
<article class="ex-tier-meta">
|
|
69
69
|
<p class="step">Lesson {index + 1}</p>
|
|
70
70
|
<h3>
|
|
71
71
|
<a
|
|
@@ -130,7 +130,7 @@ const competencyEdges: DiagramEdge[] = competencies.flatMap(({ data }) =>
|
|
|
130
130
|
competencyNodes.length > 0 && (
|
|
131
131
|
<Diagram
|
|
132
132
|
id="competency-graph"
|
|
133
|
-
|
|
133
|
+
caption="The competency dependency graph: an arrow runs from a prerequisite to the competency that builds on it"
|
|
134
134
|
nodes={competencyNodes}
|
|
135
135
|
edges={competencyEdges}
|
|
136
136
|
/>
|
|
@@ -142,7 +142,7 @@ const competencyEdges: DiagramEdge[] = competencies.flatMap(({ data }) =>
|
|
|
142
142
|
competencies
|
|
143
143
|
.sort((left, right) => left.data.id.localeCompare(right.data.id))
|
|
144
144
|
.map(({ data }) => (
|
|
145
|
-
<li>
|
|
145
|
+
<li class="ex-tier-meta">
|
|
146
146
|
<strong>{data.title}</strong>
|
|
147
147
|
<br />
|
|
148
148
|
<code>{data.id}</code>
|
|
@@ -181,12 +181,11 @@ const competencyEdges: DiagramEdge[] = competencies.flatMap(({ data }) =>
|
|
|
181
181
|
font-weight: 700;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
/* Generated course logistics: the meta tier (`ex-tier-meta`,
|
|
185
|
+
`surfaces.css`) draws each card's surface, border and radius. */
|
|
184
186
|
.lesson-sequence article,
|
|
185
187
|
.competency-map li {
|
|
186
188
|
padding: 1rem;
|
|
187
|
-
border: 1px solid var(--sl-color-gray-5);
|
|
188
|
-
border-radius: 0.55rem;
|
|
189
|
-
background: var(--sl-color-gray-7);
|
|
190
189
|
}
|
|
191
190
|
|
|
192
191
|
.lesson-sequence h3,
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* The course's "Figures and tables" page — LaTeX's `\listoffigures` and
|
|
4
|
+
* `\listoftables` in one (docs/plans/figures-and-short-captions.md, decision
|
|
5
|
+
* 9.4): every numbered figure, table and diagram with an `id`, by part and
|
|
6
|
+
* chapter.
|
|
7
|
+
*
|
|
8
|
+
* An entry is the float's number, linked to its place in its chapter, and
|
|
9
|
+
* its short caption; or its long caption, muted, when it has no short one;
|
|
10
|
+
* or the number alone when the manifest has neither — a float whose captions
|
|
11
|
+
* a wrapper component sets (a lab's `DerivationDiagram.astro`) reaches the
|
|
12
|
+
* manifest without them. A float without an `id` has no stable anchor and
|
|
13
|
+
* is not in the manifest's lists, so it is not listed.
|
|
14
|
+
*
|
|
15
|
+
* Modelled on `CourseContents.astro`: it reads the manifest — parts in
|
|
16
|
+
* config order, chapters in reading order, then each chapter's figures,
|
|
17
|
+
* tables and diagrams, by kind and number. A chapter or a part with none is
|
|
18
|
+
* left out.
|
|
19
|
+
*
|
|
20
|
+
* Usage, on a top-level page the sidebar's Reference group lists
|
|
21
|
+
* (`content/docs/figures-and-tables.mdx`):
|
|
22
|
+
*
|
|
23
|
+
* ```mdx
|
|
24
|
+
* import FloatIndex from 'explico/components/FloatIndex.astro';
|
|
25
|
+
*
|
|
26
|
+
* <FloatIndex />
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
import { loadManifest, type ManifestLessonFloat } from '../compiler/manifest';
|
|
30
|
+
import { FLOAT_KIND_WORDS, type FloatKind } from './float-name';
|
|
31
|
+
|
|
32
|
+
interface Props {
|
|
33
|
+
/** Heading level for each part's name; a chapter's is one below. Defaults to `h2`. */
|
|
34
|
+
readonly headingLevel?: 2 | 3;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const { headingLevel = 2 } = Astro.props;
|
|
38
|
+
const PartHeading = `h${headingLevel}` as 'h2' | 'h3';
|
|
39
|
+
const ChapterHeading = `h${headingLevel + 1}` as 'h3' | 'h4';
|
|
40
|
+
|
|
41
|
+
const manifest = await loadManifest();
|
|
42
|
+
const base = import.meta.env.BASE_URL.replace(/\/$/, '');
|
|
43
|
+
const lessonById = new Map(manifest.lessons.map((lesson) => [lesson.id, lesson]));
|
|
44
|
+
|
|
45
|
+
const KINDS = [
|
|
46
|
+
['figures', 'fig'],
|
|
47
|
+
['tables', 'tab'],
|
|
48
|
+
['diagrams', 'dia'],
|
|
49
|
+
] as const satisfies readonly (readonly [string, FloatKind])[];
|
|
50
|
+
|
|
51
|
+
const byNumber = (a: ManifestLessonFloat, b: ManifestLessonFloat) =>
|
|
52
|
+
a.number.localeCompare(b.number, undefined, { numeric: true });
|
|
53
|
+
|
|
54
|
+
const parts = manifest.parts
|
|
55
|
+
.map((part) => ({
|
|
56
|
+
...part,
|
|
57
|
+
chapters: part.lessons
|
|
58
|
+
.map((id) => lessonById.get(id))
|
|
59
|
+
.filter((lesson): lesson is NonNullable<typeof lesson> => lesson !== undefined)
|
|
60
|
+
.map((lesson, index) => ({
|
|
61
|
+
lesson,
|
|
62
|
+
// The chapter's number, as its sections' numbers begin.
|
|
63
|
+
number: `${part.ordinal}.${index + 1}`,
|
|
64
|
+
entries: KINDS.flatMap(([field, kind]) =>
|
|
65
|
+
[...lesson[field]].sort(byNumber).map((float) => ({
|
|
66
|
+
...float,
|
|
67
|
+
label: `${FLOAT_KIND_WORDS[kind]} ${float.number}`,
|
|
68
|
+
href: `${base}/${lesson.slug}/#${kind}-${float.key}`,
|
|
69
|
+
})),
|
|
70
|
+
),
|
|
71
|
+
}))
|
|
72
|
+
.filter((chapter) => chapter.entries.length > 0),
|
|
73
|
+
}))
|
|
74
|
+
.filter((part) => part.chapters.length > 0);
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
<div class="float-index not-content">
|
|
78
|
+
{
|
|
79
|
+
parts.length === 0 && (
|
|
80
|
+
<p class="float-index__empty">No chapter has a numbered figure, table or diagram yet.</p>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
{
|
|
84
|
+
parts.map((part) => (
|
|
85
|
+
<section class="float-index__part">
|
|
86
|
+
<PartHeading id={`floats-${part.dir}`}>{part.label}</PartHeading>
|
|
87
|
+
{part.chapters.map(({ lesson, number, entries }) => (
|
|
88
|
+
<section class="float-index__chapter">
|
|
89
|
+
<ChapterHeading id={`floats-${lesson.slug.replace(/\//g, '-')}`}>
|
|
90
|
+
<span class="float-index__chapter-number">{number}</span>{' '}
|
|
91
|
+
<a href={`${base}/${lesson.slug}/`}>{lesson.title}</a>
|
|
92
|
+
</ChapterHeading>
|
|
93
|
+
<ol class="float-index__list">
|
|
94
|
+
{entries.map((entry) => (
|
|
95
|
+
<li>
|
|
96
|
+
<a class="float-index__number" href={entry.href}>
|
|
97
|
+
{entry.label}
|
|
98
|
+
</a>
|
|
99
|
+
{entry.short ? (
|
|
100
|
+
<span class="float-index__name">{entry.short}</span>
|
|
101
|
+
) : entry.caption ? (
|
|
102
|
+
<span class="float-index__name float-index__name--long">{entry.caption}</span>
|
|
103
|
+
) : null}
|
|
104
|
+
</li>
|
|
105
|
+
))}
|
|
106
|
+
</ol>
|
|
107
|
+
</section>
|
|
108
|
+
))}
|
|
109
|
+
</section>
|
|
110
|
+
))
|
|
111
|
+
}
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<style>
|
|
115
|
+
.float-index {
|
|
116
|
+
display: grid;
|
|
117
|
+
gap: 1.75rem;
|
|
118
|
+
margin-block-start: 1.5rem;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.float-index__part,
|
|
122
|
+
.float-index__chapter {
|
|
123
|
+
display: grid;
|
|
124
|
+
gap: 0.4rem;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.float-index__part :global(h2),
|
|
128
|
+
.float-index__part :global(h3) {
|
|
129
|
+
margin: 0;
|
|
130
|
+
font-size: var(--sl-text-lg);
|
|
131
|
+
line-height: 1.3;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.float-index__chapter :global(h3),
|
|
135
|
+
.float-index__chapter :global(h4) {
|
|
136
|
+
margin: 0.5rem 0 0;
|
|
137
|
+
font-size: var(--sl-text-base);
|
|
138
|
+
line-height: 1.4;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.float-index__chapter-number {
|
|
142
|
+
color: var(--sl-color-gray-3);
|
|
143
|
+
font-variant-numeric: tabular-nums;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* `not-content` drops Starlight's link colour with the rest of its prose
|
|
147
|
+
styles; the index keeps the accent for its links. */
|
|
148
|
+
.float-index__chapter a {
|
|
149
|
+
color: var(--sl-color-text-accent);
|
|
150
|
+
text-decoration: none;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.float-index__chapter a:hover,
|
|
154
|
+
.float-index__chapter a:focus-visible {
|
|
155
|
+
text-decoration: underline;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/* One row per float, as a list of figures sets it: the number in a column
|
|
159
|
+
of its own, the name beside it. */
|
|
160
|
+
.float-index__list {
|
|
161
|
+
display: grid;
|
|
162
|
+
gap: 0;
|
|
163
|
+
margin: 0;
|
|
164
|
+
padding: 0;
|
|
165
|
+
list-style: none;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.float-index__list li {
|
|
169
|
+
display: grid;
|
|
170
|
+
grid-template-columns: 8.5rem minmax(0, 1fr);
|
|
171
|
+
gap: 0 0.75rem;
|
|
172
|
+
margin: 0;
|
|
173
|
+
padding-block: 0.35rem;
|
|
174
|
+
border-block-start: 1px solid var(--sl-color-gray-6);
|
|
175
|
+
line-height: 1.45;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.float-index__number {
|
|
179
|
+
font-weight: 600;
|
|
180
|
+
font-variant-numeric: tabular-nums;
|
|
181
|
+
white-space: nowrap;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/* A float with no short caption is listed by its long one, muted: the
|
|
185
|
+
name to give it is still to be written. */
|
|
186
|
+
.float-index__name--long {
|
|
187
|
+
color: var(--sl-color-gray-3);
|
|
188
|
+
font-size: var(--sl-text-sm);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.float-index__empty {
|
|
192
|
+
color: var(--sl-color-gray-3);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
@media (max-width: 30rem) {
|
|
196
|
+
.float-index__list li {
|
|
197
|
+
grid-template-columns: minmax(0, 1fr);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
</style>
|