explico 0.7.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1409 -1
- package/README.md +60 -15
- package/dist/compiler/astro.d.ts +6 -1
- package/dist/compiler/cli.d.ts +54 -3
- package/dist/compiler/cli.js +80 -6
- package/dist/compiler/collections.d.ts +35 -2
- package/dist/compiler/collections.js +84 -8
- package/dist/compiler/course-config.d.ts +3 -194
- package/dist/compiler/course-config.js +78 -5
- package/dist/compiler/exports/standalone-html.d.ts +6 -1
- package/dist/compiler/knowledge-version.js +1 -1
- package/dist/compiler/lesson-derivation.d.ts +27 -10
- package/dist/compiler/lesson-derivation.js +23 -8
- package/dist/compiler/logging.d.ts +14 -1
- package/dist/compiler/logging.js +6 -2
- package/dist/compiler/manifest.d.ts +204 -31
- package/dist/compiler/manifest.js +270 -55
- package/dist/compiler/resolution-report.d.ts +6 -1
- package/dist/course-config-Dsnri9Tk.d.ts +464 -0
- package/dist/reference/base-accents.d.mts +26 -0
- package/dist/reference/base-accents.mjs +31 -0
- package/dist/reference/base-roman-labels.d.mts +2 -2
- package/dist/reference/base-roman-labels.mjs +2 -2
- package/dist/reference/consistency.d.ts +14 -2
- package/dist/reference/consistency.js +108 -4
- package/dist/reference/delimiter-size.d.ts +21 -11
- package/dist/reference/delimiter-size.js +15 -39
- package/dist/reference/diagrams-validate.d.ts +7 -2
- package/dist/reference/diagrams-validate.js +9 -1
- package/dist/reference/equations-validate.d.ts +8 -5
- package/dist/reference/equations-validate.js +1 -0
- package/dist/reference/equations.d.mts +69 -34
- package/dist/reference/equations.mjs +90 -44
- package/dist/reference/examples-validate.d.ts +31 -0
- package/dist/reference/examples-validate.js +48 -0
- package/dist/reference/figures-validate.d.ts +1 -1
- package/dist/reference/figures-validate.js +4 -1
- package/dist/reference/folds-validate.d.ts +33 -0
- package/dist/reference/folds-validate.js +32 -0
- package/dist/reference/gate-abbreviations.d.ts +3 -1
- package/dist/reference/gate-abbreviations.js +3 -2
- package/dist/reference/gate-course-rules.d.ts +32 -0
- package/dist/reference/gate-course-rules.js +85 -0
- package/dist/reference/gate-heading-length.d.ts +5 -2
- package/dist/reference/gate-heading-length.js +47 -23
- package/dist/reference/gate-heading-structure.d.ts +41 -0
- package/dist/reference/gate-heading-structure.js +113 -0
- package/dist/reference/gate-math-punctuation.d.ts +71 -0
- package/dist/reference/gate-math-punctuation.js +251 -0
- package/dist/reference/gate-math.d.ts +44 -6
- package/dist/reference/gate-math.js +63 -10
- package/dist/reference/gate-part-pages.d.ts +50 -0
- package/dist/reference/gate-part-pages.js +118 -0
- package/dist/reference/gate-prose-notation.d.ts +32 -11
- package/dist/reference/gate-prose-notation.js +30 -8
- package/dist/reference/katex-delimiters.d.mts +15 -0
- package/dist/reference/katex-delimiters.mjs +138 -0
- package/dist/reference/katex-options.mjs +4 -0
- package/dist/reference/keyed-float-validate.d.ts +27 -10
- package/dist/reference/keyed-float-validate.js +15 -1
- package/dist/reference/keyed-jsx-floats.d.mts +11 -5
- package/dist/reference/keyed-jsx-floats.mjs +73 -20
- package/dist/reference/knowledge-checks-validate.d.ts +1 -1
- package/dist/reference/knowledge-checks.d.mts +3 -1
- package/dist/reference/knowledge-checks.mjs +9 -4
- package/dist/reference/lab-math-scope.d.ts +1 -0
- package/dist/reference/lab-math-slot.d.ts +34 -0
- package/dist/reference/lab-math-slot.js +6 -0
- package/dist/reference/math-glyphs.d.mts +80 -4
- package/dist/reference/math-glyphs.mjs +588 -39
- package/dist/reference/page-notation.d.ts +6 -1
- package/dist/reference/raw-floats-validate.js +1 -1
- package/dist/reference/raw-floats.mjs +1 -1
- package/dist/reference/rehype-heading-anchors.d.mts +14 -0
- package/dist/reference/rehype-heading-anchors.mjs +116 -17
- package/dist/reference/rehype-table-header-labels.d.mts +6 -1
- package/dist/reference/rehype-table-header-labels.mjs +21 -3
- package/dist/reference/remark-asides.d.mts +3 -0
- package/dist/reference/remark-asides.mjs +180 -0
- package/dist/reference/remark-memo.d.mts +3 -1
- package/dist/reference/remark-memo.mjs +42 -11
- package/dist/reference/remark-notation.d.mts +60 -10
- package/dist/reference/remark-notation.mjs +387 -36
- package/dist/reference/render-lab-math.d.ts +4 -26
- package/dist/reference/render-lab-math.js +5 -5
- package/dist/reference/sections-validate.d.ts +27 -0
- package/dist/reference/sections-validate.js +41 -0
- package/dist/reference/sections.d.mts +97 -0
- package/dist/reference/sections.mjs +306 -0
- package/dist/reference/tables-validate.d.ts +1 -1
- package/dist/reference/types.d.ts +24 -1
- package/dist/reference/unit-words.d.ts +37 -0
- package/dist/reference/unit-words.js +170 -0
- package/dist/rules/catalog.d.ts +176 -0
- package/dist/rules/catalog.js +983 -0
- package/dist/rules/course-rules.d.ts +115 -0
- package/dist/rules/course-rules.js +292 -0
- package/dist/rules/course-rules.schema.json +138 -0
- package/dist/rules/in-effect.d.ts +53 -0
- package/dist/rules/in-effect.js +142 -0
- package/dist/rules/rules.schema.json +1072 -0
- package/dist/rules/settings.d.ts +3 -0
- package/dist/rules/settings.js +290 -0
- package/dist/rules/waivers.d.ts +53 -0
- package/dist/rules/waivers.js +132 -0
- package/dist/session/PreferenceStore.d.ts +5 -0
- package/dist/session/PreferenceStore.js +9 -0
- package/docs/VERSIONING.md +2 -2
- package/docs/authoring/README.md +232 -40
- package/docs/authoring/agents.md +55 -9
- package/docs/authoring/components.md +178 -22
- package/docs/authoring/migrating.md +1324 -2
- package/docs/authoring/notation.md +616 -344
- package/docs/authoring/prose.md +551 -0
- package/docs/authoring/retired.md +35 -0
- package/docs/authoring/structure-rules.md +422 -40
- package/docs/authoring/structure.md +445 -62
- package/docs/authoring/versioning.md +20 -28
- package/docs/authoring/visualizations.md +142 -20
- package/docs/katex-bracket-scaling.md +191 -0
- package/package.json +3 -2
- package/src/compiler/cli.ts +139 -6
- package/src/compiler/collections.ts +144 -10
- package/src/compiler/course-config.ts +273 -34
- package/src/compiler/knowledge-version.ts +1 -1
- package/src/compiler/lesson-derivation.ts +54 -15
- package/src/compiler/logging.ts +21 -4
- package/src/compiler/manifest.ts +573 -104
- package/src/components/BlockHeading.astro +113 -0
- package/src/components/CourseContents.astro +221 -0
- package/src/components/CurriculumMap.astro +5 -6
- package/src/components/FloatIndex.astro +200 -0
- package/src/components/HoverPanel.astro +431 -79
- package/src/components/PartBridge.astro +27 -0
- package/src/components/PartOverview.astro +401 -0
- package/src/components/PartScope.astro +27 -0
- package/src/components/abbreviation/AbbreviationLayer.astro +49 -51
- package/src/components/assessments/AssessmentRunner.tsx +8 -5
- package/src/components/assessments/AssessmentSet.astro +23 -26
- package/src/components/block-heading.ts +146 -0
- package/src/components/card-arrow.ts +243 -0
- package/src/components/card-drag.ts +631 -0
- package/src/components/card-jump.ts +45 -0
- package/src/components/card-manager.ts +1589 -0
- package/src/components/card-memory.ts +177 -0
- package/src/components/card-placement.ts +504 -0
- package/src/components/card-side.ts +336 -0
- package/src/components/card-travel.ts +197 -0
- package/src/components/citation/CitationLayer.astro +77 -76
- package/src/components/diagram/Diagram.astro +161 -316
- package/src/components/disclosure/course-disclosure.ts +24 -0
- package/src/components/disclosure/hover-preview.ts +122 -42
- package/src/components/disclosure/persisted-disclosure.ts +3 -3
- package/src/components/disclosure/print-as-shown.ts +252 -0
- package/src/components/examples/CompactExample.astro +3 -1
- package/src/components/examples/CompactExamples.astro +101 -94
- package/src/components/figure/Figure.astro +174 -274
- package/src/components/figure/FloatCaption.astro +87 -0
- package/src/components/figure/figure-state.ts +58 -0
- package/src/components/figure/figure-tools.ts +87 -0
- package/src/components/float-name.ts +101 -0
- package/src/components/fold/Fold.astro +125 -0
- package/src/components/fold/fold-id.ts +40 -0
- package/src/components/memo/Memo.astro +162 -231
- package/src/components/memo/ShortNote.astro +6 -6
- package/src/components/notation/NotationFormula.astro +5 -1
- package/src/components/notation/NotationGlossary.astro +24 -14
- package/src/components/notation/NotationLayer.astro +169 -162
- package/src/components/notation/NotationMath.astro +10 -1
- package/src/components/notation/render-notation-math.ts +111 -10
- package/src/components/reference/FloatRefLayer.astro +485 -423
- package/src/components/starlight/AboutPanel.astro +22 -6
- package/src/components/starlight/CollapsibleSections.astro +207 -93
- package/src/components/starlight/DiagramEnhancer.astro +15 -138
- package/src/components/starlight/HeadingAnchorEnhancer.astro +193 -1
- package/src/components/starlight/KeyedFloatEnhancer.astro +100 -9
- package/src/components/starlight/LayoutHeader.astro +298 -19
- package/src/components/starlight/LayoutPageSidebar.astro +99 -12
- package/src/components/starlight/LayoutPageTitle.astro +120 -0
- package/src/components/starlight/LayoutSidebar.astro +229 -11
- package/src/components/starlight/LessonExportActions.astro +53 -72
- package/src/components/starlight/LessonFooter.astro +26 -4
- package/src/components/starlight/section-parts.ts +15 -2
- package/src/components/starlight/section-summary.ts +181 -0
- package/src/components/table/Table.astro +95 -76
- package/src/components/table/TableOverflowExpander.astro +65 -28
- package/src/content-config.ts +40 -4
- package/src/reference/base-accents.mjs +31 -0
- package/src/reference/base-roman-labels.mjs +2 -2
- package/src/reference/consistency.ts +176 -5
- package/src/reference/delimiter-size.ts +40 -61
- package/src/reference/diagrams-validate.ts +17 -1
- package/src/reference/equations-validate.ts +10 -5
- package/src/reference/equations.mjs +90 -44
- package/src/reference/examples-validate.ts +97 -0
- package/src/reference/figures-validate.ts +6 -1
- package/src/reference/folds-validate.ts +65 -0
- package/src/reference/gate-abbreviations.ts +3 -2
- package/src/reference/gate-course-rules.ts +134 -0
- package/src/reference/gate-heading-length.ts +68 -37
- package/src/reference/gate-heading-structure.ts +193 -0
- package/src/reference/gate-math-punctuation.ts +381 -0
- package/src/reference/gate-math.ts +119 -12
- package/src/reference/gate-part-pages.ts +217 -0
- package/src/reference/gate-prose-notation.ts +74 -13
- package/src/reference/katex-delimiters.mjs +138 -0
- package/src/reference/katex-options.mjs +4 -0
- package/src/reference/keyed-float-validate.ts +45 -11
- package/src/reference/keyed-jsx-floats.mjs +73 -20
- package/src/reference/knowledge-checks-validate.ts +1 -1
- package/src/reference/knowledge-checks.mjs +9 -4
- package/src/reference/lab-math-slot.ts +36 -0
- package/src/reference/math-glyphs.mjs +588 -39
- package/src/reference/raw-floats-validate.ts +1 -1
- package/src/reference/raw-floats.mjs +1 -1
- package/src/reference/rehype-heading-anchors.mjs +116 -17
- package/src/reference/rehype-table-header-labels.mjs +21 -3
- package/src/reference/remark-asides.mjs +180 -0
- package/src/reference/remark-memo.mjs +42 -11
- package/src/reference/remark-notation.mjs +387 -36
- package/src/reference/render-lab-math.ts +17 -32
- package/src/reference/sections-validate.ts +81 -0
- package/src/reference/sections.mjs +306 -0
- package/src/reference/tables-validate.ts +2 -1
- package/src/reference/types.ts +24 -0
- package/src/reference/unit-words.ts +233 -0
- package/src/rules/catalog.ts +1298 -0
- package/src/rules/course-rules.ts +456 -0
- package/src/rules/in-effect.ts +235 -0
- package/src/rules/settings.ts +448 -0
- package/src/rules/waivers.ts +249 -0
- package/src/session/PreferenceStore.ts +15 -0
- package/src/styles/global.css +1471 -163
- package/src/styles/surfaces.css +182 -0
- package/src/components/hover-panel.ts +0 -339
- package/src/components/popup-stack.ts +0 -345
|
@@ -4,23 +4,24 @@
|
|
|
4
4
|
* Every display equation (`$$…$$`) in a lesson is numbered and given an `id`
|
|
5
5
|
* from appearance order. When the caller supplies `numbering`
|
|
6
6
|
* (`{ prefix, offset }` — see `resolveNumbering`), the number is
|
|
7
|
-
* `‹
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* part
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* trees and non-lesson
|
|
7
|
+
* `‹prefix›.‹offset + n›`: `n` counts every display equation in the lesson,
|
|
8
|
+
* and the course's `numbering.floats` scope decides the rest — `chapter` (the
|
|
9
|
+
* default) makes the prefix `‹part›.‹chapter›` with no offset, so `(2.2.4)` is
|
|
10
|
+
* the fourth display equation of part 2's second chapter; `part` makes it the
|
|
11
|
+
* part's ordinal alone, with `offset` carrying the count from earlier lessons
|
|
12
|
+
* of the part so the sequence runs on across it. `##` and `###` headings never
|
|
13
|
+
* affect it. See `docs/authoring/structure.md` for the level names. With no
|
|
14
|
+
* `numbering` the number falls back to the `##`-section scheme
|
|
15
|
+
* `‹section›.‹indexInSection›` (used by hand-built test trees and non-lesson
|
|
16
|
+
* pages). An author additionally
|
|
16
17
|
* marks an equation with a trailing `\label{eq:<key>}` inside its `$$…$$`
|
|
17
18
|
* block (inside math, so the `:` is safe and the form matches amsmath) when
|
|
18
19
|
* they want a *stable* anchor that survives edits and cross-page references:
|
|
19
20
|
*
|
|
20
|
-
* [[eq-discount-factor-def]] same page → "(2.4)" linked to #eq-…
|
|
21
|
+
* [[eq-discount-factor-def]] same page → "(2.2.4)" linked to #eq-…
|
|
21
22
|
* [[foundations/present-value#eq-pv]] cross page → number from the manifest
|
|
22
23
|
*
|
|
23
|
-
* An unkeyed equation still gets a positional anchor (`#eq-2-4`) so it can be
|
|
24
|
+
* An unkeyed equation still gets a positional anchor (`#eq-2-2-4`) so it can be
|
|
24
25
|
* deep-linked, but only a keyed equation is referenceable by name.
|
|
25
26
|
*
|
|
26
27
|
* `scanEquationLabels` (raw Markdown) and `collectEquationLabels` (mdast tree)
|
|
@@ -37,6 +38,28 @@ export const EQ_KEY_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
|
37
38
|
export const EQ_LABEL_PATTERN =
|
|
38
39
|
/\\label\{\s*eq:([a-z0-9]+(?:-[a-z0-9]+)*)\s*\}/g;
|
|
39
40
|
|
|
41
|
+
/**
|
|
42
|
+
* A `\caption{<short caption>}` anywhere in a math source — the equation's
|
|
43
|
+
* short caption, plain text with no nested braces. Global; the text in group
|
|
44
|
+
* 1. An equation takes only a short caption: it names the equation in the
|
|
45
|
+
* `[[eq-…]]` preview card and in the "Copy LaTeX — ‹caption›" tooltip, and is
|
|
46
|
+
* stripped before KaTeX exactly like `\label{}`.
|
|
47
|
+
*/
|
|
48
|
+
export const EQ_CAPTION_PATTERN = /\\caption\{([^{}]*)\}/g;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The short caption a display equation carries, trimmed, or `null` when it
|
|
52
|
+
* has none. Only the first `\caption{}` counts; `remark-notation.mjs` fails a
|
|
53
|
+
* page that gives one equation two, or an empty one.
|
|
54
|
+
*/
|
|
55
|
+
export function equationCaption(latex) {
|
|
56
|
+
EQ_CAPTION_PATTERN.lastIndex = 0;
|
|
57
|
+
const match = EQ_CAPTION_PATTERN.exec(String(latex ?? ''));
|
|
58
|
+
EQ_CAPTION_PATTERN.lastIndex = 0;
|
|
59
|
+
const text = match?.[1]?.replace(/\s+/g, ' ').trim();
|
|
60
|
+
return text ? text : null;
|
|
61
|
+
}
|
|
62
|
+
|
|
40
63
|
/**
|
|
41
64
|
* A `[[ … ]]` payload that targets an equation: `eq-<key>` on the current page,
|
|
42
65
|
* or `<lesson-slug>#eq-<key>` across pages. `lesson` (group 1) is a `/`-slug.
|
|
@@ -52,10 +75,14 @@ export function parseEquationRef(raw) {
|
|
|
52
75
|
return match[1] ? { lesson: match[1], key: match[2] } : { key: match[2] };
|
|
53
76
|
}
|
|
54
77
|
|
|
55
|
-
/**
|
|
78
|
+
/**
|
|
79
|
+
* Remove every `\label{eq:…}` and `\caption{…}` (and the whitespace they
|
|
80
|
+
* leave) from a math source: both are identity markers, not math KaTeX parses.
|
|
81
|
+
*/
|
|
56
82
|
export function stripEquationLabels(latex) {
|
|
57
83
|
return String(latex)
|
|
58
84
|
.replace(EQ_LABEL_PATTERN, '')
|
|
85
|
+
.replace(EQ_CAPTION_PATTERN, '')
|
|
59
86
|
.replace(/[ \t]+\n/g, '\n')
|
|
60
87
|
.replace(/\n{3,}/g, '\n\n')
|
|
61
88
|
.replace(/[ \t]{2,}/g, ' ')
|
|
@@ -70,13 +97,14 @@ function equationNumber(section, index) {
|
|
|
70
97
|
/**
|
|
71
98
|
* Normalise the numbering input to `{ prefix, offset }`.
|
|
72
99
|
*
|
|
73
|
-
* `prefix` is
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* legacy per-lesson form (`prefix = n`,
|
|
78
|
-
* hand-built trees and unit tests keep
|
|
79
|
-
* `##`-section fallback for
|
|
100
|
+
* `prefix` is everything before the last dot — `‹part›.‹chapter›` under the
|
|
101
|
+
* default `numbering.floats: "chapter"`, the part's ordinal under `"part"`.
|
|
102
|
+
* `offset` is how many items of the kind precede the lesson in that scope: 0
|
|
103
|
+
* for a chapter, the earlier chapters' count for a part, so `n` runs on
|
|
104
|
+
* across it. A bare number is the legacy per-lesson form (`prefix = n`,
|
|
105
|
+
* `offset = 0`) and is still accepted so hand-built trees and unit tests keep
|
|
106
|
+
* working. `undefined`/`null`/`0`/`''` ⇒ the `##`-section fallback for
|
|
107
|
+
* non-lesson pages.
|
|
80
108
|
*/
|
|
81
109
|
export function resolveNumbering(input) {
|
|
82
110
|
if (input == null) return { prefix: undefined, offset: 0 };
|
|
@@ -90,9 +118,9 @@ export function resolveNumbering(input) {
|
|
|
90
118
|
}
|
|
91
119
|
|
|
92
120
|
/**
|
|
93
|
-
* The visible number for one display equation. With a `prefix`
|
|
94
|
-
*
|
|
95
|
-
*
|
|
121
|
+
* The visible number for one display equation. With a `prefix` the scheme is
|
|
122
|
+
* `‹prefix›.‹offset + indexInLesson›` and `##` headings are ignored; without
|
|
123
|
+
* it, the legacy `##`-section scheme.
|
|
96
124
|
*/
|
|
97
125
|
export function displayEquationNumber({
|
|
98
126
|
prefix,
|
|
@@ -106,7 +134,13 @@ export function displayEquationNumber({
|
|
|
106
134
|
: `${prefix}.${offset + indexInLesson}`;
|
|
107
135
|
}
|
|
108
136
|
|
|
109
|
-
/**
|
|
137
|
+
/**
|
|
138
|
+
* A display equation's anchor id: `eq-<key>` when it carries a
|
|
139
|
+
* `\label{eq:…}`, else the positional form, e.g. `2.4` → `eq-2-4`.
|
|
140
|
+
*
|
|
141
|
+
* @param {{ key?: string, number?: string | number }} equation - `number` is
|
|
142
|
+
* read only when there is no `key`, so a keyed caller may omit it.
|
|
143
|
+
*/
|
|
110
144
|
export function equationAnchorId({ key, number }) {
|
|
111
145
|
return key ? `eq-${key}` : `eq-${String(number).replace(/\./g, '-')}`;
|
|
112
146
|
}
|
|
@@ -130,10 +164,8 @@ export function stripFencedCode(markdown) {
|
|
|
130
164
|
* component while still finding `<Table>`/`<Figure>` tags at the top level.
|
|
131
165
|
*
|
|
132
166
|
* `transparent`, when given, removes only those components' opening/closing
|
|
133
|
-
* tags and exposes their body to the later blanking pass.
|
|
134
|
-
*
|
|
135
|
-
* wrapper, their bodies are details in the lesson's reading order, so numbered
|
|
136
|
-
* equations/floats in them stay in the official sequence. Section headings in
|
|
167
|
+
* tags and exposes their body to the later blanking pass. The
|
|
168
|
+
* {@link TRANSPARENT_COMPONENTS} are the intentional uses. Section headings in
|
|
137
169
|
* a transparent body do not change the surrounding page's section counter.
|
|
138
170
|
*/
|
|
139
171
|
export function stripJsxComponents(markdown, keep, transparent = []) {
|
|
@@ -163,23 +195,35 @@ export function stripJsxComponents(markdown, keep, transparent = []) {
|
|
|
163
195
|
);
|
|
164
196
|
}
|
|
165
197
|
|
|
198
|
+
/**
|
|
199
|
+
* The components whose bodies stay in the lesson's reading order: `<Memo>`,
|
|
200
|
+
* `<ShortNote>` and `<Fold>`. Unlike a worked-example/presentation wrapper,
|
|
201
|
+
* their bodies are details or folded stretches of the lesson itself, so a
|
|
202
|
+
* numbered equation or float in one stays in the official sequence. Every
|
|
203
|
+
* scan and tree walk that numbers equations or floats reads this list.
|
|
204
|
+
*/
|
|
205
|
+
export const TRANSPARENT_COMPONENTS = Object.freeze(['Memo', 'ShortNote', 'Fold']);
|
|
206
|
+
|
|
166
207
|
/**
|
|
167
208
|
* Every display equation in a Markdown body, keyed or not, in appearance order
|
|
168
|
-
* with its assigned number
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
209
|
+
* with its assigned number and its `\caption{}` short caption (`short`, or
|
|
210
|
+
* `null`). `$$…$$` blocks only. With `numbering`
|
|
211
|
+
* (`{ prefix, offset }`, the lesson's float-number prefix and the count of
|
|
212
|
+
* display equations before it in that scope) the number is
|
|
213
|
+
* `‹prefix›.‹offset + n›`, `n` sequential over every display block in the
|
|
214
|
+
* lesson; without it, `##` headings advance a section counter and the number
|
|
215
|
+
* is `‹section›.‹indexInSection›`.
|
|
173
216
|
* Either way every display block advances the index, so a keyed equation's
|
|
174
217
|
* number reflects its position among *all* display equations, not only the
|
|
175
218
|
* keyed ones.
|
|
176
219
|
*/
|
|
177
220
|
export function scanDisplayEquations(markdown, numbering) {
|
|
178
221
|
const { prefix, offset } = resolveNumbering(numbering);
|
|
179
|
-
const text = stripJsxComponents(
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
222
|
+
const text = stripJsxComponents(
|
|
223
|
+
stripFencedCode(markdown),
|
|
224
|
+
undefined,
|
|
225
|
+
TRANSPARENT_COMPONENTS,
|
|
226
|
+
);
|
|
183
227
|
const tokens = [];
|
|
184
228
|
const heading = /^[ \t]{0,3}##[ \t]+\S/gm;
|
|
185
229
|
const display = /\$\$([\s\S]+?)\$\$/g;
|
|
@@ -193,6 +237,7 @@ export function scanDisplayEquations(markdown, numbering) {
|
|
|
193
237
|
kind: 'equation',
|
|
194
238
|
index: match.index,
|
|
195
239
|
key: label ? label[1] : null,
|
|
240
|
+
short: equationCaption(match[1]),
|
|
196
241
|
});
|
|
197
242
|
}
|
|
198
243
|
tokens.sort((a, b) => a.index - b.index);
|
|
@@ -218,6 +263,7 @@ export function scanDisplayEquations(markdown, numbering) {
|
|
|
218
263
|
indexInSection,
|
|
219
264
|
indexInLesson,
|
|
220
265
|
}),
|
|
266
|
+
short: token.short,
|
|
221
267
|
section,
|
|
222
268
|
indexInSection,
|
|
223
269
|
});
|
|
@@ -255,11 +301,11 @@ export function collectDisplayEquations(tree, numbering) {
|
|
|
255
301
|
let indexInSection = 0;
|
|
256
302
|
let indexInLesson = 0;
|
|
257
303
|
|
|
258
|
-
// Top-level blocks plus the bodies of
|
|
259
|
-
// components (a `<CompactExample>` worked example, an `<Aside>`) remain
|
|
260
|
-
// illustrative and outside the lesson's official equation sequence.
|
|
261
|
-
//
|
|
262
|
-
//
|
|
304
|
+
// Top-level blocks plus the bodies of the `TRANSPARENT_COMPONENTS`: other
|
|
305
|
+
// MDX components (a `<CompactExample>` worked example, an `<Aside>`) remain
|
|
306
|
+
// illustrative and outside the lesson's official equation sequence. The
|
|
307
|
+
// transparent ones occupy an exact point in the reading order, even while
|
|
308
|
+
// visually collapsed.
|
|
263
309
|
const collectFromDetail = (node) => {
|
|
264
310
|
if (!node || typeof node !== 'object') return;
|
|
265
311
|
if (node.type === 'math') {
|
|
@@ -269,6 +315,7 @@ export function collectDisplayEquations(tree, numbering) {
|
|
|
269
315
|
const label = EQ_LABEL_PATTERN.exec(String(node.value ?? ''));
|
|
270
316
|
equations.push({
|
|
271
317
|
key: label ? label[1] : null,
|
|
318
|
+
short: equationCaption(node.value),
|
|
272
319
|
number: displayEquationNumber({
|
|
273
320
|
prefix,
|
|
274
321
|
offset,
|
|
@@ -284,8 +331,7 @@ export function collectDisplayEquations(tree, numbering) {
|
|
|
284
331
|
}
|
|
285
332
|
if (
|
|
286
333
|
node.type === 'mdxJsxFlowElement' &&
|
|
287
|
-
node.name
|
|
288
|
-
node.name !== 'ShortNote'
|
|
334
|
+
!TRANSPARENT_COMPONENTS.includes(node.name)
|
|
289
335
|
) {
|
|
290
336
|
return;
|
|
291
337
|
}
|
|
@@ -304,7 +350,7 @@ export function collectDisplayEquations(tree, numbering) {
|
|
|
304
350
|
if (node.type === 'math') collectFromDetail(node);
|
|
305
351
|
else if (
|
|
306
352
|
node.type === 'mdxJsxFlowElement' &&
|
|
307
|
-
(node.name
|
|
353
|
+
TRANSPARENT_COMPONENTS.includes(node.name)
|
|
308
354
|
) {
|
|
309
355
|
collectFromDetail(node);
|
|
310
356
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { KeyedFloatLabelRecord, KeyedFloatLessonInput, KeyedFloatValidation } from './keyed-float-validate.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `validateKeyedFloats` for `<CompactExamples>` — see
|
|
5
|
+
* `keyed-float-validate.ts`. A worked-example set is a numbered kind of its
|
|
6
|
+
* own, like a knowledge check (docs/plans/titles-and-containers.md Part 6,
|
|
7
|
+
* Q11): `Example ‹prefix›.‹n›`, anchored `#ex-‹id›`, cited with `[[ex-‹id›]]`.
|
|
8
|
+
*
|
|
9
|
+
* On top of the shared checks, both of the set's names are required
|
|
10
|
+
* (`ex-missing-prop`): the `id` its anchor and references need, and the
|
|
11
|
+
* `short` title its heading shows after the engine's "Example" badge. There is
|
|
12
|
+
* no default title to fall back on.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
type ExampleDiagnosticCode = 'ex-invalid-key' | 'ex-duplicate-key' | 'ex-ref-resolves' | 'ex-key-unused' | 'ex-removed-prop' | 'ex-missing-prop';
|
|
16
|
+
interface ExampleDiagnostic {
|
|
17
|
+
readonly code: ExampleDiagnosticCode;
|
|
18
|
+
readonly severity: 'error' | 'warning' | 'info';
|
|
19
|
+
readonly message: string;
|
|
20
|
+
readonly file?: string;
|
|
21
|
+
readonly lessonId?: string;
|
|
22
|
+
readonly key?: string;
|
|
23
|
+
}
|
|
24
|
+
type ExampleLabelRecord = KeyedFloatLabelRecord;
|
|
25
|
+
type ExampleLessonInput = KeyedFloatLessonInput;
|
|
26
|
+
interface ExampleValidation extends KeyedFloatValidation {
|
|
27
|
+
readonly diagnostics: readonly ExampleDiagnostic[];
|
|
28
|
+
}
|
|
29
|
+
declare function validateExamples(lessons: readonly ExampleLessonInput[]): ExampleValidation;
|
|
30
|
+
|
|
31
|
+
export { type ExampleDiagnostic, type ExampleDiagnosticCode, type ExampleLabelRecord, type ExampleLessonInput, type ExampleValidation, validateExamples };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
validateKeyedFloats
|
|
3
|
+
} from "./keyed-float-validate.js";
|
|
4
|
+
import { scanKeyedJsxFloats } from "./keyed-jsx-floats.mjs";
|
|
5
|
+
const REQUIRED_PROPS = {
|
|
6
|
+
id: "give it an `id` (lowercase letters, digits and hyphens): the set is anchored `#ex-\u2039id\u203A` and cited with `[[ex-\u2039id\u203A]]`.",
|
|
7
|
+
short: 'give it a `short` title naming the topic, e.g. "Converting between coordinate systems"; the engine draws "Example" and the number before it.'
|
|
8
|
+
};
|
|
9
|
+
function validateExamples(lessons) {
|
|
10
|
+
const shared = validateKeyedFloats(lessons, {
|
|
11
|
+
tagName: "CompactExamples",
|
|
12
|
+
prefix: "ex",
|
|
13
|
+
noun: "worked-example set",
|
|
14
|
+
removedProps: {
|
|
15
|
+
summary: 'rename it `short`: the set\u2019s topical title, shown after the engine\u2019s "Example" badge.'
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
const missing = [];
|
|
19
|
+
for (const lesson of lessons) {
|
|
20
|
+
const sets = scanKeyedJsxFloats(lesson.body, {
|
|
21
|
+
tagName: "CompactExamples",
|
|
22
|
+
numbering: lesson.numbering
|
|
23
|
+
});
|
|
24
|
+
for (const set of sets) {
|
|
25
|
+
for (const [prop, fix] of Object.entries(REQUIRED_PROPS)) {
|
|
26
|
+
if (set.props.includes(prop)) continue;
|
|
27
|
+
missing.push({
|
|
28
|
+
code: "ex-missing-prop",
|
|
29
|
+
severity: "error",
|
|
30
|
+
message: `${lesson.lessonId} has a <CompactExamples>${set.key ? ` (id="${set.key}")` : ""} with no \`${prop}\`: ${fix}`,
|
|
31
|
+
file: lesson.file,
|
|
32
|
+
lessonId: lesson.lessonId,
|
|
33
|
+
...set.key ? { key: set.key } : {}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const diagnostics = [
|
|
39
|
+
...shared.diagnostics,
|
|
40
|
+
...missing
|
|
41
|
+
].sort(
|
|
42
|
+
(a, b) => a.code.localeCompare(b.code) || (a.lessonId ?? "").localeCompare(b.lessonId ?? "") || (a.key ?? "").localeCompare(b.key ?? "") || a.message.localeCompare(b.message)
|
|
43
|
+
);
|
|
44
|
+
return { ...shared, diagnostics };
|
|
45
|
+
}
|
|
46
|
+
export {
|
|
47
|
+
validateExamples
|
|
48
|
+
};
|
|
@@ -2,7 +2,7 @@ import { KeyedFloatLabelRecord, KeyedFloatLessonInput, KeyedFloatValidation } fr
|
|
|
2
2
|
|
|
3
3
|
/** `validateKeyedFloats` for `<Figure>` — see `keyed-float-validate.ts`. */
|
|
4
4
|
|
|
5
|
-
type FigureDiagnosticCode = 'fig-invalid-key' | 'fig-duplicate-key' | 'fig-ref-resolves' | 'fig-key-unused';
|
|
5
|
+
type FigureDiagnosticCode = 'fig-invalid-key' | 'fig-duplicate-key' | 'fig-ref-resolves' | 'fig-key-unused' | 'fig-removed-prop';
|
|
6
6
|
interface FigureDiagnostic {
|
|
7
7
|
readonly code: FigureDiagnosticCode;
|
|
8
8
|
readonly severity: 'error' | 'warning' | 'info';
|
|
@@ -5,7 +5,10 @@ function validateFigures(lessons) {
|
|
|
5
5
|
return validateKeyedFloats(lessons, {
|
|
6
6
|
tagName: "Figure",
|
|
7
7
|
prefix: "fig",
|
|
8
|
-
noun: "figure"
|
|
8
|
+
noun: "figure",
|
|
9
|
+
removedProps: {
|
|
10
|
+
title: "a figure is named by its `caption` (the long caption, shown below it with its number) and an optional `short` caption; move a short title to `short`."
|
|
11
|
+
}
|
|
9
12
|
});
|
|
10
13
|
}
|
|
11
14
|
export {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { KeyedFloatLessonInput } from './keyed-float-validate.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `<Fold>` validation, run inside `compileManifest()`
|
|
5
|
+
* (docs/plans/figures-and-short-captions.md, decision 11). A fold's bar is
|
|
6
|
+
* its `label` or, without one, the numbers and short captions of the floats
|
|
7
|
+
* it holds (`../components/float-name.ts`), so a fold needs one or the other:
|
|
8
|
+
*
|
|
9
|
+
* - `fold-unnamed` (error): a `<Fold>` with no `label` and no `<Figure>`,
|
|
10
|
+
* `<Table>` or `<Diagram>` that takes a number inside it — its bar would
|
|
11
|
+
* have nothing to show.
|
|
12
|
+
*
|
|
13
|
+
* `Fold.astro` throws on the same fold when it renders; this reports it
|
|
14
|
+
* earlier, at `validate:content`, with the lesson it is in. A float that a
|
|
15
|
+
* wrapper component renders counts when the lesson writes it under a float's
|
|
16
|
+
* tag name (`<Diagram id="…" />` for a wrapper imported as `Diagram`), the
|
|
17
|
+
* way the numbering scan sees it.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
type FoldDiagnosticCode = 'fold-unnamed';
|
|
21
|
+
interface FoldDiagnostic {
|
|
22
|
+
readonly code: FoldDiagnosticCode;
|
|
23
|
+
readonly severity: 'error';
|
|
24
|
+
readonly message: string;
|
|
25
|
+
readonly file?: string;
|
|
26
|
+
readonly lessonId?: string;
|
|
27
|
+
}
|
|
28
|
+
interface FoldValidation {
|
|
29
|
+
readonly diagnostics: readonly FoldDiagnostic[];
|
|
30
|
+
}
|
|
31
|
+
declare function validateFolds(lessons: readonly KeyedFloatLessonInput[]): FoldValidation;
|
|
32
|
+
|
|
33
|
+
export { type FoldDiagnostic, type FoldDiagnosticCode, type FoldValidation, validateFolds };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { stripFencedCode } from "./equations.mjs";
|
|
2
|
+
import { scanKeyedJsxFloats } from "./keyed-jsx-floats.mjs";
|
|
3
|
+
const ATTRIBUTES = String.raw`(?:"[^"]*"|'[^']*'|\{(?:[^{}]|\{[^{}]*\})*\}|[^>"'])*`;
|
|
4
|
+
const FOLD = new RegExp(`<Fold\\b(${ATTRIBUTES})>([\\s\\S]*?)<\\/Fold\\s*>`, "g");
|
|
5
|
+
const NUMBERED_FLOATS = ["Figure", "Table", "Diagram"];
|
|
6
|
+
function validateFolds(lessons) {
|
|
7
|
+
const diagnostics = [];
|
|
8
|
+
for (const lesson of [...lessons].sort(
|
|
9
|
+
(a, b) => a.lessonId.localeCompare(b.lessonId)
|
|
10
|
+
)) {
|
|
11
|
+
for (const [, attributes = "", body = ""] of stripFencedCode(lesson.body).matchAll(
|
|
12
|
+
FOLD
|
|
13
|
+
)) {
|
|
14
|
+
if (/(?:^|\s)label\s*=/.test(attributes)) continue;
|
|
15
|
+
const named = NUMBERED_FLOATS.some(
|
|
16
|
+
(tagName) => scanKeyedJsxFloats(body, { tagName, numbering: void 0 }).length > 0
|
|
17
|
+
);
|
|
18
|
+
if (named) continue;
|
|
19
|
+
diagnostics.push({
|
|
20
|
+
code: "fold-unnamed",
|
|
21
|
+
severity: "error",
|
|
22
|
+
message: `${lesson.lessonId} has a <Fold> with no \`label\` and no numbered <Figure>, <Table> or <Diagram> inside it, so its bar has nothing to name it by: give it a \`label\`, or fold a numbered float with it.`,
|
|
23
|
+
file: lesson.file,
|
|
24
|
+
lessonId: lesson.lessonId
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return { diagnostics };
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
validateFolds
|
|
32
|
+
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { MathGateDiagnostic } from './gate-math.js';
|
|
2
2
|
import { NotationRegistryInput, AbbreviationDefinitionInput } from './types.js';
|
|
3
|
-
import { CourseAbbreviations } from '../
|
|
3
|
+
import { a as CourseAbbreviations } from '../course-config-Dsnri9Tk.js';
|
|
4
|
+
import '../rules/catalog.js';
|
|
5
|
+
import '../rules/course-rules.js';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Scan every lesson body for an abbreviation-shaped token outside `$…$` that
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { collectOperators, collectRomanLabels } from "./math-glyphs.mjs";
|
|
2
2
|
import { isBaseAbbreviation } from "./base-abbreviations.mjs";
|
|
3
3
|
import { pageScope } from "./gate-math.js";
|
|
4
|
-
import {
|
|
4
|
+
import { lessonLine, proseOnly } from "./gate-prose-notation.js";
|
|
5
5
|
const CAPS_RUN = /\b([A-Z]{2,})(?:'s|s)?\b/g;
|
|
6
6
|
const DOTTED_ABBR = /\b(?:[A-Za-z]\.){2,}/g;
|
|
7
7
|
function findAbbreviationLikeTokens(text) {
|
|
@@ -49,9 +49,10 @@ function gateAbbreviations(input) {
|
|
|
49
49
|
reported.add(token);
|
|
50
50
|
diagnostics.push({
|
|
51
51
|
context: "abbreviation",
|
|
52
|
+
code: "abbreviation-unexplained",
|
|
52
53
|
severity: "error",
|
|
53
54
|
file: lesson.source.file,
|
|
54
|
-
line:
|
|
55
|
+
line: lessonLine(lesson, index),
|
|
55
56
|
token,
|
|
56
57
|
message: `Unexplained abbreviation ${JSON.stringify(token)} outside math. ${FIX}`
|
|
57
58
|
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { MathGateDiagnostic } from './gate-math.js';
|
|
2
|
+
import { NotationLessonInput } from './types.js';
|
|
3
|
+
import { CourseRule } from '../rules/course-rules.js';
|
|
4
|
+
import '../rules/catalog.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The gate for a course's own rules (`courseRules` in `course.settings.json`,
|
|
8
|
+
* resolved by `src/rules/course-rules.ts`).
|
|
9
|
+
*
|
|
10
|
+
* One loop over every enabled course rule's `forbid` patterns, matched against
|
|
11
|
+
* the same "is this really prose" surface rule N9 uses — `proseOnly` blanks
|
|
12
|
+
* code fences, inline code, `$…$` / `$$…$$`, `[[key]]` references, `import`
|
|
13
|
+
* lines, JSX tags and MDX comments to spaces, so offsets still point at the
|
|
14
|
+
* source. A match becomes a `MathGateDiagnostic` with the rule's own ID as its
|
|
15
|
+
* code and a lesson `file` + `line`, which is what lets `{/* explico-allow X1:
|
|
16
|
+
* reason *\/}` waive one place.
|
|
17
|
+
*
|
|
18
|
+
* Nothing here knows what a rule *means*: the patterns come from the course,
|
|
19
|
+
* already validated (they compile, and none matches the empty string). The
|
|
20
|
+
* three scopes are the three units an author can reason about — a paragraph, a
|
|
21
|
+
* whole lesson's prose, a heading.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Scan every lesson body for what the course's own rules forbid. Only enabled
|
|
26
|
+
* rules with at least one pattern do anything; a guide rule is not this gate's
|
|
27
|
+
* business. Findings carry the rule ID as `code`, so `ownerOf` / `ruleIdOf`
|
|
28
|
+
* name the rule and a waiver can target it.
|
|
29
|
+
*/
|
|
30
|
+
declare function gateCourseRules(lessons: readonly NotationLessonInput[], rules: readonly CourseRule[]): MathGateDiagnostic[];
|
|
31
|
+
|
|
32
|
+
export { gateCourseRules };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { lessonLine, proseOnly } from "./gate-prose-notation.js";
|
|
2
|
+
const HEADING = /^[ \t]{0,3}(#{1,6})[ \t]+(.*)$/;
|
|
3
|
+
function paragraphs(text) {
|
|
4
|
+
const units = [];
|
|
5
|
+
let index = 0;
|
|
6
|
+
let start = -1;
|
|
7
|
+
const lines = text.split("\n");
|
|
8
|
+
lines.forEach((line, i) => {
|
|
9
|
+
const blank = line.trim() === "";
|
|
10
|
+
if (!blank && start === -1) start = index;
|
|
11
|
+
if ((blank || i === lines.length - 1) && start !== -1) {
|
|
12
|
+
const end = blank ? index : index + line.length;
|
|
13
|
+
units.push({ text: text.slice(start, end).replace(/\s+$/, ""), index: start });
|
|
14
|
+
start = -1;
|
|
15
|
+
}
|
|
16
|
+
index += line.length + 1;
|
|
17
|
+
});
|
|
18
|
+
return units.filter((unit) => unit.text !== "");
|
|
19
|
+
}
|
|
20
|
+
function headings(text) {
|
|
21
|
+
const units = [];
|
|
22
|
+
let index = 0;
|
|
23
|
+
for (const line of text.split("\n")) {
|
|
24
|
+
const match = HEADING.exec(line);
|
|
25
|
+
if (match !== null) {
|
|
26
|
+
const body = match[2].replace(/\s+#*\s*$/, "");
|
|
27
|
+
if (body !== "") {
|
|
28
|
+
units.push({ text: body, index: index + line.indexOf(body) });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
index += line.length + 1;
|
|
32
|
+
}
|
|
33
|
+
return units;
|
|
34
|
+
}
|
|
35
|
+
function unitsFor(scope, prose) {
|
|
36
|
+
switch (scope) {
|
|
37
|
+
case "prose":
|
|
38
|
+
return [{ text: prose, index: 0 }];
|
|
39
|
+
case "heading":
|
|
40
|
+
return headings(prose);
|
|
41
|
+
case "paragraph":
|
|
42
|
+
return paragraphs(prose);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function* matches(pattern, unit) {
|
|
46
|
+
const regex = new RegExp(pattern.pattern, `${pattern.flags}g`);
|
|
47
|
+
let match;
|
|
48
|
+
while ((match = regex.exec(unit.text)) !== null) {
|
|
49
|
+
yield [unit.index + match.index, match[0]];
|
|
50
|
+
if (match.index === regex.lastIndex) regex.lastIndex += 1;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function gateCourseRules(lessons, rules) {
|
|
54
|
+
const active = rules.filter((rule) => rule.enabled && rule.forbid.length > 0);
|
|
55
|
+
if (active.length === 0) return [];
|
|
56
|
+
const diagnostics = [];
|
|
57
|
+
for (const lesson of lessons) {
|
|
58
|
+
const prose = proseOnly(lesson.body);
|
|
59
|
+
const cache = /* @__PURE__ */ new Map();
|
|
60
|
+
for (const rule of active) {
|
|
61
|
+
for (const pattern of rule.forbid) {
|
|
62
|
+
if (!cache.has(pattern.scope)) cache.set(pattern.scope, unitsFor(pattern.scope, prose));
|
|
63
|
+
for (const unit of cache.get(pattern.scope)) {
|
|
64
|
+
for (const [index, token] of matches(pattern, unit)) {
|
|
65
|
+
diagnostics.push({
|
|
66
|
+
context: "course",
|
|
67
|
+
code: rule.id,
|
|
68
|
+
severity: rule.severity,
|
|
69
|
+
file: lesson.source.file,
|
|
70
|
+
line: lessonLine(lesson, index),
|
|
71
|
+
// The matched text, trimmed — unless the pattern deliberately
|
|
72
|
+
// matched whitespace, in which case there is nothing else to show.
|
|
73
|
+
token: token.trim() || token,
|
|
74
|
+
message: `${pattern.message} (course rule ${rule.id})`
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return diagnostics;
|
|
82
|
+
}
|
|
83
|
+
export {
|
|
84
|
+
gateCourseRules
|
|
85
|
+
};
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { MathGateDiagnostic } from './gate-math.js';
|
|
2
2
|
import { NotationLessonInput } from './types.js';
|
|
3
3
|
|
|
4
|
+
/** Rule S1's default; a course sets its own with `rules.S1.maxLength`. */
|
|
4
5
|
declare const HEADING_LENGTH_LIMIT = 65;
|
|
5
6
|
/**
|
|
6
|
-
* Scan every lesson body for a heading or a
|
|
7
|
+
* Scan every lesson body for a heading or a short caption running past
|
|
7
8
|
* `HEADING_LENGTH_LIMIT` characters. Returns one `MathGateDiagnostic`
|
|
8
9
|
* (context `'structure'`, always an error) per finding.
|
|
9
10
|
*/
|
|
10
|
-
declare function gateHeadingLength(lessons: readonly NotationLessonInput[]
|
|
11
|
+
declare function gateHeadingLength(lessons: readonly NotationLessonInput[], options?: {
|
|
12
|
+
readonly maxLength?: number;
|
|
13
|
+
}): MathGateDiagnostic[];
|
|
11
14
|
|
|
12
15
|
export { HEADING_LENGTH_LIMIT, gateHeadingLength };
|