explico 0.11.1 → 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.
Files changed (298) hide show
  1. package/CHANGELOG.md +1292 -1
  2. package/README.md +91 -13
  3. package/dist/compiler/astro.d.ts +11 -4
  4. package/dist/compiler/astro.js +48 -15
  5. package/dist/compiler/cli.d.ts +27 -12
  6. package/dist/compiler/cli.js +96 -25
  7. package/dist/compiler/collections.d.ts +33 -2
  8. package/dist/compiler/collections.js +91 -19
  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 +15 -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 +115 -8
  51. package/dist/compiler/manifest.js +221 -39
  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 +3 -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 +8 -2
  61. package/dist/reference/base-operators.mjs +8 -10
  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/figure-symbols.d.ts +32 -0
  88. package/dist/reference/figure-symbols.js +55 -0
  89. package/dist/reference/figures-validate.d.ts +1 -1
  90. package/dist/reference/gate-abbreviations.d.ts +1 -1
  91. package/dist/reference/gate-abbreviations.js +1 -1
  92. package/dist/reference/gate-course-rules.d.ts +4 -3
  93. package/dist/reference/gate-course-rules.js +9 -4
  94. package/dist/reference/gate-heading-length.d.ts +2 -0
  95. package/dist/reference/gate-heading-length.js +7 -5
  96. package/dist/reference/gate-heading-structure.js +26 -0
  97. package/dist/reference/gate-indices.d.ts +35 -0
  98. package/dist/reference/gate-indices.js +299 -0
  99. package/dist/reference/gate-math.d.ts +32 -2
  100. package/dist/reference/gate-math.js +68 -12
  101. package/dist/reference/gate-mdx-expression.d.ts +17 -0
  102. package/dist/reference/gate-mdx-expression.js +29 -0
  103. package/dist/reference/gate-non-chapter-pages.d.ts +39 -0
  104. package/dist/reference/gate-non-chapter-pages.js +156 -0
  105. package/dist/reference/gate-part-pages.d.ts +4 -2
  106. package/dist/reference/gate-part-pages.js +2 -1
  107. package/dist/reference/gate-prop-text.d.ts +28 -0
  108. package/dist/reference/gate-prop-text.js +106 -0
  109. package/dist/reference/gate-prose-notation.d.ts +3 -1
  110. package/dist/reference/gate-prose-notation.js +4 -2
  111. package/dist/reference/gate-retired-keys.d.ts +35 -0
  112. package/dist/reference/gate-retired-keys.js +58 -0
  113. package/dist/reference/gloss.d.ts +2 -0
  114. package/dist/reference/katex-measure.d.ts +17 -0
  115. package/dist/reference/katex-measure.js +223 -0
  116. package/dist/reference/keyed-float-validate.js +12 -0
  117. package/dist/reference/keyed-jsx-floats.d.mts +2 -2
  118. package/dist/reference/keyed-jsx-floats.mjs +11 -14
  119. package/dist/reference/lab-katex-validate.d.ts +43 -0
  120. package/dist/reference/lab-katex-validate.js +96 -0
  121. package/dist/reference/lab-math-scope.d.ts +1 -0
  122. package/dist/reference/lab-math-scope.js +13 -2
  123. package/dist/reference/lint-ignore.js +1 -1
  124. package/dist/reference/math-glyphs.d.mts +94 -2
  125. package/dist/reference/math-glyphs.mjs +851 -46
  126. package/dist/reference/mixed-content.d.ts +8 -3
  127. package/dist/reference/mixed-content.js +4 -2
  128. package/dist/reference/page-notation.d.ts +39 -7
  129. package/dist/reference/page-notation.js +62 -10
  130. package/dist/reference/raw-floats.mjs +37 -13
  131. package/dist/reference/references.d.ts +11 -1
  132. package/dist/reference/references.js +6 -0
  133. package/dist/reference/registry.js +115 -11
  134. package/dist/reference/rehype-fail-katex-errors.d.mts +9 -0
  135. package/dist/reference/rehype-fail-katex-errors.mjs +19 -0
  136. package/dist/reference/rehype-heading-anchors.mjs +11 -1
  137. package/dist/reference/remark-citation.mjs +0 -0
  138. package/dist/reference/remark-notation.mjs +275 -23
  139. package/dist/reference/render-lab-math.d.ts +13 -2
  140. package/dist/reference/render-lab-math.js +18 -6
  141. package/dist/reference/sections-validate.d.ts +28 -3
  142. package/dist/reference/sections-validate.js +31 -4
  143. package/dist/reference/sections.d.mts +24 -6
  144. package/dist/reference/sections.mjs +92 -17
  145. package/dist/reference/tables-validate.d.ts +1 -1
  146. package/dist/reference/tables-validate.js +24 -1
  147. package/dist/reference/text-measure.d.ts +10 -0
  148. package/dist/reference/text-measure.js +41 -0
  149. package/dist/reference/text-widths.d.ts +10 -0
  150. package/dist/reference/text-widths.js +1238 -0
  151. package/dist/reference/types.d.ts +64 -8
  152. package/dist/rules/catalog.js +223 -32
  153. package/dist/rules/in-effect.d.ts +1 -1
  154. package/dist/rules/rules.schema.json +100 -27
  155. package/dist/rules/settings.d.ts +1 -1
  156. package/dist/rules/settings.js +7 -0
  157. package/dist/session/PreferenceStore.d.ts +18 -1
  158. package/dist/session/PreferenceStore.js +29 -1
  159. package/docs/authoring/README.md +33 -21
  160. package/docs/authoring/agents.md +38 -7
  161. package/docs/authoring/components.md +115 -10
  162. package/docs/authoring/migrating.md +878 -0
  163. package/docs/authoring/notation.md +285 -30
  164. package/docs/authoring/retired.md +1 -0
  165. package/docs/authoring/structure-rules.md +84 -45
  166. package/docs/authoring/structure.md +67 -2
  167. package/docs/authoring/visualizations.md +167 -25
  168. package/package.json +12 -7
  169. package/src/compiler/astro.ts +72 -21
  170. package/src/compiler/cli.ts +143 -35
  171. package/src/compiler/collections.ts +158 -27
  172. package/src/compiler/content-schema.ts +108 -0
  173. package/src/compiler/content-schemas.ts +391 -0
  174. package/src/compiler/course-config.ts +86 -15
  175. package/src/compiler/exports/combined-html.ts +669 -0
  176. package/src/compiler/exports/download-index.ts +36 -0
  177. package/src/compiler/exports/export-chapters.ts +233 -0
  178. package/src/compiler/exports/export-pages.ts +44 -0
  179. package/src/compiler/exports/html-tree.ts +62 -0
  180. package/src/compiler/exports/latex/capture.ts +651 -0
  181. package/src/compiler/exports/latex/compile.ts +168 -0
  182. package/src/compiler/exports/latex/document.ts +479 -0
  183. package/src/compiler/exports/latex/explico.cls +415 -0
  184. package/src/compiler/exports/latex/numbering.ts +74 -0
  185. package/src/compiler/exports/latex/serialize.ts +787 -0
  186. package/src/compiler/exports/latex/texlive-packages.txt +76 -0
  187. package/src/compiler/exports/latex-export.ts +255 -0
  188. package/src/compiler/exports/markdown/serialize.ts +632 -0
  189. package/src/compiler/exports/markdown-export.ts +329 -0
  190. package/src/compiler/exports/standalone-html.ts +39 -70
  191. package/src/compiler/exports/static-tree.ts +944 -0
  192. package/src/compiler/exports/zip.ts +88 -0
  193. package/src/compiler/lesson-derivation.ts +13 -8
  194. package/src/compiler/manifest.ts +387 -40
  195. package/src/compiler/render-diagnostics.ts +111 -0
  196. package/src/components/CourseContents.astro +6 -0
  197. package/src/components/HoverPanel.astro +204 -6
  198. package/src/components/PartOverview.astro +18 -9
  199. package/src/components/abbreviation/AbbreviationGlossary.astro +6 -2
  200. package/src/components/abbreviation/AbbreviationLayer.astro +21 -12
  201. package/src/components/assessments/AssessmentRunner.tsx +7 -1
  202. package/src/components/assessments/AssessmentSet.astro +14 -1
  203. package/src/components/block-heading.ts +38 -0
  204. package/src/components/card-arrow.ts +18 -4
  205. package/src/components/card-carry.ts +406 -0
  206. package/src/components/card-drag.ts +1 -30
  207. package/src/components/card-fetch.ts +87 -0
  208. package/src/components/card-manager.ts +1222 -38
  209. package/src/components/card-memory.ts +5 -1
  210. package/src/components/card-placement.ts +211 -13
  211. package/src/components/card-side.ts +281 -31
  212. package/src/components/citation/CitationLayer.astro +45 -40
  213. package/src/components/diagram/Diagram.astro +123 -44
  214. package/src/components/disclosure/print-as-shown.ts +42 -6
  215. package/src/components/figure/Figure.astro +17 -1
  216. package/src/components/figure/FloatCaption.astro +4 -17
  217. package/src/components/figure/figure-state.ts +26 -17
  218. package/src/components/float-toolbar.ts +274 -0
  219. package/src/components/memo/Memo.astro +21 -10
  220. package/src/components/memo/ShortNote.astro +4 -0
  221. package/src/components/notation/NotationGlossary.astro +22 -17
  222. package/src/components/notation/NotationLayer.astro +300 -79
  223. package/src/components/notation/NotationMath.astro +8 -1
  224. package/src/components/notation/render-notation-math.ts +51 -5
  225. package/src/components/readable-text.ts +25 -0
  226. package/src/components/reference/FloatRefLayer.astro +162 -89
  227. package/src/components/starlight/HeadingAnchorEnhancer.astro +13 -8
  228. package/src/components/starlight/KeyedFloatEnhancer.astro +28 -9
  229. package/src/components/starlight/LayoutPageSidebar.astro +17 -3
  230. package/src/components/starlight/LayoutSocialIcons.astro +79 -29
  231. package/src/components/starlight/LessonExportActions.astro +384 -310
  232. package/src/components/starlight/LessonFooter.astro +66 -8
  233. package/src/components/starlight/PrintController.astro +28 -0
  234. package/src/components/starlight/TextSizeControl.astro +137 -0
  235. package/src/components/starlight/print-controller.ts +380 -0
  236. package/src/components/starlight/section-summary.ts +13 -5
  237. package/src/components/table/Table.astro +14 -22
  238. package/src/components/table/table-truncation.ts +15 -0
  239. package/src/content-config.ts +20 -312
  240. package/src/curriculum/validation.ts +72 -24
  241. package/src/reference/base-abbreviations.mjs +2 -2
  242. package/src/reference/base-operators.mjs +8 -10
  243. package/src/reference/base-roman-labels.mjs +4 -3
  244. package/src/reference/citation-format.mjs +74 -0
  245. package/src/reference/consistency.ts +47 -61
  246. package/src/reference/curriculum-alignment.ts +4 -41
  247. package/src/reference/derived-entries.ts +68 -0
  248. package/src/reference/derived-notation.mjs +121 -0
  249. package/src/reference/diagram-fit.ts +104 -0
  250. package/src/reference/diagram-layout.ts +182 -51
  251. package/src/reference/diagrams-validate.ts +2 -1
  252. package/src/reference/equations-validate.ts +41 -2
  253. package/src/reference/equations.mjs +120 -22
  254. package/src/reference/examples-validate.ts +1 -0
  255. package/src/reference/figure-fit.ts +139 -0
  256. package/src/reference/figure-pictures.ts +96 -0
  257. package/src/reference/figure-symbols.ts +117 -0
  258. package/src/reference/figures-validate.ts +2 -1
  259. package/src/reference/gate-abbreviations.ts +1 -1
  260. package/src/reference/gate-course-rules.ts +22 -7
  261. package/src/reference/gate-heading-length.ts +25 -7
  262. package/src/reference/gate-heading-structure.ts +40 -1
  263. package/src/reference/gate-indices.ts +470 -0
  264. package/src/reference/gate-math.ts +102 -13
  265. package/src/reference/gate-mdx-expression.ts +45 -0
  266. package/src/reference/gate-non-chapter-pages.ts +241 -0
  267. package/src/reference/gate-part-pages.ts +1 -1
  268. package/src/reference/gate-prop-text.ts +145 -0
  269. package/src/reference/gate-prose-notation.ts +19 -5
  270. package/src/reference/gate-retired-keys.ts +101 -0
  271. package/src/reference/gloss.ts +2 -0
  272. package/src/reference/katex-measure.ts +384 -0
  273. package/src/reference/keyed-float-validate.ts +22 -0
  274. package/src/reference/keyed-jsx-floats.mjs +11 -14
  275. package/src/reference/lab-katex-validate.ts +156 -0
  276. package/src/reference/lab-math-scope.ts +15 -1
  277. package/src/reference/lint-ignore.ts +4 -1
  278. package/src/reference/math-glyphs.mjs +851 -46
  279. package/src/reference/mixed-content.ts +11 -2
  280. package/src/reference/page-notation.ts +114 -14
  281. package/src/reference/raw-floats.mjs +37 -13
  282. package/src/reference/references.ts +14 -0
  283. package/src/reference/registry.ts +170 -9
  284. package/src/reference/rehype-fail-katex-errors.mjs +19 -0
  285. package/src/reference/rehype-heading-anchors.mjs +11 -1
  286. package/src/reference/remark-citation.mjs +0 -0
  287. package/src/reference/remark-notation.mjs +275 -23
  288. package/src/reference/render-lab-math.ts +40 -5
  289. package/src/reference/sections-validate.ts +67 -3
  290. package/src/reference/sections.mjs +92 -17
  291. package/src/reference/tables-validate.ts +40 -2
  292. package/src/reference/text-measure.ts +63 -0
  293. package/src/reference/text-widths.ts +133 -0
  294. package/src/reference/types.ts +75 -8
  295. package/src/rules/catalog.ts +241 -31
  296. package/src/rules/settings.ts +10 -0
  297. package/src/session/PreferenceStore.ts +53 -0
  298. package/src/styles/global.css +322 -83
