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
|
@@ -9,9 +9,12 @@
|
|
|
9
9
|
* names with `\explain{key}{latex}` — not the registry-wide table a card's
|
|
10
10
|
* body math uses.
|
|
11
11
|
*
|
|
12
|
-
* The
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* The symbol list under the formula is not decoration: it is the "where …"
|
|
13
|
+
* clause a textbook prints after an equation, every symbol the formula names
|
|
14
|
+
* in the order it names them — the cards it wraps with `\explain`, each
|
|
15
|
+
* linked to its own entry where the page has one, and the glosses, whose
|
|
16
|
+
* names live only here. With JavaScript off, and in the exports, this is
|
|
17
|
+
* the whole explanation; the hover panel is enhancement over it.
|
|
15
18
|
*/
|
|
16
19
|
import NotationMath from './NotationMath.astro';
|
|
17
20
|
import SafeMathNode from './SafeMathNode.astro';
|
|
@@ -25,10 +28,22 @@ import {
|
|
|
25
28
|
registerPageNotationDependencies,
|
|
26
29
|
} from '../../reference/page-notation';
|
|
27
30
|
|
|
31
|
+
/** A card the formula names with `\explain{key}{…}`, as the symbol list shows it. */
|
|
32
|
+
export interface FormulaCard {
|
|
33
|
+
readonly key: string;
|
|
34
|
+
readonly latex: string;
|
|
35
|
+
readonly label: string;
|
|
36
|
+
readonly units?: string;
|
|
37
|
+
/** Where the card's own entry is, when the page has one (`#notation-<key>` on the glossary). */
|
|
38
|
+
readonly href?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
28
41
|
export interface Props {
|
|
29
42
|
formula: string;
|
|
30
43
|
scope: readonly GlyphScopeEntry[];
|
|
31
44
|
glosses?: readonly ResolvedGloss[];
|
|
45
|
+
/** The cards the formula names, in any order; listed with the glosses in formula order. */
|
|
46
|
+
cards?: readonly FormulaCard[];
|
|
32
47
|
/** Heading for the gloss list; omitted renders the list unlabelled. */
|
|
33
48
|
glossLabel?: string;
|
|
34
49
|
/** The owning entry's display label, used to name the scrollable region. */
|
|
@@ -43,6 +58,7 @@ const {
|
|
|
43
58
|
formula,
|
|
44
59
|
scope,
|
|
45
60
|
glosses = [],
|
|
61
|
+
cards = [],
|
|
46
62
|
glossLabel = 'Symbols',
|
|
47
63
|
entryLabel,
|
|
48
64
|
number,
|
|
@@ -56,6 +72,41 @@ registerPageNotationDependencies(
|
|
|
56
72
|
);
|
|
57
73
|
const latex = stripNotationMarkers(formula);
|
|
58
74
|
const numbered = Boolean(number && numberHref);
|
|
75
|
+
// Formula order: a card sits where its `\explain{key}` is, a gloss where its
|
|
76
|
+
// glyph first appears; a symbol the text does not show sorts last.
|
|
77
|
+
const FAR = Number.MAX_SAFE_INTEGER;
|
|
78
|
+
const positionOf = (needle: string): number => {
|
|
79
|
+
const at = formula.indexOf(needle);
|
|
80
|
+
return at < 0 ? FAR : at;
|
|
81
|
+
};
|
|
82
|
+
interface ListedSymbol {
|
|
83
|
+
readonly kind: 'card' | 'gloss';
|
|
84
|
+
readonly key: string;
|
|
85
|
+
readonly latex: string;
|
|
86
|
+
readonly label: string;
|
|
87
|
+
readonly units?: string;
|
|
88
|
+
readonly href?: string;
|
|
89
|
+
readonly position: number;
|
|
90
|
+
}
|
|
91
|
+
const symbols: ListedSymbol[] = [
|
|
92
|
+
...cards.map((card) => ({
|
|
93
|
+
kind: 'card' as const,
|
|
94
|
+
key: card.key,
|
|
95
|
+
latex: card.latex,
|
|
96
|
+
label: card.label,
|
|
97
|
+
...(card.units === undefined ? {} : { units: card.units }),
|
|
98
|
+
...(card.href === undefined ? {} : { href: card.href }),
|
|
99
|
+
position: positionOf(`\\explain{${card.key}}`),
|
|
100
|
+
})),
|
|
101
|
+
...glosses.map((gloss) => ({
|
|
102
|
+
kind: 'gloss' as const,
|
|
103
|
+
key: gloss.key,
|
|
104
|
+
latex: gloss.notation,
|
|
105
|
+
label: gloss.label,
|
|
106
|
+
...(gloss.units === undefined ? {} : { units: gloss.units }),
|
|
107
|
+
position: positionOf(gloss.notation),
|
|
108
|
+
})),
|
|
109
|
+
].sort((a, b) => a.position - b.position || a.key.localeCompare(b.key));
|
|
59
110
|
// The copy tooltip names what it copies, the way a lesson equation's does
|
|
60
111
|
// with its `\caption{}`: here the owning entry's label.
|
|
61
112
|
const copyTitle = entryLabel ? `Copy LaTeX — ${entryLabel}` : 'Copy LaTeX';
|
|
@@ -104,21 +155,29 @@ const copyTitle = entryLabel ? `Copy LaTeX — ${entryLabel}` : 'Copy LaTeX';
|
|
|
104
155
|
</div>
|
|
105
156
|
|
|
106
157
|
{
|
|
107
|
-
|
|
158
|
+
symbols.length > 0 && (
|
|
108
159
|
<dl class="notation-gloss-list" data-notation-gloss-list>
|
|
109
160
|
{glossLabel && <dt class="notation-gloss-label">{glossLabel}</dt>}
|
|
110
|
-
{
|
|
111
|
-
<dd
|
|
161
|
+
{symbols.map((symbol) => (
|
|
162
|
+
<dd
|
|
163
|
+
class="notation-gloss"
|
|
164
|
+
data-notation-gloss={symbol.key}
|
|
165
|
+
data-notation-card={symbol.kind === 'card' ? '' : undefined}
|
|
166
|
+
>
|
|
112
167
|
<span
|
|
113
168
|
class="notation-gloss-symbol"
|
|
114
169
|
data-notation-symbol-trigger
|
|
115
|
-
data-notation-key={
|
|
170
|
+
data-notation-key={symbol.key}
|
|
116
171
|
>
|
|
117
|
-
<NotationMath class="notation-symbol" latex={
|
|
172
|
+
<NotationMath class="notation-symbol" latex={symbol.latex} />
|
|
118
173
|
</span>
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
174
|
+
{symbol.href ? (
|
|
175
|
+
<a class="notation-gloss-name" href={symbol.href}>{symbol.label}</a>
|
|
176
|
+
) : (
|
|
177
|
+
<span class="notation-gloss-name">{symbol.label}</span>
|
|
178
|
+
)}
|
|
179
|
+
{symbol.units && (
|
|
180
|
+
<span class="notation-gloss-units">({symbol.units})</span>
|
|
122
181
|
)}
|
|
123
182
|
</dd>
|
|
124
183
|
))}
|
|
@@ -347,6 +406,17 @@ const copyTitle = entryLabel ? `Copy LaTeX — ${entryLabel}` : 'Copy LaTeX';
|
|
|
347
406
|
color: var(--sl-color-gray-3);
|
|
348
407
|
}
|
|
349
408
|
|
|
409
|
+
a.notation-gloss-name {
|
|
410
|
+
color: inherit;
|
|
411
|
+
text-decoration: underline dotted;
|
|
412
|
+
text-underline-offset: 0.15em;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
a.notation-gloss-name:hover,
|
|
416
|
+
a.notation-gloss-name:focus-visible {
|
|
417
|
+
text-decoration-style: solid;
|
|
418
|
+
}
|
|
419
|
+
|
|
350
420
|
.notation-gloss-units {
|
|
351
421
|
color: var(--sl-color-gray-3);
|
|
352
422
|
}
|
|
@@ -6,7 +6,8 @@ import NotationLayer, {
|
|
|
6
6
|
type NotationLayerDefinition,
|
|
7
7
|
} from './NotationLayer.astro';
|
|
8
8
|
import { resolveLabel } from '../../reference/label';
|
|
9
|
-
import { formulaGlyphScope, resolveGlosses } from '../../reference/gloss';
|
|
9
|
+
import { formulaExplainKeys, formulaGlyphScope, resolveGlosses } from '../../reference/gloss';
|
|
10
|
+
import type { FormulaCard } from './NotationFormula.astro';
|
|
10
11
|
import type { GlyphScopeEntry, ResolvedGloss } from '../../reference/gloss';
|
|
11
12
|
import {
|
|
12
13
|
isLessonSlug,
|
|
@@ -74,6 +75,74 @@ const glossesByKey = new Map<string, ResolvedGloss[]>(
|
|
|
74
75
|
resolveGlosses(entry.data.key, entry.data.glosses),
|
|
75
76
|
]),
|
|
76
77
|
);
|
|
78
|
+
// The cards a formula names, for its symbol list: label and units from
|
|
79
|
+
// their own entries, each linked to its entry on this page.
|
|
80
|
+
const entryByKey = new Map(notationEntries.map((entry) => [entry.data.key, entry]));
|
|
81
|
+
const formulaCardsByKey = new Map<string, FormulaCard[]>(
|
|
82
|
+
notationEntries.map((entry) => [
|
|
83
|
+
entry.data.key,
|
|
84
|
+
[...new Set(formulaExplainKeys(formulaOf(entry.data) ?? ''))].flatMap((key) => {
|
|
85
|
+
const named = entryByKey.get(key);
|
|
86
|
+
if (!named || key === entry.data.key) return [];
|
|
87
|
+
return [
|
|
88
|
+
{
|
|
89
|
+
key,
|
|
90
|
+
latex: named.data.latex,
|
|
91
|
+
label: labelOf(named.data),
|
|
92
|
+
...(named.data.units === undefined ? {} : { units: named.data.units }),
|
|
93
|
+
href: `#notation-${key}`,
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
}),
|
|
97
|
+
]),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// What a single page defines for itself, and what a formula glosses: listed
|
|
101
|
+
// after the index so a reader who looks a letter up always finds it, and sees
|
|
102
|
+
// at a glance which meanings are course-wide and which are page-bound.
|
|
103
|
+
const lessonHref = (lessonId: string): { href: string; title: string } | undefined => {
|
|
104
|
+
const lesson = lessonById.get(lessonId);
|
|
105
|
+
if (!lesson) return undefined;
|
|
106
|
+
return {
|
|
107
|
+
href: `${import.meta.env.BASE_URL.replace(/\/$/, '')}/${lesson.id}/`,
|
|
108
|
+
title: String(lesson.data.title ?? lesson.id),
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
interface PageMeaning {
|
|
112
|
+
readonly latex: string;
|
|
113
|
+
readonly label: string;
|
|
114
|
+
readonly units?: string;
|
|
115
|
+
readonly where: 'page' | 'formula';
|
|
116
|
+
readonly href: string;
|
|
117
|
+
readonly title: string;
|
|
118
|
+
}
|
|
119
|
+
const pageMeanings: PageMeaning[] = [
|
|
120
|
+
...manifest.notation.definitions.flatMap((definition) => {
|
|
121
|
+
if (definition.kind !== 'local') return [];
|
|
122
|
+
const at = lessonHref(definition.lessonId);
|
|
123
|
+
if (!at) return [];
|
|
124
|
+
return [
|
|
125
|
+
{
|
|
126
|
+
latex: definition.notation,
|
|
127
|
+
label: definition.label,
|
|
128
|
+
...(definition.units === undefined ? {} : { units: definition.units }),
|
|
129
|
+
where: 'page' as const,
|
|
130
|
+
...at,
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
}),
|
|
134
|
+
...notationEntries.flatMap((entry) =>
|
|
135
|
+
(glossesByKey.get(entry.data.key) ?? []).map((gloss) => ({
|
|
136
|
+
latex: gloss.notation,
|
|
137
|
+
label: gloss.label,
|
|
138
|
+
...(gloss.units === undefined ? {} : { units: gloss.units }),
|
|
139
|
+
where: 'formula' as const,
|
|
140
|
+
href: `#notation-${entry.data.key}`,
|
|
141
|
+
title: labelOf(entry.data),
|
|
142
|
+
})),
|
|
143
|
+
),
|
|
144
|
+
].sort((a, b) => a.latex.localeCompare(b.latex) || a.label.localeCompare(b.label) || a.title.localeCompare(b.title));
|
|
145
|
+
|
|
77
146
|
const formulaScopeByKey = new Map<string, GlyphScopeEntry[]>(
|
|
78
147
|
notationEntries.map((entry) => [
|
|
79
148
|
entry.data.key,
|
|
@@ -87,6 +156,21 @@ const formulaScopeByKey = new Map<string, GlyphScopeEntry[]>(
|
|
|
87
156
|
]),
|
|
88
157
|
);
|
|
89
158
|
|
|
159
|
+
// Astro's content layer logs a body that fails to render (`[ERROR]
|
|
160
|
+
// [glob-loader]`) and keeps the entry without it, so the glossary would ship
|
|
161
|
+
// the entry with no text and the build would end green. `validate:content`
|
|
162
|
+
// reports the usual causes first; this stops the build on any of them.
|
|
163
|
+
const unrendered = notationEntries.filter(
|
|
164
|
+
(entry) => entry.body?.trim() && entry.rendered === undefined,
|
|
165
|
+
);
|
|
166
|
+
if (unrendered.length > 0) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`Notation entries whose body did not render: ${unrendered
|
|
169
|
+
.map((entry) => entry.filePath ?? entry.id)
|
|
170
|
+
.join(', ')}. The [glob-loader] error above each says why; run validate:content for the file's own line.`,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
90
174
|
const renderedEntries = await Promise.all(
|
|
91
175
|
notationEntries
|
|
92
176
|
.sort(
|
|
@@ -151,6 +235,31 @@ const panelDefinitions: NotationLayerDefinition[] = notationEntries.map(
|
|
|
151
235
|
</ul>
|
|
152
236
|
</nav>
|
|
153
237
|
|
|
238
|
+
{
|
|
239
|
+
pageMeanings.length > 0 && (
|
|
240
|
+
<section class="glossary-page-meanings" aria-labelledby="glossary-page-meanings-heading">
|
|
241
|
+
<h2 id="glossary-page-meanings-heading">On one page only</h2>
|
|
242
|
+
<p class="glossary-intro">
|
|
243
|
+
Symbols a single chapter defines for itself, or a formula names for its own use. The
|
|
244
|
+
letter may mean something else elsewhere; the page is where its card is.
|
|
245
|
+
</p>
|
|
246
|
+
<ul>
|
|
247
|
+
{pageMeanings.map((meaning) => (
|
|
248
|
+
<li>
|
|
249
|
+
<NotationMath class="glossary-symbol" latex={meaning.latex} />
|
|
250
|
+
<span class="glossary-page-meaning-text">
|
|
251
|
+
{' '}— {meaning.label}
|
|
252
|
+
{meaning.units && <span class="glossary-page-meaning-units"> ({meaning.units})</span>},{' '}
|
|
253
|
+
{meaning.where === 'page' ? 'on' : 'in the formula of'}{' '}
|
|
254
|
+
<a href={meaning.href}>{meaning.title}</a>
|
|
255
|
+
</span>
|
|
256
|
+
</li>
|
|
257
|
+
))}
|
|
258
|
+
</ul>
|
|
259
|
+
</section>
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
|
|
154
263
|
<div class="notation-glossary">
|
|
155
264
|
{
|
|
156
265
|
renderedEntries.map(({ entry, Content }) => {
|
|
@@ -241,6 +350,7 @@ const panelDefinitions: NotationLayerDefinition[] = notationEntries.map(
|
|
|
241
350
|
formula={formulaOf(entry.data) ?? ''}
|
|
242
351
|
scope={formulaScopeByKey.get(entry.data.key) ?? []}
|
|
243
352
|
glosses={glossesByKey.get(entry.data.key)}
|
|
353
|
+
cards={formulaCardsByKey.get(entry.data.key)}
|
|
244
354
|
entryLabel={labelOf(entry.data)}
|
|
245
355
|
number={equationNumberByKey.get(entry.data.key)}
|
|
246
356
|
numberHref={`#notation-${entry.data.key}`}
|
|
@@ -323,6 +433,27 @@ const panelDefinitions: NotationLayerDefinition[] = notationEntries.map(
|
|
|
323
433
|
padding-inline-start: 1.25rem;
|
|
324
434
|
}
|
|
325
435
|
|
|
436
|
+
.glossary-page-meanings {
|
|
437
|
+
margin-block: 1.5rem 2rem;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.glossary-page-meanings h2 {
|
|
441
|
+
font-size: var(--sl-text-h4);
|
|
442
|
+
margin-block-end: 0.25rem;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.glossary-page-meanings ul {
|
|
446
|
+
display: grid;
|
|
447
|
+
grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
|
|
448
|
+
gap: 0.3rem 1.5rem;
|
|
449
|
+
padding-inline-start: 1.25rem;
|
|
450
|
+
font-size: var(--sl-text-sm);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.glossary-page-meaning-units {
|
|
454
|
+
color: var(--sl-color-gray-3);
|
|
455
|
+
}
|
|
456
|
+
|
|
326
457
|
.notation-glossary {
|
|
327
458
|
display: grid;
|
|
328
459
|
gap: 1.5rem;
|
|
@@ -1141,21 +1141,29 @@ for (const gloss of glossItems) {
|
|
|
1141
1141
|
cursor: help;
|
|
1142
1142
|
}
|
|
1143
1143
|
|
|
1144
|
-
/* The highlight blends with what is behind it instead of covering it.
|
|
1145
|
-
KaTeX lays a formula out as boxes that paint in source order, so an
|
|
1146
|
-
opaque background (and its ring) on one symbol is painted over the glyph
|
|
1147
|
-
to its left — hovering the y of `dy` hid part of the d. `multiply` (light
|
|
1148
|
-
theme) and `screen` (dark) tint the page and leave ink of either theme as
|
|
1149
|
-
it was. */
|
|
1150
1144
|
:global(.katex-html [data-notation-key]:hover),
|
|
1151
1145
|
:global([data-notation-key].is-notation-active) {
|
|
1152
1146
|
background: var(--sl-color-accent-low);
|
|
1153
1147
|
box-shadow: 0 0 0 0.12em var(--sl-color-accent-low);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
/* In math the highlight blends with what is behind it instead of covering
|
|
1151
|
+
it. KaTeX lays a formula out as boxes that paint in source order, so an
|
|
1152
|
+
opaque background (and its ring) on one symbol is painted over the glyph
|
|
1153
|
+
to its left — hovering the y of `dy` hid part of the d. `multiply` (light
|
|
1154
|
+
theme) and `screen` (dark) tint the page and leave ink of either theme as
|
|
1155
|
+
it was. Only math: a prose link (`a.notation-term`) is one box with its
|
|
1156
|
+
own text in it, and blending its tint with whatever sits behind it (a
|
|
1157
|
+
glossary row's panel) changes the ground its text is read on — in the
|
|
1158
|
+
light theme the engine's own link colour fell from 4.75:1 on the opaque
|
|
1159
|
+
tint to 4.4:1, under AA. */
|
|
1160
|
+
:global(.katex-html [data-notation-key]:hover),
|
|
1161
|
+
:global(.katex-html [data-notation-key].is-notation-active) {
|
|
1154
1162
|
mix-blend-mode: screen;
|
|
1155
1163
|
}
|
|
1156
1164
|
|
|
1157
1165
|
:global(:root[data-theme='light'] .katex-html [data-notation-key]:hover),
|
|
1158
|
-
:global(:root[data-theme='light'] [data-notation-key].is-notation-active) {
|
|
1166
|
+
:global(:root[data-theme='light'] .katex-html [data-notation-key].is-notation-active) {
|
|
1159
1167
|
mix-blend-mode: multiply;
|
|
1160
1168
|
}
|
|
1161
1169
|
|
|
@@ -13,21 +13,49 @@
|
|
|
13
13
|
* `aria-expanded` for free; the inline script only adds Escape-to-close and
|
|
14
14
|
* outside-click-to-close.
|
|
15
15
|
*
|
|
16
|
+
* On a chapter or a part's page it adds the page's own version (rule C5,
|
|
17
|
+
* `lessons[].since` / `parts[].overview.since`): "unchanged since version
|
|
18
|
+
* 0.2.0", or "with changes after version 0.4.0, not yet released" — the line
|
|
19
|
+
* the page's PDF and Markdown print under their title.
|
|
20
|
+
*
|
|
16
21
|
* The panel also *publishes* the identity it displays: the `data-knowledge-*`
|
|
17
|
-
* and `data-engine-version` attributes
|
|
18
|
-
*
|
|
22
|
+
* and `data-engine-version` attributes (and `data-page-since` on the page's
|
|
23
|
+
* line, `unreleased` for a change not yet released) carry the same values as
|
|
24
|
+
* the rendered text. A build check that wants to confirm the shipped page really carries the
|
|
19
25
|
* configured version would otherwise have to match the panel's prose and its
|
|
20
26
|
* `version · date` join, which pins a course's test to one version string and
|
|
21
27
|
* makes the check fail on every bump; a bare version substring is not even
|
|
22
28
|
* scoped to the panel, so the engine's own version elsewhere on the page can
|
|
23
29
|
* satisfy it. These attributes are exact, scoped, and stable across a bump.
|
|
24
30
|
*/
|
|
31
|
+
import { docsSlugsForRoute } from '../../compiler/lesson-derivation';
|
|
25
32
|
import { loadManifest } from '../../compiler/manifest';
|
|
33
|
+
import { versionPhrase } from '../../compiler/page-versions';
|
|
26
34
|
|
|
27
|
-
const
|
|
35
|
+
const manifest = await loadManifest();
|
|
36
|
+
const { about } = manifest;
|
|
28
37
|
const { engine, material, knowledge, authors, disclaimer, changelogHref } =
|
|
29
38
|
about;
|
|
30
39
|
|
|
40
|
+
// The page's own version (rule C5): a chapter's or a part page's, so a reader
|
|
41
|
+
// can tell a downloaded PDF's "unchanged since version 0.2.0" against the
|
|
42
|
+
// live page. Other pages, and a course with the rule off, show no such line.
|
|
43
|
+
const routeSlugs = docsSlugsForRoute(Astro.locals.starlightRoute.entry.id);
|
|
44
|
+
const lesson = manifest.lessons.find((entry) => routeSlugs.includes(entry.slug));
|
|
45
|
+
const overview = lesson
|
|
46
|
+
? undefined
|
|
47
|
+
: manifest.parts.find((entry) => routeSlugs.includes(`${entry.dir}/index`))
|
|
48
|
+
?.overview;
|
|
49
|
+
const pageSince = lesson ? lesson.since : overview ? overview.since : undefined;
|
|
50
|
+
const pageLine =
|
|
51
|
+
manifest.pageVersions.enabled && pageSince !== undefined
|
|
52
|
+
? versionPhrase(pageSince, knowledge.version)
|
|
53
|
+
: undefined;
|
|
54
|
+
|
|
55
|
+
// What this course keeps in the reader's browser, and their say over it
|
|
56
|
+
// (`session/reading-memory.ts`): the reading-memory answer, and forgetting.
|
|
57
|
+
const readingMemory = manifest.readingMemory ?? 'ask';
|
|
58
|
+
|
|
31
59
|
/** `mailto:` an email, pass through an http(s) URL, plain text otherwise. */
|
|
32
60
|
function contactHref(contact: string): string | undefined {
|
|
33
61
|
if (/^https?:\/\//i.test(contact)) return contact;
|
|
@@ -64,6 +92,14 @@ const knowledgeLine = knowledge.date
|
|
|
64
92
|
<dt>Knowledge version</dt>
|
|
65
93
|
<dd data-knowledge-line>{knowledgeLine}</dd>
|
|
66
94
|
</div>
|
|
95
|
+
{
|
|
96
|
+
pageLine && (
|
|
97
|
+
<div>
|
|
98
|
+
<dt>This page</dt>
|
|
99
|
+
<dd data-page-since={pageSince ?? 'unreleased'}>{pageLine}</dd>
|
|
100
|
+
</div>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
67
103
|
<div>
|
|
68
104
|
<dt>Engine</dt>
|
|
69
105
|
<dd>
|
|
@@ -123,6 +159,31 @@ const knowledgeLine = knowledge.date
|
|
|
123
159
|
</p>
|
|
124
160
|
)
|
|
125
161
|
}
|
|
162
|
+
|
|
163
|
+
<div class="about-browser" data-about-browser>
|
|
164
|
+
<p class="about-browser__title">In this browser</p>
|
|
165
|
+
<p>No cookies. Nothing this course keeps leaves your browser.</p>
|
|
166
|
+
{
|
|
167
|
+
readingMemory === 'ask' ? (
|
|
168
|
+
<label class="about-browser__switch">
|
|
169
|
+
<input type="checkbox" data-reading-memory-toggle />
|
|
170
|
+
<span>
|
|
171
|
+
Remember my reading: the chapters I open and the cards I keep open
|
|
172
|
+
</span>
|
|
173
|
+
</label>
|
|
174
|
+
) : (
|
|
175
|
+
<p>This course keeps nothing of your reading beyond the tab.</p>
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
<p class="about-browser__hint">
|
|
179
|
+
Settings you choose, such as the theme and the text size, are kept
|
|
180
|
+
either way.
|
|
181
|
+
</p>
|
|
182
|
+
<button type="button" class="about-browser__forget" data-forget-course>
|
|
183
|
+
Forget what this course stored
|
|
184
|
+
</button>
|
|
185
|
+
<p class="about-browser__status" role="status" data-forget-status></p>
|
|
186
|
+
</div>
|
|
126
187
|
</div>
|
|
127
188
|
</details>
|
|
128
189
|
|
|
@@ -190,6 +251,22 @@ const knowledgeLine = knowledge.date
|
|
|
190
251
|
line-height: 1.5;
|
|
191
252
|
}
|
|
192
253
|
|
|
254
|
+
/* Below 50rem the visible copy is the one in the phone menu. There the
|
|
255
|
+
toggle starts its row, at the start of the screen, and the panel hung
|
|
256
|
+
from the toggle's end ran off the screen's start edge and past the
|
|
257
|
+
menu's end. So it is a sheet fixed to the window instead, centred
|
|
258
|
+
under the header; the toggle's place in the menu no longer matters. */
|
|
259
|
+
@media not all and (min-width: 50rem) {
|
|
260
|
+
.about-body {
|
|
261
|
+
position: fixed;
|
|
262
|
+
inset-inline: 0.75rem;
|
|
263
|
+
top: calc(var(--sl-nav-height) + 0.5rem);
|
|
264
|
+
width: auto;
|
|
265
|
+
max-width: 21rem;
|
|
266
|
+
margin-inline: auto;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
193
270
|
.about-name {
|
|
194
271
|
margin: 0;
|
|
195
272
|
font-size: var(--sl-text-sm);
|
|
@@ -240,9 +317,115 @@ const knowledgeLine = knowledge.date
|
|
|
240
317
|
.about-body a {
|
|
241
318
|
color: var(--sl-color-text-accent);
|
|
242
319
|
}
|
|
320
|
+
|
|
321
|
+
.about-browser {
|
|
322
|
+
margin: 0.85rem 0 0;
|
|
323
|
+
padding-top: 0.75rem;
|
|
324
|
+
border-top: 1px solid var(--sl-color-gray-6);
|
|
325
|
+
display: grid;
|
|
326
|
+
gap: 0.4rem;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.about-browser p {
|
|
330
|
+
margin: 0;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.about-browser__title {
|
|
334
|
+
color: var(--sl-color-gray-3);
|
|
335
|
+
text-transform: uppercase;
|
|
336
|
+
letter-spacing: 0.03em;
|
|
337
|
+
font-size: 0.65rem;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.about-browser__switch {
|
|
341
|
+
display: flex;
|
|
342
|
+
align-items: flex-start;
|
|
343
|
+
gap: 0.45rem;
|
|
344
|
+
cursor: pointer;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.about-browser__switch input {
|
|
348
|
+
margin: 0.15rem 0 0;
|
|
349
|
+
accent-color: var(--sl-color-accent);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.about-browser__hint {
|
|
353
|
+
color: var(--sl-color-gray-2);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.about-browser__forget {
|
|
357
|
+
justify-self: start;
|
|
358
|
+
border: 1px solid var(--sl-color-gray-5);
|
|
359
|
+
border-radius: 0.3rem;
|
|
360
|
+
padding: 0.2rem 0.55rem;
|
|
361
|
+
color: var(--sl-color-text);
|
|
362
|
+
background: none;
|
|
363
|
+
font: inherit;
|
|
364
|
+
cursor: pointer;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.about-browser__forget:hover,
|
|
368
|
+
.about-browser__forget:focus-visible {
|
|
369
|
+
border-color: var(--sl-color-accent);
|
|
370
|
+
background: var(--sl-color-accent-low);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.about-browser__status:empty {
|
|
374
|
+
display: none;
|
|
375
|
+
}
|
|
243
376
|
}
|
|
244
377
|
</style>
|
|
245
378
|
|
|
379
|
+
<script>
|
|
380
|
+
/**
|
|
381
|
+
* The reader's say over what the course keeps in their browser: the
|
|
382
|
+
* reading-memory answer, as a switch every copy of the panel shows the
|
|
383
|
+
* same, and "Forget what this course stored", after which the course asks
|
|
384
|
+
* again as on a first visit.
|
|
385
|
+
*/
|
|
386
|
+
import {
|
|
387
|
+
answerReadingMemory,
|
|
388
|
+
forgetCourse,
|
|
389
|
+
readingAnswer,
|
|
390
|
+
READING_MEMORY_EVENT,
|
|
391
|
+
} from '../../session/reading-memory';
|
|
392
|
+
|
|
393
|
+
(() => {
|
|
394
|
+
const base =
|
|
395
|
+
document.querySelector<HTMLElement>('[data-explico-reading-memory]')
|
|
396
|
+
?.dataset.explicoBase ?? '/';
|
|
397
|
+
const toggles = [
|
|
398
|
+
...document.querySelectorAll<HTMLInputElement>(
|
|
399
|
+
'[data-reading-memory-toggle]',
|
|
400
|
+
),
|
|
401
|
+
];
|
|
402
|
+
const sync = () => {
|
|
403
|
+
const yes = readingAnswer(base) === 'yes';
|
|
404
|
+
for (const toggle of toggles) toggle.checked = yes;
|
|
405
|
+
};
|
|
406
|
+
for (const toggle of toggles) {
|
|
407
|
+
toggle.addEventListener('change', () => {
|
|
408
|
+
answerReadingMemory(base, toggle.checked);
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
for (const button of document.querySelectorAll<HTMLButtonElement>(
|
|
412
|
+
'[data-forget-course]',
|
|
413
|
+
)) {
|
|
414
|
+
button.addEventListener('click', () => {
|
|
415
|
+
forgetCourse(base);
|
|
416
|
+
for (const status of document.querySelectorAll<HTMLElement>(
|
|
417
|
+
'[data-forget-status]',
|
|
418
|
+
)) {
|
|
419
|
+
status.textContent =
|
|
420
|
+
'Forgotten: this course keeps nothing of yours in this browser now.';
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
window.addEventListener(READING_MEMORY_EVENT, sync);
|
|
425
|
+
sync();
|
|
426
|
+
})();
|
|
427
|
+
</script>
|
|
428
|
+
|
|
246
429
|
<script>
|
|
247
430
|
(() => {
|
|
248
431
|
const panels = Array.from(
|
|
@@ -62,9 +62,11 @@
|
|
|
62
62
|
* outright, a rule no CSS can override — confirmed the hard way while
|
|
63
63
|
* building the print handling below), so `peeking` still has to force
|
|
64
64
|
* `details.open = true` to render anything; only `data-state` distinguishes
|
|
65
|
-
* a real expand from a peek.
|
|
66
|
-
*
|
|
67
|
-
*
|
|
65
|
+
* a real expand from a peek. A peek never persists anything: it carries
|
|
66
|
+
* `data-explico-temporary-open` from its first `open = true` until the
|
|
67
|
+
* `toggle` its closing fires, and `wirePersistedDetails`'s `toggle` listener
|
|
68
|
+
* skips an element that has it. Only a genuine commit saves, through
|
|
69
|
+
* `savePersistedOpenState`.
|
|
68
70
|
*
|
|
69
71
|
* Persistence goes through `../disclosure/persisted-disclosure` (keyed by
|
|
70
72
|
* heading `id` + page path, same convention as `Figure.astro`'s
|
|
@@ -369,6 +371,17 @@
|
|
|
369
371
|
});
|
|
370
372
|
};
|
|
371
373
|
|
|
374
|
+
// A peek is no choice: it opens the real `<details>` to render, and that
|
|
375
|
+
// fires a native `toggle`, which `wirePersistedDetails` would save as
|
|
376
|
+
// "open". `persisted-disclosure.ts` skips a `toggle` while this marker
|
|
377
|
+
// is on the element, so the marker is on from the peek's first
|
|
378
|
+
// `open = true` until the `toggle` its closing fires has passed. Without
|
|
379
|
+
// it a reader who collapsed a section and reloaded with the pointer
|
|
380
|
+
// still over its title lost the choice for good: the browser sends the
|
|
381
|
+
// pointer's `pointerenter` after the reload, the peek opened, and
|
|
382
|
+
// "open" was stored over "closed".
|
|
383
|
+
const TEMPORARY_OPEN = 'data-explico-temporary-open';
|
|
384
|
+
|
|
372
385
|
// Promote an in-progress peek to a real, persisted expand — grown
|
|
373
386
|
// from the peek's *current* rendered height, not from zero. The
|
|
374
387
|
// `position: absolute` → `static` switch happens the instant before
|
|
@@ -378,6 +391,7 @@
|
|
|
378
391
|
// animation completes.
|
|
379
392
|
const commitFromPeek = () => {
|
|
380
393
|
const peekPx = body.getBoundingClientRect().height;
|
|
394
|
+
details.removeAttribute(TEMPORARY_OPEN); // now a choice
|
|
381
395
|
body.style.transition = 'none';
|
|
382
396
|
body.style.maxHeight = `${peekPx}px`;
|
|
383
397
|
void body.offsetHeight; // reflow with the peek height frozen…
|
|
@@ -393,6 +407,7 @@
|
|
|
393
407
|
|
|
394
408
|
const enterPeek = () => {
|
|
395
409
|
if (details.dataset.state !== 'closed') return;
|
|
410
|
+
details.setAttribute(TEMPORARY_OPEN, '');
|
|
396
411
|
details.open = true; // render — see the module comment
|
|
397
412
|
details.dataset.state = 'peeking' satisfies State;
|
|
398
413
|
growTo(body, Math.min(body.scrollHeight, peekMaxPx(body)), () => {});
|
|
@@ -402,6 +417,12 @@
|
|
|
402
417
|
if (details.dataset.state !== 'peeking') return;
|
|
403
418
|
growTo(body, 0, () => {
|
|
404
419
|
details.dataset.state = 'closed' satisfies State;
|
|
420
|
+
// `toggle` is queued, not synchronous: the marker goes when it fires.
|
|
421
|
+
details.addEventListener(
|
|
422
|
+
'toggle',
|
|
423
|
+
() => details.removeAttribute(TEMPORARY_OPEN),
|
|
424
|
+
{ once: true },
|
|
425
|
+
);
|
|
405
426
|
details.open = false;
|
|
406
427
|
});
|
|
407
428
|
};
|
|
@@ -216,7 +216,11 @@
|
|
|
216
216
|
if (active?.figure === figure) collapseExpansion();
|
|
217
217
|
else expand(figure, button, scroll, evaluate);
|
|
218
218
|
});
|
|
219
|
-
new ResizeObserver(evaluate)
|
|
219
|
+
const resizes = new ResizeObserver(evaluate);
|
|
220
|
+
resizes.observe(scroll);
|
|
221
|
+
// The text-size menu resizes the drawing without resizing its scroller.
|
|
222
|
+
const frame = scroll.querySelector('.diagram-frame');
|
|
223
|
+
if (frame) resizes.observe(frame);
|
|
220
224
|
updateExpandButton(figure, button, false);
|
|
221
225
|
evaluate();
|
|
222
226
|
}
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
<script>
|
|
58
58
|
import { HEADINGS, sectionParts } from './section-parts';
|
|
59
59
|
import { animatedScrollIntoView } from '../scroll-animation';
|
|
60
|
-
import { floatOf } from '
|
|
60
|
+
import { floatOf } from '../../reference/float-name';
|
|
61
61
|
import { readableText } from '../readable-text';
|
|
62
62
|
|
|
63
63
|
(() => {
|