explico 0.12.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. package/CHANGELOG.md +1137 -1
  2. package/README.md +91 -13
  3. package/dist/compiler/astro.d.ts +4 -3
  4. package/dist/compiler/astro.js +45 -15
  5. package/dist/compiler/cli.d.ts +26 -12
  6. package/dist/compiler/cli.js +96 -25
  7. package/dist/compiler/collections.d.ts +9 -1
  8. package/dist/compiler/collections.js +55 -16
  9. package/dist/compiler/content-schema.d.ts +31 -0
  10. package/dist/compiler/content-schema.js +82 -0
  11. package/dist/compiler/content-schemas.d.ts +422 -0
  12. package/dist/compiler/content-schemas.js +229 -0
  13. package/dist/compiler/course-config.d.ts +1 -1
  14. package/dist/compiler/course-config.js +30 -2
  15. package/dist/compiler/exports/combined-html.d.ts +97 -0
  16. package/dist/compiler/exports/combined-html.js +492 -0
  17. package/dist/compiler/exports/download-index.d.ts +9 -0
  18. package/dist/compiler/exports/download-index.js +33 -0
  19. package/dist/compiler/exports/export-chapters.d.ts +98 -0
  20. package/dist/compiler/exports/export-chapters.js +164 -0
  21. package/dist/compiler/exports/export-pages.d.ts +48 -0
  22. package/dist/compiler/exports/export-pages.js +22 -0
  23. package/dist/compiler/exports/html-tree.d.ts +22 -0
  24. package/dist/compiler/exports/html-tree.js +47 -0
  25. package/dist/compiler/exports/latex/capture.d.ts +106 -0
  26. package/dist/compiler/exports/latex/capture.js +435 -0
  27. package/dist/compiler/exports/latex/compile.d.ts +73 -0
  28. package/dist/compiler/exports/latex/compile.js +115 -0
  29. package/dist/compiler/exports/latex/document.d.ts +102 -0
  30. package/dist/compiler/exports/latex/document.js +349 -0
  31. package/dist/compiler/exports/latex/explico.cls +415 -0
  32. package/dist/compiler/exports/latex/numbering.d.ts +18 -0
  33. package/dist/compiler/exports/latex/numbering.js +57 -0
  34. package/dist/compiler/exports/latex/serialize.d.ts +112 -0
  35. package/dist/compiler/exports/latex/serialize.js +606 -0
  36. package/dist/compiler/exports/latex-export.d.ts +44 -0
  37. package/dist/compiler/exports/latex-export.js +169 -0
  38. package/dist/compiler/exports/markdown/serialize.d.ts +51 -0
  39. package/dist/compiler/exports/markdown/serialize.js +527 -0
  40. package/dist/compiler/exports/markdown-export.d.ts +39 -0
  41. package/dist/compiler/exports/markdown-export.js +240 -0
  42. package/dist/compiler/exports/standalone-html.d.ts +14 -5
  43. package/dist/compiler/exports/standalone-html.js +23 -51
  44. package/dist/compiler/exports/static-tree.d.ts +253 -0
  45. package/dist/compiler/exports/static-tree.js +625 -0
  46. package/dist/compiler/exports/zip.d.ts +16 -0
  47. package/dist/compiler/exports/zip.js +72 -0
  48. package/dist/compiler/lesson-derivation.d.ts +8 -2
  49. package/dist/compiler/lesson-derivation.js +6 -7
  50. package/dist/compiler/manifest.d.ts +87 -6
  51. package/dist/compiler/manifest.js +174 -31
  52. package/dist/compiler/render-diagnostics.d.ts +65 -0
  53. package/dist/compiler/render-diagnostics.js +56 -0
  54. package/dist/compiler/resolution-report.d.ts +2 -1
  55. package/dist/{course-config-Dsnri9Tk.d.ts → course-config-rRtME5UV.d.ts} +56 -13
  56. package/dist/curriculum/validation.d.ts +9 -3
  57. package/dist/curriculum/validation.js +35 -17
  58. package/dist/reference/base-abbreviations.d.mts +2 -2
  59. package/dist/reference/base-abbreviations.mjs +2 -2
  60. package/dist/reference/base-operators.d.mts +2 -2
  61. package/dist/reference/base-operators.mjs +2 -2
  62. package/dist/reference/base-roman-labels.d.mts +4 -3
  63. package/dist/reference/base-roman-labels.mjs +4 -3
  64. package/dist/reference/citation-format.d.mts +35 -0
  65. package/dist/reference/citation-format.mjs +74 -0
  66. package/dist/reference/consistency.d.ts +5 -9
  67. package/dist/reference/consistency.js +34 -38
  68. package/dist/reference/curriculum-alignment.js +3 -24
  69. package/dist/reference/derived-entries.d.ts +45 -0
  70. package/dist/reference/derived-entries.js +50 -0
  71. package/dist/reference/derived-notation.d.mts +125 -0
  72. package/dist/reference/derived-notation.mjs +121 -0
  73. package/dist/reference/diagram-fit.d.ts +45 -0
  74. package/dist/reference/diagram-fit.js +54 -0
  75. package/dist/reference/diagram-layout.d.ts +59 -10
  76. package/dist/reference/diagram-layout.js +85 -24
  77. package/dist/reference/diagrams-validate.d.ts +1 -1
  78. package/dist/reference/equations-validate.d.ts +1 -1
  79. package/dist/reference/equations-validate.js +26 -1
  80. package/dist/reference/equations.d.mts +27 -0
  81. package/dist/reference/equations.mjs +120 -22
  82. package/dist/reference/examples-validate.d.ts +1 -1
  83. package/dist/reference/figure-fit.d.ts +70 -0
  84. package/dist/reference/figure-fit.js +60 -0
  85. package/dist/reference/figure-pictures.d.ts +27 -0
  86. package/dist/reference/figure-pictures.js +49 -0
  87. package/dist/reference/figures-validate.d.ts +1 -1
  88. package/dist/reference/gate-abbreviations.d.ts +1 -1
  89. package/dist/reference/gate-abbreviations.js +1 -1
  90. package/dist/reference/gate-course-rules.d.ts +4 -3
  91. package/dist/reference/gate-course-rules.js +9 -4
  92. package/dist/reference/gate-heading-length.d.ts +2 -0
  93. package/dist/reference/gate-heading-length.js +7 -5
  94. package/dist/reference/gate-heading-structure.js +26 -0
  95. package/dist/reference/gate-indices.d.ts +35 -0
  96. package/dist/reference/gate-indices.js +299 -0
  97. package/dist/reference/gate-math.d.ts +32 -2
  98. package/dist/reference/gate-math.js +68 -12
  99. package/dist/reference/gate-mdx-expression.d.ts +17 -0
  100. package/dist/reference/gate-mdx-expression.js +29 -0
  101. package/dist/reference/gate-part-pages.d.ts +1 -1
  102. package/dist/reference/gate-prop-text.d.ts +28 -0
  103. package/dist/reference/gate-prop-text.js +106 -0
  104. package/dist/reference/gate-prose-notation.d.ts +3 -1
  105. package/dist/reference/gate-prose-notation.js +4 -2
  106. package/dist/reference/gate-retired-keys.d.ts +35 -0
  107. package/dist/reference/gate-retired-keys.js +58 -0
  108. package/dist/reference/gloss.d.ts +2 -0
  109. package/dist/reference/katex-measure.d.ts +17 -0
  110. package/dist/reference/katex-measure.js +223 -0
  111. package/dist/reference/keyed-float-validate.js +12 -0
  112. package/dist/reference/keyed-jsx-floats.d.mts +2 -2
  113. package/dist/reference/keyed-jsx-floats.mjs +11 -14
  114. package/dist/reference/lab-math-scope.d.ts +1 -0
  115. package/dist/reference/lab-math-scope.js +13 -2
  116. package/dist/reference/math-glyphs.d.mts +94 -2
  117. package/dist/reference/math-glyphs.mjs +851 -46
  118. package/dist/reference/mixed-content.d.ts +8 -3
  119. package/dist/reference/mixed-content.js +4 -2
  120. package/dist/reference/page-notation.d.ts +34 -4
  121. package/dist/reference/page-notation.js +54 -2
  122. package/dist/reference/references.d.ts +11 -1
  123. package/dist/reference/references.js +6 -0
  124. package/dist/reference/registry.js +104 -10
  125. package/dist/reference/rehype-fail-katex-errors.d.mts +9 -0
  126. package/dist/reference/rehype-fail-katex-errors.mjs +19 -0
  127. package/dist/reference/rehype-heading-anchors.mjs +11 -1
  128. package/dist/reference/remark-citation.mjs +0 -0
  129. package/dist/reference/remark-notation.mjs +84 -20
  130. package/dist/reference/render-lab-math.d.ts +2 -0
  131. package/dist/reference/render-lab-math.js +17 -5
  132. package/dist/reference/sections-validate.d.ts +28 -3
  133. package/dist/reference/sections-validate.js +31 -4
  134. package/dist/reference/sections.d.mts +24 -6
  135. package/dist/reference/sections.mjs +92 -17
  136. package/dist/reference/tables-validate.d.ts +1 -1
  137. package/dist/reference/tables-validate.js +24 -1
  138. package/dist/reference/text-measure.d.ts +10 -0
  139. package/dist/reference/text-measure.js +41 -0
  140. package/dist/reference/text-widths.d.ts +10 -0
  141. package/dist/reference/text-widths.js +1238 -0
  142. package/dist/reference/types.d.ts +64 -8
  143. package/dist/rules/catalog.js +191 -30
  144. package/dist/rules/in-effect.d.ts +1 -1
  145. package/dist/rules/rules.schema.json +79 -24
  146. package/dist/rules/settings.d.ts +1 -1
  147. package/dist/rules/settings.js +7 -0
  148. package/dist/session/PreferenceStore.d.ts +18 -1
  149. package/dist/session/PreferenceStore.js +29 -1
  150. package/docs/authoring/README.md +32 -21
  151. package/docs/authoring/agents.md +38 -7
  152. package/docs/authoring/components.md +111 -6
  153. package/docs/authoring/migrating.md +707 -0
  154. package/docs/authoring/notation.md +281 -29
  155. package/docs/authoring/retired.md +1 -0
  156. package/docs/authoring/structure-rules.md +10 -41
  157. package/docs/authoring/structure.md +53 -2
  158. package/docs/authoring/visualizations.md +144 -18
  159. package/package.json +12 -7
  160. package/src/compiler/astro.ts +57 -17
  161. package/src/compiler/cli.ts +143 -35
  162. package/src/compiler/collections.ts +92 -19
  163. package/src/compiler/content-schema.ts +108 -0
  164. package/src/compiler/content-schemas.ts +391 -0
  165. package/src/compiler/course-config.ts +86 -15
  166. package/src/compiler/exports/combined-html.ts +669 -0
  167. package/src/compiler/exports/download-index.ts +36 -0
  168. package/src/compiler/exports/export-chapters.ts +233 -0
  169. package/src/compiler/exports/export-pages.ts +44 -0
  170. package/src/compiler/exports/html-tree.ts +62 -0
  171. package/src/compiler/exports/latex/capture.ts +651 -0
  172. package/src/compiler/exports/latex/compile.ts +168 -0
  173. package/src/compiler/exports/latex/document.ts +479 -0
  174. package/src/compiler/exports/latex/explico.cls +415 -0
  175. package/src/compiler/exports/latex/numbering.ts +74 -0
  176. package/src/compiler/exports/latex/serialize.ts +787 -0
  177. package/src/compiler/exports/latex/texlive-packages.txt +76 -0
  178. package/src/compiler/exports/latex-export.ts +255 -0
  179. package/src/compiler/exports/markdown/serialize.ts +632 -0
  180. package/src/compiler/exports/markdown-export.ts +329 -0
  181. package/src/compiler/exports/standalone-html.ts +39 -70
  182. package/src/compiler/exports/static-tree.ts +944 -0
  183. package/src/compiler/exports/zip.ts +88 -0
  184. package/src/compiler/lesson-derivation.ts +13 -8
  185. package/src/compiler/manifest.ts +299 -28
  186. package/src/compiler/render-diagnostics.ts +111 -0
  187. package/src/components/HoverPanel.astro +204 -6
  188. package/src/components/PartOverview.astro +12 -9
  189. package/src/components/abbreviation/AbbreviationLayer.astro +21 -12
  190. package/src/components/assessments/AssessmentRunner.tsx +7 -1
  191. package/src/components/assessments/AssessmentSet.astro +14 -1
  192. package/src/components/block-heading.ts +38 -0
  193. package/src/components/card-arrow.ts +18 -4
  194. package/src/components/card-carry.ts +406 -0
  195. package/src/components/card-drag.ts +1 -30
  196. package/src/components/card-fetch.ts +87 -0
  197. package/src/components/card-manager.ts +1222 -38
  198. package/src/components/card-memory.ts +5 -1
  199. package/src/components/card-placement.ts +211 -13
  200. package/src/components/card-side.ts +281 -31
  201. package/src/components/citation/CitationLayer.astro +45 -40
  202. package/src/components/diagram/Diagram.astro +123 -44
  203. package/src/components/figure/Figure.astro +11 -0
  204. package/src/components/figure/FloatCaption.astro +4 -17
  205. package/src/components/float-toolbar.ts +274 -0
  206. package/src/components/memo/Memo.astro +21 -10
  207. package/src/components/memo/ShortNote.astro +4 -0
  208. package/src/components/notation/NotationGlossary.astro +22 -17
  209. package/src/components/notation/NotationLayer.astro +321 -79
  210. package/src/components/notation/NotationMath.astro +8 -1
  211. package/src/components/notation/render-notation-math.ts +51 -5
  212. package/src/components/readable-text.ts +25 -0
  213. package/src/components/reference/FloatRefLayer.astro +162 -89
  214. package/src/components/starlight/HeadingAnchorEnhancer.astro +13 -8
  215. package/src/components/starlight/KeyedFloatEnhancer.astro +28 -9
  216. package/src/components/starlight/LayoutPageSidebar.astro +17 -3
  217. package/src/components/starlight/LayoutSocialIcons.astro +79 -29
  218. package/src/components/starlight/LessonExportActions.astro +384 -310
  219. package/src/components/starlight/LessonFooter.astro +47 -9
  220. package/src/components/starlight/PrintController.astro +28 -0
  221. package/src/components/starlight/TextSizeControl.astro +137 -0
  222. package/src/components/starlight/print-controller.ts +380 -0
  223. package/src/components/starlight/section-summary.ts +13 -5
  224. package/src/components/table/Table.astro +13 -21
  225. package/src/components/table/table-truncation.ts +15 -0
  226. package/src/content-config.ts +20 -312
  227. package/src/curriculum/validation.ts +72 -24
  228. package/src/reference/base-abbreviations.mjs +2 -2
  229. package/src/reference/base-operators.mjs +2 -2
  230. package/src/reference/base-roman-labels.mjs +4 -3
  231. package/src/reference/citation-format.mjs +74 -0
  232. package/src/reference/consistency.ts +47 -61
  233. package/src/reference/curriculum-alignment.ts +4 -41
  234. package/src/reference/derived-entries.ts +68 -0
  235. package/src/reference/derived-notation.mjs +121 -0
  236. package/src/reference/diagram-fit.ts +104 -0
  237. package/src/reference/diagram-layout.ts +182 -51
  238. package/src/reference/diagrams-validate.ts +2 -1
  239. package/src/reference/equations-validate.ts +41 -2
  240. package/src/reference/equations.mjs +120 -22
  241. package/src/reference/examples-validate.ts +1 -0
  242. package/src/reference/figure-fit.ts +139 -0
  243. package/src/reference/figure-pictures.ts +96 -0
  244. package/src/reference/figures-validate.ts +2 -1
  245. package/src/reference/gate-abbreviations.ts +1 -1
  246. package/src/reference/gate-course-rules.ts +22 -7
  247. package/src/reference/gate-heading-length.ts +25 -7
  248. package/src/reference/gate-heading-structure.ts +40 -1
  249. package/src/reference/gate-indices.ts +470 -0
  250. package/src/reference/gate-math.ts +102 -13
  251. package/src/reference/gate-mdx-expression.ts +45 -0
  252. package/src/reference/gate-prop-text.ts +145 -0
  253. package/src/reference/gate-prose-notation.ts +19 -5
  254. package/src/reference/gate-retired-keys.ts +101 -0
  255. package/src/reference/gloss.ts +2 -0
  256. package/src/reference/katex-measure.ts +384 -0
  257. package/src/reference/keyed-float-validate.ts +22 -0
  258. package/src/reference/keyed-jsx-floats.mjs +11 -14
  259. package/src/reference/lab-math-scope.ts +15 -1
  260. package/src/reference/math-glyphs.mjs +851 -46
  261. package/src/reference/mixed-content.ts +11 -2
  262. package/src/reference/page-notation.ts +100 -3
  263. package/src/reference/references.ts +14 -0
  264. package/src/reference/registry.ts +154 -8
  265. package/src/reference/rehype-fail-katex-errors.mjs +19 -0
  266. package/src/reference/rehype-heading-anchors.mjs +11 -1
  267. package/src/reference/remark-citation.mjs +0 -0
  268. package/src/reference/remark-notation.mjs +84 -20
  269. package/src/reference/render-lab-math.ts +29 -5
  270. package/src/reference/sections-validate.ts +67 -3
  271. package/src/reference/sections.mjs +92 -17
  272. package/src/reference/tables-validate.ts +40 -2
  273. package/src/reference/text-measure.ts +63 -0
  274. package/src/reference/text-widths.ts +133 -0
  275. package/src/reference/types.ts +75 -8
  276. package/src/rules/catalog.ts +205 -29
  277. package/src/rules/settings.ts +10 -0
  278. package/src/session/PreferenceStore.ts +53 -0
  279. package/src/styles/global.css +291 -75
