explico 0.13.1 → 0.15.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 +990 -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 +100 -0
- package/dist/compiler/cli-main.js +433 -0
- package/dist/compiler/cli-reference.d.ts +50 -0
- package/dist/compiler/cli-reference.js +213 -0
- package/dist/compiler/cli-verbs.d.ts +45 -0
- package/dist/compiler/cli-verbs.js +224 -0
- package/dist/compiler/cli-verify.d.ts +51 -0
- package/dist/compiler/cli-verify.js +151 -0
- package/dist/compiler/cli.d.ts +408 -24
- package/dist/compiler/cli.js +1328 -8
- package/dist/compiler/collections.d.ts +88 -1
- package/dist/compiler/collections.js +170 -25
- package/dist/compiler/content-release.d.ts +49 -0
- package/dist/compiler/content-release.js +63 -0
- package/dist/compiler/content-schemas.d.ts +2 -2
- 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 +364 -58
- package/dist/compiler/page-versions.d.ts +184 -0
- package/dist/compiler/page-versions.js +217 -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 +82 -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 +6 -2
- package/dist/reference/diagram-fit.js +5 -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 +237 -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 +34 -10
- package/dist/rules/rules.schema.json +280 -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 +31 -9
- package/docs/authoring/agents.md +72 -18
- package/docs/authoring/cli-reference.json +2172 -0
- package/docs/authoring/cli.md +887 -0
- package/docs/authoring/components.md +15 -0
- package/docs/authoring/deck.md +612 -0
- package/docs/authoring/migrating.md +1195 -16
- package/docs/authoring/notation.md +49 -6
- package/docs/authoring/structure.md +80 -1
- package/docs/authoring/versioning.md +56 -3
- package/docs/authoring/visualizations.md +7 -2
- package/docs/course-agent/AGENTS.md +40 -0
- package/docs/course-agent/agents/rules-reviewer.md +33 -0
- package/docs/course-agent/skills/draft-lesson/SKILL.md +38 -0
- package/docs/course-agent/skills/migrate-course/SKILL.md +32 -0
- package/docs/course-agent/skills/release-content/SKILL.md +26 -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 +557 -0
- package/src/compiler/cli-reference.ts +285 -0
- package/src/compiler/cli-verbs.ts +270 -0
- package/src/compiler/cli-verify.ts +241 -0
- package/src/compiler/cli.ts +2119 -170
- package/src/compiler/collections.ts +284 -25
- package/src/compiler/content-release.ts +91 -0
- 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 +568 -63
- package/src/compiler/page-versions.ts +401 -0
- package/src/compiler/resolution-report.ts +38 -7
- package/src/compiler/validate-content.ts +215 -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 +137 -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-fit.ts +18 -4
- 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 +274 -4
- package/src/rules/course-rules.ts +21 -1
- package/src/rules/in-effect.ts +58 -11
- 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
|
@@ -386,6 +386,258 @@ export function normalizeCards(config: CourseConfig): NormalizedCards {
|
|
|
386
386
|
return { carry };
|
|
387
387
|
}
|
|
388
388
|
|
|
389
|
+
/**
|
|
390
|
+
* The marks the sidebar puts beside a chapter or part page for one reader
|
|
391
|
+
* (`sidebarMarks` in the course settings, `page-marks.ts`): a quiet ring
|
|
392
|
+
* while they have not opened it, an accent dot when its content changed
|
|
393
|
+
* since they last did. Every field is optional; {@link normalizeSidebarMarks}
|
|
394
|
+
* fills in the defaults.
|
|
395
|
+
*/
|
|
396
|
+
export interface CourseSidebarMarks {
|
|
397
|
+
/** Mark a page the reader has not opened yet. Default `true`. */
|
|
398
|
+
readonly unopened?: boolean;
|
|
399
|
+
/** Mark a page whose content changed since the reader last opened it. Default `true`. */
|
|
400
|
+
readonly changed?: boolean;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/** {@link CourseSidebarMarks} with every default applied. */
|
|
404
|
+
export interface NormalizedSidebarMarks {
|
|
405
|
+
readonly unopened: boolean;
|
|
406
|
+
readonly changed: boolean;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export const SIDEBAR_MARKS_DEFAULTS: NormalizedSidebarMarks = { unopened: true, changed: true };
|
|
410
|
+
|
|
411
|
+
/** Validate `CourseConfig.sidebarMarks` and apply its defaults. Throws on a bad value. */
|
|
412
|
+
export function normalizeSidebarMarks(config: CourseConfig): NormalizedSidebarMarks {
|
|
413
|
+
const marks = config.sidebarMarks ?? {};
|
|
414
|
+
const read = (field: keyof NormalizedSidebarMarks): boolean => {
|
|
415
|
+
const value: unknown = marks[field] ?? SIDEBAR_MARKS_DEFAULTS[field];
|
|
416
|
+
if (typeof value !== 'boolean') {
|
|
417
|
+
throw new Error(
|
|
418
|
+
`CourseConfig sidebarMarks.${field} must be true or false; received ${JSON.stringify(value)}.`,
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
return value;
|
|
422
|
+
};
|
|
423
|
+
return { unopened: read('unopened'), changed: read('changed') };
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Which edition of a lesson its sidebar row opens, for a reader who has not
|
|
428
|
+
* chosen (`sidebarEditions` in the course settings, `sidebar-editions.ts`). A
|
|
429
|
+
* lesson with a chapter and a deck is one row: the row opens the **lead**
|
|
430
|
+
* edition, and the **other** is an icon at the row's end, or not listed. A
|
|
431
|
+
* reader switches both in the sidebar's foot. The plain `string` fields
|
|
432
|
+
* arrive as JSON; {@link normalizeSidebarEditions} rejects any value it does
|
|
433
|
+
* not list.
|
|
434
|
+
*/
|
|
435
|
+
export interface CourseSidebarEditions {
|
|
436
|
+
/** The edition a lesson's row opens: `chapter` (default) or `slides`. */
|
|
437
|
+
readonly lead?: string;
|
|
438
|
+
/** Whether the row also lists the other edition: `listed` (default) or `hidden`. */
|
|
439
|
+
readonly other?: string;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export type SidebarEditionLead = 'chapter' | 'slides';
|
|
443
|
+
export type SidebarEditionOther = 'listed' | 'hidden';
|
|
444
|
+
|
|
445
|
+
/** {@link CourseSidebarEditions} with every default applied. */
|
|
446
|
+
export interface NormalizedSidebarEditions {
|
|
447
|
+
readonly lead: SidebarEditionLead;
|
|
448
|
+
readonly other: SidebarEditionOther;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/** The engine's defaults (decision D10): the chapter leads, the slides are listed. */
|
|
452
|
+
export const SIDEBAR_EDITIONS_DEFAULTS: NormalizedSidebarEditions = { lead: 'chapter', other: 'listed' };
|
|
453
|
+
|
|
454
|
+
/** Validate `CourseConfig.sidebarEditions` and apply its defaults. Throws on a bad value. */
|
|
455
|
+
export function normalizeSidebarEditions(config: CourseConfig): NormalizedSidebarEditions {
|
|
456
|
+
const editions = config.sidebarEditions ?? {};
|
|
457
|
+
const choice = <T extends string>(field: keyof NormalizedSidebarEditions, allowed: readonly T[]): T => {
|
|
458
|
+
const value: unknown = editions[field] ?? SIDEBAR_EDITIONS_DEFAULTS[field];
|
|
459
|
+
if (typeof value !== 'string' || !(allowed as readonly string[]).includes(value)) {
|
|
460
|
+
throw new Error(
|
|
461
|
+
`CourseConfig sidebarEditions.${field} must be one of ${allowed.map((item) => JSON.stringify(item)).join(', ')}; ` +
|
|
462
|
+
`received ${JSON.stringify(value)}.`,
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
return value as T;
|
|
466
|
+
};
|
|
467
|
+
return {
|
|
468
|
+
lead: choice<SidebarEditionLead>('lead', ['chapter', 'slides']),
|
|
469
|
+
other: choice<SidebarEditionOther>('other', ['listed', 'hidden']),
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* A course's decks: the slide edition of a lesson (`<lesson>.deck.mdx`,
|
|
475
|
+
* `docs/authoring/deck.md`). Every field is optional; {@link normalizeDeck}
|
|
476
|
+
* fills in the defaults. The plain `string` fields arrive as JSON, and
|
|
477
|
+
* {@link normalizeDeck} rejects any value it does not list.
|
|
478
|
+
*/
|
|
479
|
+
export interface CourseDeck {
|
|
480
|
+
/**
|
|
481
|
+
* The canvas every deck of the course is laid out on: `16:9` (default),
|
|
482
|
+
* `16:10` or `4:3`. One setting for the course, because the frame-fit check,
|
|
483
|
+
* the captured figures and the PDF's page all read it.
|
|
484
|
+
*/
|
|
485
|
+
readonly aspectRatio?: string;
|
|
486
|
+
/**
|
|
487
|
+
* The line on a frame that names the symbols it shows: `first-use`
|
|
488
|
+
* (default, the symbols the deck has not shown before), `every-frame`
|
|
489
|
+
* (every symbol on the frame), or `off`.
|
|
490
|
+
*/
|
|
491
|
+
readonly legend?: string;
|
|
492
|
+
/**
|
|
493
|
+
* Who sees a frame's speaker notes: `reader` (default — under each frame in
|
|
494
|
+
* the scroll view, in the presenter window and in the handout), `presenter`
|
|
495
|
+
* (the presenter window and the handout only), or `off` (nowhere; the
|
|
496
|
+
* handout PDF is not built).
|
|
497
|
+
*/
|
|
498
|
+
readonly notes?: string;
|
|
499
|
+
/** End each deck on a frame of what its lesson's competencies say a student can now do. Default `true`. */
|
|
500
|
+
readonly outcomesFrame?: boolean;
|
|
501
|
+
/** Offer the slides-with-notes PDF beside the slides PDF. Default `true`. */
|
|
502
|
+
readonly handoutPdf?: boolean;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
export type DeckAspectRatio = '16:9' | '16:10' | '4:3';
|
|
506
|
+
export type DeckLegend = 'first-use' | 'every-frame' | 'off';
|
|
507
|
+
export type DeckNotes = 'reader' | 'presenter' | 'off';
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* The logical canvas a frame is laid out on, in CSS pixels: the width of the
|
|
511
|
+
* page's reading column, so a figure, a table or a lab lays out on a frame at
|
|
512
|
+
* the size it was made for, and the frame is scaled to the screen. Every
|
|
513
|
+
* ratio is 450 px tall, so the type scale and a frame's capacity in lines are
|
|
514
|
+
* the same in all three. Printed, the canvas is scaled by 1.6 onto a page
|
|
515
|
+
* 7.5 in tall (13.333 × 7.5 in for 16:9).
|
|
516
|
+
*/
|
|
517
|
+
export const DECK_CANVAS: Readonly<Record<DeckAspectRatio, { readonly width: number; readonly height: number }>> = {
|
|
518
|
+
'16:9': { width: 800, height: 450 },
|
|
519
|
+
'16:10': { width: 720, height: 450 },
|
|
520
|
+
'4:3': { width: 600, height: 450 },
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
/** {@link CourseDeck} with every default applied, as the manifest publishes it. */
|
|
524
|
+
export interface NormalizedDeck {
|
|
525
|
+
readonly aspectRatio: DeckAspectRatio;
|
|
526
|
+
readonly legend: DeckLegend;
|
|
527
|
+
readonly notes: DeckNotes;
|
|
528
|
+
readonly outcomesFrame: boolean;
|
|
529
|
+
readonly handoutPdf: boolean;
|
|
530
|
+
readonly canvas: { readonly width: number; readonly height: number };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
export const DECK_DEFAULTS: Omit<NormalizedDeck, 'canvas'> = {
|
|
534
|
+
aspectRatio: '16:9',
|
|
535
|
+
legend: 'first-use',
|
|
536
|
+
notes: 'reader',
|
|
537
|
+
outcomesFrame: true,
|
|
538
|
+
handoutPdf: true,
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
/** Validate `CourseConfig.deck` and apply its defaults. Throws on a bad value. */
|
|
542
|
+
export function normalizeDeck(config: CourseConfig): NormalizedDeck {
|
|
543
|
+
const deck = config.deck ?? {};
|
|
544
|
+
const choice = <T extends string>(field: keyof CourseDeck, allowed: readonly T[]): T => {
|
|
545
|
+
const value: unknown = deck[field] ?? DECK_DEFAULTS[field as keyof typeof DECK_DEFAULTS];
|
|
546
|
+
if (typeof value !== 'string' || !(allowed as readonly string[]).includes(value)) {
|
|
547
|
+
throw new Error(
|
|
548
|
+
`CourseConfig deck.${field} must be one of ${allowed.map((item) => JSON.stringify(item)).join(', ')}; ` +
|
|
549
|
+
`received ${JSON.stringify(value)}.`,
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
return value as T;
|
|
553
|
+
};
|
|
554
|
+
const flag = (field: 'outcomesFrame' | 'handoutPdf'): boolean => {
|
|
555
|
+
const value: unknown = deck[field] ?? DECK_DEFAULTS[field];
|
|
556
|
+
if (typeof value !== 'boolean') {
|
|
557
|
+
throw new Error(`CourseConfig deck.${field} must be true or false; received ${JSON.stringify(value)}.`);
|
|
558
|
+
}
|
|
559
|
+
return value;
|
|
560
|
+
};
|
|
561
|
+
const aspectRatio = choice<DeckAspectRatio>('aspectRatio', ['16:9', '16:10', '4:3']);
|
|
562
|
+
return {
|
|
563
|
+
aspectRatio,
|
|
564
|
+
legend: choice<DeckLegend>('legend', ['first-use', 'every-frame', 'off']),
|
|
565
|
+
notes: choice<DeckNotes>('notes', ['reader', 'presenter', 'off']),
|
|
566
|
+
outcomesFrame: flag('outcomesFrame'),
|
|
567
|
+
handoutPdf: flag('handoutPdf'),
|
|
568
|
+
canvas: DECK_CANVAS[aspectRatio],
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Whether the course asks each reader if it may remember their reading in
|
|
574
|
+
* their browser (`readingMemory` in the course settings,
|
|
575
|
+
* `session/reading-memory.ts`): which chapters they opened (the sidebar's
|
|
576
|
+
* marks) and the cards they keep or carry, across visits. `ask` (default)
|
|
577
|
+
* shows the question until the reader answers; `off` never asks and never
|
|
578
|
+
* keeps reading memory, so the marks are off and cards last as long as the tab.
|
|
579
|
+
*/
|
|
580
|
+
export type ReadingMemorySetting = 'ask' | 'off';
|
|
581
|
+
|
|
582
|
+
/** Validate `CourseConfig.readingMemory`. Throws on a bad value. */
|
|
583
|
+
export function normalizeReadingMemory(config: CourseConfig): ReadingMemorySetting {
|
|
584
|
+
const value: unknown = config.readingMemory ?? 'ask';
|
|
585
|
+
if (value !== 'ask' && value !== 'off') {
|
|
586
|
+
throw new Error(`CourseConfig readingMemory must be "ask" or "off"; received ${JSON.stringify(value)}.`);
|
|
587
|
+
}
|
|
588
|
+
return value;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* How the page tells its zones apart, and how a deck's slides sit on it
|
|
593
|
+
* (`appearance` in the course settings; `zones.css` and `deck.css` hold the
|
|
594
|
+
* colours). `zones`: `tonal` (the default) gives both sidebars one step of the
|
|
595
|
+
* neutral grey and the header a step further; `accent-header` makes the
|
|
596
|
+
* header a deep band of the course accent; `paper` lays the reading column
|
|
597
|
+
* on the sidebars' colour as a sheet; `none` keeps Starlight's colours.
|
|
598
|
+
* `slides`: `lifted` (the default) gives each slide an edge and a drop
|
|
599
|
+
* shadow, and in the dark theme a sheet a step lighter than the page; `edge`
|
|
600
|
+
* the edge and shadow on the page's own colour; `sorter` lays the deck on a
|
|
601
|
+
* tinted well. Every field is optional; {@link normalizeAppearance} fills in
|
|
602
|
+
* the defaults.
|
|
603
|
+
*/
|
|
604
|
+
export interface CourseAppearance {
|
|
605
|
+
/** `tonal` (default), `accent-header`, `paper` or `none`. */
|
|
606
|
+
readonly zones?: string;
|
|
607
|
+
/** `lifted` (default), `edge` or `sorter`. */
|
|
608
|
+
readonly slides?: string;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
export type AppearanceZones = 'tonal' | 'accent-header' | 'paper' | 'none';
|
|
612
|
+
export type AppearanceSlides = 'lifted' | 'edge' | 'sorter';
|
|
613
|
+
|
|
614
|
+
/** {@link CourseAppearance} with every default applied. */
|
|
615
|
+
export interface NormalizedAppearance {
|
|
616
|
+
readonly zones: AppearanceZones;
|
|
617
|
+
readonly slides: AppearanceSlides;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
export const APPEARANCE_DEFAULTS: NormalizedAppearance = { zones: 'tonal', slides: 'lifted' };
|
|
621
|
+
|
|
622
|
+
const APPEARANCE_ZONES: readonly AppearanceZones[] = ['tonal', 'accent-header', 'paper', 'none'];
|
|
623
|
+
const APPEARANCE_SLIDES: readonly AppearanceSlides[] = ['lifted', 'edge', 'sorter'];
|
|
624
|
+
|
|
625
|
+
/** Validate `CourseConfig.appearance` and apply its defaults. Throws on a bad value. */
|
|
626
|
+
export function normalizeAppearance(config: CourseConfig): NormalizedAppearance {
|
|
627
|
+
const appearance = config.appearance ?? {};
|
|
628
|
+
const choice = <T extends string>(field: keyof NormalizedAppearance, allowed: readonly T[]): T => {
|
|
629
|
+
const value: unknown = appearance[field] ?? APPEARANCE_DEFAULTS[field];
|
|
630
|
+
if (!allowed.includes(value as T)) {
|
|
631
|
+
throw new Error(
|
|
632
|
+
`CourseConfig appearance.${field} must be one of ${allowed.map((v) => `"${v}"`).join(', ')}; ` +
|
|
633
|
+
`received ${JSON.stringify(value)}.`,
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
return value as T;
|
|
637
|
+
};
|
|
638
|
+
return { zones: choice('zones', APPEARANCE_ZONES), slides: choice('slides', APPEARANCE_SLIDES) };
|
|
639
|
+
}
|
|
640
|
+
|
|
389
641
|
/**
|
|
390
642
|
* Rule S2's word ranges (`rules.S2.bridgeMinWords` … in the course settings),
|
|
391
643
|
* in the shape `gate-part-pages.ts` takes. Built by `partPageLimits` in
|
|
@@ -488,6 +740,21 @@ export interface CourseConfig {
|
|
|
488
740
|
readonly numbering?: CourseNumbering;
|
|
489
741
|
/** How explanation cards start for a reader: "Carry cards" on or off. */
|
|
490
742
|
readonly cards?: CourseCards;
|
|
743
|
+
/** The sidebar's marks for a page the reader has not opened, or that changed since. */
|
|
744
|
+
readonly sidebarMarks?: CourseSidebarMarks;
|
|
745
|
+
/** Which edition a lesson's sidebar row opens, and whether it lists the other. */
|
|
746
|
+
readonly sidebarEditions?: CourseSidebarEditions;
|
|
747
|
+
/** How the course's decks look and what they show (`docs/authoring/deck.md`). */
|
|
748
|
+
readonly deck?: CourseDeck;
|
|
749
|
+
/**
|
|
750
|
+
* Ask each reader whether the course may remember their reading in their
|
|
751
|
+
* browser (`ask`, the default), or never keep it (`off`). A plain `string`
|
|
752
|
+
* because the settings arrive as JSON; {@link normalizeReadingMemory}
|
|
753
|
+
* rejects any other value.
|
|
754
|
+
*/
|
|
755
|
+
readonly readingMemory?: string;
|
|
756
|
+
/** How the page tells header, sidebars and article apart, and how slides sit on it. */
|
|
757
|
+
readonly appearance?: CourseAppearance;
|
|
491
758
|
/**
|
|
492
759
|
* How a check's and an example set's heading names its kind: `icon-word`
|
|
493
760
|
* (default) or `icon` ({@link BlockLabels}). A plain `string` because the
|