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,625 @@
1
+ import { parse, parseFragment } from "parse5";
2
+ import {
3
+ attribute,
4
+ childrenOf,
5
+ elements,
6
+ hasClass,
7
+ inferBasePrefix,
8
+ isElement
9
+ } from "./html-tree.js";
10
+ const SKIPPED_TAGS = /* @__PURE__ */ new Set([
11
+ "button",
12
+ "input",
13
+ "nav",
14
+ "noscript",
15
+ "script",
16
+ "select",
17
+ "style",
18
+ "svg",
19
+ "template",
20
+ "textarea",
21
+ "astro-island"
22
+ ]);
23
+ const SKIPPED_CLASSES = [
24
+ "sr-only",
25
+ "sl-anchor-link",
26
+ "keyed-equation__controls",
27
+ "float-tools",
28
+ "float-caption__anchor",
29
+ "keyed-figure__tools-stub",
30
+ "short-note__collapse",
31
+ "notation-enhancement-help",
32
+ "pagination-links",
33
+ "explico-site-footer",
34
+ "lesson-breadcrumb",
35
+ // A part page's stats line and chapter list are navigation the site builds;
36
+ // a print export's own contents replace them. Its objectives stay.
37
+ "part-meta",
38
+ "part-chapters"
39
+ ];
40
+ const REF_CLASSES = [
41
+ ["equation-ref", "equation"],
42
+ ["figure-ref", "figure"],
43
+ ["table-ref", "table"],
44
+ ["diagram-ref", "diagram"],
45
+ ["section-ref", "section"],
46
+ ["example-ref", "example"],
47
+ ["knowledge-check-ref", "check"],
48
+ ["memo-ref", "memo"]
49
+ ];
50
+ function skipped(node) {
51
+ if (SKIPPED_TAGS.has(node.tagName)) return true;
52
+ if (attribute(node, "aria-hidden") === "true") return true;
53
+ if (attribute(node, "data-print-omit") !== void 0) return true;
54
+ return SKIPPED_CLASSES.some((name) => hasClass(node, name));
55
+ }
56
+ function readableText(node) {
57
+ if (!isElement(node)) {
58
+ return node.nodeName === "#text" ? node.value : "";
59
+ }
60
+ if (skipped(node)) return "";
61
+ return childrenOf(node).map(readableText).join("");
62
+ }
63
+ function collapse(text) {
64
+ return text.replace(/[ \t\r\n\f]+/gu, " ");
65
+ }
66
+ function texOf(katex) {
67
+ const annotation = elements(
68
+ katex,
69
+ (node) => node.tagName === "annotation" && attribute(node, "encoding") === "application/x-tex"
70
+ )[0];
71
+ if (!annotation) return void 0;
72
+ return childrenOf(annotation).map((child) => "value" in child ? String(child.value) : "").join("").trim();
73
+ }
74
+ function firstByClass(root, name) {
75
+ return elements(root, (node) => hasClass(node, name))[0];
76
+ }
77
+ function cleanRoute(pathname, base) {
78
+ let path = pathname;
79
+ if (path.startsWith(base)) path = path.slice(base.length);
80
+ return path.replace(/\/index\.html$/u, "").replace(/\.html$/u, "").replace(/^\/+|\/+$/gu, "");
81
+ }
82
+ function courseTarget(href, context) {
83
+ const trimmed = href.trim();
84
+ if (trimmed.startsWith("#")) {
85
+ return { route: context.route, anchor: decodeURIComponent(trimmed.slice(1)) };
86
+ }
87
+ if (/^[a-z][a-z0-9+.-]*:/iu.test(trimmed) || trimmed.startsWith("//")) return void 0;
88
+ const page = new URL(
89
+ `${context.base}${context.route ? `${context.route}/` : ""}`,
90
+ "https://explico.invalid"
91
+ );
92
+ const url = new URL(trimmed, page);
93
+ return {
94
+ route: cleanRoute(url.pathname, context.base),
95
+ anchor: decodeURIComponent(url.hash.slice(1))
96
+ };
97
+ }
98
+ function trimInlines(inlines) {
99
+ const result = [...inlines];
100
+ while (result[0]?.kind === "text" && !result[0].value.trim()) result.shift();
101
+ while (result.at(-1)?.kind === "text" && !result.at(-1).value.trim()) {
102
+ result.pop();
103
+ }
104
+ const first = result[0];
105
+ if (first?.kind === "text") result[0] = { kind: "text", value: first.value.replace(/^ +/u, "") };
106
+ const last = result.at(-1);
107
+ if (last?.kind === "text") {
108
+ result[result.length - 1] = { kind: "text", value: last.value.replace(/ +$/u, "") };
109
+ }
110
+ return result;
111
+ }
112
+ function readLink(node, context) {
113
+ const href = attribute(node, "href") ?? "";
114
+ const children = readInlines(node, context);
115
+ if (hasClass(node, "citation-ref")) {
116
+ const number = attribute(node, "data-citation-number") ?? readableText(node).replace(/\D/gu, "");
117
+ return [
118
+ {
119
+ kind: "cite",
120
+ number,
121
+ anchor: href.replace(/^#/u, ""),
122
+ sourceId: attribute(node, "data-citation-id"),
123
+ locator: attribute(node, "data-citation-locator") || void 0
124
+ }
125
+ ];
126
+ }
127
+ if (hasClass(node, "memo-marker")) {
128
+ return [
129
+ {
130
+ kind: "memoMark",
131
+ anchor: href.replace(/^#/u, ""),
132
+ label: attribute(node, "data-memo-number") ?? readableText(node).trim()
133
+ }
134
+ ];
135
+ }
136
+ if (hasClass(node, "short-note-marker")) {
137
+ return [{ kind: "noteMark", anchor: href.replace(/^#/u, ""), children }];
138
+ }
139
+ if (hasClass(node, "notation-term")) {
140
+ return [{ kind: "term", key: attribute(node, "data-notation-key") ?? "", children }];
141
+ }
142
+ if (!href) return children;
143
+ const target = courseTarget(href, context);
144
+ if (!target) return [{ kind: "link", href, children }];
145
+ const refKind = REF_CLASSES.find(([name]) => hasClass(node, name))?.[1] ?? (target.route === context.route ? "anchor" : "page");
146
+ return [{ kind: "ref", refKind, target, children }];
147
+ }
148
+ function readInline(node, context) {
149
+ if (!isElement(node)) {
150
+ if (node.nodeName !== "#text") return [];
151
+ const value = collapse(node.value);
152
+ return value ? [{ kind: "text", value }] : [];
153
+ }
154
+ if (hasClass(node, "katex")) {
155
+ const tex = texOf(node);
156
+ return tex ? [{ kind: "math", tex }] : [];
157
+ }
158
+ if (skipped(node)) return [];
159
+ switch (node.tagName) {
160
+ case "em":
161
+ case "i":
162
+ return [{ kind: "emphasis", children: readInlines(node, context) }];
163
+ case "strong":
164
+ case "b":
165
+ return [{ kind: "strong", children: readInlines(node, context) }];
166
+ case "sup":
167
+ if (hasClass(node, "memo-marker-wrap")) return readInlines(node, context);
168
+ return [{ kind: "superscript", children: readInlines(node, context) }];
169
+ case "sub":
170
+ return [{ kind: "subscript", children: readInlines(node, context) }];
171
+ case "code":
172
+ case "kbd":
173
+ return [{ kind: "code", value: readableText(node) }];
174
+ case "br":
175
+ return [{ kind: "break" }];
176
+ case "a":
177
+ return readLink(node, context);
178
+ default:
179
+ return readInlines(node, context);
180
+ }
181
+ }
182
+ function readInlines(node, context) {
183
+ const result = [];
184
+ for (const child of childrenOf(node)) {
185
+ for (const inline of readInline(child, context)) {
186
+ const previous = result.at(-1);
187
+ if (inline.kind === "text" && previous?.kind === "text") {
188
+ result[result.length - 1] = {
189
+ kind: "text",
190
+ value: collapse(previous.value + inline.value)
191
+ };
192
+ } else {
193
+ result.push(inline);
194
+ }
195
+ }
196
+ }
197
+ return result;
198
+ }
199
+ function inlineContent(node, context) {
200
+ return node ? trimInlines(readInlines(node, context)) : [];
201
+ }
202
+ function numberAttribute(node, name) {
203
+ return Number.parseFloat(attribute(node, name) ?? "0") || 0;
204
+ }
205
+ function readDiagramGeometry(figure) {
206
+ const svg = elements(figure, (node) => node.tagName === "svg" && !!attribute(node, "viewBox")).find((node) => elements(node, (child) => hasClass(child, "diagram-node")).length > 0);
207
+ if (!svg) return void 0;
208
+ const [, , width, height] = (attribute(svg, "viewBox") ?? "0 0 0 0").trim().split(/[\s,]+/u).map(Number);
209
+ const nodes = elements(svg, (node) => hasClass(node, "diagram-node")).flatMap((group) => {
210
+ const rect = elements(group, (node) => node.tagName === "rect")[0];
211
+ if (!rect) return [];
212
+ const text = firstByClass(group, "diagram-node-text");
213
+ const formula = firstByClass(group, "diagram-node-formula");
214
+ const katex = formula ? elements(formula, (node) => hasClass(node, "katex"))[0] : void 0;
215
+ const label = text ? collapse(readableText(text)).trim() : "";
216
+ const tex = katex ? texOf(katex) : void 0;
217
+ return [
218
+ {
219
+ x: numberAttribute(rect, "x"),
220
+ y: numberAttribute(rect, "y"),
221
+ width: numberAttribute(rect, "width"),
222
+ height: numberAttribute(rect, "height"),
223
+ radius: numberAttribute(rect, "rx"),
224
+ ...label ? { text: label } : {},
225
+ ...tex ? { tex } : {}
226
+ }
227
+ ];
228
+ });
229
+ const edges = elements(svg, (node) => node.tagName === "path" && hasClass(node, "diagram-edge")).map(
230
+ (path) => [...(attribute(path, "d") ?? "").matchAll(/[ML]\s*(-?[\d.]+)[\s,]+(-?[\d.]+)/gu)].map(
231
+ (match) => [Number(match[1]), Number(match[2])]
232
+ )
233
+ ).filter((points) => points.length >= 2);
234
+ return { width: width ?? 0, height: height ?? 0, nodes, edges };
235
+ }
236
+ function readCaption(figcaption, context) {
237
+ if (!figcaption) return [];
238
+ const text = firstByClass(figcaption, "float-caption__text");
239
+ return inlineContent(text, context);
240
+ }
241
+ function floatBase(node, prefix, context) {
242
+ const figcaption = elements(node, (child) => child.tagName === "figcaption")[0];
243
+ const key = attribute(node, `data-${prefix}-key`);
244
+ const short = attribute(node, `data-${prefix}-short`);
245
+ return {
246
+ anchor: attribute(node, "id") ?? "",
247
+ number: attribute(node, `data-${prefix}-number`) ?? "",
248
+ ...key ? { key } : {},
249
+ ...short ? { short } : {},
250
+ caption: readCaption(figcaption, context)
251
+ };
252
+ }
253
+ function readFigurePicture(body) {
254
+ if (!body) return { kind: "capture" };
255
+ const diagram = elements(body, (node) => node.tagName === "figure" && hasClass(node, "diagram"))[0];
256
+ if (diagram) {
257
+ const geometry = readDiagramGeometry(diagram);
258
+ if (geometry) return { kind: "diagram", geometry };
259
+ }
260
+ const meaningful = childrenOf(body).filter(
261
+ (child) => isElement(child) && !["script", "style"].includes(child.tagName) || child.nodeName === "#text" && child.value.trim()
262
+ );
263
+ const only = meaningful.length === 1 && isElement(meaningful[0]) ? meaningful[0] : void 0;
264
+ const image = only?.tagName === "img" ? only : only?.tagName === "picture" ? elements(only, (node) => node.tagName === "img")[0] : void 0;
265
+ if (image && attribute(image, "src")) {
266
+ return { kind: "image", src: attribute(image, "src"), alt: attribute(image, "alt") ?? "" };
267
+ }
268
+ return { kind: "capture" };
269
+ }
270
+ function readTableCells(row, context) {
271
+ return childrenOf(row).filter((cell) => isElement(cell) && ["td", "th"].includes(cell.tagName)).map((cell) => {
272
+ const style = attribute(cell, "style") ?? "";
273
+ const align = attribute(cell, "align") ?? /text-align:\s*(left|center|right)/u.exec(style)?.[1];
274
+ return {
275
+ header: cell.tagName === "th",
276
+ colspan: Number.parseInt(attribute(cell, "colspan") ?? "1", 10) || 1,
277
+ rowspan: Number.parseInt(attribute(cell, "rowspan") ?? "1", 10) || 1,
278
+ ...align === "left" || align === "center" || align === "right" ? { align } : {},
279
+ children: inlineContent(cell, context)
280
+ };
281
+ });
282
+ }
283
+ function readTable(table, context) {
284
+ const rows = (section) => elements(table, (node) => node.tagName === section).flatMap(
285
+ (group) => childrenOf(group).filter(
286
+ (row) => isElement(row) && row.tagName === "tr" && !hasClass(row, "keyed-table__more-row")
287
+ )
288
+ ).map((row) => readTableCells(row, context));
289
+ const head = rows("thead");
290
+ const body = rows("tbody");
291
+ if (head.length === 0 && body.length === 0) {
292
+ return {
293
+ head: [],
294
+ body: childrenOf(table).filter((row) => isElement(row) && row.tagName === "tr").map((row) => readTableCells(row, context))
295
+ };
296
+ }
297
+ return { head, body };
298
+ }
299
+ function headingOf(wrapper) {
300
+ return childrenOf(wrapper).find(
301
+ (child) => isElement(child) && /^h[1-6]$/u.test(child.tagName)
302
+ );
303
+ }
304
+ function blockTitle(heading, context) {
305
+ if (!heading) return [];
306
+ return inlineContent(firstByClass(heading, "block-heading__title") ?? heading, context);
307
+ }
308
+ function readExamples(node, context) {
309
+ const heading = elements(node, (child) => hasClass(child, "block-heading"))[0];
310
+ const items = elements(node, (child) => hasClass(child, "compact-example")).map((section) => {
311
+ const title = elements(section, (child) => hasClass(child, "compact-example__heading"))[0];
312
+ return {
313
+ title: title ? inlineContent(title, context) : [{ kind: "text", value: attribute(section, "data-example-label") ?? "" }],
314
+ children: readBlocks(section, context, (child) => child !== title)
315
+ };
316
+ });
317
+ return {
318
+ kind: "examples",
319
+ anchor: attribute(heading ?? node, "id") ?? "",
320
+ number: (heading && attribute(heading, "data-ex-number")) ?? "",
321
+ title: blockTitle(heading, context),
322
+ items
323
+ };
324
+ }
325
+ function readNotationEntry(entry, context) {
326
+ const key = attribute(entry, "data-notation-entry") ?? "";
327
+ const symbol = elements(entry, (node) => hasClass(node, "notation-symbol"))[0];
328
+ const symbolKatex = symbol ? elements(symbol, (node) => hasClass(node, "katex"))[0] : void 0;
329
+ const label = elements(entry, (node) => node.tagName === "a" && !!attribute(node, "data-notation-link"))[0];
330
+ const summary = elements(entry, (node) => attribute(node, "data-notation-summary") !== void 0)[0];
331
+ const units = elements(entry, (node) => attribute(node, "data-notation-units") !== void 0)[0];
332
+ const formula = elements(entry, (node) => attribute(node, "data-notation-formula") !== void 0)[0];
333
+ const formulaKatex = formula ? elements(formula, (node) => hasClass(node, "katex"))[0] : void 0;
334
+ const unitsInlines = units ? trimInlines(
335
+ readInlines(units, context).filter(
336
+ (inline) => !(inline.kind === "strong")
337
+ )
338
+ ) : void 0;
339
+ const symbolTex = symbolKatex ? texOf(symbolKatex) : void 0;
340
+ const formulaTex = formulaKatex ? texOf(formulaKatex) : void 0;
341
+ return {
342
+ key,
343
+ anchor: attribute(entry, "id") ?? `notation-${key}`,
344
+ ...symbolTex ? { symbolTex } : {},
345
+ label: inlineContent(label, context),
346
+ summary: inlineContent(summary, context),
347
+ ...unitsInlines && unitsInlines.length > 0 ? { units: unitsInlines } : {},
348
+ ...formulaTex ? { formulaTex } : {}
349
+ };
350
+ }
351
+ function readBlocks(node, context, keep = () => true) {
352
+ const blocks = [];
353
+ let pending = [];
354
+ const flush = () => {
355
+ const content = trimInlines(pending);
356
+ if (content.length > 0) blocks.push({ kind: "paragraph", children: content });
357
+ pending = [];
358
+ };
359
+ for (const child of childrenOf(node)) {
360
+ if (!keep(child)) continue;
361
+ const block = isElement(child) ? readBlock(child, context) : void 0;
362
+ if (block === void 0) {
363
+ pending.push(...readInline(child, context));
364
+ continue;
365
+ }
366
+ flush();
367
+ blocks.push(...block);
368
+ }
369
+ flush();
370
+ return blocks;
371
+ }
372
+ function readBlock(node, context) {
373
+ if (hasClass(node, "katex-display")) {
374
+ const katex = elements(node, (child) => hasClass(child, "katex"))[0];
375
+ const tex = katex ? texOf(katex) : void 0;
376
+ return tex ? [{ kind: "displayMath", tex }] : [];
377
+ }
378
+ if (skipped(node)) return [];
379
+ if (hasClass(node, "notation-layer")) return [];
380
+ if (hasClass(node, "keyed-equation")) {
381
+ const katex = elements(node, (child) => hasClass(child, "katex"))[0];
382
+ const key = attribute(node, "data-eq-key");
383
+ return [
384
+ {
385
+ kind: "equation",
386
+ anchor: attribute(node, "id") ?? "",
387
+ number: attribute(node, "data-eq-number") ?? "",
388
+ ...key ? { key } : {},
389
+ tex: (katex && texOf(katex)) ?? ""
390
+ }
391
+ ];
392
+ }
393
+ if (node.tagName === "figure" && hasClass(node, "keyed-figure")) {
394
+ const body = firstByClass(node, "keyed-figure__body");
395
+ const island = body ? elements(body, (child) => child.tagName === "astro-island")[0] : void 0;
396
+ const figcaption = elements(node, (child) => child.tagName === "figcaption")[0];
397
+ const live = figcaption ? firstByClass(figcaption, "float-caption__state") : void 0;
398
+ const alt = island ? void 0 : body && attribute(body, "aria-label");
399
+ return [
400
+ {
401
+ kind: "figure",
402
+ ...floatBase(node, "fig", context),
403
+ live: inlineContent(live, context),
404
+ picture: readFigurePicture(body),
405
+ ...alt ? { alt } : {}
406
+ }
407
+ ];
408
+ }
409
+ if (node.tagName === "figure" && hasClass(node, "keyed-table")) {
410
+ const table = elements(node, (child) => child.tagName === "table")[0];
411
+ return [
412
+ {
413
+ kind: "table",
414
+ ...floatBase(node, "tab", context),
415
+ ...table ? readTable(table, context) : { head: [], body: [] }
416
+ }
417
+ ];
418
+ }
419
+ if (node.tagName === "figure" && hasClass(node, "diagram")) {
420
+ const geometry = readDiagramGeometry(node);
421
+ return [
422
+ {
423
+ kind: "diagram",
424
+ ...floatBase(node, "dia", context),
425
+ geometry: geometry ?? { width: 0, height: 0, nodes: [], edges: [] }
426
+ }
427
+ ];
428
+ }
429
+ if (hasClass(node, "memo-block")) {
430
+ const heading = elements(node, (child) => hasClass(child, "memo-block__heading"))[0];
431
+ const body = firstByClass(node, "memo-block__body");
432
+ const short = attribute(node, "data-memo-short");
433
+ return [
434
+ {
435
+ kind: "memo",
436
+ anchor: (heading && attribute(heading, "id")) ?? `memo-${attribute(node, "data-memo-key") ?? ""}`,
437
+ label: attribute(node, "data-memo-number") ?? "",
438
+ ...short ? { short } : {},
439
+ title: inlineContent(heading, context),
440
+ children: body ? readBlocks(body, context) : []
441
+ }
442
+ ];
443
+ }
444
+ if (node.tagName === "aside" && hasClass(node, "short-note")) {
445
+ const body = firstByClass(node, "short-note__body");
446
+ return [
447
+ {
448
+ kind: "note",
449
+ anchor: attribute(node, "id") ?? "",
450
+ title: attribute(node, "aria-label") ?? "",
451
+ children: body ? readBlocks(body, context) : []
452
+ }
453
+ ];
454
+ }
455
+ if (node.tagName === "aside" && hasClass(node, "starlight-aside")) {
456
+ const variant = /starlight-aside--([a-z]+)/u.exec(attribute(node, "class") ?? "")?.[1] ?? "note";
457
+ const title = firstByClass(node, "starlight-aside__title");
458
+ const content = firstByClass(node, "starlight-aside__content");
459
+ return [
460
+ {
461
+ kind: "aside",
462
+ variant,
463
+ title: inlineContent(title, context),
464
+ children: content ? readBlocks(content, context) : []
465
+ }
466
+ ];
467
+ }
468
+ if (hasClass(node, "fold")) {
469
+ const label = firstByClass(node, "fold__label");
470
+ const body = firstByClass(node, "fold__body");
471
+ const namesFloats = attribute(node, "data-names-floats") !== void 0;
472
+ return [
473
+ {
474
+ kind: "fold",
475
+ ...label && !namesFloats ? { title: inlineContent(label, context) } : {},
476
+ children: body ? readBlocks(body, context) : []
477
+ }
478
+ ];
479
+ }
480
+ if (node.tagName === "compact-examples" || hasClass(node, "compact-examples")) {
481
+ return [readExamples(node, context)];
482
+ }
483
+ if (hasClass(node, "sl-heading-wrapper")) {
484
+ const heading = headingOf(node);
485
+ if (!heading) return readBlocks(node, context);
486
+ if (attribute(heading, "data-block-kind") === "check") {
487
+ const anchor = attribute(heading, "id") ?? "";
488
+ return [
489
+ {
490
+ kind: "check",
491
+ anchor,
492
+ number: attribute(heading, "data-check-number") ?? "",
493
+ assessmentId: anchor.replace(/^check-/u, ""),
494
+ title: blockTitle(heading, context)
495
+ }
496
+ ];
497
+ }
498
+ return readBlock(heading, context) ?? [];
499
+ }
500
+ if (/^h[1-6]$/u.test(node.tagName)) {
501
+ if (hasClass(node, "citations-heading")) return [];
502
+ const anchor = attribute(node, "id");
503
+ const number = attribute(node, "data-sec-number");
504
+ return [
505
+ {
506
+ kind: "heading",
507
+ level: Number(node.tagName.slice(1)),
508
+ ...anchor ? { anchor } : {},
509
+ ...number ? { number } : {},
510
+ children: inlineContent(node, context)
511
+ }
512
+ ];
513
+ }
514
+ if (node.tagName === "ol" && hasClass(node, "citations-list")) {
515
+ return [
516
+ {
517
+ kind: "references",
518
+ items: childrenOf(node).filter((item) => isElement(item) && item.tagName === "li").map((item) => {
519
+ const anchor = attribute(item, "id") ?? "";
520
+ return {
521
+ number: anchor.replace(/^cite-/u, ""),
522
+ anchor,
523
+ children: trimInlines(
524
+ readInlines(item, context).filter(
525
+ (inline) => !(inline.kind === "ref" && inline.target.anchor.startsWith("cite-ref-"))
526
+ )
527
+ )
528
+ };
529
+ })
530
+ }
531
+ ];
532
+ }
533
+ switch (node.tagName) {
534
+ case "p": {
535
+ const children = inlineContent(node, context);
536
+ return children.length > 0 ? [{ kind: "paragraph", children }] : [];
537
+ }
538
+ case "ul":
539
+ case "ol": {
540
+ const start = Number.parseInt(attribute(node, "start") ?? "", 10);
541
+ return [
542
+ {
543
+ kind: "list",
544
+ ordered: node.tagName === "ol",
545
+ ...Number.isFinite(start) ? { start } : {},
546
+ items: childrenOf(node).filter((item) => isElement(item) && item.tagName === "li").map((item) => readBlocks(item, context))
547
+ }
548
+ ];
549
+ }
550
+ case "blockquote":
551
+ return [{ kind: "quote", children: readBlocks(node, context) }];
552
+ case "pre":
553
+ return [{ kind: "code", value: readableText(node).replace(/\n$/u, "") }];
554
+ case "hr":
555
+ return [{ kind: "rule" }];
556
+ case "img": {
557
+ const src = attribute(node, "src");
558
+ return src ? [{ kind: "image", src, alt: attribute(node, "alt") ?? "" }] : [];
559
+ }
560
+ case "table":
561
+ return [
562
+ {
563
+ kind: "table",
564
+ anchor: attribute(node, "id") ?? "",
565
+ number: "",
566
+ caption: [],
567
+ ...readTable(node, context)
568
+ }
569
+ ];
570
+ case "details":
571
+ return readBlocks(node, context, (child) => !(isElement(child) && child.tagName === "summary"));
572
+ case "div":
573
+ case "section":
574
+ case "article":
575
+ case "header":
576
+ case "footer":
577
+ case "main":
578
+ case "figure":
579
+ case "aside":
580
+ case "dl":
581
+ case "dd":
582
+ case "dt":
583
+ case "li":
584
+ case "compact-example":
585
+ return readBlocks(node, context);
586
+ default:
587
+ return void 0;
588
+ }
589
+ }
590
+ function readInlineFragment(html, route, base = "/") {
591
+ return inlineContent(parseFragment(html), { route, base });
592
+ }
593
+ function readStaticPage(html, slug, route) {
594
+ const document = parse(html);
595
+ const base = inferBasePrefix(document);
596
+ const context = { route, base };
597
+ const main = elements(document, (node) => node.tagName === "main")[0];
598
+ if (!main) throw new Error(`Built page ${slug} has no <main> element.`);
599
+ const title = elements(main, (node) => node.tagName === "h1")[0];
600
+ const content = elements(main, (node) => hasClass(node, "sl-markdown-content"))[0];
601
+ if (!content) throw new Error(`Built page ${slug} has no .sl-markdown-content element.`);
602
+ const layer = elements(
603
+ main,
604
+ (node) => hasClass(node, "notation-layer") && attribute(node, "data-notation-layer-id") === "page-notation"
605
+ )[0];
606
+ const entries = layer ? elements(layer, (node) => hasClass(node, "notation-entry") && attribute(node, "data-notation-entry") !== void 0) : [];
607
+ const remarks = layer ? elements(layer, (node) => node.tagName === "ul" && hasClass(node, "notation-suspended")).flatMap(
608
+ (list) => childrenOf(list).filter((item) => isElement(item) && item.tagName === "li").map((item) => inlineContent(item, context))
609
+ ) : [];
610
+ return {
611
+ slug,
612
+ route,
613
+ title: title ? collapse(readableText(title)).trim() : slug,
614
+ blocks: readBlocks(content, context),
615
+ notation: entries.map((entry) => readNotationEntry(entry, context)),
616
+ notationRemarks: remarks
617
+ };
618
+ }
619
+ export {
620
+ courseTarget,
621
+ readDiagramGeometry,
622
+ readInlineFragment,
623
+ readStaticPage,
624
+ readableText
625
+ };
@@ -0,0 +1,16 @@
1
+ import { Buffer } from 'node:buffer';
2
+
3
+ /**
4
+ * A minimal, deterministic ZIP writer for export downloads: entries sorted by
5
+ * path, one fixed timestamp (1980-01-01, the format's epoch), UTF-8 names,
6
+ * deflate where it helps. The same files always give the same bytes, so a
7
+ * rebuild does not churn the published download.
8
+ */
9
+ interface ZipEntry {
10
+ /** Path inside the archive, `/`-separated, no leading slash. */
11
+ readonly path: string;
12
+ readonly content: string | Uint8Array;
13
+ }
14
+ declare function zip(entries: readonly ZipEntry[]): Buffer;
15
+
16
+ export { type ZipEntry, zip };