explico 0.1.1 → 0.3.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 +102 -0
- package/README.md +46 -5
- package/dist/compiler/cli.d.ts +17 -0
- package/dist/compiler/cli.js +41 -3
- package/dist/compiler/collections.js +31 -1
- package/dist/compiler/course-config.d.ts +52 -1
- package/dist/compiler/knowledge-version.d.ts +58 -0
- package/dist/compiler/knowledge-version.js +80 -0
- package/dist/compiler/manifest.d.ts +87 -3
- package/dist/compiler/manifest.js +133 -20
- package/dist/compiler/resolution-report.d.ts +5 -0
- package/dist/reference/base-operators.d.mts +38 -0
- package/dist/reference/base-operators.mjs +103 -0
- package/dist/reference/consistency.d.ts +7 -2
- package/dist/reference/consistency.js +142 -2
- package/dist/reference/diagram-layout.d.ts +63 -0
- package/dist/reference/diagram-layout.js +122 -0
- package/dist/reference/equations-validate.d.ts +5 -0
- package/dist/reference/equations-validate.js +1 -1
- package/dist/reference/equations.d.mts +76 -18
- package/dist/reference/equations.mjs +118 -41
- package/dist/reference/figures-validate.d.ts +21 -0
- package/dist/reference/figures-validate.js +13 -0
- package/dist/reference/gate-math.d.ts +5 -4
- package/dist/reference/gate-math.js +73 -2
- package/dist/reference/gloss.d.ts +87 -0
- package/dist/reference/gloss.js +59 -0
- package/dist/reference/keyed-float-validate.d.ts +67 -0
- package/dist/reference/keyed-float-validate.js +110 -0
- package/dist/reference/keyed-jsx-floats.d.mts +56 -0
- package/dist/reference/keyed-jsx-floats.mjs +167 -0
- package/dist/reference/math-glyphs.d.mts +18 -0
- package/dist/reference/math-glyphs.mjs +92 -0
- package/dist/reference/registry.js +25 -0
- package/dist/reference/rehype-heading-anchors.d.mts +1 -0
- package/dist/reference/rehype-heading-anchors.mjs +144 -0
- package/dist/reference/remark-notation.d.mts +12 -0
- package/dist/reference/remark-notation.mjs +228 -32
- package/dist/reference/tables-validate.d.ts +21 -0
- package/dist/reference/tables-validate.js +13 -0
- package/dist/reference/types.d.ts +25 -2
- package/dist/reference/units-vocab.js +19 -1
- package/docs/VERSIONING.md +66 -0
- package/docs/authoring/README.md +96 -0
- package/docs/authoring/agents.md +63 -0
- package/docs/authoring/migrating.md +107 -0
- package/docs/authoring/notation.md +296 -0
- package/docs/authoring/versioning.md +116 -0
- package/docs/authoring/visualizations.md +195 -0
- package/package.json +20 -5
- package/src/compiler/cli.ts +49 -0
- package/src/compiler/collections.ts +52 -3
- package/src/compiler/course-config.ts +54 -0
- package/src/compiler/knowledge-version.ts +157 -0
- package/src/compiler/manifest.ts +270 -17
- package/src/components/CurriculumMap.astro +27 -0
- package/src/components/HoverPanel.astro +42 -0
- package/src/components/citation/CitationLayer.astro +1 -0
- package/src/components/diagram/Diagram.astro +317 -0
- package/src/components/examples/CompactExamples.astro +1 -1
- package/src/components/figure/Figure.astro +104 -0
- package/src/components/hover-panel.ts +71 -5
- package/src/components/notation/NotationFormula.astro +154 -0
- package/src/components/notation/NotationGlossary.astro +60 -0
- package/src/components/notation/NotationLayer.astro +248 -103
- package/src/components/notation/render-notation-math.ts +66 -0
- package/src/components/popup-stack.ts +345 -0
- package/src/components/reference/FloatRefLayer.astro +671 -0
- package/src/components/starlight/AboutPanel.astro +255 -0
- package/src/components/starlight/HeadingAnchorEnhancer.astro +131 -0
- package/src/components/starlight/KeyedFloatEnhancer.astro +137 -0
- package/src/components/starlight/LayoutSocialIcons.astro +12 -0
- package/src/components/starlight/LessonFooter.astro +8 -2
- package/src/components/table/Table.astro +104 -0
- package/src/content-config.ts +38 -0
- package/src/reference/base-operators.mjs +103 -0
- package/src/reference/consistency.ts +236 -2
- package/src/reference/diagram-layout.ts +246 -0
- package/src/reference/equations-validate.ts +6 -1
- package/src/reference/equations.mjs +118 -41
- package/src/reference/figures-validate.ts +40 -0
- package/src/reference/gate-math.ts +104 -7
- package/src/reference/gloss.ts +153 -0
- package/src/reference/keyed-float-validate.ts +207 -0
- package/src/reference/keyed-jsx-floats.mjs +167 -0
- package/src/reference/math-glyphs.mjs +92 -0
- package/src/reference/registry.ts +43 -0
- package/src/reference/rehype-heading-anchors.mjs +144 -0
- package/src/reference/remark-notation.mjs +228 -32
- package/src/reference/tables-validate.ts +40 -0
- package/src/reference/types.ts +26 -1
- package/src/reference/units-vocab.ts +18 -0
- package/src/styles/global.css +193 -3
- package/src/components/starlight/EquationEnhancer.astro +0 -72
|
@@ -36,10 +36,17 @@ export const NOTATION_KEY_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
|
|
|
36
36
|
import { GlyphResolutionError, resolveMathGlyphs } from './math-glyphs.mjs';
|
|
37
37
|
import {
|
|
38
38
|
EQ_LABEL_PATTERN,
|
|
39
|
+
collectDisplayEquations,
|
|
39
40
|
collectEquationLabels,
|
|
41
|
+
equationAnchorId,
|
|
40
42
|
parseEquationRef,
|
|
41
43
|
stripEquationLabels,
|
|
42
44
|
} from './equations.mjs';
|
|
45
|
+
import {
|
|
46
|
+
collectKeyedJsxFloats,
|
|
47
|
+
keyedFloatAnchorId,
|
|
48
|
+
parseKeyedFloatRef,
|
|
49
|
+
} from './keyed-jsx-floats.mjs';
|
|
43
50
|
|
|
44
51
|
const PROSE_SKIP_TYPES = new Set([
|
|
45
52
|
'code',
|
|
@@ -307,34 +314,39 @@ function defaultHref(definition, key, base = '') {
|
|
|
307
314
|
}
|
|
308
315
|
|
|
309
316
|
/**
|
|
310
|
-
* Wrap a
|
|
311
|
-
* (which splices the inner `math-display` element out).
|
|
312
|
-
* a
|
|
313
|
-
*
|
|
317
|
+
* Wrap a display `$$…$$` `math` node so the container survives `rehype-katex`
|
|
318
|
+
* (which splices the inner `math-display` element out). Every display equation
|
|
319
|
+
* in a lesson is numbered; a keyed one anchors at `#eq-<key>`, an unkeyed one
|
|
320
|
+
* at its positional `#eq-<section>-<index>`. The visible number is a focusable
|
|
321
|
+
* `<a>` sibling — never authored, never inside KaTeX — so it survives print and
|
|
322
|
+
* screen readers name it "Equation 2.4".
|
|
314
323
|
*/
|
|
315
|
-
function
|
|
324
|
+
function equationNode(mathNode, { key, number }) {
|
|
325
|
+
const id = equationAnchorId({ key, number });
|
|
326
|
+
const hProperties = {
|
|
327
|
+
id,
|
|
328
|
+
className: ['keyed-equation'],
|
|
329
|
+
'data-eq-number': number,
|
|
330
|
+
};
|
|
331
|
+
if (key) hProperties['data-eq-key'] = key;
|
|
332
|
+
else hProperties['data-eq-auto'] = '';
|
|
333
|
+
|
|
316
334
|
return {
|
|
317
335
|
type: 'paragraph',
|
|
318
|
-
data: {
|
|
319
|
-
hName: 'div',
|
|
320
|
-
hProperties: {
|
|
321
|
-
id: `eq-${key}`,
|
|
322
|
-
className: ['keyed-equation'],
|
|
323
|
-
'data-eq-key': key,
|
|
324
|
-
'data-eq-number': number,
|
|
325
|
-
},
|
|
326
|
-
},
|
|
336
|
+
data: { hName: 'div', hProperties },
|
|
327
337
|
children: [
|
|
328
338
|
mathNode,
|
|
329
339
|
{
|
|
330
340
|
type: 'link',
|
|
331
|
-
url:
|
|
341
|
+
url: `#${id}`,
|
|
332
342
|
children: [{ type: 'text', value: `(${number})` }],
|
|
333
343
|
data: {
|
|
334
344
|
hProperties: {
|
|
335
345
|
className: ['keyed-equation__number'],
|
|
336
346
|
'data-eq-number': number,
|
|
337
347
|
'aria-label': `Equation ${number}`,
|
|
348
|
+
// Plain hover tooltip — the number is a permalink to this equation.
|
|
349
|
+
title: 'Anchor to this equation',
|
|
338
350
|
},
|
|
339
351
|
},
|
|
340
352
|
},
|
|
@@ -342,6 +354,32 @@ function keyedEquationNode(mathNode, key, number) {
|
|
|
342
354
|
};
|
|
343
355
|
}
|
|
344
356
|
|
|
357
|
+
/**
|
|
358
|
+
* Give a `<Table>`/`<Figure>` `mdxJsxFlowElement` its computed `number` and
|
|
359
|
+
* `anchorId` as real JSX attributes, so the actual Astro component (which
|
|
360
|
+
* renders the numbered anchor, unlike `equationNode`'s synthesized hast div)
|
|
361
|
+
* receives them as props. The compiler is the single source of truth for
|
|
362
|
+
* both — the component only renders what it's given.
|
|
363
|
+
*/
|
|
364
|
+
function injectFloatNumberAttributes(node, float, prefix) {
|
|
365
|
+
const anchorId = keyedFloatAnchorId({
|
|
366
|
+
key: float.key,
|
|
367
|
+
number: float.number,
|
|
368
|
+
prefix,
|
|
369
|
+
});
|
|
370
|
+
node.attributes = (node.attributes ?? []).filter(
|
|
371
|
+
(attribute) =>
|
|
372
|
+
!(
|
|
373
|
+
attribute?.type === 'mdxJsxAttribute' &&
|
|
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
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
345
383
|
/** Resolve a `[[eq-key]]` / `[[slug#eq-key]]` reference to `{ number, href }`. */
|
|
346
384
|
function resolveEquationRef(eqRef, eqCtx, file, node) {
|
|
347
385
|
if (eqRef.lesson) {
|
|
@@ -369,7 +407,47 @@ function resolveEquationRef(eqRef, eqCtx, file, node) {
|
|
|
369
407
|
return { number, href: `#eq-${eqRef.key}` };
|
|
370
408
|
}
|
|
371
409
|
|
|
372
|
-
|
|
410
|
+
/**
|
|
411
|
+
* Resolve a `[[<prefix>-key]]` / `[[slug#<prefix>-key]]` reference (a
|
|
412
|
+
* `<Table>`/`<Figure>` float) to `{ number, href }` — the table/figure
|
|
413
|
+
* equivalent of `resolveEquationRef`.
|
|
414
|
+
*/
|
|
415
|
+
function resolveKeyedFloatRef(floatRef, ctx, { prefix, tagName }, file, node) {
|
|
416
|
+
if (floatRef.lesson) {
|
|
417
|
+
const number = ctx.crossPage?.[floatRef.lesson]?.[floatRef.key];
|
|
418
|
+
if (number === undefined) {
|
|
419
|
+
fail(
|
|
420
|
+
file,
|
|
421
|
+
`Unknown ${tagName} reference [[${floatRef.lesson}#${prefix}-${floatRef.key}]]: lesson ${floatRef.lesson} has no <${tagName}> with id="${floatRef.key}".`,
|
|
422
|
+
node,
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
return {
|
|
426
|
+
number,
|
|
427
|
+
href: `${ctx.base.replace(/\/$/, '')}/${floatRef.lesson}/#${prefix}-${floatRef.key}`,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
const number = ctx.pageLabels.get(floatRef.key);
|
|
431
|
+
if (number === undefined) {
|
|
432
|
+
fail(
|
|
433
|
+
file,
|
|
434
|
+
`Unknown ${tagName} reference [[${prefix}-${floatRef.key}]]: no <${tagName}> on this page carries id="${floatRef.key}".`,
|
|
435
|
+
node,
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
return { number, href: `#${prefix}-${floatRef.key}` };
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function proseNodes(
|
|
442
|
+
value,
|
|
443
|
+
resolve,
|
|
444
|
+
file,
|
|
445
|
+
node,
|
|
446
|
+
references,
|
|
447
|
+
eqCtx,
|
|
448
|
+
tableCtx,
|
|
449
|
+
figureCtx,
|
|
450
|
+
) {
|
|
373
451
|
const base = eqCtx?.base ?? '';
|
|
374
452
|
const strayExplain = firstUnescaped(value, EXPLAIN);
|
|
375
453
|
if (strayExplain !== -1) {
|
|
@@ -390,7 +468,18 @@ function proseNodes(value, resolve, file, node, references, eqCtx) {
|
|
|
390
468
|
|
|
391
469
|
const key = match[1].trim();
|
|
392
470
|
const eqRef = eqCtx ? parseEquationRef(key) : undefined;
|
|
393
|
-
|
|
471
|
+
const tableRef =
|
|
472
|
+
!eqRef && tableCtx ? parseKeyedFloatRef(key, { prefix: 'tab' }) : undefined;
|
|
473
|
+
const figureRef =
|
|
474
|
+
!eqRef && !tableRef && figureCtx
|
|
475
|
+
? parseKeyedFloatRef(key, { prefix: 'fig' })
|
|
476
|
+
: undefined;
|
|
477
|
+
if (
|
|
478
|
+
!eqRef &&
|
|
479
|
+
!tableRef &&
|
|
480
|
+
!figureRef &&
|
|
481
|
+
!NOTATION_KEY_PATTERN.test(key)
|
|
482
|
+
) {
|
|
394
483
|
fail(file, `Invalid notation key "${key}" in [[...]].`, node);
|
|
395
484
|
}
|
|
396
485
|
|
|
@@ -421,9 +510,46 @@ function proseNodes(value, resolve, file, node, references, eqCtx) {
|
|
|
421
510
|
continue;
|
|
422
511
|
}
|
|
423
512
|
|
|
513
|
+
const floatRef = tableRef ?? figureRef;
|
|
514
|
+
if (floatRef) {
|
|
515
|
+
const kind = tableRef ? 'table' : 'figure';
|
|
516
|
+
const prefix = tableRef ? 'tab' : 'fig';
|
|
517
|
+
const tagName = tableRef ? 'Table' : 'Figure';
|
|
518
|
+
const { number, href } = resolveKeyedFloatRef(
|
|
519
|
+
floatRef,
|
|
520
|
+
tableRef ? tableCtx : figureCtx,
|
|
521
|
+
{ prefix, tagName },
|
|
522
|
+
file,
|
|
523
|
+
node,
|
|
524
|
+
);
|
|
525
|
+
output.push({
|
|
526
|
+
type: 'link',
|
|
527
|
+
url: href,
|
|
528
|
+
children: [{ type: 'text', value: `${tagName} ${number}` }],
|
|
529
|
+
data: {
|
|
530
|
+
hProperties: {
|
|
531
|
+
className: [`${kind}-ref`],
|
|
532
|
+
[`data-${prefix}-ref`]: floatRef.key,
|
|
533
|
+
...(floatRef.lesson ? { [`data-${prefix}-lesson`]: floatRef.lesson } : {}),
|
|
534
|
+
},
|
|
535
|
+
},
|
|
536
|
+
});
|
|
537
|
+
references.push({
|
|
538
|
+
key: floatRef.key,
|
|
539
|
+
kind,
|
|
540
|
+
...(floatRef.lesson ? { lesson: floatRef.lesson } : {}),
|
|
541
|
+
});
|
|
542
|
+
cursor = match.index + match[0].length;
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
|
|
424
546
|
const definition = resolve(key, 'prose', node);
|
|
425
|
-
|
|
426
|
-
|
|
547
|
+
// `title`/`summary` are the pre-C1b field names, kept as the first
|
|
548
|
+
// fallback for a caller still supplying them; real content authors
|
|
549
|
+
// `label`/`meaning` (see `content-config.ts`), which raw frontmatter
|
|
550
|
+
// (`manifest.notation.raw`, a page's own `notation.local`) carries as-is.
|
|
551
|
+
const title = definition?.title ?? definition?.label ?? key;
|
|
552
|
+
const summary = definition?.summary ?? definition?.meaning;
|
|
427
553
|
const href = defaultHref(definition, key, base);
|
|
428
554
|
|
|
429
555
|
const properties = {
|
|
@@ -552,7 +678,16 @@ function inspectExplainCalls(value, resolve, file, node, references) {
|
|
|
552
678
|
* unknown?: 'error' | 'warn' | 'ignore',
|
|
553
679
|
* base?: string,
|
|
554
680
|
* equations?: Record<string, Record<string, string>>,
|
|
681
|
+
* tables?: Record<string, Record<string, string>>,
|
|
682
|
+
* figures?: Record<string, Record<string, string>>,
|
|
683
|
+
* chapters?: Record<string, number>,
|
|
555
684
|
* }} [options]
|
|
685
|
+
*
|
|
686
|
+
* `chapters` maps a lesson slug to its 1-based course ordinal. When the
|
|
687
|
+
* current page has one, every display equation / `<Table>` / `<Figure>` is
|
|
688
|
+
* numbered `‹chapter›.‹n›` (`##` headings ignored); with no entry the legacy
|
|
689
|
+
* `##`-section scheme applies. It must agree with `manifest.lessons[].chapter`,
|
|
690
|
+
* which is what feeds `equations`/`tables`/`figures` above.
|
|
556
691
|
*/
|
|
557
692
|
export default function remarkNotation(options = {}) {
|
|
558
693
|
const unknown = options.unknown ?? 'error';
|
|
@@ -568,6 +703,10 @@ export default function remarkNotation(options = {}) {
|
|
|
568
703
|
);
|
|
569
704
|
const localDefinitions = localDefinitionsFromFile(file);
|
|
570
705
|
const lessonId = lessonIdFrom(file);
|
|
706
|
+
// The lesson's course ordinal (see the `chapters` option). Falsy / missing
|
|
707
|
+
// ⇒ the legacy `##`-section numbering, so `0` and `undefined` behave alike.
|
|
708
|
+
const chapter =
|
|
709
|
+
(lessonId && options.chapters?.[lessonId.replace(/\./g, '/')]) || undefined;
|
|
571
710
|
const registryFile = isNotationRegistryFile(file);
|
|
572
711
|
const mathScope = pageGlyphScope(
|
|
573
712
|
localDefinitions,
|
|
@@ -577,10 +716,10 @@ export default function remarkNotation(options = {}) {
|
|
|
577
716
|
const references = [];
|
|
578
717
|
const resolved = new Map();
|
|
579
718
|
|
|
580
|
-
// Equation identity (D7): assign every `\label{eq:…}` its
|
|
581
|
-
//
|
|
582
|
-
// the
|
|
583
|
-
const equationLabels = collectEquationLabels(tree);
|
|
719
|
+
// Equation identity (D7): assign every `\label{eq:…}` its number before any
|
|
720
|
+
// math node is rewritten, so a `[[eq-key]]` earlier in the page still
|
|
721
|
+
// resolves. `chapter` picks the `‹chapter›.‹n›` vs `##`-section scheme.
|
|
722
|
+
const equationLabels = collectEquationLabels(tree, chapter);
|
|
584
723
|
const pageLabels = new Map();
|
|
585
724
|
for (const label of equationLabels) {
|
|
586
725
|
if (pageLabels.has(label.key)) {
|
|
@@ -598,6 +737,53 @@ export default function remarkNotation(options = {}) {
|
|
|
598
737
|
crossPage: options.equations ?? {},
|
|
599
738
|
};
|
|
600
739
|
|
|
740
|
+
// Number and anchor every display equation on a lesson page, keyed or
|
|
741
|
+
// not (F3). Non-lesson pages (notation registry, stray docs) keep the
|
|
742
|
+
// keyed-only behaviour so their body math is left untouched.
|
|
743
|
+
const displayEquations = lessonId
|
|
744
|
+
? collectDisplayEquations(tree, chapter)
|
|
745
|
+
: [];
|
|
746
|
+
const equationByNode = new Map(
|
|
747
|
+
displayEquations.map((equation) => [equation.node, equation]),
|
|
748
|
+
);
|
|
749
|
+
|
|
750
|
+
// Keyed floats (F-tables-figures): every `<Table>`/`<Figure>` on a lesson
|
|
751
|
+
// page is numbered and anchored, keyed or not — same rule as equations,
|
|
752
|
+
// via the shared engine in `keyed-jsx-floats.mjs`.
|
|
753
|
+
const makeFloatCtx = (tagName, crossPage) => {
|
|
754
|
+
const floats = lessonId
|
|
755
|
+
? collectKeyedJsxFloats(tree, { tagName, chapter })
|
|
756
|
+
: [];
|
|
757
|
+
const pageFloatLabels = new Map();
|
|
758
|
+
for (const float of floats) {
|
|
759
|
+
if (float.key == null) continue;
|
|
760
|
+
if (pageFloatLabels.has(float.key)) {
|
|
761
|
+
fail(
|
|
762
|
+
file,
|
|
763
|
+
`Duplicate <${tagName} id="${float.key}"> on this page; an id is unique per lesson.`,
|
|
764
|
+
float.node,
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
pageFloatLabels.set(float.key, float.number);
|
|
768
|
+
}
|
|
769
|
+
return {
|
|
770
|
+
ctx: {
|
|
771
|
+
base: options.base ?? '',
|
|
772
|
+
pageLabels: pageFloatLabels,
|
|
773
|
+
crossPage: crossPage ?? {},
|
|
774
|
+
},
|
|
775
|
+
byNode: new Map(floats.map((float) => [float.node, float])),
|
|
776
|
+
};
|
|
777
|
+
};
|
|
778
|
+
const { ctx: tableCtx, byNode: tableByNode } = makeFloatCtx(
|
|
779
|
+
'Table',
|
|
780
|
+
options.tables,
|
|
781
|
+
);
|
|
782
|
+
const { ctx: figureCtx, byNode: figureByNode } = makeFloatCtx(
|
|
783
|
+
'Figure',
|
|
784
|
+
options.figures,
|
|
785
|
+
);
|
|
786
|
+
|
|
601
787
|
const resolve = (key, kind, node) => {
|
|
602
788
|
const context = {
|
|
603
789
|
file,
|
|
@@ -647,7 +833,6 @@ export default function remarkNotation(options = {}) {
|
|
|
647
833
|
node,
|
|
648
834
|
);
|
|
649
835
|
}
|
|
650
|
-
node.__eqKey = labelMatch[1];
|
|
651
836
|
value = stripEquationLabels(value);
|
|
652
837
|
replaceMathSource(node, value);
|
|
653
838
|
}
|
|
@@ -717,6 +902,8 @@ export default function remarkNotation(options = {}) {
|
|
|
717
902
|
child,
|
|
718
903
|
references,
|
|
719
904
|
eqCtx,
|
|
905
|
+
tableCtx,
|
|
906
|
+
figureCtx,
|
|
720
907
|
);
|
|
721
908
|
if (replacement) {
|
|
722
909
|
node.children.splice(index, 1, ...replacement);
|
|
@@ -725,14 +912,12 @@ export default function remarkNotation(options = {}) {
|
|
|
725
912
|
continue;
|
|
726
913
|
}
|
|
727
914
|
visit(child, false);
|
|
728
|
-
if (child && child.
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
pageLabels.get(key),
|
|
735
|
-
);
|
|
915
|
+
if (child && child.type === 'math' && equationByNode.has(child)) {
|
|
916
|
+
node.children[index] = equationNode(child, equationByNode.get(child));
|
|
917
|
+
} else if (child?.type === 'mdxJsxFlowElement' && tableByNode.has(child)) {
|
|
918
|
+
injectFloatNumberAttributes(child, tableByNode.get(child), 'tab');
|
|
919
|
+
} else if (child?.type === 'mdxJsxFlowElement' && figureByNode.has(child)) {
|
|
920
|
+
injectFloatNumberAttributes(child, figureByNode.get(child), 'fig');
|
|
736
921
|
}
|
|
737
922
|
}
|
|
738
923
|
};
|
|
@@ -749,5 +934,16 @@ export default function remarkNotation(options = {}) {
|
|
|
749
934
|
indexInSection,
|
|
750
935
|
}),
|
|
751
936
|
);
|
|
937
|
+
const floatLabelData = (byNode) =>
|
|
938
|
+
[...byNode.values()]
|
|
939
|
+
.filter((float) => float.key != null)
|
|
940
|
+
.map(({ key, number, section, indexInSection }) => ({
|
|
941
|
+
key,
|
|
942
|
+
number,
|
|
943
|
+
section,
|
|
944
|
+
indexInSection,
|
|
945
|
+
}));
|
|
946
|
+
file.data.tableLabels = floatLabelData(tableByNode);
|
|
947
|
+
file.data.figureLabels = floatLabelData(figureByNode);
|
|
752
948
|
};
|
|
753
949
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** `validateKeyedFloats` for `<Table>` — see `keyed-float-validate.ts`. */
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
validateKeyedFloats,
|
|
5
|
+
type KeyedFloatLabelRecord,
|
|
6
|
+
type KeyedFloatLessonInput,
|
|
7
|
+
type KeyedFloatValidation,
|
|
8
|
+
} from './keyed-float-validate';
|
|
9
|
+
|
|
10
|
+
export type TableDiagnosticCode =
|
|
11
|
+
| 'tab-invalid-key'
|
|
12
|
+
| 'tab-duplicate-key'
|
|
13
|
+
| 'tab-ref-resolves'
|
|
14
|
+
| 'tab-key-unused';
|
|
15
|
+
|
|
16
|
+
export interface TableDiagnostic {
|
|
17
|
+
readonly code: TableDiagnosticCode;
|
|
18
|
+
readonly severity: 'error' | 'warning';
|
|
19
|
+
readonly message: string;
|
|
20
|
+
readonly file?: string;
|
|
21
|
+
readonly lessonId?: string;
|
|
22
|
+
readonly key?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type TableLabelRecord = KeyedFloatLabelRecord;
|
|
26
|
+
export type TableLessonInput = KeyedFloatLessonInput;
|
|
27
|
+
|
|
28
|
+
export interface TableValidation extends KeyedFloatValidation {
|
|
29
|
+
readonly diagnostics: readonly TableDiagnostic[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function validateTables(
|
|
33
|
+
lessons: readonly TableLessonInput[],
|
|
34
|
+
): TableValidation {
|
|
35
|
+
return validateKeyedFloats(lessons, {
|
|
36
|
+
tagName: 'Table',
|
|
37
|
+
prefix: 'tab',
|
|
38
|
+
noun: 'table',
|
|
39
|
+
}) as TableValidation;
|
|
40
|
+
}
|
package/src/reference/types.ts
CHANGED
|
@@ -21,6 +21,20 @@ export type NotationAlignment =
|
|
|
21
21
|
|
|
22
22
|
export type NotationReferenceKind = 'prose' | 'math' | 'definition';
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* A gloss resolved by the loader (D15). Declared on a card, keyed
|
|
26
|
+
* `<card key>.<slug(name)>`, and deliberately absent from the registry: a
|
|
27
|
+
* gloss is never a definition record, never a reference target, and never a
|
|
28
|
+
* curriculum node. See `reference/gloss.ts`.
|
|
29
|
+
*/
|
|
30
|
+
export interface NotationGlossEntry {
|
|
31
|
+
readonly key: string;
|
|
32
|
+
readonly notation: string;
|
|
33
|
+
readonly label: string;
|
|
34
|
+
readonly units?: string;
|
|
35
|
+
readonly ownerKey: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
24
38
|
export interface NotationReferenceInput {
|
|
25
39
|
readonly key: string;
|
|
26
40
|
readonly kind: NotationReferenceKind;
|
|
@@ -44,6 +58,8 @@ export interface SharedNotationDefinitionInput {
|
|
|
44
58
|
readonly body: string;
|
|
45
59
|
/** References harvested from the Markdown body. */
|
|
46
60
|
readonly references: readonly NotationReferenceInput[];
|
|
61
|
+
/** Symbols this entry's `formula` names but does not make cards of. */
|
|
62
|
+
readonly glosses: readonly NotationGlossEntry[];
|
|
47
63
|
readonly source: SourceSpan;
|
|
48
64
|
}
|
|
49
65
|
|
|
@@ -59,6 +75,8 @@ export interface LocalNotationDefinitionInput {
|
|
|
59
75
|
readonly alignment: NotationAlignment;
|
|
60
76
|
/** References harvested from summary content. */
|
|
61
77
|
readonly references: readonly NotationReferenceInput[];
|
|
78
|
+
/** Symbols this entry's `formula` names but does not make cards of. */
|
|
79
|
+
readonly glosses: readonly NotationGlossEntry[];
|
|
62
80
|
readonly source: SourceSpan;
|
|
63
81
|
}
|
|
64
82
|
|
|
@@ -90,7 +108,8 @@ export type NotationDiagnosticCode =
|
|
|
90
108
|
| 'alignment-unknown-lesson'
|
|
91
109
|
| 'alignment-introduction'
|
|
92
110
|
| 'alignment-unavailable'
|
|
93
|
-
| 'alignment-review-state'
|
|
111
|
+
| 'alignment-review-state'
|
|
112
|
+
| 'gloss-collides-with-card';
|
|
94
113
|
|
|
95
114
|
export type DiagnosticSeverity = 'error' | 'warning';
|
|
96
115
|
|
|
@@ -120,6 +139,12 @@ export interface ResolvedNotationReference {
|
|
|
120
139
|
interface NotationDefinitionRecordBase {
|
|
121
140
|
readonly id: string;
|
|
122
141
|
readonly key: string;
|
|
142
|
+
/**
|
|
143
|
+
* Symbols this entry's `formula` names but does not make cards of (D15).
|
|
144
|
+
* Carried on the record so the manifest can build the entry-scoped formula
|
|
145
|
+
* table; deliberately **not** a definition, a reference target, or an edge.
|
|
146
|
+
*/
|
|
147
|
+
readonly glosses: readonly NotationGlossEntry[];
|
|
123
148
|
readonly scope: NotationDefinitionScope;
|
|
124
149
|
readonly notation: string;
|
|
125
150
|
readonly label: string;
|
|
@@ -118,6 +118,24 @@ export const UNITS_VOCAB: ReadonlySet<string> = new Set([
|
|
|
118
118
|
'quoted',
|
|
119
119
|
'explicitly',
|
|
120
120
|
'calculations',
|
|
121
|
+
// motion / physical quantities (playground corpus)
|
|
122
|
+
'meter',
|
|
123
|
+
'meters',
|
|
124
|
+
'second',
|
|
125
|
+
'seconds',
|
|
126
|
+
'speed',
|
|
127
|
+
'velocity',
|
|
128
|
+
'acceleration',
|
|
129
|
+
'gravitational',
|
|
130
|
+
'launch',
|
|
131
|
+
'angle',
|
|
132
|
+
'degrees',
|
|
133
|
+
'horizontal',
|
|
134
|
+
'vertical',
|
|
135
|
+
'level',
|
|
136
|
+
'ground',
|
|
137
|
+
'squared',
|
|
138
|
+
'above',
|
|
121
139
|
]);
|
|
122
140
|
|
|
123
141
|
/** Lowercase word tokens of a `units` string (split on whitespace and hyphens). */
|