@@ -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';
@@ -16,6 +17,7 @@ import '../../reference/examples-validate.js';
16
17
  import '../../reference/folds-validate.js';
17
18
  import '../../reference/knowledge-checks-validate.js';
18
19
  import '../../reference/raw-floats-validate.js';
20
+ import '../../reference/lab-katex-validate.js';
19
21
  import '../../reference/gate-math.js';
20
22
  import '../../reference/mixed-content.js';
21
23
  import '../../reference/gloss.js';
@@ -23,17 +25,25 @@ import '../../rules/waivers.js';
23
25
  import '../knowledge-version.js';
24
26
  import '../lesson-derivation.js';
25
27
 
26
- /** Inputs for producing one standalone lesson from Astro's final HTML. */
28
+ /** Inputs for producing one standalone page from Astro's final HTML. */
27
29
  interface StandaloneLessonInput {
28
30
  readonly html: string;
29
31
  readonly sourceFile: string;
30
32
  readonly outputRoot: string;
33
+ /** The page's docs slug: a chapter's, or `index`, `motion/index`, `glossary`. */
31
34
  readonly lessonSlug: string;
32
35
  readonly canonicalBaseUrl?: string;
33
36
  }
34
- /** Build one fully inlined lesson document without writing it. */
37
+ /** Build one fully inlined page document without writing it. */
35
38
  declare function buildStandaloneLessonHtml(input: StandaloneLessonInput): Promise<string>;
36
- /** 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
+ */
37
47
  declare function writeStandaloneLessonExports(directory: URL, manifest: Manifest): Promise<void>;
38
48
 
39
- 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
  };