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
|
@@ -33,7 +33,11 @@
|
|
|
33
33
|
|
|
34
34
|
export const NOTATION_KEY_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
|
|
35
35
|
|
|
36
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
GlyphResolutionError,
|
|
38
|
+
resolveMathGlyphs,
|
|
39
|
+
stripNotationMarkers,
|
|
40
|
+
} from './math-glyphs.mjs';
|
|
37
41
|
import {
|
|
38
42
|
EQ_LABEL_PATTERN,
|
|
39
43
|
collectDisplayEquations,
|
|
@@ -47,6 +51,12 @@ import {
|
|
|
47
51
|
keyedFloatAnchorId,
|
|
48
52
|
parseKeyedFloatRef,
|
|
49
53
|
} from './keyed-jsx-floats.mjs';
|
|
54
|
+
import { collectRawFloats } from './raw-floats.mjs';
|
|
55
|
+
import {
|
|
56
|
+
knowledgeCheckAnchorId,
|
|
57
|
+
parseKnowledgeCheckRef,
|
|
58
|
+
} from './knowledge-checks.mjs';
|
|
59
|
+
import { injectMdxStringAttributes } from './mdx-attributes.mjs';
|
|
50
60
|
|
|
51
61
|
const PROSE_SKIP_TYPES = new Set([
|
|
52
62
|
'code',
|
|
@@ -313,13 +323,58 @@ function defaultHref(definition, key, base = '') {
|
|
|
313
323
|
return `${base.replace(/\/$/, '')}/glossary/#notation-${key}`;
|
|
314
324
|
}
|
|
315
325
|
|
|
326
|
+
/** A non-mdast hast element (a `<div>`, `<button>`, `<svg>`, …) built through
|
|
327
|
+
* the same `data.hName`/`hProperties` override `equationNode` already uses
|
|
328
|
+
* for its own wrapper — mdast-to-hast renders exactly what these say
|
|
329
|
+
* regardless of the carrier node's own `type`. */
|
|
330
|
+
function el(hName, hProperties, children = []) {
|
|
331
|
+
return { type: 'paragraph', data: { hName, hProperties }, children };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** Feather/Lucide's "copy" glyph (two overlapping rounded rects), matching
|
|
335
|
+
* the stroke-icon-button style `LessonExportActions.astro` already uses for
|
|
336
|
+
* lesson-level actions. */
|
|
337
|
+
function copyIconNode() {
|
|
338
|
+
return el('svg', { viewBox: '0 0 24 24', 'aria-hidden': 'true' }, [
|
|
339
|
+
el('rect', { x: '9', y: '9', width: '10', height: '10', rx: '1' }),
|
|
340
|
+
el('path', {
|
|
341
|
+
d: 'M6 15H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v1',
|
|
342
|
+
}),
|
|
343
|
+
]);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Wrap an inline `$…$` `math` node in an inert `<span data-latex>` so a
|
|
348
|
+
* reader can trigger the same "click an unexplained part to copy" gesture
|
|
349
|
+
* a display equation gets (`KeyedFloatEnhancer.astro`), without a
|
|
350
|
+
* `.keyed-equation` block's room for a dedicated button — a persistent
|
|
351
|
+
* button here would break the reading line (see `global.css`, which gives
|
|
352
|
+
* this wrapper no visible styling of its own). `mathNode.value` is read
|
|
353
|
+
* after the resolver above has already bound the page's glyphs into it, same
|
|
354
|
+
* as `equationNode` below.
|
|
355
|
+
*/
|
|
356
|
+
function inlineEquationNode(mathNode) {
|
|
357
|
+
const latex = stripNotationMarkers(String(mathNode.value ?? ''));
|
|
358
|
+
return el('span', { className: ['inline-equation'], 'data-latex': latex }, [
|
|
359
|
+
mathNode,
|
|
360
|
+
]);
|
|
361
|
+
}
|
|
362
|
+
|
|
316
363
|
/**
|
|
317
364
|
* Wrap a display `$$…$$` `math` node so the container survives `rehype-katex`
|
|
318
365
|
* (which splices the inner `math-display` element out). Every display equation
|
|
319
366
|
* in a lesson is numbered; a keyed one anchors at `#eq-<key>`, an unkeyed one
|
|
320
367
|
* at its positional `#eq-<section>-<index>`. The visible number is a focusable
|
|
321
368
|
* `<a>` sibling — never authored, never inside KaTeX — so it survives print and
|
|
322
|
-
* screen readers name it "Equation 2.4".
|
|
369
|
+
* screen readers name it "Equation 2.4". A "Copy LaTeX" button sits beside it
|
|
370
|
+
* (`.keyed-equation__controls`, styled in `global.css`) — the equation's own
|
|
371
|
+
* copy affordance, distinct from the notation popup card's (`NotationLayer.astro`),
|
|
372
|
+
* which only exists for a symbol with a registry entry, not a bare display
|
|
373
|
+
* equation. `mathNode.value` is read here — after the resolver above has
|
|
374
|
+
* already bound the page's glyphs into it (`replaceMathSource`) — so an
|
|
375
|
+
* auto-bound symbol's `\explain{…}` marker gets stripped same as an
|
|
376
|
+
* author-written one; either way the reader copies plain LaTeX, never this
|
|
377
|
+
* system's own notation-key plumbing (`math-glyphs.mjs:stripNotationMarkers`).
|
|
323
378
|
*/
|
|
324
379
|
function equationNode(mathNode, { key, number }) {
|
|
325
380
|
const id = equationAnchorId({ key, number });
|
|
@@ -331,31 +386,47 @@ function equationNode(mathNode, { key, number }) {
|
|
|
331
386
|
if (key) hProperties['data-eq-key'] = key;
|
|
332
387
|
else hProperties['data-eq-auto'] = '';
|
|
333
388
|
|
|
389
|
+
const latex = stripNotationMarkers(String(mathNode.value ?? ''));
|
|
390
|
+
|
|
334
391
|
return {
|
|
335
392
|
type: 'paragraph',
|
|
336
393
|
data: { hName: 'div', hProperties },
|
|
337
394
|
children: [
|
|
338
395
|
mathNode,
|
|
339
|
-
{
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
396
|
+
el('div', { className: ['keyed-equation__controls'] }, [
|
|
397
|
+
{
|
|
398
|
+
type: 'link',
|
|
399
|
+
url: `#${id}`,
|
|
400
|
+
children: [{ type: 'text', value: `(${number})` }],
|
|
401
|
+
data: {
|
|
402
|
+
hProperties: {
|
|
403
|
+
className: ['keyed-equation__number'],
|
|
404
|
+
'data-eq-number': number,
|
|
405
|
+
'aria-label': `Equation ${number}`,
|
|
406
|
+
// Plain hover tooltip — the number is a permalink to this equation.
|
|
407
|
+
title: 'Anchor to this equation',
|
|
408
|
+
},
|
|
350
409
|
},
|
|
351
410
|
},
|
|
352
|
-
|
|
411
|
+
el(
|
|
412
|
+
'button',
|
|
413
|
+
{
|
|
414
|
+
type: 'button',
|
|
415
|
+
className: ['keyed-equation__copy'],
|
|
416
|
+
'data-eq-copy': '',
|
|
417
|
+
'data-latex': latex,
|
|
418
|
+
'aria-label': `Copy LaTeX for Equation ${number}`,
|
|
419
|
+
title: 'Copy LaTeX',
|
|
420
|
+
},
|
|
421
|
+
[copyIconNode()],
|
|
422
|
+
),
|
|
423
|
+
]),
|
|
353
424
|
],
|
|
354
425
|
};
|
|
355
426
|
}
|
|
356
427
|
|
|
357
428
|
/**
|
|
358
|
-
* Give a `<Table>`/`<Figure>` `mdxJsxFlowElement` its computed `number` and
|
|
429
|
+
* Give a `<Table>`/`<Figure>`/`<Diagram>` `mdxJsxFlowElement` its computed `number` and
|
|
359
430
|
* `anchorId` as real JSX attributes, so the actual Astro component (which
|
|
360
431
|
* renders the numbered anchor, unlike `equationNode`'s synthesized hast div)
|
|
361
432
|
* receives them as props. The compiler is the single source of truth for
|
|
@@ -367,17 +438,10 @@ function injectFloatNumberAttributes(node, float, prefix) {
|
|
|
367
438
|
number: float.number,
|
|
368
439
|
prefix,
|
|
369
440
|
});
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
(attribute.name === 'number' || attribute.name === 'anchorId')
|
|
375
|
-
),
|
|
376
|
-
);
|
|
377
|
-
node.attributes.push(
|
|
378
|
-
{ type: 'mdxJsxAttribute', name: 'number', value: float.number },
|
|
379
|
-
{ type: 'mdxJsxAttribute', name: 'anchorId', value: anchorId },
|
|
380
|
-
);
|
|
441
|
+
injectMdxStringAttributes(node, {
|
|
442
|
+
number: float.number,
|
|
443
|
+
anchorId,
|
|
444
|
+
});
|
|
381
445
|
}
|
|
382
446
|
|
|
383
447
|
/** Resolve a `[[eq-key]]` / `[[slug#eq-key]]` reference to `{ number, href }`. */
|
|
@@ -409,7 +473,7 @@ function resolveEquationRef(eqRef, eqCtx, file, node) {
|
|
|
409
473
|
|
|
410
474
|
/**
|
|
411
475
|
* Resolve a `[[<prefix>-key]]` / `[[slug#<prefix>-key]]` reference (a
|
|
412
|
-
* `<Table>`/`<Figure>` float) to `{ number, href }` — the
|
|
476
|
+
* `<Table>`/`<Figure>`/`<Diagram>` float) to `{ number, href }` — the keyed-float
|
|
413
477
|
* equivalent of `resolveEquationRef`.
|
|
414
478
|
*/
|
|
415
479
|
function resolveKeyedFloatRef(floatRef, ctx, { prefix, tagName }, file, node) {
|
|
@@ -438,6 +502,33 @@ function resolveKeyedFloatRef(floatRef, ctx, { prefix, tagName }, file, node) {
|
|
|
438
502
|
return { number, href: `#${prefix}-${floatRef.key}` };
|
|
439
503
|
}
|
|
440
504
|
|
|
505
|
+
/** Resolve a numbered knowledge check declared by a lesson's `.checks.yml`. */
|
|
506
|
+
function resolveKnowledgeCheckRef(checkRef, ctx, file, node) {
|
|
507
|
+
if (checkRef.lesson) {
|
|
508
|
+
const number = ctx.crossPage?.[checkRef.lesson]?.[checkRef.key];
|
|
509
|
+
if (number === undefined) {
|
|
510
|
+
fail(
|
|
511
|
+
file,
|
|
512
|
+
`Unknown knowledge-check reference [[${checkRef.lesson}#check-${checkRef.key}]]: that lesson's .checks.yml does not list "${checkRef.key}".`,
|
|
513
|
+
node,
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
return {
|
|
517
|
+
number,
|
|
518
|
+
href: `${ctx.base.replace(/\/$/, '')}/${checkRef.lesson}/#${knowledgeCheckAnchorId(checkRef.key)}`,
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
const number = ctx.pageLabels.get(checkRef.key);
|
|
522
|
+
if (number === undefined) {
|
|
523
|
+
fail(
|
|
524
|
+
file,
|
|
525
|
+
`Unknown knowledge-check reference [[check-${checkRef.key}]]: this lesson's .checks.yml does not list "${checkRef.key}".`,
|
|
526
|
+
node,
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
return { number, href: `#${knowledgeCheckAnchorId(checkRef.key)}` };
|
|
530
|
+
}
|
|
531
|
+
|
|
441
532
|
function proseNodes(
|
|
442
533
|
value,
|
|
443
534
|
resolve,
|
|
@@ -447,6 +538,8 @@ function proseNodes(
|
|
|
447
538
|
eqCtx,
|
|
448
539
|
tableCtx,
|
|
449
540
|
figureCtx,
|
|
541
|
+
diagramCtx,
|
|
542
|
+
checkCtx,
|
|
450
543
|
) {
|
|
451
544
|
const base = eqCtx?.base ?? '';
|
|
452
545
|
const strayExplain = firstUnescaped(value, EXPLAIN);
|
|
@@ -474,10 +567,20 @@ function proseNodes(
|
|
|
474
567
|
!eqRef && !tableRef && figureCtx
|
|
475
568
|
? parseKeyedFloatRef(key, { prefix: 'fig' })
|
|
476
569
|
: undefined;
|
|
570
|
+
const diagramRef =
|
|
571
|
+
!eqRef && !tableRef && !figureRef && diagramCtx
|
|
572
|
+
? parseKeyedFloatRef(key, { prefix: 'dia' })
|
|
573
|
+
: undefined;
|
|
574
|
+
const checkRef =
|
|
575
|
+
!eqRef && !tableRef && !figureRef && !diagramRef && checkCtx
|
|
576
|
+
? parseKnowledgeCheckRef(key)
|
|
577
|
+
: undefined;
|
|
477
578
|
if (
|
|
478
579
|
!eqRef &&
|
|
479
580
|
!tableRef &&
|
|
480
581
|
!figureRef &&
|
|
582
|
+
!diagramRef &&
|
|
583
|
+
!checkRef &&
|
|
481
584
|
!NOTATION_KEY_PATTERN.test(key)
|
|
482
585
|
) {
|
|
483
586
|
fail(file, `Invalid notation key "${key}" in [[...]].`, node);
|
|
@@ -510,14 +613,44 @@ function proseNodes(
|
|
|
510
613
|
continue;
|
|
511
614
|
}
|
|
512
615
|
|
|
513
|
-
|
|
616
|
+
if (checkRef) {
|
|
617
|
+
const { number, href } = resolveKnowledgeCheckRef(
|
|
618
|
+
checkRef,
|
|
619
|
+
checkCtx,
|
|
620
|
+
file,
|
|
621
|
+
node,
|
|
622
|
+
);
|
|
623
|
+
output.push({
|
|
624
|
+
type: 'link',
|
|
625
|
+
url: href,
|
|
626
|
+
children: [{ type: 'text', value: `Knowledge check ${number}` }],
|
|
627
|
+
data: {
|
|
628
|
+
hProperties: {
|
|
629
|
+
className: ['knowledge-check-ref'],
|
|
630
|
+
'data-check-ref': checkRef.key,
|
|
631
|
+
...(checkRef.lesson
|
|
632
|
+
? { 'data-check-lesson': checkRef.lesson }
|
|
633
|
+
: {}),
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
});
|
|
637
|
+
references.push({
|
|
638
|
+
key: checkRef.key,
|
|
639
|
+
kind: 'knowledge-check',
|
|
640
|
+
...(checkRef.lesson ? { lesson: checkRef.lesson } : {}),
|
|
641
|
+
});
|
|
642
|
+
cursor = match.index + match[0].length;
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const floatRef = tableRef ?? figureRef ?? diagramRef;
|
|
514
647
|
if (floatRef) {
|
|
515
|
-
const kind = tableRef ? 'table' : 'figure';
|
|
516
|
-
const prefix = tableRef ? 'tab' : 'fig';
|
|
517
|
-
const tagName = tableRef ? 'Table' : 'Figure';
|
|
648
|
+
const kind = tableRef ? 'table' : figureRef ? 'figure' : 'diagram';
|
|
649
|
+
const prefix = tableRef ? 'tab' : figureRef ? 'fig' : 'dia';
|
|
650
|
+
const tagName = tableRef ? 'Table' : figureRef ? 'Figure' : 'Diagram';
|
|
518
651
|
const { number, href } = resolveKeyedFloatRef(
|
|
519
652
|
floatRef,
|
|
520
|
-
tableRef ? tableCtx : figureCtx,
|
|
653
|
+
tableRef ? tableCtx : figureRef ? figureCtx : diagramCtx,
|
|
521
654
|
{ prefix, tagName },
|
|
522
655
|
file,
|
|
523
656
|
node,
|
|
@@ -671,26 +804,231 @@ function inspectExplainCalls(value, resolve, file, node, references) {
|
|
|
671
804
|
}
|
|
672
805
|
}
|
|
673
806
|
|
|
807
|
+
/**
|
|
808
|
+
* The numbering inputs a lesson page needs, read off one manifest.
|
|
809
|
+
*
|
|
810
|
+
* Every number on a page comes from two places that MUST agree: the page's own
|
|
811
|
+
* counters (`numbering`) and the manifest tables a cross-page `[[slug#eq-key]]`
|
|
812
|
+
* is resolved against (`equations` / `tables` / `figures` / `diagrams` /
|
|
813
|
+
* `checks`).
|
|
814
|
+
* Deriving every input from the same manifest in one step stops a course config from
|
|
815
|
+
* supplying one without the other — the omission that produced page-local
|
|
816
|
+
* `(2.2)` and cross-page `(4.5)` for the same equation.
|
|
817
|
+
*
|
|
818
|
+
* `numbering` maps a lesson slug to
|
|
819
|
+
* `{ part, equations, tables, figures, diagrams, checks }`: the part's ordinal
|
|
820
|
+
* (the number before the dot) and, per numbered kind, how many earlier lessons
|
|
821
|
+
* of that part already used.
|
|
822
|
+
*/
|
|
823
|
+
export function numberingFromManifest(manifest) {
|
|
824
|
+
if (!manifest || typeof manifest !== 'object') return {};
|
|
825
|
+
return {
|
|
826
|
+
equations: manifest.equations?.numbersBySlug ?? {},
|
|
827
|
+
tables: manifest.tables?.numbersBySlug ?? {},
|
|
828
|
+
figures: manifest.figures?.numbersBySlug ?? {},
|
|
829
|
+
diagrams: manifest.diagrams?.numbersBySlug ?? {},
|
|
830
|
+
checks: manifest.checks?.numbersBySlug ?? {},
|
|
831
|
+
numbering: Object.fromEntries(
|
|
832
|
+
(manifest.lessons ?? []).map((lesson) => [
|
|
833
|
+
lesson.slug,
|
|
834
|
+
{
|
|
835
|
+
part: lesson.part,
|
|
836
|
+
equations: lesson.numbering?.equations ?? 0,
|
|
837
|
+
tables: lesson.numbering?.tables ?? 0,
|
|
838
|
+
figures: lesson.numbering?.figures ?? 0,
|
|
839
|
+
diagrams: lesson.numbering?.diagrams ?? 0,
|
|
840
|
+
checks: lesson.numbering?.checks ?? 0,
|
|
841
|
+
},
|
|
842
|
+
]),
|
|
843
|
+
),
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* The options object `remarkNotation` needs for one manifest, in one call.
|
|
849
|
+
*
|
|
850
|
+
* A consumer used to hand-assemble `definitions`, `equations`, `tables`,
|
|
851
|
+
* `figures`, and `chapters` separately — easy to do partially, which is how a
|
|
852
|
+
* page ends up numbered under the legacy `##`-section scheme while the
|
|
853
|
+
* manifest (and every cross-page reference to that page) uses the part
|
|
854
|
+
* scheme. This does not return that literal four-map-plus-chapters shape:
|
|
855
|
+
* `chapters` is the superseded per-lesson form (see `pageNumbering` below),
|
|
856
|
+
* and passing `manifest` through already makes `numberingFromManifest()`
|
|
857
|
+
* derive `numbering` plus every `equations`/`tables`/`figures`/`diagrams`/
|
|
858
|
+
* `checks` map from the same source in one step — strictly more correct than
|
|
859
|
+
* reconstructing `chapters` by hand.
|
|
860
|
+
*
|
|
861
|
+
* `overrides` is spread last, so a caller can add `strict`, `unknown`,
|
|
862
|
+
* `resolve`, `base`, or override anything above:
|
|
863
|
+
*
|
|
864
|
+
* [remarkNotation, createRemarkNotationOptions(manifest, { strict: true })]
|
|
865
|
+
*
|
|
866
|
+
* @param {object} manifest - a compiled Explico manifest (`compileManifest()`).
|
|
867
|
+
* @param {object} [overrides] - additional/overriding `remarkNotation` options.
|
|
868
|
+
*/
|
|
869
|
+
export function createRemarkNotationOptions(manifest, overrides = {}) {
|
|
870
|
+
return {
|
|
871
|
+
definitions: manifest?.notation?.raw ?? [],
|
|
872
|
+
manifest,
|
|
873
|
+
...overrides,
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* `{ prefix, offset }` for one kind of float on one page, from whichever
|
|
879
|
+
* numbering option the caller supplied. `numbering` is the current form;
|
|
880
|
+
* `chapters` is the superseded per-lesson form (prefix = the lesson's ordinal,
|
|
881
|
+
* no offset) and is still read so an unmigrated config keeps working.
|
|
882
|
+
*/
|
|
883
|
+
function pageNumbering(options, slug, kind) {
|
|
884
|
+
if (!slug) return undefined;
|
|
885
|
+
const placement = options.numbering?.[slug];
|
|
886
|
+
if (placement?.part) {
|
|
887
|
+
return { prefix: placement.part, offset: placement[kind] ?? 0 };
|
|
888
|
+
}
|
|
889
|
+
const chapter = options.chapters?.[slug];
|
|
890
|
+
return chapter ? { prefix: chapter, offset: 0 } : undefined;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* Fail when the number this page just computed for a keyed float disagrees
|
|
895
|
+
* with the number the manifest publishes for the same key (WERN — a reference
|
|
896
|
+
* that shows a different number than its target). The two are produced by the
|
|
897
|
+
* same algorithm from the same content, so any disagreement means the render
|
|
898
|
+
* path and the manifest were fed different inputs; in practice, an
|
|
899
|
+
* `astro.config.mjs` that passes the manifest number tables but not
|
|
900
|
+
* `numbering`, which silently drops the page back to the legacy `##`-section
|
|
901
|
+
* scheme.
|
|
902
|
+
*/
|
|
903
|
+
function assertManifestNumberParity({
|
|
904
|
+
file,
|
|
905
|
+
slug,
|
|
906
|
+
pageLabels,
|
|
907
|
+
crossPage,
|
|
908
|
+
placement,
|
|
909
|
+
noun,
|
|
910
|
+
syntax,
|
|
911
|
+
}) {
|
|
912
|
+
// No entry for this slug at all means the caller supplied no manifest table
|
|
913
|
+
// (hand-built test trees, non-lesson pages) — nothing to cross-check.
|
|
914
|
+
const published = crossPage?.[slug];
|
|
915
|
+
if (!published) return;
|
|
916
|
+
|
|
917
|
+
for (const [key, number] of pageLabels) {
|
|
918
|
+
const expected = published[key];
|
|
919
|
+
if (expected === undefined) {
|
|
920
|
+
fail(
|
|
921
|
+
file,
|
|
922
|
+
`Stale Explico manifest for ${slug}: this page numbers ${noun} ` +
|
|
923
|
+
`${syntax(key)} as (${number}), but the manifest lists no such ` +
|
|
924
|
+
`${noun} for the lesson. Recompile the manifest before rendering.`,
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
if (expected !== number) {
|
|
928
|
+
fail(
|
|
929
|
+
file,
|
|
930
|
+
`Inconsistent ${noun} numbering for ${syntax(key)} in ${slug}: this ` +
|
|
931
|
+
`page renders it as (${number}) while a cross-page reference to it ` +
|
|
932
|
+
`resolves to (${expected}). ` +
|
|
933
|
+
(placement === undefined
|
|
934
|
+
? 'The remarkNotation options carry the manifest number tables ' +
|
|
935
|
+
'but no `numbering` entry for this lesson, so the page fell ' +
|
|
936
|
+
'back to the legacy `##`-section scheme. Pass `manifest` to ' +
|
|
937
|
+
'remarkNotation so both sides use the part scheme.'
|
|
938
|
+
: 'The page counters and the manifest disagree; recompile the ' +
|
|
939
|
+
'manifest from the current content.'),
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/**
|
|
946
|
+
* Strict-mode guard: a catalogued lesson (one `remarkNotation` is told the
|
|
947
|
+
* slug of) that contains at least one equation/`<Table>`/`<Figure>` — keyed or
|
|
948
|
+
* not — must have both a `numbering` placement and a cross-page number table
|
|
949
|
+
* entry. Without this, `assertManifestNumberParity` above only ever catches a
|
|
950
|
+
* missing/wrong map for *keyed* items (it walks `pageLabels`, which unkeyed
|
|
951
|
+
* equations/floats never enter — see `keyed-jsx-floats.mjs`), so a lesson
|
|
952
|
+
* whose only floats are unkeyed can silently fall back to `##`-section
|
|
953
|
+
* numbering with no diagnostic at all. `Diagram` is deliberately excluded by
|
|
954
|
+
* every caller: `Diagram.astro` already tolerates an unplaced diagram
|
|
955
|
+
* rendering without a number, by design.
|
|
956
|
+
*/
|
|
957
|
+
function assertStrictNumbering({ file, slug, noun, count, placement, crossPage }) {
|
|
958
|
+
if (!slug || count === 0) return;
|
|
959
|
+
const plural = count === 1 ? '' : 's';
|
|
960
|
+
if (placement === undefined) {
|
|
961
|
+
fail(
|
|
962
|
+
file,
|
|
963
|
+
`Strict mode: ${slug} has ${count} ${noun}${plural} but remarkNotation ` +
|
|
964
|
+
`has no \`numbering\` entry for this lesson, so it would silently ` +
|
|
965
|
+
`fall back to the legacy ##-section numbering. Pass \`manifest\` ` +
|
|
966
|
+
`(e.g. via createRemarkNotationOptions) or add a \`numbering\` entry ` +
|
|
967
|
+
`for "${slug}".`,
|
|
968
|
+
);
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
if (crossPage?.[slug] === undefined) {
|
|
972
|
+
fail(
|
|
973
|
+
file,
|
|
974
|
+
`Strict mode: ${slug} has ${count} ${noun}${plural} but remarkNotation ` +
|
|
975
|
+
`has no cross-page ${noun} number table entry for this lesson. Pass ` +
|
|
976
|
+
`\`manifest\` (e.g. via createRemarkNotationOptions) so every ` +
|
|
977
|
+
`catalogued lesson is covered.`,
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
674
982
|
/**
|
|
675
983
|
* @param {{
|
|
676
984
|
* definitions?: Map<string, object> | readonly object[] | Record<string, object> | (() => Map<string, object> | readonly object[] | Record<string, object>),
|
|
677
985
|
* resolve?: (key: string, context: object) => object | undefined,
|
|
678
986
|
* unknown?: 'error' | 'warn' | 'ignore',
|
|
987
|
+
* strict?: boolean,
|
|
679
988
|
* base?: string,
|
|
989
|
+
* manifest?: object,
|
|
680
990
|
* equations?: Record<string, Record<string, string>>,
|
|
681
991
|
* tables?: Record<string, Record<string, string>>,
|
|
682
992
|
* figures?: Record<string, Record<string, string>>,
|
|
993
|
+
* diagrams?: Record<string, Record<string, string>>,
|
|
994
|
+
* checks?: Record<string, Record<string, string>>,
|
|
995
|
+
* numbering?: Record<string, { part: number, equations: number, tables: number, figures: number, diagrams: number, checks: number }>,
|
|
683
996
|
* chapters?: Record<string, number>,
|
|
684
997
|
* }} [options]
|
|
685
998
|
*
|
|
686
|
-
* `
|
|
687
|
-
*
|
|
688
|
-
*
|
|
689
|
-
*
|
|
690
|
-
*
|
|
999
|
+
* `manifest` is the preferred wiring: pass the compiled manifest and all
|
|
1000
|
+
* numbering inputs below are read off it together, so they cannot drift apart.
|
|
1001
|
+
* They stay available individually for hand-built trees, and an explicitly
|
|
1002
|
+
* supplied one still wins over the manifest.
|
|
1003
|
+
*
|
|
1004
|
+
* `numbering` maps a lesson slug to its **part** (the number before the dot)
|
|
1005
|
+
* and, per kind of float, how many the earlier lessons of that part already
|
|
1006
|
+
* used. Every display equation / `<Table>` / `<Figure>` / `<Diagram>` on the page is then
|
|
1007
|
+
* numbered `‹part›.‹offset + n›`, with `##` and `###` headings ignored; with no
|
|
1008
|
+
* entry the legacy `##`-section scheme applies. It must agree with
|
|
1009
|
+
* `manifest.lessons[]`, which is what feeds `equations`/`tables`/`figures`/`diagrams`
|
|
1010
|
+
* above — supplying the tables without `numbering` numbers the same equation
|
|
1011
|
+
* twice, and `assertManifestNumberParity` turns that into a build error rather
|
|
1012
|
+
* than a page whose references disagree with their targets.
|
|
1013
|
+
*
|
|
1014
|
+
* `chapters` is the superseded per-lesson form (slug → the lesson's course
|
|
1015
|
+
* ordinal, no running offset). Still read when `numbering` has no entry, so a
|
|
1016
|
+
* config written against the old option keeps building.
|
|
1017
|
+
*
|
|
1018
|
+
* `strict` (default `false`) additionally fails the build when a catalogued
|
|
1019
|
+
* lesson contains an equation/`<Table>`/`<Figure>` — keyed or not — but has no
|
|
1020
|
+
* `numbering` placement or no cross-page number table entry, instead of
|
|
1021
|
+
* silently rendering it under the legacy `##`-section scheme. Off by default
|
|
1022
|
+
* for backward compatibility; `createRemarkNotationOptions(manifest, { strict:
|
|
1023
|
+
* true })` is the recommended way to turn it on for a course whose manifest
|
|
1024
|
+
* covers every catalogued lesson.
|
|
691
1025
|
*/
|
|
692
1026
|
export default function remarkNotation(options = {}) {
|
|
693
1027
|
const unknown = options.unknown ?? 'error';
|
|
1028
|
+
const strict = options.strict ?? false;
|
|
1029
|
+
// The manifest supplies every numbering input at once; an explicit option
|
|
1030
|
+
// still overrides it, so existing per-field wiring keeps working.
|
|
1031
|
+
const numbering = { ...numberingFromManifest(options.manifest), ...options };
|
|
694
1032
|
|
|
695
1033
|
return function transform(tree, file) {
|
|
696
1034
|
const suppliedDefinitions =
|
|
@@ -703,10 +1041,10 @@ export default function remarkNotation(options = {}) {
|
|
|
703
1041
|
);
|
|
704
1042
|
const localDefinitions = localDefinitionsFromFile(file);
|
|
705
1043
|
const lessonId = lessonIdFrom(file);
|
|
706
|
-
|
|
707
|
-
//
|
|
708
|
-
|
|
709
|
-
|
|
1044
|
+
const lessonSlug = lessonId ? lessonId.replace(/\./g, '/') : undefined;
|
|
1045
|
+
// `{ prefix, offset }` per kind of float — see the `numbering` option.
|
|
1046
|
+
// Missing ⇒ the legacy `##`-section numbering.
|
|
1047
|
+
const equationNumbering = pageNumbering(numbering, lessonSlug, 'equations');
|
|
710
1048
|
const registryFile = isNotationRegistryFile(file);
|
|
711
1049
|
const mathScope = pageGlyphScope(
|
|
712
1050
|
localDefinitions,
|
|
@@ -718,8 +1056,9 @@ export default function remarkNotation(options = {}) {
|
|
|
718
1056
|
|
|
719
1057
|
// Equation identity (D7): assign every `\label{eq:…}` its number before any
|
|
720
1058
|
// math node is rewritten, so a `[[eq-key]]` earlier in the page still
|
|
721
|
-
// resolves. `
|
|
722
|
-
|
|
1059
|
+
// resolves. `equationNumbering` picks the `‹part›.‹n›` vs `##`-section
|
|
1060
|
+
// scheme.
|
|
1061
|
+
const equationLabels = collectEquationLabels(tree, equationNumbering);
|
|
723
1062
|
const pageLabels = new Map();
|
|
724
1063
|
for (const label of equationLabels) {
|
|
725
1064
|
if (pageLabels.has(label.key)) {
|
|
@@ -734,25 +1073,47 @@ export default function remarkNotation(options = {}) {
|
|
|
734
1073
|
const eqCtx = {
|
|
735
1074
|
base: options.base ?? '',
|
|
736
1075
|
pageLabels,
|
|
737
|
-
crossPage:
|
|
1076
|
+
crossPage: numbering.equations ?? {},
|
|
738
1077
|
};
|
|
1078
|
+
// WERN guard: what this page prints next to the equation must be what a
|
|
1079
|
+
// reference from another page prints for it.
|
|
1080
|
+
assertManifestNumberParity({
|
|
1081
|
+
file,
|
|
1082
|
+
slug: lessonSlug,
|
|
1083
|
+
pageLabels,
|
|
1084
|
+
crossPage: eqCtx.crossPage,
|
|
1085
|
+
placement: equationNumbering,
|
|
1086
|
+
noun: 'equation',
|
|
1087
|
+
syntax: (key) => `eq:${key}`,
|
|
1088
|
+
});
|
|
739
1089
|
|
|
740
1090
|
// Number and anchor every display equation on a lesson page, keyed or
|
|
741
1091
|
// not (F3). Non-lesson pages (notation registry, stray docs) keep the
|
|
742
1092
|
// keyed-only behaviour so their body math is left untouched.
|
|
743
1093
|
const displayEquations = lessonId
|
|
744
|
-
? collectDisplayEquations(tree,
|
|
1094
|
+
? collectDisplayEquations(tree, equationNumbering)
|
|
745
1095
|
: [];
|
|
746
1096
|
const equationByNode = new Map(
|
|
747
1097
|
displayEquations.map((equation) => [equation.node, equation]),
|
|
748
1098
|
);
|
|
1099
|
+
if (strict) {
|
|
1100
|
+
assertStrictNumbering({
|
|
1101
|
+
file,
|
|
1102
|
+
slug: lessonSlug,
|
|
1103
|
+
noun: 'equation',
|
|
1104
|
+
count: displayEquations.length,
|
|
1105
|
+
placement: equationNumbering,
|
|
1106
|
+
crossPage: numbering.equations,
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
749
1109
|
|
|
750
|
-
// Keyed floats
|
|
1110
|
+
// Keyed floats: every `<Table>`/`<Figure>`/`<Diagram>` on a lesson
|
|
751
1111
|
// page is numbered and anchored, keyed or not — same rule as equations,
|
|
752
1112
|
// via the shared engine in `keyed-jsx-floats.mjs`.
|
|
753
|
-
const makeFloatCtx = (tagName, crossPage) => {
|
|
1113
|
+
const makeFloatCtx = (tagName, crossPage, kind) => {
|
|
1114
|
+
const floatNumbering = pageNumbering(numbering, lessonSlug, kind);
|
|
754
1115
|
const floats = lessonId
|
|
755
|
-
? collectKeyedJsxFloats(tree, { tagName,
|
|
1116
|
+
? collectKeyedJsxFloats(tree, { tagName, numbering: floatNumbering })
|
|
756
1117
|
: [];
|
|
757
1118
|
const pageFloatLabels = new Map();
|
|
758
1119
|
for (const float of floats) {
|
|
@@ -766,6 +1127,26 @@ export default function remarkNotation(options = {}) {
|
|
|
766
1127
|
}
|
|
767
1128
|
pageFloatLabels.set(float.key, float.number);
|
|
768
1129
|
}
|
|
1130
|
+
assertManifestNumberParity({
|
|
1131
|
+
file,
|
|
1132
|
+
slug: lessonSlug,
|
|
1133
|
+
pageLabels: pageFloatLabels,
|
|
1134
|
+
crossPage: crossPage ?? {},
|
|
1135
|
+
placement: floatNumbering,
|
|
1136
|
+
noun: tagName,
|
|
1137
|
+
syntax: (key) => `<${tagName} id="${key}">`,
|
|
1138
|
+
});
|
|
1139
|
+
// Diagram is deliberately excluded — see `assertStrictNumbering`'s doc.
|
|
1140
|
+
if (strict && tagName !== 'Diagram') {
|
|
1141
|
+
assertStrictNumbering({
|
|
1142
|
+
file,
|
|
1143
|
+
slug: lessonSlug,
|
|
1144
|
+
noun: tagName,
|
|
1145
|
+
count: floats.length,
|
|
1146
|
+
placement: floatNumbering,
|
|
1147
|
+
crossPage,
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
769
1150
|
return {
|
|
770
1151
|
ctx: {
|
|
771
1152
|
base: options.base ?? '',
|
|
@@ -777,12 +1158,26 @@ export default function remarkNotation(options = {}) {
|
|
|
777
1158
|
};
|
|
778
1159
|
const { ctx: tableCtx, byNode: tableByNode } = makeFloatCtx(
|
|
779
1160
|
'Table',
|
|
780
|
-
|
|
1161
|
+
numbering.tables,
|
|
1162
|
+
'tables',
|
|
781
1163
|
);
|
|
782
1164
|
const { ctx: figureCtx, byNode: figureByNode } = makeFloatCtx(
|
|
783
1165
|
'Figure',
|
|
784
|
-
|
|
1166
|
+
numbering.figures,
|
|
1167
|
+
'figures',
|
|
1168
|
+
);
|
|
1169
|
+
const { ctx: diagramCtx, byNode: diagramByNode } = makeFloatCtx(
|
|
1170
|
+
'Diagram',
|
|
1171
|
+
numbering.diagrams,
|
|
1172
|
+
'diagrams',
|
|
785
1173
|
);
|
|
1174
|
+
const checkCtx = {
|
|
1175
|
+
base: options.base ?? '',
|
|
1176
|
+
pageLabels: new Map(
|
|
1177
|
+
Object.entries(numbering.checks?.[lessonSlug] ?? {}),
|
|
1178
|
+
),
|
|
1179
|
+
crossPage: numbering.checks ?? {},
|
|
1180
|
+
};
|
|
786
1181
|
|
|
787
1182
|
const resolve = (key, kind, node) => {
|
|
788
1183
|
const context = {
|
|
@@ -904,6 +1299,8 @@ export default function remarkNotation(options = {}) {
|
|
|
904
1299
|
eqCtx,
|
|
905
1300
|
tableCtx,
|
|
906
1301
|
figureCtx,
|
|
1302
|
+
diagramCtx,
|
|
1303
|
+
checkCtx,
|
|
907
1304
|
);
|
|
908
1305
|
if (replacement) {
|
|
909
1306
|
node.children.splice(index, 1, ...replacement);
|
|
@@ -914,14 +1311,72 @@ export default function remarkNotation(options = {}) {
|
|
|
914
1311
|
visit(child, false);
|
|
915
1312
|
if (child && child.type === 'math' && equationByNode.has(child)) {
|
|
916
1313
|
node.children[index] = equationNode(child, equationByNode.get(child));
|
|
1314
|
+
} else if (lessonId && child?.type === 'inlineMath') {
|
|
1315
|
+
// Scoped to lesson pages, same as `displayEquations` above — a
|
|
1316
|
+
// registry/glossary page's inline math is left exactly as before.
|
|
1317
|
+
node.children[index] = inlineEquationNode(child);
|
|
917
1318
|
} else if (child?.type === 'mdxJsxFlowElement' && tableByNode.has(child)) {
|
|
918
1319
|
injectFloatNumberAttributes(child, tableByNode.get(child), 'tab');
|
|
919
1320
|
} else if (child?.type === 'mdxJsxFlowElement' && figureByNode.has(child)) {
|
|
920
1321
|
injectFloatNumberAttributes(child, figureByNode.get(child), 'fig');
|
|
1322
|
+
} else if (child?.type === 'mdxJsxFlowElement' && diagramByNode.has(child)) {
|
|
1323
|
+
injectFloatNumberAttributes(child, diagramByNode.get(child), 'dia');
|
|
921
1324
|
}
|
|
922
1325
|
}
|
|
923
1326
|
};
|
|
924
1327
|
|
|
1328
|
+
// Render-time defense in depth for content invisible to keyed-float
|
|
1329
|
+
// validation — the manifest builder runs the same check via
|
|
1330
|
+
// `validateRawFloats`/`scanRawFloats` (raw-floats.mjs); this catches
|
|
1331
|
+
// content added since the manifest was last compiled.
|
|
1332
|
+
if (lessonId) {
|
|
1333
|
+
const rawFloats = collectRawFloats(tree);
|
|
1334
|
+
const plural = (count, noun) => (count === 1 ? noun : `${noun}s`);
|
|
1335
|
+
if (rawFloats.markdownTableNodes.length > 0) {
|
|
1336
|
+
warn(
|
|
1337
|
+
file,
|
|
1338
|
+
`${lessonSlug} has ${rawFloats.markdownTableNodes.length} raw ` +
|
|
1339
|
+
`Markdown ${plural(rawFloats.markdownTableNodes.length, 'table')} ` +
|
|
1340
|
+
`not wrapped in <Table>; it has no stable number and cannot be ` +
|
|
1341
|
+
`referenced with [[tab-…]]. Wrap it in <Table id="…">…</Table> ` +
|
|
1342
|
+
`to give it one.`,
|
|
1343
|
+
rawFloats.markdownTableNodes[0],
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
if (rawFloats.htmlTableNodes.length > 0) {
|
|
1347
|
+
warn(
|
|
1348
|
+
file,
|
|
1349
|
+
`${lessonSlug} has ${rawFloats.htmlTableNodes.length} raw HTML ` +
|
|
1350
|
+
`<table> ${plural(rawFloats.htmlTableNodes.length, 'element')} ` +
|
|
1351
|
+
`not wrapped in <Table>; it has no stable number and cannot be ` +
|
|
1352
|
+
`referenced with [[tab-…]]. Wrap it in <Table id="…">…</Table> ` +
|
|
1353
|
+
`to give it one.`,
|
|
1354
|
+
rawFloats.htmlTableNodes[0],
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
if (rawFloats.unkeyedDiagramNodes.length > 0) {
|
|
1358
|
+
warn(
|
|
1359
|
+
file,
|
|
1360
|
+
`${lessonSlug} has ${rawFloats.unkeyedDiagramNodes.length} ` +
|
|
1361
|
+
`<Diagram> ${plural(rawFloats.unkeyedDiagramNodes.length, 'component')} ` +
|
|
1362
|
+
`with no id; it cannot be referenced with [[dia-…]]. Add an id ` +
|
|
1363
|
+
`to give it a stable reference.`,
|
|
1364
|
+
rawFloats.unkeyedDiagramNodes[0],
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1367
|
+
if (rawFloats.nestedDiagramNodes.length > 0) {
|
|
1368
|
+
warn(
|
|
1369
|
+
file,
|
|
1370
|
+
`${lessonSlug} has ${rawFloats.nestedDiagramNodes.length} ` +
|
|
1371
|
+
`<Diagram> ${plural(rawFloats.nestedDiagramNodes.length, 'component')} ` +
|
|
1372
|
+
`nested inside another component; it is invisible to diagram ` +
|
|
1373
|
+
`numbering. Move it to the top level (or inside ` +
|
|
1374
|
+
`<Memo>/<ShortNote>), or wrap it in <Figure> to give it identity.`,
|
|
1375
|
+
rawFloats.nestedDiagramNodes[0],
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
|
|
925
1380
|
visit(tree);
|
|
926
1381
|
|
|
927
1382
|
file.data.notationReferences = references;
|
|
@@ -945,5 +1400,6 @@ export default function remarkNotation(options = {}) {
|
|
|
945
1400
|
}));
|
|
946
1401
|
file.data.tableLabels = floatLabelData(tableByNode);
|
|
947
1402
|
file.data.figureLabels = floatLabelData(figureByNode);
|
|
1403
|
+
file.data.diagramLabels = floatLabelData(diagramByNode);
|
|
948
1404
|
};
|
|
949
1405
|
}
|