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
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule N17's engine half: make `\left`/`\right` grow strictly outward.
|
|
3
|
+
*
|
|
4
|
+
* Importing this module for its side effect installs a KaTeX whose
|
|
5
|
+
* `\delimitershortfall` is `-1pt` instead of the stock `5pt`, so a scalable
|
|
6
|
+
* delimiter pair is always *strictly* taller than what it wraps and nesting
|
|
7
|
+
* depth shows up on its own. See `docs/katex-bracket-scaling.md` for the
|
|
8
|
+
* derivation and `docs/authoring/notation.md#n17` for the authoring rule.
|
|
9
|
+
*
|
|
10
|
+
* ## Why this is not a `patches/` file
|
|
11
|
+
*
|
|
12
|
+
* `delimiterExtend` is a function-local `const` inside `makeLeftRightDelim`.
|
|
13
|
+
* Nothing KaTeX exports reaches it — `__defineMacro`, `__defineFunction`,
|
|
14
|
+
* `__defineSymbol`, `__setFontMetrics` and the settings schema all sit above
|
|
15
|
+
* the layout engine — so the constant can only be changed in the module's own
|
|
16
|
+
* source. A `pnpm patch` does that, but a patch is project-local: it cannot
|
|
17
|
+
* travel inside the published `explico` artifact, so every downstream course
|
|
18
|
+
* would have to reapply it or silently render flat nests.
|
|
19
|
+
*
|
|
20
|
+
* So the engine does it in code instead. It reads KaTeX's own ESM bundle,
|
|
21
|
+
* rewrites that one constant, imports the result as a module, and moves the
|
|
22
|
+
* rendering entry points onto the shared `katex` object every consumer in the
|
|
23
|
+
* process already imported. KaTeX's default export is a plain mutable object
|
|
24
|
+
* and `rehype-katex` reads `katex.renderToString` at call time, so the swap
|
|
25
|
+
* reaches lesson-body math without forking the plugin.
|
|
26
|
+
*
|
|
27
|
+
* ## Why this is safe to rely on
|
|
28
|
+
*
|
|
29
|
+
* The assumptions above (a matching constant, a mutable export, a bundle with
|
|
30
|
+
* no imports of its own) are all checked here, and `assertDelimiterGrowth()`
|
|
31
|
+
* renders a known nest afterwards and throws when the sizes do not actually
|
|
32
|
+
* increase. A course therefore either renders with engine-sized delimiters or
|
|
33
|
+
* fails its build saying so — it cannot quietly get the stock behaviour.
|
|
34
|
+
*/
|
|
35
|
+
import katex from 'katex';
|
|
36
|
+
|
|
37
|
+
/** TeX's `\delimitershortfall`, in points, as KaTeX hard-codes it. */
|
|
38
|
+
const STOCK_SHORTFALL = '5.0';
|
|
39
|
+
/** The negative shortfall that forces strict outward growth. */
|
|
40
|
+
const GROWN_SHORTFALL = '-1.0';
|
|
41
|
+
|
|
42
|
+
const needle = (points) =>
|
|
43
|
+
`var delimiterExtend = ${points} / options.fontMetrics().ptPerEm;`;
|
|
44
|
+
|
|
45
|
+
/** The entry points that build a delimiter and must come from the rewrite. */
|
|
46
|
+
const RENDER_EXPORTS = [
|
|
47
|
+
'render',
|
|
48
|
+
'renderToString',
|
|
49
|
+
'__renderToDomTree',
|
|
50
|
+
'__renderToHTMLTree',
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Exported for the test that pins both failure messages — a KaTeX upgrade that
|
|
55
|
+
* moves the constant, and a leftover `patches/` file, are the two ways this
|
|
56
|
+
* stops being able to install itself.
|
|
57
|
+
*/
|
|
58
|
+
export function rewriteBundle(source) {
|
|
59
|
+
const stock = needle(STOCK_SHORTFALL);
|
|
60
|
+
if (!source.includes(stock)) {
|
|
61
|
+
// Either the constant moved or someone already rewrote it. Both mean the
|
|
62
|
+
// assumption this module rests on no longer holds for this KaTeX build.
|
|
63
|
+
const alreadyGrown = source.includes(needle(GROWN_SHORTFALL));
|
|
64
|
+
throw new Error(
|
|
65
|
+
alreadyGrown
|
|
66
|
+
? `explico: katex ${katex.version} already carries the -1pt delimiter ` +
|
|
67
|
+
'shortfall (a patches/ file?). Remove that patch — the engine ' +
|
|
68
|
+
'installs it in code (src/reference/katex-delimiters.mjs).'
|
|
69
|
+
: `explico: cannot install the N17 delimiter sizing into katex ` +
|
|
70
|
+
`${katex.version}: the \\delimitershortfall constant ` +
|
|
71
|
+
`(${stock}) is not in its ESM bundle. Re-derive the one-line ` +
|
|
72
|
+
'change against the new bundle — see docs/katex-bracket-scaling.md.',
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return source.replace(stock, needle(GROWN_SHORTFALL));
|
|
76
|
+
}
|
|
77
|
+
|
|
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');
|
|
86
|
+
const require = createRequire(import.meta.url);
|
|
87
|
+
const source = readFileSync(require.resolve('katex/dist/katex.mjs'), 'utf8');
|
|
88
|
+
// The bundle is self-contained, so it can be re-imported from a data URL
|
|
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.
|
|
91
|
+
const rewritten = rewriteBundle(source);
|
|
92
|
+
const module = await import(
|
|
93
|
+
/* @vite-ignore */
|
|
94
|
+
`data:text/javascript;base64,${Buffer.from(rewritten).toString('base64')}`
|
|
95
|
+
);
|
|
96
|
+
return module.default;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Render a known nest and confirm the outer pair really is larger than the
|
|
101
|
+
* inner one. Cheap, and it turns every way this could go wrong — an upstream
|
|
102
|
+
* refactor, a frozen export, an import that beat us to it — into one loud
|
|
103
|
+
* failure at the point of installation.
|
|
104
|
+
*/
|
|
105
|
+
export function assertDelimiterGrowth(instance = katex) {
|
|
106
|
+
const html = instance.renderToString(
|
|
107
|
+
String.raw`\left[1+\left(\frac{a}{b}\right)\right]`,
|
|
108
|
+
{ displayMode: true, output: 'html' },
|
|
109
|
+
);
|
|
110
|
+
const sizes = [...html.matchAll(/delimsizing size(\d)/g)].map((m) =>
|
|
111
|
+
Number(m[1]),
|
|
112
|
+
);
|
|
113
|
+
// Two pairs, so four glyphs: outer, inner, inner, outer.
|
|
114
|
+
const grew = sizes.length === 4 && sizes[0] > sizes[1];
|
|
115
|
+
if (!grew) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
'explico: the N17 delimiter sizing did not take effect — a nested ' +
|
|
118
|
+
`\\left…\\right pair rendered as [${sizes.join(', ')}] instead of ` +
|
|
119
|
+
'growing outward. Something rendered math before ' +
|
|
120
|
+
"'explico/reference/katex-delimiters.mjs' was imported, or katex " +
|
|
121
|
+
'changed shape. See docs/katex-bracket-scaling.md.',
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
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();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export default katex;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// Side-effect import: installs rule N17's delimiter sizing onto the shared
|
|
2
|
+
// `katex` module before anything renders. A course wires this file into its
|
|
3
|
+
// `astro.config.mjs`, so `rehype-katex` inherits it without a patch.
|
|
4
|
+
import './katex-delimiters.mjs';
|
|
1
5
|
import { NOTATION_KEY_PATTERN } from './remark-notation.mjs';
|
|
2
6
|
|
|
3
7
|
export const EXPLAIN_MACRO = String.raw`\htmlData{notation-key=#1}{#2}`;
|
|
@@ -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 };
|