explico 0.8.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 +1239 -1
- package/README.md +60 -15
- package/dist/compiler/astro.d.ts +6 -1
- package/dist/compiler/cli.d.ts +46 -2
- package/dist/compiler/cli.js +70 -4
- package/dist/compiler/collections.d.ts +35 -2
- package/dist/compiler/collections.js +68 -5
- 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 +4 -1
- package/dist/compiler/logging.js +4 -1
- 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 +3 -2
- 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 +60 -31
- package/dist/reference/equations.mjs +83 -43
- 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 +1 -0
- 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 +45 -21
- 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 +22 -6
- package/dist/reference/gate-math.js +42 -8
- 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 +21 -11
- package/dist/reference/gate-prose-notation.js +25 -7
- package/dist/reference/katex-delimiters.mjs +20 -7
- 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 +572 -37
- 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 +4 -4
- 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 +17 -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 +1096 -2
- package/docs/authoring/notation.md +576 -322
- 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/package.json +2 -2
- package/src/compiler/cli.ts +116 -6
- package/src/compiler/collections.ts +111 -7
- 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 +8 -2
- 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 +3 -4
- package/src/components/notation/NotationLayer.astro +169 -162
- package/src/components/notation/NotationMath.astro +10 -1
- package/src/components/reference/FloatRefLayer.astro +485 -423
- package/src/components/starlight/AboutPanel.astro +5 -4
- 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 +3 -2
- package/src/reference/diagrams-validate.ts +17 -1
- package/src/reference/equations-validate.ts +10 -5
- package/src/reference/equations.mjs +83 -43
- 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 +1 -0
- package/src/reference/gate-course-rules.ts +134 -0
- package/src/reference/gate-heading-length.ts +66 -35
- package/src/reference/gate-heading-structure.ts +193 -0
- package/src/reference/gate-math-punctuation.ts +381 -0
- package/src/reference/gate-math.ts +69 -10
- package/src/reference/gate-part-pages.ts +217 -0
- package/src/reference/gate-prose-notation.ts +59 -12
- package/src/reference/katex-delimiters.mjs +20 -7
- 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 +572 -37
- 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 +16 -31
- 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 +17 -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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findSpelledUnits } from "./unit-words.js";
|
|
1
2
|
const CODE_FENCE = /```[\s\S]*?```/g;
|
|
2
3
|
const INLINE_CODE = /(^|[^`])`[^`\n]*`/g;
|
|
3
4
|
const DISPLAY_MATH = /\$\$([\s\S]+?)\$\$/g;
|
|
@@ -5,9 +6,12 @@ const INLINE_MATH = /(?<![\\$])\$(?!\s)([^$\n]+?)(?<![\\\s])\$(?!\d)/g;
|
|
|
5
6
|
const IMPORT_LINE = /^[ \t]*import\b[^\n]*$/gm;
|
|
6
7
|
const KEY_REFERENCE = /\[\[[^\]\n]*\]\]/g;
|
|
7
8
|
const JSX_TAG = /<\/?[A-Za-z][^<>]*>/g;
|
|
8
|
-
|
|
9
|
+
const MDX_COMMENT = /\{\/\*[\s\S]*?\*\/\}/g;
|
|
10
|
+
function proseOnly(body, options = {}) {
|
|
9
11
|
const blank = (match) => match.replace(/[^\n]/g, " ");
|
|
10
|
-
|
|
12
|
+
const code = body.replace(MDX_COMMENT, blank).replace(CODE_FENCE, blank).replace(INLINE_CODE, (match, lead) => lead + blank(match.slice(lead.length)));
|
|
13
|
+
const math = options.keepMath ? code : code.replace(DISPLAY_MATH, blank).replace(INLINE_MATH, blank);
|
|
14
|
+
return math.replace(IMPORT_LINE, blank).replace(KEY_REFERENCE, blank).replace(JSX_TAG, blank);
|
|
11
15
|
}
|
|
12
16
|
function lineOf(text, index) {
|
|
13
17
|
return text.slice(0, index).split("\n").length;
|
|
@@ -40,6 +44,7 @@ const SLASH_IDIOMS = /* @__PURE__ */ new Set([
|
|
|
40
44
|
]);
|
|
41
45
|
const SLASH_TOKENS = /\b([A-Za-z]{1,6})\/([A-Za-z]{1,6})\b/g;
|
|
42
46
|
const FAKE_SCRIPT = /[A-Za-z0-9][_^][A-Za-z0-9{]/g;
|
|
47
|
+
const PROSE_NOTATION_CODE = "prose-notation";
|
|
43
48
|
function findAll(text, pattern, toFinding) {
|
|
44
49
|
const out = [];
|
|
45
50
|
const re = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
|
|
@@ -51,8 +56,17 @@ function findAll(text, pattern, toFinding) {
|
|
|
51
56
|
return out;
|
|
52
57
|
}
|
|
53
58
|
const FIX = "Wrap it in $\u2026$ (or $$\u2026$$) instead \u2014 see docs/authoring/notation.md#n9.";
|
|
54
|
-
function findingsIn(text) {
|
|
59
|
+
function findingsIn(text, withMath, options) {
|
|
60
|
+
const spelled = findSpelledUnits(text, withMath, options.allowSpelledUnits).map(
|
|
61
|
+
(unit) => ({
|
|
62
|
+
index: unit.index,
|
|
63
|
+
token: unit.token,
|
|
64
|
+
code: "spelled-unit",
|
|
65
|
+
message: `Spelled-out unit ${JSON.stringify(unit.token)}: write it as ${unit.suggestion}. A unit that quantifies something is an upright symbol inside $\u2026$, even in flowing prose. Where the sentence defines or names the unit itself, waive the block with {/* explico-allow N9: reason */} \u2014 see docs/authoring/notation.md#n9.`
|
|
66
|
+
})
|
|
67
|
+
);
|
|
55
68
|
return [
|
|
69
|
+
...spelled,
|
|
56
70
|
...findAll(text, /\\\|/g, (m) => ({
|
|
57
71
|
index: m.index,
|
|
58
72
|
token: m[0],
|
|
@@ -83,13 +97,15 @@ function findingsIn(text) {
|
|
|
83
97
|
}).filter(Boolean)
|
|
84
98
|
];
|
|
85
99
|
}
|
|
86
|
-
function gateProseNotation(lessons) {
|
|
100
|
+
function gateProseNotation(lessons, options = {}) {
|
|
87
101
|
const diagnostics = [];
|
|
88
102
|
for (const lesson of lessons) {
|
|
89
103
|
const text = proseOnly(lesson.body);
|
|
90
|
-
|
|
104
|
+
const withMath = proseOnly(lesson.body, { keepMath: true });
|
|
105
|
+
for (const finding of findingsIn(text, withMath, options)) {
|
|
91
106
|
diagnostics.push({
|
|
92
107
|
context: "prose",
|
|
108
|
+
code: finding.code ?? PROSE_NOTATION_CODE,
|
|
93
109
|
severity: "error",
|
|
94
110
|
file: lesson.source.file,
|
|
95
111
|
line: lessonLine(lesson, finding.index),
|
|
@@ -100,15 +116,17 @@ function gateProseNotation(lessons) {
|
|
|
100
116
|
}
|
|
101
117
|
return diagnostics;
|
|
102
118
|
}
|
|
103
|
-
function gateProseNotationInAssessments(assessments) {
|
|
119
|
+
function gateProseNotationInAssessments(assessments, options = {}) {
|
|
104
120
|
const diagnostics = [];
|
|
105
121
|
for (const assessment of assessments) {
|
|
106
122
|
for (const item of assessment.items ?? []) {
|
|
107
123
|
for (const field of [item.prompt, item.explanation]) {
|
|
108
124
|
if (typeof field !== "string") continue;
|
|
109
|
-
|
|
125
|
+
const withMath = proseOnly(field, { keepMath: true });
|
|
126
|
+
for (const finding of findingsIn(proseOnly(field), withMath, options)) {
|
|
110
127
|
diagnostics.push({
|
|
111
128
|
context: "prose",
|
|
129
|
+
code: finding.code ?? PROSE_NOTATION_CODE,
|
|
112
130
|
severity: "error",
|
|
113
131
|
file: `${assessment.id} (${item.id ?? "?"})`,
|
|
114
132
|
token: finding.token,
|
|
@@ -32,8 +32,6 @@
|
|
|
32
32
|
* increase. A course therefore either renders with engine-sized delimiters or
|
|
33
33
|
* fails its build saying so — it cannot quietly get the stock behaviour.
|
|
34
34
|
*/
|
|
35
|
-
import { readFileSync } from 'node:fs';
|
|
36
|
-
import { createRequire } from 'node:module';
|
|
37
35
|
import katex from 'katex';
|
|
38
36
|
|
|
39
37
|
/** TeX's `\delimitershortfall`, in points, as KaTeX hard-codes it. */
|
|
@@ -78,12 +76,21 @@ export function rewriteBundle(source) {
|
|
|
78
76
|
}
|
|
79
77
|
|
|
80
78
|
async function loadGrownKatex() {
|
|
79
|
+
// Imported here, not at the top: should a browser graph ever reach this
|
|
80
|
+
// module (a lab island once did, through `render-lab-math.ts`; islands now
|
|
81
|
+
// import `lab-math-slot.ts`), Vite's `node:fs` stub throws on first access —
|
|
82
|
+
// which, for a static named import, is module evaluation itself, so the
|
|
83
|
+
// island would never hydrate.
|
|
84
|
+
const { readFileSync } = await import('node:fs');
|
|
85
|
+
const { createRequire } = await import('node:module');
|
|
81
86
|
const require = createRequire(import.meta.url);
|
|
82
87
|
const source = readFileSync(require.resolve('katex/dist/katex.mjs'), 'utf8');
|
|
83
88
|
// The bundle is self-contained, so it can be re-imported from a data URL
|
|
84
|
-
// with no base to resolve relative specifiers against.
|
|
89
|
+
// with no base to resolve relative specifiers against. The URL is built at
|
|
90
|
+
// run time on purpose, so Vite has nothing to analyze here.
|
|
85
91
|
const rewritten = rewriteBundle(source);
|
|
86
92
|
const module = await import(
|
|
93
|
+
/* @vite-ignore */
|
|
87
94
|
`data:text/javascript;base64,${Buffer.from(rewritten).toString('base64')}`
|
|
88
95
|
);
|
|
89
96
|
return module.default;
|
|
@@ -116,10 +123,16 @@ export function assertDelimiterGrowth(instance = katex) {
|
|
|
116
123
|
}
|
|
117
124
|
}
|
|
118
125
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
126
|
+
// Build and server rendering only. In a browser there is no KaTeX bundle on
|
|
127
|
+
// disk to rewrite. No client graph should include this module (lab math is
|
|
128
|
+
// rendered on the server); if one does, a client-side render falls back to
|
|
129
|
+
// stock sizing rather than breaking the island.
|
|
130
|
+
if (typeof window === 'undefined') {
|
|
131
|
+
const grown = await loadGrownKatex();
|
|
132
|
+
for (const name of RENDER_EXPORTS) {
|
|
133
|
+
if (typeof grown[name] === 'function') katex[name] = grown[name];
|
|
134
|
+
}
|
|
135
|
+
assertDelimiterGrowth();
|
|
122
136
|
}
|
|
123
|
-
assertDelimiterGrowth();
|
|
124
137
|
|
|
125
138
|
export default katex;
|
|
@@ -8,11 +8,18 @@
|
|
|
8
8
|
* - `<prefix>-ref-resolves` (error): every `[[<prefix>-key]]` /
|
|
9
9
|
* `[[slug#<prefix>-key]]` points at the matching keyed float with that `id`.
|
|
10
10
|
* - `<prefix>-key-unused` (info): a keyed float is never referenced.
|
|
11
|
+
* - `<prefix>-removed-prop` (error): the tag sets a prop that no longer
|
|
12
|
+
* exists. Mostly a name prop (`<Figure title>`, `<Diagram label>`) — every
|
|
13
|
+
* float is named by the one caption pair, `caption` (long) and `short`
|
|
14
|
+
* (optional), and a removed prop would otherwise render nothing and escape
|
|
15
|
+
* rule S1 — and a diagram's compaction props (`defaultOpen`,
|
|
16
|
+
* `rememberState`), since a diagram no longer folds by itself.
|
|
11
17
|
*
|
|
12
|
-
* `tables-validate.ts`, `figures-validate.ts`,
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
18
|
+
* `tables-validate.ts`, `figures-validate.ts`, `diagrams-validate.ts` and
|
|
19
|
+
* `examples-validate.ts` (`<CompactExamples>`, which adds its required-prop
|
|
20
|
+
* check) are thin wrappers over this so callers import a kind-specific
|
|
21
|
+
* validator with narrow diagnostic-code types, while `equations-validate.ts`
|
|
22
|
+
* stays untouched — equations predate this and keep their own dedicated path.
|
|
16
23
|
*/
|
|
17
24
|
interface KeyedFloatKind {
|
|
18
25
|
/** The MDX component tag, e.g. `Table` or `Figure`. */
|
|
@@ -21,10 +28,19 @@ interface KeyedFloatKind {
|
|
|
21
28
|
readonly prefix: string;
|
|
22
29
|
/** Lowercase noun used in diagnostic messages, e.g. `table` or `figure`. */
|
|
23
30
|
readonly noun: string;
|
|
31
|
+
/**
|
|
32
|
+
* Name props this kind no longer takes, each with the fix its diagnostic
|
|
33
|
+
* gives, e.g. `{ title: 'use \`caption\` …' }`. Omitted: none.
|
|
34
|
+
*/
|
|
35
|
+
readonly removedProps?: Readonly<Record<string, string>>;
|
|
24
36
|
}
|
|
25
37
|
interface KeyedFloatLabelRecord {
|
|
26
38
|
readonly key: string;
|
|
27
39
|
readonly number: string;
|
|
40
|
+
/** The float's literal `short` caption, when it has one. */
|
|
41
|
+
readonly short?: string;
|
|
42
|
+
/** The float's literal `caption`; absent when it is an expression. */
|
|
43
|
+
readonly caption?: string;
|
|
28
44
|
readonly lessonId: string;
|
|
29
45
|
readonly slug: string;
|
|
30
46
|
readonly section: number;
|
|
@@ -57,14 +73,15 @@ interface KeyedFloatLessonInput {
|
|
|
57
73
|
readonly file: string;
|
|
58
74
|
readonly body: string;
|
|
59
75
|
/**
|
|
60
|
-
* `{ prefix, offset }` — the
|
|
61
|
-
*
|
|
62
|
-
* When set, floats are numbered
|
|
63
|
-
*
|
|
64
|
-
* passes a different `offset`
|
|
76
|
+
* `{ prefix, offset }` — the lesson's float-number prefix (`‹part›.‹chapter›`,
|
|
77
|
+
* or `‹part›` under `numbering.floats: "part"`) and how many floats of this
|
|
78
|
+
* kind precede the lesson in that scope. When set, floats are numbered
|
|
79
|
+
* `‹prefix›.‹offset + n›`; when omitted, the legacy `##`-section scheme.
|
|
80
|
+
* Each kind counts on its own, so the caller passes a different `offset`
|
|
81
|
+
* for `<Table>` than for `<Figure>`.
|
|
65
82
|
*/
|
|
66
83
|
readonly numbering?: {
|
|
67
|
-
readonly prefix: number;
|
|
84
|
+
readonly prefix: number | string;
|
|
68
85
|
readonly offset: number;
|
|
69
86
|
};
|
|
70
87
|
}
|
|
@@ -21,7 +21,7 @@ function bodyFloatRefs(lesson, prefix) {
|
|
|
21
21
|
return refs;
|
|
22
22
|
}
|
|
23
23
|
function validateKeyedFloats(lessons, kind) {
|
|
24
|
-
const { tagName, prefix, noun } = kind;
|
|
24
|
+
const { tagName, prefix, noun, removedProps = {} } = kind;
|
|
25
25
|
const labels = [];
|
|
26
26
|
const diagnostics = [];
|
|
27
27
|
const numbersBySlug = {};
|
|
@@ -34,6 +34,18 @@ function validateKeyedFloats(lessons, kind) {
|
|
|
34
34
|
tagName,
|
|
35
35
|
numbering: lesson.numbering
|
|
36
36
|
})) {
|
|
37
|
+
for (const prop of raw.props) {
|
|
38
|
+
const fix = removedProps[prop];
|
|
39
|
+
if (fix === void 0) continue;
|
|
40
|
+
diagnostics.push({
|
|
41
|
+
code: `${prefix}-removed-prop`,
|
|
42
|
+
severity: "error",
|
|
43
|
+
message: `${lesson.lessonId} has a <${tagName} ${prop}="\u2026">${raw.key ? ` (id="${raw.key}")` : ""}, but <${tagName}> has no \`${prop}\` prop: ${fix}`,
|
|
44
|
+
file: lesson.file,
|
|
45
|
+
lessonId: lesson.lessonId,
|
|
46
|
+
...raw.key ? { key: raw.key } : {}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
37
49
|
if (raw.key == null) continue;
|
|
38
50
|
if (!KEYED_FLOAT_KEY_PATTERN.test(raw.key)) {
|
|
39
51
|
diagnostics.push({
|
|
@@ -62,6 +74,8 @@ function validateKeyedFloats(lessons, kind) {
|
|
|
62
74
|
labels.push({
|
|
63
75
|
key: raw.key,
|
|
64
76
|
number: raw.number,
|
|
77
|
+
...raw.short ? { short: raw.short } : {},
|
|
78
|
+
...raw.caption ? { caption: raw.caption } : {},
|
|
65
79
|
lessonId: lesson.lessonId,
|
|
66
80
|
slug: lesson.slug,
|
|
67
81
|
section: raw.section,
|
|
@@ -19,10 +19,13 @@ export function parseKeyedFloatRef(raw: any, { prefix }: {
|
|
|
19
19
|
} | undefined;
|
|
20
20
|
/**
|
|
21
21
|
* The raw-text equivalent of `collectKeyedJsxFloats`, mirroring
|
|
22
|
-
* `scanEquationLabels`: every top-level `<TagName …>` in appearance order
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
22
|
+
* `scanEquationLabels`: every top-level `<TagName …>` in appearance order,
|
|
23
|
+
* with its literal `short` / `caption` (the caption pair every float takes;
|
|
24
|
+
* `null` when absent or an expression) and `props`, the names of every prop
|
|
25
|
+
* the tag sets, for `keyed-float-validate.ts`'s removed-prop check.
|
|
26
|
+
* With `numbering` (`{ prefix, offset }` — the lesson's float-number prefix
|
|
27
|
+
* and the count of this kind of float before it in that scope) the number is
|
|
28
|
+
* `‹prefix›.‹offset + n›`, `n` sequential over the whole lesson; without it, `##`
|
|
26
29
|
* headings advance a section counter. Other JSX components' bodies are blanked
|
|
27
30
|
* first so a `<Table>` nested inside e.g. `<CompactExample>` (and any `##`-like
|
|
28
31
|
* text inside an unrelated component) is never counted.
|
|
@@ -33,6 +36,9 @@ export function scanKeyedJsxFloats(markdown: any, { tagName, numbering }: {
|
|
|
33
36
|
}): {
|
|
34
37
|
key: string | null | undefined;
|
|
35
38
|
number: string;
|
|
39
|
+
short: string | null | undefined;
|
|
40
|
+
caption: string | null | undefined;
|
|
41
|
+
props: any[] | undefined;
|
|
36
42
|
section: number;
|
|
37
43
|
indexInSection: number;
|
|
38
44
|
}[];
|
|
@@ -45,7 +51,7 @@ export function countKeyedJsxFloats(markdown: any, { tagName }: {
|
|
|
45
51
|
}): number;
|
|
46
52
|
/**
|
|
47
53
|
* The mdast equivalent of `scanKeyedJsxFloats`: walk the tree's top-level
|
|
48
|
-
* children (and through
|
|
54
|
+
* children (and through the `TRANSPARENT_COMPONENTS`' bodies) in document order, advancing the
|
|
49
55
|
* section counter on every top-level depth-2 heading and emitting a record for
|
|
50
56
|
* every `mdxJsxFlowElement` named `tagName` — keyed or not, exactly like
|
|
51
57
|
* `collectDisplayEquations`. `numbering` is threaded the same way as in
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
* `#eq-key` — `keyedFloatAnchorId` prepends `tab-`/`fig-`/`dia-`.
|
|
12
12
|
*
|
|
13
13
|
* Only top-level occurrences count, exactly like `collectDisplayEquations`,
|
|
14
|
-
* except that `<Memo
|
|
15
|
-
*
|
|
14
|
+
* except that the `TRANSPARENT_COMPONENTS` (`<Memo>`, `<ShortNote>`,
|
|
15
|
+
* `<Fold>`) are see-through: a float in a memo or a fold stays in the
|
|
16
|
+
* lesson's reading order. A keyed float nested inside a
|
|
16
17
|
* `<CompactExample>` (or any other component) is illustrative, not part of the
|
|
17
18
|
* official sequence.
|
|
18
19
|
*
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
resolveNumbering,
|
|
27
28
|
stripFencedCode,
|
|
28
29
|
stripJsxComponents,
|
|
30
|
+
TRANSPARENT_COMPONENTS,
|
|
29
31
|
} from './equations.mjs';
|
|
30
32
|
|
|
31
33
|
/** Same shape as `EQ_KEY_PATTERN` — the notation-key grammar without dots. */
|
|
@@ -37,10 +39,11 @@ function floatNumber(section, index) {
|
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
/**
|
|
40
|
-
* The visible number for one float. With a `prefix` (
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
42
|
+
* The visible number for one float. With a `prefix` (`‹part›.‹chapter›`, or
|
|
43
|
+
* the part alone under `numbering.floats: "part"`) the scheme is
|
|
44
|
+
* `‹prefix›.‹offset + indexInLesson›` and `##` headings are ignored; without
|
|
45
|
+
* it, the legacy `##`-section scheme. Mirrors `displayEquationNumber` in
|
|
46
|
+
* `equations.mjs`. Each kind of float counts on its own — a `<Table>` never
|
|
44
47
|
* advances the `<Figure>` or `<Diagram>` sequence — so `offset` is per kind too.
|
|
45
48
|
*/
|
|
46
49
|
function keyedFloatNumber({
|
|
@@ -79,22 +82,60 @@ function readIdAttribute(attributesSource) {
|
|
|
79
82
|
return match ? match[2] : undefined;
|
|
80
83
|
}
|
|
81
84
|
|
|
85
|
+
/**
|
|
86
|
+
* A literal string attribute (`name="…"` / `name='…'`) of a JSX opening tag's
|
|
87
|
+
* attribute source, trimmed; `null` when absent, empty, or an expression.
|
|
88
|
+
*/
|
|
89
|
+
function readStringAttribute(attributesSource, name) {
|
|
90
|
+
const match = new RegExp(
|
|
91
|
+
String.raw`(?:^|\s)${name}\s*=\s*(?:"([^"]*)"|'([^']*)')`,
|
|
92
|
+
).exec(attributesSource);
|
|
93
|
+
const value = (match?.[1] ?? match?.[2])?.trim();
|
|
94
|
+
return value ? value : null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Every prop name a JSX opening tag's attribute source sets (`name=`). */
|
|
98
|
+
function attributeNames(attributesSource) {
|
|
99
|
+
const bare = attributesSource.replace(
|
|
100
|
+
/"[^"]*"|'[^']*'|\{(?:[^{}]|\{[^{}]*\})*\}/g,
|
|
101
|
+
' ',
|
|
102
|
+
);
|
|
103
|
+
return [...bare.matchAll(/(?:^|\s)([A-Za-z_][\w:.-]*)\s*=/g)].map(
|
|
104
|
+
(match) => match[1],
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** A literal string value of an mdast JSX attribute, trimmed, else `null`. */
|
|
109
|
+
function mdxStringAttribute(node, name) {
|
|
110
|
+
const attribute = (node.attributes ?? []).find(
|
|
111
|
+
(candidate) =>
|
|
112
|
+
candidate?.type === 'mdxJsxAttribute' && candidate.name === name,
|
|
113
|
+
);
|
|
114
|
+
const value =
|
|
115
|
+
typeof attribute?.value === 'string' ? attribute.value.trim() : '';
|
|
116
|
+
return value ? value : null;
|
|
117
|
+
}
|
|
118
|
+
|
|
82
119
|
/**
|
|
83
120
|
* The raw-text equivalent of `collectKeyedJsxFloats`, mirroring
|
|
84
|
-
* `scanEquationLabels`: every top-level `<TagName …>` in appearance order
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
121
|
+
* `scanEquationLabels`: every top-level `<TagName …>` in appearance order,
|
|
122
|
+
* with its literal `short` / `caption` (the caption pair every float takes;
|
|
123
|
+
* `null` when absent or an expression) and `props`, the names of every prop
|
|
124
|
+
* the tag sets, for `keyed-float-validate.ts`'s removed-prop check.
|
|
125
|
+
* With `numbering` (`{ prefix, offset }` — the lesson's float-number prefix
|
|
126
|
+
* and the count of this kind of float before it in that scope) the number is
|
|
127
|
+
* `‹prefix›.‹offset + n›`, `n` sequential over the whole lesson; without it, `##`
|
|
88
128
|
* headings advance a section counter. Other JSX components' bodies are blanked
|
|
89
129
|
* first so a `<Table>` nested inside e.g. `<CompactExample>` (and any `##`-like
|
|
90
130
|
* text inside an unrelated component) is never counted.
|
|
91
131
|
*/
|
|
92
132
|
export function scanKeyedJsxFloats(markdown, { tagName, numbering }) {
|
|
93
133
|
const { prefix, offset } = resolveNumbering(numbering);
|
|
94
|
-
const text = stripJsxComponents(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
134
|
+
const text = stripJsxComponents(
|
|
135
|
+
stripFencedCode(markdown),
|
|
136
|
+
tagName,
|
|
137
|
+
TRANSPARENT_COMPONENTS,
|
|
138
|
+
);
|
|
98
139
|
const tokens = [];
|
|
99
140
|
const heading = /^[ \t]{0,3}##[ \t]+\S/gm;
|
|
100
141
|
const opening = new RegExp(`<${tagName}\\b([^>]*)>`, 'g');
|
|
@@ -102,8 +143,16 @@ export function scanKeyedJsxFloats(markdown, { tagName, numbering }) {
|
|
|
102
143
|
tokens.push({ kind: 'section', index: match.index });
|
|
103
144
|
}
|
|
104
145
|
for (const match of text.matchAll(opening)) {
|
|
105
|
-
const
|
|
106
|
-
|
|
146
|
+
const attributes = match[1] ?? '';
|
|
147
|
+
const key = readIdAttribute(attributes);
|
|
148
|
+
tokens.push({
|
|
149
|
+
kind: 'float',
|
|
150
|
+
index: match.index,
|
|
151
|
+
key: key ?? null,
|
|
152
|
+
short: readStringAttribute(attributes, 'short'),
|
|
153
|
+
caption: readStringAttribute(attributes, 'caption'),
|
|
154
|
+
props: attributeNames(attributes),
|
|
155
|
+
});
|
|
107
156
|
}
|
|
108
157
|
tokens.sort((a, b) => a.index - b.index);
|
|
109
158
|
|
|
@@ -128,6 +177,9 @@ export function scanKeyedJsxFloats(markdown, { tagName, numbering }) {
|
|
|
128
177
|
indexInSection,
|
|
129
178
|
indexInLesson,
|
|
130
179
|
}),
|
|
180
|
+
short: token.short,
|
|
181
|
+
caption: token.caption,
|
|
182
|
+
props: token.props,
|
|
131
183
|
section,
|
|
132
184
|
indexInSection,
|
|
133
185
|
});
|
|
@@ -145,7 +197,7 @@ export function countKeyedJsxFloats(markdown, { tagName }) {
|
|
|
145
197
|
|
|
146
198
|
/**
|
|
147
199
|
* The mdast equivalent of `scanKeyedJsxFloats`: walk the tree's top-level
|
|
148
|
-
* children (and through
|
|
200
|
+
* children (and through the `TRANSPARENT_COMPONENTS`' bodies) in document order, advancing the
|
|
149
201
|
* section counter on every top-level depth-2 heading and emitting a record for
|
|
150
202
|
* every `mdxJsxFlowElement` named `tagName` — keyed or not, exactly like
|
|
151
203
|
* `collectDisplayEquations`. `numbering` is threaded the same way as in
|
|
@@ -171,6 +223,8 @@ export function collectKeyedJsxFloats(tree, { tagName, numbering }) {
|
|
|
171
223
|
typeof idAttribute?.value === 'string' ? idAttribute.value : undefined;
|
|
172
224
|
floats.push({
|
|
173
225
|
key: key ?? null,
|
|
226
|
+
short: mdxStringAttribute(node, 'short'),
|
|
227
|
+
caption: mdxStringAttribute(node, 'caption'),
|
|
174
228
|
number: keyedFloatNumber({
|
|
175
229
|
prefix,
|
|
176
230
|
offset,
|
|
@@ -186,8 +240,7 @@ export function collectKeyedJsxFloats(tree, { tagName, numbering }) {
|
|
|
186
240
|
}
|
|
187
241
|
if (
|
|
188
242
|
node.type === 'mdxJsxFlowElement' &&
|
|
189
|
-
node.name
|
|
190
|
-
node.name !== 'ShortNote'
|
|
243
|
+
!TRANSPARENT_COMPONENTS.includes(node.name)
|
|
191
244
|
) {
|
|
192
245
|
return;
|
|
193
246
|
}
|
|
@@ -207,7 +260,7 @@ export function collectKeyedJsxFloats(tree, { tagName, numbering }) {
|
|
|
207
260
|
collectFromDetail(node);
|
|
208
261
|
} else if (
|
|
209
262
|
node.type === 'mdxJsxFlowElement' &&
|
|
210
|
-
(node.name
|
|
263
|
+
TRANSPARENT_COMPONENTS.includes(node.name)
|
|
211
264
|
) {
|
|
212
265
|
collectFromDetail(node);
|
|
213
266
|
}
|
|
@@ -10,7 +10,9 @@ export function parseKnowledgeCheckRef(raw: any): {
|
|
|
10
10
|
export function knowledgeCheckAnchorId(key: any): string;
|
|
11
11
|
/**
|
|
12
12
|
* Number an ordered assessment-id list. With manifest placement, the number
|
|
13
|
-
* is
|
|
13
|
+
* is `‹prefix›.‹offset + index›` — `prefix` is `‹part›.‹chapter›`, or the part
|
|
14
|
+
* alone under `numbering.floats: "part"`; without it, the list remains
|
|
15
|
+
* usefully page-local.
|
|
14
16
|
*/
|
|
15
17
|
export function collectKnowledgeChecks(assessmentIds: any, numbering: any): any;
|
|
16
18
|
/**
|
|
@@ -28,12 +28,17 @@ export function knowledgeCheckAnchorId(key) {
|
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Number an ordered assessment-id list. With manifest placement, the number
|
|
31
|
-
* is
|
|
31
|
+
* is `‹prefix›.‹offset + index›` — `prefix` is `‹part›.‹chapter›`, or the part
|
|
32
|
+
* alone under `numbering.floats: "part"`; without it, the list remains
|
|
33
|
+
* usefully page-local.
|
|
32
34
|
*/
|
|
33
35
|
export function collectKnowledgeChecks(assessmentIds, numbering) {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
const rawPrefix = numbering?.prefix;
|
|
37
|
+
const prefix =
|
|
38
|
+
(Number.isInteger(rawPrefix) && rawPrefix > 0) ||
|
|
39
|
+
(typeof rawPrefix === 'string' && /^\d+(?:\.\d+)*$/.test(rawPrefix))
|
|
40
|
+
? rawPrefix
|
|
41
|
+
: undefined;
|
|
37
42
|
const offset = Number.isInteger(numbering?.offset) && numbering.offset >= 0
|
|
38
43
|
? numbering.offset
|
|
39
44
|
: 0;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The half of lab math an interactive island needs in the browser: the shape
|
|
3
|
+
* of a compiled template and its slot placeholder. It imports nothing, so an
|
|
4
|
+
* island that uses it stays clear of the KaTeX build chain behind
|
|
5
|
+
* `render-lab-math.ts`, which is server-only (`katex-delimiters.mjs` reads
|
|
6
|
+
* KaTeX's bundle from disk).
|
|
7
|
+
*/
|
|
8
|
+
interface RenderedLabMath {
|
|
9
|
+
/** KaTeX HTML+MathML with `data-notation-key` and `data-lab-slot` spans. */
|
|
10
|
+
readonly html: string;
|
|
11
|
+
/** Slot names, in template order, the island must supply at runtime. */
|
|
12
|
+
readonly slots: readonly string[];
|
|
13
|
+
/** Notation keys the template resolved to, for diagnostics. */
|
|
14
|
+
readonly keys: readonly string[];
|
|
15
|
+
/**
|
|
16
|
+
* Plain, copyable LaTeX — every explained glyph resolved to its bare
|
|
17
|
+
* symbol, every `\slot{name}{…}` replaced by `slotPlaceholder(name)` — so
|
|
18
|
+
* the island can substitute each slot's *current* live value in and keep
|
|
19
|
+
* `[data-notation-formula]`'s `data-latex` accurate as the reader drags a
|
|
20
|
+
* control, the same "click the equation to copy it" gesture a static
|
|
21
|
+
* lesson or glossary equation gets (`NotationLayer.astro`).
|
|
22
|
+
*/
|
|
23
|
+
readonly plainLatexTemplate: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A slot's placeholder inside `plainLatexTemplate` — distinct from the
|
|
27
|
+
* numeric sentinel `render-lab-math.ts` uses internally (that one only has to
|
|
28
|
+
* survive a single notation-binding pass at build time; this one is the
|
|
29
|
+
* stable, name-keyed token an island substitutes with a live value at
|
|
30
|
+
* runtime, so it's exported for that purpose).
|
|
31
|
+
*/
|
|
32
|
+
declare function slotPlaceholder(name: string): string;
|
|
33
|
+
|
|
34
|
+
export { type RenderedLabMath, slotPlaceholder };
|