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,106 @@
|
|
|
1
|
+
const blank = (text) => text.replace(/[^\n]/g, " ");
|
|
2
|
+
function blankNonJsx(text) {
|
|
3
|
+
return text.replace(/^---\r?\n[\s\S]*?\r?\n---(?=\r?\n|$)/, blank).replace(/\{\/\*[\s\S]*?\*\/\}/g, blank).replace(/```[\s\S]*?```/g, blank).replace(/(^|[^`])`[^`\n]*`/g, (match, lead) => lead + blank(match.slice(lead.length)));
|
|
4
|
+
}
|
|
5
|
+
function readTag(text, start) {
|
|
6
|
+
let quote;
|
|
7
|
+
let depth = 0;
|
|
8
|
+
let props = "";
|
|
9
|
+
for (let index = start; index < text.length; index += 1) {
|
|
10
|
+
const char = text[index];
|
|
11
|
+
const kept = depth === 0 || char === "\n" ? char : " ";
|
|
12
|
+
if (quote !== void 0) {
|
|
13
|
+
if (char === quote) quote = void 0;
|
|
14
|
+
props += kept;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (char === '"' || char === "'" || char === "`" && depth > 0) {
|
|
18
|
+
quote = char;
|
|
19
|
+
} else if (char === "{") {
|
|
20
|
+
depth += 1;
|
|
21
|
+
} else if (char === "}") {
|
|
22
|
+
depth = Math.max(0, depth - 1);
|
|
23
|
+
props += " ";
|
|
24
|
+
continue;
|
|
25
|
+
} else if (char === ">" && depth === 0) {
|
|
26
|
+
return { end: index + 1, props: props + char };
|
|
27
|
+
}
|
|
28
|
+
props += depth === 0 || char === "\n" ? char : " ";
|
|
29
|
+
}
|
|
30
|
+
return void 0;
|
|
31
|
+
}
|
|
32
|
+
function propStrings(text) {
|
|
33
|
+
const searchable = blankNonJsx(text);
|
|
34
|
+
const found = [];
|
|
35
|
+
const opening = /<([A-Za-z][\w.:-]*)/g;
|
|
36
|
+
let match;
|
|
37
|
+
while (match = opening.exec(searchable)) {
|
|
38
|
+
const tag = readTag(searchable, match.index);
|
|
39
|
+
if (tag === void 0) break;
|
|
40
|
+
for (const prop of tag.props.matchAll(/([A-Za-z_][\w:.-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/g)) {
|
|
41
|
+
const value = prop[2] ?? prop[3] ?? "";
|
|
42
|
+
const valueAt = match.index + (prop.index ?? 0) + prop[0].length - value.length - 1;
|
|
43
|
+
found.push({ tag: match[1], name: prop[1], value: text.slice(valueAt, valueAt + value.length), offset: valueAt });
|
|
44
|
+
}
|
|
45
|
+
opening.lastIndex = tag.end;
|
|
46
|
+
}
|
|
47
|
+
return found;
|
|
48
|
+
}
|
|
49
|
+
function blankPropStrings(text) {
|
|
50
|
+
let out = text;
|
|
51
|
+
for (const { value, offset } of propStrings(text)) {
|
|
52
|
+
out = out.slice(0, offset) + blank(value) + out.slice(offset + value.length);
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
const INLINE_MATH = /(?<![\\$])\$(?!\s)([^$\n]+?)(?<![\\\s])\$(?!\d)/;
|
|
57
|
+
const REFERENCE = /\[\[[^\]\n]*\]\]/;
|
|
58
|
+
const CITATION = /\[@[^\]\n]*\]/;
|
|
59
|
+
function markupIn(value) {
|
|
60
|
+
const math = INLINE_MATH.exec(value);
|
|
61
|
+
if (math) {
|
|
62
|
+
return {
|
|
63
|
+
found: `math (\`${math[0]}\`)`,
|
|
64
|
+
advice: 'Write the symbol in Unicode or words ("v\u2080", "the launch speed"); the prose next to it typesets the symbol.'
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const reference = REFERENCE.exec(value);
|
|
68
|
+
if (reference) {
|
|
69
|
+
return {
|
|
70
|
+
found: `a reference (\`${reference[0]}\`)`,
|
|
71
|
+
advice: "Name the target in words, and reference it from the prose next to the component."
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const citation = CITATION.exec(value);
|
|
75
|
+
if (citation) {
|
|
76
|
+
return {
|
|
77
|
+
found: `a citation (\`${citation[0]}\`)`,
|
|
78
|
+
advice: "Cite the source in the prose next to the component."
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return void 0;
|
|
82
|
+
}
|
|
83
|
+
function findMarkupInProps(records) {
|
|
84
|
+
const issues = [];
|
|
85
|
+
for (const { file, text } of records) {
|
|
86
|
+
const path = file.replace(/\\/g, "/");
|
|
87
|
+
if (!path.startsWith("content/docs/") || !path.endsWith(".mdx")) continue;
|
|
88
|
+
for (const prop of propStrings(text)) {
|
|
89
|
+
const markup = markupIn(prop.value);
|
|
90
|
+
if (markup === void 0) continue;
|
|
91
|
+
const line = text.slice(0, prop.offset).split("\n").length;
|
|
92
|
+
issues.push({
|
|
93
|
+
kind: "markup-in-prop",
|
|
94
|
+
message: `${file}:${line}: <${prop.tag}> \`${prop.name}\` holds ${markup.found}, but a prop is plain text: it would print as typed. ${markup.advice}`,
|
|
95
|
+
file,
|
|
96
|
+
line
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return issues;
|
|
101
|
+
}
|
|
102
|
+
export {
|
|
103
|
+
blankPropStrings,
|
|
104
|
+
findMarkupInProps,
|
|
105
|
+
propStrings
|
|
106
|
+
};
|
|
@@ -34,10 +34,12 @@ interface AssessmentLike {
|
|
|
34
34
|
/**
|
|
35
35
|
* `keepMath` leaves `$…$` / `$$…$$` in place (everything else still blanked,
|
|
36
36
|
* offsets unchanged) — for a check that needs to see what a math span sits
|
|
37
|
-
* next to, such as `$x = 3$ meters`.
|
|
37
|
+
* next to, such as `$x = 3$ meters`. `keepReferences` leaves `[[…]]` in
|
|
38
|
+
* place, for the check that a reference is well formed.
|
|
38
39
|
*/
|
|
39
40
|
declare function proseOnly(body: string, options?: {
|
|
40
41
|
readonly keepMath?: boolean;
|
|
42
|
+
readonly keepReferences?: boolean;
|
|
41
43
|
}): string;
|
|
42
44
|
/** Exported for the same reason as {@link proseOnly}. */
|
|
43
45
|
declare function lineOf(text: string, index: number): number;
|
|
@@ -6,12 +6,14 @@ const INLINE_MATH = /(?<![\\$])\$(?!\s)([^$\n]+?)(?<![\\\s])\$(?!\d)/g;
|
|
|
6
6
|
const IMPORT_LINE = /^[ \t]*import\b[^\n]*$/gm;
|
|
7
7
|
const KEY_REFERENCE = /\[\[[^\]\n]*\]\]/g;
|
|
8
8
|
const JSX_TAG = /<\/?[A-Za-z][^<>]*>/g;
|
|
9
|
+
const LINK_DESTINATION = /\]\((?:[^()\n]|\([^()\n]*\))*\)/g;
|
|
10
|
+
const LINK_DEFINITION = /^[ \t]{0,3}\[[^\]\n]+\]:[ \t]*\S[^\n]*$/gm;
|
|
9
11
|
const MDX_COMMENT = /\{\/\*[\s\S]*?\*\/\}/g;
|
|
10
12
|
function proseOnly(body, options = {}) {
|
|
11
13
|
const blank = (match) => match.replace(/[^\n]/g, " ");
|
|
12
14
|
const code = body.replace(MDX_COMMENT, blank).replace(CODE_FENCE, blank).replace(INLINE_CODE, (match, lead) => lead + blank(match.slice(lead.length)));
|
|
13
15
|
const math = options.keepMath ? code : code.replace(DISPLAY_MATH, blank).replace(INLINE_MATH, blank);
|
|
14
|
-
return math.replace(IMPORT_LINE, blank).replace(KEY_REFERENCE, blank).replace(JSX_TAG, blank);
|
|
16
|
+
return math.replace(IMPORT_LINE, blank).replace(KEY_REFERENCE, (match) => options.keepReferences ? match : blank(match)).replace(LINK_DESTINATION, (match) => `]${blank(match.slice(1))}`).replace(LINK_DEFINITION, blank).replace(JSX_TAG, blank);
|
|
15
17
|
}
|
|
16
18
|
function lineOf(text, index) {
|
|
17
19
|
return text.slice(0, index).split("\n").length;
|
|
@@ -43,7 +45,7 @@ const SLASH_IDIOMS = /* @__PURE__ */ new Set([
|
|
|
43
45
|
"a/b"
|
|
44
46
|
]);
|
|
45
47
|
const SLASH_TOKENS = /\b([A-Za-z]{1,6})\/([A-Za-z]{1,6})\b/g;
|
|
46
|
-
const FAKE_SCRIPT = /[A-Za-z0-9][_^][A-Za-z0-9
|
|
48
|
+
const FAKE_SCRIPT = /[A-Za-z0-9]+[_^](?:\{[^{}\n]{0,24}\}?|[A-Za-z0-9]+)/g;
|
|
47
49
|
const PROSE_NOTATION_CODE = "prose-notation";
|
|
48
50
|
function findAll(text, pattern, toFinding) {
|
|
49
51
|
const out = [];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { CurriculumIssue } from '../curriculum/validation.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The retired-key gate (invariant `retired-keys`). A content file that still
|
|
5
|
+
* sets a key the engine no longer reads fails the build, once per key and
|
|
6
|
+
* file, so a course stops maintaining a field that does nothing. The finding
|
|
7
|
+
* points at the key's line, and deleting that line clears it. See
|
|
8
|
+
* docs/authoring/structure.md#retired-keys.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** One content file as `loadContentRecords` (`compiler/collections.ts`) reads it. */
|
|
12
|
+
interface ContentRecord {
|
|
13
|
+
/** Repository-relative path, e.g. `content/sources/hull.json`. */
|
|
14
|
+
readonly file: string;
|
|
15
|
+
/** Top-level fields: a page's frontmatter, or a JSON record's object. */
|
|
16
|
+
readonly data: Readonly<Record<string, unknown>>;
|
|
17
|
+
readonly text: string;
|
|
18
|
+
}
|
|
19
|
+
/** Each retired key, with what an author does about it. Keys are plain identifiers. */
|
|
20
|
+
declare const RETIRED_KEYS: Readonly<Record<string, string>>;
|
|
21
|
+
/** One `retired-key` issue per retired key a record sets, in record order. */
|
|
22
|
+
declare function findRetiredKeys(records: readonly ContentRecord[]): CurriculumIssue[];
|
|
23
|
+
/** The 1-based line a `key:` (YAML) or `"key":` (JSON) first starts on. */
|
|
24
|
+
declare function keyLine(text: string, key: string): number | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Starlight frontmatter a lesson may set but explico cannot honour: the
|
|
27
|
+
* manifest, not Starlight, decides which pages a course lists and in what
|
|
28
|
+
* order (invariant `retired-keys`). `draft: true` is an error — Starlight
|
|
29
|
+
* drops the page from a production build while the sidebar still names it, so
|
|
30
|
+
* the build fails — and `sidebar.order` a warning, since chapters follow the
|
|
31
|
+
* tracks and the key does nothing.
|
|
32
|
+
*/
|
|
33
|
+
declare function findUnsupportedKeys(records: readonly ContentRecord[]): CurriculumIssue[];
|
|
34
|
+
|
|
35
|
+
export { type ContentRecord, RETIRED_KEYS, findRetiredKeys, findUnsupportedKeys, keyLine };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const RETIRED_KEYS = {
|
|
2
|
+
editorialStatus: "explico no longer tracks a draft / in-review / reviewed status, because lessons are reviewed continuously. Delete the line."
|
|
3
|
+
};
|
|
4
|
+
function findRetiredKeys(records) {
|
|
5
|
+
const issues = [];
|
|
6
|
+
for (const { file, data, text } of records) {
|
|
7
|
+
for (const [key, advice] of Object.entries(RETIRED_KEYS)) {
|
|
8
|
+
if (!(key in data)) continue;
|
|
9
|
+
const line = keyLine(text, key);
|
|
10
|
+
const at = line === void 0 ? file : `${file}:${line}`;
|
|
11
|
+
issues.push({
|
|
12
|
+
kind: "retired-key",
|
|
13
|
+
message: `${at}: \`${key}\` is retired. ${advice}`,
|
|
14
|
+
file,
|
|
15
|
+
...line === void 0 ? {} : { line }
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return issues;
|
|
20
|
+
}
|
|
21
|
+
function keyLine(text, key) {
|
|
22
|
+
const match = new RegExp(`^[ \\t]*(?:"${key}"|${key})[ \\t]*:`, "m").exec(text);
|
|
23
|
+
return match === null ? void 0 : text.slice(0, match.index).split("\n").length;
|
|
24
|
+
}
|
|
25
|
+
function findUnsupportedKeys(records) {
|
|
26
|
+
const issues = [];
|
|
27
|
+
for (const { file, data, text } of records) {
|
|
28
|
+
if (!file.replace(/\\/g, "/").startsWith("content/docs/")) continue;
|
|
29
|
+
if (data.draft === true) {
|
|
30
|
+
const line = keyLine(text, "draft");
|
|
31
|
+
issues.push({
|
|
32
|
+
kind: "unsupported-key",
|
|
33
|
+
message: `${line === void 0 ? file : `${file}:${line}`}: \`draft: true\` is not supported in a course: Starlight leaves a draft out of a production build, but explico still lists and numbers it, so the build fails ("The slug \u2026 specified in the Starlight sidebar config does not exist"). Keep an unfinished lesson outside content/docs/ until it is ready.`,
|
|
34
|
+
file,
|
|
35
|
+
...line === void 0 ? {} : { line }
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const sidebar = data.sidebar;
|
|
39
|
+
const chapter = /^content\/docs\/[^/]+\/(?!index\.mdx?$)[^/]+\.mdx?$/.test(file.replace(/\\/g, "/"));
|
|
40
|
+
if (chapter && sidebar !== null && typeof sidebar === "object" && "order" in sidebar) {
|
|
41
|
+
const line = keyLine(text, "order");
|
|
42
|
+
issues.push({
|
|
43
|
+
kind: "ignored-key",
|
|
44
|
+
severity: "warning",
|
|
45
|
+
message: `${line === void 0 ? file : `${file}:${line}`}: \`sidebar.order\` does nothing in a course: chapters follow the order of the tracks (content/tracks/*.json), then their slugs. Order them there and delete the line.`,
|
|
46
|
+
file,
|
|
47
|
+
...line === void 0 ? {} : { line }
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return issues;
|
|
52
|
+
}
|
|
53
|
+
export {
|
|
54
|
+
RETIRED_KEYS,
|
|
55
|
+
findRetiredKeys,
|
|
56
|
+
findUnsupportedKeys,
|
|
57
|
+
keyLine
|
|
58
|
+
};
|
|
@@ -41,6 +41,8 @@ interface ResolvedGloss {
|
|
|
41
41
|
interface GlyphScopeEntry {
|
|
42
42
|
readonly key: string;
|
|
43
43
|
readonly notation: string;
|
|
44
|
+
/** Rule N23: the entry's index is a fixed label, so it is no family. */
|
|
45
|
+
readonly fixedIndex?: true;
|
|
44
46
|
}
|
|
45
47
|
/**
|
|
46
48
|
* The key segment derived from a gloss name: lower-cased, runs of anything
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** A formula's box at one font size, in px. */
|
|
2
|
+
interface FormulaMetrics {
|
|
3
|
+
/** Advance width. */
|
|
4
|
+
readonly width: number;
|
|
5
|
+
/** Height above the baseline. */
|
|
6
|
+
readonly height: number;
|
|
7
|
+
/** Depth below the baseline. */
|
|
8
|
+
readonly depth: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Measure `latex` as the notation KaTeX renders it — the same macros
|
|
12
|
+
* (`\explain`, `\slot`), trust boundary and delimiter sizing as lesson math —
|
|
13
|
+
* at a font size of `fontPx`. Throws on LaTeX KaTeX cannot render.
|
|
14
|
+
*/
|
|
15
|
+
declare function measureFormula(latex: string, fontPx: number): FormulaMetrics;
|
|
16
|
+
|
|
17
|
+
export { type FormulaMetrics, measureFormula };
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import katex, {} from "katex";
|
|
2
|
+
import fontMetricsData from "katex/src/fontMetricsData.js";
|
|
3
|
+
import { createNotationKatexOptions } from "./katex-options.mjs";
|
|
4
|
+
const SIZE_MULTIPLIERS = [0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.2, 1.44, 1.728, 2.074, 2.488];
|
|
5
|
+
const CLASS_PADDING = {
|
|
6
|
+
"x-arrow-pad": [0.5, 0.5],
|
|
7
|
+
"cd-arrow-pad": [0.27778, 0.55556],
|
|
8
|
+
boxpad: [0.3, 0.3],
|
|
9
|
+
"cancel-pad": [0.2, 0.2],
|
|
10
|
+
anglpad: [0.03889, 0.03889]
|
|
11
|
+
};
|
|
12
|
+
const CLASS_MARGIN = {
|
|
13
|
+
"cancel-lap": [-0.2, -0.2],
|
|
14
|
+
angl: [0, 0.03889]
|
|
15
|
+
};
|
|
16
|
+
const CLASS_BORDER = {
|
|
17
|
+
fbox: [0.04, 0.04],
|
|
18
|
+
fcolorbox: [0.04, 0.04],
|
|
19
|
+
angl: [0, 0.049]
|
|
20
|
+
};
|
|
21
|
+
const ZERO_WIDTH = /* @__PURE__ */ new Set(["llap", "rlap", "clap", "pstrut", "vlist-s"]);
|
|
22
|
+
function has(node, name) {
|
|
23
|
+
return node.classes?.includes(name) ?? false;
|
|
24
|
+
}
|
|
25
|
+
function em(value) {
|
|
26
|
+
if (!value) return 0;
|
|
27
|
+
const match = /^(-?\d*\.?\d+)em$/.exec(value.trim());
|
|
28
|
+
return match ? Number(match[1]) : 0;
|
|
29
|
+
}
|
|
30
|
+
function sizing(node) {
|
|
31
|
+
if (!has(node, "sizing") && !has(node, "fontsize-ensurer")) return 1;
|
|
32
|
+
let from = 0;
|
|
33
|
+
let to = 0;
|
|
34
|
+
for (const name of node.classes ?? []) {
|
|
35
|
+
const reset = /^reset-size(\d+)$/.exec(name);
|
|
36
|
+
if (reset) from = Number(reset[1]);
|
|
37
|
+
const size = /^size(\d+)$/.exec(name);
|
|
38
|
+
if (size) to = Number(size[1]);
|
|
39
|
+
}
|
|
40
|
+
const fromMultiplier = SIZE_MULTIPLIERS[from - 1];
|
|
41
|
+
const toMultiplier = SIZE_MULTIPLIERS[to - 1];
|
|
42
|
+
return fromMultiplier && toMultiplier ? toMultiplier / fromMultiplier : 1;
|
|
43
|
+
}
|
|
44
|
+
function pairOf(table, node) {
|
|
45
|
+
let total = 0;
|
|
46
|
+
for (const name of node.classes ?? []) {
|
|
47
|
+
const pair = table[name];
|
|
48
|
+
if (pair) total += pair[0] + pair[1];
|
|
49
|
+
}
|
|
50
|
+
return total;
|
|
51
|
+
}
|
|
52
|
+
const ROOT_FONT = { family: "Main", bold: false, italic: false };
|
|
53
|
+
const FONT_CLASSES = {
|
|
54
|
+
textbf: { bold: true },
|
|
55
|
+
textit: { italic: true },
|
|
56
|
+
textrm: { family: "Main" },
|
|
57
|
+
texttt: { family: "Typewriter" },
|
|
58
|
+
mathnormal: { family: "Math", italic: true },
|
|
59
|
+
mathit: { family: "Main", italic: true },
|
|
60
|
+
mathrm: { italic: false },
|
|
61
|
+
mathbf: { family: "Main", bold: true },
|
|
62
|
+
boldsymbol: { family: "Math", bold: true, italic: true },
|
|
63
|
+
amsrm: { family: "AMS" },
|
|
64
|
+
mathbb: { family: "AMS" },
|
|
65
|
+
textbb: { family: "AMS" },
|
|
66
|
+
mathcal: { family: "Caligraphic" },
|
|
67
|
+
mathfrak: { family: "Fraktur" },
|
|
68
|
+
textfrak: { family: "Fraktur" },
|
|
69
|
+
mathboldfrak: { family: "Fraktur", bold: true },
|
|
70
|
+
textboldfrak: { family: "Fraktur", bold: true },
|
|
71
|
+
mathtt: { family: "Typewriter" },
|
|
72
|
+
mathscr: { family: "Script" },
|
|
73
|
+
textscr: { family: "Script" },
|
|
74
|
+
mathsf: { family: "SansSerif" },
|
|
75
|
+
textsf: { family: "SansSerif" },
|
|
76
|
+
mathboldsf: { family: "SansSerif", bold: true },
|
|
77
|
+
textboldsf: { family: "SansSerif", bold: true },
|
|
78
|
+
mathsfit: { family: "SansSerif", italic: true },
|
|
79
|
+
mathitsf: { family: "SansSerif", italic: true },
|
|
80
|
+
textitsf: { family: "SansSerif", italic: true },
|
|
81
|
+
mainrm: { family: "Main", italic: false }
|
|
82
|
+
};
|
|
83
|
+
function fontOf(node, inherited) {
|
|
84
|
+
let font = inherited;
|
|
85
|
+
for (const name of node.classes ?? []) {
|
|
86
|
+
const rule = FONT_CLASSES[name];
|
|
87
|
+
if (rule) font = { ...font, ...rule };
|
|
88
|
+
}
|
|
89
|
+
if (has(node, "delimsizing")) {
|
|
90
|
+
for (const size of [1, 2, 3, 4]) {
|
|
91
|
+
if (has(node, `size${size}`)) font = { ...font, family: `Size${size}` };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (has(node, "op-symbol")) {
|
|
95
|
+
if (has(node, "small-op")) font = { ...font, family: "Size1" };
|
|
96
|
+
if (has(node, "large-op")) font = { ...font, family: "Size2" };
|
|
97
|
+
}
|
|
98
|
+
return font;
|
|
99
|
+
}
|
|
100
|
+
const METRICS = fontMetricsData;
|
|
101
|
+
function metricsFor(font) {
|
|
102
|
+
const variants = [
|
|
103
|
+
font.bold && font.italic ? "BoldItalic" : font.bold ? "Bold" : font.italic ? "Italic" : "Regular",
|
|
104
|
+
font.bold ? "Bold" : void 0,
|
|
105
|
+
font.italic ? "Italic" : void 0,
|
|
106
|
+
"Regular"
|
|
107
|
+
];
|
|
108
|
+
for (const variant of variants) {
|
|
109
|
+
const table = variant && METRICS[`${font.family}-${variant}`];
|
|
110
|
+
if (table) return table;
|
|
111
|
+
}
|
|
112
|
+
return METRICS["Main-Regular"] ?? {};
|
|
113
|
+
}
|
|
114
|
+
const WIDE_GLYPHS = /* @__PURE__ */ new Map([
|
|
115
|
+
[8748, 0.85],
|
|
116
|
+
// ∬ \iint
|
|
117
|
+
[8749, 1.22],
|
|
118
|
+
// ∭ \iiint
|
|
119
|
+
[8751, 0.85],
|
|
120
|
+
// ∯ \oiint
|
|
121
|
+
[8752, 1.22],
|
|
122
|
+
// ∰ \oiiint
|
|
123
|
+
[10764, 1.6]
|
|
124
|
+
// ⨌ \iiiint
|
|
125
|
+
]);
|
|
126
|
+
function glyphWidth(table, char) {
|
|
127
|
+
if (char === "\u200B") return 0;
|
|
128
|
+
const metrics = table[String(char.codePointAt(0))] ?? table["77"];
|
|
129
|
+
return metrics?.[4] ?? 1;
|
|
130
|
+
}
|
|
131
|
+
function glyphAdvances(node, font) {
|
|
132
|
+
const [first, ...rest] = [...node.text ?? ""];
|
|
133
|
+
if (first === void 0) return [];
|
|
134
|
+
const table = metricsFor(font);
|
|
135
|
+
const wide = (char) => WIDE_GLYPHS.get(char.codePointAt(0) ?? 0);
|
|
136
|
+
const firstWidth = wide(first) ?? (node.width && node.width > 0 ? node.width : glyphWidth(table, first));
|
|
137
|
+
return [firstWidth, ...rest.map((char) => wide(char) ?? glyphWidth(table, char))];
|
|
138
|
+
}
|
|
139
|
+
function glyphPx(advanceEm, fontPx) {
|
|
140
|
+
return Math.max(Math.round(advanceEm * Math.round(fontPx)), advanceEm * fontPx);
|
|
141
|
+
}
|
|
142
|
+
function advance(node, inherited, context, parent) {
|
|
143
|
+
const frame = {
|
|
144
|
+
scale: inherited.scale * sizing(node),
|
|
145
|
+
font: fontOf(node, inherited.font)
|
|
146
|
+
};
|
|
147
|
+
const scale = frame.scale * context.fontPx;
|
|
148
|
+
const style = node.style ?? {};
|
|
149
|
+
if (typeof node.text === "string") {
|
|
150
|
+
const marginRight = style.marginRight !== void 0 ? em(style.marginRight) : Math.max(0, node.italic ?? 0);
|
|
151
|
+
const glyphs = glyphAdvances(node, frame.font).reduce(
|
|
152
|
+
(sum, width) => sum + glyphPx(width, scale),
|
|
153
|
+
0
|
|
154
|
+
);
|
|
155
|
+
return glyphs + (marginRight + em(style.marginLeft)) * scale;
|
|
156
|
+
}
|
|
157
|
+
if (!node.classes) return 0;
|
|
158
|
+
const margins = (em(style.marginLeft) + em(style.marginRight) + pairOf(CLASS_MARGIN, node) + rootMargin(node, parent)) * scale;
|
|
159
|
+
if (node.classes.some((name) => ZERO_WIDTH.has(name))) return margins;
|
|
160
|
+
if (has(node, "accent-body") && !has(node, "accent-full")) return margins;
|
|
161
|
+
let content;
|
|
162
|
+
if (has(node, "nulldelimiter")) {
|
|
163
|
+
content = 0.12 * scale;
|
|
164
|
+
} else if (em(style.width) > 0) {
|
|
165
|
+
content = em(style.width) * scale;
|
|
166
|
+
} else if (has(node, "vlist-t")) {
|
|
167
|
+
content = tableWidth(node, frame, context);
|
|
168
|
+
} else if (has(node, "vlist")) {
|
|
169
|
+
content = Math.max(
|
|
170
|
+
0,
|
|
171
|
+
...(node.children ?? []).map((row) => advance(row, frame, context, node))
|
|
172
|
+
);
|
|
173
|
+
} else {
|
|
174
|
+
content = (node.children ?? []).reduce(
|
|
175
|
+
(sum, child) => sum + advance(child, frame, context, node),
|
|
176
|
+
0
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
content = Math.max(content, em(style.minWidth) * scale);
|
|
180
|
+
if (has(node, "vertical-separator")) content = Math.max(content, 1);
|
|
181
|
+
const padding = (em(style.paddingLeft) + pairOf(CLASS_PADDING, node)) * scale;
|
|
182
|
+
const border = (em(style.borderRightWidth) + pairOf(CLASS_BORDER, node)) * scale;
|
|
183
|
+
return content + padding + border + margins;
|
|
184
|
+
}
|
|
185
|
+
function rootMargin(node, parent) {
|
|
186
|
+
return has(node, "root") && parent !== void 0 && has(parent, "sqrt") ? 0.2777777778 - 0.5555555556 : 0;
|
|
187
|
+
}
|
|
188
|
+
function tableWidth(node, frame, context) {
|
|
189
|
+
const columns = [];
|
|
190
|
+
for (const row of node.children ?? []) {
|
|
191
|
+
let column = 0;
|
|
192
|
+
for (const cell of row.children ?? []) {
|
|
193
|
+
if (has(cell, "vlist-s")) continue;
|
|
194
|
+
columns[column] = Math.max(columns[column] ?? 0, advance(cell, frame, context, row));
|
|
195
|
+
column += 1;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return columns.reduce((sum, width) => sum + width, 0);
|
|
199
|
+
}
|
|
200
|
+
function collectBases(node, out) {
|
|
201
|
+
if (has(node, "base")) {
|
|
202
|
+
out.push(node);
|
|
203
|
+
return out;
|
|
204
|
+
}
|
|
205
|
+
for (const child of node.children ?? []) collectBases(child, out);
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
function measureFormula(latex, fontPx) {
|
|
209
|
+
const { __renderToHTMLTree } = katex;
|
|
210
|
+
const tree = __renderToHTMLTree(latex, {
|
|
211
|
+
...createNotationKatexOptions(),
|
|
212
|
+
displayMode: false
|
|
213
|
+
});
|
|
214
|
+
const bases = collectBases(tree, []);
|
|
215
|
+
return {
|
|
216
|
+
width: advance(tree, { scale: 1, font: ROOT_FONT }, { fontPx }),
|
|
217
|
+
height: Math.max(0, ...bases.map((base) => base.height ?? 0)) * fontPx,
|
|
218
|
+
depth: Math.max(0, ...bases.map((base) => base.depth ?? 0)) * fontPx
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
export {
|
|
222
|
+
measureFormula
|
|
223
|
+
};
|
|
@@ -46,6 +46,18 @@ function validateKeyedFloats(lessons, kind) {
|
|
|
46
46
|
...raw.key ? { key: raw.key } : {}
|
|
47
47
|
});
|
|
48
48
|
}
|
|
49
|
+
for (const prop of ["short", "caption"]) {
|
|
50
|
+
if (!raw.props.includes(prop) || raw[prop] != null) continue;
|
|
51
|
+
const places = prefix === "ex" ? `its [[${prefix}-\u2026]] card and a copied link` : `its [[${prefix}-\u2026]] card, a copied link, a folded section's line and the "Figures and tables" page`;
|
|
52
|
+
diagnostics.push({
|
|
53
|
+
code: `${prefix}-unread-prop`,
|
|
54
|
+
severity: "warning",
|
|
55
|
+
message: `${lesson.lessonId} has a <${tagName} ${prop}={\u2026}>${raw.key ? ` (id="${raw.key}")` : ""}, but the manifest reads only a quoted \`${prop}\`, so ${places} name this ${noun} without it. Write ${prop}="\u2026", with typographic quotes (\u2018\u2019 \u201C\u201D) inside.`,
|
|
56
|
+
file: lesson.file,
|
|
57
|
+
lessonId: lesson.lessonId,
|
|
58
|
+
...raw.key ? { key: raw.key } : {}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
49
61
|
if (raw.key == null) continue;
|
|
50
62
|
if (!KEYED_FLOAT_KEY_PATTERN.test(raw.key)) {
|
|
51
63
|
diagnostics.push({
|
|
@@ -50,8 +50,8 @@ export function countKeyedJsxFloats(markdown: any, { tagName }: {
|
|
|
50
50
|
tagName: any;
|
|
51
51
|
}): number;
|
|
52
52
|
/**
|
|
53
|
-
* The mdast equivalent of `scanKeyedJsxFloats`: walk the
|
|
54
|
-
*
|
|
53
|
+
* The mdast equivalent of `scanKeyedJsxFloats`: walk the lesson's flow (and
|
|
54
|
+
* through the `TRANSPARENT_COMPONENTS`' bodies) in document order, advancing the
|
|
55
55
|
* section counter on every top-level depth-2 heading and emitting a record for
|
|
56
56
|
* every `mdxJsxFlowElement` named `tagName` — keyed or not, exactly like
|
|
57
57
|
* `collectDisplayEquations`. `numbering` is threaded the same way as in
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
import {
|
|
27
27
|
resolveNumbering,
|
|
28
|
+
stripContainerDirectives,
|
|
28
29
|
stripFencedCode,
|
|
29
30
|
stripJsxComponents,
|
|
30
31
|
TRANSPARENT_COMPONENTS,
|
|
@@ -131,10 +132,9 @@ function mdxStringAttribute(node, name) {
|
|
|
131
132
|
*/
|
|
132
133
|
export function scanKeyedJsxFloats(markdown, { tagName, numbering }) {
|
|
133
134
|
const { prefix, offset } = resolveNumbering(numbering);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
tagName,
|
|
137
|
-
TRANSPARENT_COMPONENTS,
|
|
135
|
+
// A `:::note` aside is a callout, like `<Aside>`: nothing in it numbers.
|
|
136
|
+
const text = stripContainerDirectives(
|
|
137
|
+
stripJsxComponents(stripFencedCode(markdown), tagName, TRANSPARENT_COMPONENTS),
|
|
138
138
|
);
|
|
139
139
|
const tokens = [];
|
|
140
140
|
const heading = /^[ \t]{0,3}##[ \t]+\S/gm;
|
|
@@ -196,8 +196,8 @@ export function countKeyedJsxFloats(markdown, { tagName }) {
|
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
/**
|
|
199
|
-
* The mdast equivalent of `scanKeyedJsxFloats`: walk the
|
|
200
|
-
*
|
|
199
|
+
* The mdast equivalent of `scanKeyedJsxFloats`: walk the lesson's flow (and
|
|
200
|
+
* through the `TRANSPARENT_COMPONENTS`' bodies) in document order, advancing the
|
|
201
201
|
* section counter on every top-level depth-2 heading and emitting a record for
|
|
202
202
|
* every `mdxJsxFlowElement` named `tagName` — keyed or not, exactly like
|
|
203
203
|
* `collectDisplayEquations`. `numbering` is threaded the same way as in
|
|
@@ -210,8 +210,12 @@ export function collectKeyedJsxFloats(tree, { tagName, numbering }) {
|
|
|
210
210
|
let indexInSection = 0;
|
|
211
211
|
let indexInLesson = 0;
|
|
212
212
|
|
|
213
|
+
// The lesson's flow — top-level blocks, list items, blockquotes — and the
|
|
214
|
+
// `TRANSPARENT_COMPONENTS`' bodies; never another component's body or a
|
|
215
|
+
// `:::note` aside. `scanKeyedJsxFloats` reads the same set from the text.
|
|
213
216
|
const collectFromDetail = (node) => {
|
|
214
217
|
if (!node || typeof node !== 'object') return;
|
|
218
|
+
if (node.type === 'containerDirective') return;
|
|
215
219
|
if (node.type === 'mdxJsxFlowElement' && node.name === tagName) {
|
|
216
220
|
indexInSection += 1;
|
|
217
221
|
indexInLesson += 1;
|
|
@@ -256,14 +260,7 @@ export function collectKeyedJsxFloats(tree, { tagName, numbering }) {
|
|
|
256
260
|
indexInSection = 0;
|
|
257
261
|
continue;
|
|
258
262
|
}
|
|
259
|
-
|
|
260
|
-
collectFromDetail(node);
|
|
261
|
-
} else if (
|
|
262
|
-
node.type === 'mdxJsxFlowElement' &&
|
|
263
|
-
TRANSPARENT_COMPONENTS.includes(node.name)
|
|
264
|
-
) {
|
|
265
|
-
collectFromDetail(node);
|
|
266
|
-
}
|
|
263
|
+
collectFromDetail(node);
|
|
267
264
|
}
|
|
268
265
|
return floats;
|
|
269
266
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { loadManifest } from "../compiler/manifest.js";
|
|
2
2
|
import {
|
|
3
|
+
bindScopeToPage,
|
|
3
4
|
registerPageNotationDependencies,
|
|
4
5
|
routeIdFromLocals
|
|
5
6
|
} from "./page-notation.js";
|
|
@@ -9,7 +10,8 @@ async function labMathScope(notation, body = "", component) {
|
|
|
9
10
|
const local = notation?.local ?? [];
|
|
10
11
|
const scope = local.map((definition) => ({
|
|
11
12
|
key: definition.key,
|
|
12
|
-
notation: definition.latex
|
|
13
|
+
notation: definition.latex,
|
|
14
|
+
...definition.fixedIndex === true ? { fixedIndex: true } : {}
|
|
13
15
|
}));
|
|
14
16
|
const referenced = /* @__PURE__ */ new Set();
|
|
15
17
|
const explicitlyExplained = /* @__PURE__ */ new Set();
|
|
@@ -32,7 +34,11 @@ async function labMathScope(notation, body = "", component) {
|
|
|
32
34
|
}
|
|
33
35
|
const shared = sharedByKey.get(key);
|
|
34
36
|
if (shared) {
|
|
35
|
-
scope.push({
|
|
37
|
+
scope.push({
|
|
38
|
+
key: shared.key,
|
|
39
|
+
notation: shared.notation,
|
|
40
|
+
...shared.fixedIndex ? { fixedIndex: true } : {}
|
|
41
|
+
});
|
|
36
42
|
resolvedKeys.add(key);
|
|
37
43
|
continue;
|
|
38
44
|
}
|
|
@@ -48,6 +54,11 @@ async function labMathScope(notation, body = "", component) {
|
|
|
48
54
|
resolvedKeys,
|
|
49
55
|
component.context
|
|
50
56
|
);
|
|
57
|
+
bindScopeToPage(scope, {
|
|
58
|
+
locals: component.locals,
|
|
59
|
+
context: component.context,
|
|
60
|
+
checkIndices: !manifest.rules.disabled.includes("N23")
|
|
61
|
+
});
|
|
51
62
|
}
|
|
52
63
|
return scope;
|
|
53
64
|
}
|