explico 0.12.0 → 0.13.1
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 +1137 -1
- package/README.md +91 -13
- package/dist/compiler/astro.d.ts +4 -3
- package/dist/compiler/astro.js +45 -15
- package/dist/compiler/cli.d.ts +26 -12
- package/dist/compiler/cli.js +96 -25
- package/dist/compiler/collections.d.ts +9 -1
- package/dist/compiler/collections.js +55 -16
- 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 +14 -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 +87 -6
- package/dist/compiler/manifest.js +174 -31
- package/dist/compiler/render-diagnostics.d.ts +65 -0
- package/dist/compiler/render-diagnostics.js +56 -0
- package/dist/compiler/resolution-report.d.ts +2 -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 +2 -2
- package/dist/reference/base-operators.mjs +2 -2
- 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/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-part-pages.d.ts +1 -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-math-scope.d.ts +1 -0
- package/dist/reference/lab-math-scope.js +13 -2
- 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 +34 -4
- package/dist/reference/page-notation.js +54 -2
- package/dist/reference/references.d.ts +11 -1
- package/dist/reference/references.js +6 -0
- package/dist/reference/registry.js +104 -10
- 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 +84 -20
- package/dist/reference/render-lab-math.d.ts +2 -0
- package/dist/reference/render-lab-math.js +17 -5
- 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 +191 -30
- package/dist/rules/in-effect.d.ts +1 -1
- package/dist/rules/rules.schema.json +79 -24
- 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 +32 -21
- package/docs/authoring/agents.md +38 -7
- package/docs/authoring/components.md +111 -6
- package/docs/authoring/migrating.md +707 -0
- package/docs/authoring/notation.md +281 -29
- package/docs/authoring/retired.md +1 -0
- package/docs/authoring/structure-rules.md +10 -41
- package/docs/authoring/structure.md +53 -2
- package/docs/authoring/visualizations.md +144 -18
- package/package.json +12 -7
- package/src/compiler/astro.ts +57 -17
- package/src/compiler/cli.ts +143 -35
- package/src/compiler/collections.ts +92 -19
- 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 +299 -28
- package/src/compiler/render-diagnostics.ts +111 -0
- package/src/components/HoverPanel.astro +204 -6
- package/src/components/PartOverview.astro +12 -9
- 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/figure/Figure.astro +11 -0
- package/src/components/figure/FloatCaption.astro +4 -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 +321 -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 +47 -9
- 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 +13 -21
- 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 +2 -2
- 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/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-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-math-scope.ts +15 -1
- package/src/reference/math-glyphs.mjs +851 -46
- package/src/reference/mixed-content.ts +11 -2
- package/src/reference/page-notation.ts +100 -3
- package/src/reference/references.ts +14 -0
- package/src/reference/registry.ts +154 -8
- 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 +84 -20
- package/src/reference/render-lab-math.ts +29 -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 +205 -29
- package/src/rules/settings.ts +10 -0
- package/src/session/PreferenceStore.ts +53 -0
- package/src/styles/global.css +291 -75
|
@@ -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,9 +172,53 @@ 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
|
|
|
179
|
+
/**
|
|
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
|
+
|
|
125
222
|
/**
|
|
126
223
|
* Extend one compiled page bundle — a lesson's, or a non-chapter page's
|
|
127
224
|
* (rule S10) — with only the definitions requested by components rendered on
|
|
@@ -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,23 @@ 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);
|
|
169
188
|
|
|
170
189
|
// A page's own `notation.local` entries are in its scope by construction:
|
|
171
190
|
// its math binds to one by shape — an operator entry `{}'` for `w'`, the
|
|
@@ -201,7 +220,6 @@ export function buildNotationRegistry(
|
|
|
201
220
|
|
|
202
221
|
bundles.push({
|
|
203
222
|
lessonId: lesson.lessonId,
|
|
204
|
-
status: lesson.status,
|
|
205
223
|
source: lesson.source,
|
|
206
224
|
bindings: [...bindingByKey]
|
|
207
225
|
.map(([key, definitionId]) => ({ key, definitionId }))
|
|
@@ -225,6 +243,28 @@ export function buildNotationRegistry(
|
|
|
225
243
|
}
|
|
226
244
|
}
|
|
227
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
|
+
|
|
228
268
|
for (const candidate of definitions.values()) {
|
|
229
269
|
if (!reachableDefinitions.has(candidate.id)) {
|
|
230
270
|
diagnostics.push({
|
|
@@ -295,6 +335,7 @@ function buildLessonIndex(
|
|
|
295
335
|
);
|
|
296
336
|
continue;
|
|
297
337
|
}
|
|
338
|
+
diagnostics.push(...malformedReferences(lesson));
|
|
298
339
|
const previous = lessons.get(lesson.lessonId);
|
|
299
340
|
if (previous !== undefined) {
|
|
300
341
|
diagnostics.push(
|
|
@@ -351,7 +392,26 @@ function buildDefinitionIndex(
|
|
|
351
392
|
diagnostics.push(
|
|
352
393
|
diagnostic({
|
|
353
394
|
code: 'invalid-key',
|
|
354
|
-
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(', ')}.`,
|
|
355
415
|
key: candidate.input.key,
|
|
356
416
|
lessonId:
|
|
357
417
|
candidate.kind === 'local' ? candidate.lesson.lessonId : undefined,
|
|
@@ -474,7 +534,7 @@ function validateReferenceKey(
|
|
|
474
534
|
diagnostics.push(
|
|
475
535
|
diagnostic({
|
|
476
536
|
code: 'invalid-key',
|
|
477
|
-
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)}`,
|
|
478
538
|
key: reference.key,
|
|
479
539
|
lessonId: owner.kind === 'local' ? owner.lesson.lessonId : undefined,
|
|
480
540
|
definitionId: owner.id,
|
|
@@ -493,7 +553,7 @@ function validatePageReferenceKey(
|
|
|
493
553
|
diagnostics.push(
|
|
494
554
|
diagnostic({
|
|
495
555
|
code: 'invalid-key',
|
|
496
|
-
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)}`,
|
|
497
557
|
key: reference.key,
|
|
498
558
|
lessonId: lesson.lessonId,
|
|
499
559
|
source: reference.source,
|
|
@@ -554,7 +614,6 @@ function toDefinitionRecord(
|
|
|
554
614
|
sources: input.sources,
|
|
555
615
|
seeAlso: input.seeAlso,
|
|
556
616
|
alignment: input.alignment,
|
|
557
|
-
status: input.status,
|
|
558
617
|
aiAssisted: input.aiAssisted,
|
|
559
618
|
body: input.body,
|
|
560
619
|
glosses: input.glosses,
|
|
@@ -562,6 +621,7 @@ function toDefinitionRecord(
|
|
|
562
621
|
resolvedReferences: candidate.resolvedReferences,
|
|
563
622
|
...(input.formula === undefined ? {} : { formula: input.formula }),
|
|
564
623
|
...(input.units === undefined ? {} : { units: input.units }),
|
|
624
|
+
...indexRecordFields(input),
|
|
565
625
|
};
|
|
566
626
|
return record;
|
|
567
627
|
}
|
|
@@ -579,16 +639,33 @@ function toDefinitionRecord(
|
|
|
579
639
|
sources: input.sources,
|
|
580
640
|
seeAlso: input.seeAlso,
|
|
581
641
|
alignment: input.alignment,
|
|
582
|
-
status: candidate.lesson.status,
|
|
583
642
|
glosses: input.glosses,
|
|
584
643
|
source: input.source,
|
|
585
644
|
resolvedReferences: candidate.resolvedReferences,
|
|
586
645
|
...(input.formula === undefined ? {} : { formula: input.formula }),
|
|
587
646
|
...(input.units === undefined ? {} : { units: input.units }),
|
|
647
|
+
...indexRecordFields(input),
|
|
588
648
|
};
|
|
589
649
|
return record;
|
|
590
650
|
}
|
|
591
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
|
+
|
|
592
669
|
function definitionFingerprint(candidate: DefinitionCandidate): string {
|
|
593
670
|
const references = sortedReferences(candidate.input.references).map(
|
|
594
671
|
(reference) => ({
|
|
@@ -623,7 +700,6 @@ function definitionFingerprint(candidate: DefinitionCandidate): string {
|
|
|
623
700
|
sources: [...input.sources].sort(),
|
|
624
701
|
seeAlso: [...input.seeAlso].sort(),
|
|
625
702
|
alignment,
|
|
626
|
-
status: input.status,
|
|
627
703
|
aiAssisted: input.aiAssisted,
|
|
628
704
|
body: normalizeText(input.body),
|
|
629
705
|
glosses: input.glosses,
|
|
@@ -774,6 +850,76 @@ function sortedSources(values: readonly SourceSpan[]): SourceSpan[] {
|
|
|
774
850
|
return [...values].sort(compareSourceSpans);
|
|
775
851
|
}
|
|
776
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
|
+
|
|
777
923
|
function diagnostic(
|
|
778
924
|
input: Omit<NotationDiagnostic, 'severity'>,
|
|
779
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
|
|
|
@@ -69,6 +69,11 @@ const HEADING_TAG = /^h([1-6])$/;
|
|
|
69
69
|
const LINK_ICON_PATH =
|
|
70
70
|
'm12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42';
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* A heading's text as a reader hears it. KaTeX writes each formula twice —
|
|
74
|
+
* MathML, whose `<annotation>` holds the LaTeX, and the `aria-hidden` glyphs —
|
|
75
|
+
* so a formula is read once, from its MathML, without the LaTeX.
|
|
76
|
+
*/
|
|
72
77
|
function headingText(node) {
|
|
73
78
|
let text = '';
|
|
74
79
|
const walk = (current) => {
|
|
@@ -77,10 +82,15 @@ function headingText(node) {
|
|
|
77
82
|
text += String(current.value ?? '');
|
|
78
83
|
return;
|
|
79
84
|
}
|
|
85
|
+
if (current.type === 'element') {
|
|
86
|
+
const classes = current.properties?.className;
|
|
87
|
+
if (current.tagName === 'annotation') return;
|
|
88
|
+
if (Array.isArray(classes) && classes.includes('katex-html')) return;
|
|
89
|
+
}
|
|
80
90
|
if (Array.isArray(current.children)) current.children.forEach(walk);
|
|
81
91
|
};
|
|
82
92
|
walk(node);
|
|
83
|
-
return text.replace(/\s+/g, ' ').trim();
|
|
93
|
+
return text.replace(/[\u200b\u2061-\u2064]/g, '').replace(/\s+/g, ' ').trim();
|
|
84
94
|
}
|
|
85
95
|
|
|
86
96
|
// A heading's marker, e.g. `## Some title [collapsed]`, stripped from the
|
|
Binary file
|