explico 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +344 -1
- package/README.md +117 -3
- package/dist/compiler/astro.d.ts +38 -0
- package/dist/compiler/astro.js +258 -0
- package/dist/compiler/cli.d.ts +13 -0
- package/dist/compiler/cli.js +32 -12
- package/dist/compiler/collections.d.ts +3 -2
- package/dist/compiler/collections.js +28 -1
- package/dist/compiler/course-config.d.ts +86 -4
- package/dist/compiler/course-config.js +39 -0
- package/dist/compiler/exports/standalone-html.d.ts +34 -0
- package/dist/compiler/exports/standalone-html.js +803 -0
- package/dist/compiler/lesson-derivation.d.ts +1 -1
- package/dist/compiler/lesson-derivation.js +2 -2
- package/dist/compiler/manifest.d.ts +112 -7
- package/dist/compiler/manifest.js +205 -40
- package/dist/compiler/resolution-report.d.ts +4 -0
- package/dist/reference/base-abbreviations.d.mts +30 -0
- package/dist/reference/base-abbreviations.mjs +60 -0
- package/dist/reference/base-operators.d.mts +15 -0
- package/dist/reference/base-operators.mjs +40 -0
- package/dist/reference/base-roman-labels.d.mts +35 -0
- package/dist/reference/base-roman-labels.mjs +43 -0
- package/dist/reference/consistency.d.ts +8 -2
- package/dist/reference/consistency.js +50 -3
- package/dist/reference/diagrams-validate.d.ts +21 -0
- package/dist/reference/diagrams-validate.js +13 -0
- package/dist/reference/equations-validate.d.ts +8 -3
- package/dist/reference/equations-validate.js +1 -1
- package/dist/reference/equations.d.mts +66 -35
- package/dist/reference/equations.mjs +142 -40
- package/dist/reference/gate-abbreviations.d.ts +17 -0
- package/dist/reference/gate-abbreviations.js +64 -0
- package/dist/reference/gate-math.d.ts +28 -5
- package/dist/reference/gate-math.js +5 -2
- package/dist/reference/gate-prose-notation.d.ts +60 -0
- package/dist/reference/gate-prose-notation.js +125 -0
- package/dist/reference/keyed-float-validate.d.ts +14 -8
- package/dist/reference/keyed-float-validate.js +1 -1
- package/dist/reference/keyed-jsx-floats.d.mts +24 -21
- package/dist/reference/keyed-jsx-floats.mjs +90 -41
- package/dist/reference/knowledge-checks-validate.d.ts +35 -0
- package/dist/reference/knowledge-checks-validate.js +78 -0
- package/dist/reference/knowledge-checks.d.mts +25 -0
- package/dist/reference/knowledge-checks.mjs +45 -0
- package/dist/reference/lab-math-scope.d.ts +9 -1
- package/dist/reference/lab-math-scope.js +32 -4
- package/dist/reference/math-glyphs.d.mts +30 -0
- package/dist/reference/math-glyphs.mjs +367 -0
- package/dist/reference/mdx-attributes.d.mts +13 -0
- package/dist/reference/mdx-attributes.mjs +38 -0
- package/dist/reference/mixed-content.d.ts +17 -0
- package/dist/reference/mixed-content.js +29 -0
- package/dist/reference/page-notation.d.ts +52 -0
- package/dist/reference/page-notation.js +171 -0
- package/dist/reference/raw-floats-validate.d.ts +18 -0
- package/dist/reference/raw-floats-validate.js +53 -0
- package/dist/reference/raw-floats.d.mts +18 -0
- package/dist/reference/raw-floats.mjs +129 -0
- package/dist/reference/rehype-heading-anchors.d.mts +1 -1
- package/dist/reference/rehype-heading-anchors.mjs +107 -1
- package/dist/reference/rehype-table-header-labels.d.mts +26 -0
- package/dist/reference/rehype-table-header-labels.mjs +152 -0
- package/dist/reference/remark-abbreviations.d.mts +7 -0
- package/dist/reference/remark-abbreviations.mjs +174 -0
- package/dist/reference/remark-memo.d.mts +16 -0
- package/dist/reference/remark-memo.mjs +339 -0
- package/dist/reference/remark-notation.d.mts +102 -5
- package/dist/reference/remark-notation.mjs +506 -50
- package/dist/reference/remark-short-note.d.mts +4 -0
- package/dist/reference/remark-short-note.mjs +237 -0
- package/dist/reference/types.d.ts +16 -1
- package/dist/reference/units-vocab.js +4 -0
- package/docs/VERSIONING.md +33 -4
- package/docs/authoring/README.md +12 -0
- package/docs/authoring/agents.md +17 -2
- package/docs/authoring/components.md +135 -0
- package/docs/authoring/migrating.md +154 -7
- package/docs/authoring/notation.md +568 -8
- package/docs/authoring/structure.md +251 -0
- package/docs/authoring/visualizations.md +36 -3
- package/package.json +5 -2
- package/src/compiler/astro.ts +332 -0
- package/src/compiler/cli.ts +33 -10
- package/src/compiler/collections.ts +45 -4
- package/src/compiler/course-config.ts +131 -3
- package/src/compiler/exports/standalone-html.ts +963 -0
- package/src/compiler/lesson-derivation.ts +2 -2
- package/src/compiler/manifest.ts +367 -49
- package/src/components/HoverPanel.astro +45 -4
- package/src/components/abbreviation/AbbreviationGlossary.astro +70 -0
- package/src/components/abbreviation/AbbreviationLayer.astro +127 -0
- package/src/components/assessments/AssessmentRunner.tsx +52 -17
- package/src/components/assessments/AssessmentSet.astro +98 -14
- package/src/components/assessments/RenderedContent.tsx +55 -0
- package/src/components/assessments/assessment-randomization.ts +16 -0
- package/src/components/diagram/Diagram.astro +444 -83
- package/src/components/disclosure/hover-preview.ts +202 -0
- package/src/components/disclosure/persisted-disclosure.ts +66 -0
- package/src/components/examples/CompactExamples.astro +2 -0
- package/src/components/figure/Figure.astro +279 -10
- package/src/components/memo/Memo.astro +453 -0
- package/src/components/memo/ShortNote.astro +223 -0
- package/src/components/notation/NotationFormula.astro +18 -1
- package/src/components/notation/NotationLayer.astro +116 -8
- package/src/components/notation/render-notation-math.ts +39 -2
- package/src/components/notation/safe-math-to-react.ts +50 -0
- package/src/components/reference/FloatRefLayer.astro +233 -15
- package/src/components/starlight/CollapsibleSections.astro +461 -0
- package/src/components/starlight/DiagramEnhancer.astro +385 -0
- package/src/components/starlight/HeadingAnchorEnhancer.astro +5 -17
- package/src/components/starlight/KeyedFloatEnhancer.astro +81 -3
- package/src/components/starlight/LayoutHeader.astro +50 -0
- package/src/components/starlight/LayoutSidebar.astro +62 -1
- package/src/components/starlight/LayoutSocialIcons.astro +41 -2
- package/src/components/starlight/LessonExportActions.astro +436 -0
- package/src/components/starlight/LessonFooter.astro +38 -2
- package/src/components/starlight/section-parts.ts +31 -0
- package/src/components/table/Table.astro +296 -9
- package/src/components/table/TableOverflowExpander.astro +489 -0
- package/src/content-config.ts +20 -0
- package/src/reference/base-abbreviations.mjs +60 -0
- package/src/reference/base-operators.mjs +40 -0
- package/src/reference/base-roman-labels.mjs +43 -0
- package/src/reference/consistency.ts +73 -2
- package/src/reference/diagrams-validate.ts +40 -0
- package/src/reference/equations-validate.ts +6 -4
- package/src/reference/equations.mjs +142 -40
- package/src/reference/gate-abbreviations.ts +128 -0
- package/src/reference/gate-math.ts +27 -7
- package/src/reference/gate-prose-notation.ts +224 -0
- package/src/reference/keyed-float-validate.ts +12 -9
- package/src/reference/keyed-jsx-floats.mjs +90 -41
- package/src/reference/knowledge-checks-validate.ts +130 -0
- package/src/reference/knowledge-checks.mjs +45 -0
- package/src/reference/lab-math-scope.ts +45 -3
- package/src/reference/math-glyphs.mjs +367 -0
- package/src/reference/mdx-attributes.mjs +38 -0
- package/src/reference/mixed-content.ts +69 -0
- package/src/reference/page-notation.ts +252 -0
- package/src/reference/raw-floats-validate.ts +103 -0
- package/src/reference/raw-floats.mjs +129 -0
- package/src/reference/rehype-heading-anchors.mjs +107 -1
- package/src/reference/rehype-table-header-labels.mjs +152 -0
- package/src/reference/remark-abbreviations.mjs +174 -0
- package/src/reference/remark-memo.mjs +339 -0
- package/src/reference/remark-notation.mjs +506 -50
- package/src/reference/remark-short-note.mjs +237 -0
- package/src/reference/types.ts +16 -0
- package/src/reference/units-vocab.ts +4 -0
- package/src/styles/global.css +1125 -50
|
@@ -2,12 +2,17 @@
|
|
|
2
2
|
* Equation identity (Step D7, extended F3) — shared, framework-free helpers.
|
|
3
3
|
*
|
|
4
4
|
* Every display equation (`$$…$$`) in a lesson is numbered and given an `id`
|
|
5
|
-
* from appearance order. When the caller supplies
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* the
|
|
10
|
-
*
|
|
5
|
+
* from appearance order. When the caller supplies `numbering`
|
|
6
|
+
* (`{ prefix, offset }` — see `resolveNumbering`), the number is
|
|
7
|
+
* `‹part›.‹offset + n›`: the prefix is the **part**'s ordinal among the sidebar
|
|
8
|
+
* groups, `n` counts every display equation in the lesson, and `offset` carries
|
|
9
|
+
* the count from earlier lessons of the same part so the sequence runs
|
|
10
|
+
* continuously across a part rather than restarting on every page. `##` and
|
|
11
|
+
* `###` headings never affect it — `(2.4)` is the fourth display equation of
|
|
12
|
+
* part 2, wherever in that part it sits. See `docs/authoring/structure.md` for
|
|
13
|
+
* the level names. With no `numbering` the number falls back to the
|
|
14
|
+
* `##`-section scheme `‹section›.‹indexInSection›` (used by hand-built test
|
|
15
|
+
* trees and non-lesson pages). An author additionally
|
|
11
16
|
* marks an equation with a trailing `\label{eq:<key>}` inside its `$$…$$`
|
|
12
17
|
* block (inside math, so the `:` is safe and the form matches amsmath) when
|
|
13
18
|
* they want a *stable* anchor that survives edits and cross-page references:
|
|
@@ -63,19 +68,42 @@ function equationNumber(section, index) {
|
|
|
63
68
|
}
|
|
64
69
|
|
|
65
70
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
71
|
+
* Normalise the numbering input to `{ prefix, offset }`.
|
|
72
|
+
*
|
|
73
|
+
* `prefix` is the number before the dot — the **part**'s ordinal (its position
|
|
74
|
+
* among the sidebar groups). `offset` is how many display equations already
|
|
75
|
+
* appeared in earlier lessons of that same part, so `n` runs continuously
|
|
76
|
+
* across a part instead of restarting on every page. A bare number is the
|
|
77
|
+
* legacy per-lesson form (`prefix = n`, `offset = 0`) and is still accepted so
|
|
78
|
+
* hand-built trees and unit tests keep working. `undefined`/`null`/`0` ⇒ the
|
|
79
|
+
* `##`-section fallback for non-lesson pages.
|
|
80
|
+
*/
|
|
81
|
+
export function resolveNumbering(input) {
|
|
82
|
+
if (input == null) return { prefix: undefined, offset: 0 };
|
|
83
|
+
if (typeof input === 'number') {
|
|
84
|
+
return { prefix: input || undefined, offset: 0 };
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
prefix: input.prefix || undefined,
|
|
88
|
+
offset: Number(input.offset) || 0,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The visible number for one display equation. With a `prefix` (the part's
|
|
94
|
+
* ordinal) the scheme is `‹part›.‹offset + indexInLesson›` and `##` headings
|
|
68
95
|
* are ignored; without it, the legacy `##`-section scheme.
|
|
69
96
|
*/
|
|
70
97
|
export function displayEquationNumber({
|
|
71
|
-
|
|
98
|
+
prefix,
|
|
99
|
+
offset = 0,
|
|
72
100
|
section,
|
|
73
101
|
indexInSection,
|
|
74
102
|
indexInLesson,
|
|
75
103
|
}) {
|
|
76
|
-
return
|
|
104
|
+
return prefix == null
|
|
77
105
|
? equationNumber(section, indexInSection)
|
|
78
|
-
: `${
|
|
106
|
+
: `${prefix}.${offset + indexInLesson}`;
|
|
79
107
|
}
|
|
80
108
|
|
|
81
109
|
/** The positional anchor id for an unkeyed equation, e.g. `2.4` → `eq-2-4`. */
|
|
@@ -100,25 +128,58 @@ export function stripFencedCode(markdown) {
|
|
|
100
128
|
* `keep`, when given, leaves a component of that name untouched instead of
|
|
101
129
|
* blanking it — `keyed-jsx-floats.mjs` uses this to blank every *other*
|
|
102
130
|
* component while still finding `<Table>`/`<Figure>` tags at the top level.
|
|
131
|
+
*
|
|
132
|
+
* `transparent`, when given, removes only those components' opening/closing
|
|
133
|
+
* tags and exposes their body to the later blanking pass. `<Memo>` and
|
|
134
|
+
* `<ShortNote>` are intentional uses: unlike a worked-example/presentation
|
|
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
|
|
137
|
+
* a transparent body do not change the surrounding page's section counter.
|
|
103
138
|
*/
|
|
104
|
-
export function stripJsxComponents(markdown, keep) {
|
|
105
|
-
|
|
139
|
+
export function stripJsxComponents(markdown, keep, transparent = []) {
|
|
140
|
+
let source = String(markdown);
|
|
141
|
+
const attributes = String.raw`(?:"[^"]*"|'[^']*'|\{(?:[^{}]|\{[^{}]*\})*\}|[^>"'])*`;
|
|
142
|
+
for (const name of transparent) {
|
|
143
|
+
const component = new RegExp(
|
|
144
|
+
`<${name}\\b${attributes}>([\\s\\S]*?)<\\/${name}\\s*>`,
|
|
145
|
+
'g',
|
|
146
|
+
);
|
|
147
|
+
source = source.replace(component, (block, body) => {
|
|
148
|
+
const bodyStart = block.indexOf(body);
|
|
149
|
+
const visibleBody = body.replace(
|
|
150
|
+
/(^|\n)([ \t]{0,3})##(?=[ \t]+\S)/g,
|
|
151
|
+
(match) => match.replace(/#/g, ' '),
|
|
152
|
+
);
|
|
153
|
+
return (
|
|
154
|
+
block.slice(0, bodyStart).replace(/[^\n]/g, ' ') +
|
|
155
|
+
visibleBody +
|
|
156
|
+
block.slice(bodyStart + body.length).replace(/[^\n]/g, ' ')
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return source.replace(
|
|
106
161
|
/<([A-Z][A-Za-z0-9]*)(?:\s[^>]*)?>[\s\S]*?<\/\1>/g,
|
|
107
162
|
(block, name) => (name === keep ? block : block.replace(/[^\n]/g, ' ')),
|
|
108
163
|
);
|
|
109
164
|
}
|
|
110
165
|
|
|
111
166
|
/**
|
|
112
|
-
* Every
|
|
113
|
-
* its assigned number. `$$…$$` blocks only. With `
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
167
|
+
* Every display equation in a Markdown body, keyed or not, in appearance order
|
|
168
|
+
* with its assigned number. `$$…$$` blocks only. With `numbering`
|
|
169
|
+
* (`{ prefix, offset }`, the part's ordinal and the count of display equations
|
|
170
|
+
* in earlier lessons of that part) the number is `‹part›.‹offset + n›`, `n`
|
|
171
|
+
* sequential over every display block in the lesson; without it, `##` headings
|
|
172
|
+
* advance a section counter and the number is `‹section›.‹indexInSection›`.
|
|
173
|
+
* Either way every display block advances the index, so a keyed equation's
|
|
174
|
+
* number reflects its position among *all* display equations, not only the
|
|
175
|
+
* keyed ones.
|
|
119
176
|
*/
|
|
120
|
-
export function
|
|
121
|
-
const
|
|
177
|
+
export function scanDisplayEquations(markdown, numbering) {
|
|
178
|
+
const { prefix, offset } = resolveNumbering(numbering);
|
|
179
|
+
const text = stripJsxComponents(stripFencedCode(markdown), undefined, [
|
|
180
|
+
'Memo',
|
|
181
|
+
'ShortNote',
|
|
182
|
+
]);
|
|
122
183
|
const tokens = [];
|
|
123
184
|
const heading = /^[ \t]{0,3}##[ \t]+\S/gm;
|
|
124
185
|
const display = /\$\$([\s\S]+?)\$\$/g;
|
|
@@ -148,11 +209,11 @@ export function scanEquationLabels(markdown, chapter) {
|
|
|
148
209
|
}
|
|
149
210
|
indexInSection += 1;
|
|
150
211
|
indexInLesson += 1;
|
|
151
|
-
if (token.key == null) continue;
|
|
152
212
|
labels.push({
|
|
153
213
|
key: token.key,
|
|
154
214
|
number: displayEquationNumber({
|
|
155
|
-
|
|
215
|
+
prefix,
|
|
216
|
+
offset,
|
|
156
217
|
section,
|
|
157
218
|
indexInSection,
|
|
158
219
|
indexInLesson,
|
|
@@ -164,30 +225,43 @@ export function scanEquationLabels(markdown, chapter) {
|
|
|
164
225
|
return labels;
|
|
165
226
|
}
|
|
166
227
|
|
|
228
|
+
/** The keyed subset of `scanDisplayEquations` — the manifest builder's view. */
|
|
229
|
+
export function scanEquationLabels(markdown, numbering) {
|
|
230
|
+
return scanDisplayEquations(markdown, numbering).filter(
|
|
231
|
+
(equation) => equation.key != null,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* How many display equations a lesson body holds. This is what makes `n` run
|
|
237
|
+
* continuously across a part: a lesson's `offset` is the sum of this over every
|
|
238
|
+
* earlier lesson in the same part.
|
|
239
|
+
*/
|
|
240
|
+
export function countDisplayEquations(markdown) {
|
|
241
|
+
return scanDisplayEquations(markdown).length;
|
|
242
|
+
}
|
|
243
|
+
|
|
167
244
|
/**
|
|
168
245
|
* The mdast equivalent of `scanEquationLabels` for *every* display equation:
|
|
169
246
|
* walk the tree in document order, advancing the section counter on every
|
|
170
247
|
* depth-2 heading and emitting a record for every `math` node. `key` is the
|
|
171
|
-
* `\label{eq:…}` payload when present, else `null`. `
|
|
248
|
+
* `\label{eq:…}` payload when present, else `null`. `numbering` is threaded the
|
|
172
249
|
* same way as in `scanEquationLabels`.
|
|
173
250
|
*/
|
|
174
|
-
export function collectDisplayEquations(tree,
|
|
251
|
+
export function collectDisplayEquations(tree, numbering) {
|
|
252
|
+
const { prefix, offset } = resolveNumbering(numbering);
|
|
175
253
|
const equations = [];
|
|
176
254
|
let section = 0;
|
|
177
255
|
let indexInSection = 0;
|
|
178
256
|
let indexInLesson = 0;
|
|
179
257
|
|
|
180
|
-
//
|
|
181
|
-
// `<CompactExample>` worked example, an `<Aside>`)
|
|
182
|
-
//
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (node
|
|
187
|
-
section += 1;
|
|
188
|
-
indexInSection = 0;
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
258
|
+
// Top-level blocks plus the bodies of `<Memo>` and `<ShortNote>`: other MDX
|
|
259
|
+
// components (a `<CompactExample>` worked example, an `<Aside>`) remain
|
|
260
|
+
// illustrative and outside the lesson's official equation sequence. These
|
|
261
|
+
// two detail components are deliberately transparent because they occupy an
|
|
262
|
+
// exact point in the reading order, even while visually collapsed.
|
|
263
|
+
const collectFromDetail = (node) => {
|
|
264
|
+
if (!node || typeof node !== 'object') return;
|
|
191
265
|
if (node.type === 'math') {
|
|
192
266
|
indexInSection += 1;
|
|
193
267
|
indexInLesson += 1;
|
|
@@ -196,7 +270,8 @@ export function collectDisplayEquations(tree, chapter) {
|
|
|
196
270
|
equations.push({
|
|
197
271
|
key: label ? label[1] : null,
|
|
198
272
|
number: displayEquationNumber({
|
|
199
|
-
|
|
273
|
+
prefix,
|
|
274
|
+
offset,
|
|
200
275
|
section,
|
|
201
276
|
indexInSection,
|
|
202
277
|
indexInLesson,
|
|
@@ -205,6 +280,33 @@ export function collectDisplayEquations(tree, chapter) {
|
|
|
205
280
|
indexInSection,
|
|
206
281
|
node,
|
|
207
282
|
});
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (
|
|
286
|
+
node.type === 'mdxJsxFlowElement' &&
|
|
287
|
+
node.name !== 'Memo' &&
|
|
288
|
+
node.name !== 'ShortNote'
|
|
289
|
+
) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (!Array.isArray(node.children)) return;
|
|
293
|
+
for (const child of node.children) collectFromDetail(child);
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const children = Array.isArray(tree?.children) ? tree.children : [];
|
|
297
|
+
for (const node of children) {
|
|
298
|
+
if (!node || typeof node !== 'object') continue;
|
|
299
|
+
if (node.type === 'heading' && node.depth === 2) {
|
|
300
|
+
section += 1;
|
|
301
|
+
indexInSection = 0;
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (node.type === 'math') collectFromDetail(node);
|
|
305
|
+
else if (
|
|
306
|
+
node.type === 'mdxJsxFlowElement' &&
|
|
307
|
+
(node.name === 'Memo' || node.name === 'ShortNote')
|
|
308
|
+
) {
|
|
309
|
+
collectFromDetail(node);
|
|
208
310
|
}
|
|
209
311
|
}
|
|
210
312
|
return equations;
|
|
@@ -214,8 +316,8 @@ export function collectDisplayEquations(tree, chapter) {
|
|
|
214
316
|
* The keyed subset of `collectDisplayEquations` — the tree-walk counterpart of
|
|
215
317
|
* `scanEquationLabels`. Numbers reflect position among all display equations.
|
|
216
318
|
*/
|
|
217
|
-
export function collectEquationLabels(tree,
|
|
218
|
-
return collectDisplayEquations(tree,
|
|
319
|
+
export function collectEquationLabels(tree, numbering) {
|
|
320
|
+
return collectDisplayEquations(tree, numbering).filter(
|
|
219
321
|
(equation) => equation.key != null,
|
|
220
322
|
);
|
|
221
323
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { MathGateDiagnostic } from './gate-math.js';
|
|
2
|
+
import { NotationRegistryInput, AbbreviationDefinitionInput } from './types.js';
|
|
3
|
+
import { CourseAbbreviations } from '../compiler/course-config.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Scan every lesson body for an abbreviation-shaped token outside `$…$` that
|
|
7
|
+
* is neither universal nor explained. Returns one `MathGateDiagnostic`
|
|
8
|
+
* (context `'abbreviation'`, always an error) per unresolved token, deduped
|
|
9
|
+
* per lesson so a term used many times on one page is reported once.
|
|
10
|
+
*/
|
|
11
|
+
declare function gateAbbreviations(input: {
|
|
12
|
+
readonly notationInput: NotationRegistryInput;
|
|
13
|
+
readonly abbreviations: readonly AbbreviationDefinitionInput[];
|
|
14
|
+
readonly courseAbbreviations?: CourseAbbreviations;
|
|
15
|
+
}): MathGateDiagnostic[];
|
|
16
|
+
|
|
17
|
+
export { gateAbbreviations };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { collectOperators, collectRomanLabels } from "./math-glyphs.mjs";
|
|
2
|
+
import { isBaseAbbreviation } from "./base-abbreviations.mjs";
|
|
3
|
+
import { pageScope } from "./gate-math.js";
|
|
4
|
+
import { lineOf, proseOnly } from "./gate-prose-notation.js";
|
|
5
|
+
const CAPS_RUN = /\b([A-Z]{2,})(?:'s|s)?\b/g;
|
|
6
|
+
const DOTTED_ABBR = /\b(?:[A-Za-z]\.){2,}/g;
|
|
7
|
+
function findAbbreviationLikeTokens(text) {
|
|
8
|
+
const out = [];
|
|
9
|
+
for (const match of text.matchAll(CAPS_RUN)) {
|
|
10
|
+
out.push({ token: match[1] ?? match[0], index: match.index ?? 0 });
|
|
11
|
+
}
|
|
12
|
+
for (const match of text.matchAll(DOTTED_ABBR)) {
|
|
13
|
+
out.push({ token: match[0], index: match.index ?? 0 });
|
|
14
|
+
}
|
|
15
|
+
return out.sort((a, b) => a.index - b.index);
|
|
16
|
+
}
|
|
17
|
+
const FIX = "Add it to an abbreviations-table entry (content/abbreviations/*.md), allowlist it in abbreviations.allow in course.config.ts if it is common, non-technical usage, or \u2014 if it names a math quantity \u2014 introduce it as \\mathrm{\u2026} (or \\operatorname{\u2026}) bound to a notation entry the reader can hover. See docs/authoring/notation.md#n14.";
|
|
18
|
+
function gateAbbreviations(input) {
|
|
19
|
+
const { notationInput, abbreviations, courseAbbreviations } = input;
|
|
20
|
+
const tableTokens = /* @__PURE__ */ new Set();
|
|
21
|
+
for (const entry of abbreviations) {
|
|
22
|
+
tableTokens.add(entry.abbr);
|
|
23
|
+
for (const alias of entry.aliases) tableTokens.add(alias);
|
|
24
|
+
}
|
|
25
|
+
const sharedByKey = new Map(
|
|
26
|
+
notationInput.sharedDefinitions.map((definition) => [
|
|
27
|
+
definition.key,
|
|
28
|
+
{ key: definition.key, notation: definition.notation }
|
|
29
|
+
])
|
|
30
|
+
);
|
|
31
|
+
const diagnostics = [];
|
|
32
|
+
for (const lesson of notationInput.lessons) {
|
|
33
|
+
const mathQuantityNames = /* @__PURE__ */ new Set();
|
|
34
|
+
for (const entry of pageScope(lesson, sharedByKey)) {
|
|
35
|
+
for (const label of collectRomanLabels(entry.notation)) {
|
|
36
|
+
mathQuantityNames.add(label.name);
|
|
37
|
+
}
|
|
38
|
+
for (const operator of collectOperators(entry.notation)) {
|
|
39
|
+
mathQuantityNames.add(operator.name);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const text = proseOnly(lesson.body);
|
|
43
|
+
const reported = /* @__PURE__ */ new Set();
|
|
44
|
+
for (const { token, index } of findAbbreviationLikeTokens(text)) {
|
|
45
|
+
if (reported.has(token)) continue;
|
|
46
|
+
if (isBaseAbbreviation(token, courseAbbreviations)) continue;
|
|
47
|
+
if (tableTokens.has(token)) continue;
|
|
48
|
+
if (mathQuantityNames.has(token)) continue;
|
|
49
|
+
reported.add(token);
|
|
50
|
+
diagnostics.push({
|
|
51
|
+
context: "abbreviation",
|
|
52
|
+
severity: "error",
|
|
53
|
+
file: lesson.source.file,
|
|
54
|
+
line: lineOf(lesson.body, index),
|
|
55
|
+
token,
|
|
56
|
+
message: `Unexplained abbreviation ${JSON.stringify(token)} outside math. ${FIX}`
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return diagnostics;
|
|
61
|
+
}
|
|
62
|
+
export {
|
|
63
|
+
gateAbbreviations
|
|
64
|
+
};
|
|
@@ -1,14 +1,25 @@
|
|
|
1
|
-
import { NotationRegistryInput } from './types.js';
|
|
1
|
+
import { NotationRegistryInput, NotationLessonInput } from './types.js';
|
|
2
2
|
|
|
3
3
|
interface MathGateDiagnostic {
|
|
4
|
-
readonly context: 'body' | 'formula' | 'assessment';
|
|
4
|
+
readonly context: 'body' | 'formula' | 'assessment' | 'prose' | 'abbreviation';
|
|
5
5
|
/**
|
|
6
6
|
* Body / component-slot math blocks the build (the render path needs every
|
|
7
7
|
* glyph resolved for the KaTeX trust callback). `formula` joined it at D15:
|
|
8
8
|
* a formula's glyphs are now rendered live and explorable, so an unresolved
|
|
9
9
|
* one is a hole the reader can see, and the corpus was brought to zero when
|
|
10
|
-
* the tier was promoted. `assessment`
|
|
11
|
-
*
|
|
10
|
+
* the tier was promoted. `assessment` joined the same way when
|
|
11
|
+
* `AssessmentRunner` started rendering `prompt` / `explanation` through
|
|
12
|
+
* KaTeX (`mixed-content.ts`) instead of showing them as raw text: an
|
|
13
|
+
* unresolved glyph is now a visible hole in the assessment UI, not an
|
|
14
|
+
* invisible warning, so the corpus was brought to zero and the tier
|
|
15
|
+
* promoted in the same change — see `CHANGELOG.md`. `prose` (N9,
|
|
16
|
+
* `gate-prose-notation.ts`) is the complementary direction — plain-text
|
|
17
|
+
* notation sitting *outside* `$…$` — and ships straight as an error: it has
|
|
18
|
+
* no partial corpus to absorb, since every prior instance was fixed in the
|
|
19
|
+
* same change that introduced the check. `abbreviation` (N14,
|
|
20
|
+
* `gate-abbreviations.ts`) is the same "ships straight as an error" story
|
|
21
|
+
* for a different shape of plain-text token — an unexplained acronym or
|
|
22
|
+
* dotted abbreviation, rather than a mis-typed quantity/unit.
|
|
12
23
|
*/
|
|
13
24
|
readonly severity: 'error' | 'warning';
|
|
14
25
|
readonly file: string;
|
|
@@ -29,6 +40,18 @@ interface LessonAssessmentLink {
|
|
|
29
40
|
readonly lessonId: string;
|
|
30
41
|
readonly assessmentIds: readonly string[];
|
|
31
42
|
}
|
|
43
|
+
type ScopeEntry = {
|
|
44
|
+
key: string;
|
|
45
|
+
notation: string;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* A lesson's flat glyph table: its own `notation.local` entries plus every
|
|
49
|
+
* shared card it introduces with `[[key]]` / `\explain{key}`. Exported so a
|
|
50
|
+
* caller that needs the *same* scope for a different purpose — rendering an
|
|
51
|
+
* assessment's math (`mixed-content.ts`), say — doesn't reconstruct it by
|
|
52
|
+
* hand and risk drifting from what the completeness gate actually checks.
|
|
53
|
+
*/
|
|
54
|
+
declare function pageScope(lesson: NotationLessonInput, sharedByKey: ReadonlyMap<string, ScopeEntry>): ScopeEntry[];
|
|
32
55
|
/**
|
|
33
56
|
* Resolve every rendered-math context against the page glyph table. Returns one
|
|
34
57
|
* diagnostic per unresolved token; an empty array means the gate passed.
|
|
@@ -39,4 +62,4 @@ declare function gateContentMath(input: {
|
|
|
39
62
|
readonly lessonAssessments: readonly LessonAssessmentLink[];
|
|
40
63
|
}): MathGateDiagnostic[];
|
|
41
64
|
|
|
42
|
-
export { type MathGateDiagnostic, gateContentMath };
|
|
65
|
+
export { type MathGateDiagnostic, type ScopeEntry, gateContentMath, pageScope };
|
|
@@ -4,7 +4,9 @@ import { formulaGlyphScope, unknownFormulaScopeKeys } from "./gloss.js";
|
|
|
4
4
|
const SEVERITY = {
|
|
5
5
|
body: "error",
|
|
6
6
|
formula: "error",
|
|
7
|
-
assessment: "
|
|
7
|
+
assessment: "error",
|
|
8
|
+
prose: "error",
|
|
9
|
+
abbreviation: "error"
|
|
8
10
|
};
|
|
9
11
|
const TERM_KEY = /\[\[\s*([a-z0-9]+(?:[.-][a-z0-9]+)*)\s*\]\]/g;
|
|
10
12
|
const EXPLAIN_KEY = /\\explain\s*\{([a-z0-9]+(?:[.-][a-z0-9]+)*)\}/g;
|
|
@@ -218,5 +220,6 @@ function gateContentMath(input) {
|
|
|
218
220
|
return diagnostics;
|
|
219
221
|
}
|
|
220
222
|
export {
|
|
221
|
-
gateContentMath
|
|
223
|
+
gateContentMath,
|
|
224
|
+
pageScope
|
|
222
225
|
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { MathGateDiagnostic } from './gate-math.js';
|
|
2
|
+
import { NotationLessonInput } from './types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The plain-text-notation gate (rule N9), run from the CLI alongside the
|
|
6
|
+
* completeness gate (`gate-math.ts`). Where that gate asks "does every glyph
|
|
7
|
+
* *inside* `$…$` resolve", this one asks the complementary question: "is
|
|
8
|
+
* anything that reads as math sitting *outside* `$…$` in the first place" —
|
|
9
|
+
* a unit abbreviation, a division of two variable-like symbols, an escaped
|
|
10
|
+
* absolute-value bar, a hand-typed sub/superscript. Both gates share one
|
|
11
|
+
* `MathGateDiagnostic` shape and one output array (`manifest.diagnostics.math`)
|
|
12
|
+
* so the CLI needs no separate wiring.
|
|
13
|
+
*
|
|
14
|
+
* Two entry points, one set of checks (`findingsIn`): `gateProseNotation`
|
|
15
|
+
* scans `lesson.body` with fenced/inline code and every `$…$` / `$$…$$` span
|
|
16
|
+
* already blanked out (same `stripCode` + math-span erasure `gate-math.ts`
|
|
17
|
+
* uses), so it only ever sees prose and table cells. `gateProseNotationInAssessments`
|
|
18
|
+
* scans an assessment item's `prompt` / `explanation` the same way — a plain
|
|
19
|
+
* JSON string field, not Markdown, but one that renders through the same
|
|
20
|
+
* KaTeX pipeline as a lesson body since `AssessmentRunner` picked it up
|
|
21
|
+
* (`mixed-content.ts`), so the same rule applies.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
interface AssessmentItemLike {
|
|
25
|
+
readonly id?: string;
|
|
26
|
+
readonly prompt?: string;
|
|
27
|
+
readonly explanation?: string;
|
|
28
|
+
}
|
|
29
|
+
interface AssessmentLike {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly items?: readonly AssessmentItemLike[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Blank code, math, reference, and JSX-tag spans so offsets don't shift.
|
|
35
|
+
* Exported so `gate-abbreviations.ts` (N14) scans exactly the same "is this
|
|
36
|
+
* really prose" surface as N9, instead of drifting with a second copy of
|
|
37
|
+
* these regexes.
|
|
38
|
+
*/
|
|
39
|
+
declare function proseOnly(body: string): string;
|
|
40
|
+
/** Exported for the same reason as {@link proseOnly}. */
|
|
41
|
+
declare function lineOf(text: string, index: number): number;
|
|
42
|
+
/**
|
|
43
|
+
* Scan every lesson body for plain-text math notation outside `$…$`. Returns
|
|
44
|
+
* one `MathGateDiagnostic` (context `'prose'`, always an error — N9 has no
|
|
45
|
+
* escape hatch) per finding.
|
|
46
|
+
*/
|
|
47
|
+
declare function gateProseNotation(lessons: readonly NotationLessonInput[]): MathGateDiagnostic[];
|
|
48
|
+
/**
|
|
49
|
+
* Scan every assessment `prompt` / `explanation` for the same plain-text
|
|
50
|
+
* notation N9 forbids in a lesson body — `AssessmentRunner` renders both
|
|
51
|
+
* through the same KaTeX pipeline now (`mixed-content.ts`), so text sitting
|
|
52
|
+
* outside `$…$` there is just as invisible to the reader's hover cards as it
|
|
53
|
+
* would be in a lesson. No line number: an assessment item is one JSON string
|
|
54
|
+
* field, not a multi-line source file, so `file` names the item instead —
|
|
55
|
+
* same `"<assessmentId> (<itemId>)"` shape `gate-math.ts` already uses for
|
|
56
|
+
* this context.
|
|
57
|
+
*/
|
|
58
|
+
declare function gateProseNotationInAssessments(assessments: readonly AssessmentLike[]): MathGateDiagnostic[];
|
|
59
|
+
|
|
60
|
+
export { gateProseNotation, gateProseNotationInAssessments, lineOf, proseOnly };
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
const CODE_FENCE = /```[\s\S]*?```/g;
|
|
2
|
+
const INLINE_CODE = /(^|[^`])`[^`\n]*`/g;
|
|
3
|
+
const DISPLAY_MATH = /\$\$([\s\S]+?)\$\$/g;
|
|
4
|
+
const INLINE_MATH = /(?<![\\$])\$(?!\s)([^$\n]+?)(?<![\\\s])\$(?!\d)/g;
|
|
5
|
+
const IMPORT_LINE = /^[ \t]*import\b[^\n]*$/gm;
|
|
6
|
+
const KEY_REFERENCE = /\[\[[^\]\n]*\]\]/g;
|
|
7
|
+
const JSX_TAG = /<\/?[A-Za-z][^<>]*>/g;
|
|
8
|
+
function proseOnly(body) {
|
|
9
|
+
const blank = (match) => match.replace(/[^\n]/g, " ");
|
|
10
|
+
return body.replace(CODE_FENCE, blank).replace(INLINE_CODE, (match, lead) => lead + blank(match.slice(lead.length))).replace(DISPLAY_MATH, blank).replace(INLINE_MATH, blank).replace(IMPORT_LINE, blank).replace(KEY_REFERENCE, blank).replace(JSX_TAG, blank);
|
|
11
|
+
}
|
|
12
|
+
function lineOf(text, index) {
|
|
13
|
+
return text.slice(0, index).split("\n").length;
|
|
14
|
+
}
|
|
15
|
+
const UNIT = String.raw`m/s²|m/s|km/h|rad/s|m²/s²|J/kg|m²|s⁻¹|kg|N|J|W|Hz|deg|°|rad|m|s`;
|
|
16
|
+
const NUMERAL_UNIT = new RegExp(
|
|
17
|
+
String.raw`(?<![\w.])-?\d+(?:\.\d+)?\s?(?:${UNIT})(?![A-Za-z])`,
|
|
18
|
+
"g"
|
|
19
|
+
);
|
|
20
|
+
const BARE_UNIT_PARENS = new RegExp(String.raw`\((?:${UNIT})\)`, "g");
|
|
21
|
+
const SLASH_IDIOMS = /* @__PURE__ */ new Set([
|
|
22
|
+
"and/or",
|
|
23
|
+
"his/her",
|
|
24
|
+
"him/her",
|
|
25
|
+
"he/she",
|
|
26
|
+
"she/he",
|
|
27
|
+
"on/off",
|
|
28
|
+
"yes/no",
|
|
29
|
+
"pass/fail",
|
|
30
|
+
"input/output",
|
|
31
|
+
"output/input",
|
|
32
|
+
"read/write",
|
|
33
|
+
"either/or",
|
|
34
|
+
"client/server",
|
|
35
|
+
"pre/post",
|
|
36
|
+
"a/b"
|
|
37
|
+
]);
|
|
38
|
+
const SLASH_TOKENS = /\b([A-Za-z]{1,6})\/([A-Za-z]{1,6})\b/g;
|
|
39
|
+
const FAKE_SCRIPT = /[A-Za-z0-9][_^][A-Za-z0-9{]/g;
|
|
40
|
+
function findAll(text, pattern, toFinding) {
|
|
41
|
+
const out = [];
|
|
42
|
+
const re = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
|
|
43
|
+
let match;
|
|
44
|
+
while (match = re.exec(text)) {
|
|
45
|
+
out.push(toFinding(match));
|
|
46
|
+
if (match[0].length === 0) re.lastIndex++;
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
const FIX = "Wrap it in $\u2026$ (or $$\u2026$$) instead \u2014 see docs/authoring/notation.md#n9.";
|
|
51
|
+
function findingsIn(text) {
|
|
52
|
+
return [
|
|
53
|
+
...findAll(text, /\\\|/g, (m) => ({
|
|
54
|
+
index: m.index,
|
|
55
|
+
token: m[0],
|
|
56
|
+
message: `Escaped pipe ${JSON.stringify(m[0])} outside math. Use \\lvert\u2026\\rvert for an absolute value or norm inside $\u2026$, not an escaped table pipe. ${FIX}`
|
|
57
|
+
})),
|
|
58
|
+
...findAll(text, NUMERAL_UNIT, (m) => ({
|
|
59
|
+
index: m.index,
|
|
60
|
+
token: m[0].trim(),
|
|
61
|
+
message: `Numeral with a plain-text unit ${JSON.stringify(m[0].trim())} outside math. ${FIX}`
|
|
62
|
+
})),
|
|
63
|
+
...findAll(text, BARE_UNIT_PARENS, (m) => ({
|
|
64
|
+
index: m.index,
|
|
65
|
+
token: m[0],
|
|
66
|
+
message: `Plain-text unit ${JSON.stringify(m[0])} outside math. ${FIX}`
|
|
67
|
+
})),
|
|
68
|
+
...findAll(text, FAKE_SCRIPT, (m) => ({
|
|
69
|
+
index: m.index,
|
|
70
|
+
token: m[0],
|
|
71
|
+
message: `${JSON.stringify(m[0])} looks like a hand-typed sub/superscript outside math. ${FIX}`
|
|
72
|
+
})),
|
|
73
|
+
...findAll(text, SLASH_TOKENS, (m) => {
|
|
74
|
+
if (SLASH_IDIOMS.has(m[0].toLowerCase())) return null;
|
|
75
|
+
return {
|
|
76
|
+
index: m.index,
|
|
77
|
+
token: m[0],
|
|
78
|
+
message: `${JSON.stringify(m[0])} outside math reads as a division of two symbols/units, not English prose. If it really is prose, add the idiom to SLASH_IDIOMS in gate-prose-notation.ts (reviewed like any other rule change). ${FIX}`
|
|
79
|
+
};
|
|
80
|
+
}).filter(Boolean)
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
function gateProseNotation(lessons) {
|
|
84
|
+
const diagnostics = [];
|
|
85
|
+
for (const lesson of lessons) {
|
|
86
|
+
const text = proseOnly(lesson.body);
|
|
87
|
+
for (const finding of findingsIn(text)) {
|
|
88
|
+
diagnostics.push({
|
|
89
|
+
context: "prose",
|
|
90
|
+
severity: "error",
|
|
91
|
+
file: lesson.source.file,
|
|
92
|
+
line: lineOf(lesson.body, finding.index),
|
|
93
|
+
token: finding.token,
|
|
94
|
+
message: finding.message
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return diagnostics;
|
|
99
|
+
}
|
|
100
|
+
function gateProseNotationInAssessments(assessments) {
|
|
101
|
+
const diagnostics = [];
|
|
102
|
+
for (const assessment of assessments) {
|
|
103
|
+
for (const item of assessment.items ?? []) {
|
|
104
|
+
for (const field of [item.prompt, item.explanation]) {
|
|
105
|
+
if (typeof field !== "string") continue;
|
|
106
|
+
for (const finding of findingsIn(proseOnly(field))) {
|
|
107
|
+
diagnostics.push({
|
|
108
|
+
context: "prose",
|
|
109
|
+
severity: "error",
|
|
110
|
+
file: `${assessment.id} (${item.id ?? "?"})`,
|
|
111
|
+
token: finding.token,
|
|
112
|
+
message: finding.message
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return diagnostics;
|
|
119
|
+
}
|
|
120
|
+
export {
|
|
121
|
+
gateProseNotation,
|
|
122
|
+
gateProseNotationInAssessments,
|
|
123
|
+
lineOf,
|
|
124
|
+
proseOnly
|
|
125
|
+
};
|
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
* - `<prefix>-invalid-key` (error): an `id` must match the key grammar.
|
|
7
7
|
* - `<prefix>-duplicate-key` (error): an `id` is unique per lesson.
|
|
8
8
|
* - `<prefix>-ref-resolves` (error): every `[[<prefix>-key]]` /
|
|
9
|
-
* `[[slug#<prefix>-key]]` points at
|
|
9
|
+
* `[[slug#<prefix>-key]]` points at the matching keyed float with that `id`.
|
|
10
10
|
* - `<prefix>-key-unused` (warning): a keyed float never referenced.
|
|
11
11
|
*
|
|
12
|
-
* `tables-validate.ts` and `
|
|
13
|
-
* so callers import
|
|
14
|
-
* narrow diagnostic-code types, while `equations-validate.ts` stays
|
|
15
|
-
*
|
|
12
|
+
* `tables-validate.ts`, `figures-validate.ts`, and `diagrams-validate.ts` are
|
|
13
|
+
* thin wrappers over this so callers import a kind-specific validator with
|
|
14
|
+
* narrow diagnostic-code types, while `equations-validate.ts` stays untouched
|
|
15
|
+
* — equations predate this and keep their own dedicated path.
|
|
16
16
|
*/
|
|
17
17
|
interface KeyedFloatKind {
|
|
18
18
|
/** The MDX component tag, e.g. `Table` or `Figure`. */
|
|
@@ -57,10 +57,16 @@ interface KeyedFloatLessonInput {
|
|
|
57
57
|
readonly file: string;
|
|
58
58
|
readonly body: string;
|
|
59
59
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
60
|
+
* `{ prefix, offset }` — the part's 1-based ordinal among the sidebar groups,
|
|
61
|
+
* and how many floats of this kind earlier lessons of that part already used.
|
|
62
|
+
* When set, floats are numbered `‹part›.‹offset + n›`; when omitted, the
|
|
63
|
+
* legacy `##`-section scheme. Each kind counts on its own, so the caller
|
|
64
|
+
* passes a different `offset` for `<Table>` than for `<Figure>`.
|
|
62
65
|
*/
|
|
63
|
-
readonly
|
|
66
|
+
readonly numbering?: {
|
|
67
|
+
readonly prefix: number;
|
|
68
|
+
readonly offset: number;
|
|
69
|
+
};
|
|
64
70
|
}
|
|
65
71
|
declare function validateKeyedFloats(lessons: readonly KeyedFloatLessonInput[], kind: KeyedFloatKind): KeyedFloatValidation;
|
|
66
72
|
|
|
@@ -32,7 +32,7 @@ function validateKeyedFloats(lessons, kind) {
|
|
|
32
32
|
const table = {};
|
|
33
33
|
for (const raw of scanKeyedJsxFloats(lesson.body, {
|
|
34
34
|
tagName,
|
|
35
|
-
|
|
35
|
+
numbering: lesson.numbering
|
|
36
36
|
})) {
|
|
37
37
|
if (raw.key == null) continue;
|
|
38
38
|
if (!KEYED_FLOAT_KEY_PATTERN.test(raw.key)) {
|