explico 0.3.0 → 0.5.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 +344 -1
- package/README.md +117 -3
- package/dist/compiler/astro.d.ts +38 -0
- package/dist/compiler/astro.js +258 -0
- package/dist/compiler/cli.d.ts +13 -0
- package/dist/compiler/cli.js +32 -12
- package/dist/compiler/collections.d.ts +3 -2
- package/dist/compiler/collections.js +28 -1
- package/dist/compiler/course-config.d.ts +86 -4
- package/dist/compiler/course-config.js +39 -0
- package/dist/compiler/exports/standalone-html.d.ts +34 -0
- package/dist/compiler/exports/standalone-html.js +803 -0
- package/dist/compiler/lesson-derivation.d.ts +1 -1
- package/dist/compiler/lesson-derivation.js +2 -2
- package/dist/compiler/manifest.d.ts +112 -7
- package/dist/compiler/manifest.js +205 -40
- package/dist/compiler/resolution-report.d.ts +4 -0
- package/dist/reference/base-abbreviations.d.mts +30 -0
- package/dist/reference/base-abbreviations.mjs +60 -0
- package/dist/reference/base-operators.d.mts +15 -0
- package/dist/reference/base-operators.mjs +40 -0
- package/dist/reference/base-roman-labels.d.mts +35 -0
- package/dist/reference/base-roman-labels.mjs +43 -0
- package/dist/reference/consistency.d.ts +8 -2
- package/dist/reference/consistency.js +50 -3
- package/dist/reference/diagrams-validate.d.ts +21 -0
- package/dist/reference/diagrams-validate.js +13 -0
- package/dist/reference/equations-validate.d.ts +8 -3
- package/dist/reference/equations-validate.js +1 -1
- package/dist/reference/equations.d.mts +66 -35
- package/dist/reference/equations.mjs +142 -40
- package/dist/reference/gate-abbreviations.d.ts +17 -0
- package/dist/reference/gate-abbreviations.js +64 -0
- package/dist/reference/gate-math.d.ts +28 -5
- package/dist/reference/gate-math.js +5 -2
- package/dist/reference/gate-prose-notation.d.ts +60 -0
- package/dist/reference/gate-prose-notation.js +125 -0
- package/dist/reference/keyed-float-validate.d.ts +14 -8
- package/dist/reference/keyed-float-validate.js +1 -1
- package/dist/reference/keyed-jsx-floats.d.mts +24 -21
- package/dist/reference/keyed-jsx-floats.mjs +90 -41
- package/dist/reference/knowledge-checks-validate.d.ts +35 -0
- package/dist/reference/knowledge-checks-validate.js +78 -0
- package/dist/reference/knowledge-checks.d.mts +25 -0
- package/dist/reference/knowledge-checks.mjs +45 -0
- package/dist/reference/lab-math-scope.d.ts +9 -1
- package/dist/reference/lab-math-scope.js +32 -4
- package/dist/reference/math-glyphs.d.mts +30 -0
- package/dist/reference/math-glyphs.mjs +367 -0
- package/dist/reference/mdx-attributes.d.mts +13 -0
- package/dist/reference/mdx-attributes.mjs +38 -0
- package/dist/reference/mixed-content.d.ts +17 -0
- package/dist/reference/mixed-content.js +29 -0
- package/dist/reference/page-notation.d.ts +52 -0
- package/dist/reference/page-notation.js +171 -0
- package/dist/reference/raw-floats-validate.d.ts +18 -0
- package/dist/reference/raw-floats-validate.js +53 -0
- package/dist/reference/raw-floats.d.mts +18 -0
- package/dist/reference/raw-floats.mjs +129 -0
- package/dist/reference/rehype-heading-anchors.d.mts +1 -1
- package/dist/reference/rehype-heading-anchors.mjs +107 -1
- package/dist/reference/rehype-table-header-labels.d.mts +26 -0
- package/dist/reference/rehype-table-header-labels.mjs +152 -0
- package/dist/reference/remark-abbreviations.d.mts +7 -0
- package/dist/reference/remark-abbreviations.mjs +174 -0
- package/dist/reference/remark-memo.d.mts +16 -0
- package/dist/reference/remark-memo.mjs +339 -0
- package/dist/reference/remark-notation.d.mts +102 -5
- package/dist/reference/remark-notation.mjs +506 -50
- package/dist/reference/remark-short-note.d.mts +4 -0
- package/dist/reference/remark-short-note.mjs +237 -0
- package/dist/reference/types.d.ts +16 -1
- package/dist/reference/units-vocab.js +4 -0
- package/docs/VERSIONING.md +33 -4
- package/docs/authoring/README.md +12 -0
- package/docs/authoring/agents.md +17 -2
- package/docs/authoring/components.md +135 -0
- package/docs/authoring/migrating.md +154 -7
- package/docs/authoring/notation.md +568 -8
- package/docs/authoring/structure.md +251 -0
- package/docs/authoring/visualizations.md +36 -3
- package/package.json +5 -2
- package/src/compiler/astro.ts +332 -0
- package/src/compiler/cli.ts +33 -10
- package/src/compiler/collections.ts +45 -4
- package/src/compiler/course-config.ts +131 -3
- package/src/compiler/exports/standalone-html.ts +963 -0
- package/src/compiler/lesson-derivation.ts +2 -2
- package/src/compiler/manifest.ts +367 -49
- package/src/components/HoverPanel.astro +45 -4
- package/src/components/abbreviation/AbbreviationGlossary.astro +70 -0
- package/src/components/abbreviation/AbbreviationLayer.astro +127 -0
- package/src/components/assessments/AssessmentRunner.tsx +52 -17
- package/src/components/assessments/AssessmentSet.astro +98 -14
- package/src/components/assessments/RenderedContent.tsx +55 -0
- package/src/components/assessments/assessment-randomization.ts +16 -0
- package/src/components/diagram/Diagram.astro +444 -83
- package/src/components/disclosure/hover-preview.ts +202 -0
- package/src/components/disclosure/persisted-disclosure.ts +66 -0
- package/src/components/examples/CompactExamples.astro +2 -0
- package/src/components/figure/Figure.astro +279 -10
- package/src/components/memo/Memo.astro +453 -0
- package/src/components/memo/ShortNote.astro +223 -0
- package/src/components/notation/NotationFormula.astro +18 -1
- package/src/components/notation/NotationLayer.astro +116 -8
- package/src/components/notation/render-notation-math.ts +39 -2
- package/src/components/notation/safe-math-to-react.ts +50 -0
- package/src/components/reference/FloatRefLayer.astro +233 -15
- package/src/components/starlight/CollapsibleSections.astro +461 -0
- package/src/components/starlight/DiagramEnhancer.astro +385 -0
- package/src/components/starlight/HeadingAnchorEnhancer.astro +5 -17
- package/src/components/starlight/KeyedFloatEnhancer.astro +81 -3
- package/src/components/starlight/LayoutHeader.astro +50 -0
- package/src/components/starlight/LayoutSidebar.astro +62 -1
- package/src/components/starlight/LayoutSocialIcons.astro +41 -2
- package/src/components/starlight/LessonExportActions.astro +436 -0
- package/src/components/starlight/LessonFooter.astro +38 -2
- package/src/components/starlight/section-parts.ts +31 -0
- package/src/components/table/Table.astro +296 -9
- package/src/components/table/TableOverflowExpander.astro +489 -0
- package/src/content-config.ts +20 -0
- package/src/reference/base-abbreviations.mjs +60 -0
- package/src/reference/base-operators.mjs +40 -0
- package/src/reference/base-roman-labels.mjs +43 -0
- package/src/reference/consistency.ts +73 -2
- package/src/reference/diagrams-validate.ts +40 -0
- package/src/reference/equations-validate.ts +6 -4
- package/src/reference/equations.mjs +142 -40
- package/src/reference/gate-abbreviations.ts +128 -0
- package/src/reference/gate-math.ts +27 -7
- package/src/reference/gate-prose-notation.ts +224 -0
- package/src/reference/keyed-float-validate.ts +12 -9
- package/src/reference/keyed-jsx-floats.mjs +90 -41
- package/src/reference/knowledge-checks-validate.ts +130 -0
- package/src/reference/knowledge-checks.mjs +45 -0
- package/src/reference/lab-math-scope.ts +45 -3
- package/src/reference/math-glyphs.mjs +367 -0
- package/src/reference/mdx-attributes.mjs +38 -0
- package/src/reference/mixed-content.ts +69 -0
- package/src/reference/page-notation.ts +252 -0
- package/src/reference/raw-floats-validate.ts +103 -0
- package/src/reference/raw-floats.mjs +129 -0
- package/src/reference/rehype-heading-anchors.mjs +107 -1
- package/src/reference/rehype-table-header-labels.mjs +152 -0
- package/src/reference/remark-abbreviations.mjs +174 -0
- package/src/reference/remark-memo.mjs +339 -0
- package/src/reference/remark-notation.mjs +506 -50
- package/src/reference/remark-short-note.mjs +237 -0
- package/src/reference/types.ts +16 -0
- package/src/reference/units-vocab.ts +4 -0
- package/src/styles/global.css +1125 -50
package/src/compiler/cli.ts
CHANGED
|
@@ -40,7 +40,11 @@ import remarkMdx from 'remark-mdx';
|
|
|
40
40
|
import { createNotationKatexOptions } from '../reference/katex-options.mjs';
|
|
41
41
|
import rehypeFailKatexErrors from '../reference/rehype-fail-katex-errors.mjs';
|
|
42
42
|
import remarkCitation from '../reference/remark-citation.mjs';
|
|
43
|
-
import
|
|
43
|
+
import remarkMemo from '../reference/remark-memo.mjs';
|
|
44
|
+
import remarkNotation, {
|
|
45
|
+
createRemarkNotationOptions,
|
|
46
|
+
} from '../reference/remark-notation.mjs';
|
|
47
|
+
import remarkShortNote from '../reference/remark-short-note.mjs';
|
|
44
48
|
import type { Manifest, ManifestLesson } from './manifest';
|
|
45
49
|
import { isLessonSlug, lessonIdFromSlug } from './lesson-derivation';
|
|
46
50
|
|
|
@@ -173,6 +177,9 @@ export function lessonDiagnostics(
|
|
|
173
177
|
for (const diagnostic of [
|
|
174
178
|
...manifest.diagnostics.tables,
|
|
175
179
|
...manifest.diagnostics.figures,
|
|
180
|
+
...manifest.diagnostics.diagrams,
|
|
181
|
+
...manifest.diagnostics.checks,
|
|
182
|
+
...manifest.diagnostics.rawFloats,
|
|
176
183
|
]) {
|
|
177
184
|
if (
|
|
178
185
|
diagnostic.lessonId !== id &&
|
|
@@ -221,15 +228,18 @@ function processorFor(manifest: Manifest) {
|
|
|
221
228
|
// shape here that it does there (D7/F-tables-figures parity).
|
|
222
229
|
remarkMdx,
|
|
223
230
|
remarkMath,
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
231
|
+
remarkShortNote,
|
|
232
|
+
remarkMemo,
|
|
233
|
+
// `manifest` carries the number tables *and* the part/offset placement
|
|
234
|
+
// together, so this check numbers a page exactly as the Astro build
|
|
235
|
+
// does. Passing the tables alone silently numbered pages by `##` section
|
|
236
|
+
// here while cross-page references read the part-scheme manifest.
|
|
237
|
+
// No `strict: true` here: `renderLesson()` below is also called
|
|
238
|
+
// directly with a synthetic path/body that has no manifest entry at all
|
|
239
|
+
// (fixture renderability checks — see its own doc comment), which
|
|
240
|
+
// strict mode would reject as "catalogued lesson with no numbering",
|
|
241
|
+
// even though it was never meant to be catalogued in the first place.
|
|
242
|
+
[remarkNotation, createRemarkNotationOptions(manifest)],
|
|
233
243
|
[remarkCitation, { sources: manifest.sources }],
|
|
234
244
|
],
|
|
235
245
|
rehypePlugins: [
|
|
@@ -480,6 +490,7 @@ export interface StatusReport {
|
|
|
480
490
|
readonly keyedEquations: number;
|
|
481
491
|
readonly keyedTables: number;
|
|
482
492
|
readonly keyedFigures: number;
|
|
493
|
+
readonly keyedDiagrams: number;
|
|
483
494
|
};
|
|
484
495
|
readonly draftDebt: {
|
|
485
496
|
readonly lessons: readonly string[];
|
|
@@ -525,6 +536,9 @@ export function contentStatus(manifest: Manifest): StatusReport {
|
|
|
525
536
|
...manifest.diagnostics.equations.map((d) => d.severity),
|
|
526
537
|
...manifest.diagnostics.tables.map((d) => d.severity),
|
|
527
538
|
...manifest.diagnostics.figures.map((d) => d.severity),
|
|
539
|
+
...manifest.diagnostics.diagrams.map((d) => d.severity),
|
|
540
|
+
...manifest.diagnostics.checks.map((d) => d.severity),
|
|
541
|
+
...manifest.diagnostics.rawFloats.map((d) => d.severity),
|
|
528
542
|
...manifest.diagnostics.consistency.map(() => 'warning' as const),
|
|
529
543
|
...manifest.diagnostics.knowledge.map((d) => d.severity),
|
|
530
544
|
];
|
|
@@ -541,6 +555,10 @@ export function contentStatus(manifest: Manifest): StatusReport {
|
|
|
541
555
|
(count, lesson) => count + lesson.figures.length,
|
|
542
556
|
0,
|
|
543
557
|
);
|
|
558
|
+
const keyedDiagrams = manifest.lessons.reduce(
|
|
559
|
+
(count, lesson) => count + lesson.diagrams.length,
|
|
560
|
+
0,
|
|
561
|
+
);
|
|
544
562
|
const assessments = new Set(
|
|
545
563
|
manifest.lessons.flatMap((lesson) => lesson.assessments),
|
|
546
564
|
).size;
|
|
@@ -557,6 +575,7 @@ export function contentStatus(manifest: Manifest): StatusReport {
|
|
|
557
575
|
keyedEquations,
|
|
558
576
|
keyedTables,
|
|
559
577
|
keyedFigures,
|
|
578
|
+
keyedDiagrams,
|
|
560
579
|
},
|
|
561
580
|
draftDebt: { lessons: draftLessons, notation: draftNotation },
|
|
562
581
|
orphans,
|
|
@@ -597,6 +616,8 @@ export interface ContextReport {
|
|
|
597
616
|
readonly equations: readonly { key: string; number: string }[];
|
|
598
617
|
readonly tables: readonly { key: string; number: string }[];
|
|
599
618
|
readonly figures: readonly { key: string; number: string }[];
|
|
619
|
+
readonly diagrams: readonly { key: string; number: string }[];
|
|
620
|
+
readonly knowledgeChecks: readonly { key: string; number: string }[];
|
|
600
621
|
readonly sources: readonly {
|
|
601
622
|
id: string;
|
|
602
623
|
title: string;
|
|
@@ -667,6 +688,8 @@ export function contentContext(
|
|
|
667
688
|
equations: lesson.equations.map((equation) => ({ ...equation })),
|
|
668
689
|
tables: lesson.tables.map((table) => ({ ...table })),
|
|
669
690
|
figures: lesson.figures.map((figure) => ({ ...figure })),
|
|
691
|
+
diagrams: lesson.diagrams.map((diagram) => ({ ...diagram })),
|
|
692
|
+
knowledgeChecks: lesson.knowledgeChecks.map((check) => ({ ...check })),
|
|
670
693
|
sources: lesson.sources.map((id) => {
|
|
671
694
|
const record = sourceById.get(id) as Record<string, unknown> | undefined;
|
|
672
695
|
return {
|
|
@@ -24,6 +24,7 @@ import type {
|
|
|
24
24
|
TrackDefinition,
|
|
25
25
|
} from '../curriculum/validation';
|
|
26
26
|
import type {
|
|
27
|
+
AbbreviationDefinitionInput,
|
|
27
28
|
EditorialStatus,
|
|
28
29
|
LocalNotationDefinitionInput,
|
|
29
30
|
NotationAlignment,
|
|
@@ -37,6 +38,8 @@ import type {
|
|
|
37
38
|
import { isSubstantiveMeaning } from '../reference/prose';
|
|
38
39
|
import { parseEquationRef } from '../reference/equations.mjs';
|
|
39
40
|
import { parseKeyedFloatRef } from '../reference/keyed-jsx-floats.mjs';
|
|
41
|
+
import { parseKnowledgeCheckRef } from '../reference/knowledge-checks.mjs';
|
|
42
|
+
import { parseMemoRef } from '../reference/remark-memo.mjs';
|
|
40
43
|
import { resolveLabel } from '../reference/label';
|
|
41
44
|
import { resolveGlosses } from '../reference/gloss';
|
|
42
45
|
import {
|
|
@@ -231,13 +234,17 @@ export function extractNotationReferences(
|
|
|
231
234
|
for (const match of searchable.matchAll(pattern)) {
|
|
232
235
|
const key = (match[1] ?? '').trim();
|
|
233
236
|
// `[[eq-key]]` / `[[slug#eq-key]]` are equation references (D7), and
|
|
234
|
-
// `[[tab-key]]`/`[[fig-key]]` (and their cross-page forms)
|
|
235
|
-
//
|
|
236
|
-
//
|
|
237
|
+
// `[[tab-key]]`/`[[fig-key]]`/`[[dia-key]]` (and their cross-page forms)
|
|
238
|
+
// are keyed-float references, `[[check-key]]` targets a lesson's
|
|
239
|
+
// assessment list, and `[[memo-key]]` is a page-local memo reference —
|
|
240
|
+
// all resolved by the remark pass and the manifest, not notation keys.
|
|
237
241
|
if (
|
|
238
242
|
parseEquationRef(key) ||
|
|
239
243
|
parseKeyedFloatRef(key, { prefix: 'tab' }) ||
|
|
240
|
-
parseKeyedFloatRef(key, { prefix: 'fig' })
|
|
244
|
+
parseKeyedFloatRef(key, { prefix: 'fig' }) ||
|
|
245
|
+
parseKeyedFloatRef(key, { prefix: 'dia' }) ||
|
|
246
|
+
parseKnowledgeCheckRef(key) ||
|
|
247
|
+
parseMemoRef(key)
|
|
241
248
|
) {
|
|
242
249
|
continue;
|
|
243
250
|
}
|
|
@@ -438,6 +445,40 @@ function sharedNotationEntries(): SharedNotationDefinitionInput[] {
|
|
|
438
445
|
});
|
|
439
446
|
}
|
|
440
447
|
|
|
448
|
+
/**
|
|
449
|
+
* Abbreviations table (rule N14) — a lighter tier than a notation card, read
|
|
450
|
+
* straight off disk the same way `sharedNotationEntries` reads `notation/`.
|
|
451
|
+
* The directory is optional: a course that never uses this feature need not
|
|
452
|
+
* create an empty `content/abbreviations/`.
|
|
453
|
+
*/
|
|
454
|
+
function abbreviationEntries(): AbbreviationDefinitionInput[] {
|
|
455
|
+
const directory = join(CONTENT_ROOT, 'abbreviations');
|
|
456
|
+
if (!existsSync(directory)) return [];
|
|
457
|
+
return filesBelow(directory, ['.md']).map((path) => {
|
|
458
|
+
const { file, data } = parseDocument(path);
|
|
459
|
+
const key = string(data.key, `${file} key`);
|
|
460
|
+
const filenameKey = basename(path, '.md');
|
|
461
|
+
if (key !== filenameKey) {
|
|
462
|
+
throw new Error(`${file} declares key ${key}; expected ${filenameKey}`);
|
|
463
|
+
}
|
|
464
|
+
const domain = optionalString(data.domain, `${file} domain`);
|
|
465
|
+
return {
|
|
466
|
+
key,
|
|
467
|
+
abbr: string(data.abbr, `${file} abbr`),
|
|
468
|
+
expansion: meaningText(data.expansion, `${file} expansion`),
|
|
469
|
+
aliases: strings(data.aliases, `${file} aliases`),
|
|
470
|
+
source: { file },
|
|
471
|
+
...(domain === undefined ? {} : { domain }),
|
|
472
|
+
};
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export async function loadAbbreviationRegistryInput(): Promise<
|
|
477
|
+
readonly AbbreviationDefinitionInput[]
|
|
478
|
+
> {
|
|
479
|
+
return abbreviationEntries();
|
|
480
|
+
}
|
|
481
|
+
|
|
441
482
|
function notationLessonEntries(): NotationLessonInput[] {
|
|
442
483
|
const lessons: NotationLessonInput[] = [];
|
|
443
484
|
for (const path of filesBelow(join(CONTENT_ROOT, 'docs'), ['.md', '.mdx'])) {
|
|
@@ -7,13 +7,25 @@
|
|
|
7
7
|
* supplies its own value of this shape.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* One **part** — a sidebar group, holding the chapters (lessons) whose slugs
|
|
12
|
+
* start with `dir`. A part's 1-based position in `CourseConfig.parts` is the
|
|
13
|
+
* number before the dot in every equation, table, and figure number inside it.
|
|
14
|
+
* See `docs/authoring/structure.md` for the full level taxonomy.
|
|
15
|
+
*/
|
|
16
|
+
export interface CoursePart {
|
|
11
17
|
/** Lesson-slug prefix, e.g. `bonds` for `bonds/yield-to-maturity`. */
|
|
12
18
|
readonly dir: string;
|
|
13
19
|
/** Sidebar group heading. */
|
|
14
20
|
readonly label: string;
|
|
15
21
|
}
|
|
16
22
|
|
|
23
|
+
/**
|
|
24
|
+
* @deprecated Renamed to {@link CoursePart}. "Section" now only ever means an
|
|
25
|
+
* `##` heading inside a lesson. Removed in the next MINOR release.
|
|
26
|
+
*/
|
|
27
|
+
export type CourseSection = CoursePart;
|
|
28
|
+
|
|
17
29
|
export interface CourseConventions {
|
|
18
30
|
/** Notation key a lesson's stated sign convention must resolve to. */
|
|
19
31
|
readonly signConventionKey: string;
|
|
@@ -37,6 +49,39 @@ export interface CourseOperators {
|
|
|
37
49
|
readonly deny?: readonly string[];
|
|
38
50
|
}
|
|
39
51
|
|
|
52
|
+
/**
|
|
53
|
+
* A course's edits to the shipped roman-label baseline (`BASE_ROMAN_LABELS`),
|
|
54
|
+
* consumed by the `roman-label-explained` consistency check (rule N10). A
|
|
55
|
+
* `\mathrm{…}` multi-letter label a lesson uses is fine when it is on the
|
|
56
|
+
* baseline plus `allow` minus `deny`; otherwise it needs a notation entry the
|
|
57
|
+
* reader can hover. Same shape as {@link CourseOperators}, one level down:
|
|
58
|
+
* this governs `\mathrm{KE}`-style labels, not `\operatorname{Var}`-style
|
|
59
|
+
* operators.
|
|
60
|
+
*/
|
|
61
|
+
export interface CourseRomanLabels {
|
|
62
|
+
/** Labels this course also treats as universal qualifiers (`eff`, `net`, …). */
|
|
63
|
+
readonly allow?: readonly string[];
|
|
64
|
+
/** Baseline labels this course wants explained anyway. */
|
|
65
|
+
readonly deny?: readonly string[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A course's edits to the shipped abbreviation baseline (`BASE_ABBREVIATIONS`),
|
|
70
|
+
* consumed by the `abbreviation-explained` gate (rule N14). An abbreviation-
|
|
71
|
+
* shaped token — an all-caps acronym or a dotted form like `w.r.t.` — a
|
|
72
|
+
* lesson uses in prose is fine when it is on the baseline plus `allow` minus
|
|
73
|
+
* `deny`; otherwise it needs an entry in the `abbreviations` content
|
|
74
|
+
* collection, or — if it also names a math quantity — a notation entry the
|
|
75
|
+
* reader can hover. Same shape as {@link CourseOperators} / {@link
|
|
76
|
+
* CourseRomanLabels}.
|
|
77
|
+
*/
|
|
78
|
+
export interface CourseAbbreviations {
|
|
79
|
+
/** Abbreviations this course also treats as universal (`GDP`, `CPU`, …). */
|
|
80
|
+
readonly allow?: readonly string[];
|
|
81
|
+
/** Baseline abbreviations this course wants explained anyway. */
|
|
82
|
+
readonly deny?: readonly string[];
|
|
83
|
+
}
|
|
84
|
+
|
|
40
85
|
/** One person or team credited for the study material. */
|
|
41
86
|
export interface CourseAuthor {
|
|
42
87
|
readonly name: string;
|
|
@@ -71,11 +116,39 @@ export interface CourseAbout {
|
|
|
71
116
|
readonly changelogHref?: string;
|
|
72
117
|
}
|
|
73
118
|
|
|
119
|
+
/** Optional, build-time lesson artifacts exposed to readers. */
|
|
120
|
+
export interface LessonExportsConfig {
|
|
121
|
+
/** Emit one self-contained, offline-capable HTML file per lesson. */
|
|
122
|
+
readonly standaloneHtml?: boolean;
|
|
123
|
+
/** Offer the browser's native print dialog with explico's lesson print layout. */
|
|
124
|
+
readonly print?: boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Published course root used for links that intentionally leave the exported
|
|
127
|
+
* lesson. Required at export time when such a link is present.
|
|
128
|
+
*/
|
|
129
|
+
readonly canonicalBaseUrl?: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** The stable, non-optional export settings carried by the manifest. */
|
|
133
|
+
export interface NormalizedLessonExports {
|
|
134
|
+
readonly standaloneHtml: boolean;
|
|
135
|
+
readonly print: boolean;
|
|
136
|
+
readonly canonicalBaseUrl?: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
74
139
|
export interface CourseConfig {
|
|
75
140
|
readonly title: string;
|
|
76
141
|
readonly description: string;
|
|
77
|
-
/**
|
|
78
|
-
|
|
142
|
+
/**
|
|
143
|
+
* The course's parts (sidebar groups), in order. Exactly one of `parts` or
|
|
144
|
+
* the deprecated `sections` must be given.
|
|
145
|
+
*/
|
|
146
|
+
readonly parts?: readonly CoursePart[];
|
|
147
|
+
/**
|
|
148
|
+
* @deprecated Renamed to {@link CourseConfig.parts}. Still honoured, so a
|
|
149
|
+
* course on the old name keeps building; removed in the next MINOR release.
|
|
150
|
+
*/
|
|
151
|
+
readonly sections?: readonly CoursePart[];
|
|
79
152
|
/** Namespaces used by competency / notation `domain` fields. */
|
|
80
153
|
readonly domains: readonly string[];
|
|
81
154
|
readonly conventions: CourseConventions;
|
|
@@ -83,4 +156,59 @@ export interface CourseConfig {
|
|
|
83
156
|
readonly about: CourseAbout;
|
|
84
157
|
/** Edits to the shipped named-operator baseline (rule N8). Optional. */
|
|
85
158
|
readonly operators?: CourseOperators;
|
|
159
|
+
/** Edits to the shipped roman-label baseline (rule N10). Optional. */
|
|
160
|
+
readonly romanLabels?: CourseRomanLabels;
|
|
161
|
+
/** Edits to the shipped abbreviation baseline (rule N14). Optional. */
|
|
162
|
+
readonly abbreviations?: CourseAbbreviations;
|
|
163
|
+
/** Per-lesson export actions and downloadable artifacts. Disabled when omitted. */
|
|
164
|
+
readonly lessonExports?: LessonExportsConfig;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Validate and canonicalize the build-facing lesson-export configuration. */
|
|
168
|
+
export function normalizeLessonExports(
|
|
169
|
+
config: CourseConfig,
|
|
170
|
+
): NormalizedLessonExports {
|
|
171
|
+
const standaloneHtml = config.lessonExports?.standaloneHtml === true;
|
|
172
|
+
const print = config.lessonExports?.print === true;
|
|
173
|
+
const rawBase = config.lessonExports?.canonicalBaseUrl;
|
|
174
|
+
if (rawBase === undefined) return { standaloneHtml, print };
|
|
175
|
+
|
|
176
|
+
let parsed: URL;
|
|
177
|
+
try {
|
|
178
|
+
parsed = new URL(rawBase);
|
|
179
|
+
} catch {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`CourseConfig lessonExports.canonicalBaseUrl must be an absolute ` +
|
|
182
|
+
`http(s) URL; received ${JSON.stringify(rawBase)}.`,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`CourseConfig lessonExports.canonicalBaseUrl must use http(s); ` +
|
|
188
|
+
`received ${JSON.stringify(rawBase)}.`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (parsed.search || parsed.hash) {
|
|
192
|
+
throw new Error(
|
|
193
|
+
'CourseConfig lessonExports.canonicalBaseUrl cannot contain a query or fragment.',
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
if (!parsed.pathname.endsWith('/')) parsed.pathname += '/';
|
|
197
|
+
return { standaloneHtml, print, canonicalBaseUrl: parsed.toString() };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* The course's parts, from whichever field the config uses. Throws rather than
|
|
202
|
+
* silently producing a course with no sidebar groups — a config with neither
|
|
203
|
+
* field would otherwise number every float by the `##`-section fallback.
|
|
204
|
+
*/
|
|
205
|
+
export function coursePartsOf(config: CourseConfig): readonly CoursePart[] {
|
|
206
|
+
const parts = config.parts ?? config.sections;
|
|
207
|
+
if (!parts || parts.length === 0) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
'CourseConfig needs at least one entry in `parts` (a sidebar group: ' +
|
|
210
|
+
'{ dir, label }). The old name `sections` is still accepted.',
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
return parts;
|
|
86
214
|
}
|