@@ -0,0 +1,527 @@
1
+ import { gfmTableToMarkdown } from "mdast-util-gfm-table";
2
+ import { mathToMarkdown } from "mdast-util-math";
3
+ import { toMarkdown } from "mdast-util-to-markdown";
4
+ import { captionTitle } from "../../../components/float-name.js";
5
+ import { stripNotationMarkers } from "../../../reference/math-glyphs.mjs";
6
+ const text = (value) => ({ type: "text", value });
7
+ const html = (value) => ({ type: "html", value });
8
+ const strong = (children) => ({ type: "strong", children });
9
+ const emphasis = (children) => ({ type: "emphasis", children });
10
+ const paragraph = (children) => ({ type: "paragraph", children });
11
+ const link = (url, children) => ({ type: "link", url, children });
12
+ const anchor = (id) => html(`<a id="${id.replace(/"/gu, "&quot;")}"></a>`);
13
+ function plainTex(tex) {
14
+ return stripNotationMarkers(tex).trim();
15
+ }
16
+ function cellTex(tex) {
17
+ return tex.replace(/\\\|/gu, "\\Vert ").replace(/\|/gu, "\\vert ");
18
+ }
19
+ const ALERTS = {
20
+ note: "NOTE",
21
+ tip: "TIP",
22
+ caution: "WARNING",
23
+ danger: "CAUTION"
24
+ };
25
+ function plainText(inlines) {
26
+ return inlines.map((inline) => {
27
+ switch (inline.kind) {
28
+ case "text":
29
+ case "code":
30
+ return inline.value;
31
+ case "math":
32
+ return plainTex(inline.tex);
33
+ case "break":
34
+ return " ";
35
+ case "cite":
36
+ return `[${inline.number}]`;
37
+ case "memoMark":
38
+ return inline.label;
39
+ default:
40
+ return "children" in inline ? plainText(inline.children) : "";
41
+ }
42
+ }).join("");
43
+ }
44
+ function diagramEdges(geometry) {
45
+ const nearest = ([x, y]) => {
46
+ let best = -1;
47
+ let bestDistance = Number.POSITIVE_INFINITY;
48
+ geometry.nodes.forEach((node, index) => {
49
+ const dx = Math.max(node.x - x, 0, x - (node.x + node.width));
50
+ const dy = Math.max(node.y - y, 0, y - (node.y + node.height));
51
+ const distance = Math.hypot(dx, dy);
52
+ if (distance < bestDistance) [best, bestDistance] = [index, distance];
53
+ });
54
+ return best;
55
+ };
56
+ return geometry.edges.flatMap((edge) => {
57
+ const start = edge[0];
58
+ const end = edge.at(-1);
59
+ if (!start || !end) return [];
60
+ const from = nearest(start);
61
+ const to = nearest(end);
62
+ return from === -1 || to === -1 || from === to ? [] : [[from, to]];
63
+ });
64
+ }
65
+ class MarkdownWriter {
66
+ constructor(context) {
67
+ this.context = context;
68
+ this.route = context.page.route;
69
+ this.notationAnchors = new Map(context.page.notation.map((entry) => [entry.key, entry.anchor]));
70
+ }
71
+ context;
72
+ route;
73
+ notationAnchors;
74
+ /** The link to an anchor on a page: into its file when the download has it, else the website. */
75
+ targetUrl(target) {
76
+ const file = this.context.documentFiles.get(target.route);
77
+ if (file !== void 0) {
78
+ if (!target.anchor) return file || "#";
79
+ return `${file}#${target.anchor}`;
80
+ }
81
+ const base = this.context.canonicalBaseUrl;
82
+ if (!base) return void 0;
83
+ return `${base}${target.route ? `${target.route}/` : ""}${target.anchor ? `#${target.anchor}` : ""}`;
84
+ }
85
+ inline(inline, inTable = false) {
86
+ switch (inline.kind) {
87
+ case "text":
88
+ return [text(inline.value)];
89
+ case "emphasis":
90
+ return [emphasis(this.inlines(inline.children, inTable))];
91
+ case "strong":
92
+ return [strong(this.inlines(inline.children, inTable))];
93
+ case "superscript":
94
+ return [html("<sup>"), ...this.inlines(inline.children, inTable), html("</sup>")];
95
+ case "subscript":
96
+ return [html("<sub>"), ...this.inlines(inline.children, inTable), html("</sub>")];
97
+ case "code":
98
+ return [{ type: "inlineCode", value: inline.value }];
99
+ case "math": {
100
+ const tex = plainTex(inline.tex);
101
+ return tex ? [{ type: "inlineMath", value: inTable ? cellTex(tex) : tex }] : [];
102
+ }
103
+ case "break":
104
+ return inTable ? [html("<br>")] : [{ type: "break" }];
105
+ case "link":
106
+ return [link(inline.href, this.inlines(inline.children, inTable))];
107
+ case "ref": {
108
+ const url = this.targetUrl(inline.target);
109
+ const children = this.inlines(inline.children, inTable);
110
+ return url ? [link(url, children)] : children;
111
+ }
112
+ case "term": {
113
+ const target = this.notationAnchors.get(inline.key);
114
+ const children = this.inlines(inline.children, inTable);
115
+ return target ? [link(`#${target}`, children)] : children;
116
+ }
117
+ case "cite":
118
+ return [link(`#${inline.anchor}`, [text(`[${inline.number}]`)])];
119
+ case "memoMark":
120
+ return [html("<sup>"), link(`#${inline.anchor}`, [text(inline.label)]), html("</sup>")];
121
+ case "noteMark":
122
+ return [link(`#${inline.anchor}`, this.inlines(inline.children, inTable))];
123
+ }
124
+ }
125
+ inlines(inlines, inTable = false) {
126
+ return inlines.flatMap((inline) => this.inline(inline, inTable));
127
+ }
128
+ /** A choice's label, its math as math like the prompt's. */
129
+ optionLabel(option) {
130
+ return option.labelSegments ? this.segments(option.labelSegments) : [text(option.label)];
131
+ }
132
+ segments(segments) {
133
+ return segments.flatMap(
134
+ (segment) => segment.type === "math" ? [{ type: "inlineMath", value: plainTex(segment.latex) }] : [text(segment.value)]
135
+ );
136
+ }
137
+ /** A float's caption line: its number and short caption in bold, then the long one. */
138
+ captionLine(label, block, extra = []) {
139
+ const short = block.short?.trim();
140
+ const head = short ? `${label}. ${captionTitle(short)}` : `${label}.`;
141
+ const long = [...this.inlines(block.caption), ...extra.length > 0 ? [text(" "), ...this.inlines(extra)] : []];
142
+ return [strong([text(head)]), ...long.length > 0 ? [text(" "), ...long] : []];
143
+ }
144
+ picture(key, alt) {
145
+ const file = this.context.pictures.get(key);
146
+ return file ? { type: "image", url: file, alt } : void 0;
147
+ }
148
+ table(block) {
149
+ const rows = [...block.head, ...block.body];
150
+ const spans = rows.some((row2) => row2.some((cell2) => cell2.colspan > 1 || cell2.rowspan > 1));
151
+ const caption = block.number ? [paragraph([anchor(block.anchor), ...this.captionLine(`Table ${block.number}`, block)])] : [];
152
+ if (spans) return [...caption, ...this.htmlTable(block)];
153
+ const columns = Math.max(0, ...rows.map((row2) => row2.length));
154
+ if (columns === 0) return caption;
155
+ const cell = (source) => ({
156
+ type: "tableCell",
157
+ children: source ? this.inlines(source.children, true) : []
158
+ });
159
+ const row = (cells) => ({
160
+ type: "tableRow",
161
+ children: Array.from({ length: columns }, (_, index) => cell(cells[index]))
162
+ });
163
+ const head = block.head[0] ?? [];
164
+ const firstBody = block.body[0] ?? head;
165
+ const table = {
166
+ type: "table",
167
+ align: Array.from({ length: columns }, (_, index) => (firstBody[index] ?? head[index])?.align ?? null),
168
+ children: [row(head), ...block.head.slice(1).map(row), ...block.body.map(row)]
169
+ };
170
+ return [...caption, table];
171
+ }
172
+ /**
173
+ * A table with spanning cells as HTML, which pipe tables cannot hold. Each
174
+ * cell's content stands on lines of its own, so Markdown (and its math)
175
+ * still reads it.
176
+ */
177
+ htmlTable(block) {
178
+ const cellHtml = (cell) => {
179
+ const tag = cell.header ? "th" : "td";
180
+ const attributes = [
181
+ ...cell.colspan > 1 ? [`colspan="${cell.colspan}"`] : [],
182
+ ...cell.rowspan > 1 ? [`rowspan="${cell.rowspan}"`] : [],
183
+ ...cell.align ? [`align="${cell.align}"`] : []
184
+ ];
185
+ const content = serializeNodes([paragraph(this.inlines(cell.children))]).trim();
186
+ return [`<${tag}${attributes.length > 0 ? ` ${attributes.join(" ")}` : ""}>`, "", content, "", `</${tag}>`];
187
+ };
188
+ const lines = [
189
+ "<table>",
190
+ ...block.head.flatMap((row) => ["<tr>", ...row.flatMap(cellHtml), "</tr>"]),
191
+ ...block.body.flatMap((row) => ["<tr>", ...row.flatMap(cellHtml), "</tr>"]),
192
+ "</table>"
193
+ ];
194
+ return [{ type: "html", value: lines.join("\n") }];
195
+ }
196
+ /** A diagram's boxes and arrows as a list: what the picture shows, as text. */
197
+ diagramText(geometry) {
198
+ const label = (index) => {
199
+ const node = geometry.nodes[index];
200
+ return [
201
+ ...node.text ? [text(node.text)] : [],
202
+ ...node.text && node.tex ? [text(" ")] : [],
203
+ ...node.tex ? [{ type: "inlineMath", value: plainTex(node.tex) }] : []
204
+ ];
205
+ };
206
+ const edges = diagramEdges(geometry);
207
+ const joined = new Set(edges.flat());
208
+ const items = [
209
+ ...edges.map(([from, to]) => ({
210
+ type: "listItem",
211
+ spread: false,
212
+ children: [paragraph([...label(from), text(" \u2192 "), ...label(to)])]
213
+ })),
214
+ ...geometry.nodes.map((_, index) => index).filter((index) => !joined.has(index)).map((index) => ({ type: "listItem", spread: false, children: [paragraph(label(index))] }))
215
+ ];
216
+ if (items.length === 0) return [];
217
+ return [
218
+ paragraph([emphasis([text("The diagram as text:")])]),
219
+ { type: "list", ordered: false, spread: false, children: items }
220
+ ];
221
+ }
222
+ /** A titled quote: a memo, a short note, an example set, a check. */
223
+ quote(title, children, id) {
224
+ return {
225
+ type: "blockquote",
226
+ children: [paragraph([...id ? [anchor(id)] : [], strong(title)]), ...children]
227
+ };
228
+ }
229
+ check(block) {
230
+ const items = this.context.checkItems.get(block.assessmentId) ?? [];
231
+ const questions = {
232
+ type: "list",
233
+ ordered: true,
234
+ spread: false,
235
+ children: items.map((item) => ({
236
+ type: "listItem",
237
+ spread: false,
238
+ children: [
239
+ paragraph(this.segments(item.promptSegments)),
240
+ ...item.type === "single-choice" && item.options && item.options.length > 0 ? [
241
+ {
242
+ type: "list",
243
+ ordered: false,
244
+ spread: false,
245
+ children: item.options.map(
246
+ (option, index) => ({
247
+ type: "listItem",
248
+ spread: false,
249
+ children: [
250
+ paragraph([text(`(${String.fromCharCode(97 + index)}) `), ...this.optionLabel(option)])
251
+ ]
252
+ })
253
+ )
254
+ }
255
+ ] : []
256
+ ]
257
+ }))
258
+ };
259
+ return this.quote(
260
+ [text(`Knowledge check ${block.number}: `), ...this.inlines(block.title)],
261
+ [
262
+ ...items.length > 0 ? [questions] : [],
263
+ paragraph([emphasis([text("Answers: "), link("#answers", [text("end of the chapter")]), text(".")])])
264
+ ],
265
+ block.anchor
266
+ );
267
+ }
268
+ block(block, inBox = false) {
269
+ switch (block.kind) {
270
+ case "heading": {
271
+ const content = [
272
+ ...block.anchor ? [anchor(block.anchor)] : [],
273
+ ...block.number ? [text(`${block.number} `)] : [],
274
+ ...this.inlines(block.children)
275
+ ];
276
+ if (inBox) return [paragraph([strong(content)])];
277
+ const depth = Math.min(Math.max(block.level, 2), 6);
278
+ return [{ type: "heading", depth, children: content }];
279
+ }
280
+ case "paragraph": {
281
+ const children = this.inlines(block.children);
282
+ return children.length > 0 ? [paragraph(children)] : [];
283
+ }
284
+ case "list":
285
+ return [
286
+ {
287
+ type: "list",
288
+ ordered: block.ordered,
289
+ ...block.ordered && block.start ? { start: block.start } : {},
290
+ spread: false,
291
+ children: block.items.map((item) => ({
292
+ type: "listItem",
293
+ spread: false,
294
+ children: this.blocks(item, inBox)
295
+ }))
296
+ }
297
+ ];
298
+ case "equation":
299
+ return [
300
+ paragraph([anchor(block.anchor)]),
301
+ { type: "math", value: `${plainTex(block.tex)}
302
+ \\tag{${block.number}}` }
303
+ ];
304
+ case "displayMath":
305
+ return [{ type: "math", value: plainTex(block.tex) }];
306
+ case "figure": {
307
+ const alt = block.alt ?? [block.short && captionTitle(block.short), plainText(block.caption)].filter(Boolean).join(" ");
308
+ const image = this.picture(block.anchor, alt || "Figure");
309
+ const missing = [
310
+ emphasis([
311
+ text("Interactive figure"),
312
+ ...this.context.canonicalBaseUrl ? [text(": "), link(`${this.context.canonicalBaseUrl}${this.route}/#${block.anchor}`, [text("see it on the course website")])] : [],
313
+ text(".")
314
+ ])
315
+ ];
316
+ return [
317
+ paragraph([anchor(block.anchor), ...image ? [image] : missing]),
318
+ paragraph(this.captionLine(`Figure ${block.number}`, block, block.live))
319
+ ];
320
+ }
321
+ case "diagram": {
322
+ const image = this.picture(block.anchor, block.short ?? plainText(block.caption) ?? "Diagram");
323
+ return [
324
+ paragraph([anchor(block.anchor), ...image ? [image] : []]),
325
+ paragraph(this.captionLine(`Diagram ${block.number}`, block)),
326
+ ...this.diagramText(block.geometry)
327
+ ];
328
+ }
329
+ case "table":
330
+ return this.table(block);
331
+ case "memo":
332
+ return [
333
+ this.quote(
334
+ [text(`Memo ${block.label}: `), ...this.inlines(block.title)],
335
+ this.blocks(block.children, true),
336
+ block.anchor
337
+ )
338
+ ];
339
+ case "note":
340
+ return [this.quote([text(block.title)], this.blocks(block.children, true), block.anchor)];
341
+ case "aside": {
342
+ const title = this.inlines(block.title);
343
+ return [
344
+ {
345
+ type: "blockquote",
346
+ children: [
347
+ paragraph([
348
+ html(`[!${ALERTS[block.variant] ?? "NOTE"}]`),
349
+ ...title.length > 0 ? [text("\n"), strong(title)] : []
350
+ ]),
351
+ ...this.blocks(block.children, true)
352
+ ]
353
+ }
354
+ ];
355
+ }
356
+ case "fold":
357
+ return [
358
+ ...block.title && block.title.length > 0 ? [paragraph([strong(this.inlines(block.title))])] : [],
359
+ ...this.blocks(block.children, inBox)
360
+ ];
361
+ case "examples":
362
+ return [
363
+ this.quote(
364
+ [text(`Example ${block.number}: `), ...this.inlines(block.title)],
365
+ block.items.flatMap((item) => [paragraph([strong(this.inlines(item.title))]), ...this.blocks(item.children, true)]),
366
+ block.anchor
367
+ )
368
+ ];
369
+ case "check":
370
+ return [this.check(block)];
371
+ case "quote":
372
+ return [{ type: "blockquote", children: this.blocks(block.children, inBox) }];
373
+ case "image": {
374
+ const image = this.picture(block.src, block.alt);
375
+ return image ? [paragraph([image])] : [];
376
+ }
377
+ case "code":
378
+ return [{ type: "code", value: block.value }];
379
+ case "rule":
380
+ return [{ type: "thematicBreak" }];
381
+ case "references":
382
+ return [
383
+ { type: "heading", depth: 2, children: [text("References")] },
384
+ {
385
+ type: "list",
386
+ ordered: true,
387
+ start: Number.parseInt(block.items[0]?.number ?? "1", 10) || 1,
388
+ spread: false,
389
+ children: block.items.map((item) => ({
390
+ type: "listItem",
391
+ spread: false,
392
+ children: [paragraph([anchor(item.anchor), ...this.inlines(item.children)])]
393
+ }))
394
+ }
395
+ ];
396
+ }
397
+ }
398
+ blocks(blocks, inBox = false) {
399
+ return blocks.flatMap((block) => this.block(block, inBox));
400
+ }
401
+ notation(entries, remarks) {
402
+ if (entries.length === 0) return [];
403
+ return [
404
+ { type: "heading", depth: 2, children: [text("Notation")] },
405
+ ...remarks.map((remark) => paragraph([emphasis(this.inlines(remark))])),
406
+ {
407
+ type: "list",
408
+ ordered: false,
409
+ spread: false,
410
+ children: entries.map((entry) => {
411
+ const symbol = entry.symbolTex ? plainTex(entry.symbolTex) : "";
412
+ const line = [
413
+ anchor(entry.anchor),
414
+ ...symbol ? [{ type: "inlineMath", value: symbol }, text(" ")] : [],
415
+ strong([...this.inlines(entry.label), text(".")]),
416
+ ...entry.summary.length > 0 ? [text(" "), ...this.inlines(entry.summary)] : [],
417
+ ...entry.units ? [text(" "), emphasis([text("Units:")]), text(" "), ...this.inlines(entry.units), text(".")] : []
418
+ ];
419
+ return {
420
+ type: "listItem",
421
+ spread: false,
422
+ children: [
423
+ paragraph(line),
424
+ ...entry.formulaTex ? [{ type: "math", value: plainTex(entry.formulaTex) }] : []
425
+ ]
426
+ };
427
+ })
428
+ }
429
+ ];
430
+ }
431
+ answers(checks) {
432
+ if (checks.length === 0) return [];
433
+ return [
434
+ { type: "heading", depth: 2, children: [anchor("answers"), text("Answers to the knowledge checks")] },
435
+ ...checks.flatMap((check) => {
436
+ const items = this.context.checkItems.get(check.assessmentId) ?? [];
437
+ return [
438
+ paragraph([
439
+ strong([link(`#${check.anchor}`, [text(`Knowledge check ${check.number}`)]), text(": "), ...this.inlines(check.title)])
440
+ ]),
441
+ {
442
+ type: "list",
443
+ ordered: true,
444
+ spread: false,
445
+ children: items.map((item) => {
446
+ const answer = [];
447
+ if (item.type === "single-choice" && item.options) {
448
+ const index = item.options.findIndex((option) => option.id === item.correctOptionId);
449
+ if (index !== -1) {
450
+ answer.push(
451
+ strong([text(`(${String.fromCharCode(97 + index)}) `), ...this.optionLabel(item.options[index]), text(".")]),
452
+ text(" ")
453
+ );
454
+ }
455
+ } else if (item.type === "numeric" && item.answer) {
456
+ answer.push(
457
+ strong([text(String(item.answer.value))]),
458
+ text(` (accepted within \xB1${item.answer.tolerance}). `)
459
+ );
460
+ }
461
+ return { type: "listItem", spread: false, children: [paragraph([...answer, ...this.segments(item.explanationSegments)])] };
462
+ })
463
+ }
464
+ ];
465
+ })
466
+ ];
467
+ }
468
+ }
469
+ function serializeNodes(children) {
470
+ const root = { type: "root", children };
471
+ return toMarkdown(root, {
472
+ bullet: "-",
473
+ emphasis: "*",
474
+ strong: "*",
475
+ fence: "`",
476
+ fences: true,
477
+ rule: "-",
478
+ listItemIndent: "one",
479
+ incrementListMarker: true,
480
+ extensions: [gfmTableToMarkdown(), mathToMarkdown()]
481
+ });
482
+ }
483
+ function checksOf(blocks) {
484
+ const checks = [];
485
+ const visit = (list) => {
486
+ for (const block of list) {
487
+ if (block.kind === "check") checks.push(block);
488
+ if (block.kind === "list") block.items.forEach(visit);
489
+ if (block.kind === "examples") block.items.forEach((item) => visit(item.children));
490
+ if ("children" in block && Array.isArray(block.children) && block.kind !== "paragraph" && block.kind !== "heading") {
491
+ visit(block.children);
492
+ }
493
+ }
494
+ };
495
+ visit(blocks);
496
+ return checks;
497
+ }
498
+ function serializeMarkdownPage(context, options = {}) {
499
+ const writer = new MarkdownWriter(context);
500
+ const { page, source } = context;
501
+ const sourceLine = source ? [
502
+ text("From "),
503
+ emphasis([text(source.course)]),
504
+ ...source.version ? [text(`, version ${source.version}`)] : [],
505
+ text("."),
506
+ ...source.url ? [text(" Interactive version: "), link(source.url, [text(source.url)]), text(".")] : []
507
+ ] : [];
508
+ return serializeNodes([
509
+ {
510
+ type: "heading",
511
+ depth: 1,
512
+ children: [anchor("_top"), ...options.number ? [text(`${options.number} `)] : [], text(page.title)]
513
+ },
514
+ ...sourceLine.length > 0 ? [paragraph(sourceLine)] : [],
515
+ ...writer.blocks(page.blocks),
516
+ ...writer.notation(page.notation, page.notationRemarks),
517
+ ...writer.answers(checksOf(page.blocks))
518
+ ]);
519
+ }
520
+ function serializeMarkdownNodes(children) {
521
+ return serializeNodes(children);
522
+ }
523
+ export {
524
+ diagramEdges,
525
+ serializeMarkdownNodes,
526
+ serializeMarkdownPage
527
+ };
@@ -0,0 +1,39 @@
1
+ import { Manifest } from '../manifest.js';
2
+ import { ExportLogger, PreparedChapters } from './export-chapters.js';
3
+ import '../../curriculum/validation.js';
4
+ import '../../reference/consistency.js';
5
+ import '../../course-config-rRtME5UV.js';
6
+ import '../../rules/catalog.js';
7
+ import '../../rules/course-rules.js';
8
+ import '../collections.js';
9
+ import '../../reference/types.js';
10
+ import '../../reference/gate-retired-keys.js';
11
+ import '../../reference/lint-ignore.js';
12
+ import '../../reference/equations-validate.js';
13
+ import '../../reference/tables-validate.js';
14
+ import '../../reference/keyed-float-validate.js';
15
+ import '../../reference/figures-validate.js';
16
+ import '../../reference/diagrams-validate.js';
17
+ import '../../reference/examples-validate.js';
18
+ import '../../reference/folds-validate.js';
19
+ import '../../reference/knowledge-checks-validate.js';
20
+ import '../../reference/raw-floats-validate.js';
21
+ import '../../reference/lab-katex-validate.js';
22
+ import '../../reference/gate-math.js';
23
+ import '../../reference/mixed-content.js';
24
+ import '../../reference/gloss.js';
25
+ import '../../rules/waivers.js';
26
+ import '../knowledge-version.js';
27
+ import '../lesson-derivation.js';
28
+ import './static-tree.js';
29
+ import './html-tree.js';
30
+ import 'parse5';
31
+ import './latex/capture.js';
32
+
33
+ declare function writeMarkdownExports(directory: URL, manifest: Manifest, options?: {
34
+ readonly logger?: ExportLogger;
35
+ readonly projectRoot?: string;
36
+ readonly prepared?: PreparedChapters;
37
+ }): Promise<void>;
38
+
39
+ export { writeMarkdownExports };