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,6 +9,13 @@
|
|
|
9
9
|
* each part heading linking its overview page where one exists, and study
|
|
10
10
|
* times totalled per part and for the course when every chapter states one.
|
|
11
11
|
*
|
|
12
|
+
* When the course has a deck, each title starts with the glyph of what it
|
|
13
|
+
* opens (a chapter's sheet, a slides-only lesson's frame), a key and a deck
|
|
14
|
+
* count join the counts, and a slides column ends every row: a lesson's own
|
|
15
|
+
* deck is a tile on its title line, a part's deck one bar along the chapters
|
|
16
|
+
* it covers (`deck-lane.ts`). It lists every edition whatever the sidebar's
|
|
17
|
+
* controls say: it answers "what is in the course".
|
|
18
|
+
*
|
|
12
19
|
* Usage, on a splash page:
|
|
13
20
|
*
|
|
14
21
|
* ```mdx
|
|
@@ -18,15 +25,21 @@
|
|
|
18
25
|
* ```
|
|
19
26
|
*/
|
|
20
27
|
import { loadManifest } from '../compiler/manifest';
|
|
28
|
+
import { contentsKey, deckLanes, laneLink, titleGlyph } from './deck-lane';
|
|
21
29
|
|
|
22
30
|
interface Props {
|
|
23
31
|
/** Heading level for each part's name. Defaults to `h2`. */
|
|
24
32
|
readonly headingLevel?: 2 | 3;
|
|
25
33
|
/** Set `false` to drop the per-chapter description column. */
|
|
26
34
|
readonly descriptions?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Set `false` to drop the slides column, the glyphs, the key and the deck
|
|
37
|
+
* count. They show only when the course has a deck.
|
|
38
|
+
*/
|
|
39
|
+
readonly slides?: boolean;
|
|
27
40
|
}
|
|
28
41
|
|
|
29
|
-
const { headingLevel = 2, descriptions = true } = Astro.props;
|
|
42
|
+
const { headingLevel = 2, descriptions = true, slides = true } = Astro.props;
|
|
30
43
|
const Heading = `h${headingLevel}` as 'h2' | 'h3';
|
|
31
44
|
|
|
32
45
|
const manifest = await loadManifest();
|
|
@@ -44,7 +57,24 @@ const parts = manifest.parts
|
|
|
44
57
|
(lesson): lesson is NonNullable<typeof lesson> => lesson !== undefined,
|
|
45
58
|
),
|
|
46
59
|
}))
|
|
47
|
-
.filter((part) => part.chapters.length > 0)
|
|
60
|
+
.filter((part) => part.chapters.length > 0)
|
|
61
|
+
.map((part) => ({
|
|
62
|
+
...part,
|
|
63
|
+
lanes: slides ? deckLanes({ lessons: part.chapters, decks: manifest.decks, part }) : [],
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
// The glyphs, the key and the count say which lessons have slides: in a
|
|
67
|
+
// course with no deck they would explain one glyph, so the list stays as it
|
|
68
|
+
// was. The column is as wide as the part that needs most lanes, so it lines
|
|
69
|
+
// up across parts.
|
|
70
|
+
const key = slides ? contentsKey(manifest.lessons, manifest.decks) : [];
|
|
71
|
+
const glyphs = key.length > 0;
|
|
72
|
+
const laneCount = Math.max(0, ...parts.map((part) => part.lanes.length));
|
|
73
|
+
const keyLabel = {
|
|
74
|
+
chapter: 'Chapter',
|
|
75
|
+
deck: 'Slides',
|
|
76
|
+
'part-deck': 'Slides for a whole part',
|
|
77
|
+
} as const;
|
|
48
78
|
|
|
49
79
|
const chapterCount = parts.reduce(
|
|
50
80
|
(total, part) => total + part.chapters.length,
|
|
@@ -74,19 +104,42 @@ const hours = (range: { min: number; max: number }) => {
|
|
|
74
104
|
};
|
|
75
105
|
---
|
|
76
106
|
|
|
77
|
-
<div
|
|
107
|
+
<div
|
|
108
|
+
class="course-contents not-content"
|
|
109
|
+
data-deck-lanes={laneCount > 0 ? laneCount : undefined}
|
|
110
|
+
style={laneCount > 0 ? `--deck-lanes: ${laneCount}` : undefined}
|
|
111
|
+
>
|
|
78
112
|
<p class="course-meta">
|
|
79
113
|
<span>{parts.length} parts</span>
|
|
80
114
|
<span>{chapterCount} chapters</span>
|
|
115
|
+
{
|
|
116
|
+
glyphs && (
|
|
117
|
+
<span>
|
|
118
|
+
{manifest.decks.length} {manifest.decks.length === 1 ? 'slide deck' : 'slide decks'}
|
|
119
|
+
</span>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
81
122
|
{courseMinutes && <span>{hours(courseMinutes)} of study</span>}
|
|
82
123
|
</p>
|
|
124
|
+
{
|
|
125
|
+
glyphs && (
|
|
126
|
+
<ul class="contents-key" aria-label="Key">
|
|
127
|
+
{key.map((glyph) => (
|
|
128
|
+
<li>
|
|
129
|
+
<span class={`contents-glyph contents-glyph--${glyph}`} aria-hidden="true" />
|
|
130
|
+
{keyLabel[glyph]}
|
|
131
|
+
</li>
|
|
132
|
+
))}
|
|
133
|
+
</ul>
|
|
134
|
+
)
|
|
135
|
+
}
|
|
83
136
|
|
|
84
137
|
{
|
|
85
138
|
parts.map((part) => (
|
|
86
139
|
<section class="course-part">
|
|
87
140
|
<Heading id={`contents-${part.dir}`}>
|
|
88
141
|
{part.overview ? (
|
|
89
|
-
<a href={`${base}${part.link}`}>{part.label}</a>
|
|
142
|
+
<a href={`${base}${part.link}`}>{glyphs && <span class="contents-glyph contents-glyph--part" aria-hidden="true" />}{part.label}</a>
|
|
90
143
|
) : (
|
|
91
144
|
part.label
|
|
92
145
|
)}
|
|
@@ -99,13 +152,23 @@ const hours = (range: { min: number; max: number }) => {
|
|
|
99
152
|
{part.studyMinutes && <span>{minutes(part.studyMinutes)}</span>}
|
|
100
153
|
</p>
|
|
101
154
|
<ol aria-labelledby={`contents-${part.dir}`}>
|
|
102
|
-
{part.chapters.map((lesson) => (
|
|
155
|
+
{part.chapters.map((lesson, row) => (
|
|
103
156
|
<li>
|
|
104
|
-
<a href={`${base}/${lesson.slug}/`}>{lesson.title}</a>
|
|
157
|
+
<a href={`${base}/${lesson.slug}/`}>{glyphs && <span class={`contents-glyph contents-glyph--${titleGlyph(lesson)}`} aria-hidden="true" />}{lesson.title}</a>
|
|
105
158
|
{lesson.studyMinutes && (
|
|
106
159
|
<span class="chapter-time">{minutes(lesson.studyMinutes)}</span>
|
|
107
160
|
)}
|
|
108
161
|
{descriptions && <p>{lesson.description}</p>}
|
|
162
|
+
{part.lanes.map((lane, index) => {
|
|
163
|
+
const cell = lane[row];
|
|
164
|
+
return (
|
|
165
|
+
cell && (
|
|
166
|
+
<a {...laneLink(cell, index, laneCount, base)}>
|
|
167
|
+
<span class="sr-only">{cell.name}</span>
|
|
168
|
+
</a>
|
|
169
|
+
)
|
|
170
|
+
);
|
|
171
|
+
})}
|
|
109
172
|
</li>
|
|
110
173
|
))}
|
|
111
174
|
</ol>
|
|
@@ -224,4 +287,59 @@ const hours = (range: { min: number; max: number }) => {
|
|
|
224
287
|
text-align: start;
|
|
225
288
|
}
|
|
226
289
|
}
|
|
290
|
+
|
|
291
|
+
/* The key under the counts: the glyphs the list uses, in the accent. */
|
|
292
|
+
.contents-key {
|
|
293
|
+
display: flex;
|
|
294
|
+
flex-wrap: wrap;
|
|
295
|
+
gap: 0.25rem 1.25rem;
|
|
296
|
+
margin: -1.1rem 0 0;
|
|
297
|
+
padding: 0;
|
|
298
|
+
list-style: none;
|
|
299
|
+
color: var(--sl-color-gray-2);
|
|
300
|
+
font-size: var(--sl-text-sm);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.contents-key li {
|
|
304
|
+
margin: 0;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.contents-key :global(.contents-glyph) {
|
|
308
|
+
color: var(--sl-color-text-accent);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/* The slides column (`global.css`): one 1.7rem column per lane at the
|
|
312
|
+
row's end. The description keeps to the title's and time's columns. A
|
|
313
|
+
bar's cell spans a row's title and description, and reaches the line
|
|
314
|
+
between two rows: the row's bottom padding and the list's gap below it,
|
|
315
|
+
the next row's top border and padding above. */
|
|
316
|
+
.course-contents[data-deck-lanes] {
|
|
317
|
+
--deck-lane-rows: 2;
|
|
318
|
+
--deck-lane-down: 0.5rem;
|
|
319
|
+
--deck-lane-up: calc(0.4rem + 1px);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.course-contents[data-deck-lanes] .course-part li {
|
|
323
|
+
grid-template-columns: 1.9rem minmax(0, 1fr) auto repeat(var(--deck-lanes), 1.7rem);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.course-contents[data-deck-lanes] .course-part li > p {
|
|
327
|
+
grid-column: 2 / 4;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/* On a phone the time goes under the title, and the cells span the three
|
|
331
|
+
rows. */
|
|
332
|
+
@media (max-width: 40rem) {
|
|
333
|
+
.course-contents[data-deck-lanes] {
|
|
334
|
+
--deck-lane-rows: 3;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.course-contents[data-deck-lanes] .course-part li {
|
|
338
|
+
grid-template-columns: 1.9rem minmax(0, 1fr) repeat(var(--deck-lanes), 1.7rem);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.course-contents[data-deck-lanes] .course-part li > p {
|
|
342
|
+
grid-column: 2;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
227
345
|
</style>
|
|
@@ -2,8 +2,29 @@
|
|
|
2
2
|
import { getCollection } from 'astro:content';
|
|
3
3
|
import { loadManifest } from '../compiler/manifest';
|
|
4
4
|
import { isLessonSlug, lessonIdFromSlug } from '../compiler/lesson-derivation';
|
|
5
|
+
import { competencyGraphsByPart } from '../curriculum/competency-graph';
|
|
5
6
|
import Diagram, { type DiagramEdge, type DiagramNode } from './diagram/Diagram.astro';
|
|
6
7
|
|
|
8
|
+
export interface Props {
|
|
9
|
+
/**
|
|
10
|
+
* `'whole'` (the default) draws the competency graph as one diagram;
|
|
11
|
+
* `'per-part'` draws one diagram per part, in sidebar order, holding the
|
|
12
|
+
* competencies its lessons teach first and, labelled with their part, the
|
|
13
|
+
* earlier competencies they build on. A graph too wide for the column as
|
|
14
|
+
* one diagram (rule V8) usually fits as one per part.
|
|
15
|
+
*/
|
|
16
|
+
graph?: 'whole' | 'per-part';
|
|
17
|
+
/** The layout direction of every competency diagram; `Diagram`'s default is `'TB'`. */
|
|
18
|
+
direction?: 'TB' | 'LR';
|
|
19
|
+
/**
|
|
20
|
+
* Passed to every competency diagram: the width, in px, above which rule V8
|
|
21
|
+
* warns, or `false` to let a whole-course graph scroll without a warning.
|
|
22
|
+
*/
|
|
23
|
+
warnAboveWidth?: number | false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const { graph = 'whole', direction, warnAboveWidth } = Astro.props;
|
|
27
|
+
|
|
7
28
|
const [tracks, docs, competencies, manifest] = await Promise.all([
|
|
8
29
|
getCollection('tracks'),
|
|
9
30
|
getCollection('docs'),
|
|
@@ -37,6 +58,15 @@ const competencyEdges: DiagramEdge[] = competencies.flatMap(({ data }) =>
|
|
|
37
58
|
to: data.id,
|
|
38
59
|
})),
|
|
39
60
|
);
|
|
61
|
+
|
|
62
|
+
const partGraphs =
|
|
63
|
+
graph === 'per-part'
|
|
64
|
+
? competencyGraphsByPart(
|
|
65
|
+
manifest.parts,
|
|
66
|
+
new Map(manifest.lessons.map((lesson) => [lesson.id, lesson.teaches])),
|
|
67
|
+
competencies.map(({ data }) => data),
|
|
68
|
+
)
|
|
69
|
+
: [];
|
|
40
70
|
---
|
|
41
71
|
|
|
42
72
|
{
|
|
@@ -127,12 +157,30 @@ const competencyEdges: DiagramEdge[] = competencies.flatMap(({ data }) =>
|
|
|
127
157
|
</p>
|
|
128
158
|
|
|
129
159
|
{
|
|
130
|
-
|
|
160
|
+
partGraphs.map((partGraph) => (
|
|
161
|
+
<>
|
|
162
|
+
<h3 id={`${partGraph.id}-heading`}>{partGraph.title}</h3>
|
|
163
|
+
<Diagram
|
|
164
|
+
id={partGraph.id}
|
|
165
|
+
caption={`The competencies taught in ${partGraph.title}: an arrow runs from a prerequisite to the competency that builds on it, and a box labelled with an earlier part is a competency taught there`}
|
|
166
|
+
nodes={partGraph.nodes}
|
|
167
|
+
edges={partGraph.edges}
|
|
168
|
+
direction={direction}
|
|
169
|
+
warnAboveWidth={warnAboveWidth}
|
|
170
|
+
/>
|
|
171
|
+
</>
|
|
172
|
+
))
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
{
|
|
176
|
+
graph === 'whole' && competencyNodes.length > 0 && (
|
|
131
177
|
<Diagram
|
|
132
178
|
id="competency-graph"
|
|
133
179
|
caption="The competency dependency graph: an arrow runs from a prerequisite to the competency that builds on it"
|
|
134
180
|
nodes={competencyNodes}
|
|
135
181
|
edges={competencyEdges}
|
|
182
|
+
direction={direction}
|
|
183
|
+
warnAboveWidth={warnAboveWidth}
|
|
136
184
|
/>
|
|
137
185
|
)
|
|
138
186
|
}
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* ```
|
|
28
28
|
*/
|
|
29
29
|
import { loadManifest, type ManifestLessonFloat } from '../compiler/manifest';
|
|
30
|
-
import { FLOAT_KIND_WORDS, type FloatKind } from '
|
|
30
|
+
import { FLOAT_KIND_WORDS, type FloatKind } from '../reference/float-name';
|
|
31
31
|
|
|
32
32
|
interface Props {
|
|
33
33
|
/** Heading level for each part's name; a chapter's is one below. Defaults to `h2`. */
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* collection — chapter order, titles, descriptions, float counts, and which
|
|
8
8
|
* parts this one builds on or feeds. A course authors the page's prose; it
|
|
9
9
|
* never re-types the list, so a renamed chapter or a new prerequisite cannot
|
|
10
|
-
* leave a stale copy behind on the part page.
|
|
10
|
+
* leave a stale copy behind on the part page. When the course has a deck, the
|
|
11
|
+
* list has the contents list's glyphs and slides column (`deck-lane.ts`,
|
|
12
|
+
* `CourseContents.astro`).
|
|
11
13
|
*
|
|
12
14
|
* Usage, in `content/docs/<part>/index.mdx`:
|
|
13
15
|
*
|
|
@@ -38,6 +40,7 @@
|
|
|
38
40
|
*/
|
|
39
41
|
import { getCollection } from 'astro:content';
|
|
40
42
|
import { loadManifest, type ManifestPart } from '../compiler/manifest';
|
|
43
|
+
import { deckLanes, laneLink, titleGlyph } from './deck-lane';
|
|
41
44
|
|
|
42
45
|
interface Props {
|
|
43
46
|
/**
|
|
@@ -53,9 +56,14 @@ interface Props {
|
|
|
53
56
|
readonly chain?: readonly string[];
|
|
54
57
|
/** Set `false` to drop the rail and keep only the reading list. */
|
|
55
58
|
readonly rail?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Set `false` to drop the slides column and the glyphs before the titles.
|
|
61
|
+
* They show only when the course has a deck.
|
|
62
|
+
*/
|
|
63
|
+
readonly slides?: boolean;
|
|
56
64
|
}
|
|
57
65
|
|
|
58
|
-
const { part: partProp, chain = [], rail = true } = Astro.props;
|
|
66
|
+
const { part: partProp, chain = [], rail = true, slides = true } = Astro.props;
|
|
59
67
|
|
|
60
68
|
const routeSlug = Astro.locals.starlightRoute.entry.id;
|
|
61
69
|
const partDir = partProp ?? routeSlug.replace(/\/index$/, '').split('/')[0];
|
|
@@ -82,6 +90,11 @@ const chapters = part.lessons
|
|
|
82
90
|
.map((id) => lessonById.get(id))
|
|
83
91
|
.filter((lesson): lesson is NonNullable<typeof lesson> => lesson !== undefined);
|
|
84
92
|
|
|
93
|
+
// As in the contents list: glyphs only in a course with a deck, and the
|
|
94
|
+
// column only when this part has one.
|
|
95
|
+
const glyphs = slides && manifest.decks.length > 0;
|
|
96
|
+
const lanes = glyphs ? deckLanes({ lessons: chapters, decks: manifest.decks, part }) : [];
|
|
97
|
+
|
|
85
98
|
const competencyById = new Map(
|
|
86
99
|
competencyEntries.map(({ data }) => [data.id, data]),
|
|
87
100
|
);
|
|
@@ -169,11 +182,15 @@ const hasRail =
|
|
|
169
182
|
</ul>
|
|
170
183
|
|
|
171
184
|
<div class="part-body">
|
|
172
|
-
<ol
|
|
185
|
+
<ol
|
|
186
|
+
class="part-chapters"
|
|
187
|
+
data-deck-lanes={lanes.length > 0 ? lanes.length : undefined}
|
|
188
|
+
style={lanes.length > 0 ? `--deck-lanes: ${lanes.length}` : undefined}
|
|
189
|
+
>
|
|
173
190
|
{
|
|
174
|
-
chapters.map((lesson) => (
|
|
191
|
+
chapters.map((lesson, row) => (
|
|
175
192
|
<li>
|
|
176
|
-
<a href={`${base}/${lesson.slug}/`}>{lesson.title}</a>
|
|
193
|
+
<a href={`${base}/${lesson.slug}/`}>{glyphs && <span class={`contents-glyph contents-glyph--${titleGlyph(lesson)}`} aria-hidden="true" />}{lesson.title}</a>
|
|
177
194
|
<p>{lesson.description}</p>
|
|
178
195
|
<p class="chapter-meta">
|
|
179
196
|
{lesson.studyMinutes && (
|
|
@@ -192,6 +209,16 @@ const hasRail =
|
|
|
192
209
|
</span>
|
|
193
210
|
)}
|
|
194
211
|
</p>
|
|
212
|
+
{lanes.map((lane, index) => {
|
|
213
|
+
const cell = lane[row];
|
|
214
|
+
return (
|
|
215
|
+
cell && (
|
|
216
|
+
<a {...laneLink(cell, index, lanes.length, base)}>
|
|
217
|
+
<span class="sr-only">{cell.name}</span>
|
|
218
|
+
</a>
|
|
219
|
+
)
|
|
220
|
+
);
|
|
221
|
+
})}
|
|
195
222
|
</li>
|
|
196
223
|
))
|
|
197
224
|
}
|
|
@@ -342,6 +369,25 @@ const hasRail =
|
|
|
342
369
|
line-height: 1.5;
|
|
343
370
|
}
|
|
344
371
|
|
|
372
|
+
/* The slides column (`global.css`): one 1.7rem column per lane at the
|
|
373
|
+
row's end, the title, description and meta line kept to the second
|
|
374
|
+
column. A bar's cell spans the three, and reaches the line under its
|
|
375
|
+
row: the row's bottom padding, then the next row's top padding and the
|
|
376
|
+
line itself above. */
|
|
377
|
+
.part-chapters[data-deck-lanes] {
|
|
378
|
+
--deck-lane-rows: 3;
|
|
379
|
+
--deck-lane-down: 0.85rem;
|
|
380
|
+
--deck-lane-up: calc(0.85rem + 1px);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.part-chapters[data-deck-lanes] > li {
|
|
384
|
+
grid-template-columns: 2.25rem minmax(0, 1fr) repeat(var(--deck-lanes), 1.7rem);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.part-chapters[data-deck-lanes] > li > :not(.deck-lane) {
|
|
388
|
+
grid-column: 2;
|
|
389
|
+
}
|
|
390
|
+
|
|
345
391
|
.chapter-meta {
|
|
346
392
|
display: flex;
|
|
347
393
|
flex-wrap: wrap;
|
|
@@ -7,6 +7,8 @@ import { renderNotationMath } from '../notation/render-notation-math';
|
|
|
7
7
|
import type { MixedContentNode } from '../notation/render-notation-math';
|
|
8
8
|
import { knowledgeCheckAnchorId } from '../../reference/knowledge-checks.mjs';
|
|
9
9
|
import BlockHeading from '../BlockHeading.astro';
|
|
10
|
+
import { deckOfRoute } from '../deck/deck-context';
|
|
11
|
+
import { routeIdFromLocals } from '../../reference/page-notation';
|
|
10
12
|
import AssessmentRunner from './AssessmentRunner';
|
|
11
13
|
|
|
12
14
|
interface Props {
|
|
@@ -17,17 +19,29 @@ interface Props {
|
|
|
17
19
|
* `[[check-…]]` references; undeclared legacy embeds keep a local fallback.
|
|
18
20
|
*/
|
|
19
21
|
id?: string;
|
|
22
|
+
/**
|
|
23
|
+
* The lesson whose checks the set asks, by its id. Set by the engine on a
|
|
24
|
+
* part's deck (`deck-remark.mjs`): the chapter the frame's section covers.
|
|
25
|
+
* Elsewhere the page's own lesson.
|
|
26
|
+
*/
|
|
27
|
+
lesson?: string;
|
|
20
28
|
}
|
|
21
29
|
|
|
22
30
|
const explicitId = Astro.props.id;
|
|
23
31
|
|
|
24
32
|
const base = import.meta.env.BASE_URL.replace(/\/$/, '');
|
|
25
|
-
const lessonId = Astro.url.pathname
|
|
26
|
-
.replace(new RegExp(`^${base}/`), '')
|
|
27
|
-
.replace(/\/$/, '')
|
|
28
|
-
.replace(/\//g, '.');
|
|
29
|
-
|
|
30
33
|
const manifest = await loadManifest();
|
|
34
|
+
// On a deck (docs/authoring/deck.md) the checks are its lesson's, and a check
|
|
35
|
+
// is a question frame: its label, not a heading of its own. On a part's deck
|
|
36
|
+
// they are the chapter's whose section the frame is in.
|
|
37
|
+
const deck = deckOfRoute(manifest, routeIdFromLocals(Astro.locals));
|
|
38
|
+
const lessonId =
|
|
39
|
+
Astro.props.lesson ??
|
|
40
|
+
deck?.lessonId ??
|
|
41
|
+
Astro.url.pathname
|
|
42
|
+
.replace(new RegExp(`^${base}/`), '')
|
|
43
|
+
.replace(/\/$/, '')
|
|
44
|
+
.replace(/\//g, '.');
|
|
31
45
|
// A check folds like a section, by the course's `disclosure.knowledgeChecks`
|
|
32
46
|
// (collapsible and open unless the course says otherwise).
|
|
33
47
|
const { knowledgeChecks } = manifest.disclosure ?? DISCLOSURE_DEFAULTS;
|
|
@@ -101,16 +115,28 @@ function renderedContent(
|
|
|
101
115
|
{
|
|
102
116
|
entries.map(({ entry, number, anchorId }) => (
|
|
103
117
|
<>
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
118
|
+
{deck ? (
|
|
119
|
+
<p
|
|
120
|
+
class="deck-check__label"
|
|
121
|
+
id={anchorId}
|
|
122
|
+
data-block-kind="check"
|
|
123
|
+
data-check-number={number}
|
|
124
|
+
data-check-title={entry.data.title}
|
|
125
|
+
>
|
|
126
|
+
<span class="deck-check__kind">Knowledge check {number}</span> {entry.data.title}
|
|
127
|
+
</p>
|
|
128
|
+
) : (
|
|
129
|
+
<BlockHeading
|
|
130
|
+
kind="check"
|
|
131
|
+
anchorId={anchorId}
|
|
132
|
+
title={entry.data.title}
|
|
133
|
+
number={number}
|
|
134
|
+
collapsible={knowledgeChecks.collapsible}
|
|
135
|
+
defaultOpen={knowledgeChecks.defaultOpen}
|
|
136
|
+
card="accent"
|
|
137
|
+
class="assessment-heading-wrapper"
|
|
138
|
+
/>
|
|
139
|
+
)}
|
|
114
140
|
<AssessmentRunner
|
|
115
141
|
client:visible
|
|
116
142
|
assessmentId={entry.data.id}
|
|
@@ -63,14 +63,23 @@ export async function titledBlocksOf(
|
|
|
63
63
|
* The text a reader sees for each outline heading with inline math, by its
|
|
64
64
|
* anchor (`manifest.lessons[].headings`, v30). Astro lists a heading by all
|
|
65
65
|
* the text in it, and KaTeX writes a formula three ways — MathML, its LaTeX
|
|
66
|
-
* annotation and its glyphs — so its own list reads `R\explain{k}{R}R`.
|
|
66
|
+
* annotation and its glyphs — so its own list reads `R\explain{k}{R}R`. A
|
|
67
|
+
* deck beside its chapter, or a part's, is no lesson: its page lists its
|
|
68
|
+
* frames by their titles as the reader sees them (`decks[].frames`).
|
|
67
69
|
*/
|
|
68
|
-
export async function mathHeadingTextsOf(
|
|
69
|
-
const
|
|
70
|
+
export async function mathHeadingTextsOf(pageId: string): Promise<ReadonlyMap<string, string>> {
|
|
71
|
+
const manifest = await manifestOnce();
|
|
72
|
+
const lesson = manifest.lessons.find((entry) => entry.id === pageId);
|
|
73
|
+
if (lesson) {
|
|
74
|
+
return new Map(
|
|
75
|
+
lesson.headings.filter((heading) => heading.math).map((heading) => [heading.slug, heading.text]),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
const deck = (manifest.decks ?? []).find((entry) => entry.pageId === pageId);
|
|
70
79
|
return new Map(
|
|
71
|
-
(
|
|
72
|
-
.
|
|
73
|
-
|
|
80
|
+
(deck?.frames ?? []).flatMap((frame): [string, string][] =>
|
|
81
|
+
frame.slug === undefined ? [] : [[frame.slug, frame.title]],
|
|
82
|
+
),
|
|
74
83
|
);
|
|
75
84
|
}
|
|
76
85
|
|
|
@@ -63,6 +63,7 @@ import {
|
|
|
63
63
|
createBrowserPreferenceStore,
|
|
64
64
|
createSessionPreferenceStore,
|
|
65
65
|
} from '../session/PreferenceStore';
|
|
66
|
+
import { rememberReading } from '../session/reading-memory';
|
|
66
67
|
import { decodeCard, keyOf, type RememberedCard } from './card-memory';
|
|
67
68
|
import type { HoldEdge } from './card-placement';
|
|
68
69
|
|
|
@@ -305,21 +306,23 @@ const setKey = (base: string) => `explico:cards:carried:${base}`;
|
|
|
305
306
|
|
|
306
307
|
/**
|
|
307
308
|
* K5, D9: this tab's own set; a tab with none takes the course's last set,
|
|
308
|
-
* which is its own from then on.
|
|
309
|
+
* which is its own from then on. The course's last set is reading memory
|
|
310
|
+
* (`../session/reading-memory.ts`): it is read and kept only with the
|
|
311
|
+
* reader's yes, so without it a new tab starts with no cards.
|
|
309
312
|
*/
|
|
310
313
|
export function readCarried(base: string): CarriedSet {
|
|
311
314
|
const own = decodeCarried(tab.get(setKey(base)));
|
|
312
315
|
if (own) return own;
|
|
313
|
-
const last = decodeCarried(preferences.get(setKey(base))) ?? EMPTY_SET;
|
|
316
|
+
const last = (rememberReading(base) ? decodeCarried(preferences.get(setKey(base))) : null) ?? EMPTY_SET;
|
|
314
317
|
tab.set(setKey(base), encodeCarried(last));
|
|
315
318
|
return last;
|
|
316
319
|
}
|
|
317
320
|
|
|
318
|
-
/** K5: this tab's set, written as the course's last set too. Returns what was stored. */
|
|
321
|
+
/** K5: this tab's set, written as the course's last set too with the reader's yes. Returns what was stored. */
|
|
319
322
|
export function writeCarried(base: string, set: CarriedSet): string {
|
|
320
323
|
const text = encodeCarried(set);
|
|
321
324
|
tab.set(setKey(base), text);
|
|
322
|
-
preferences.set(setKey(base), text);
|
|
325
|
+
if (rememberReading(base)) preferences.set(setKey(base), text);
|
|
323
326
|
return text;
|
|
324
327
|
}
|
|
325
328
|
|
|
@@ -75,6 +75,10 @@ import {
|
|
|
75
75
|
type CarriedSet,
|
|
76
76
|
type OpenedAt,
|
|
77
77
|
} from './card-carry';
|
|
78
|
+
import {
|
|
79
|
+
READING_MEMORY_EVENT,
|
|
80
|
+
type ReadingMemoryChange,
|
|
81
|
+
} from '../session/reading-memory';
|
|
78
82
|
import { isThisPage, pageTitleOf } from './card-fetch';
|
|
79
83
|
import { wireJumpArrow } from './card-jump';
|
|
80
84
|
import { readableText } from './readable-text';
|
|
@@ -2051,7 +2055,7 @@ function rememberCards(): void {
|
|
|
2051
2055
|
width: card.width,
|
|
2052
2056
|
});
|
|
2053
2057
|
}
|
|
2054
|
-
writeRememberedCards({
|
|
2058
|
+
writeRememberedCards(base, {
|
|
2055
2059
|
cards: saved,
|
|
2056
2060
|
pointed: pointedKey !== null && keys.has(pointedKey) ? pointedKey : null,
|
|
2057
2061
|
});
|
|
@@ -2599,11 +2603,19 @@ function install(): void {
|
|
|
2599
2603
|
if (carry) {
|
|
2600
2604
|
restoreCarriedSet(readCarried(base));
|
|
2601
2605
|
} else {
|
|
2602
|
-
const page = readRememberedCards();
|
|
2606
|
+
const page = readRememberedCards(base);
|
|
2603
2607
|
remembered = [...page.cards];
|
|
2604
2608
|
rememberedPointed = page.pointed;
|
|
2605
2609
|
}
|
|
2606
2610
|
window.addEventListener('pagehide', leavePage);
|
|
2611
|
+
// A yes to reading memory keeps what is open now beyond the tab; a no has
|
|
2612
|
+
// already deleted the kept copies, and the tab's own go on.
|
|
2613
|
+
window.addEventListener(READING_MEMORY_EVENT, (event) => {
|
|
2614
|
+
const { answer } = (event as CustomEvent<ReadingMemoryChange>).detail;
|
|
2615
|
+
if (answer !== 'yes') return;
|
|
2616
|
+
lastStored = null;
|
|
2617
|
+
leavePage();
|
|
2618
|
+
});
|
|
2607
2619
|
document.addEventListener('visibilitychange', () => {
|
|
2608
2620
|
if (document.visibilityState === 'hidden') leavePage();
|
|
2609
2621
|
});
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
* Remembering a page's open explanation cards. A reader who leaves a lesson
|
|
3
3
|
* with cards pinned, docked or moved finds them where they were on coming
|
|
4
4
|
* back to it: through a link, Back or a reload, in another tab, or after the
|
|
5
|
-
* browser restarts. Closing the last card forgets the page.
|
|
5
|
+
* browser restarts. Closing the last card forgets the page. This is reading
|
|
6
|
+
* memory (`../session/reading-memory.ts`): kept in the browser only with the
|
|
7
|
+
* reader's yes; without it, in the tab's session, so Back, a reload and a
|
|
8
|
+
* link still bring the cards back until the tab is closed.
|
|
6
9
|
*
|
|
7
10
|
* The card manager (`./card-manager.ts`) saves the kept cards as the page is
|
|
8
11
|
* left (`pagehide`) and restores them as each layer registers. A card is
|
|
@@ -17,7 +20,7 @@
|
|
|
17
20
|
* `decodeCards` and `restorable` are pure, so `tests/unit/card-memory.test.ts`
|
|
18
21
|
* checks them in Node.
|
|
19
22
|
*/
|
|
20
|
-
import {
|
|
23
|
+
import { readingMemoryStore } from '../session/reading-memory';
|
|
21
24
|
import type { MovedSpot } from './card-drag';
|
|
22
25
|
import type { CardAnchor } from './card-placement';
|
|
23
26
|
|
|
@@ -159,21 +162,22 @@ export function restorable(
|
|
|
159
162
|
// ---------------------------------------------------------------------------
|
|
160
163
|
|
|
161
164
|
// The one seam for browser storage; this module never touches it itself.
|
|
162
|
-
//
|
|
163
|
-
// open on the same page share one entry, and the last
|
|
164
|
-
|
|
165
|
-
|
|
165
|
+
// With the reader's yes, local storage, so the cards outlive the tab and a
|
|
166
|
+
// browser restart (tabs open on the same page share one entry, and the last
|
|
167
|
+
// one left wins); without it, the tab's session storage.
|
|
166
168
|
/**
|
|
167
169
|
* One entry per page, kept only while the page has cards to bring back. The
|
|
168
170
|
* path includes a course's base, so courses on one origin never share one.
|
|
169
171
|
*/
|
|
170
172
|
const storageKey = () => `explico:cards:${window.location.pathname}`;
|
|
171
173
|
|
|
172
|
-
/** The cards this page had open when the reader last left it
|
|
173
|
-
export const readRememberedCards = (): RememberedPage =>
|
|
174
|
+
/** The cards this page had open when the reader last left it, in the course at `base`. */
|
|
175
|
+
export const readRememberedCards = (base: string): RememberedPage =>
|
|
176
|
+
decodeCards(readingMemoryStore(base).get(storageKey()));
|
|
174
177
|
|
|
175
178
|
/** Remember this page's cards; with none, the page is forgotten. */
|
|
176
|
-
export function writeRememberedCards(page: RememberedPage): void {
|
|
179
|
+
export function writeRememberedCards(base: string, page: RememberedPage): void {
|
|
180
|
+
const store = readingMemoryStore(base);
|
|
177
181
|
const key = storageKey();
|
|
178
182
|
if (page.cards.length > 0) store.set(key, encodeCards(page));
|
|
179
183
|
else if (store.remove) store.remove(key);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* A deck page's behaviour in the browser: the scaled frames, Present mode,
|
|
4
|
+
* the presenter window and the steps' effects (`deck-controller.ts`).
|
|
5
|
+
* `LessonFooter.astro` includes it on a deck's page only.
|
|
6
|
+
*/
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<script>
|
|
10
|
+
import { installDecks } from './deck-controller';
|
|
11
|
+
|
|
12
|
+
installDecks();
|
|
13
|
+
</script>
|