explico 0.11.1 → 0.13.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 +1292 -1
- package/README.md +91 -13
- package/dist/compiler/astro.d.ts +11 -4
- package/dist/compiler/astro.js +48 -15
- package/dist/compiler/cli.d.ts +27 -12
- package/dist/compiler/cli.js +96 -25
- package/dist/compiler/collections.d.ts +33 -2
- package/dist/compiler/collections.js +91 -19
- package/dist/compiler/content-schema.d.ts +31 -0
- package/dist/compiler/content-schema.js +82 -0
- package/dist/compiler/content-schemas.d.ts +422 -0
- package/dist/compiler/content-schemas.js +229 -0
- package/dist/compiler/course-config.d.ts +1 -1
- package/dist/compiler/course-config.js +30 -2
- package/dist/compiler/exports/combined-html.d.ts +97 -0
- package/dist/compiler/exports/combined-html.js +492 -0
- package/dist/compiler/exports/download-index.d.ts +9 -0
- package/dist/compiler/exports/download-index.js +33 -0
- package/dist/compiler/exports/export-chapters.d.ts +98 -0
- package/dist/compiler/exports/export-chapters.js +164 -0
- package/dist/compiler/exports/export-pages.d.ts +48 -0
- package/dist/compiler/exports/export-pages.js +22 -0
- package/dist/compiler/exports/html-tree.d.ts +22 -0
- package/dist/compiler/exports/html-tree.js +47 -0
- package/dist/compiler/exports/latex/capture.d.ts +106 -0
- package/dist/compiler/exports/latex/capture.js +435 -0
- package/dist/compiler/exports/latex/compile.d.ts +73 -0
- package/dist/compiler/exports/latex/compile.js +115 -0
- package/dist/compiler/exports/latex/document.d.ts +102 -0
- package/dist/compiler/exports/latex/document.js +349 -0
- package/dist/compiler/exports/latex/explico.cls +415 -0
- package/dist/compiler/exports/latex/numbering.d.ts +18 -0
- package/dist/compiler/exports/latex/numbering.js +57 -0
- package/dist/compiler/exports/latex/serialize.d.ts +112 -0
- package/dist/compiler/exports/latex/serialize.js +606 -0
- package/dist/compiler/exports/latex-export.d.ts +44 -0
- package/dist/compiler/exports/latex-export.js +169 -0
- package/dist/compiler/exports/markdown/serialize.d.ts +51 -0
- package/dist/compiler/exports/markdown/serialize.js +527 -0
- package/dist/compiler/exports/markdown-export.d.ts +39 -0
- package/dist/compiler/exports/markdown-export.js +240 -0
- package/dist/compiler/exports/standalone-html.d.ts +15 -5
- package/dist/compiler/exports/standalone-html.js +23 -51
- package/dist/compiler/exports/static-tree.d.ts +253 -0
- package/dist/compiler/exports/static-tree.js +625 -0
- package/dist/compiler/exports/zip.d.ts +16 -0
- package/dist/compiler/exports/zip.js +72 -0
- package/dist/compiler/lesson-derivation.d.ts +8 -2
- package/dist/compiler/lesson-derivation.js +6 -7
- package/dist/compiler/manifest.d.ts +115 -8
- package/dist/compiler/manifest.js +221 -39
- package/dist/compiler/render-diagnostics.d.ts +65 -0
- package/dist/compiler/render-diagnostics.js +56 -0
- package/dist/compiler/resolution-report.d.ts +3 -1
- package/dist/{course-config-Dsnri9Tk.d.ts → course-config-rRtME5UV.d.ts} +56 -13
- package/dist/curriculum/validation.d.ts +9 -3
- package/dist/curriculum/validation.js +35 -17
- package/dist/reference/base-abbreviations.d.mts +2 -2
- package/dist/reference/base-abbreviations.mjs +2 -2
- package/dist/reference/base-operators.d.mts +8 -2
- package/dist/reference/base-operators.mjs +8 -10
- package/dist/reference/base-roman-labels.d.mts +4 -3
- package/dist/reference/base-roman-labels.mjs +4 -3
- package/dist/reference/citation-format.d.mts +35 -0
- package/dist/reference/citation-format.mjs +74 -0
- package/dist/reference/consistency.d.ts +5 -9
- package/dist/reference/consistency.js +34 -38
- package/dist/reference/curriculum-alignment.js +3 -24
- package/dist/reference/derived-entries.d.ts +45 -0
- package/dist/reference/derived-entries.js +50 -0
- package/dist/reference/derived-notation.d.mts +125 -0
- package/dist/reference/derived-notation.mjs +121 -0
- package/dist/reference/diagram-fit.d.ts +45 -0
- package/dist/reference/diagram-fit.js +54 -0
- package/dist/reference/diagram-layout.d.ts +59 -10
- package/dist/reference/diagram-layout.js +85 -24
- package/dist/reference/diagrams-validate.d.ts +1 -1
- package/dist/reference/equations-validate.d.ts +1 -1
- package/dist/reference/equations-validate.js +26 -1
- package/dist/reference/equations.d.mts +27 -0
- package/dist/reference/equations.mjs +120 -22
- package/dist/reference/examples-validate.d.ts +1 -1
- package/dist/reference/figure-fit.d.ts +70 -0
- package/dist/reference/figure-fit.js +60 -0
- package/dist/reference/figure-pictures.d.ts +27 -0
- package/dist/reference/figure-pictures.js +49 -0
- package/dist/reference/figure-symbols.d.ts +32 -0
- package/dist/reference/figure-symbols.js +55 -0
- package/dist/reference/figures-validate.d.ts +1 -1
- package/dist/reference/gate-abbreviations.d.ts +1 -1
- package/dist/reference/gate-abbreviations.js +1 -1
- package/dist/reference/gate-course-rules.d.ts +4 -3
- package/dist/reference/gate-course-rules.js +9 -4
- package/dist/reference/gate-heading-length.d.ts +2 -0
- package/dist/reference/gate-heading-length.js +7 -5
- package/dist/reference/gate-heading-structure.js +26 -0
- package/dist/reference/gate-indices.d.ts +35 -0
- package/dist/reference/gate-indices.js +299 -0
- package/dist/reference/gate-math.d.ts +32 -2
- package/dist/reference/gate-math.js +68 -12
- package/dist/reference/gate-mdx-expression.d.ts +17 -0
- package/dist/reference/gate-mdx-expression.js +29 -0
- package/dist/reference/gate-non-chapter-pages.d.ts +39 -0
- package/dist/reference/gate-non-chapter-pages.js +156 -0
- package/dist/reference/gate-part-pages.d.ts +4 -2
- package/dist/reference/gate-part-pages.js +2 -1
- package/dist/reference/gate-prop-text.d.ts +28 -0
- package/dist/reference/gate-prop-text.js +106 -0
- package/dist/reference/gate-prose-notation.d.ts +3 -1
- package/dist/reference/gate-prose-notation.js +4 -2
- package/dist/reference/gate-retired-keys.d.ts +35 -0
- package/dist/reference/gate-retired-keys.js +58 -0
- package/dist/reference/gloss.d.ts +2 -0
- package/dist/reference/katex-measure.d.ts +17 -0
- package/dist/reference/katex-measure.js +223 -0
- package/dist/reference/keyed-float-validate.js +12 -0
- package/dist/reference/keyed-jsx-floats.d.mts +2 -2
- package/dist/reference/keyed-jsx-floats.mjs +11 -14
- package/dist/reference/lab-katex-validate.d.ts +43 -0
- package/dist/reference/lab-katex-validate.js +96 -0
- package/dist/reference/lab-math-scope.d.ts +1 -0
- package/dist/reference/lab-math-scope.js +13 -2
- package/dist/reference/lint-ignore.js +1 -1
- package/dist/reference/math-glyphs.d.mts +94 -2
- package/dist/reference/math-glyphs.mjs +851 -46
- package/dist/reference/mixed-content.d.ts +8 -3
- package/dist/reference/mixed-content.js +4 -2
- package/dist/reference/page-notation.d.ts +39 -7
- package/dist/reference/page-notation.js +62 -10
- package/dist/reference/raw-floats.mjs +37 -13
- package/dist/reference/references.d.ts +11 -1
- package/dist/reference/references.js +6 -0
- package/dist/reference/registry.js +115 -11
- package/dist/reference/rehype-fail-katex-errors.d.mts +9 -0
- package/dist/reference/rehype-fail-katex-errors.mjs +19 -0
- package/dist/reference/rehype-heading-anchors.mjs +11 -1
- package/dist/reference/remark-citation.mjs +0 -0
- package/dist/reference/remark-notation.mjs +275 -23
- package/dist/reference/render-lab-math.d.ts +13 -2
- package/dist/reference/render-lab-math.js +18 -6
- package/dist/reference/sections-validate.d.ts +28 -3
- package/dist/reference/sections-validate.js +31 -4
- package/dist/reference/sections.d.mts +24 -6
- package/dist/reference/sections.mjs +92 -17
- package/dist/reference/tables-validate.d.ts +1 -1
- package/dist/reference/tables-validate.js +24 -1
- package/dist/reference/text-measure.d.ts +10 -0
- package/dist/reference/text-measure.js +41 -0
- package/dist/reference/text-widths.d.ts +10 -0
- package/dist/reference/text-widths.js +1238 -0
- package/dist/reference/types.d.ts +64 -8
- package/dist/rules/catalog.js +223 -32
- package/dist/rules/in-effect.d.ts +1 -1
- package/dist/rules/rules.schema.json +100 -27
- package/dist/rules/settings.d.ts +1 -1
- package/dist/rules/settings.js +7 -0
- package/dist/session/PreferenceStore.d.ts +18 -1
- package/dist/session/PreferenceStore.js +29 -1
- package/docs/authoring/README.md +33 -21
- package/docs/authoring/agents.md +38 -7
- package/docs/authoring/components.md +115 -10
- package/docs/authoring/migrating.md +878 -0
- package/docs/authoring/notation.md +285 -30
- package/docs/authoring/retired.md +1 -0
- package/docs/authoring/structure-rules.md +84 -45
- package/docs/authoring/structure.md +67 -2
- package/docs/authoring/visualizations.md +167 -25
- package/package.json +12 -7
- package/src/compiler/astro.ts +72 -21
- package/src/compiler/cli.ts +143 -35
- package/src/compiler/collections.ts +158 -27
- package/src/compiler/content-schema.ts +108 -0
- package/src/compiler/content-schemas.ts +391 -0
- package/src/compiler/course-config.ts +86 -15
- package/src/compiler/exports/combined-html.ts +669 -0
- package/src/compiler/exports/download-index.ts +36 -0
- package/src/compiler/exports/export-chapters.ts +233 -0
- package/src/compiler/exports/export-pages.ts +44 -0
- package/src/compiler/exports/html-tree.ts +62 -0
- package/src/compiler/exports/latex/capture.ts +651 -0
- package/src/compiler/exports/latex/compile.ts +168 -0
- package/src/compiler/exports/latex/document.ts +479 -0
- package/src/compiler/exports/latex/explico.cls +415 -0
- package/src/compiler/exports/latex/numbering.ts +74 -0
- package/src/compiler/exports/latex/serialize.ts +787 -0
- package/src/compiler/exports/latex/texlive-packages.txt +76 -0
- package/src/compiler/exports/latex-export.ts +255 -0
- package/src/compiler/exports/markdown/serialize.ts +632 -0
- package/src/compiler/exports/markdown-export.ts +329 -0
- package/src/compiler/exports/standalone-html.ts +39 -70
- package/src/compiler/exports/static-tree.ts +944 -0
- package/src/compiler/exports/zip.ts +88 -0
- package/src/compiler/lesson-derivation.ts +13 -8
- package/src/compiler/manifest.ts +387 -40
- package/src/compiler/render-diagnostics.ts +111 -0
- package/src/components/CourseContents.astro +6 -0
- package/src/components/HoverPanel.astro +204 -6
- package/src/components/PartOverview.astro +18 -9
- package/src/components/abbreviation/AbbreviationGlossary.astro +6 -2
- package/src/components/abbreviation/AbbreviationLayer.astro +21 -12
- package/src/components/assessments/AssessmentRunner.tsx +7 -1
- package/src/components/assessments/AssessmentSet.astro +14 -1
- package/src/components/block-heading.ts +38 -0
- package/src/components/card-arrow.ts +18 -4
- package/src/components/card-carry.ts +406 -0
- package/src/components/card-drag.ts +1 -30
- package/src/components/card-fetch.ts +87 -0
- package/src/components/card-manager.ts +1222 -38
- package/src/components/card-memory.ts +5 -1
- package/src/components/card-placement.ts +211 -13
- package/src/components/card-side.ts +281 -31
- package/src/components/citation/CitationLayer.astro +45 -40
- package/src/components/diagram/Diagram.astro +123 -44
- package/src/components/disclosure/print-as-shown.ts +42 -6
- package/src/components/figure/Figure.astro +17 -1
- package/src/components/figure/FloatCaption.astro +4 -17
- package/src/components/figure/figure-state.ts +26 -17
- package/src/components/float-toolbar.ts +274 -0
- package/src/components/memo/Memo.astro +21 -10
- package/src/components/memo/ShortNote.astro +4 -0
- package/src/components/notation/NotationGlossary.astro +22 -17
- package/src/components/notation/NotationLayer.astro +300 -79
- package/src/components/notation/NotationMath.astro +8 -1
- package/src/components/notation/render-notation-math.ts +51 -5
- package/src/components/readable-text.ts +25 -0
- package/src/components/reference/FloatRefLayer.astro +162 -89
- package/src/components/starlight/HeadingAnchorEnhancer.astro +13 -8
- package/src/components/starlight/KeyedFloatEnhancer.astro +28 -9
- package/src/components/starlight/LayoutPageSidebar.astro +17 -3
- package/src/components/starlight/LayoutSocialIcons.astro +79 -29
- package/src/components/starlight/LessonExportActions.astro +384 -310
- package/src/components/starlight/LessonFooter.astro +66 -8
- package/src/components/starlight/PrintController.astro +28 -0
- package/src/components/starlight/TextSizeControl.astro +137 -0
- package/src/components/starlight/print-controller.ts +380 -0
- package/src/components/starlight/section-summary.ts +13 -5
- package/src/components/table/Table.astro +14 -22
- package/src/components/table/table-truncation.ts +15 -0
- package/src/content-config.ts +20 -312
- package/src/curriculum/validation.ts +72 -24
- package/src/reference/base-abbreviations.mjs +2 -2
- package/src/reference/base-operators.mjs +8 -10
- package/src/reference/base-roman-labels.mjs +4 -3
- package/src/reference/citation-format.mjs +74 -0
- package/src/reference/consistency.ts +47 -61
- package/src/reference/curriculum-alignment.ts +4 -41
- package/src/reference/derived-entries.ts +68 -0
- package/src/reference/derived-notation.mjs +121 -0
- package/src/reference/diagram-fit.ts +104 -0
- package/src/reference/diagram-layout.ts +182 -51
- package/src/reference/diagrams-validate.ts +2 -1
- package/src/reference/equations-validate.ts +41 -2
- package/src/reference/equations.mjs +120 -22
- package/src/reference/examples-validate.ts +1 -0
- package/src/reference/figure-fit.ts +139 -0
- package/src/reference/figure-pictures.ts +96 -0
- package/src/reference/figure-symbols.ts +117 -0
- package/src/reference/figures-validate.ts +2 -1
- package/src/reference/gate-abbreviations.ts +1 -1
- package/src/reference/gate-course-rules.ts +22 -7
- package/src/reference/gate-heading-length.ts +25 -7
- package/src/reference/gate-heading-structure.ts +40 -1
- package/src/reference/gate-indices.ts +470 -0
- package/src/reference/gate-math.ts +102 -13
- package/src/reference/gate-mdx-expression.ts +45 -0
- package/src/reference/gate-non-chapter-pages.ts +241 -0
- package/src/reference/gate-part-pages.ts +1 -1
- package/src/reference/gate-prop-text.ts +145 -0
- package/src/reference/gate-prose-notation.ts +19 -5
- package/src/reference/gate-retired-keys.ts +101 -0
- package/src/reference/gloss.ts +2 -0
- package/src/reference/katex-measure.ts +384 -0
- package/src/reference/keyed-float-validate.ts +22 -0
- package/src/reference/keyed-jsx-floats.mjs +11 -14
- package/src/reference/lab-katex-validate.ts +156 -0
- package/src/reference/lab-math-scope.ts +15 -1
- package/src/reference/lint-ignore.ts +4 -1
- package/src/reference/math-glyphs.mjs +851 -46
- package/src/reference/mixed-content.ts +11 -2
- package/src/reference/page-notation.ts +114 -14
- package/src/reference/raw-floats.mjs +37 -13
- package/src/reference/references.ts +14 -0
- package/src/reference/registry.ts +170 -9
- package/src/reference/rehype-fail-katex-errors.mjs +19 -0
- package/src/reference/rehype-heading-anchors.mjs +11 -1
- package/src/reference/remark-citation.mjs +0 -0
- package/src/reference/remark-notation.mjs +275 -23
- package/src/reference/render-lab-math.ts +40 -5
- package/src/reference/sections-validate.ts +67 -3
- package/src/reference/sections.mjs +92 -17
- package/src/reference/tables-validate.ts +40 -2
- package/src/reference/text-measure.ts +63 -0
- package/src/reference/text-widths.ts +133 -0
- package/src/reference/types.ts +75 -8
- package/src/rules/catalog.ts +241 -31
- package/src/rules/settings.ts +10 -0
- package/src/session/PreferenceStore.ts +53 -0
- package/src/styles/global.css +322 -83
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { resolveMathGlyphs } from './math-glyphs.mjs';
|
|
22
22
|
import type { ScopeEntry } from './gate-math';
|
|
23
|
+
import type { DerivedUse } from './types';
|
|
23
24
|
|
|
24
25
|
export type ContentSegment =
|
|
25
26
|
| { readonly type: 'text'; readonly value: string }
|
|
@@ -35,11 +36,17 @@ const MATH_SPAN =
|
|
|
35
36
|
|
|
36
37
|
/**
|
|
37
38
|
* Split `text` into ordered segments, resolving each math span's glyphs
|
|
38
|
-
* against `scope`. Never throws.
|
|
39
|
+
* against `scope`. Never throws. With `derive` (an assessment, which is page
|
|
40
|
+
* math), an indexed symbol takes its derived card (rule N23), and each one
|
|
41
|
+
* used is added to `derived`.
|
|
39
42
|
*/
|
|
40
43
|
export function splitMixedContent(
|
|
41
44
|
text: string,
|
|
42
45
|
scope: readonly ScopeEntry[],
|
|
46
|
+
options: {
|
|
47
|
+
readonly derive?: boolean;
|
|
48
|
+
readonly derived?: Map<string, DerivedUse>;
|
|
49
|
+
} = {},
|
|
43
50
|
): ContentSegment[] {
|
|
44
51
|
const segments: ContentSegment[] = [];
|
|
45
52
|
let cursor = 0;
|
|
@@ -53,7 +60,9 @@ export function splitMixedContent(
|
|
|
53
60
|
const raw = match[1] ?? match[2] ?? '';
|
|
54
61
|
let latex = raw;
|
|
55
62
|
try {
|
|
56
|
-
|
|
63
|
+
const result = resolveMathGlyphs(raw, [...scope], { derive: options.derive === true });
|
|
64
|
+
latex = result.latex;
|
|
65
|
+
for (const use of result.derived) options.derived?.set(use.key, use);
|
|
57
66
|
} catch {
|
|
58
67
|
latex = raw;
|
|
59
68
|
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
Manifest,
|
|
3
|
+
ManifestDerivedNotation,
|
|
4
|
+
ManifestLessonNotation,
|
|
5
|
+
} from '../compiler/manifest';
|
|
2
6
|
import {
|
|
3
7
|
formulaExplainKeys,
|
|
4
8
|
formulaGlyphScope,
|
|
5
9
|
type GlyphScopeEntry,
|
|
6
10
|
} from './gloss';
|
|
7
11
|
import { resolveReferenceId } from './references';
|
|
8
|
-
import
|
|
12
|
+
import { derivedNotationEntries } from './derived-entries';
|
|
13
|
+
import type { DerivedUse, NotationDefinitionRecord } from './types';
|
|
9
14
|
|
|
10
15
|
export interface PageNotationDependency {
|
|
11
16
|
readonly key: string;
|
|
@@ -47,6 +52,55 @@ export function registerPageNotationDependencies(
|
|
|
47
52
|
}
|
|
48
53
|
}
|
|
49
54
|
|
|
55
|
+
const derivedByLocals = new WeakMap<object, Map<string, DerivedUse>>();
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Record the derived cards (rule N23) a nested server component's math uses —
|
|
59
|
+
* a lab's or a diagram's — so the page ships them with its own, and their
|
|
60
|
+
* base and index entries, which a derived card's formula opens. Keyed by
|
|
61
|
+
* `Astro.locals`, like {@link registerPageNotationDependencies}.
|
|
62
|
+
*/
|
|
63
|
+
export function registerPageDerivedNotation(
|
|
64
|
+
locals: object,
|
|
65
|
+
uses: Iterable<DerivedUse>,
|
|
66
|
+
context: string,
|
|
67
|
+
): void {
|
|
68
|
+
let byKey = derivedByLocals.get(locals);
|
|
69
|
+
if (!byKey) {
|
|
70
|
+
byKey = new Map();
|
|
71
|
+
derivedByLocals.set(locals, byKey);
|
|
72
|
+
}
|
|
73
|
+
const entries: string[] = [];
|
|
74
|
+
for (const use of uses) {
|
|
75
|
+
byKey.set(use.key, use);
|
|
76
|
+
entries.push(use.base, use.index);
|
|
77
|
+
}
|
|
78
|
+
registerPageNotationDependencies(locals, entries, context);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The page a glyph table was built for, and whether rule N23 is on there. */
|
|
82
|
+
export interface ScopePage {
|
|
83
|
+
readonly locals: object;
|
|
84
|
+
readonly context: string;
|
|
85
|
+
readonly checkIndices: boolean;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const pageByScope = new WeakMap<object, ScopePage>();
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Tie a lab's glyph table to the page it renders on (`labMathScope` does), so
|
|
92
|
+
* `renderLabMath` can give its indexed symbols derived cards and register
|
|
93
|
+
* them, and knows whether to hold them to rule N23. A table a course copies
|
|
94
|
+
* loses the tie: its math then composes no derived card.
|
|
95
|
+
*/
|
|
96
|
+
export function bindScopeToPage(scope: object, page: ScopePage): void {
|
|
97
|
+
pageByScope.set(scope, page);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function pageOfScope(scope: object): ScopePage | undefined {
|
|
101
|
+
return pageByScope.get(scope);
|
|
102
|
+
}
|
|
103
|
+
|
|
50
104
|
export function pageNotationDependencies(
|
|
51
105
|
locals: object,
|
|
52
106
|
): PageNotationDependency[] {
|
|
@@ -103,7 +157,6 @@ function manifestDefinition(
|
|
|
103
157
|
notation: definition.notation,
|
|
104
158
|
label: definition.label,
|
|
105
159
|
summary: definition.summary,
|
|
106
|
-
editorialStatus: definition.status,
|
|
107
160
|
seeAlso: [...definition.seeAlso],
|
|
108
161
|
glosses: [...definition.glosses],
|
|
109
162
|
formulaScope: formulaGlyphScope({
|
|
@@ -119,26 +172,73 @@ function manifestDefinition(
|
|
|
119
172
|
...(definition.formula === undefined
|
|
120
173
|
? {}
|
|
121
174
|
: { formula: definition.formula }),
|
|
175
|
+
...(definition.range === undefined ? {} : { range: definition.range }),
|
|
122
176
|
};
|
|
123
177
|
}
|
|
124
178
|
|
|
125
179
|
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
180
|
+
* The derived cards (rule N23) a page uses: the ones its body and assessment
|
|
181
|
+
* math use, as the manifest composed them, and the ones a lab or a diagram on
|
|
182
|
+
* it registered while rendering (`locals`), composed here the same way. They
|
|
183
|
+
* travel with the page as hidden sources; their base and index entries are
|
|
184
|
+
* in its payload already.
|
|
185
|
+
*/
|
|
186
|
+
export function derivedNotationForPage(
|
|
187
|
+
manifest: Manifest,
|
|
188
|
+
pageId: string,
|
|
189
|
+
locals?: object,
|
|
190
|
+
): readonly ManifestDerivedNotation[] {
|
|
191
|
+
const page =
|
|
192
|
+
manifest.lessons.find((entry) => entry.id === pageId) ??
|
|
193
|
+
manifest.pages.find((entry) => entry.id === pageId);
|
|
194
|
+
const compiled = page?.notation.derived ?? [];
|
|
195
|
+
const registered = [...((locals && derivedByLocals.get(locals)?.values()) ?? [])].filter(
|
|
196
|
+
(use) => !compiled.some((entry) => entry.key === use.key),
|
|
197
|
+
);
|
|
198
|
+
if (registered.length === 0) return compiled;
|
|
199
|
+
|
|
200
|
+
const definitionsById = new Map(
|
|
201
|
+
manifest.notation.definitions.map((definition) => [definition.id, definition]),
|
|
202
|
+
);
|
|
203
|
+
const availableIds = new Set(definitionsById.keys());
|
|
204
|
+
const records = new Map<string, NotationDefinitionRecord>();
|
|
205
|
+
for (const use of registered) {
|
|
206
|
+
for (const key of [use.base, use.index]) {
|
|
207
|
+
const id = resolveReferenceId(key, { kind: 'page', lessonId: pageId }, availableIds);
|
|
208
|
+
const record = id === undefined ? undefined : definitionsById.get(id);
|
|
209
|
+
if (record) records.set(key, record);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const cardsByKey = new Map<string, GlyphScopeEntry>(
|
|
213
|
+
manifest.notation.definitions
|
|
214
|
+
.filter((definition) => definition.kind === 'shared')
|
|
215
|
+
.map((definition) => [definition.key, { key: definition.key, notation: definition.notation }]),
|
|
216
|
+
);
|
|
217
|
+
return [...compiled, ...derivedNotationEntries(registered, records, cardsByKey)].sort(
|
|
218
|
+
(left, right) => left.key.localeCompare(right.key),
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Extend one compiled page bundle — a lesson's, or a non-chapter page's
|
|
224
|
+
* (rule S10) — with only the definitions requested by components rendered on
|
|
225
|
+
* that page (plus those definitions' normal closure).
|
|
128
226
|
*/
|
|
129
227
|
export function notationDefinitionsForRenderedPage(
|
|
130
228
|
manifest: Manifest,
|
|
131
|
-
|
|
229
|
+
pageId: string,
|
|
132
230
|
dependencies: readonly PageNotationDependency[],
|
|
133
231
|
): ManifestLessonNotation[] {
|
|
134
|
-
const
|
|
135
|
-
|
|
232
|
+
const page =
|
|
233
|
+
manifest.lessons.find((entry) => entry.id === pageId) ??
|
|
234
|
+
manifest.pages.find((entry) => entry.id === pageId);
|
|
235
|
+
if (!page) return [];
|
|
136
236
|
|
|
137
237
|
const definitionsById = new Map(
|
|
138
238
|
manifest.notation.definitions.map((definition) => [definition.id, definition]),
|
|
139
239
|
);
|
|
140
240
|
const availableIds = new Set(definitionsById.keys());
|
|
141
|
-
const ids = new Set(
|
|
241
|
+
const ids = new Set(page.notation.definitionIds);
|
|
142
242
|
const contextsByKey = new Map<string, Set<string>>();
|
|
143
243
|
|
|
144
244
|
for (const dependency of dependencies) {
|
|
@@ -151,14 +251,14 @@ export function notationDefinitionsForRenderedPage(
|
|
|
151
251
|
|
|
152
252
|
const definitionId = resolveReferenceId(
|
|
153
253
|
dependency.key,
|
|
154
|
-
{ kind: 'page', lessonId },
|
|
254
|
+
{ kind: 'page', lessonId: pageId },
|
|
155
255
|
availableIds,
|
|
156
256
|
);
|
|
157
257
|
if (!definitionId) {
|
|
158
258
|
throw new Error(
|
|
159
|
-
`Notation payload error
|
|
259
|
+
`Notation payload error on page "${pageId}": ${dependency.context} ` +
|
|
160
260
|
`emitted data-notation-key="${dependency.key}", but that key is not ` +
|
|
161
|
-
`defined
|
|
261
|
+
`defined on this page or in the shared notation registry.`,
|
|
162
262
|
);
|
|
163
263
|
}
|
|
164
264
|
ids.add(definitionId);
|
|
@@ -202,7 +302,7 @@ export function notationDefinitionsForRenderedPage(
|
|
|
202
302
|
if (previous && previous !== id) {
|
|
203
303
|
const context = [...(contextsByKey.get(definition.key) ?? [])].join(', ');
|
|
204
304
|
throw new Error(
|
|
205
|
-
`Notation payload error
|
|
305
|
+
`Notation payload error on page "${pageId}": key "${definition.key}" ` +
|
|
206
306
|
`would have two meanings (${previous} and ${id})` +
|
|
207
307
|
(context ? ` after ${context} emitted it.` : '.'),
|
|
208
308
|
);
|
|
@@ -220,7 +320,7 @@ export function notationDefinitionsForRenderedPage(
|
|
|
220
320
|
}
|
|
221
321
|
|
|
222
322
|
const existingById = new Map(
|
|
223
|
-
|
|
323
|
+
page.notation.definitions.map((definition) => [definition.id, definition]),
|
|
224
324
|
);
|
|
225
325
|
return [...ids]
|
|
226
326
|
.sort()
|
|
@@ -12,11 +12,13 @@
|
|
|
12
12
|
* `keyed-jsx-floats.mjs`'s top-level-only scan entirely, so it never even
|
|
13
13
|
* reaches diagram numbering.
|
|
14
14
|
*
|
|
15
|
-
* A raw table
|
|
16
|
-
*
|
|
17
|
-
* `<
|
|
18
|
-
*
|
|
19
|
-
*
|
|
15
|
+
* A raw table counts only where wrapping it would number it: at the top level
|
|
16
|
+
* of the lesson body or in a `TRANSPARENT_COMPONENTS` body (`<Memo>`,
|
|
17
|
+
* `<ShortNote>`, `<Fold>`), exactly where `keyed-jsx-floats.mjs` numbers a
|
|
18
|
+
* `<Table>`. A raw table in `<Table>`/`<Figure>` is the intended, wrapped
|
|
19
|
+
* case; one in any other component (a worked example's `<CompactExample>`,
|
|
20
|
+
* an `<Aside>`) is illustrative, like an equation there, and a `<Table>`
|
|
21
|
+
* there would get no number — so neither is flagged.
|
|
20
22
|
*
|
|
21
23
|
* `scanRawFloats` (raw Markdown, for the manifest builder) and
|
|
22
24
|
* `collectRawFloats` (mdast, for `remark-notation.mjs`) MUST agree on counts
|
|
@@ -24,7 +26,11 @@
|
|
|
24
26
|
* `keyed-jsx-floats.mjs` holds `scanKeyedJsxFloats`/`collectKeyedJsxFloats` to.
|
|
25
27
|
*/
|
|
26
28
|
|
|
27
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
stripFencedCode,
|
|
31
|
+
stripJsxComponents,
|
|
32
|
+
TRANSPARENT_COMPONENTS,
|
|
33
|
+
} from './equations.mjs';
|
|
28
34
|
import { collectKeyedJsxFloats, scanKeyedJsxFloats } from './keyed-jsx-floats.mjs';
|
|
29
35
|
|
|
30
36
|
/** Blank `<Table>…</Table>` and `<Figure>…</Figure>` bodies — legitimately wrapped, not raw. */
|
|
@@ -67,9 +73,16 @@ function findDiagramTags(text) {
|
|
|
67
73
|
export function scanRawFloats(markdown) {
|
|
68
74
|
const codeStripped = stripFencedCode(String(markdown));
|
|
69
75
|
const withoutWrappedFloats = blankFloatWrappers(codeStripped);
|
|
76
|
+
// Every component body but a transparent one is blanked — `<Table>` and
|
|
77
|
+
// `<Figure>` among them — so only the tables a `<Table>` would number remain.
|
|
78
|
+
const numberableText = stripJsxComponents(
|
|
79
|
+
codeStripped,
|
|
80
|
+
undefined,
|
|
81
|
+
TRANSPARENT_COMPONENTS,
|
|
82
|
+
);
|
|
70
83
|
|
|
71
|
-
const markdownTableCount = findMarkdownTableStarts(
|
|
72
|
-
const htmlTableCount = findHtmlTableTags(
|
|
84
|
+
const markdownTableCount = findMarkdownTableStarts(numberableText).length;
|
|
85
|
+
const htmlTableCount = findHtmlTableTags(numberableText).length;
|
|
73
86
|
|
|
74
87
|
const diagramsOutsideWrappers = findDiagramTags(withoutWrappedFloats).length;
|
|
75
88
|
const visibleDiagrams = scanKeyedJsxFloats(markdown, { tagName: 'Diagram' });
|
|
@@ -95,7 +108,10 @@ export function collectRawFloats(tree) {
|
|
|
95
108
|
const htmlTableNodes = [];
|
|
96
109
|
const allDiagramNodes = [];
|
|
97
110
|
|
|
98
|
-
|
|
111
|
+
// `numberable`: a `<Table>` here would be numbered — the top level, or a
|
|
112
|
+
// transparent component's body. Any other MDX component (a capitalized
|
|
113
|
+
// name, the ones `stripJsxComponents` blanks) ends it for its subtree.
|
|
114
|
+
const walk = (node, numberable) => {
|
|
99
115
|
if (!node || typeof node !== 'object') return;
|
|
100
116
|
if (
|
|
101
117
|
node.type === 'mdxJsxFlowElement' &&
|
|
@@ -103,18 +119,26 @@ export function collectRawFloats(tree) {
|
|
|
103
119
|
) {
|
|
104
120
|
return; // legitimately wrapped — don't descend
|
|
105
121
|
}
|
|
106
|
-
if (node.type === 'table') markdownTableNodes.push(node);
|
|
107
|
-
if (
|
|
122
|
+
if (numberable && node.type === 'table') markdownTableNodes.push(node);
|
|
123
|
+
if (
|
|
124
|
+
numberable &&
|
|
125
|
+
node.type === 'html' &&
|
|
126
|
+
/<table\b/i.test(String(node.value ?? ''))
|
|
127
|
+
) {
|
|
108
128
|
htmlTableNodes.push(node);
|
|
109
129
|
}
|
|
110
130
|
if (node.type === 'mdxJsxFlowElement' && node.name === 'Diagram') {
|
|
111
131
|
allDiagramNodes.push(node);
|
|
112
132
|
}
|
|
133
|
+
const component =
|
|
134
|
+
(node.type === 'mdxJsxFlowElement' || node.type === 'mdxJsxTextElement') &&
|
|
135
|
+
/^[A-Z]/.test(String(node.name ?? '')) &&
|
|
136
|
+
!TRANSPARENT_COMPONENTS.includes(node.name);
|
|
113
137
|
if (Array.isArray(node.children)) {
|
|
114
|
-
for (const child of node.children) walk(child);
|
|
138
|
+
for (const child of node.children) walk(child, numberable && !component);
|
|
115
139
|
}
|
|
116
140
|
};
|
|
117
|
-
walk(tree);
|
|
141
|
+
walk(tree, true);
|
|
118
142
|
|
|
119
143
|
const visibleDiagrams = collectKeyedJsxFloats(tree, { tagName: 'Diagram' });
|
|
120
144
|
const visibleNodes = new Set(visibleDiagrams.map((diagram) => diagram.node));
|
|
@@ -10,6 +10,20 @@ export function isNotationKey(value: string): boolean {
|
|
|
10
10
|
return NOTATION_KEY_PATTERN.test(value);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* The `[[…]]` prefixes that name something other than a notation entry: an
|
|
15
|
+
* equation, a table, a figure, a diagram, a worked-example set, a knowledge
|
|
16
|
+
* check, a section, a memo. `remark-notation.mjs` and `remark-memo.mjs` route
|
|
17
|
+
* a reference by its prefix before notation keys are looked at, so a key
|
|
18
|
+
* that starts with one could never be reached.
|
|
19
|
+
*/
|
|
20
|
+
export const RESERVED_KEY_PREFIXES = ['eq-', 'tab-', 'fig-', 'dia-', 'ex-', 'check-', 'sec-', 'memo-'] as const;
|
|
21
|
+
|
|
22
|
+
/** The reserved prefix `key` starts with, if any. */
|
|
23
|
+
export function reservedKeyPrefix(key: string): string | undefined {
|
|
24
|
+
return RESERVED_KEY_PREFIXES.find((prefix) => key.startsWith(prefix));
|
|
25
|
+
}
|
|
26
|
+
|
|
13
27
|
export function sharedDefinitionId(key: string): string {
|
|
14
28
|
return `shared:${key}`;
|
|
15
29
|
}
|
|
@@ -3,9 +3,12 @@ import {
|
|
|
3
3
|
compareSourceSpans,
|
|
4
4
|
isNotationKey,
|
|
5
5
|
localDefinitionId,
|
|
6
|
+
RESERVED_KEY_PREFIXES,
|
|
7
|
+
reservedKeyPrefix,
|
|
6
8
|
resolveReferenceId,
|
|
7
9
|
sharedDefinitionId,
|
|
8
10
|
} from './references';
|
|
11
|
+
import { lineOf, proseOnly } from './gate-prose-notation';
|
|
9
12
|
import type {
|
|
10
13
|
LocalNotationDefinitionInput,
|
|
11
14
|
LocalNotationDefinitionRecord,
|
|
@@ -165,7 +168,35 @@ export function buildNotationRegistry(
|
|
|
165
168
|
),
|
|
166
169
|
);
|
|
167
170
|
}
|
|
168
|
-
|
|
171
|
+
// A formula's references bring their cards onto the page (above) but are
|
|
172
|
+
// no dependency: formulas may name each other, as `confidence-level`
|
|
173
|
+
// (γ = 1 − α) and `significance-level` (α = 1 − γ) do. A cycle through
|
|
174
|
+
// meanings and bodies is still an error.
|
|
175
|
+
const dependencyEdges = new Map<string, readonly string[]>();
|
|
176
|
+
for (const candidate of definitions.values()) {
|
|
177
|
+
dependencyEdges.set(
|
|
178
|
+
candidate.id,
|
|
179
|
+
uniqueSorted(
|
|
180
|
+
candidate.resolvedReferences
|
|
181
|
+
.filter((reference) => reference.kind !== 'formula')
|
|
182
|
+
.map((reference) => reference.definitionId)
|
|
183
|
+
.filter((definitionId) => definitionId !== candidate.id),
|
|
184
|
+
),
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
detectCycles(definitions, dependencyEdges, diagnostics);
|
|
188
|
+
|
|
189
|
+
// A page's own `notation.local` entries are in its scope by construction:
|
|
190
|
+
// its math binds to one by shape — an operator entry `{}'` for `w'`, the
|
|
191
|
+
// fix rule N22 itself suggests — even where no `[[key]]` or `\explain`
|
|
192
|
+
// names it. Each seeds its page's bundle, so the page renders its card.
|
|
193
|
+
const localIdsByLesson = new Map<string, string[]>();
|
|
194
|
+
for (const candidate of definitions.values()) {
|
|
195
|
+
if (candidate.kind !== 'local') continue;
|
|
196
|
+
const ids = localIdsByLesson.get(candidate.lesson.lessonId) ?? [];
|
|
197
|
+
ids.push(candidate.id);
|
|
198
|
+
localIdsByLesson.set(candidate.lesson.lessonId, ids);
|
|
199
|
+
}
|
|
169
200
|
|
|
170
201
|
const bundles: NotationPageBundle[] = [];
|
|
171
202
|
const backlinks: NotationBacklink[] = [];
|
|
@@ -176,7 +207,10 @@ export function buildNotationRegistry(
|
|
|
176
207
|
const directIds = new Set(
|
|
177
208
|
references.map((reference) => reference.definitionId),
|
|
178
209
|
);
|
|
179
|
-
const definitionIds = transitiveClosure(
|
|
210
|
+
const definitionIds = transitiveClosure(
|
|
211
|
+
new Set([...directIds, ...(localIdsByLesson.get(lesson.lessonId) ?? [])]),
|
|
212
|
+
edgeIndex,
|
|
213
|
+
);
|
|
180
214
|
for (const id of definitionIds) reachableDefinitions.add(id);
|
|
181
215
|
|
|
182
216
|
const bindingByKey = new Map<string, string>();
|
|
@@ -186,7 +220,6 @@ export function buildNotationRegistry(
|
|
|
186
220
|
|
|
187
221
|
bundles.push({
|
|
188
222
|
lessonId: lesson.lessonId,
|
|
189
|
-
status: lesson.status,
|
|
190
223
|
source: lesson.source,
|
|
191
224
|
bindings: [...bindingByKey]
|
|
192
225
|
.map(([key, definitionId]) => ({ key, definitionId }))
|
|
@@ -210,6 +243,28 @@ export function buildNotationRegistry(
|
|
|
210
243
|
}
|
|
211
244
|
}
|
|
212
245
|
|
|
246
|
+
// A page-local entry under a shared entry's key wins on its page, so the
|
|
247
|
+
// one key means two things in the course: the glossary's, and the page's.
|
|
248
|
+
for (const candidate of definitions.values()) {
|
|
249
|
+
if (candidate.kind !== 'local') continue;
|
|
250
|
+
const shared = definitions.get(sharedDefinitionId(candidate.input.key));
|
|
251
|
+
if (shared === undefined) continue;
|
|
252
|
+
diagnostics.push({
|
|
253
|
+
code: 'local-shadows-shared',
|
|
254
|
+
severity: 'warning',
|
|
255
|
+
message:
|
|
256
|
+
`${candidate.lesson.lessonId} defines notation.local ${JSON.stringify(candidate.input.key)}, ` +
|
|
257
|
+
`which is also a shared entry (${shared.input.source.file}): on this page ` +
|
|
258
|
+
`[[${candidate.input.key}]] means "${candidate.input.label}", everywhere else ` +
|
|
259
|
+
`"${shared.input.label}". Give the local entry a key of its own, or use the shared one.`,
|
|
260
|
+
key: candidate.input.key,
|
|
261
|
+
lessonId: candidate.lesson.lessonId,
|
|
262
|
+
definitionId: candidate.id,
|
|
263
|
+
source: candidate.input.source,
|
|
264
|
+
relatedSources: [shared.input.source],
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
213
268
|
for (const candidate of definitions.values()) {
|
|
214
269
|
if (!reachableDefinitions.has(candidate.id)) {
|
|
215
270
|
diagnostics.push({
|
|
@@ -280,6 +335,7 @@ function buildLessonIndex(
|
|
|
280
335
|
);
|
|
281
336
|
continue;
|
|
282
337
|
}
|
|
338
|
+
diagnostics.push(...malformedReferences(lesson));
|
|
283
339
|
const previous = lessons.get(lesson.lessonId);
|
|
284
340
|
if (previous !== undefined) {
|
|
285
341
|
diagnostics.push(
|
|
@@ -336,7 +392,26 @@ function buildDefinitionIndex(
|
|
|
336
392
|
diagnostics.push(
|
|
337
393
|
diagnostic({
|
|
338
394
|
code: 'invalid-key',
|
|
339
|
-
message: `${candidate.id} has invalid notation key ${JSON.stringify(candidate.input.key)}`,
|
|
395
|
+
message: `${candidate.id} has invalid notation key ${JSON.stringify(candidate.input.key)}${keyHint(candidate.input.key)}`,
|
|
396
|
+
key: candidate.input.key,
|
|
397
|
+
lessonId:
|
|
398
|
+
candidate.kind === 'local' ? candidate.lesson.lessonId : undefined,
|
|
399
|
+
definitionId: candidate.id,
|
|
400
|
+
source: candidate.input.source,
|
|
401
|
+
}),
|
|
402
|
+
);
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
const reserved = reservedKeyPrefix(candidate.input.key);
|
|
406
|
+
if (reserved !== undefined) {
|
|
407
|
+
diagnostics.push(
|
|
408
|
+
diagnostic({
|
|
409
|
+
code: 'invalid-key',
|
|
410
|
+
message:
|
|
411
|
+
`${candidate.id} has notation key ${JSON.stringify(candidate.input.key)}, which starts with ` +
|
|
412
|
+
`the reserved \`${reserved}\`: [[${candidate.input.key}]] is read as a reference to that kind ` +
|
|
413
|
+
`of float and never reaches this entry. Rename the key; none may start with ` +
|
|
414
|
+
`${RESERVED_KEY_PREFIXES.map((prefix) => `\`${prefix}\``).join(', ')}.`,
|
|
340
415
|
key: candidate.input.key,
|
|
341
416
|
lessonId:
|
|
342
417
|
candidate.kind === 'local' ? candidate.lesson.lessonId : undefined,
|
|
@@ -459,7 +534,7 @@ function validateReferenceKey(
|
|
|
459
534
|
diagnostics.push(
|
|
460
535
|
diagnostic({
|
|
461
536
|
code: 'invalid-key',
|
|
462
|
-
message: `${owner.id} references invalid notation key ${JSON.stringify(reference.key)}`,
|
|
537
|
+
message: `${owner.id} references invalid notation key ${JSON.stringify(reference.key)}${keyHint(reference.key)}`,
|
|
463
538
|
key: reference.key,
|
|
464
539
|
lessonId: owner.kind === 'local' ? owner.lesson.lessonId : undefined,
|
|
465
540
|
definitionId: owner.id,
|
|
@@ -478,7 +553,7 @@ function validatePageReferenceKey(
|
|
|
478
553
|
diagnostics.push(
|
|
479
554
|
diagnostic({
|
|
480
555
|
code: 'invalid-key',
|
|
481
|
-
message: `${lesson.lessonId} references invalid notation key ${JSON.stringify(reference.key)}`,
|
|
556
|
+
message: `${lesson.lessonId} references invalid notation key ${JSON.stringify(reference.key)}${keyHint(reference.key)}`,
|
|
482
557
|
key: reference.key,
|
|
483
558
|
lessonId: lesson.lessonId,
|
|
484
559
|
source: reference.source,
|
|
@@ -539,7 +614,6 @@ function toDefinitionRecord(
|
|
|
539
614
|
sources: input.sources,
|
|
540
615
|
seeAlso: input.seeAlso,
|
|
541
616
|
alignment: input.alignment,
|
|
542
|
-
status: input.status,
|
|
543
617
|
aiAssisted: input.aiAssisted,
|
|
544
618
|
body: input.body,
|
|
545
619
|
glosses: input.glosses,
|
|
@@ -547,6 +621,7 @@ function toDefinitionRecord(
|
|
|
547
621
|
resolvedReferences: candidate.resolvedReferences,
|
|
548
622
|
...(input.formula === undefined ? {} : { formula: input.formula }),
|
|
549
623
|
...(input.units === undefined ? {} : { units: input.units }),
|
|
624
|
+
...indexRecordFields(input),
|
|
550
625
|
};
|
|
551
626
|
return record;
|
|
552
627
|
}
|
|
@@ -564,16 +639,33 @@ function toDefinitionRecord(
|
|
|
564
639
|
sources: input.sources,
|
|
565
640
|
seeAlso: input.seeAlso,
|
|
566
641
|
alignment: input.alignment,
|
|
567
|
-
status: candidate.lesson.status,
|
|
568
642
|
glosses: input.glosses,
|
|
569
643
|
source: input.source,
|
|
570
644
|
resolvedReferences: candidate.resolvedReferences,
|
|
571
645
|
...(input.formula === undefined ? {} : { formula: input.formula }),
|
|
572
646
|
...(input.units === undefined ? {} : { units: input.units }),
|
|
647
|
+
...indexRecordFields(input),
|
|
573
648
|
};
|
|
574
649
|
return record;
|
|
575
650
|
}
|
|
576
651
|
|
|
652
|
+
/** Rule N23's optional fields, carried from the input to the record as is. */
|
|
653
|
+
function indexRecordFields(input: {
|
|
654
|
+
readonly reads?: string;
|
|
655
|
+
readonly range?: string;
|
|
656
|
+
readonly fixedIndex?: true;
|
|
657
|
+
readonly formulaTemplate?: string;
|
|
658
|
+
}): { reads?: string; range?: string; fixedIndex?: true; formulaTemplate?: string } {
|
|
659
|
+
return {
|
|
660
|
+
...(input.reads === undefined ? {} : { reads: input.reads }),
|
|
661
|
+
...(input.range === undefined ? {} : { range: input.range }),
|
|
662
|
+
...(input.fixedIndex === undefined ? {} : { fixedIndex: input.fixedIndex }),
|
|
663
|
+
...(input.formulaTemplate === undefined
|
|
664
|
+
? {}
|
|
665
|
+
: { formulaTemplate: input.formulaTemplate }),
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
|
|
577
669
|
function definitionFingerprint(candidate: DefinitionCandidate): string {
|
|
578
670
|
const references = sortedReferences(candidate.input.references).map(
|
|
579
671
|
(reference) => ({
|
|
@@ -608,7 +700,6 @@ function definitionFingerprint(candidate: DefinitionCandidate): string {
|
|
|
608
700
|
sources: [...input.sources].sort(),
|
|
609
701
|
seeAlso: [...input.seeAlso].sort(),
|
|
610
702
|
alignment,
|
|
611
|
-
status: input.status,
|
|
612
703
|
aiAssisted: input.aiAssisted,
|
|
613
704
|
body: normalizeText(input.body),
|
|
614
705
|
glosses: input.glosses,
|
|
@@ -759,6 +850,76 @@ function sortedSources(values: readonly SourceSpan[]): SourceSpan[] {
|
|
|
759
850
|
return [...values].sort(compareSourceSpans);
|
|
760
851
|
}
|
|
761
852
|
|
|
853
|
+
/**
|
|
854
|
+
* A `[[` that never closes. remark-notation fails the page on one ("Malformed
|
|
855
|
+
* notation reference near …"), but nothing before the render said so. Each
|
|
856
|
+
* `[[` in the prose (code, math and JSX blanked) must open a reference that
|
|
857
|
+
* closes on its line, with no `[` or `]` inside.
|
|
858
|
+
*/
|
|
859
|
+
function malformedReferences(lesson: NotationLessonInput): NotationDiagnostic[] {
|
|
860
|
+
if (!lesson.body?.includes('[[')) return [];
|
|
861
|
+
const prose = proseOnly(lesson.body, { keepReferences: true });
|
|
862
|
+
const found: NotationDiagnostic[] = [];
|
|
863
|
+
for (let at = prose.indexOf('[['); at !== -1; at = prose.indexOf('[[', at + 2)) {
|
|
864
|
+
if (lesson.body[at - 1] === '\\') continue;
|
|
865
|
+
const closed = /^\[\[[^[\]\n]*\]\]/.exec(prose.slice(at));
|
|
866
|
+
if (closed) {
|
|
867
|
+
at += closed[0].length - 2;
|
|
868
|
+
continue;
|
|
869
|
+
}
|
|
870
|
+
const near = lesson.body.slice(at, at + 40).split('\n')[0];
|
|
871
|
+
const line = lineOf(lesson.body, at) + (lesson.bodyLine ?? 1) - 1;
|
|
872
|
+
found.push(
|
|
873
|
+
diagnostic({
|
|
874
|
+
code: 'malformed-reference',
|
|
875
|
+
message:
|
|
876
|
+
`${lesson.lessonId} opens a reference that never closes, near ${JSON.stringify(near)}: ` +
|
|
877
|
+
'close it with ]], as [[semantic.key]], on the same line.',
|
|
878
|
+
lessonId: lesson.lessonId,
|
|
879
|
+
source: { ...lesson.source, line },
|
|
880
|
+
}),
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
return found;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/** What each reserved `[[…]]` prefix names. */
|
|
887
|
+
const FLOAT_KINDS: Readonly<Record<string, string>> = {
|
|
888
|
+
'eq-': 'an equation',
|
|
889
|
+
'tab-': 'a table',
|
|
890
|
+
'fig-': 'a figure',
|
|
891
|
+
'dia-': 'a diagram',
|
|
892
|
+
'ex-': 'a worked-example set',
|
|
893
|
+
'check-': 'a knowledge check',
|
|
894
|
+
'sec-': 'a section',
|
|
895
|
+
'memo-': 'a memo',
|
|
896
|
+
};
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* What a malformed key most likely meant, after its message: a wiki-style
|
|
900
|
+
* `[[key|text]]` link text, or anything else outside the key grammar.
|
|
901
|
+
*/
|
|
902
|
+
function keyHint(key: string): string {
|
|
903
|
+
// `[[dia-My_Diagram]]`: a float reference whose id breaks the grammar falls
|
|
904
|
+
// through to the notation keys, which it never was.
|
|
905
|
+
const reserved = reservedKeyPrefix(key);
|
|
906
|
+
if (reserved !== undefined) {
|
|
907
|
+
return (
|
|
908
|
+
`: [[${reserved}…]] names ${FLOAT_KINDS[reserved]}, whose id is lower-case words and digits ` +
|
|
909
|
+
`joined by "-"; ${JSON.stringify(key.slice(reserved.length))} is not one.`
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
const bar = key.indexOf('|');
|
|
913
|
+
if (bar > 0) {
|
|
914
|
+
const target = key.slice(0, bar).trim();
|
|
915
|
+
return (
|
|
916
|
+
`: a [[…]] reference takes no link text of its own — it reads as the entry's label. ` +
|
|
917
|
+
`Write [[${target}]], with the words around it.`
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
return ': a key is lower-case words and digits joined by "-" or "." (motion.launch-speed).';
|
|
921
|
+
}
|
|
922
|
+
|
|
762
923
|
function diagnostic(
|
|
763
924
|
input: Omit<NotationDiagnostic, 'severity'>,
|
|
764
925
|
): NotationDiagnostic {
|
|
@@ -31,13 +31,32 @@ function katexErrors(node, output = []) {
|
|
|
31
31
|
return output;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* KaTeX writes each formula twice: as MathML, whose `<annotation>` holds the
|
|
36
|
+
* LaTeX source, for assistive technology, and as the visible glyphs. Mark the
|
|
37
|
+
* MathML copy `data-pagefind-ignore`, so the site search indexes a formula
|
|
38
|
+
* once, as it reads, and never its LaTeX (`\explain{…}{…}`, `\dfrac`).
|
|
39
|
+
*/
|
|
40
|
+
export function markMathmlForSearch(node) {
|
|
41
|
+
if (!node || typeof node !== 'object') return;
|
|
42
|
+
const classes = node.properties?.className;
|
|
43
|
+
if (node.type === 'element' && Array.isArray(classes) && classes.includes('katex-mathml')) {
|
|
44
|
+
node.properties.dataPagefindIgnore = '';
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (Array.isArray(node.children)) node.children.forEach(markMathmlForSearch);
|
|
48
|
+
}
|
|
49
|
+
|
|
34
50
|
/**
|
|
35
51
|
* rehype-katex reports parse failures as non-fatal VFile messages and renders
|
|
36
52
|
* a `.katex-error` fallback. Turn those recovered failures into compiler
|
|
37
53
|
* errors so Astro cannot successfully build a lesson containing broken math.
|
|
54
|
+
* The pass every course runs right after KaTeX, it also keeps the LaTeX
|
|
55
|
+
* annotations out of the site search (`markMathmlForSearch`).
|
|
38
56
|
*/
|
|
39
57
|
export default function rehypeFailKatexErrors() {
|
|
40
58
|
return function failOnKatexErrors(tree, file) {
|
|
59
|
+
markMathmlForSearch(tree);
|
|
41
60
|
const errors = katexErrors(tree);
|
|
42
61
|
if (errors.length === 0) return;
|
|
43
62
|
|