explico 0.13.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +879 -1
- package/README.md +200 -30
- package/dist/compiler/astro.d.ts +15 -2
- package/dist/compiler/astro.js +40 -11
- package/dist/compiler/cli-main.d.ts +43 -0
- package/dist/compiler/cli-main.js +392 -0
- package/dist/compiler/cli.d.ts +424 -9
- package/dist/compiler/cli.js +1289 -8
- package/dist/compiler/collections.d.ts +88 -1
- package/dist/compiler/collections.js +170 -25
- package/dist/compiler/course-config.d.ts +1 -1
- package/dist/compiler/course-config.js +105 -1
- package/dist/compiler/decks.d.ts +161 -0
- package/dist/compiler/decks.js +235 -0
- package/dist/compiler/exports/combined-html.d.ts +5 -1
- package/dist/compiler/exports/deck-fit.d.ts +55 -0
- package/dist/compiler/exports/deck-fit.js +90 -0
- package/dist/compiler/exports/export-chapters.d.ts +37 -6
- package/dist/compiler/exports/export-chapters.js +64 -5
- package/dist/compiler/exports/export-pages.d.ts +30 -3
- package/dist/compiler/exports/export-pages.js +24 -1
- package/dist/compiler/exports/latex/beamerthemeexplico.sty +124 -0
- package/dist/compiler/exports/latex/cache.d.ts +78 -0
- package/dist/compiler/exports/latex/cache.js +129 -0
- package/dist/compiler/exports/latex/capture.d.ts +10 -1
- package/dist/compiler/exports/latex/capture.js +3 -1
- package/dist/compiler/exports/latex/compile.d.ts +13 -2
- package/dist/compiler/exports/latex/compile.js +12 -1
- package/dist/compiler/exports/latex/deck.d.ts +92 -0
- package/dist/compiler/exports/latex/deck.js +349 -0
- package/dist/compiler/exports/latex/document.d.ts +51 -6
- package/dist/compiler/exports/latex/document.js +71 -12
- package/dist/compiler/exports/latex/explico-common.sty +198 -0
- package/dist/compiler/exports/latex/explico.cls +13 -183
- package/dist/compiler/exports/latex/serialize.d.ts +60 -4
- package/dist/compiler/exports/latex/serialize.js +4 -2
- package/dist/compiler/exports/latex-export.d.ts +24 -2
- package/dist/compiler/exports/latex-export.js +107 -26
- package/dist/compiler/exports/markdown/serialize.d.ts +24 -4
- package/dist/compiler/exports/markdown/serialize.js +85 -8
- package/dist/compiler/exports/markdown-export.d.ts +6 -1
- package/dist/compiler/exports/markdown-export.js +50 -11
- package/dist/compiler/exports/standalone-html.d.ts +10 -5
- package/dist/compiler/exports/standalone-html.js +3 -3
- package/dist/compiler/exports/static-tree.d.ts +38 -1
- package/dist/compiler/exports/static-tree.js +120 -4
- package/dist/compiler/knowledge-version.d.ts +26 -2
- package/dist/compiler/knowledge-version.js +27 -17
- package/dist/compiler/lesson-derivation.d.ts +143 -6
- package/dist/compiler/lesson-derivation.js +145 -13
- package/dist/compiler/manifest.d.ts +133 -4
- package/dist/compiler/manifest.js +359 -58
- package/dist/compiler/page-versions.d.ts +160 -0
- package/dist/compiler/page-versions.js +198 -0
- package/dist/compiler/render-diagnostics.d.ts +5 -1
- package/dist/compiler/resolution-report.d.ts +24 -6
- package/dist/compiler/resolution-report.js +22 -4
- package/dist/compiler/validate-content.d.ts +7 -0
- package/dist/compiler/validate-content.js +160 -0
- package/dist/{course-config-rRtME5UV.d.ts → course-config-BLtIdkei.d.ts} +164 -1
- package/dist/curriculum/competency-graph.d.ts +54 -0
- package/dist/curriculum/competency-graph.js +41 -0
- package/dist/reference/base-library.d.mts +8 -0
- package/dist/reference/base-library.mjs +19 -0
- package/dist/reference/consistency.d.ts +8 -4
- package/dist/reference/consistency.js +76 -26
- package/dist/reference/deck-remark.d.mts +123 -0
- package/dist/reference/deck-remark.mjs +600 -0
- package/dist/reference/deck-source.d.ts +107 -0
- package/dist/reference/deck-source.js +140 -0
- package/dist/reference/decks-validate.d.ts +133 -0
- package/dist/reference/decks-validate.js +359 -0
- package/dist/reference/derived-entries.d.ts +5 -1
- package/dist/reference/diagram-fit.d.ts +1 -1
- package/dist/reference/figure-symbols.d.ts +23 -6
- package/dist/reference/figure-symbols.js +51 -15
- package/dist/reference/float-name.d.ts +43 -0
- package/dist/reference/float-name.js +58 -0
- package/dist/reference/folds-validate.d.ts +1 -1
- package/dist/reference/gate-abbreviations.d.ts +1 -1
- package/dist/reference/gate-indices.js +5 -4
- package/dist/reference/gate-math.d.ts +29 -2
- package/dist/reference/gate-math.js +99 -18
- package/dist/reference/gate-part-pages.d.ts +1 -1
- package/dist/reference/lab-katex-validate.d.ts +10 -6
- package/dist/reference/lab-katex-validate.js +17 -9
- package/dist/reference/lab-math-scope.js +29 -4
- package/dist/reference/math-glyphs.d.mts +25 -0
- package/dist/reference/math-glyphs.mjs +189 -25
- package/dist/reference/page-notation.d.ts +22 -3
- package/dist/reference/page-notation.js +32 -10
- package/dist/reference/references.d.ts +14 -1
- package/dist/reference/references.js +13 -0
- package/dist/reference/registry-scope.d.mts +33 -0
- package/dist/reference/registry-scope.mjs +34 -0
- package/dist/reference/registry.js +29 -10
- package/dist/reference/rehype-deck.d.mts +6 -0
- package/dist/reference/rehype-deck.mjs +286 -0
- package/dist/reference/rehype-heading-anchors.d.mts +20 -0
- package/dist/reference/rehype-heading-anchors.mjs +48 -6
- package/dist/reference/remark-notation.mjs +249 -40
- package/dist/reference/source-lines.d.mts +12 -0
- package/dist/reference/source-lines.mjs +65 -0
- package/dist/reference/types.d.ts +28 -0
- package/dist/rules/catalog.d.ts +21 -4
- package/dist/rules/catalog.js +221 -2
- package/dist/rules/course-rules.d.ts +5 -1
- package/dist/rules/course-rules.js +17 -3
- package/dist/rules/course-rules.schema.json +12 -0
- package/dist/rules/in-effect.d.ts +13 -3
- package/dist/rules/in-effect.js +30 -7
- package/dist/rules/rules.schema.json +259 -1
- package/dist/rules/settings.d.ts +1 -1
- package/dist/session/PreferenceStore.d.ts +6 -0
- package/dist/session/PreferenceStore.js +18 -1
- package/dist/session/deck-memory.d.ts +116 -0
- package/dist/session/deck-memory.js +143 -0
- package/dist/session/reading-memory.d.ts +96 -0
- package/dist/session/reading-memory.js +133 -0
- package/docs/VERSIONING.md +21 -18
- package/docs/authoring/README.md +29 -9
- package/docs/authoring/agents.md +63 -16
- package/docs/authoring/components.md +15 -0
- package/docs/authoring/deck.md +588 -0
- package/docs/authoring/migrating.md +1045 -16
- package/docs/authoring/notation.md +49 -6
- package/docs/authoring/structure.md +80 -1
- package/docs/authoring/versioning.md +54 -2
- package/docs/authoring/visualizations.md +7 -2
- package/docs/course-agent/AGENTS.md +37 -0
- package/docs/course-agent/agents/rules-reviewer.md +33 -0
- package/docs/course-agent/skills/draft-lesson/SKILL.md +37 -0
- package/docs/course-agent/skills/migrate-course/SKILL.md +31 -0
- package/docs/course-agent/skills/release-content/SKILL.md +24 -0
- package/docs/diagram-scaling.md +148 -0
- package/docs/privacy.md +59 -0
- package/package.json +8 -2
- package/src/compiler/astro.ts +60 -21
- package/src/compiler/cli-main.ts +469 -0
- package/src/compiler/cli.ts +1905 -16
- package/src/compiler/collections.ts +284 -25
- package/src/compiler/course-config.ts +267 -0
- package/src/compiler/decks.ts +455 -0
- package/src/compiler/exports/deck-fit.ts +127 -0
- package/src/compiler/exports/export-chapters.ts +105 -6
- package/src/compiler/exports/export-pages.ts +46 -2
- package/src/compiler/exports/latex/beamerthemeexplico.sty +124 -0
- package/src/compiler/exports/latex/cache.ts +183 -0
- package/src/compiler/exports/latex/capture.ts +5 -2
- package/src/compiler/exports/latex/compile.ts +18 -0
- package/src/compiler/exports/latex/deck.ts +419 -0
- package/src/compiler/exports/latex/document.ts +117 -21
- package/src/compiler/exports/latex/explico-common.sty +198 -0
- package/src/compiler/exports/latex/explico.cls +13 -183
- package/src/compiler/exports/latex/serialize.ts +25 -21
- package/src/compiler/exports/latex/texlive-packages.txt +6 -2
- package/src/compiler/exports/latex-export.ts +187 -45
- package/src/compiler/exports/markdown/serialize.ts +118 -9
- package/src/compiler/exports/markdown-export.ts +56 -11
- package/src/compiler/exports/standalone-html.ts +8 -7
- package/src/compiler/exports/static-tree.ts +175 -3
- package/src/compiler/knowledge-version.ts +56 -21
- package/src/compiler/lesson-derivation.ts +282 -16
- package/src/compiler/manifest.ts +558 -63
- package/src/compiler/page-versions.ts +359 -0
- package/src/compiler/resolution-report.ts +38 -7
- package/src/compiler/validate-content.ts +212 -0
- package/src/components/CourseContents.astro +124 -6
- package/src/components/CurriculumMap.astro +49 -1
- package/src/components/FloatIndex.astro +1 -1
- package/src/components/PartOverview.astro +51 -5
- package/src/components/assessments/AssessmentSet.astro +41 -15
- package/src/components/block-heading.ts +15 -6
- package/src/components/card-carry.ts +7 -4
- package/src/components/card-manager.ts +14 -2
- package/src/components/card-memory.ts +13 -9
- package/src/components/deck/DeckEnhancer.astro +13 -0
- package/src/components/deck/deck-context.ts +104 -0
- package/src/components/deck/deck-controller.ts +1287 -0
- package/src/components/deck/deck-map.ts +91 -0
- package/src/components/deck-lane.ts +163 -0
- package/src/components/diagram/Diagram.astro +131 -86
- package/src/components/disclosure/persisted-disclosure.ts +9 -1
- package/src/components/figure/Figure.astro +7 -2
- package/src/components/figure/FloatCaption.astro +5 -3
- package/src/components/fold/Fold.astro +2 -2
- package/src/components/notation/NotationFormula.astro +81 -11
- package/src/components/notation/NotationGlossary.astro +132 -1
- package/src/components/notation/NotationLayer.astro +15 -7
- package/src/components/starlight/AboutPanel.astro +186 -3
- package/src/components/starlight/CollapsibleSections.astro +24 -3
- package/src/components/starlight/DiagramEnhancer.astro +5 -1
- package/src/components/starlight/HeadingAnchorEnhancer.astro +1 -1
- package/src/components/starlight/LayoutHeader.astro +17 -0
- package/src/components/starlight/LayoutPageTitle.astro +110 -0
- package/src/components/starlight/LayoutSidebar.astro +807 -21
- package/src/components/starlight/LayoutSocialIcons.astro +62 -6
- package/src/components/starlight/LessonExportActions.astro +27 -9
- package/src/components/starlight/LessonFooter.astro +59 -0
- package/src/components/starlight/ReadingMemoryNotice.astro +114 -0
- package/src/components/starlight/TextSizeControl.astro +24 -13
- package/src/components/starlight/page-marks.ts +139 -0
- package/src/components/starlight/section-summary.ts +1 -1
- package/src/components/starlight/sidebar-editions.ts +245 -0
- package/src/components/table/Table.astro +15 -7
- package/src/content-config.ts +87 -1
- package/src/curriculum/competency-graph.ts +95 -0
- package/src/reference/base-library.mjs +19 -0
- package/src/reference/consistency.ts +126 -39
- package/src/reference/deck-remark.mjs +600 -0
- package/src/reference/deck-source.ts +273 -0
- package/src/reference/decks-validate.ts +604 -0
- package/src/reference/diagram-layout.ts +3 -1
- package/src/reference/figure-symbols.ts +108 -31
- package/src/reference/folds-validate.ts +1 -1
- package/src/reference/gate-indices.ts +5 -3
- package/src/reference/gate-math.ts +169 -25
- package/src/reference/lab-katex-validate.ts +30 -15
- package/src/reference/lab-math-scope.ts +34 -3
- package/src/reference/math-glyphs.mjs +189 -25
- package/src/reference/page-notation.ts +69 -15
- package/src/reference/references.ts +28 -0
- package/src/reference/registry-scope.mjs +34 -0
- package/src/reference/registry.ts +41 -11
- package/src/reference/rehype-deck.mjs +286 -0
- package/src/reference/rehype-heading-anchors.mjs +48 -6
- package/src/reference/remark-notation.mjs +249 -40
- package/src/reference/source-lines.mjs +65 -0
- package/src/reference/types.ts +28 -0
- package/src/rules/catalog.ts +257 -4
- package/src/rules/course-rules.ts +21 -1
- package/src/rules/in-effect.ts +54 -8
- package/src/session/PreferenceStore.ts +23 -0
- package/src/session/deck-memory.ts +263 -0
- package/src/session/reading-memory.ts +243 -0
- package/src/styles/deck.css +1150 -0
- package/src/styles/global.css +958 -27
- package/src/styles/zones.css +277 -0
- /package/src/{components → reference}/float-name.ts +0 -0
|
@@ -6,6 +6,7 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import { zip } from "./zip.js";
|
|
7
7
|
import {
|
|
8
8
|
consoleLogger,
|
|
9
|
+
deckBlocks,
|
|
9
10
|
epochOf,
|
|
10
11
|
figuresOf,
|
|
11
12
|
imageFile,
|
|
@@ -14,57 +15,79 @@ import {
|
|
|
14
15
|
readBuiltPage,
|
|
15
16
|
strictMode
|
|
16
17
|
} from "./export-chapters.js";
|
|
18
|
+
import { latexCacheFromEnvironment } from "./latex/cache.js";
|
|
17
19
|
import { compileLatex, missingCharacters, findLatexEngine } from "./latex/compile.js";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
+
import { deckNumberingProblems, deckOverflows, deckSource } from "./latex/deck.js";
|
|
21
|
+
import {
|
|
22
|
+
bookSince,
|
|
23
|
+
bookSource,
|
|
24
|
+
chapterFolderName,
|
|
25
|
+
chapterSource,
|
|
26
|
+
deckCheckItems
|
|
27
|
+
} from "./latex/document.js";
|
|
28
|
+
import { courseFileStem, deckFileStem, deckOwnerSlug, deckSaveName } from "./export-pages.js";
|
|
20
29
|
import { auxLabels, numberingMismatches, websiteNumbers } from "./latex/numbering.js";
|
|
21
|
-
async function pagePictures(outputRoot, base, page,
|
|
30
|
+
async function pagePictures(outputRoot, base, page, captures, drawnOn = () => page.route, folder = `figures/${page.route}`) {
|
|
22
31
|
const pictures = /* @__PURE__ */ new Map();
|
|
23
32
|
for (const figure of figuresOf(page.blocks)) {
|
|
24
|
-
const capturedFigure =
|
|
33
|
+
const capturedFigure = captures.get(drawnOn(figure.anchor))?.get(figure.anchor);
|
|
25
34
|
if (capturedFigure) {
|
|
26
|
-
pictures.set(figure.anchor, {
|
|
27
|
-
path: `figures/${page.route}/${figure.anchor}.pdf`,
|
|
28
|
-
content: capturedFigure.pdf
|
|
29
|
-
});
|
|
35
|
+
pictures.set(figure.anchor, { path: `${folder}/${figure.anchor}.pdf`, content: capturedFigure.pdf });
|
|
30
36
|
continue;
|
|
31
37
|
}
|
|
32
38
|
if (figure.picture.kind !== "image" || needsCapture(figure)) continue;
|
|
33
39
|
const file = imageFile(outputRoot, base, page.route, figure.picture.src);
|
|
34
40
|
pictures.set(figure.anchor, {
|
|
35
|
-
path:
|
|
41
|
+
path: `${folder}/${figure.anchor}${extname(file).toLowerCase()}`,
|
|
36
42
|
content: await readFile(file)
|
|
37
43
|
});
|
|
38
44
|
}
|
|
39
45
|
return pictures;
|
|
40
46
|
}
|
|
41
|
-
|
|
47
|
+
function slidesLesson(manifest, lesson) {
|
|
48
|
+
const byId = new Map(manifest.competencies.map((competency) => [competency.id, competency]));
|
|
49
|
+
const outcomes = lesson.teaches.flatMap((id) => {
|
|
50
|
+
const outcome = byId.get(id)?.outcome;
|
|
51
|
+
return typeof outcome === "string" && outcome.trim() !== "" ? [outcome.trim()] : [];
|
|
52
|
+
});
|
|
53
|
+
const base = manifest.lessonExports.canonicalBaseUrl;
|
|
54
|
+
return { lesson, outcomes, ...base ? { slidesUrl: `${base}${lesson.slug}/` } : {} };
|
|
55
|
+
}
|
|
56
|
+
async function mapWithLimit(items, limit, work, order = items.map((_item, index) => index)) {
|
|
42
57
|
const results = new Array(items.length);
|
|
43
58
|
let next = 0;
|
|
44
59
|
const worker = async () => {
|
|
45
|
-
while (next <
|
|
46
|
-
const index = next
|
|
60
|
+
while (next < order.length) {
|
|
61
|
+
const index = order[next++];
|
|
47
62
|
results[index] = await work(items[index]);
|
|
48
63
|
}
|
|
49
64
|
};
|
|
50
|
-
await Promise.all(Array.from({ length: Math.max(1, Math.min(limit,
|
|
65
|
+
await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, order.length)) }, worker));
|
|
51
66
|
return results;
|
|
52
67
|
}
|
|
68
|
+
function startOrder(jobs) {
|
|
69
|
+
const rank = { course: 0, part: 1, chapter: 2, deck: 2 };
|
|
70
|
+
const size = jobs.map(
|
|
71
|
+
({ files }) => files.reduce((sum, file) => sum + (typeof file.content === "string" ? Buffer.byteLength(file.content) : file.content.byteLength), 0)
|
|
72
|
+
);
|
|
73
|
+
return jobs.map((_job, index) => index).sort((a, b) => rank[jobs[a].kind] - rank[jobs[b].kind] || size[b] - size[a] || a - b);
|
|
74
|
+
}
|
|
53
75
|
async function writeLatexExports(directory, manifest, options = {}) {
|
|
54
76
|
if (!manifest.lessonExports.latex) return;
|
|
55
77
|
const logger = options.logger ?? consoleLogger;
|
|
56
78
|
const strict = strictMode();
|
|
57
79
|
const outputRoot = fileURLToPath(directory);
|
|
58
|
-
const { base, chapters, captures } = options.prepared ?? await prepareChapters(outputRoot, manifest, { logger, projectRoot: options.projectRoot });
|
|
80
|
+
const { base, chapters, decks, captures } = options.prepared ?? await prepareChapters(outputRoot, manifest, { logger, projectRoot: options.projectRoot });
|
|
59
81
|
const engine = await findLatexEngine();
|
|
60
82
|
if ("missing" in engine) {
|
|
61
83
|
if (strict) throw new Error(`LaTeX export cannot build PDFs: ${engine.missing}`);
|
|
62
84
|
logger.warn(`LaTeX export: writing the source zips only. ${engine.missing}`);
|
|
63
85
|
}
|
|
64
86
|
const jobs = [];
|
|
87
|
+
const againstWebsite = (website) => (aux) => numberingMismatches(website, auxLabels(aux));
|
|
65
88
|
const pictures = /* @__PURE__ */ new Map();
|
|
66
89
|
for (const { lesson, page } of chapters) {
|
|
67
|
-
const chapterPictures = await pagePictures(outputRoot, base, page, captures
|
|
90
|
+
const chapterPictures = await pagePictures(outputRoot, base, page, captures);
|
|
68
91
|
pictures.set(lesson.id, chapterPictures);
|
|
69
92
|
jobs.push({
|
|
70
93
|
kind: "chapter",
|
|
@@ -72,9 +95,52 @@ async function writeLatexExports(directory, manifest, options = {}) {
|
|
|
72
95
|
files: chapterSource({ manifest, lesson, page, documentRoutes: /* @__PURE__ */ new Set([page.route]), pictures: chapterPictures }),
|
|
73
96
|
target: lesson.slug,
|
|
74
97
|
folder: chapterFolderName(lesson),
|
|
75
|
-
|
|
98
|
+
numbering: againstWebsite(websiteNumbers(page, `${lesson.part}.${lesson.chapter}`)),
|
|
99
|
+
since: lesson.since
|
|
76
100
|
});
|
|
77
101
|
}
|
|
102
|
+
const lessonsById = new Map(manifest.lessons.map((lesson) => [lesson.id, lesson]));
|
|
103
|
+
for (const { deck, page, figureRoutes } of decks) {
|
|
104
|
+
if (deck.part === void 0 && !lessonsById.has(deck.lessonId ?? "")) continue;
|
|
105
|
+
const owner = deckOwnerSlug(deck, manifest);
|
|
106
|
+
const deckPictures = await pagePictures(
|
|
107
|
+
outputRoot,
|
|
108
|
+
base,
|
|
109
|
+
{ route: page.route, blocks: deckBlocks(page) },
|
|
110
|
+
captures,
|
|
111
|
+
(anchor) => figureRoutes.get(anchor) ?? page.route,
|
|
112
|
+
"figures"
|
|
113
|
+
);
|
|
114
|
+
const { files, frameEnds } = deckSource({ manifest, deck, page, checkItems: deckCheckItems(manifest, deck), pictures: deckPictures });
|
|
115
|
+
const stem = deckFileStem(owner);
|
|
116
|
+
const numbering = (aux) => deckNumberingProblems(aux, page.frames.length);
|
|
117
|
+
const common = { kind: "deck", folder: `${deckSaveName(owner)}-latex`, numbering, since: deck.since };
|
|
118
|
+
jobs.push({
|
|
119
|
+
...common,
|
|
120
|
+
name: stem,
|
|
121
|
+
files,
|
|
122
|
+
target: stem,
|
|
123
|
+
review: (log) => {
|
|
124
|
+
const over = deckOverflows(log, frameEnds);
|
|
125
|
+
if (over.length === 0) return;
|
|
126
|
+
logger.warn(
|
|
127
|
+
`LaTeX export: on paper, ${over.length === 1 ? "a slide" : `${over.length} slides`} of ${stem} run${over.length === 1 ? "s" : ""} over the bottom: ${over.map(({ frame, points }) => `slide ${frame} by ${points} pt`).join(", ")}. The web page fits them (rule D2), but the PDF cuts them off. Shorten the slide, or split it.`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
if (manifest.deck.handoutPdf && manifest.deck.notes !== "off") {
|
|
132
|
+
const handout = files.find((file) => file.path === "handout.tex");
|
|
133
|
+
jobs.push({
|
|
134
|
+
...common,
|
|
135
|
+
name: `${stem}-notes`,
|
|
136
|
+
files: files.flatMap(
|
|
137
|
+
(file) => file.path === "handout.tex" ? [] : file.path === "main.tex" ? [{ path: "main.tex", content: handout.content }] : [file]
|
|
138
|
+
),
|
|
139
|
+
target: `${stem}-notes`,
|
|
140
|
+
noZip: true
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
78
144
|
const byId = new Map(chapters.map((chapter) => [chapter.lesson.id, chapter]));
|
|
79
145
|
const pageSlugs = new Set(manifest.pages.map((page) => page.slug));
|
|
80
146
|
const parts = [];
|
|
@@ -83,9 +149,13 @@ async function writeLatexExports(directory, manifest, options = {}) {
|
|
|
83
149
|
const chapter = byId.get(id);
|
|
84
150
|
return chapter ? [{ ...chapter, pictures: pictures.get(id) }] : [];
|
|
85
151
|
});
|
|
152
|
+
const slides = part.lessons.flatMap((id) => {
|
|
153
|
+
const lesson = lessonsById.get(id);
|
|
154
|
+
return lesson && !lesson.editions.includes("chapter") ? [slidesLesson(manifest, lesson)] : [];
|
|
155
|
+
});
|
|
86
156
|
if (partChapters.length === 0) continue;
|
|
87
157
|
const overview = pageSlugs.has(`${part.dir}/index`) ? (await readBuiltPage(outputRoot, `${part.dir}/index`)).page : void 0;
|
|
88
|
-
parts.push({ part, ...overview ? { overview } : {}, chapters: partChapters });
|
|
158
|
+
parts.push({ part, ...overview ? { overview } : {}, chapters: partChapters, ...slides.length > 0 ? { slides } : {} });
|
|
89
159
|
}
|
|
90
160
|
const bookNumbers = (bookParts) => new Map(
|
|
91
161
|
bookParts.flatMap(
|
|
@@ -106,7 +176,8 @@ async function writeLatexExports(directory, manifest, options = {}) {
|
|
|
106
176
|
}),
|
|
107
177
|
target: `part/${entry.part.dir}`,
|
|
108
178
|
folder: `${entry.part.dir}-latex`,
|
|
109
|
-
|
|
179
|
+
numbering: againstWebsite(bookNumbers([entry])),
|
|
180
|
+
since: bookSince([entry])
|
|
110
181
|
});
|
|
111
182
|
}
|
|
112
183
|
if (parts.length > 0) {
|
|
@@ -116,33 +187,40 @@ async function writeLatexExports(directory, manifest, options = {}) {
|
|
|
116
187
|
files: bookSource({ manifest, parts, document: "course", title: course }),
|
|
117
188
|
target: "course",
|
|
118
189
|
folder: `${courseFileStem(manifest)}-latex`,
|
|
119
|
-
|
|
190
|
+
numbering: againstWebsite(bookNumbers(parts)),
|
|
191
|
+
since: bookSince(parts)
|
|
120
192
|
});
|
|
121
193
|
}
|
|
122
|
-
const pending = jobs.map((job) => ({
|
|
194
|
+
const pending = jobs.filter((job) => !job.noZip).map((job) => ({
|
|
123
195
|
target: join(outputRoot, "downloads", `${job.target}-latex.zip`),
|
|
124
196
|
content: zip(job.files.map((file) => ({ path: `${job.folder}/${file.path}`, content: file.content })))
|
|
125
197
|
}));
|
|
126
198
|
const durations = [];
|
|
199
|
+
const cache = "missing" in engine ? void 0 : await latexCacheFromEnvironment(engine, logger);
|
|
200
|
+
let reused = 0;
|
|
127
201
|
const started = performance.now();
|
|
128
202
|
if (!("missing" in engine)) {
|
|
129
203
|
const compiled = await mapWithLimit(jobs, availableParallelism(), async (job) => {
|
|
130
204
|
const jobStarted = performance.now();
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
const
|
|
205
|
+
const options2 = { epoch: epochOf(manifest, job.since), name: job.name };
|
|
206
|
+
const compile = cache ? await cache.compile(job.files, options2) : { result: await compileLatex(job.files, engine, options2), reused: false };
|
|
207
|
+
const { result } = compile;
|
|
208
|
+
if (compile.reused) reused += 1;
|
|
209
|
+
else durations.push({ kind: job.kind, seconds: (performance.now() - jobStarted) / 1e3 });
|
|
210
|
+
const problems = job.numbering(result.aux);
|
|
134
211
|
if (problems.length > 0) {
|
|
135
212
|
throw new Error(`LaTeX numbers ${job.name} differently from the website:
|
|
136
213
|
${problems.join("\n ")}`);
|
|
137
214
|
}
|
|
138
|
-
|
|
215
|
+
job.review?.(result.log);
|
|
216
|
+
const missing = (job.kind === "chapter" || job.kind === "deck") && !job.noZip ? missingCharacters(result.log) : [];
|
|
139
217
|
if (missing.length > 0) {
|
|
140
218
|
logger.warn(
|
|
141
219
|
`LaTeX export: the PDF of ${job.name} leaves ${missing.length} character(s) blank, which its fonts cannot draw: ${missing.slice(0, 12).join(", ")}${missing.length > 12 ? ", \u2026" : ""}. The web page shows them, the PDF does not. Under LuaLaTeX, install a font that covers them on the build machine (explico.cls looks for CMU Serif, Noto Serif or DejaVu Serif for Greek, Noto CJK or Fandol for Chinese, Japanese and Korean, and Noto Color Emoji for emoji); pdfLaTeX sets none of them. Or write the word in Latin script, or a symbol as math ($\\alpha$).`
|
|
142
220
|
);
|
|
143
221
|
}
|
|
144
222
|
return result.pdf;
|
|
145
|
-
});
|
|
223
|
+
}, startOrder(jobs));
|
|
146
224
|
jobs.forEach((job, index) => {
|
|
147
225
|
pending.push({ target: join(outputRoot, "downloads", `${job.target}.pdf`), content: compiled[index] });
|
|
148
226
|
});
|
|
@@ -158,12 +236,15 @@ async function writeLatexExports(directory, manifest, options = {}) {
|
|
|
158
236
|
} finally {
|
|
159
237
|
await Promise.all(temporary.map((file) => rm(file.temporary, { force: true })));
|
|
160
238
|
}
|
|
239
|
+
const pruned = cache ? await cache.prune() : 0;
|
|
161
240
|
const pdfs = pending.filter((file) => file.target.endsWith(".pdf")).length;
|
|
162
241
|
const seconds = (kind) => durations.filter((entry) => entry.kind === kind).reduce((sum, entry) => sum + entry.seconds, 0);
|
|
163
242
|
logger.info(
|
|
164
|
-
`LaTeX export: ${chapters.length} chapter(s), ${parts.length} part(s)${parts.length > 0 ? " and the course" : ""}: ${jobs.length} source zip(s)` + (pdfs > 0 ? `, ${pdfs} PDF(s) with ${"name" in engine ? engine.name : ""} in ${wall.toFixed(1)} s on up to ${availableParallelism()} core(s) (compile time: chapters ${seconds("chapter").toFixed(1)} s, parts ${seconds("part").toFixed(1)} s, course ${seconds("course").toFixed(1)} s)` : "") + (captures.size > 0 ? `, figures from ${captures.size} page(s) drawn as vector PDF` : "") + "."
|
|
243
|
+
`LaTeX export: ${chapters.length} chapter(s), ${decks.length} deck(s), ${parts.length} part(s)${parts.length > 0 ? " and the course" : ""}: ${jobs.filter((job) => !job.noZip).length} source zip(s)` + (pdfs > 0 ? `, ${pdfs} PDF(s) with ${"name" in engine ? engine.name : ""} in ${wall.toFixed(1)} s on up to ${availableParallelism()} core(s) (` + (cache ? `${pdfs - reused} compiled, ${reused} reused from ${cache.directory}${pruned > 0 ? `, ${pruned} unused entr${pruned === 1 ? "y" : "ies"} deleted` : ""}; ` : "") + `compile time: chapters ${seconds("chapter").toFixed(1)} s, decks ${seconds("deck").toFixed(1)} s, parts ${seconds("part").toFixed(1)} s, course ${seconds("course").toFixed(1)} s)` : "") + (captures.size > 0 ? `, figures from ${captures.size} page(s) drawn as vector PDF` : "") + "."
|
|
165
244
|
);
|
|
166
245
|
}
|
|
167
246
|
export {
|
|
247
|
+
mapWithLimit,
|
|
248
|
+
startOrder,
|
|
168
249
|
writeLatexExports
|
|
169
250
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RootContent } from 'mdast';
|
|
2
2
|
import { CheckItem } from '../latex/serialize.js';
|
|
3
|
-
import { StaticPage, DiagramGeometry } from '../static-tree.js';
|
|
3
|
+
import { StaticPage, StaticDeck, NotationEntry, DiagramGeometry } from '../static-tree.js';
|
|
4
4
|
import '../../../reference/mixed-content.js';
|
|
5
5
|
import '../../../reference/gate-math.js';
|
|
6
6
|
import '../../../reference/types.js';
|
|
@@ -32,10 +32,13 @@ interface MarkdownChapterContext {
|
|
|
32
32
|
readonly pictures: ReadonlyMap<string, string>;
|
|
33
33
|
/** Answers of this page's checks, by assessment id, in authored order. */
|
|
34
34
|
readonly checkItems: ReadonlyMap<string, readonly CheckItem[]>;
|
|
35
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* The line under the title: the course, the version the page's content is
|
|
37
|
+
* at (`versionPhrase`, rule C5) and its live URL.
|
|
38
|
+
*/
|
|
36
39
|
readonly source?: {
|
|
37
40
|
readonly course: string;
|
|
38
|
-
readonly
|
|
41
|
+
readonly since?: string;
|
|
39
42
|
readonly url?: string;
|
|
40
43
|
};
|
|
41
44
|
}
|
|
@@ -45,7 +48,24 @@ declare function diagramEdges(geometry: DiagramGeometry): [number, number][];
|
|
|
45
48
|
declare function serializeMarkdownPage(context: MarkdownChapterContext, options?: {
|
|
46
49
|
readonly number?: string;
|
|
47
50
|
}): string;
|
|
51
|
+
/** The slide sizes Marp's built-in themes have. */
|
|
52
|
+
type MarpSize = '16:9' | '4:3';
|
|
53
|
+
interface MarpDeckContext extends Omit<MarkdownChapterContext, 'page'> {
|
|
54
|
+
readonly deck: StaticDeck;
|
|
55
|
+
/** The symbols its frames use, by first use: its closing notation slide. */
|
|
56
|
+
readonly symbols: readonly NotationEntry[];
|
|
57
|
+
readonly size: MarpSize;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A deck in Marp's Markdown (decision D10): front matter that turns Marp on,
|
|
61
|
+
* one slide per frame between `---` lines, numbered as the deck is, each
|
|
62
|
+
* frame's notes and its check's answers as an HTML comment (Marp's speaker
|
|
63
|
+
* notes), and a closing slide of the symbols the frames use. A step shows
|
|
64
|
+
* its frame's final state, as the handout does. The file reads as plain
|
|
65
|
+
* Markdown too; Marp, its VS Code extension and Deckset present it.
|
|
66
|
+
*/
|
|
67
|
+
declare function serializeMarpDeck(context: MarpDeckContext): string;
|
|
48
68
|
/** Markdown of a few blocks of mdast, written the same way (for a download's `index.md`). */
|
|
49
69
|
declare function serializeMarkdownNodes(children: RootContent[]): string;
|
|
50
70
|
|
|
51
|
-
export { type MarkdownChapterContext, diagramEdges, serializeMarkdownNodes, serializeMarkdownPage };
|
|
71
|
+
export { type MarkdownChapterContext, type MarpDeckContext, type MarpSize, diagramEdges, serializeMarkdownNodes, serializeMarkdownPage, serializeMarpDeck };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { gfmTableToMarkdown } from "mdast-util-gfm-table";
|
|
2
2
|
import { mathToMarkdown } from "mdast-util-math";
|
|
3
3
|
import { toMarkdown } from "mdast-util-to-markdown";
|
|
4
|
-
import { captionTitle } from "../../../
|
|
4
|
+
import { captionTitle } from "../../../reference/float-name.js";
|
|
5
5
|
import { stripNotationMarkers } from "../../../reference/math-glyphs.mjs";
|
|
6
6
|
const text = (value) => ({ type: "text", value });
|
|
7
7
|
const html = (value) => ({ type: "html", value });
|
|
@@ -428,16 +428,19 @@ class MarkdownWriter {
|
|
|
428
428
|
}
|
|
429
429
|
];
|
|
430
430
|
}
|
|
431
|
-
|
|
431
|
+
/**
|
|
432
|
+
* The answers to `checks`: under their own heading at a page's end, or
|
|
433
|
+
* bare (`inline`), as a deck's speaker notes give them.
|
|
434
|
+
*/
|
|
435
|
+
answers(checks, inline = false) {
|
|
432
436
|
if (checks.length === 0) return [];
|
|
433
437
|
return [
|
|
434
|
-
{ type: "heading", depth: 2, children: [anchor("answers"), text("Answers to the knowledge checks")] },
|
|
438
|
+
...inline ? [] : [{ type: "heading", depth: 2, children: [anchor("answers"), text("Answers to the knowledge checks")] }],
|
|
435
439
|
...checks.flatMap((check) => {
|
|
436
440
|
const items = this.context.checkItems.get(check.assessmentId) ?? [];
|
|
441
|
+
const name = text(`Knowledge check ${check.number}`);
|
|
437
442
|
return [
|
|
438
|
-
paragraph([
|
|
439
|
-
strong([link(`#${check.anchor}`, [text(`Knowledge check ${check.number}`)]), text(": "), ...this.inlines(check.title)])
|
|
440
|
-
]),
|
|
443
|
+
paragraph([strong([inline ? name : link(`#${check.anchor}`, [name]), text(": "), ...this.inlines(check.title)])]),
|
|
441
444
|
{
|
|
442
445
|
type: "list",
|
|
443
446
|
ordered: true,
|
|
@@ -501,7 +504,7 @@ function serializeMarkdownPage(context, options = {}) {
|
|
|
501
504
|
const sourceLine = source ? [
|
|
502
505
|
text("From "),
|
|
503
506
|
emphasis([text(source.course)]),
|
|
504
|
-
...source.
|
|
507
|
+
...source.since ? [text(`, ${source.since}`)] : [],
|
|
505
508
|
text("."),
|
|
506
509
|
...source.url ? [text(" Interactive version: "), link(source.url, [text(source.url)]), text(".")] : []
|
|
507
510
|
] : [];
|
|
@@ -517,11 +520,85 @@ function serializeMarkdownPage(context, options = {}) {
|
|
|
517
520
|
...writer.answers(checksOf(page.blocks))
|
|
518
521
|
]);
|
|
519
522
|
}
|
|
523
|
+
function serializeMarpDeck(context) {
|
|
524
|
+
const { deck, source } = context;
|
|
525
|
+
const page = {
|
|
526
|
+
slug: deck.slug,
|
|
527
|
+
route: deck.route,
|
|
528
|
+
title: deck.title,
|
|
529
|
+
blocks: [],
|
|
530
|
+
notation: deck.notation,
|
|
531
|
+
notationRemarks: []
|
|
532
|
+
};
|
|
533
|
+
const writer = new MarkdownWriter({ ...context, page });
|
|
534
|
+
const heading = (depth, children) => ({ type: "heading", depth, children });
|
|
535
|
+
const lead = { type: "html", value: "<!-- _class: lead -->" };
|
|
536
|
+
const notes = (blocks) => blocks.length === 0 ? [] : [{ type: "html", value: `<!--
|
|
537
|
+
${serializeNodes(blocks).trim().replace(/--/gu, "- -")}
|
|
538
|
+
-->` }];
|
|
539
|
+
const slides = deck.frames.map((frame) => {
|
|
540
|
+
const title = writer.inlines(frame.title);
|
|
541
|
+
if (frame.kind === "title") {
|
|
542
|
+
return [
|
|
543
|
+
lead,
|
|
544
|
+
heading(1, title),
|
|
545
|
+
...source ? [
|
|
546
|
+
paragraph([
|
|
547
|
+
emphasis([text(source.course)]),
|
|
548
|
+
...source.since ? [text(`, ${source.since}`)] : [],
|
|
549
|
+
text("."),
|
|
550
|
+
...source.url ? [text(" Interactive version: "), link(source.url, [text(source.url)]), text(".")] : []
|
|
551
|
+
])
|
|
552
|
+
] : []
|
|
553
|
+
];
|
|
554
|
+
}
|
|
555
|
+
if (frame.kind === "section") return [lead, heading(1, title)];
|
|
556
|
+
const blocks = frame.segments.flatMap((segment) => segment.blocks);
|
|
557
|
+
const legend = frame.legend.filter((item) => item.tex);
|
|
558
|
+
return [
|
|
559
|
+
heading(2, title),
|
|
560
|
+
...writer.blocks(blocks),
|
|
561
|
+
...legend.length > 0 ? [
|
|
562
|
+
paragraph([
|
|
563
|
+
emphasis(
|
|
564
|
+
legend.flatMap((item, index) => [
|
|
565
|
+
...index > 0 ? [text(" \xB7 ")] : [],
|
|
566
|
+
{ type: "inlineMath", value: plainTex(item.tex) },
|
|
567
|
+
text(" "),
|
|
568
|
+
...writer.inlines(item.label)
|
|
569
|
+
])
|
|
570
|
+
)
|
|
571
|
+
])
|
|
572
|
+
] : [],
|
|
573
|
+
...notes([...writer.blocks(frame.notes), ...writer.answers(checksOf(blocks), true)])
|
|
574
|
+
];
|
|
575
|
+
});
|
|
576
|
+
if (context.symbols.length > 0) {
|
|
577
|
+
slides.push([heading(2, [text("Notation")]), ...writer.notation(context.symbols, []).slice(1)]);
|
|
578
|
+
}
|
|
579
|
+
const body = serializeNodes(
|
|
580
|
+
slides.flatMap((slide, index) => [...index > 0 ? [{ type: "thematicBreak" }] : [], ...slide])
|
|
581
|
+
);
|
|
582
|
+
const frontMatter = [
|
|
583
|
+
"---",
|
|
584
|
+
"marp: true",
|
|
585
|
+
`title: ${JSON.stringify(deck.title)}`,
|
|
586
|
+
`size: ${context.size}`,
|
|
587
|
+
"paginate: true",
|
|
588
|
+
"math: katex",
|
|
589
|
+
"---",
|
|
590
|
+
""
|
|
591
|
+
].join("\n");
|
|
592
|
+
const markdown = body.replace(/<a id="[^"]*"><\/a> ?/gu, "").replace(/(?<!!)\[([^\]]*)\]\(#[^)]*\)/gu, "$1").replace(/\n{3,}/gu, "\n\n");
|
|
593
|
+
return `${frontMatter}
|
|
594
|
+
${markdown}`;
|
|
595
|
+
}
|
|
520
596
|
function serializeMarkdownNodes(children) {
|
|
521
597
|
return serializeNodes(children);
|
|
522
598
|
}
|
|
523
599
|
export {
|
|
524
600
|
diagramEdges,
|
|
525
601
|
serializeMarkdownNodes,
|
|
526
|
-
serializeMarkdownPage
|
|
602
|
+
serializeMarkdownPage,
|
|
603
|
+
serializeMarpDeck
|
|
527
604
|
};
|
|
@@ -2,7 +2,7 @@ import { Manifest } from '../manifest.js';
|
|
|
2
2
|
import { ExportLogger, PreparedChapters } from './export-chapters.js';
|
|
3
3
|
import '../../curriculum/validation.js';
|
|
4
4
|
import '../../reference/consistency.js';
|
|
5
|
-
import '../../course-config-
|
|
5
|
+
import '../../course-config-BLtIdkei.js';
|
|
6
6
|
import '../../rules/catalog.js';
|
|
7
7
|
import '../../rules/course-rules.js';
|
|
8
8
|
import '../collections.js';
|
|
@@ -24,11 +24,16 @@ import '../../reference/mixed-content.js';
|
|
|
24
24
|
import '../../reference/gloss.js';
|
|
25
25
|
import '../../rules/waivers.js';
|
|
26
26
|
import '../knowledge-version.js';
|
|
27
|
+
import '../page-versions.js';
|
|
28
|
+
import '../decks.js';
|
|
29
|
+
import '../../reference/decks-validate.js';
|
|
30
|
+
import '../../reference/deck-source.js';
|
|
27
31
|
import '../lesson-derivation.js';
|
|
28
32
|
import './static-tree.js';
|
|
29
33
|
import './html-tree.js';
|
|
30
34
|
import 'parse5';
|
|
31
35
|
import './latex/capture.js';
|
|
36
|
+
import 'node:http';
|
|
32
37
|
|
|
33
38
|
declare function writeMarkdownExports(directory: URL, manifest: Manifest, options?: {
|
|
34
39
|
readonly logger?: ExportLogger;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { basename, dirname, extname, join } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import {
|
|
4
|
+
import { newestSince, versionPhrase } from "../page-versions.js";
|
|
5
|
+
import { courseFileStem, deckFileStem, deckOwnerSlug, deckSaveName } from "./export-pages.js";
|
|
5
6
|
import {
|
|
6
7
|
consoleLogger,
|
|
8
|
+
deckBlocks,
|
|
7
9
|
diagramsOf,
|
|
8
10
|
figuresOf,
|
|
9
11
|
imageExtension,
|
|
@@ -13,8 +15,9 @@ import {
|
|
|
13
15
|
prepareChapters,
|
|
14
16
|
readBuiltPage
|
|
15
17
|
} from "./export-chapters.js";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
+
import { deckSymbols } from "./latex/deck.js";
|
|
19
|
+
import { checkItems, deckCheckItems } from "./latex/document.js";
|
|
20
|
+
import { serializeMarkdownNodes, serializeMarkdownPage, serializeMarpDeck } from "./markdown/serialize.js";
|
|
18
21
|
import { zip } from "./zip.js";
|
|
19
22
|
async function pagePictures(outputRoot, base, page, captured, assetDir) {
|
|
20
23
|
const pictures = /* @__PURE__ */ new Map();
|
|
@@ -57,15 +60,19 @@ async function writeMarkdownExports(directory, manifest, options = {}) {
|
|
|
57
60
|
if (!manifest.lessonExports.markdown) return;
|
|
58
61
|
const logger = options.logger ?? consoleLogger;
|
|
59
62
|
const outputRoot = fileURLToPath(directory);
|
|
60
|
-
const { base, chapters, captures } = options.prepared ?? await prepareChapters(outputRoot, manifest, { logger, projectRoot: options.projectRoot });
|
|
63
|
+
const { base, chapters, decks, captures } = options.prepared ?? await prepareChapters(outputRoot, manifest, { logger, projectRoot: options.projectRoot });
|
|
61
64
|
const canonicalBaseUrl = manifest.lessonExports.canonicalBaseUrl;
|
|
62
65
|
const course = manifest.about.material.name;
|
|
63
|
-
const
|
|
64
|
-
const source = (route) => ({
|
|
66
|
+
const current = manifest.about.knowledge.version;
|
|
67
|
+
const source = (route, since) => ({
|
|
65
68
|
course,
|
|
66
|
-
|
|
69
|
+
since: versionPhrase(since, current),
|
|
67
70
|
...canonicalBaseUrl ? { url: `${canonicalBaseUrl}${route ? `${route}/` : ""}` } : {}
|
|
68
71
|
});
|
|
72
|
+
const partSince = (entry) => newestSince([
|
|
73
|
+
...entry.part.overview ? [entry.part.overview.since] : [],
|
|
74
|
+
...entry.chapters.map(({ lesson }) => lesson.since)
|
|
75
|
+
]);
|
|
69
76
|
const chapterFiles = async (chapter, file, assetDir, documentFiles) => {
|
|
70
77
|
const { lesson, page } = chapter;
|
|
71
78
|
const pictures = await pagePictures(outputRoot, base, page, captures.get(page.route), assetDir);
|
|
@@ -76,7 +83,7 @@ async function writeMarkdownExports(directory, manifest, options = {}) {
|
|
|
76
83
|
...canonicalBaseUrl ? { canonicalBaseUrl } : {},
|
|
77
84
|
pictures: new Map([...pictures].map(([key, picture]) => [key, picture.path])),
|
|
78
85
|
checkItems: checkItems(manifest, lesson),
|
|
79
|
-
source: source(page.route)
|
|
86
|
+
source: source(page.route, lesson.since)
|
|
80
87
|
},
|
|
81
88
|
{ number: `${lesson.part}.${lesson.chapter}` }
|
|
82
89
|
);
|
|
@@ -101,6 +108,38 @@ async function writeMarkdownExports(directory, manifest, options = {}) {
|
|
|
101
108
|
await chapterFiles(chapter, "lesson.md", "assets", /* @__PURE__ */ new Map([[chapter.page.route, "lesson.md"]]))
|
|
102
109
|
);
|
|
103
110
|
}
|
|
111
|
+
for (const { deck, page, figureRoutes } of decks) {
|
|
112
|
+
if (deck.part === void 0 && !manifest.lessons.some((entry) => entry.id === deck.lessonId)) continue;
|
|
113
|
+
const owner = deckOwnerSlug(deck, manifest);
|
|
114
|
+
const captured = /* @__PURE__ */ new Map();
|
|
115
|
+
for (const [anchor, route] of figureRoutes) {
|
|
116
|
+
const figure = captures.get(route)?.get(anchor);
|
|
117
|
+
if (figure) captured.set(anchor, figure);
|
|
118
|
+
}
|
|
119
|
+
const blocks = [...deckBlocks(page), ...page.frames.flatMap((frame) => frame.notes)];
|
|
120
|
+
const pictures = await pagePictures(
|
|
121
|
+
outputRoot,
|
|
122
|
+
base,
|
|
123
|
+
{ slug: page.slug, route: page.route, title: page.title, blocks, notation: [], notationRemarks: [] },
|
|
124
|
+
captured,
|
|
125
|
+
"assets"
|
|
126
|
+
);
|
|
127
|
+
const items = deckCheckItems(manifest, deck);
|
|
128
|
+
const markdown = serializeMarpDeck({
|
|
129
|
+
deck: page,
|
|
130
|
+
documentFiles: /* @__PURE__ */ new Map([[page.route, ""]]),
|
|
131
|
+
...canonicalBaseUrl ? { canonicalBaseUrl } : {},
|
|
132
|
+
pictures: new Map([...pictures].map(([key, picture]) => [key, picture.path])),
|
|
133
|
+
checkItems: items,
|
|
134
|
+
source: source(page.route, deck.since),
|
|
135
|
+
symbols: deckSymbols({ deck: page, documentRoutes: /* @__PURE__ */ new Set([page.route]), pictures: /* @__PURE__ */ new Map(), checkItems: items }),
|
|
136
|
+
size: manifest.deck.aspectRatio === "4:3" ? "4:3" : "16:9"
|
|
137
|
+
});
|
|
138
|
+
pack(`${deckFileStem(owner)}-markdown.zip`, `${deckSaveName(owner)}-markdown`, [
|
|
139
|
+
{ path: "slides.md", content: markdown },
|
|
140
|
+
...[...pictures.values()].map((picture) => ({ path: picture.path, content: picture.content }))
|
|
141
|
+
]);
|
|
142
|
+
}
|
|
104
143
|
const byId = new Map(chapters.map((chapter) => [chapter.lesson.id, chapter]));
|
|
105
144
|
const pageSlugs = new Set(manifest.pages.map((page) => page.slug));
|
|
106
145
|
const parts = manifest.parts.map((part) => ({
|
|
@@ -121,7 +160,7 @@ async function writeMarkdownExports(directory, manifest, options = {}) {
|
|
|
121
160
|
...canonicalBaseUrl ? { canonicalBaseUrl } : {},
|
|
122
161
|
pictures: new Map([...pictures].map(([key, picture]) => [key, picture.path])),
|
|
123
162
|
checkItems: /* @__PURE__ */ new Map(),
|
|
124
|
-
source: source(page.route)
|
|
163
|
+
source: source(page.route, partSince(entry))
|
|
125
164
|
},
|
|
126
165
|
{ number: `Part ${entry.part.ordinal}:` }
|
|
127
166
|
);
|
|
@@ -186,7 +225,7 @@ async function writeMarkdownExports(directory, manifest, options = {}) {
|
|
|
186
225
|
{
|
|
187
226
|
type: "paragraph",
|
|
188
227
|
children: [
|
|
189
|
-
{ type: "text", value: `Content
|
|
228
|
+
{ type: "text", value: `Content ${versionPhrase(newestSince(parts.map(partSince)), current)}.` },
|
|
190
229
|
...canonicalBaseUrl ? [
|
|
191
230
|
{ type: "text", value: " Interactive version: " },
|
|
192
231
|
{ type: "link", url: canonicalBaseUrl, children: [{ type: "text", value: canonicalBaseUrl }] },
|
|
@@ -232,7 +271,7 @@ async function writeMarkdownExports(directory, manifest, options = {}) {
|
|
|
232
271
|
await Promise.all(pending.map((file) => rm(`${file.target}.tmp-${process.pid}`, { force: true })));
|
|
233
272
|
}
|
|
234
273
|
logger.info(
|
|
235
|
-
`Markdown export: ${chapters.length} chapter(s), ${parts.length} part(s)` + (parts.length > 0 ? " and the course" : "") + "."
|
|
274
|
+
`Markdown export: ${chapters.length} chapter(s), ${decks.length} deck(s), ${parts.length} part(s)` + (parts.length > 0 ? " and the course" : "") + "."
|
|
236
275
|
);
|
|
237
276
|
}
|
|
238
277
|
export {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Manifest } from '../manifest.js';
|
|
2
2
|
import '../../curriculum/validation.js';
|
|
3
3
|
import '../../reference/consistency.js';
|
|
4
|
-
import '../../course-config-
|
|
4
|
+
import '../../course-config-BLtIdkei.js';
|
|
5
5
|
import '../../rules/catalog.js';
|
|
6
6
|
import '../../rules/course-rules.js';
|
|
7
7
|
import '../collections.js';
|
|
@@ -23,6 +23,10 @@ import '../../reference/mixed-content.js';
|
|
|
23
23
|
import '../../reference/gloss.js';
|
|
24
24
|
import '../../rules/waivers.js';
|
|
25
25
|
import '../knowledge-version.js';
|
|
26
|
+
import '../page-versions.js';
|
|
27
|
+
import '../decks.js';
|
|
28
|
+
import '../../reference/decks-validate.js';
|
|
29
|
+
import '../../reference/deck-source.js';
|
|
26
30
|
import '../lesson-derivation.js';
|
|
27
31
|
|
|
28
32
|
/** Inputs for producing one standalone page from Astro's final HTML. */
|
|
@@ -39,10 +43,11 @@ declare function buildStandaloneLessonHtml(input: StandaloneLessonInput): Promis
|
|
|
39
43
|
/** Astro's built HTML file of a docs slug. */
|
|
40
44
|
declare function pageSourceFile(outputRoot: string, slug: string): Promise<string>;
|
|
41
45
|
/**
|
|
42
|
-
* Emit the opt-in standalone HTML artifact of every chapter
|
|
43
|
-
* (`
|
|
44
|
-
* `downloads/<docs slug>.html`. A page
|
|
45
|
-
* part's and the course's files
|
|
46
|
+
* Emit the opt-in standalone HTML artifact of every chapter and deck
|
|
47
|
+
* (`standaloneDownloads`) from Astro's completed static output, at
|
|
48
|
+
* `downloads/<docs slug>.html` and `downloads/<lesson>-slides.html`. A page
|
|
49
|
+
* that is not a chapter is in its part's and the course's files
|
|
50
|
+
* (`combined-html.ts`).
|
|
46
51
|
*/
|
|
47
52
|
declare function writeStandaloneLessonExports(directory: URL, manifest: Manifest): Promise<void>;
|
|
48
53
|
|
|
@@ -10,7 +10,7 @@ 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
12
|
import { parse, serialize } from "parse5";
|
|
13
|
-
import { exportPageRoute,
|
|
13
|
+
import { exportPageRoute, standaloneDownloads } from "./export-pages.js";
|
|
14
14
|
import {
|
|
15
15
|
attribute,
|
|
16
16
|
elements,
|
|
@@ -760,9 +760,9 @@ async function writeStandaloneLessonExports(directory, manifest) {
|
|
|
760
760
|
if (!manifest.lessonExports.standaloneHtml) return;
|
|
761
761
|
const outputRoot = fileURLToPath(directory);
|
|
762
762
|
const pending = [];
|
|
763
|
-
for (const slug of
|
|
763
|
+
for (const { slug, download } of standaloneDownloads(manifest)) {
|
|
764
764
|
const sourceFile = await pageSourceFile(outputRoot, slug);
|
|
765
|
-
const target = join(outputRoot, "downloads", `${
|
|
765
|
+
const target = join(outputRoot, "downloads", `${download}.html`);
|
|
766
766
|
pending.push({
|
|
767
767
|
target,
|
|
768
768
|
temporary: `${target}.tmp-${process.pid}`,
|