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,50 @@
|
|
|
1
|
+
import { composeDerivedEntry } from "./derived-notation.mjs";
|
|
2
|
+
import { scopeEntry } from "./gate-math.js";
|
|
3
|
+
import { formulaExplainKeys } from "./gloss.js";
|
|
4
|
+
import { baseSymbolLatex, derivedNotationLatex } from "./math-glyphs.mjs";
|
|
5
|
+
function derivedNotationEntries(uses, records, cardsByKey) {
|
|
6
|
+
const derived = /* @__PURE__ */ new Map();
|
|
7
|
+
for (const use of uses) {
|
|
8
|
+
if (derived.has(use.key)) continue;
|
|
9
|
+
const base = records.get(use.base);
|
|
10
|
+
const index = records.get(use.index);
|
|
11
|
+
if (!base || !index) continue;
|
|
12
|
+
const notation = derivedNotationLatex(base.notation, index.notation);
|
|
13
|
+
const entry = composeDerivedEntry({
|
|
14
|
+
key: use.key,
|
|
15
|
+
notation,
|
|
16
|
+
baseLatex: baseSymbolLatex(base.notation),
|
|
17
|
+
base,
|
|
18
|
+
index: {
|
|
19
|
+
...index,
|
|
20
|
+
...index.formulaTemplate === void 0 ? {} : { formula: index.formulaTemplate }
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
const formulaScope = [
|
|
24
|
+
{ key: use.key, notation },
|
|
25
|
+
...index.glosses.map((gloss) => ({ key: gloss.key, notation: gloss.notation })),
|
|
26
|
+
scopeEntry(base)
|
|
27
|
+
];
|
|
28
|
+
for (const key of formulaExplainKeys(entry.formula ?? "")) {
|
|
29
|
+
if (formulaScope.some((row) => row.key === key)) continue;
|
|
30
|
+
const card = records.get(key) ?? cardsByKey.get(key);
|
|
31
|
+
if (card) formulaScope.push(scopeEntry(card));
|
|
32
|
+
}
|
|
33
|
+
derived.set(use.key, {
|
|
34
|
+
key: entry.key,
|
|
35
|
+
base: entry.base,
|
|
36
|
+
index: entry.index,
|
|
37
|
+
notation: entry.notation,
|
|
38
|
+
label: entry.label,
|
|
39
|
+
summary: entry.summary,
|
|
40
|
+
glosses: [...index.glosses],
|
|
41
|
+
formulaScope,
|
|
42
|
+
...entry.units === void 0 ? {} : { units: entry.units },
|
|
43
|
+
...entry.formula === void 0 ? {} : { formula: entry.formula }
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return [...derived.values()].sort((left, right) => left.key.localeCompare(right.key));
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
derivedNotationEntries
|
|
50
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A label as it reads inside a sentence: its first letter lowercased unless
|
|
3
|
+
* the first word is an acronym (`OLS estimate`) — the same rule
|
|
4
|
+
* `composeHeaderLabel` applies to an operator's operand.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} label
|
|
7
|
+
* @returns {string}
|
|
8
|
+
*/
|
|
9
|
+
export function inSentence(label: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* A derived card's title: the index entry's `reads` with `{base}` replaced by
|
|
12
|
+
* the base's label (lowercased in mid-sentence), or `"<index label> of
|
|
13
|
+
* <base label>"` when the index entry gives no `reads`.
|
|
14
|
+
*
|
|
15
|
+
* @param {{ label: string, reads?: string }} index
|
|
16
|
+
* @param {string} baseLabel
|
|
17
|
+
* @returns {string}
|
|
18
|
+
*/
|
|
19
|
+
export function derivedLabel(index: {
|
|
20
|
+
label: string;
|
|
21
|
+
reads?: string;
|
|
22
|
+
}, baseLabel: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* An index entry's formula with `{base}` bound: to the base symbol under its
|
|
25
|
+
* own `\explain`, for a derived card, so the reader opens the base's card
|
|
26
|
+
* from it.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} formula
|
|
29
|
+
* @param {string} baseKey
|
|
30
|
+
* @param {string} baseLatex
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
export function derivedFormula(formula: string, baseKey: string, baseLatex: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* An index entry's own formula, shown on the index entry's card and checked
|
|
36
|
+
* by the formula gate: `{base}` becomes a placeholder square bound to the
|
|
37
|
+
* entry itself, as an operator entry is shown over a square.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} formula
|
|
40
|
+
* @param {string} ownKey
|
|
41
|
+
* @returns {string}
|
|
42
|
+
*/
|
|
43
|
+
export function indexEntryFormula(formula: string, ownKey: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* @typedef {object} DerivedSource
|
|
46
|
+
* @property {string} key
|
|
47
|
+
* @property {string} notation
|
|
48
|
+
* @property {string} label
|
|
49
|
+
* @property {string} [summary]
|
|
50
|
+
* @property {string} [units]
|
|
51
|
+
* @property {string} [reads]
|
|
52
|
+
* @property {string} [formula]
|
|
53
|
+
* @property {readonly { key: string, notation: string, label: string, units?: string, ownerKey: string }[]} [glosses]
|
|
54
|
+
*/
|
|
55
|
+
/**
|
|
56
|
+
* The derived entry for one (base, index) pair. `notation` is the indexed
|
|
57
|
+
* symbol, from `derivedNotationLatex`.
|
|
58
|
+
*
|
|
59
|
+
* @param {{ key: string, notation: string, baseLatex: string, base: DerivedSource, index: DerivedSource }} input
|
|
60
|
+
*/
|
|
61
|
+
export function composeDerivedEntry({ key, notation, baseLatex, base, index }: {
|
|
62
|
+
key: string;
|
|
63
|
+
notation: string;
|
|
64
|
+
baseLatex: string;
|
|
65
|
+
base: DerivedSource;
|
|
66
|
+
index: DerivedSource;
|
|
67
|
+
}): {
|
|
68
|
+
formula?: string | undefined;
|
|
69
|
+
units?: string | undefined;
|
|
70
|
+
key: string;
|
|
71
|
+
notation: string;
|
|
72
|
+
base: string;
|
|
73
|
+
index: string;
|
|
74
|
+
label: string;
|
|
75
|
+
summary: string;
|
|
76
|
+
glosses: {
|
|
77
|
+
key: string;
|
|
78
|
+
notation: string;
|
|
79
|
+
label: string;
|
|
80
|
+
units?: string;
|
|
81
|
+
ownerKey: string;
|
|
82
|
+
}[];
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Derived notation entries (rule N23): the card for a symbol with a subscript
|
|
86
|
+
* that an **index entry** explains — `\mathbf{v}_0` from the base entry
|
|
87
|
+
* `velocity-vector` (`\mathbf{v}`) and the index entry `motion.initial`
|
|
88
|
+
* (`{}_0`, "Initial value").
|
|
89
|
+
*
|
|
90
|
+
* The resolver writes one `\explain` around the whole indexed symbol, with
|
|
91
|
+
* the key `derivedNotationKey(base, index)` (`math-glyphs.mjs`); the build
|
|
92
|
+
* materializes the entry from the two, here, so that everything downstream
|
|
93
|
+
* treats it as one more card. What the reader sees first is about the
|
|
94
|
+
* indexed symbol; the base's own card is one level deeper, through the base
|
|
95
|
+
* symbol on the card's formula line:
|
|
96
|
+
*
|
|
97
|
+
* | Field | From |
|
|
98
|
+
* | ------- | ------------------------------------------- |
|
|
99
|
+
* | symbol | the base's symbol plus the index |
|
|
100
|
+
* | title | the index entry's `reads`, the base's label |
|
|
101
|
+
* | summary | the index entry's `meaning`, never the base's |
|
|
102
|
+
* | formula | the index entry's `formula`, `{base}` bound |
|
|
103
|
+
* | units | the base entry |
|
|
104
|
+
*
|
|
105
|
+
* Plain JavaScript with no parser, so the remark pass and the manifest share
|
|
106
|
+
* it; `derivedNotationLatex` in `math-glyphs.mjs` builds the symbol.
|
|
107
|
+
*/
|
|
108
|
+
/** The indexed symbol, in an index entry's `reads` and `formula`. */
|
|
109
|
+
export const BASE_PLACEHOLDER: "{base}";
|
|
110
|
+
export type DerivedSource = {
|
|
111
|
+
key: string;
|
|
112
|
+
notation: string;
|
|
113
|
+
label: string;
|
|
114
|
+
summary?: string | undefined;
|
|
115
|
+
units?: string | undefined;
|
|
116
|
+
reads?: string | undefined;
|
|
117
|
+
formula?: string | undefined;
|
|
118
|
+
glosses?: readonly {
|
|
119
|
+
key: string;
|
|
120
|
+
notation: string;
|
|
121
|
+
label: string;
|
|
122
|
+
units?: string;
|
|
123
|
+
ownerKey: string;
|
|
124
|
+
}[] | undefined;
|
|
125
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derived notation entries (rule N23): the card for a symbol with a subscript
|
|
3
|
+
* that an **index entry** explains — `\mathbf{v}_0` from the base entry
|
|
4
|
+
* `velocity-vector` (`\mathbf{v}`) and the index entry `motion.initial`
|
|
5
|
+
* (`{}_0`, "Initial value").
|
|
6
|
+
*
|
|
7
|
+
* The resolver writes one `\explain` around the whole indexed symbol, with
|
|
8
|
+
* the key `derivedNotationKey(base, index)` (`math-glyphs.mjs`); the build
|
|
9
|
+
* materializes the entry from the two, here, so that everything downstream
|
|
10
|
+
* treats it as one more card. What the reader sees first is about the
|
|
11
|
+
* indexed symbol; the base's own card is one level deeper, through the base
|
|
12
|
+
* symbol on the card's formula line:
|
|
13
|
+
*
|
|
14
|
+
* | Field | From |
|
|
15
|
+
* | ------- | ------------------------------------------- |
|
|
16
|
+
* | symbol | the base's symbol plus the index |
|
|
17
|
+
* | title | the index entry's `reads`, the base's label |
|
|
18
|
+
* | summary | the index entry's `meaning`, never the base's |
|
|
19
|
+
* | formula | the index entry's `formula`, `{base}` bound |
|
|
20
|
+
* | units | the base entry |
|
|
21
|
+
*
|
|
22
|
+
* Plain JavaScript with no parser, so the remark pass and the manifest share
|
|
23
|
+
* it; `derivedNotationLatex` in `math-glyphs.mjs` builds the symbol.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** The indexed symbol, in an index entry's `reads` and `formula`. */
|
|
27
|
+
export const BASE_PLACEHOLDER = '{base}';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A label as it reads inside a sentence: its first letter lowercased unless
|
|
31
|
+
* the first word is an acronym (`OLS estimate`) — the same rule
|
|
32
|
+
* `composeHeaderLabel` applies to an operator's operand.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} label
|
|
35
|
+
* @returns {string}
|
|
36
|
+
*/
|
|
37
|
+
export function inSentence(label) {
|
|
38
|
+
const [first = ''] = label.split(/\s+/u);
|
|
39
|
+
if (first.length > 1 && first === first.toUpperCase()) return label;
|
|
40
|
+
return label.charAt(0).toLowerCase() + label.slice(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A derived card's title: the index entry's `reads` with `{base}` replaced by
|
|
45
|
+
* the base's label (lowercased in mid-sentence), or `"<index label> of
|
|
46
|
+
* <base label>"` when the index entry gives no `reads`.
|
|
47
|
+
*
|
|
48
|
+
* @param {{ label: string, reads?: string }} index
|
|
49
|
+
* @param {string} baseLabel
|
|
50
|
+
* @returns {string}
|
|
51
|
+
*/
|
|
52
|
+
export function derivedLabel(index, baseLabel) {
|
|
53
|
+
const reads = index.reads ?? `${index.label} of ${BASE_PLACEHOLDER}`;
|
|
54
|
+
const at = reads.indexOf(BASE_PLACEHOLDER);
|
|
55
|
+
const filled = reads
|
|
56
|
+
.split(BASE_PLACEHOLDER)
|
|
57
|
+
.join(at === 0 ? baseLabel : inSentence(baseLabel));
|
|
58
|
+
// `{base}` first in a later position, or a `reads` that starts lowercase.
|
|
59
|
+
return filled.charAt(0).toUpperCase() + filled.slice(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* An index entry's formula with `{base}` bound: to the base symbol under its
|
|
64
|
+
* own `\explain`, for a derived card, so the reader opens the base's card
|
|
65
|
+
* from it.
|
|
66
|
+
*
|
|
67
|
+
* @param {string} formula
|
|
68
|
+
* @param {string} baseKey
|
|
69
|
+
* @param {string} baseLatex
|
|
70
|
+
* @returns {string}
|
|
71
|
+
*/
|
|
72
|
+
export function derivedFormula(formula, baseKey, baseLatex) {
|
|
73
|
+
return formula.split(BASE_PLACEHOLDER).join(`\\explain{${baseKey}}{${baseLatex}}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* An index entry's own formula, shown on the index entry's card and checked
|
|
78
|
+
* by the formula gate: `{base}` becomes a placeholder square bound to the
|
|
79
|
+
* entry itself, as an operator entry is shown over a square.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} formula
|
|
82
|
+
* @param {string} ownKey
|
|
83
|
+
* @returns {string}
|
|
84
|
+
*/
|
|
85
|
+
export function indexEntryFormula(formula, ownKey) {
|
|
86
|
+
return formula.split(BASE_PLACEHOLDER).join(`\\explain{${ownKey}}{\\square}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @typedef {object} DerivedSource
|
|
91
|
+
* @property {string} key
|
|
92
|
+
* @property {string} notation
|
|
93
|
+
* @property {string} label
|
|
94
|
+
* @property {string} [summary]
|
|
95
|
+
* @property {string} [units]
|
|
96
|
+
* @property {string} [reads]
|
|
97
|
+
* @property {string} [formula]
|
|
98
|
+
* @property {readonly { key: string, notation: string, label: string, units?: string, ownerKey: string }[]} [glosses]
|
|
99
|
+
*/
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The derived entry for one (base, index) pair. `notation` is the indexed
|
|
103
|
+
* symbol, from `derivedNotationLatex`.
|
|
104
|
+
*
|
|
105
|
+
* @param {{ key: string, notation: string, baseLatex: string, base: DerivedSource, index: DerivedSource }} input
|
|
106
|
+
*/
|
|
107
|
+
export function composeDerivedEntry({ key, notation, baseLatex, base, index }) {
|
|
108
|
+
return {
|
|
109
|
+
key,
|
|
110
|
+
notation,
|
|
111
|
+
base: base.key,
|
|
112
|
+
index: index.key,
|
|
113
|
+
label: derivedLabel(index, base.label),
|
|
114
|
+
summary: index.summary ?? '',
|
|
115
|
+
glosses: [...(index.glosses ?? [])],
|
|
116
|
+
...(base.units === undefined ? {} : { units: base.units }),
|
|
117
|
+
...(index.formula === undefined
|
|
118
|
+
? {}
|
|
119
|
+
: { formula: derivedFormula(index.formula, base.key, baseLatex) }),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { M as ManifestRules } from '../course-config-rRtME5UV.js';
|
|
2
|
+
import '../rules/catalog.js';
|
|
3
|
+
import '../rules/course-rules.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Rule V8: a diagram is no wider than the lesson column.
|
|
7
|
+
*
|
|
8
|
+
* `Diagram.astro` lays a diagram out at build time and so knows its width, but
|
|
9
|
+
* not the page it is on or what the course allows, and a diagram drawn by a
|
|
10
|
+
* wrapper component (`<CourseDiagram kind="…">`) never shows its nodes to the
|
|
11
|
+
* manifest walk. So the check runs on what was rendered: the component marks
|
|
12
|
+
* a diagram wider than {@link diagramWidthLimit} with `data-diagram-too-wide`,
|
|
13
|
+
* `compiler/astro.ts` finds each mark with {@link oversizedDiagrams} on every
|
|
14
|
+
* built page and reports a `dia-too-wide` warning with the lesson file and the
|
|
15
|
+
* diagram id, and `content warnings` lists what the last build reported
|
|
16
|
+
* (`compiler/render-diagnostics.ts`). Under `astro dev` the component prints
|
|
17
|
+
* the same message as it renders.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The lesson column at a 1366px-wide window, the most common laptop screen
|
|
22
|
+
* (632px at 1280, 832px at 1920), unless the course sets
|
|
23
|
+
* `rules.V8.columnWidth`.
|
|
24
|
+
*/
|
|
25
|
+
declare const DEFAULT_COLUMN_WIDTH = 707;
|
|
26
|
+
/**
|
|
27
|
+
* The widest a diagram may lay out, or `undefined` when nothing checks it: rule
|
|
28
|
+
* V8 is off, or the diagram says `warnAboveWidth={false}`. A number in
|
|
29
|
+
* `warnAboveWidth` replaces the course's column width for that diagram.
|
|
30
|
+
*/
|
|
31
|
+
declare function diagramWidthLimit(warnAboveWidth: number | false | undefined, rules: Pick<ManifestRules, 'disabled' | 'settings'> | undefined): number | undefined;
|
|
32
|
+
/** What to tell the author about one diagram wider than its limit. */
|
|
33
|
+
declare function diagramTooWideMessage(id: string, width: number, limit: number): string;
|
|
34
|
+
/** A diagram on a built page that is wider than its limit. */
|
|
35
|
+
interface OversizedDiagram {
|
|
36
|
+
readonly code: 'dia-too-wide';
|
|
37
|
+
/** The diagram's `id`. */
|
|
38
|
+
readonly key: string;
|
|
39
|
+
readonly width: number;
|
|
40
|
+
readonly limit: number;
|
|
41
|
+
}
|
|
42
|
+
/** The diagrams `Diagram.astro` marked too wide in `html`. */
|
|
43
|
+
declare function oversizedDiagrams(html: string): OversizedDiagram[];
|
|
44
|
+
|
|
45
|
+
export { DEFAULT_COLUMN_WIDTH, type OversizedDiagram, diagramTooWideMessage, diagramWidthLimit, oversizedDiagrams };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { parse } from "parse5";
|
|
2
|
+
const DEFAULT_COLUMN_WIDTH = 707;
|
|
3
|
+
function diagramWidthLimit(warnAboveWidth, rules) {
|
|
4
|
+
if (warnAboveWidth === false || rules?.disabled.includes("V8")) return void 0;
|
|
5
|
+
if (warnAboveWidth !== void 0) return warnAboveWidth;
|
|
6
|
+
const columnWidth = rules?.settings.V8?.columnWidth;
|
|
7
|
+
return typeof columnWidth === "number" ? columnWidth : DEFAULT_COLUMN_WIDTH;
|
|
8
|
+
}
|
|
9
|
+
function diagramTooWideMessage(id, width, limit) {
|
|
10
|
+
return `Diagram "${id}" lays out ${Math.round(width)}px wide, wider than the ${limit}px it may take (rule V8), so a reader has to scroll it or widen it to see it whole. Try direction="TB", a shorter label, or a narrower node \`width\` (its label wraps inside it); set warnAboveWidth={false} only when the diagram is meant to scroll.`;
|
|
11
|
+
}
|
|
12
|
+
function isElement(node) {
|
|
13
|
+
return "tagName" in node;
|
|
14
|
+
}
|
|
15
|
+
function attribute(node, name) {
|
|
16
|
+
return node.attrs.find((attr) => attr.name === name)?.value;
|
|
17
|
+
}
|
|
18
|
+
function viewportKey(node) {
|
|
19
|
+
for (const child of "childNodes" in node ? node.childNodes : []) {
|
|
20
|
+
if (!isElement(child)) continue;
|
|
21
|
+
const match = /^diagram-(.+)-viewport$/.exec(attribute(child, "id") ?? "");
|
|
22
|
+
if (match) return match[1];
|
|
23
|
+
const inner = viewportKey(child);
|
|
24
|
+
if (inner) return inner;
|
|
25
|
+
}
|
|
26
|
+
return void 0;
|
|
27
|
+
}
|
|
28
|
+
function oversizedDiagrams(html) {
|
|
29
|
+
if (!html.includes("data-diagram-too-wide")) return [];
|
|
30
|
+
const found = [];
|
|
31
|
+
const visit = (node) => {
|
|
32
|
+
for (const child of "childNodes" in node ? node.childNodes : []) {
|
|
33
|
+
if (!isElement(child)) continue;
|
|
34
|
+
const limit = attribute(child, "data-diagram-too-wide");
|
|
35
|
+
if (limit !== void 0) {
|
|
36
|
+
found.push({
|
|
37
|
+
code: "dia-too-wide",
|
|
38
|
+
key: viewportKey(child) ?? attribute(child, "id") ?? "",
|
|
39
|
+
width: Number(attribute(child, "data-diagram-width")),
|
|
40
|
+
limit: Number(limit)
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
visit(child);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
visit(parse(html));
|
|
47
|
+
return found;
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
DEFAULT_COLUMN_WIDTH,
|
|
51
|
+
diagramTooWideMessage,
|
|
52
|
+
diagramWidthLimit,
|
|
53
|
+
oversizedDiagrams
|
|
54
|
+
};
|
|
@@ -45,19 +45,68 @@ interface DiagramLayout {
|
|
|
45
45
|
* would otherwise create a phantom zero-size node).
|
|
46
46
|
*/
|
|
47
47
|
declare function layoutDiagram(nodes: readonly DiagramNodeInput[], edges: readonly DiagramEdgeInput[], options?: DiagramLayoutOptions): DiagramLayout;
|
|
48
|
-
|
|
48
|
+
/** A node's text font size, in px. */
|
|
49
|
+
declare const NODE_FONT_PX = 13.6;
|
|
50
|
+
/**
|
|
51
|
+
* A node's formula font size, in px: KaTeX's own `.katex { font-size: 1.21em }`
|
|
52
|
+
* of the node's text. `katex.css` is unlayered, so no rule in the component's
|
|
53
|
+
* `@layer` can shrink it, and at that size KaTeX_Main's x-height matches the
|
|
54
|
+
* label's.
|
|
55
|
+
*/
|
|
56
|
+
declare const FORMULA_FONT_PX: number;
|
|
57
|
+
/** What a node holds, measured: its label, and its formula's box in px. */
|
|
58
|
+
interface NodeBoxContent {
|
|
59
|
+
readonly text?: string;
|
|
60
|
+
/** `measureFormula(latex, FORMULA_FONT_PX)`. */
|
|
61
|
+
readonly formula?: {
|
|
62
|
+
readonly width: number;
|
|
63
|
+
readonly height: number;
|
|
64
|
+
readonly depth: number;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
interface NodeBoxOptions {
|
|
68
|
+
/** The author's width: the label wraps inside it and the height follows. */
|
|
69
|
+
readonly width?: number;
|
|
70
|
+
/** The author's height. */
|
|
71
|
+
readonly height?: number;
|
|
72
|
+
/** The widest box a label is wrapped to fit before it takes a second line. */
|
|
73
|
+
readonly maxWidth?: number;
|
|
74
|
+
}
|
|
75
|
+
interface NodeBox {
|
|
49
76
|
readonly width: number;
|
|
50
77
|
readonly height: number;
|
|
78
|
+
/** The label as it is wrapped in DejaVu Sans. */
|
|
79
|
+
readonly lines: readonly string[];
|
|
51
80
|
}
|
|
52
81
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
82
|
+
* Size a node's box from its measured content: as wide as its widest line of
|
|
83
|
+
* text or its formula, whichever is wider, plus the inset, and as tall as its
|
|
84
|
+
* lines of text, the gap and its formula's line. A label wider than `maxWidth`
|
|
85
|
+
* wraps; a formula never does, so a wide formula widens the box instead and the
|
|
86
|
+
* label wraps to it. An author's `width` or `height` makes the box larger,
|
|
87
|
+
* never too small for its formula or its lines.
|
|
58
88
|
*/
|
|
59
|
-
declare function
|
|
60
|
-
|
|
61
|
-
|
|
89
|
+
declare function nodeBoxSize(content: NodeBoxContent, options?: NodeBoxOptions): NodeBox;
|
|
90
|
+
/** One node to fit: what it holds, and the author's size if any. */
|
|
91
|
+
interface FitNodeInput {
|
|
92
|
+
readonly id: string;
|
|
93
|
+
readonly content: NodeBoxContent;
|
|
94
|
+
readonly width?: number;
|
|
95
|
+
readonly height?: number;
|
|
96
|
+
}
|
|
97
|
+
interface FitOptions extends DiagramLayoutOptions {
|
|
98
|
+
/** The widest the diagram should lay out (rule V8's column); omit to skip fitting. */
|
|
99
|
+
readonly fitWidth?: number;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Size every node from its content and lay the diagram out. When `fitWidth`
|
|
103
|
+
* is set and the diagram comes out wider, wrap the labels of the nodes without
|
|
104
|
+
* an author's `width` narrower, step by step, until it fits; a diagram that
|
|
105
|
+
* still does not fit keeps its narrowest layout, and the caller warns.
|
|
106
|
+
*/
|
|
107
|
+
declare function fitDiagramLayout(nodes: readonly FitNodeInput[], edges: readonly DiagramEdgeInput[], options?: FitOptions): {
|
|
108
|
+
readonly layout: DiagramLayout;
|
|
109
|
+
readonly boxes: ReadonlyMap<string, NodeBox>;
|
|
110
|
+
};
|
|
62
111
|
|
|
63
|
-
export { type
|
|
112
|
+
export { type DiagramEdgeInput, type DiagramLayout, type DiagramLayoutOptions, type DiagramNodeInput, FORMULA_FONT_PX, type FitNodeInput, type FitOptions, type LaidOutEdge, type LaidOutNode, NODE_FONT_PX, type NodeBox, type NodeBoxContent, type NodeBoxOptions, type Point, fitDiagramLayout, layoutDiagram, nodeBoxSize };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import dagre from "@dagrejs/dagre";
|
|
2
|
+
import { textWidth, wrapText } from "./text-measure.js";
|
|
2
3
|
function layoutDiagram(nodes, edges, options = {}) {
|
|
3
4
|
const ids = /* @__PURE__ */ new Set();
|
|
4
5
|
for (const node of nodes) {
|
|
@@ -19,6 +20,11 @@ function layoutDiagram(nodes, edges, options = {}) {
|
|
|
19
20
|
if (!ids.has(edge.to)) {
|
|
20
21
|
throw new Error(`Diagram edge references unknown node "${edge.to}".`);
|
|
21
22
|
}
|
|
23
|
+
if (edge.from === edge.to) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`Diagram edge from "${edge.from}" to itself: a diagram draws no loop on one node. Name the repetition in the label ("Repeat until it converges"), or draw the step it returns to as its own node.`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
22
28
|
}
|
|
23
29
|
const graph = new dagre.graphlib.Graph();
|
|
24
30
|
graph.setGraph({
|
|
@@ -91,32 +97,87 @@ function layoutDiagram(nodes, edges, options = {}) {
|
|
|
91
97
|
height: maxY - minY + padding * 2
|
|
92
98
|
};
|
|
93
99
|
}
|
|
94
|
-
const
|
|
100
|
+
const NODE_FONT_PX = 13.6;
|
|
101
|
+
const FORMULA_FONT_PX = NODE_FONT_PX * 1.21;
|
|
102
|
+
const LINE_PX = 17;
|
|
103
|
+
const CSS_PADDING_X = 8;
|
|
104
|
+
const INSET_X = 16;
|
|
105
|
+
const INSET_Y = 14;
|
|
106
|
+
const GAP_PX = 2.4;
|
|
107
|
+
const FORMULA_ASCENT_PX = 14;
|
|
108
|
+
const FORMULA_DESCENT_PX = 5.5;
|
|
95
109
|
const MIN_WIDTH = 90;
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
110
|
+
const DEFAULT_MAX_WIDTH = 220;
|
|
111
|
+
function balancedLines(text, maxWidth) {
|
|
112
|
+
const greedy = wrapText(text, maxWidth, NODE_FONT_PX);
|
|
113
|
+
if (greedy.length <= 1) return greedy;
|
|
114
|
+
let low = 1;
|
|
115
|
+
let high = Math.ceil(maxWidth);
|
|
116
|
+
while (low < high) {
|
|
117
|
+
const middle = Math.floor((low + high) / 2);
|
|
118
|
+
if (wrapText(text, middle, NODE_FONT_PX).length <= greedy.length) high = middle;
|
|
119
|
+
else low = middle + 1;
|
|
120
|
+
}
|
|
121
|
+
return wrapText(text, high, NODE_FONT_PX);
|
|
122
|
+
}
|
|
123
|
+
function nodeBoxSize(content, options = {}) {
|
|
124
|
+
const text = content.text?.trim() ?? "";
|
|
125
|
+
const formula = content.formula;
|
|
126
|
+
const formulaWidth = formula?.width ?? 0;
|
|
127
|
+
let width;
|
|
128
|
+
let lines;
|
|
129
|
+
if (options.width !== void 0) {
|
|
130
|
+
width = Math.max(options.width, Math.ceil(formulaWidth + 2 * CSS_PADDING_X));
|
|
131
|
+
lines = text ? wrapText(text, width - 2 * CSS_PADDING_X, NODE_FONT_PX) : [];
|
|
132
|
+
} else {
|
|
133
|
+
const maxContent = Math.max(
|
|
134
|
+
(options.maxWidth ?? DEFAULT_MAX_WIDTH) - 2 * INSET_X,
|
|
135
|
+
formulaWidth
|
|
136
|
+
);
|
|
137
|
+
lines = text ? balancedLines(text, maxContent) : [];
|
|
138
|
+
const contentWidth = Math.max(
|
|
139
|
+
formulaWidth,
|
|
140
|
+
...lines.map((line) => textWidth(line, NODE_FONT_PX))
|
|
141
|
+
);
|
|
142
|
+
width = Math.max(MIN_WIDTH, Math.ceil(contentWidth + 2 * INSET_X));
|
|
143
|
+
}
|
|
144
|
+
const formulaHeight = formula ? Math.max(FORMULA_ASCENT_PX, formula.height) + Math.max(FORMULA_DESCENT_PX, formula.depth) : 0;
|
|
145
|
+
const gap = lines.length > 0 && formula ? GAP_PX : 0;
|
|
146
|
+
const contentHeight = Math.ceil(lines.length * LINE_PX + gap + formulaHeight + 2 * INSET_Y);
|
|
147
|
+
const height = Math.max(options.height ?? 0, contentHeight);
|
|
148
|
+
return { width, height, lines };
|
|
149
|
+
}
|
|
150
|
+
const FIT_MAX_WIDTHS = [DEFAULT_MAX_WIDTH, 200, 180, 160, 140, 120];
|
|
151
|
+
function fitDiagramLayout(nodes, edges, options = {}) {
|
|
152
|
+
let result;
|
|
153
|
+
for (const maxWidth of FIT_MAX_WIDTHS) {
|
|
154
|
+
const boxes = new Map(
|
|
155
|
+
nodes.map((node) => [
|
|
156
|
+
node.id,
|
|
157
|
+
nodeBoxSize(node.content, {
|
|
158
|
+
maxWidth,
|
|
159
|
+
...node.width !== void 0 ? { width: node.width } : {},
|
|
160
|
+
...node.height !== void 0 ? { height: node.height } : {}
|
|
161
|
+
})
|
|
162
|
+
])
|
|
163
|
+
);
|
|
164
|
+
const layout = layoutDiagram(
|
|
165
|
+
nodes.map((node) => ({ id: node.id, ...sizeOf(boxes.get(node.id)) })),
|
|
166
|
+
edges,
|
|
167
|
+
options
|
|
168
|
+
);
|
|
169
|
+
result = { layout, boxes };
|
|
170
|
+
if (options.fitWidth === void 0 || layout.width <= options.fitWidth) break;
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
106
173
|
}
|
|
107
|
-
function
|
|
108
|
-
|
|
109
|
-
const visibleLength = kind === "formula" ? formulaVisibleLength(content) : content.trim().length || 1;
|
|
110
|
-
const charWidth = CHAR_WIDTH[kind];
|
|
111
|
-
const naturalWidth = visibleLength * charWidth + PADDING_X * 2;
|
|
112
|
-
const width = Math.min(maxWidth, Math.max(MIN_WIDTH, naturalWidth));
|
|
113
|
-
const usableWidth = width - PADDING_X * 2;
|
|
114
|
-
const charsPerLine = Math.max(1, Math.floor(usableWidth / charWidth));
|
|
115
|
-
const lines = Math.max(1, Math.ceil(visibleLength / charsPerLine));
|
|
116
|
-
const height = lines * LINE_HEIGHT + PADDING_Y * 2;
|
|
117
|
-
return { width: Math.round(width), height: Math.round(height) };
|
|
174
|
+
function sizeOf(box) {
|
|
175
|
+
return { width: box.width, height: box.height };
|
|
118
176
|
}
|
|
119
177
|
export {
|
|
120
|
-
|
|
121
|
-
|
|
178
|
+
FORMULA_FONT_PX,
|
|
179
|
+
NODE_FONT_PX,
|
|
180
|
+
fitDiagramLayout,
|
|
181
|
+
layoutDiagram,
|
|
182
|
+
nodeBoxSize
|
|
122
183
|
};
|
|
@@ -2,7 +2,7 @@ import { KeyedFloatLabelRecord, KeyedFloatLessonInput, KeyedFloatValidation } fr
|
|
|
2
2
|
|
|
3
3
|
/** `validateKeyedFloats` for `<Diagram>` — see `keyed-float-validate.ts`. */
|
|
4
4
|
|
|
5
|
-
type DiagramDiagnosticCode = 'dia-invalid-key' | 'dia-duplicate-key' | 'dia-ref-resolves' | 'dia-key-unused' | 'dia-removed-prop';
|
|
5
|
+
type DiagramDiagnosticCode = 'dia-invalid-key' | 'dia-duplicate-key' | 'dia-ref-resolves' | 'dia-key-unused' | 'dia-removed-prop' | 'dia-unread-prop';
|
|
6
6
|
interface DiagramDiagnostic {
|
|
7
7
|
readonly code: DiagramDiagnosticCode;
|
|
8
8
|
readonly severity: 'error' | 'warning' | 'info';
|
|
@@ -25,7 +25,7 @@ interface EquationRefRecord {
|
|
|
25
25
|
/** Target lesson slug for a cross-page reference; `undefined` = same page. */
|
|
26
26
|
readonly targetSlug?: string;
|
|
27
27
|
}
|
|
28
|
-
type EquationDiagnosticCode = 'eq-duplicate-key' | 'eq-ref-resolves' | 'eq-key-unused';
|
|
28
|
+
type EquationDiagnosticCode = 'eq-duplicate-key' | 'eq-ref-resolves' | 'eq-key-unused' | 'eq-label-repeated' | 'eq-latex-ref';
|
|
29
29
|
interface EquationDiagnostic {
|
|
30
30
|
readonly code: EquationDiagnosticCode;
|
|
31
31
|
readonly severity: 'error' | 'warning' | 'info';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { parseEquationRef, scanEquationLabels } from "./equations.mjs";
|
|
1
|
+
import { parseEquationRef, repeatedEquationLabels, scanEquationLabels } from "./equations.mjs";
|
|
2
|
+
import { proseOnly } from "./gate-prose-notation.js";
|
|
2
3
|
const BRACKET_REF = /\[\[\s*([^\][]+?)\s*\]\]/g;
|
|
3
4
|
function stripCode(text) {
|
|
4
5
|
return text.replace(/```[\s\S]*?```/g, " ").replace(/~~~[\s\S]*?~~~/g, " ").replace(/`[^`\n]*`/g, " ");
|
|
@@ -16,6 +17,7 @@ function bodyEquationRefs(lesson) {
|
|
|
16
17
|
}
|
|
17
18
|
return refs;
|
|
18
19
|
}
|
|
20
|
+
const LATEX_REFERENCE = /\\(eqref|ref|autoref|cref|Cref)\s*\{([^{}\n]*)\}/g;
|
|
19
21
|
function validateEquations(lessons) {
|
|
20
22
|
const labels = [];
|
|
21
23
|
const diagnostics = [];
|
|
@@ -50,6 +52,29 @@ function validateEquations(lessons) {
|
|
|
50
52
|
});
|
|
51
53
|
}
|
|
52
54
|
numbersBySlug[lesson.slug] = table;
|
|
55
|
+
for (const match of proseOnly(lesson.body).matchAll(LATEX_REFERENCE)) {
|
|
56
|
+
const [, command, target = ""] = match;
|
|
57
|
+
const [prefix, key] = target.includes(":") ? target.split(":", 2) : ["eq", target];
|
|
58
|
+
diagnostics.push({
|
|
59
|
+
code: "eq-latex-ref",
|
|
60
|
+
severity: "error",
|
|
61
|
+
message: `${lesson.lessonId} writes \\${command}{${target}}, LaTeX's reference: MDX reads its {\u2026} as JavaScript, and the page fails to build. Write [[${prefix}-${key}]].`,
|
|
62
|
+
file: lesson.file,
|
|
63
|
+
lessonId: lesson.lessonId,
|
|
64
|
+
key: key ?? target
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
for (const keys of repeatedEquationLabels(lesson.body)) {
|
|
68
|
+
const [first, ...rest] = keys;
|
|
69
|
+
diagnostics.push({
|
|
70
|
+
code: "eq-label-repeated",
|
|
71
|
+
severity: "error",
|
|
72
|
+
message: `${lesson.lessonId} puts ${keys.map((key) => `\\label{eq:${key}}`).join(" and ")} in one display block, which is one numbered equation: eq:${first} names it, and ${rest.map((key) => `[[eq-${key}]]`).join(", ")} would point at nothing. Split the block into one $$\u2026$$ block per equation you refer to.`,
|
|
73
|
+
file: lesson.file,
|
|
74
|
+
lessonId: lesson.lessonId,
|
|
75
|
+
key: rest[0]
|
|
76
|
+
});
|
|
77
|
+
}
|
|
53
78
|
}
|
|
54
79
|
const referenced = /* @__PURE__ */ new Set();
|
|
55
80
|
for (const lesson of lessons) {
|