explico 0.12.0 → 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 +1122 -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 +663 -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 +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 +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
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { stripEquationLabels } from "./equations.mjs";
|
|
2
|
+
import { resolveMathGlyphs, stripNotationMarkers } from "./math-glyphs.mjs";
|
|
3
|
+
import {
|
|
4
|
+
equationLinkFor,
|
|
5
|
+
mathSpans,
|
|
6
|
+
pageScope,
|
|
7
|
+
suspendedOperators
|
|
8
|
+
} from "./gate-math.js";
|
|
9
|
+
import { formulaGlyphScope } from "./gloss.js";
|
|
10
|
+
import { indexOfEntry } from "./math-glyphs.mjs";
|
|
11
|
+
const INDEX_FIX = {
|
|
12
|
+
body: (token, subscript) => `Give the indexed symbol its own entry (latex: ${token}), a family entry whose index runs (latex: x_k), or an index entry for the subscript (latex: "{}_${subscript}", label and meaning), introduced with [[key]] or in notation.local`,
|
|
13
|
+
assessment: (token, subscript) => `Give the indexed symbol its own entry (latex: ${token}), a family entry whose index runs (latex: x_k), or an index entry for the subscript (latex: "{}_${subscript}"), introduced in the lesson's prose with [[key]] or in its notation.local`,
|
|
14
|
+
formula: (token, subscript) => `In a formula, wrap the symbol in \\explain{key}{${token}} with an entry for the indexed symbol, declare it under glosses: (latex: ${token}), or wrap the subscript in an index entry's key (\\explain{key}{${subscript}})`
|
|
15
|
+
};
|
|
16
|
+
const DISPLAY_OPEN = /\$\$\s*$/;
|
|
17
|
+
const RANGE_RELATION = /(?:=|<|>|\\(?:in|le|leq|ge|geq|lt|gt|dots|ldots|cdots|to|subseteq|subset)(?![A-Za-z]))/;
|
|
18
|
+
function quadClauses(mathLine) {
|
|
19
|
+
return mathLine.split(/\\quad(?![A-Za-z])/).slice(1).map((clause) => stripNotationMarkers(clause));
|
|
20
|
+
}
|
|
21
|
+
function statesRange(clause, letter) {
|
|
22
|
+
const named = new RegExp(`(?<![A-Za-z\\\\])${letter}(?![A-Za-z])`);
|
|
23
|
+
return named.test(clause) && RANGE_RELATION.test(clause);
|
|
24
|
+
}
|
|
25
|
+
function mathLineAt(latex, offset) {
|
|
26
|
+
const before = latex.slice(0, offset).split(/\\\\/);
|
|
27
|
+
const after = latex.slice(offset).split(/\\\\/);
|
|
28
|
+
return `${before[before.length - 1] ?? ""}${after[0] ?? ""}`;
|
|
29
|
+
}
|
|
30
|
+
function message(lessonId, code, details, place = "body") {
|
|
31
|
+
switch (code) {
|
|
32
|
+
case "index-explained": {
|
|
33
|
+
const index = details.index ?? "";
|
|
34
|
+
const subscript = index.length === 1 ? index : `{${index}}`;
|
|
35
|
+
const what = details.base === "false" ? `has no card, and neither does its base (${details.parts})` : `has a subscript no card explains (${details.parts})`;
|
|
36
|
+
return `${lessonId}: ${JSON.stringify(details.token)} ${what}. ${INDEX_FIX[place](details.token ?? "", subscript)}; a letter in a subscript is an index, so a base-library i, e or d there needs a card (rule N23).`;
|
|
37
|
+
}
|
|
38
|
+
case "index-swallowed":
|
|
39
|
+
return `${lessonId}: \\explain{${details.key}}{${details.token}} wraps an indexed symbol in ${details.key}, whose latex (${details.notation}) has no index, so its card cannot explain ${JSON.stringify(details.index)}. Drop the wrap and give the subscript an index entry (latex: "{}_${details.index}"), or wrap it in an entry for the indexed symbol (rule N23).`;
|
|
40
|
+
case "index-range":
|
|
41
|
+
return `${lessonId}: the running index ${details.letter} (first in ${JSON.stringify(details.token)}) has no stated set. Give ${details.where} a range (range: "1, \\\\dots, n"), state it after a \\quad on the first display line that uses it (\\quad ${details.letter} = 1, \\dots, n), or mark the card fixedIndex: true if ${details.letter} is a label there, not a running index (rule N23).`;
|
|
42
|
+
case "derived-key-taken":
|
|
43
|
+
return `${lessonId}: ${JSON.stringify(details.token)} composes the derived card ${details.key}, but an authored entry has that key. Rename the entry: a key of the form <symbol>.sub.<index> is the build's (rule N23).`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function gateIndices(lessons, entries, extras = {}) {
|
|
47
|
+
const diagnostics = [];
|
|
48
|
+
const derivedByLesson = /* @__PURE__ */ new Map();
|
|
49
|
+
for (const lesson of lessons) {
|
|
50
|
+
const file = lesson.source.file;
|
|
51
|
+
const bodyOffset = (lesson.bodyLine ?? 1) - 1;
|
|
52
|
+
const scope = pageScope(lesson, entries);
|
|
53
|
+
const local = new Map(
|
|
54
|
+
lesson.localDefinitions.map((definition) => [definition.key, definition])
|
|
55
|
+
);
|
|
56
|
+
const factsOf = (key) => key === null ? void 0 : local.get(key) ?? entries.get(key);
|
|
57
|
+
const suspended = suspendedOperators(lesson);
|
|
58
|
+
const derived = /* @__PURE__ */ new Map();
|
|
59
|
+
const reported = /* @__PURE__ */ new Set();
|
|
60
|
+
const occurrences = [];
|
|
61
|
+
const spans = mathSpans(lesson.body).sort((left, right) => left.offset - right.offset);
|
|
62
|
+
for (const span of spans) {
|
|
63
|
+
const latex = stripEquationLabels(span.latex);
|
|
64
|
+
let result;
|
|
65
|
+
try {
|
|
66
|
+
result = resolveMathGlyphs(latex, scope, { suspended, derive: true });
|
|
67
|
+
} catch {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const line = span.line + bodyOffset;
|
|
71
|
+
const equation = equationLinkFor(lesson.lessonId, span.latex);
|
|
72
|
+
const at = {
|
|
73
|
+
context: "body",
|
|
74
|
+
severity: "error",
|
|
75
|
+
file,
|
|
76
|
+
line,
|
|
77
|
+
...equation === void 0 ? {} : { equation }
|
|
78
|
+
};
|
|
79
|
+
const display = DISPLAY_OPEN.test(lesson.body.slice(0, span.offset));
|
|
80
|
+
for (const use of result.derived) {
|
|
81
|
+
if (!derived.has(use.key)) derived.set(use.key, use);
|
|
82
|
+
if (factsOf(use.key) !== void 0 && !reported.has(`taken:${use.key}`)) {
|
|
83
|
+
reported.add(`taken:${use.key}`);
|
|
84
|
+
diagnostics.push({
|
|
85
|
+
...at,
|
|
86
|
+
code: "derived-key-taken",
|
|
87
|
+
token: use.key,
|
|
88
|
+
message: message(lesson.lessonId, "derived-key-taken", {
|
|
89
|
+
key: use.key,
|
|
90
|
+
token: use.key
|
|
91
|
+
})
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (const item of result.indices) {
|
|
96
|
+
pushFindings(item, at, lesson.lessonId, "body", factsOf, reported, diagnostics);
|
|
97
|
+
for (const running of item.running) {
|
|
98
|
+
if (running.key === null && running.cover === null) continue;
|
|
99
|
+
occurrences.push({
|
|
100
|
+
letter: running.letter,
|
|
101
|
+
line,
|
|
102
|
+
display,
|
|
103
|
+
mathLine: mathLineAt(latex, running.start),
|
|
104
|
+
token: item.token,
|
|
105
|
+
ownKey: running.key,
|
|
106
|
+
coverKey: running.cover
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const byLetter = /* @__PURE__ */ new Map();
|
|
112
|
+
for (const occurrence of occurrences) {
|
|
113
|
+
const list = byLetter.get(occurrence.letter) ?? [];
|
|
114
|
+
list.push(occurrence);
|
|
115
|
+
byLetter.set(occurrence.letter, list);
|
|
116
|
+
}
|
|
117
|
+
for (const [letter, list] of byLetter) {
|
|
118
|
+
const declared = list.some(
|
|
119
|
+
(occurrence) => [occurrence.ownKey, occurrence.coverKey].some((key) => {
|
|
120
|
+
const facts = factsOf(key);
|
|
121
|
+
return facts?.range !== void 0 || facts?.fixedIndex === true;
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
if (declared) continue;
|
|
125
|
+
const firstDisplay = list.find((occurrence) => occurrence.display);
|
|
126
|
+
if (firstDisplay && quadClauses(firstDisplay.mathLine).some((clause) => statesRange(clause, letter))) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const [first] = list;
|
|
130
|
+
if (!first) continue;
|
|
131
|
+
const card = first.ownKey ?? first.coverKey;
|
|
132
|
+
diagnostics.push({
|
|
133
|
+
context: "body",
|
|
134
|
+
severity: "error",
|
|
135
|
+
code: "index-range",
|
|
136
|
+
file,
|
|
137
|
+
line: first.line,
|
|
138
|
+
token: letter,
|
|
139
|
+
message: message(lesson.lessonId, "index-range", {
|
|
140
|
+
letter,
|
|
141
|
+
token: first.token,
|
|
142
|
+
where: card === null ? `the card of ${letter}` : `the card ${card}`
|
|
143
|
+
})
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
if (derived.size > 0) derivedByLesson.set(lesson.lessonId, [...derived.values()]);
|
|
147
|
+
for (const definition of lesson.localDefinitions) {
|
|
148
|
+
if (!definition.formula || indexOfEntry(definition.notation) !== void 0) continue;
|
|
149
|
+
checkFormula(
|
|
150
|
+
definition.formula,
|
|
151
|
+
dedupeRows([...scope, ...definition.glosses]),
|
|
152
|
+
`${lesson.lessonId}: notation.local ${definition.key} formula`,
|
|
153
|
+
file,
|
|
154
|
+
factsOf,
|
|
155
|
+
reported,
|
|
156
|
+
diagnostics
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const cards = new Map(entries);
|
|
161
|
+
for (const entry of entries.values()) {
|
|
162
|
+
if (!entry.formula || indexOfEntry(entry.notation) !== void 0) continue;
|
|
163
|
+
checkFormula(
|
|
164
|
+
entry.formula,
|
|
165
|
+
formulaGlyphScope({
|
|
166
|
+
key: entry.key,
|
|
167
|
+
notation: entry.notation,
|
|
168
|
+
formula: entry.formula,
|
|
169
|
+
glosses: entry.glosses ?? [],
|
|
170
|
+
cardsByKey: cards
|
|
171
|
+
}),
|
|
172
|
+
`notation ${entry.key} formula`,
|
|
173
|
+
entry.source?.file ?? `content/notation/${entry.key}.md`,
|
|
174
|
+
(key) => key === null ? void 0 : entries.get(key),
|
|
175
|
+
/* @__PURE__ */ new Set(),
|
|
176
|
+
diagnostics
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
const lessonById = new Map(lessons.map((lesson) => [lesson.lessonId, lesson]));
|
|
180
|
+
const assessmentById = new Map(
|
|
181
|
+
(extras.assessments ?? []).map((assessment) => [assessment.id, assessment])
|
|
182
|
+
);
|
|
183
|
+
for (const link of extras.lessonAssessments ?? []) {
|
|
184
|
+
const lesson = lessonById.get(link.lessonId);
|
|
185
|
+
if (!lesson) continue;
|
|
186
|
+
const scope = pageScope(lesson, entries);
|
|
187
|
+
const local = new Map(
|
|
188
|
+
lesson.localDefinitions.map((definition) => [definition.key, definition])
|
|
189
|
+
);
|
|
190
|
+
const factsOf = (key) => key === null ? void 0 : local.get(key) ?? entries.get(key);
|
|
191
|
+
for (const assessmentId of link.assessmentIds) {
|
|
192
|
+
const assessment = assessmentById.get(assessmentId);
|
|
193
|
+
const reported = /* @__PURE__ */ new Set();
|
|
194
|
+
for (const item of assessment?.items ?? []) {
|
|
195
|
+
for (const field of [
|
|
196
|
+
item.prompt,
|
|
197
|
+
item.explanation,
|
|
198
|
+
...(item.options ?? []).map((option) => option.label)
|
|
199
|
+
]) {
|
|
200
|
+
if (typeof field !== "string") continue;
|
|
201
|
+
for (const span of mathSpans(field)) {
|
|
202
|
+
let result;
|
|
203
|
+
try {
|
|
204
|
+
result = resolveMathGlyphs(span.latex, scope, { derive: true });
|
|
205
|
+
} catch {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const at = {
|
|
209
|
+
context: "assessment",
|
|
210
|
+
severity: "error",
|
|
211
|
+
file: `${assessmentId} (${item.id ?? "?"})`,
|
|
212
|
+
line: span.line
|
|
213
|
+
};
|
|
214
|
+
for (const indexed of result.indices) {
|
|
215
|
+
pushFindings(
|
|
216
|
+
indexed,
|
|
217
|
+
at,
|
|
218
|
+
`${link.lessonId}: assessment ${assessmentId}`,
|
|
219
|
+
"assessment",
|
|
220
|
+
factsOf,
|
|
221
|
+
reported,
|
|
222
|
+
diagnostics
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return { diagnostics, derivedByLesson };
|
|
231
|
+
}
|
|
232
|
+
function pushFindings(item, at, owner, place, factsOf, reported, diagnostics) {
|
|
233
|
+
const token = item.token.replace(/\s+/g, "");
|
|
234
|
+
if (item.how === "swallowed" && !reported.has(`swallowed:${token}`)) {
|
|
235
|
+
reported.add(`swallowed:${token}`);
|
|
236
|
+
diagnostics.push({
|
|
237
|
+
...at,
|
|
238
|
+
code: "index-swallowed",
|
|
239
|
+
token: item.token,
|
|
240
|
+
message: message(owner, "index-swallowed", {
|
|
241
|
+
key: item.key ?? "",
|
|
242
|
+
token: item.token,
|
|
243
|
+
notation: factsOf(item.key)?.notation ?? "",
|
|
244
|
+
index: item.index
|
|
245
|
+
})
|
|
246
|
+
});
|
|
247
|
+
} else if (item.how === "composed" && !item.explained && !reported.has(`explained:${token}`)) {
|
|
248
|
+
reported.add(`explained:${token}`);
|
|
249
|
+
diagnostics.push({
|
|
250
|
+
...at,
|
|
251
|
+
code: "index-explained",
|
|
252
|
+
token: item.token,
|
|
253
|
+
message: message(
|
|
254
|
+
owner,
|
|
255
|
+
"index-explained",
|
|
256
|
+
{
|
|
257
|
+
token: item.token,
|
|
258
|
+
index: item.index.replace(/^\{([\s\S]*)\}$/, "$1"),
|
|
259
|
+
parts: item.unexplained.map((part) => JSON.stringify(part)).join(", "),
|
|
260
|
+
base: String(item.baseExplained)
|
|
261
|
+
},
|
|
262
|
+
place
|
|
263
|
+
)
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function checkFormula(formula, scope, owner, file, factsOf, reported, diagnostics) {
|
|
268
|
+
let result;
|
|
269
|
+
try {
|
|
270
|
+
result = resolveMathGlyphs(formula, [...scope]);
|
|
271
|
+
} catch {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const at = { context: "formula", severity: "error", file };
|
|
275
|
+
for (const item of result.indices) {
|
|
276
|
+
pushFindings(item, at, owner, "formula", factsOf, reported, diagnostics);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function dedupeRows(rows) {
|
|
280
|
+
const seen = /* @__PURE__ */ new Set();
|
|
281
|
+
return rows.filter((row) => !seen.has(row.key) && Boolean(seen.add(row.key)));
|
|
282
|
+
}
|
|
283
|
+
function indexFindings(indices) {
|
|
284
|
+
return indices.flatMap((item) => {
|
|
285
|
+
if (item.how === "swallowed") {
|
|
286
|
+
return [
|
|
287
|
+
`${item.token}: \\explain{${item.key ?? ""}}{\u2026} names an entry whose latex has no index, so its card cannot explain ${JSON.stringify(item.index)}; give the subscript an index entry ({}_${item.index.length === 1 ? item.index : `{${item.index}}`}) and drop the wrap, or name an entry for the indexed symbol`
|
|
288
|
+
];
|
|
289
|
+
}
|
|
290
|
+
if (item.how !== "composed" || item.explained) return [];
|
|
291
|
+
return [
|
|
292
|
+
`${item.token}: no card explains ${item.unexplained.map((part) => JSON.stringify(part)).join(", ")}; give the indexed symbol its own entry, a family entry (x_k), or an index entry for the subscript ({}_0) in the page's notation`
|
|
293
|
+
];
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
export {
|
|
297
|
+
gateIndices,
|
|
298
|
+
indexFindings
|
|
299
|
+
};
|
|
@@ -58,6 +58,10 @@ interface AssessmentItemLike {
|
|
|
58
58
|
readonly id?: string;
|
|
59
59
|
readonly prompt?: string;
|
|
60
60
|
readonly explanation?: string;
|
|
61
|
+
/** A choice item's options; a label's math renders like the prompt's. */
|
|
62
|
+
readonly options?: readonly {
|
|
63
|
+
readonly label?: string;
|
|
64
|
+
}[];
|
|
61
65
|
}
|
|
62
66
|
interface AssessmentLike {
|
|
63
67
|
readonly id: string;
|
|
@@ -74,12 +78,27 @@ interface MathSpan {
|
|
|
74
78
|
/** Index of `latex` in the text it was cut from. */
|
|
75
79
|
readonly offset: number;
|
|
76
80
|
}
|
|
77
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* Every `$$…$$` then `$…$` span in `body`, outside code and quoted JSX props
|
|
83
|
+
* (plain text that never reaches KaTeX; invariant `prop-text`), with its
|
|
84
|
+
* position.
|
|
85
|
+
*/
|
|
78
86
|
declare function mathSpans(body: string): MathSpan[];
|
|
87
|
+
/**
|
|
88
|
+
* One row of a page's glyph table. `fixedIndex` (rule N23) tells the
|
|
89
|
+
* resolver that an entry's index is a fixed label, so it is no family.
|
|
90
|
+
*/
|
|
79
91
|
type ScopeEntry = {
|
|
80
92
|
key: string;
|
|
81
93
|
notation: string;
|
|
94
|
+
fixedIndex?: true;
|
|
82
95
|
};
|
|
96
|
+
/** A definition as a glyph-table row, `fixedIndex` kept. */
|
|
97
|
+
declare function scopeEntry(definition: {
|
|
98
|
+
readonly key: string;
|
|
99
|
+
readonly notation: string;
|
|
100
|
+
readonly fixedIndex?: true;
|
|
101
|
+
}): ScopeEntry;
|
|
83
102
|
/**
|
|
84
103
|
* A lesson's flat glyph table: its own `notation.local` entries plus every
|
|
85
104
|
* shared card it introduces with `[[key]]` / `\explain{key}`. Exported so a
|
|
@@ -96,6 +115,17 @@ declare function pageScope(lesson: NotationLessonInput, sharedByKey: ReadonlyMap
|
|
|
96
115
|
declare function equationLinkFor(lessonId: string, latex: string): EquationLink | undefined;
|
|
97
116
|
/** Decorations a lesson suspends (`notation.suspend`, rule N22), by name. */
|
|
98
117
|
declare function suspendedOperators(lesson: NotationLessonInput): string[];
|
|
118
|
+
/**
|
|
119
|
+
* The `$…$` pairs remark-math reads as math that hold an amount of money
|
|
120
|
+
* instead: `$5 to $6`, `$5–$10`, `$5, and $x`. remark-math pairs any two
|
|
121
|
+
* single `$` on a line, left to right; `mathSpans` follows Pandoc (no space
|
|
122
|
+
* before a closing `$`, no digit after it) and skips them, so such a pair was
|
|
123
|
+
* neither checked as math nor shown as money — the page printed "5 to 6", or
|
|
124
|
+
* the build failed on the letters of "and". A pair is money when its content
|
|
125
|
+
* starts with a digit and either ends in a space or runs straight into a
|
|
126
|
+
* digit after the closing `$`.
|
|
127
|
+
*/
|
|
128
|
+
declare function currencyDollars(body: string): MathSpan[];
|
|
99
129
|
/**
|
|
100
130
|
* Resolve every rendered-math context against the page glyph table. Returns one
|
|
101
131
|
* diagnostic per unresolved token; an empty array means the gate passed.
|
|
@@ -106,4 +136,4 @@ declare function gateContentMath(input: {
|
|
|
106
136
|
readonly lessonAssessments: readonly LessonAssessmentLink[];
|
|
107
137
|
}): MathGateDiagnostic[];
|
|
108
138
|
|
|
109
|
-
export { type EquationLink, type MathGateDiagnostic, type MathSpan, type ScopeEntry, equationLinkFor, gateContentMath, mathSpans, pageScope, suspendedOperators };
|
|
139
|
+
export { type AssessmentItemLike, type AssessmentLike, type EquationLink, type LessonAssessmentLink, type MathGateDiagnostic, type MathSpan, type ScopeEntry, currencyDollars, equationLinkFor, gateContentMath, mathSpans, pageScope, scopeEntry, suspendedOperators };
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
} from "./equations.mjs";
|
|
7
7
|
import { lintDelimiterSizes } from "./delimiter-size.js";
|
|
8
8
|
import { formulaGlyphScope, unknownFormulaScopeKeys } from "./gloss.js";
|
|
9
|
+
import { blankPropStrings } from "./gate-prop-text.js";
|
|
9
10
|
const SEVERITY = {
|
|
10
11
|
body: "error",
|
|
11
12
|
formula: "error",
|
|
@@ -27,7 +28,7 @@ function lineOf(text, index) {
|
|
|
27
28
|
return text.slice(0, index).split("\n").length;
|
|
28
29
|
}
|
|
29
30
|
function mathSpans(body) {
|
|
30
|
-
const searchable = stripCode(body);
|
|
31
|
+
const searchable = stripCode(blankPropStrings(body));
|
|
31
32
|
const spans = [];
|
|
32
33
|
for (const pattern of [DISPLAY_MATH, INLINE_MATH]) {
|
|
33
34
|
pattern.lastIndex = 0;
|
|
@@ -47,11 +48,15 @@ function mathSpans(body) {
|
|
|
47
48
|
}
|
|
48
49
|
return spans;
|
|
49
50
|
}
|
|
50
|
-
function
|
|
51
|
-
|
|
51
|
+
function scopeEntry(definition) {
|
|
52
|
+
return {
|
|
52
53
|
key: definition.key,
|
|
53
|
-
notation: definition.notation
|
|
54
|
-
|
|
54
|
+
notation: definition.notation,
|
|
55
|
+
...definition.fixedIndex ? { fixedIndex: true } : {}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function pageScope(lesson, sharedByKey) {
|
|
59
|
+
const local = lesson.localDefinitions.map(scopeEntry);
|
|
55
60
|
const localKeys = new Set(local.map((entry) => entry.key));
|
|
56
61
|
const referenced = /* @__PURE__ */ new Set();
|
|
57
62
|
for (const pattern of [TERM_KEY, EXPLAIN_KEY]) {
|
|
@@ -61,7 +66,7 @@ function pageScope(lesson, sharedByKey) {
|
|
|
61
66
|
for (const key of referenced) {
|
|
62
67
|
if (localKeys.has(key)) continue;
|
|
63
68
|
const shared = sharedByKey.get(key);
|
|
64
|
-
if (shared) scope.push(
|
|
69
|
+
if (shared) scope.push(scopeEntry(shared));
|
|
65
70
|
}
|
|
66
71
|
return scope;
|
|
67
72
|
}
|
|
@@ -77,7 +82,7 @@ function equationLinkFor(lessonId, latex) {
|
|
|
77
82
|
function suspendedOperators(lesson) {
|
|
78
83
|
return (lesson.suspended ?? []).map((item) => item.operator);
|
|
79
84
|
}
|
|
80
|
-
function resolveInto(latex, scope, context, file, line, column, fix, out, equation, suspended = []) {
|
|
85
|
+
function resolveInto(latex, scope, context, file, line, column, fix, out, equation, suspended = [], derive = false) {
|
|
81
86
|
const at = equation === void 0 ? {} : { equation };
|
|
82
87
|
for (const diagnostic of lintDelimiterSizes(latex)) {
|
|
83
88
|
const locatedLine = line === void 0 ? void 0 : line + diagnostic.line - 1;
|
|
@@ -96,7 +101,8 @@ function resolveInto(latex, scope, context, file, line, column, fix, out, equati
|
|
|
96
101
|
let result;
|
|
97
102
|
try {
|
|
98
103
|
result = resolveMathGlyphs(stripEquationLabels(latex), [...scope], {
|
|
99
|
-
suspended
|
|
104
|
+
suspended,
|
|
105
|
+
derive
|
|
100
106
|
});
|
|
101
107
|
} catch (error) {
|
|
102
108
|
out.push({
|
|
@@ -127,6 +133,7 @@ function resolveInto(latex, scope, context, file, line, column, fix, out, equati
|
|
|
127
133
|
continue;
|
|
128
134
|
}
|
|
129
135
|
const fontHint = "font" in item && typeof item.font === "string" ? ` ${token} is a different symbol from the plain letter: give it its own entry (latex: ${token}) or wrap it in \\explain{key}{${token}}.` : "";
|
|
136
|
+
const lenderHint = "lenders" in item && Array.isArray(item.lenders) && item.lenders.length > 0 ? ` ${item.lenders.join(", ")} ${item.lenders.length === 1 ? "has" : "have"} ${token} with a subscript, and an indexed entry explains its own instances only, never a bare ${token} (rule N4). If ${token} is a different object, give it its own entry (latex: ${token}); if it is shorthand for that entry, wrap it in \\explain{${item.lenders[0]}}{${token}}.` : "";
|
|
130
137
|
out.push({
|
|
131
138
|
context,
|
|
132
139
|
code: "unresolved-glyph",
|
|
@@ -135,10 +142,34 @@ function resolveInto(latex, scope, context, file, line, column, fix, out, equati
|
|
|
135
142
|
line,
|
|
136
143
|
token,
|
|
137
144
|
...at,
|
|
138
|
-
message: `Unresolved notation ${JSON.stringify(token)} in ${context} math.${fontHint} ${fix}`
|
|
145
|
+
message: `Unresolved notation ${JSON.stringify(token)} in ${context} math.${fontHint}${lenderHint} ${fix}`
|
|
139
146
|
});
|
|
140
147
|
}
|
|
141
148
|
}
|
|
149
|
+
function currencyDollars(body) {
|
|
150
|
+
const searchable = stripCode(blankPropStrings(body)).replace(
|
|
151
|
+
DISPLAY_MATH,
|
|
152
|
+
(block) => block.replace(/[^\n]/g, " ")
|
|
153
|
+
);
|
|
154
|
+
const spans = [];
|
|
155
|
+
let lineStart = 0;
|
|
156
|
+
searchable.split("\n").forEach((text, index) => {
|
|
157
|
+
const dollars = [];
|
|
158
|
+
for (let at = 0; at < text.length; at += 1) {
|
|
159
|
+
if (text[at] !== "$" || text[at - 1] === "\\" || text[at - 1] === "$" || text[at + 1] === "$") continue;
|
|
160
|
+
dollars.push(at);
|
|
161
|
+
}
|
|
162
|
+
for (let pair = 0; pair + 1 < dollars.length; pair += 2) {
|
|
163
|
+
const open = dollars[pair];
|
|
164
|
+
const close = dollars[pair + 1];
|
|
165
|
+
const latex = text.slice(open + 1, close);
|
|
166
|
+
if (!/^\d/.test(latex) || !(/\s$/.test(latex) || /^\d/.test(text.slice(close + 1)))) continue;
|
|
167
|
+
spans.push({ latex, line: index + 1, column: open + 2, offset: lineStart + open + 1 });
|
|
168
|
+
}
|
|
169
|
+
lineStart += text.length + 1;
|
|
170
|
+
});
|
|
171
|
+
return spans;
|
|
172
|
+
}
|
|
142
173
|
const BODY_FIX = "Introduce the symbol with [[key]], define it in notation.local, or wrap it in \\explain{key}{latex}.";
|
|
143
174
|
function dedupeScope(scope) {
|
|
144
175
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -215,9 +246,24 @@ function gateContentMath(input) {
|
|
|
215
246
|
BODY_FIX,
|
|
216
247
|
diagnostics,
|
|
217
248
|
equationLinkFor(lesson.lessonId, span.latex),
|
|
218
|
-
suspended
|
|
249
|
+
suspended,
|
|
250
|
+
// Derived cards (rule N23) are lesson-body math, like the render path.
|
|
251
|
+
true
|
|
219
252
|
);
|
|
220
253
|
}
|
|
254
|
+
for (const span of currencyDollars(lesson.body)) {
|
|
255
|
+
const shown = span.latex.replace(/\s+$/, "");
|
|
256
|
+
diagnostics.push({
|
|
257
|
+
context: "body",
|
|
258
|
+
code: "currency-dollar",
|
|
259
|
+
severity: SEVERITY.body,
|
|
260
|
+
file,
|
|
261
|
+
line: span.line + bodyOffset,
|
|
262
|
+
column: span.column,
|
|
263
|
+
token: `$${span.latex}$`,
|
|
264
|
+
message: `"$${span.latex}$" reads as math: remark-math pairs any two dollars on a line, so the page shows "${shown}" without them. Write a currency dollar as \\$ (\\$5 to \\$6), or the amount in words.`
|
|
265
|
+
});
|
|
266
|
+
}
|
|
221
267
|
for (const definition of lesson.localDefinitions) {
|
|
222
268
|
if (!definition.formula) continue;
|
|
223
269
|
resolveInto(
|
|
@@ -272,7 +318,11 @@ function gateContentMath(input) {
|
|
|
272
318
|
const assessment = assessmentById.get(assessmentId);
|
|
273
319
|
if (!assessment?.items) continue;
|
|
274
320
|
for (const item of assessment.items) {
|
|
275
|
-
for (const field of [
|
|
321
|
+
for (const field of [
|
|
322
|
+
item.prompt,
|
|
323
|
+
item.explanation,
|
|
324
|
+
...(item.options ?? []).map((option) => option.label)
|
|
325
|
+
]) {
|
|
276
326
|
if (typeof field !== "string") continue;
|
|
277
327
|
for (const span of mathSpans(field)) {
|
|
278
328
|
resolveInto(
|
|
@@ -283,7 +333,11 @@ function gateContentMath(input) {
|
|
|
283
333
|
span.line,
|
|
284
334
|
span.column,
|
|
285
335
|
ASSESSMENT_FIX,
|
|
286
|
-
diagnostics
|
|
336
|
+
diagnostics,
|
|
337
|
+
void 0,
|
|
338
|
+
[],
|
|
339
|
+
// Page math, rendered with derived cards (rule N23).
|
|
340
|
+
true
|
|
287
341
|
);
|
|
288
342
|
}
|
|
289
343
|
}
|
|
@@ -293,9 +347,11 @@ function gateContentMath(input) {
|
|
|
293
347
|
return diagnostics;
|
|
294
348
|
}
|
|
295
349
|
export {
|
|
350
|
+
currencyDollars,
|
|
296
351
|
equationLinkFor,
|
|
297
352
|
gateContentMath,
|
|
298
353
|
mathSpans,
|
|
299
354
|
pageScope,
|
|
355
|
+
scopeEntry,
|
|
300
356
|
suspendedOperators
|
|
301
357
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { MathGateDiagnostic } from './gate-math.js';
|
|
2
|
+
import { NotationLessonInput } from './types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The MDX-expression gate (invariant `prop-text`, with the prop gate). MDX
|
|
6
|
+
* reads `{…}` in a lesson's text as JavaScript: `The set {a} has one element` renders nothing
|
|
7
|
+
* and fails the build with "a is not defined", in a compiled chunk, with no
|
|
8
|
+
* file or line. `validate:content` and `content check` parse no expression, so
|
|
9
|
+
* the build was the first to notice. This gate finds a brace pair in the prose
|
|
10
|
+
* (code, math, JSX tags, comments, references and import/export lines blanked;
|
|
11
|
+
* a directive's attributes, `:::note{icon=""}`, kept out) and says what to
|
|
12
|
+
* write instead. See docs/authoring/components.md#braces-in-prose.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
declare function gateMdxExpressions(lessons: readonly NotationLessonInput[]): MathGateDiagnostic[];
|
|
16
|
+
|
|
17
|
+
export { gateMdxExpressions };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { lessonLine, proseOnly } from "./gate-prose-notation.js";
|
|
2
|
+
const EXPORT_LINE = /^[ \t]*export\b[^\n]*$/gm;
|
|
3
|
+
const DIRECTIVE = /:{1,3}[A-Za-z][\w-]*(?:\[[^\]\n]*\])?\{[^{}\n]*\}/g;
|
|
4
|
+
function gateMdxExpressions(lessons) {
|
|
5
|
+
const blank = (match) => match.replace(/[^\n]/g, " ");
|
|
6
|
+
const diagnostics = [];
|
|
7
|
+
for (const lesson of lessons) {
|
|
8
|
+
if (!lesson.body.includes("{")) continue;
|
|
9
|
+
const prose = proseOnly(lesson.body).replace(EXPORT_LINE, blank).replace(DIRECTIVE, blank);
|
|
10
|
+
for (const match of prose.matchAll(/\{([^{}\n]*)\}/g)) {
|
|
11
|
+
const index = match.index ?? 0;
|
|
12
|
+
if (lesson.body[index - 1] === "\\") continue;
|
|
13
|
+
const inner = lesson.body.slice(index + 1, index + match[0].length - 1);
|
|
14
|
+
diagnostics.push({
|
|
15
|
+
context: "prose",
|
|
16
|
+
code: "mdx-expression",
|
|
17
|
+
severity: "error",
|
|
18
|
+
file: lesson.source.file,
|
|
19
|
+
line: lessonLine(lesson, index),
|
|
20
|
+
token: `{${inner}}`,
|
|
21
|
+
message: `{${inner}} in the prose is a JavaScript expression to MDX, and the build fails on it ("${inner.trim() || "\u2026"} is not defined"). Write \\{${inner}\\} for braces in the text, or $\\{${inner}\\}$ for a set.`
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return diagnostics;
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
gateMdxExpressions
|
|
29
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { CurriculumIssue } from '../curriculum/validation.js';
|
|
2
|
+
import { ContentRecord } from './gate-retired-keys.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The prop-text gate (invariant `prop-text`). A component's quoted prop —
|
|
6
|
+
* `caption="…"`, `short="…"`, `title="…"`, `label="…"` — reaches the page as
|
|
7
|
+
* plain text: KaTeX, the reference plugin and the citation plugin never run
|
|
8
|
+
* inside one. A `$…$`, `[[…]]` or `[@…]` there would print exactly as typed,
|
|
9
|
+
* dollars and brackets included, so it fails the build at its line with what
|
|
10
|
+
* to write instead. See docs/authoring/components.md#props-are-plain-text.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** One quoted prop of a JSX opening tag: `name="value"`. */
|
|
14
|
+
interface PropString {
|
|
15
|
+
readonly tag: string;
|
|
16
|
+
readonly name: string;
|
|
17
|
+
readonly value: string;
|
|
18
|
+
/** Index of `value` in the text it was read from. */
|
|
19
|
+
readonly offset: number;
|
|
20
|
+
}
|
|
21
|
+
/** Every quoted prop of every JSX opening tag in `text`, outside code, comments and frontmatter. */
|
|
22
|
+
declare function propStrings(text: string): PropString[];
|
|
23
|
+
/** `text` with every quoted JSX prop value blanked: the scans for math and prose skip what never renders as either. */
|
|
24
|
+
declare function blankPropStrings(text: string): string;
|
|
25
|
+
/** One `markup-in-prop` issue per quoted prop of a page's JSX that holds math, a reference or a citation. */
|
|
26
|
+
declare function findMarkupInProps(records: readonly ContentRecord[]): CurriculumIssue[];
|
|
27
|
+
|
|
28
|
+
export { type PropString, blankPropStrings, findMarkupInProps, propStrings };
|