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,240 @@
1
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
2
+ import { basename, dirname, extname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { courseFileStem } from "./export-pages.js";
5
+ import {
6
+ consoleLogger,
7
+ diagramsOf,
8
+ figuresOf,
9
+ imageExtension,
10
+ imageFile,
11
+ imagesOf,
12
+ needsCapture,
13
+ prepareChapters,
14
+ readBuiltPage
15
+ } from "./export-chapters.js";
16
+ import { checkItems } from "./latex/document.js";
17
+ import { serializeMarkdownNodes, serializeMarkdownPage } from "./markdown/serialize.js";
18
+ import { zip } from "./zip.js";
19
+ async function pagePictures(outputRoot, base, page, captured, assetDir) {
20
+ const pictures = /* @__PURE__ */ new Map();
21
+ const copy = async (key, src, name) => {
22
+ const file = imageFile(outputRoot, base, page.route, src);
23
+ pictures.set(key, { path: `${assetDir}/${name}${extname(file).toLowerCase()}`, content: await readFile(file) });
24
+ };
25
+ for (const figure of figuresOf(page.blocks)) {
26
+ if (figure.picture.kind === "image" && (!needsCapture(figure) || imageExtension(figure.picture.src) === ".svg")) {
27
+ await copy(figure.anchor, figure.picture.src, figure.anchor);
28
+ continue;
29
+ }
30
+ const png = captured?.get(figure.anchor)?.png;
31
+ if (png) pictures.set(figure.anchor, { path: `${assetDir}/${figure.anchor}.png`, content: png });
32
+ }
33
+ for (const diagram of diagramsOf(page.blocks)) {
34
+ const png = captured?.get(diagram.anchor)?.png;
35
+ if (png) pictures.set(diagram.anchor, { path: `${assetDir}/${diagram.anchor}.png`, content: png });
36
+ }
37
+ for (const image of imagesOf(page.blocks)) {
38
+ if (pictures.has(image.src)) continue;
39
+ const name = basename(new URL(image.src, "https://x.invalid/").pathname, extname(image.src)) || "image";
40
+ await copy(image.src, image.src, name);
41
+ }
42
+ return pictures;
43
+ }
44
+ function twoDigits(value) {
45
+ return String(value).padStart(2, "0");
46
+ }
47
+ function chapterFile(lesson) {
48
+ return `${twoDigits(lesson.part)}-${twoDigits(lesson.chapter)}-${lesson.slug.split("/").at(-1) ?? lesson.slug}.md`;
49
+ }
50
+ function partFile(part) {
51
+ return `${twoDigits(part.ordinal)}-00-${part.dir}.md`;
52
+ }
53
+ function relativeTo(files, route) {
54
+ return new Map([...files].map(([key, file]) => [key, key === route ? "" : file]));
55
+ }
56
+ async function writeMarkdownExports(directory, manifest, options = {}) {
57
+ if (!manifest.lessonExports.markdown) return;
58
+ const logger = options.logger ?? consoleLogger;
59
+ const outputRoot = fileURLToPath(directory);
60
+ const { base, chapters, captures } = options.prepared ?? await prepareChapters(outputRoot, manifest, { logger, projectRoot: options.projectRoot });
61
+ const canonicalBaseUrl = manifest.lessonExports.canonicalBaseUrl;
62
+ const course = manifest.about.material.name;
63
+ const version = manifest.about.knowledge.version;
64
+ const source = (route) => ({
65
+ course,
66
+ version,
67
+ ...canonicalBaseUrl ? { url: `${canonicalBaseUrl}${route ? `${route}/` : ""}` } : {}
68
+ });
69
+ const chapterFiles = async (chapter, file, assetDir, documentFiles) => {
70
+ const { lesson, page } = chapter;
71
+ const pictures = await pagePictures(outputRoot, base, page, captures.get(page.route), assetDir);
72
+ const markdown = serializeMarkdownPage(
73
+ {
74
+ page,
75
+ documentFiles: relativeTo(documentFiles, page.route),
76
+ ...canonicalBaseUrl ? { canonicalBaseUrl } : {},
77
+ pictures: new Map([...pictures].map(([key, picture]) => [key, picture.path])),
78
+ checkItems: checkItems(manifest, lesson),
79
+ source: source(page.route)
80
+ },
81
+ { number: `${lesson.part}.${lesson.chapter}` }
82
+ );
83
+ return [
84
+ { path: file, content: markdown },
85
+ ...[...pictures.values()].map((picture) => ({ path: picture.path, content: picture.content }))
86
+ ];
87
+ };
88
+ const pending = [];
89
+ const pack = (target, folder, entries) => {
90
+ const unique = new Map(entries.map((entry) => [entry.path, entry]));
91
+ pending.push({
92
+ target: join(outputRoot, "downloads", target),
93
+ content: zip([...unique.values()].map((entry) => ({ path: `${folder}/${entry.path}`, content: entry.content })))
94
+ });
95
+ };
96
+ for (const chapter of chapters) {
97
+ const name = chapter.lesson.slug.split("/").at(-1) ?? chapter.lesson.slug;
98
+ pack(
99
+ `${chapter.lesson.slug}-markdown.zip`,
100
+ `${name}-markdown`,
101
+ await chapterFiles(chapter, "lesson.md", "assets", /* @__PURE__ */ new Map([[chapter.page.route, "lesson.md"]]))
102
+ );
103
+ }
104
+ const byId = new Map(chapters.map((chapter) => [chapter.lesson.id, chapter]));
105
+ const pageSlugs = new Set(manifest.pages.map((page) => page.slug));
106
+ const parts = manifest.parts.map((part) => ({
107
+ part,
108
+ chapters: part.lessons.flatMap((id) => byId.has(id) ? [byId.get(id)] : []),
109
+ overview: pageSlugs.has(`${part.dir}/index`) ? `${part.dir}/index` : void 0
110
+ })).filter((entry) => entry.chapters.length > 0);
111
+ const overviewMarkdown = async (entry, documentFiles, assetDir) => {
112
+ if (!entry.overview) {
113
+ return { markdown: serializeMarkdownNodes([{ type: "heading", depth: 1, children: [{ type: "text", value: entry.part.label }] }]), pictures: [] };
114
+ }
115
+ const { page } = await readBuiltPage(outputRoot, entry.overview);
116
+ const pictures = await pagePictures(outputRoot, base, page, void 0, assetDir);
117
+ const markdown = serializeMarkdownPage(
118
+ {
119
+ page,
120
+ documentFiles: relativeTo(documentFiles, page.route),
121
+ ...canonicalBaseUrl ? { canonicalBaseUrl } : {},
122
+ pictures: new Map([...pictures].map(([key, picture]) => [key, picture.path])),
123
+ checkItems: /* @__PURE__ */ new Map(),
124
+ source: source(page.route)
125
+ },
126
+ { number: `Part ${entry.part.ordinal}:` }
127
+ );
128
+ return { markdown, pictures: [...pictures.values()].map((picture) => ({ path: picture.path, content: picture.content })) };
129
+ };
130
+ const contents = (entry, files) => entry.chapters.map((chapter) => ({
131
+ type: "listItem",
132
+ spread: false,
133
+ children: [
134
+ {
135
+ type: "paragraph",
136
+ children: [
137
+ {
138
+ type: "link",
139
+ url: files.get(chapter.page.route) ?? "",
140
+ children: [{ type: "text", value: `${chapter.lesson.part}.${chapter.lesson.chapter} ${chapter.page.title}` }]
141
+ }
142
+ ]
143
+ }
144
+ ]
145
+ }));
146
+ for (const entry of parts) {
147
+ const files = new Map([
148
+ ...entry.overview ? [[entry.part.dir, "index.md"]] : [],
149
+ ...entry.chapters.map((chapter) => [chapter.page.route, chapterFile(chapter.lesson)])
150
+ ]);
151
+ const overview = await overviewMarkdown(entry, files, "assets/index");
152
+ const index = `${overview.markdown}
153
+ ` + serializeMarkdownNodes([
154
+ { type: "heading", depth: 2, children: [{ type: "text", value: "Contents" }] },
155
+ { type: "list", ordered: false, spread: false, children: contents(entry, files) }
156
+ ]);
157
+ const entries = [{ path: "index.md", content: index }, ...overview.pictures];
158
+ for (const chapter of entry.chapters) {
159
+ const file = chapterFile(chapter.lesson);
160
+ entries.push(...await chapterFiles(chapter, file, `assets/${file.replace(/\.md$/u, "")}`, files));
161
+ }
162
+ pack(`part/${entry.part.dir}-markdown.zip`, `${entry.part.dir}-markdown`, entries);
163
+ }
164
+ if (parts.length > 0) {
165
+ const files = new Map([
166
+ ["", "index.md"],
167
+ ...parts.flatMap((entry) => [
168
+ ...entry.overview ? [[entry.part.dir, partFile(entry.part)]] : [],
169
+ ...entry.chapters.map((chapter) => [chapter.page.route, chapterFile(chapter.lesson)])
170
+ ])
171
+ ]);
172
+ const entries = [];
173
+ for (const entry of parts) {
174
+ if (entry.overview) {
175
+ const file = partFile(entry.part);
176
+ const overview = await overviewMarkdown(entry, files, `assets/${file.replace(/\.md$/u, "")}`);
177
+ entries.push({ path: file, content: overview.markdown }, ...overview.pictures);
178
+ }
179
+ for (const chapter of entry.chapters) {
180
+ const file = chapterFile(chapter.lesson);
181
+ entries.push(...await chapterFiles(chapter, file, `assets/${file.replace(/\.md$/u, "")}`, files));
182
+ }
183
+ }
184
+ const index = serializeMarkdownNodes([
185
+ { type: "heading", depth: 1, children: [{ type: "text", value: course }] },
186
+ {
187
+ type: "paragraph",
188
+ children: [
189
+ { type: "text", value: `Content version ${version}.` },
190
+ ...canonicalBaseUrl ? [
191
+ { type: "text", value: " Interactive version: " },
192
+ { type: "link", url: canonicalBaseUrl, children: [{ type: "text", value: canonicalBaseUrl }] },
193
+ { type: "text", value: "." }
194
+ ] : []
195
+ ]
196
+ },
197
+ ...manifest.about.material.summary ? [{ type: "paragraph", children: [{ type: "text", value: manifest.about.material.summary }] }] : [],
198
+ { type: "heading", depth: 2, children: [{ type: "text", value: "Contents" }] },
199
+ {
200
+ type: "list",
201
+ ordered: false,
202
+ spread: false,
203
+ children: parts.map((entry) => ({
204
+ type: "listItem",
205
+ spread: false,
206
+ children: [
207
+ {
208
+ type: "paragraph",
209
+ children: entry.overview ? [
210
+ {
211
+ type: "link",
212
+ url: partFile(entry.part),
213
+ children: [{ type: "text", value: `Part ${entry.part.ordinal}: ${entry.part.label}` }]
214
+ }
215
+ ] : [{ type: "text", value: `Part ${entry.part.ordinal}: ${entry.part.label}` }]
216
+ },
217
+ { type: "list", ordered: false, spread: false, children: contents(entry, files) }
218
+ ]
219
+ }))
220
+ }
221
+ ]);
222
+ entries.push({ path: "index.md", content: index });
223
+ pack("course-markdown.zip", `${courseFileStem(manifest)}-markdown`, entries);
224
+ }
225
+ try {
226
+ for (const file of pending) {
227
+ await mkdir(dirname(file.target), { recursive: true });
228
+ await writeFile(`${file.target}.tmp-${process.pid}`, file.content);
229
+ }
230
+ for (const file of pending) await rename(`${file.target}.tmp-${process.pid}`, file.target);
231
+ } finally {
232
+ await Promise.all(pending.map((file) => rm(`${file.target}.tmp-${process.pid}`, { force: true })));
233
+ }
234
+ logger.info(
235
+ `Markdown export: ${chapters.length} chapter(s), ${parts.length} part(s)` + (parts.length > 0 ? " and the course" : "") + "."
236
+ );
237
+ }
238
+ export {
239
+ writeMarkdownExports
240
+ };
@@ -1,11 +1,12 @@
1
1
  import { Manifest } from '../manifest.js';
2
2
  import '../../curriculum/validation.js';
3
3
  import '../../reference/consistency.js';
4
- import '../../course-config-Dsnri9Tk.js';
4
+ import '../../course-config-rRtME5UV.js';
5
5
  import '../../rules/catalog.js';
6
6
  import '../../rules/course-rules.js';
7
7
  import '../collections.js';
8
8
  import '../../reference/types.js';
9
+ import '../../reference/gate-retired-keys.js';
9
10
  import '../../reference/lint-ignore.js';
10
11
  import '../../reference/equations-validate.js';
11
12
  import '../../reference/tables-validate.js';
@@ -24,17 +25,25 @@ import '../../rules/waivers.js';
24
25
  import '../knowledge-version.js';
25
26
  import '../lesson-derivation.js';
26
27
 
27
- /** Inputs for producing one standalone lesson from Astro's final HTML. */
28
+ /** Inputs for producing one standalone page from Astro's final HTML. */
28
29
  interface StandaloneLessonInput {
29
30
  readonly html: string;
30
31
  readonly sourceFile: string;
31
32
  readonly outputRoot: string;
33
+ /** The page's docs slug: a chapter's, or `index`, `motion/index`, `glossary`. */
32
34
  readonly lessonSlug: string;
33
35
  readonly canonicalBaseUrl?: string;
34
36
  }
35
- /** Build one fully inlined lesson document without writing it. */
37
+ /** Build one fully inlined page document without writing it. */
36
38
  declare function buildStandaloneLessonHtml(input: StandaloneLessonInput): Promise<string>;
37
- /** Emit all opt-in lesson HTML artifacts from Astro's completed static output. */
39
+ /** Astro's built HTML file of a docs slug. */
40
+ declare function pageSourceFile(outputRoot: string, slug: string): Promise<string>;
41
+ /**
42
+ * Emit the opt-in standalone HTML artifact of every chapter
43
+ * (`standalonePageSlugs`) from Astro's completed static output, at
44
+ * `downloads/<docs slug>.html`. A page that is not a chapter is in its
45
+ * part's and the course's files (`combined-html.ts`).
46
+ */
38
47
  declare function writeStandaloneLessonExports(directory: URL, manifest: Manifest): Promise<void>;
39
48
 
40
- export { type StandaloneLessonInput, buildStandaloneLessonHtml, writeStandaloneLessonExports };
49
+ export { type StandaloneLessonInput, buildStandaloneLessonHtml, pageSourceFile, writeStandaloneLessonExports };
@@ -9,10 +9,14 @@ import {
9
9
  import { Buffer } from "node:buffer";
10
10
  import { dirname, extname, join, relative, resolve, sep } from "node:path";
11
11
  import { fileURLToPath } from "node:url";
12
+ import { parse, serialize } from "parse5";
13
+ import { exportPageRoute, standalonePageSlugs } from "./export-pages.js";
12
14
  import {
13
- parse,
14
- serialize
15
- } from "parse5";
15
+ attribute,
16
+ elements,
17
+ hasClass,
18
+ inferBasePrefix
19
+ } from "./html-tree.js";
16
20
  const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
17
21
  const CSS_URL = /url\(\s*(?:(["'])(.*?)\1|([^)]*?))\s*\)/giu;
18
22
  const CSS_IMPORT = /@import\s+(?:url\(\s*(?:(["'])(.*?)\1|([^)]*?))\s*\)|(["'])(.*?)\4)\s*([^;]*);/giu;
@@ -98,38 +102,11 @@ const STANDALONE_THEME_SCRIPT = `(() => {
98
102
  });
99
103
  apply('auto');
100
104
  })();`;
101
- function isElement(node) {
102
- return "tagName" in node;
103
- }
104
- function childrenOf(node) {
105
- return "childNodes" in node ? node.childNodes : [];
106
- }
107
- function walk(node, visit) {
108
- visit(node);
109
- for (const child of childrenOf(node)) walk(child, visit);
110
- if (isElement(node) && node.tagName === "template") {
111
- const template = node;
112
- for (const child of template.content.childNodes) walk(child, visit);
113
- }
114
- }
115
- function elements(root, predicate) {
116
- const found = [];
117
- walk(root, (node) => {
118
- if (isElement(node) && (predicate?.(node) ?? true)) found.push(node);
119
- });
120
- return found;
121
- }
122
105
  function firstElement(root, tagName) {
123
106
  const found = elements(root, (node) => node.tagName === tagName)[0];
124
107
  if (!found) throw new Error(`Standalone HTML source has no <${tagName}> element.`);
125
108
  return found;
126
109
  }
127
- function attribute(node, name) {
128
- return node.attrs.find((entry) => entry.name === name)?.value;
129
- }
130
- function hasClass(node, name) {
131
- return (attribute(node, "class") ?? "").split(/\s+/u).includes(name);
132
- }
133
110
  function setAttribute(node, name, value = "") {
134
111
  const existing = node.attrs.find((entry) => entry.name === name);
135
112
  if (existing) existing.value = value;
@@ -392,7 +369,7 @@ async function bundleBrowserGraph(entryPaths, requests, context) {
392
369
  });
393
370
  const javascript = result.outputFiles.find((file) => file.path.endsWith(".js"));
394
371
  if (!javascript) {
395
- throw new Error("Browser bundler produced no JavaScript for the standalone lesson.");
372
+ throw new Error("Browser bundler produced no JavaScript for the standalone page.");
396
373
  }
397
374
  const unsupported = result.outputFiles.filter(
398
375
  (file) => !file.path.endsWith(".js") && !file.path.endsWith(".css")
@@ -408,17 +385,6 @@ async function bundleBrowserGraph(entryPaths, requests, context) {
408
385
  css: result.outputFiles.filter((file) => file.path.endsWith(".css")).map((file) => file.text)
409
386
  };
410
387
  }
411
- function inferBasePrefix(document) {
412
- for (const node of elements(document)) {
413
- for (const name of ["href", "src", "component-url", "renderer-url"]) {
414
- const value = attribute(node, name);
415
- if (!value?.startsWith("/")) continue;
416
- const marker = value.indexOf("/astro-assets/");
417
- if (marker !== -1) return `${value.slice(0, marker)}/`;
418
- }
419
- }
420
- return "/";
421
- }
422
388
  function normalizedPagePath(pathname) {
423
389
  return pathname.replace(/\/index\.html$/iu, "/").replace(/\.html$/iu, "").replace(/\/+$/u, "");
424
390
  }
@@ -429,14 +395,17 @@ function rewriteCourseLink(href, lessonSlug, basePrefix, canonicalBaseUrl) {
429
395
  if (hasScheme(trimmed)) {
430
396
  throw new Error(`Standalone HTML cannot publish local navigation URL ${JSON.stringify(href)}.`);
431
397
  }
432
- const pagePath = `${basePrefix}${lessonSlug}/`.replace(/\/{2,}/gu, "/");
398
+ const pagePath = `${basePrefix}${exportPageRoute(lessonSlug)}/`.replace(
399
+ /\/{2,}/gu,
400
+ "/"
401
+ );
433
402
  const resolved = new URL(trimmed, `https://explico.invalid${pagePath}`);
434
403
  if (normalizedPagePath(resolved.pathname) === normalizedPagePath(pagePath)) {
435
404
  return `${resolved.search}${resolved.hash}` || "#_top";
436
405
  }
437
406
  if (!canonicalBaseUrl) {
438
407
  throw new Error(
439
- `Lesson ${lessonSlug} links outside itself (${JSON.stringify(href)}), so \`lessonExports.canonicalBaseUrl\` is required for standalone HTML.`
408
+ `Page ${lessonSlug} links outside itself (${JSON.stringify(href)}), so \`lessonExports.canonicalBaseUrl\` is required for standalone HTML.`
440
409
  );
441
410
  }
442
411
  let coursePath = resolved.pathname;
@@ -751,7 +720,7 @@ async function buildStandaloneLessonHtml(input) {
751
720
  const outputRoot = resolve(input.outputRoot);
752
721
  const sourceFile = resolve(input.sourceFile);
753
722
  if (!insideRoot(sourceFile, outputRoot)) {
754
- throw new Error("Standalone lesson source must be inside Astro build output.");
723
+ throw new Error("Standalone page source must be inside Astro build output.");
755
724
  }
756
725
  const context = {
757
726
  outputRoot,
@@ -778,8 +747,10 @@ async function buildStandaloneLessonHtml(input) {
778
747
  auditStandaloneHtml(html, input.lessonSlug);
779
748
  return html;
780
749
  }
781
- async function lessonSourceFile(outputRoot, slug) {
782
- const candidates = [join(outputRoot, slug, "index.html"), join(outputRoot, `${slug}.html`)];
750
+ async function pageSourceFile(outputRoot, slug) {
751
+ const route = exportPageRoute(slug);
752
+ const candidates = [join(outputRoot, route, "index.html")];
753
+ if (route) candidates.push(join(outputRoot, `${route}.html`));
783
754
  for (const candidate of candidates) if (await isFile(candidate)) return candidate;
784
755
  throw new Error(
785
756
  `Standalone HTML could not find Astro's built page for ${slug}; tried ` + candidates.map((path) => relative(outputRoot, path)).join(", ")
@@ -789,9 +760,9 @@ async function writeStandaloneLessonExports(directory, manifest) {
789
760
  if (!manifest.lessonExports.standaloneHtml) return;
790
761
  const outputRoot = fileURLToPath(directory);
791
762
  const pending = [];
792
- for (const lesson of manifest.lessons) {
793
- const sourceFile = await lessonSourceFile(outputRoot, lesson.slug);
794
- const target = join(outputRoot, "downloads", `${lesson.slug}.html`);
763
+ for (const slug of standalonePageSlugs(manifest)) {
764
+ const sourceFile = await pageSourceFile(outputRoot, slug);
765
+ const target = join(outputRoot, "downloads", `${slug}.html`);
795
766
  pending.push({
796
767
  target,
797
768
  temporary: `${target}.tmp-${process.pid}`,
@@ -799,7 +770,7 @@ async function writeStandaloneLessonExports(directory, manifest) {
799
770
  html: await readFile(sourceFile, "utf8"),
800
771
  sourceFile,
801
772
  outputRoot,
802
- lessonSlug: lesson.slug,
773
+ lessonSlug: slug,
803
774
  canonicalBaseUrl: manifest.lessonExports.canonicalBaseUrl
804
775
  })
805
776
  });
@@ -812,5 +783,6 @@ async function writeStandaloneLessonExports(directory, manifest) {
812
783
  }
813
784
  export {
814
785
  buildStandaloneLessonHtml,
786
+ pageSourceFile,
815
787
  writeStandaloneLessonExports
816
788
  };
@@ -0,0 +1,253 @@
1
+ import { HtmlElement, HtmlNode } from './html-tree.js';
2
+ import 'parse5';
3
+
4
+ /**
5
+ * explico's static reading tree: a built page reduced to what a reader reads,
6
+ * with explico's meaning kept — a float with its number and captions, a
7
+ * cross-reference by its target, a check by its anchor. Print exporters (the
8
+ * LaTeX source now, Markdown later) serialize this tree; none of them
9
+ * re-walks `content/` or recomputes a number.
10
+ *
11
+ * It is read off Astro's static output, where every disclosure's body, every
12
+ * table row and every formula's TeX already sit in the markup, whatever the
13
+ * reader would see collapsed. What the static page does not hold — a lab's
14
+ * drawing, a check's answers — comes from the capture pass and the manifest.
15
+ *
16
+ * Math keeps the author's `\explain{key}{…}` and `\slot{key}{…}` markers (the
17
+ * KaTeX annotation, not the stripped `data-latex`): a printer that understands
18
+ * them (explico's LaTeX class) needs no stripping, and one that doesn't strips
19
+ * them itself.
20
+ */
21
+ type RefKind = 'equation' | 'figure' | 'table' | 'diagram' | 'section' | 'example' | 'check' | 'memo' | 'anchor' | 'page';
22
+ /** A link target inside the course: a page route (base-relative, no slashes at the ends) and an anchor. */
23
+ interface CourseTarget {
24
+ readonly route: string;
25
+ readonly anchor: string;
26
+ }
27
+ type Inline = {
28
+ readonly kind: 'text';
29
+ readonly value: string;
30
+ } | {
31
+ readonly kind: 'emphasis';
32
+ readonly children: readonly Inline[];
33
+ } | {
34
+ readonly kind: 'strong';
35
+ readonly children: readonly Inline[];
36
+ } | {
37
+ readonly kind: 'superscript';
38
+ readonly children: readonly Inline[];
39
+ } | {
40
+ readonly kind: 'subscript';
41
+ readonly children: readonly Inline[];
42
+ } | {
43
+ readonly kind: 'code';
44
+ readonly value: string;
45
+ } | {
46
+ readonly kind: 'math';
47
+ readonly tex: string;
48
+ } | {
49
+ readonly kind: 'break';
50
+ } | {
51
+ readonly kind: 'link';
52
+ readonly href: string;
53
+ readonly children: readonly Inline[];
54
+ } | {
55
+ readonly kind: 'ref';
56
+ readonly refKind: RefKind;
57
+ readonly target: CourseTarget;
58
+ readonly children: readonly Inline[];
59
+ } | {
60
+ readonly kind: 'term';
61
+ readonly key: string;
62
+ readonly children: readonly Inline[];
63
+ } | {
64
+ readonly kind: 'cite';
65
+ readonly number: string;
66
+ readonly anchor: string;
67
+ readonly sourceId?: string;
68
+ readonly locator?: string;
69
+ } | {
70
+ readonly kind: 'memoMark';
71
+ readonly anchor: string;
72
+ readonly label: string;
73
+ } | {
74
+ readonly kind: 'noteMark';
75
+ readonly anchor: string;
76
+ readonly children: readonly Inline[];
77
+ };
78
+ interface TableCell {
79
+ readonly header: boolean;
80
+ readonly colspan: number;
81
+ readonly rowspan: number;
82
+ readonly align?: 'left' | 'center' | 'right';
83
+ readonly children: readonly Inline[];
84
+ }
85
+ interface DiagramNodeShape {
86
+ readonly x: number;
87
+ readonly y: number;
88
+ readonly width: number;
89
+ readonly height: number;
90
+ readonly radius: number;
91
+ readonly text?: string;
92
+ readonly tex?: string;
93
+ }
94
+ interface DiagramGeometry {
95
+ readonly width: number;
96
+ readonly height: number;
97
+ readonly nodes: readonly DiagramNodeShape[];
98
+ /** Each edge as its polyline, start to end; the arrowhead sits at the end. */
99
+ readonly edges: readonly (readonly (readonly [number, number])[])[];
100
+ }
101
+ /** What a figure shows: an image file, or something only a browser can draw. */
102
+ type FigurePicture = {
103
+ readonly kind: 'image';
104
+ readonly src: string;
105
+ readonly alt: string;
106
+ } | {
107
+ readonly kind: 'diagram';
108
+ readonly geometry: DiagramGeometry;
109
+ } | {
110
+ readonly kind: 'capture';
111
+ };
112
+ interface FloatBase {
113
+ /** The float's anchor id on its page (`fig-…`, `tab-…`, `dia-…`). */
114
+ readonly anchor: string;
115
+ readonly number: string;
116
+ readonly key?: string;
117
+ readonly short?: string;
118
+ /** The long caption. */
119
+ readonly caption: readonly Inline[];
120
+ }
121
+ type Block = {
122
+ readonly kind: 'heading';
123
+ readonly level: number;
124
+ readonly anchor?: string;
125
+ readonly number?: string;
126
+ readonly children: readonly Inline[];
127
+ } | {
128
+ readonly kind: 'paragraph';
129
+ readonly children: readonly Inline[];
130
+ } | {
131
+ readonly kind: 'list';
132
+ readonly ordered: boolean;
133
+ readonly start?: number;
134
+ readonly items: readonly (readonly Block[])[];
135
+ } | {
136
+ readonly kind: 'equation';
137
+ readonly anchor: string;
138
+ readonly number: string;
139
+ readonly key?: string;
140
+ readonly tex: string;
141
+ } | {
142
+ readonly kind: 'displayMath';
143
+ readonly tex: string;
144
+ } | (FloatBase & {
145
+ readonly kind: 'figure';
146
+ /** The live clause a lab writes after its caption; empty in the static page. */
147
+ readonly live: readonly Inline[];
148
+ readonly picture: FigurePicture;
149
+ /** The lab's own accessible label, when it gives one. */
150
+ readonly alt?: string;
151
+ }) | (FloatBase & {
152
+ readonly kind: 'table';
153
+ readonly head: readonly (readonly TableCell[])[];
154
+ readonly body: readonly (readonly TableCell[])[];
155
+ }) | (FloatBase & {
156
+ readonly kind: 'diagram';
157
+ readonly geometry: DiagramGeometry;
158
+ }) | {
159
+ readonly kind: 'memo';
160
+ readonly anchor: string;
161
+ readonly label: string;
162
+ readonly short?: string;
163
+ readonly title: readonly Inline[];
164
+ readonly children: readonly Block[];
165
+ } | {
166
+ readonly kind: 'note';
167
+ readonly anchor: string;
168
+ readonly title: string;
169
+ readonly children: readonly Block[];
170
+ } | {
171
+ readonly kind: 'aside';
172
+ readonly variant: string;
173
+ readonly title: readonly Inline[];
174
+ readonly children: readonly Block[];
175
+ } | {
176
+ readonly kind: 'fold';
177
+ readonly title?: readonly Inline[];
178
+ readonly children: readonly Block[];
179
+ } | {
180
+ readonly kind: 'examples';
181
+ readonly anchor: string;
182
+ readonly number: string;
183
+ readonly title: readonly Inline[];
184
+ readonly items: readonly {
185
+ readonly title: readonly Inline[];
186
+ readonly children: readonly Block[];
187
+ }[];
188
+ } | {
189
+ readonly kind: 'check';
190
+ readonly anchor: string;
191
+ readonly number: string;
192
+ readonly assessmentId: string;
193
+ readonly title: readonly Inline[];
194
+ } | {
195
+ readonly kind: 'quote';
196
+ readonly children: readonly Block[];
197
+ } | {
198
+ readonly kind: 'image';
199
+ readonly src: string;
200
+ readonly alt: string;
201
+ } | {
202
+ readonly kind: 'code';
203
+ readonly value: string;
204
+ } | {
205
+ readonly kind: 'rule';
206
+ } | {
207
+ readonly kind: 'references';
208
+ readonly items: readonly {
209
+ readonly number: string;
210
+ readonly anchor: string;
211
+ readonly children: readonly Inline[];
212
+ }[];
213
+ };
214
+ interface NotationEntry {
215
+ readonly key: string;
216
+ readonly anchor: string;
217
+ readonly symbolTex?: string;
218
+ readonly label: readonly Inline[];
219
+ readonly summary: readonly Inline[];
220
+ readonly units?: readonly Inline[];
221
+ readonly formulaTex?: string;
222
+ }
223
+ interface StaticPage {
224
+ /** The page's docs slug, e.g. `projectiles/optimal-launch-angle`. */
225
+ readonly slug: string;
226
+ /** Base-relative route of the page, e.g. `projectiles/optimal-launch-angle`. */
227
+ readonly route: string;
228
+ readonly title: string;
229
+ readonly blocks: readonly Block[];
230
+ /** "Notation used on this page", in the page's order. */
231
+ readonly notation: readonly NotationEntry[];
232
+ /** The notation list's page-specific remarks ("On this page, ′ is not an operator …"). */
233
+ readonly notationRemarks: readonly (readonly Inline[])[];
234
+ }
235
+ interface ReadContext {
236
+ readonly route: string;
237
+ readonly base: string;
238
+ }
239
+ /** All text below `node`, as a reader sees it: no screen-reader-only or hidden helpers. */
240
+ declare function readableText(node: HtmlNode): string;
241
+ /** Resolve a link to a course target, or `undefined` when it leaves the course. */
242
+ declare function courseTarget(href: string, context: ReadContext): CourseTarget | undefined;
243
+ /** Node boxes and edge polylines of a built `<Diagram>`'s SVG. */
244
+ declare function readDiagramGeometry(figure: HtmlElement): DiagramGeometry | undefined;
245
+ /**
246
+ * Inline content from an HTML fragment of a page — a lab's live caption
247
+ * clause, which only exists once the lab has run.
248
+ */
249
+ declare function readInlineFragment(html: string, route: string, base?: string): Inline[];
250
+ /** Read the static reading tree of one built page. */
251
+ declare function readStaticPage(html: string, slug: string, route: string): StaticPage;
252
+
253
+ export { type Block, type CourseTarget, type DiagramGeometry, type DiagramNodeShape, type FigurePicture, type Inline, type NotationEntry, type RefKind, type StaticPage, type TableCell, courseTarget, readDiagramGeometry, readInlineFragment, readStaticPage, readableText };