explico 0.3.0 → 0.5.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 +344 -1
- package/README.md +117 -3
- package/dist/compiler/astro.d.ts +38 -0
- package/dist/compiler/astro.js +258 -0
- package/dist/compiler/cli.d.ts +13 -0
- package/dist/compiler/cli.js +32 -12
- package/dist/compiler/collections.d.ts +3 -2
- package/dist/compiler/collections.js +28 -1
- package/dist/compiler/course-config.d.ts +86 -4
- package/dist/compiler/course-config.js +39 -0
- package/dist/compiler/exports/standalone-html.d.ts +34 -0
- package/dist/compiler/exports/standalone-html.js +803 -0
- package/dist/compiler/lesson-derivation.d.ts +1 -1
- package/dist/compiler/lesson-derivation.js +2 -2
- package/dist/compiler/manifest.d.ts +112 -7
- package/dist/compiler/manifest.js +205 -40
- package/dist/compiler/resolution-report.d.ts +4 -0
- package/dist/reference/base-abbreviations.d.mts +30 -0
- package/dist/reference/base-abbreviations.mjs +60 -0
- package/dist/reference/base-operators.d.mts +15 -0
- package/dist/reference/base-operators.mjs +40 -0
- package/dist/reference/base-roman-labels.d.mts +35 -0
- package/dist/reference/base-roman-labels.mjs +43 -0
- package/dist/reference/consistency.d.ts +8 -2
- package/dist/reference/consistency.js +50 -3
- package/dist/reference/diagrams-validate.d.ts +21 -0
- package/dist/reference/diagrams-validate.js +13 -0
- package/dist/reference/equations-validate.d.ts +8 -3
- package/dist/reference/equations-validate.js +1 -1
- package/dist/reference/equations.d.mts +66 -35
- package/dist/reference/equations.mjs +142 -40
- package/dist/reference/gate-abbreviations.d.ts +17 -0
- package/dist/reference/gate-abbreviations.js +64 -0
- package/dist/reference/gate-math.d.ts +28 -5
- package/dist/reference/gate-math.js +5 -2
- package/dist/reference/gate-prose-notation.d.ts +60 -0
- package/dist/reference/gate-prose-notation.js +125 -0
- package/dist/reference/keyed-float-validate.d.ts +14 -8
- package/dist/reference/keyed-float-validate.js +1 -1
- package/dist/reference/keyed-jsx-floats.d.mts +24 -21
- package/dist/reference/keyed-jsx-floats.mjs +90 -41
- package/dist/reference/knowledge-checks-validate.d.ts +35 -0
- package/dist/reference/knowledge-checks-validate.js +78 -0
- package/dist/reference/knowledge-checks.d.mts +25 -0
- package/dist/reference/knowledge-checks.mjs +45 -0
- package/dist/reference/lab-math-scope.d.ts +9 -1
- package/dist/reference/lab-math-scope.js +32 -4
- package/dist/reference/math-glyphs.d.mts +30 -0
- package/dist/reference/math-glyphs.mjs +367 -0
- package/dist/reference/mdx-attributes.d.mts +13 -0
- package/dist/reference/mdx-attributes.mjs +38 -0
- package/dist/reference/mixed-content.d.ts +17 -0
- package/dist/reference/mixed-content.js +29 -0
- package/dist/reference/page-notation.d.ts +52 -0
- package/dist/reference/page-notation.js +171 -0
- package/dist/reference/raw-floats-validate.d.ts +18 -0
- package/dist/reference/raw-floats-validate.js +53 -0
- package/dist/reference/raw-floats.d.mts +18 -0
- package/dist/reference/raw-floats.mjs +129 -0
- package/dist/reference/rehype-heading-anchors.d.mts +1 -1
- package/dist/reference/rehype-heading-anchors.mjs +107 -1
- package/dist/reference/rehype-table-header-labels.d.mts +26 -0
- package/dist/reference/rehype-table-header-labels.mjs +152 -0
- package/dist/reference/remark-abbreviations.d.mts +7 -0
- package/dist/reference/remark-abbreviations.mjs +174 -0
- package/dist/reference/remark-memo.d.mts +16 -0
- package/dist/reference/remark-memo.mjs +339 -0
- package/dist/reference/remark-notation.d.mts +102 -5
- package/dist/reference/remark-notation.mjs +506 -50
- package/dist/reference/remark-short-note.d.mts +4 -0
- package/dist/reference/remark-short-note.mjs +237 -0
- package/dist/reference/types.d.ts +16 -1
- package/dist/reference/units-vocab.js +4 -0
- package/docs/VERSIONING.md +33 -4
- package/docs/authoring/README.md +12 -0
- package/docs/authoring/agents.md +17 -2
- package/docs/authoring/components.md +135 -0
- package/docs/authoring/migrating.md +154 -7
- package/docs/authoring/notation.md +568 -8
- package/docs/authoring/structure.md +251 -0
- package/docs/authoring/visualizations.md +36 -3
- package/package.json +5 -2
- package/src/compiler/astro.ts +332 -0
- package/src/compiler/cli.ts +33 -10
- package/src/compiler/collections.ts +45 -4
- package/src/compiler/course-config.ts +131 -3
- package/src/compiler/exports/standalone-html.ts +963 -0
- package/src/compiler/lesson-derivation.ts +2 -2
- package/src/compiler/manifest.ts +367 -49
- package/src/components/HoverPanel.astro +45 -4
- package/src/components/abbreviation/AbbreviationGlossary.astro +70 -0
- package/src/components/abbreviation/AbbreviationLayer.astro +127 -0
- package/src/components/assessments/AssessmentRunner.tsx +52 -17
- package/src/components/assessments/AssessmentSet.astro +98 -14
- package/src/components/assessments/RenderedContent.tsx +55 -0
- package/src/components/assessments/assessment-randomization.ts +16 -0
- package/src/components/diagram/Diagram.astro +444 -83
- package/src/components/disclosure/hover-preview.ts +202 -0
- package/src/components/disclosure/persisted-disclosure.ts +66 -0
- package/src/components/examples/CompactExamples.astro +2 -0
- package/src/components/figure/Figure.astro +279 -10
- package/src/components/memo/Memo.astro +453 -0
- package/src/components/memo/ShortNote.astro +223 -0
- package/src/components/notation/NotationFormula.astro +18 -1
- package/src/components/notation/NotationLayer.astro +116 -8
- package/src/components/notation/render-notation-math.ts +39 -2
- package/src/components/notation/safe-math-to-react.ts +50 -0
- package/src/components/reference/FloatRefLayer.astro +233 -15
- package/src/components/starlight/CollapsibleSections.astro +461 -0
- package/src/components/starlight/DiagramEnhancer.astro +385 -0
- package/src/components/starlight/HeadingAnchorEnhancer.astro +5 -17
- package/src/components/starlight/KeyedFloatEnhancer.astro +81 -3
- package/src/components/starlight/LayoutHeader.astro +50 -0
- package/src/components/starlight/LayoutSidebar.astro +62 -1
- package/src/components/starlight/LayoutSocialIcons.astro +41 -2
- package/src/components/starlight/LessonExportActions.astro +436 -0
- package/src/components/starlight/LessonFooter.astro +38 -2
- package/src/components/starlight/section-parts.ts +31 -0
- package/src/components/table/Table.astro +296 -9
- package/src/components/table/TableOverflowExpander.astro +489 -0
- package/src/content-config.ts +20 -0
- package/src/reference/base-abbreviations.mjs +60 -0
- package/src/reference/base-operators.mjs +40 -0
- package/src/reference/base-roman-labels.mjs +43 -0
- package/src/reference/consistency.ts +73 -2
- package/src/reference/diagrams-validate.ts +40 -0
- package/src/reference/equations-validate.ts +6 -4
- package/src/reference/equations.mjs +142 -40
- package/src/reference/gate-abbreviations.ts +128 -0
- package/src/reference/gate-math.ts +27 -7
- package/src/reference/gate-prose-notation.ts +224 -0
- package/src/reference/keyed-float-validate.ts +12 -9
- package/src/reference/keyed-jsx-floats.mjs +90 -41
- package/src/reference/knowledge-checks-validate.ts +130 -0
- package/src/reference/knowledge-checks.mjs +45 -0
- package/src/reference/lab-math-scope.ts +45 -3
- package/src/reference/math-glyphs.mjs +367 -0
- package/src/reference/mdx-attributes.mjs +38 -0
- package/src/reference/mixed-content.ts +69 -0
- package/src/reference/page-notation.ts +252 -0
- package/src/reference/raw-floats-validate.ts +103 -0
- package/src/reference/raw-floats.mjs +129 -0
- package/src/reference/rehype-heading-anchors.mjs +107 -1
- package/src/reference/rehype-table-header-labels.mjs +152 -0
- package/src/reference/remark-abbreviations.mjs +174 -0
- package/src/reference/remark-memo.mjs +339 -0
- package/src/reference/remark-notation.mjs +506 -50
- package/src/reference/remark-short-note.mjs +237 -0
- package/src/reference/types.ts +16 -0
- package/src/reference/units-vocab.ts +4 -0
- package/src/styles/global.css +1125 -50
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { mkdtemp, readdir, readFile, rm } from "node:fs/promises";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import {
|
|
7
|
+
compileManifest,
|
|
8
|
+
writeManifest
|
|
9
|
+
} from "./manifest.js";
|
|
10
|
+
import { missingNotationPayloadKeys } from "../reference/page-notation.js";
|
|
11
|
+
import { writeStandaloneLessonExports } from "./exports/standalone-html.js";
|
|
12
|
+
const watchedServers = /* @__PURE__ */ new WeakSet();
|
|
13
|
+
function normalizedBasePath(base) {
|
|
14
|
+
const segment = base.replace(/^\/+|\/+$/gu, "");
|
|
15
|
+
return segment ? `/${segment}/` : "/";
|
|
16
|
+
}
|
|
17
|
+
function matchStandaloneDownload(pathname, base, lessonSlugs) {
|
|
18
|
+
const prefix = `${normalizedBasePath(base)}downloads/`;
|
|
19
|
+
if (!pathname.startsWith(prefix) || !pathname.endsWith(".html")) return void 0;
|
|
20
|
+
const slug = pathname.slice(prefix.length, -".html".length);
|
|
21
|
+
return lessonSlugs.includes(slug) ? slug : void 0;
|
|
22
|
+
}
|
|
23
|
+
async function buildDevelopmentDownloads(config, lessonSlugs) {
|
|
24
|
+
const temporaryRoot = await mkdtemp(join(tmpdir(), "explico-standalone-"));
|
|
25
|
+
const outputRoot = join(temporaryRoot, "dist");
|
|
26
|
+
try {
|
|
27
|
+
const projectRoot = fileURLToPath(config.root);
|
|
28
|
+
const astroEntry = fileURLToPath(import.meta.resolve("astro"));
|
|
29
|
+
const astroCli = resolve(dirname(astroEntry), "..", "bin", "astro.mjs");
|
|
30
|
+
await new Promise((resolveBuild, rejectBuild) => {
|
|
31
|
+
const child = spawn(
|
|
32
|
+
process.execPath,
|
|
33
|
+
[
|
|
34
|
+
astroCli,
|
|
35
|
+
"build",
|
|
36
|
+
"--root",
|
|
37
|
+
projectRoot,
|
|
38
|
+
"--outDir",
|
|
39
|
+
outputRoot,
|
|
40
|
+
"--silent"
|
|
41
|
+
],
|
|
42
|
+
{
|
|
43
|
+
cwd: projectRoot,
|
|
44
|
+
env: { ...process.env, NODE_ENV: "production" },
|
|
45
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
let diagnostic = "";
|
|
49
|
+
const capture = (chunk) => {
|
|
50
|
+
if (diagnostic.length < 16e3) diagnostic += chunk.toString("utf8");
|
|
51
|
+
};
|
|
52
|
+
child.stdout.on("data", capture);
|
|
53
|
+
child.stderr.on("data", capture);
|
|
54
|
+
child.once("error", rejectBuild);
|
|
55
|
+
child.once("close", (code) => {
|
|
56
|
+
if (code === 0) {
|
|
57
|
+
resolveBuild();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
rejectBuild(
|
|
61
|
+
new Error(
|
|
62
|
+
`Astro's standalone development build exited with code ${code}.` + (diagnostic.trim() ? `
|
|
63
|
+
${diagnostic.trim()}` : "")
|
|
64
|
+
)
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
return new Map(
|
|
69
|
+
await Promise.all(
|
|
70
|
+
lessonSlugs.map(async (slug) => [
|
|
71
|
+
slug,
|
|
72
|
+
await readFile(join(outputRoot, "downloads", `${slug}.html`))
|
|
73
|
+
])
|
|
74
|
+
)
|
|
75
|
+
);
|
|
76
|
+
} finally {
|
|
77
|
+
await rm(temporaryRoot, { recursive: true, force: true });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function assertLocalNotationParity(lessonId, authoredKeys, renderedKeys) {
|
|
81
|
+
const authored = [...new Set(authoredKeys)].sort();
|
|
82
|
+
const rendered = [...new Set(renderedKeys)].sort();
|
|
83
|
+
if (authored.length === rendered.length && authored.every((key, index) => key === rendered[index])) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
throw new Error(
|
|
87
|
+
`Stale Explico manifest for ${lessonId}: lesson notation.local has [${authored.join(", ")}], but the page notation bundle has [${rendered.join(", ")}]. Pass courseConfig to loadManifest() and add explicoContentRefresh() to the Astro integrations.`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
function assertNotationPayloadHtml(page, html) {
|
|
91
|
+
const missing = missingNotationPayloadKeys(html);
|
|
92
|
+
if (missing.length === 0) return;
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Explico notation payload invariant failed for rendered page "${page}": ${missing.map((key) => `data-notation-key="${key}"`).join(", ")} ${missing.length === 1 ? "has" : "have"} no matching \`data-notation-entry\` popup definition. A nested Astro component that emits notation must register its formula dependencies; use Diagram, NotationFormula, or labMathScope(..., { locals: Astro.locals, context }).`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
async function validateGeneratedNotationPayloads(directory) {
|
|
98
|
+
const root = fileURLToPath(directory);
|
|
99
|
+
const walk = async (path) => {
|
|
100
|
+
for (const entry of await readdir(path, { withFileTypes: true })) {
|
|
101
|
+
const child = join(path, entry.name);
|
|
102
|
+
if (entry.isDirectory()) {
|
|
103
|
+
await walk(child);
|
|
104
|
+
} else if (entry.isFile() && entry.name.endsWith(".html")) {
|
|
105
|
+
assertNotationPayloadHtml(relative(root, child), await readFile(child, "utf8"));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
await walk(root);
|
|
110
|
+
}
|
|
111
|
+
function replaceInPlace(target, source) {
|
|
112
|
+
if (Array.isArray(target) && Array.isArray(source)) {
|
|
113
|
+
target.splice(0, target.length, ...source);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (!target || !source || typeof target !== "object" || typeof source !== "object") {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const mutable = target;
|
|
120
|
+
const fresh = source;
|
|
121
|
+
for (const key of Object.keys(mutable)) {
|
|
122
|
+
if (!(key in fresh)) delete mutable[key];
|
|
123
|
+
}
|
|
124
|
+
for (const [key, value] of Object.entries(fresh)) {
|
|
125
|
+
const current = mutable[key];
|
|
126
|
+
if (Array.isArray(current) && Array.isArray(value) || current && value && typeof current === "object" && typeof value === "object" && !Array.isArray(current) && !Array.isArray(value)) {
|
|
127
|
+
replaceInPlace(current, value);
|
|
128
|
+
} else {
|
|
129
|
+
mutable[key] = value;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function explicoContentRefresh(courseConfig, manifest) {
|
|
134
|
+
let developmentConfig;
|
|
135
|
+
return {
|
|
136
|
+
name: "explico-content-refresh",
|
|
137
|
+
hooks: {
|
|
138
|
+
"astro:config:setup": ({ command, config }) => {
|
|
139
|
+
if (command === "dev") {
|
|
140
|
+
developmentConfig = { root: config.root, base: config.base };
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
"astro:server:setup": ({ server, logger }) => {
|
|
144
|
+
if (watchedServers.has(server)) return;
|
|
145
|
+
watchedServers.add(server);
|
|
146
|
+
const contentRoot = `${resolve(process.cwd(), "content")}${sep}`;
|
|
147
|
+
let timer;
|
|
148
|
+
let restarting = false;
|
|
149
|
+
let developmentDownloads;
|
|
150
|
+
const developmentRoot = developmentConfig ? fileURLToPath(developmentConfig.root) : process.cwd();
|
|
151
|
+
const generatedDevelopmentRoots = [
|
|
152
|
+
".astro",
|
|
153
|
+
"build",
|
|
154
|
+
"dist",
|
|
155
|
+
"node_modules"
|
|
156
|
+
].map((directory) => `${resolve(developmentRoot, directory)}${sep}`);
|
|
157
|
+
const invalidateDevelopmentDownloads = (path) => {
|
|
158
|
+
const absolute = resolve(path);
|
|
159
|
+
if (generatedDevelopmentRoots.some((root) => absolute.startsWith(root))) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
developmentDownloads = void 0;
|
|
163
|
+
};
|
|
164
|
+
const refresh = (path) => {
|
|
165
|
+
const absolute = resolve(path);
|
|
166
|
+
if (!absolute.startsWith(contentRoot) || restarting) return;
|
|
167
|
+
if (timer) clearTimeout(timer);
|
|
168
|
+
timer = setTimeout(async () => {
|
|
169
|
+
restarting = true;
|
|
170
|
+
try {
|
|
171
|
+
const fresh = await compileManifest(courseConfig);
|
|
172
|
+
writeManifest(fresh);
|
|
173
|
+
replaceInPlace(manifest, fresh);
|
|
174
|
+
await server.restart();
|
|
175
|
+
} catch (error) {
|
|
176
|
+
server.config.logger.error(
|
|
177
|
+
`Explico could not refresh the content manifest: ${error instanceof Error ? error.message : String(error)}`
|
|
178
|
+
);
|
|
179
|
+
} finally {
|
|
180
|
+
restarting = false;
|
|
181
|
+
}
|
|
182
|
+
}, 50);
|
|
183
|
+
};
|
|
184
|
+
server.watcher.on("add", refresh);
|
|
185
|
+
server.watcher.on("change", refresh);
|
|
186
|
+
server.watcher.on("unlink", refresh);
|
|
187
|
+
const configuredDevelopment = developmentConfig;
|
|
188
|
+
if (manifest.lessonExports?.standaloneHtml && configuredDevelopment) {
|
|
189
|
+
server.watcher.on("add", invalidateDevelopmentDownloads);
|
|
190
|
+
server.watcher.on("change", invalidateDevelopmentDownloads);
|
|
191
|
+
server.watcher.on("unlink", invalidateDevelopmentDownloads);
|
|
192
|
+
server.middlewares.use(async (request, response, next) => {
|
|
193
|
+
if (request.method !== "GET" && request.method !== "HEAD") return next();
|
|
194
|
+
let pathname;
|
|
195
|
+
try {
|
|
196
|
+
pathname = decodeURIComponent(
|
|
197
|
+
new URL(request.url ?? "/", "http://localhost").pathname
|
|
198
|
+
);
|
|
199
|
+
} catch {
|
|
200
|
+
return next();
|
|
201
|
+
}
|
|
202
|
+
const slug = matchStandaloneDownload(
|
|
203
|
+
pathname,
|
|
204
|
+
configuredDevelopment.base,
|
|
205
|
+
manifest.lessons.map((lesson) => lesson.slug)
|
|
206
|
+
);
|
|
207
|
+
if (!slug) return next();
|
|
208
|
+
try {
|
|
209
|
+
logger.info(
|
|
210
|
+
developmentDownloads ? `Serving cached standalone lesson ${slug}.html` : "Building standalone lesson downloads for local development\u2026"
|
|
211
|
+
);
|
|
212
|
+
developmentDownloads ??= buildDevelopmentDownloads(
|
|
213
|
+
configuredDevelopment,
|
|
214
|
+
manifest.lessons.map((lesson) => lesson.slug)
|
|
215
|
+
);
|
|
216
|
+
const currentDownloads = developmentDownloads;
|
|
217
|
+
currentDownloads.catch(() => {
|
|
218
|
+
if (developmentDownloads === currentDownloads) {
|
|
219
|
+
developmentDownloads = void 0;
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
const artifact = (await currentDownloads).get(slug);
|
|
223
|
+
if (!artifact) return next();
|
|
224
|
+
const filename = `${slug.split("/").at(-1) ?? "lesson"}.html`;
|
|
225
|
+
response.statusCode = 200;
|
|
226
|
+
response.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
227
|
+
response.setHeader("Content-Length", artifact.byteLength);
|
|
228
|
+
response.setHeader(
|
|
229
|
+
"Content-Disposition",
|
|
230
|
+
`attachment; filename="${filename}"`
|
|
231
|
+
);
|
|
232
|
+
response.setHeader("Cache-Control", "no-store");
|
|
233
|
+
response.end(request.method === "HEAD" ? void 0 : artifact);
|
|
234
|
+
} catch (error) {
|
|
235
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
236
|
+
logger.error(`Could not build standalone lesson downloads: ${message}`);
|
|
237
|
+
response.statusCode = 500;
|
|
238
|
+
response.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
239
|
+
response.end(`Could not build standalone lesson download.
|
|
240
|
+
${message}
|
|
241
|
+
`);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
"astro:build:done": async ({ dir }) => {
|
|
247
|
+
await validateGeneratedNotationPayloads(dir);
|
|
248
|
+
await writeStandaloneLessonExports(dir, manifest);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
export {
|
|
254
|
+
assertLocalNotationParity,
|
|
255
|
+
assertNotationPayloadHtml,
|
|
256
|
+
explicoContentRefresh,
|
|
257
|
+
matchStandaloneDownload
|
|
258
|
+
};
|
package/dist/compiler/cli.d.ts
CHANGED
|
@@ -9,7 +9,11 @@ import '../reference/equations-validate.js';
|
|
|
9
9
|
import '../reference/tables-validate.js';
|
|
10
10
|
import '../reference/keyed-float-validate.js';
|
|
11
11
|
import '../reference/figures-validate.js';
|
|
12
|
+
import '../reference/diagrams-validate.js';
|
|
13
|
+
import '../reference/knowledge-checks-validate.js';
|
|
14
|
+
import '../reference/raw-floats-validate.js';
|
|
12
15
|
import '../reference/gate-math.js';
|
|
16
|
+
import '../reference/mixed-content.js';
|
|
13
17
|
import '../reference/gloss.js';
|
|
14
18
|
import './knowledge-version.js';
|
|
15
19
|
import './lesson-derivation.js';
|
|
@@ -107,6 +111,7 @@ interface StatusReport {
|
|
|
107
111
|
readonly keyedEquations: number;
|
|
108
112
|
readonly keyedTables: number;
|
|
109
113
|
readonly keyedFigures: number;
|
|
114
|
+
readonly keyedDiagrams: number;
|
|
110
115
|
};
|
|
111
116
|
readonly draftDebt: {
|
|
112
117
|
readonly lessons: readonly string[];
|
|
@@ -161,6 +166,14 @@ interface ContextReport {
|
|
|
161
166
|
key: string;
|
|
162
167
|
number: string;
|
|
163
168
|
}[];
|
|
169
|
+
readonly diagrams: readonly {
|
|
170
|
+
key: string;
|
|
171
|
+
number: string;
|
|
172
|
+
}[];
|
|
173
|
+
readonly knowledgeChecks: readonly {
|
|
174
|
+
key: string;
|
|
175
|
+
number: string;
|
|
176
|
+
}[];
|
|
164
177
|
readonly sources: readonly {
|
|
165
178
|
id: string;
|
|
166
179
|
title: string;
|
package/dist/compiler/cli.js
CHANGED
|
@@ -12,7 +12,11 @@ import remarkMdx from "remark-mdx";
|
|
|
12
12
|
import { createNotationKatexOptions } from "../reference/katex-options.mjs";
|
|
13
13
|
import rehypeFailKatexErrors from "../reference/rehype-fail-katex-errors.mjs";
|
|
14
14
|
import remarkCitation from "../reference/remark-citation.mjs";
|
|
15
|
-
import
|
|
15
|
+
import remarkMemo from "../reference/remark-memo.mjs";
|
|
16
|
+
import remarkNotation, {
|
|
17
|
+
createRemarkNotationOptions
|
|
18
|
+
} from "../reference/remark-notation.mjs";
|
|
19
|
+
import remarkShortNote from "../reference/remark-short-note.mjs";
|
|
16
20
|
import { isLessonSlug, lessonIdFromSlug } from "./lesson-derivation.js";
|
|
17
21
|
const REPOSITORY_ROOT = process.cwd();
|
|
18
22
|
const CONTENT_ROOT = join(REPOSITORY_ROOT, "content");
|
|
@@ -84,7 +88,10 @@ function lessonDiagnostics(manifest, lesson) {
|
|
|
84
88
|
}
|
|
85
89
|
for (const diagnostic of [
|
|
86
90
|
...manifest.diagnostics.tables,
|
|
87
|
-
...manifest.diagnostics.figures
|
|
91
|
+
...manifest.diagnostics.figures,
|
|
92
|
+
...manifest.diagnostics.diagrams,
|
|
93
|
+
...manifest.diagnostics.checks,
|
|
94
|
+
...manifest.diagnostics.rawFloats
|
|
88
95
|
]) {
|
|
89
96
|
if (diagnostic.lessonId !== id && diagnostic.file !== file && !mentions(diagnostic.message, id, file)) {
|
|
90
97
|
continue;
|
|
@@ -121,15 +128,18 @@ function processorFor(manifest) {
|
|
|
121
128
|
// shape here that it does there (D7/F-tables-figures parity).
|
|
122
129
|
remarkMdx,
|
|
123
130
|
remarkMath,
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
remarkShortNote,
|
|
132
|
+
remarkMemo,
|
|
133
|
+
// `manifest` carries the number tables *and* the part/offset placement
|
|
134
|
+
// together, so this check numbers a page exactly as the Astro build
|
|
135
|
+
// does. Passing the tables alone silently numbered pages by `##` section
|
|
136
|
+
// here while cross-page references read the part-scheme manifest.
|
|
137
|
+
// No `strict: true` here: `renderLesson()` below is also called
|
|
138
|
+
// directly with a synthetic path/body that has no manifest entry at all
|
|
139
|
+
// (fixture renderability checks — see its own doc comment), which
|
|
140
|
+
// strict mode would reject as "catalogued lesson with no numbering",
|
|
141
|
+
// even though it was never meant to be catalogued in the first place.
|
|
142
|
+
[remarkNotation, createRemarkNotationOptions(manifest)],
|
|
133
143
|
[remarkCitation, { sources: manifest.sources }]
|
|
134
144
|
],
|
|
135
145
|
rehypePlugins: [
|
|
@@ -303,6 +313,9 @@ function contentStatus(manifest) {
|
|
|
303
313
|
...manifest.diagnostics.equations.map((d) => d.severity),
|
|
304
314
|
...manifest.diagnostics.tables.map((d) => d.severity),
|
|
305
315
|
...manifest.diagnostics.figures.map((d) => d.severity),
|
|
316
|
+
...manifest.diagnostics.diagrams.map((d) => d.severity),
|
|
317
|
+
...manifest.diagnostics.checks.map((d) => d.severity),
|
|
318
|
+
...manifest.diagnostics.rawFloats.map((d) => d.severity),
|
|
306
319
|
...manifest.diagnostics.consistency.map(() => "warning"),
|
|
307
320
|
...manifest.diagnostics.knowledge.map((d) => d.severity)
|
|
308
321
|
];
|
|
@@ -318,6 +331,10 @@ function contentStatus(manifest) {
|
|
|
318
331
|
(count, lesson) => count + lesson.figures.length,
|
|
319
332
|
0
|
|
320
333
|
);
|
|
334
|
+
const keyedDiagrams = manifest.lessons.reduce(
|
|
335
|
+
(count, lesson) => count + lesson.diagrams.length,
|
|
336
|
+
0
|
|
337
|
+
);
|
|
321
338
|
const assessments = new Set(
|
|
322
339
|
manifest.lessons.flatMap((lesson) => lesson.assessments)
|
|
323
340
|
).size;
|
|
@@ -332,7 +349,8 @@ function contentStatus(manifest) {
|
|
|
332
349
|
notationDefinitions: manifest.notation.definitions.length,
|
|
333
350
|
keyedEquations,
|
|
334
351
|
keyedTables,
|
|
335
|
-
keyedFigures
|
|
352
|
+
keyedFigures,
|
|
353
|
+
keyedDiagrams
|
|
336
354
|
},
|
|
337
355
|
draftDebt: { lessons: draftLessons, notation: draftNotation },
|
|
338
356
|
orphans,
|
|
@@ -388,6 +406,8 @@ function contentContext(manifest, rawId) {
|
|
|
388
406
|
equations: lesson.equations.map((equation) => ({ ...equation })),
|
|
389
407
|
tables: lesson.tables.map((table) => ({ ...table })),
|
|
390
408
|
figures: lesson.figures.map((figure) => ({ ...figure })),
|
|
409
|
+
diagrams: lesson.diagrams.map((diagram) => ({ ...diagram })),
|
|
410
|
+
knowledgeChecks: lesson.knowledgeChecks.map((check) => ({ ...check })),
|
|
391
411
|
sources: lesson.sources.map((id) => {
|
|
392
412
|
const record = sourceById.get(id);
|
|
393
413
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CurriculumCatalog } from '../curriculum/validation.js';
|
|
2
|
-
import { NotationReferenceInput, NotationRegistryInput } from '../reference/types.js';
|
|
2
|
+
import { NotationReferenceInput, AbbreviationDefinitionInput, NotationRegistryInput } from '../reference/types.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* The single path from content files to typed data.
|
|
@@ -20,6 +20,7 @@ import { NotationReferenceInput, NotationRegistryInput } from '../reference/type
|
|
|
20
20
|
*/
|
|
21
21
|
declare function extractNotationReferences(text: string, file: string, kind: NotationReferenceInput['kind'] | undefined): NotationReferenceInput[];
|
|
22
22
|
declare function loadCurriculumCatalog(): Promise<CurriculumCatalog>;
|
|
23
|
+
declare function loadAbbreviationRegistryInput(): Promise<readonly AbbreviationDefinitionInput[]>;
|
|
23
24
|
declare function loadNotationRegistryInput(): Promise<NotationRegistryInput>;
|
|
24
25
|
/**
|
|
25
26
|
* Notation frontmatter objects for `remark-notation`'s `definitions` option.
|
|
@@ -67,4 +68,4 @@ declare function notationSourceLocatorGaps(): {
|
|
|
67
68
|
total: number;
|
|
68
69
|
};
|
|
69
70
|
|
|
70
|
-
export { type NotationSpec, extractNotationReferences, loadCurriculumCatalog, loadLessonChecks, loadNotationDefinitions, loadNotationRegistryInput, loadNotationSpecs, loadSourceRecords, notationSourceLocatorGaps };
|
|
71
|
+
export { type NotationSpec, extractNotationReferences, loadAbbreviationRegistryInput, loadCurriculumCatalog, loadLessonChecks, loadNotationDefinitions, loadNotationRegistryInput, loadNotationSpecs, loadSourceRecords, notationSourceLocatorGaps };
|
|
@@ -4,6 +4,8 @@ import { parseFrontmatter } from "@astrojs/markdown-remark";
|
|
|
4
4
|
import { isSubstantiveMeaning } from "../reference/prose.js";
|
|
5
5
|
import { parseEquationRef } from "../reference/equations.mjs";
|
|
6
6
|
import { parseKeyedFloatRef } from "../reference/keyed-jsx-floats.mjs";
|
|
7
|
+
import { parseKnowledgeCheckRef } from "../reference/knowledge-checks.mjs";
|
|
8
|
+
import { parseMemoRef } from "../reference/remark-memo.mjs";
|
|
7
9
|
import { resolveLabel } from "../reference/label.js";
|
|
8
10
|
import { resolveGlosses } from "../reference/gloss.js";
|
|
9
11
|
import {
|
|
@@ -137,7 +139,7 @@ function extractNotationReferences(text, file, kind) {
|
|
|
137
139
|
for (const [pattern, referenceKind] of patterns) {
|
|
138
140
|
for (const match of searchable.matchAll(pattern)) {
|
|
139
141
|
const key = (match[1] ?? "").trim();
|
|
140
|
-
if (parseEquationRef(key) || parseKeyedFloatRef(key, { prefix: "tab" }) || parseKeyedFloatRef(key, { prefix: "fig" })) {
|
|
142
|
+
if (parseEquationRef(key) || parseKeyedFloatRef(key, { prefix: "tab" }) || parseKeyedFloatRef(key, { prefix: "fig" }) || parseKeyedFloatRef(key, { prefix: "dia" }) || parseKnowledgeCheckRef(key) || parseMemoRef(key)) {
|
|
141
143
|
continue;
|
|
142
144
|
}
|
|
143
145
|
references.push({
|
|
@@ -291,6 +293,30 @@ function sharedNotationEntries() {
|
|
|
291
293
|
};
|
|
292
294
|
});
|
|
293
295
|
}
|
|
296
|
+
function abbreviationEntries() {
|
|
297
|
+
const directory = join(CONTENT_ROOT, "abbreviations");
|
|
298
|
+
if (!existsSync(directory)) return [];
|
|
299
|
+
return filesBelow(directory, [".md"]).map((path) => {
|
|
300
|
+
const { file, data } = parseDocument(path);
|
|
301
|
+
const key = string(data.key, `${file} key`);
|
|
302
|
+
const filenameKey = basename(path, ".md");
|
|
303
|
+
if (key !== filenameKey) {
|
|
304
|
+
throw new Error(`${file} declares key ${key}; expected ${filenameKey}`);
|
|
305
|
+
}
|
|
306
|
+
const domain = optionalString(data.domain, `${file} domain`);
|
|
307
|
+
return {
|
|
308
|
+
key,
|
|
309
|
+
abbr: string(data.abbr, `${file} abbr`),
|
|
310
|
+
expansion: meaningText(data.expansion, `${file} expansion`),
|
|
311
|
+
aliases: strings(data.aliases, `${file} aliases`),
|
|
312
|
+
source: { file },
|
|
313
|
+
...domain === void 0 ? {} : { domain }
|
|
314
|
+
};
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
async function loadAbbreviationRegistryInput() {
|
|
318
|
+
return abbreviationEntries();
|
|
319
|
+
}
|
|
294
320
|
function notationLessonEntries() {
|
|
295
321
|
const lessons = [];
|
|
296
322
|
for (const path of filesBelow(join(CONTENT_ROOT, "docs"), [".md", ".mdx"])) {
|
|
@@ -403,6 +429,7 @@ function notationSourceLocatorGaps() {
|
|
|
403
429
|
}
|
|
404
430
|
export {
|
|
405
431
|
extractNotationReferences,
|
|
432
|
+
loadAbbreviationRegistryInput,
|
|
406
433
|
loadCurriculumCatalog,
|
|
407
434
|
loadLessonChecks,
|
|
408
435
|
loadNotationDefinitions,
|
|
@@ -6,12 +6,23 @@
|
|
|
6
6
|
* data value and passes it here, typed against `CourseConfig`. A second course
|
|
7
7
|
* supplies its own value of this shape.
|
|
8
8
|
*/
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* One **part** — a sidebar group, holding the chapters (lessons) whose slugs
|
|
11
|
+
* start with `dir`. A part's 1-based position in `CourseConfig.parts` is the
|
|
12
|
+
* number before the dot in every equation, table, and figure number inside it.
|
|
13
|
+
* See `docs/authoring/structure.md` for the full level taxonomy.
|
|
14
|
+
*/
|
|
15
|
+
interface CoursePart {
|
|
10
16
|
/** Lesson-slug prefix, e.g. `bonds` for `bonds/yield-to-maturity`. */
|
|
11
17
|
readonly dir: string;
|
|
12
18
|
/** Sidebar group heading. */
|
|
13
19
|
readonly label: string;
|
|
14
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* @deprecated Renamed to {@link CoursePart}. "Section" now only ever means an
|
|
23
|
+
* `##` heading inside a lesson. Removed in the next MINOR release.
|
|
24
|
+
*/
|
|
25
|
+
type CourseSection = CoursePart;
|
|
15
26
|
interface CourseConventions {
|
|
16
27
|
/** Notation key a lesson's stated sign convention must resolve to. */
|
|
17
28
|
readonly signConventionKey: string;
|
|
@@ -33,6 +44,37 @@ interface CourseOperators {
|
|
|
33
44
|
/** Baseline operators this course wants explained anyway. */
|
|
34
45
|
readonly deny?: readonly string[];
|
|
35
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* A course's edits to the shipped roman-label baseline (`BASE_ROMAN_LABELS`),
|
|
49
|
+
* consumed by the `roman-label-explained` consistency check (rule N10). A
|
|
50
|
+
* `\mathrm{…}` multi-letter label a lesson uses is fine when it is on the
|
|
51
|
+
* baseline plus `allow` minus `deny`; otherwise it needs a notation entry the
|
|
52
|
+
* reader can hover. Same shape as {@link CourseOperators}, one level down:
|
|
53
|
+
* this governs `\mathrm{KE}`-style labels, not `\operatorname{Var}`-style
|
|
54
|
+
* operators.
|
|
55
|
+
*/
|
|
56
|
+
interface CourseRomanLabels {
|
|
57
|
+
/** Labels this course also treats as universal qualifiers (`eff`, `net`, …). */
|
|
58
|
+
readonly allow?: readonly string[];
|
|
59
|
+
/** Baseline labels this course wants explained anyway. */
|
|
60
|
+
readonly deny?: readonly string[];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A course's edits to the shipped abbreviation baseline (`BASE_ABBREVIATIONS`),
|
|
64
|
+
* consumed by the `abbreviation-explained` gate (rule N14). An abbreviation-
|
|
65
|
+
* shaped token — an all-caps acronym or a dotted form like `w.r.t.` — a
|
|
66
|
+
* lesson uses in prose is fine when it is on the baseline plus `allow` minus
|
|
67
|
+
* `deny`; otherwise it needs an entry in the `abbreviations` content
|
|
68
|
+
* collection, or — if it also names a math quantity — a notation entry the
|
|
69
|
+
* reader can hover. Same shape as {@link CourseOperators} / {@link
|
|
70
|
+
* CourseRomanLabels}.
|
|
71
|
+
*/
|
|
72
|
+
interface CourseAbbreviations {
|
|
73
|
+
/** Abbreviations this course also treats as universal (`GDP`, `CPU`, …). */
|
|
74
|
+
readonly allow?: readonly string[];
|
|
75
|
+
/** Baseline abbreviations this course wants explained anyway. */
|
|
76
|
+
readonly deny?: readonly string[];
|
|
77
|
+
}
|
|
36
78
|
/** One person or team credited for the study material. */
|
|
37
79
|
interface CourseAuthor {
|
|
38
80
|
readonly name: string;
|
|
@@ -65,11 +107,37 @@ interface CourseAbout {
|
|
|
65
107
|
/** Link to the published content changelog (repo blob, docs page, …). */
|
|
66
108
|
readonly changelogHref?: string;
|
|
67
109
|
}
|
|
110
|
+
/** Optional, build-time lesson artifacts exposed to readers. */
|
|
111
|
+
interface LessonExportsConfig {
|
|
112
|
+
/** Emit one self-contained, offline-capable HTML file per lesson. */
|
|
113
|
+
readonly standaloneHtml?: boolean;
|
|
114
|
+
/** Offer the browser's native print dialog with explico's lesson print layout. */
|
|
115
|
+
readonly print?: boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Published course root used for links that intentionally leave the exported
|
|
118
|
+
* lesson. Required at export time when such a link is present.
|
|
119
|
+
*/
|
|
120
|
+
readonly canonicalBaseUrl?: string;
|
|
121
|
+
}
|
|
122
|
+
/** The stable, non-optional export settings carried by the manifest. */
|
|
123
|
+
interface NormalizedLessonExports {
|
|
124
|
+
readonly standaloneHtml: boolean;
|
|
125
|
+
readonly print: boolean;
|
|
126
|
+
readonly canonicalBaseUrl?: string;
|
|
127
|
+
}
|
|
68
128
|
interface CourseConfig {
|
|
69
129
|
readonly title: string;
|
|
70
130
|
readonly description: string;
|
|
71
|
-
/**
|
|
72
|
-
|
|
131
|
+
/**
|
|
132
|
+
* The course's parts (sidebar groups), in order. Exactly one of `parts` or
|
|
133
|
+
* the deprecated `sections` must be given.
|
|
134
|
+
*/
|
|
135
|
+
readonly parts?: readonly CoursePart[];
|
|
136
|
+
/**
|
|
137
|
+
* @deprecated Renamed to {@link CourseConfig.parts}. Still honoured, so a
|
|
138
|
+
* course on the old name keeps building; removed in the next MINOR release.
|
|
139
|
+
*/
|
|
140
|
+
readonly sections?: readonly CoursePart[];
|
|
73
141
|
/** Namespaces used by competency / notation `domain` fields. */
|
|
74
142
|
readonly domains: readonly string[];
|
|
75
143
|
readonly conventions: CourseConventions;
|
|
@@ -77,6 +145,20 @@ interface CourseConfig {
|
|
|
77
145
|
readonly about: CourseAbout;
|
|
78
146
|
/** Edits to the shipped named-operator baseline (rule N8). Optional. */
|
|
79
147
|
readonly operators?: CourseOperators;
|
|
148
|
+
/** Edits to the shipped roman-label baseline (rule N10). Optional. */
|
|
149
|
+
readonly romanLabels?: CourseRomanLabels;
|
|
150
|
+
/** Edits to the shipped abbreviation baseline (rule N14). Optional. */
|
|
151
|
+
readonly abbreviations?: CourseAbbreviations;
|
|
152
|
+
/** Per-lesson export actions and downloadable artifacts. Disabled when omitted. */
|
|
153
|
+
readonly lessonExports?: LessonExportsConfig;
|
|
80
154
|
}
|
|
155
|
+
/** Validate and canonicalize the build-facing lesson-export configuration. */
|
|
156
|
+
declare function normalizeLessonExports(config: CourseConfig): NormalizedLessonExports;
|
|
157
|
+
/**
|
|
158
|
+
* The course's parts, from whichever field the config uses. Throws rather than
|
|
159
|
+
* silently producing a course with no sidebar groups — a config with neither
|
|
160
|
+
* field would otherwise number every float by the `##`-section fallback.
|
|
161
|
+
*/
|
|
162
|
+
declare function coursePartsOf(config: CourseConfig): readonly CoursePart[];
|
|
81
163
|
|
|
82
|
-
export type
|
|
164
|
+
export { type CourseAbbreviations, type CourseAbout, type CourseAuthor, type CourseConfig, type CourseConventions, type CourseOperators, type CoursePart, type CourseRomanLabels, type CourseSection, type LessonExportsConfig, type NormalizedLessonExports, coursePartsOf, normalizeLessonExports };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
function normalizeLessonExports(config) {
|
|
2
|
+
const standaloneHtml = config.lessonExports?.standaloneHtml === true;
|
|
3
|
+
const print = config.lessonExports?.print === true;
|
|
4
|
+
const rawBase = config.lessonExports?.canonicalBaseUrl;
|
|
5
|
+
if (rawBase === void 0) return { standaloneHtml, print };
|
|
6
|
+
let parsed;
|
|
7
|
+
try {
|
|
8
|
+
parsed = new URL(rawBase);
|
|
9
|
+
} catch {
|
|
10
|
+
throw new Error(
|
|
11
|
+
`CourseConfig lessonExports.canonicalBaseUrl must be an absolute http(s) URL; received ${JSON.stringify(rawBase)}.`
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`CourseConfig lessonExports.canonicalBaseUrl must use http(s); received ${JSON.stringify(rawBase)}.`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
if (parsed.search || parsed.hash) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
"CourseConfig lessonExports.canonicalBaseUrl cannot contain a query or fragment."
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
if (!parsed.pathname.endsWith("/")) parsed.pathname += "/";
|
|
25
|
+
return { standaloneHtml, print, canonicalBaseUrl: parsed.toString() };
|
|
26
|
+
}
|
|
27
|
+
function coursePartsOf(config) {
|
|
28
|
+
const parts = config.parts ?? config.sections;
|
|
29
|
+
if (!parts || parts.length === 0) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
"CourseConfig needs at least one entry in `parts` (a sidebar group: { dir, label }). The old name `sections` is still accepted."
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return parts;
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
coursePartsOf,
|
|
38
|
+
normalizeLessonExports
|
|
39
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Manifest } from '../manifest.js';
|
|
2
|
+
import '../../curriculum/validation.js';
|
|
3
|
+
import '../../reference/consistency.js';
|
|
4
|
+
import '../course-config.js';
|
|
5
|
+
import '../collections.js';
|
|
6
|
+
import '../../reference/types.js';
|
|
7
|
+
import '../../reference/lint-ignore.js';
|
|
8
|
+
import '../../reference/equations-validate.js';
|
|
9
|
+
import '../../reference/tables-validate.js';
|
|
10
|
+
import '../../reference/keyed-float-validate.js';
|
|
11
|
+
import '../../reference/figures-validate.js';
|
|
12
|
+
import '../../reference/diagrams-validate.js';
|
|
13
|
+
import '../../reference/knowledge-checks-validate.js';
|
|
14
|
+
import '../../reference/raw-floats-validate.js';
|
|
15
|
+
import '../../reference/gate-math.js';
|
|
16
|
+
import '../../reference/mixed-content.js';
|
|
17
|
+
import '../../reference/gloss.js';
|
|
18
|
+
import '../knowledge-version.js';
|
|
19
|
+
import '../lesson-derivation.js';
|
|
20
|
+
|
|
21
|
+
/** Inputs for producing one standalone lesson from Astro's final HTML. */
|
|
22
|
+
interface StandaloneLessonInput {
|
|
23
|
+
readonly html: string;
|
|
24
|
+
readonly sourceFile: string;
|
|
25
|
+
readonly outputRoot: string;
|
|
26
|
+
readonly lessonSlug: string;
|
|
27
|
+
readonly canonicalBaseUrl?: string;
|
|
28
|
+
}
|
|
29
|
+
/** Build one fully inlined lesson document without writing it. */
|
|
30
|
+
declare function buildStandaloneLessonHtml(input: StandaloneLessonInput): Promise<string>;
|
|
31
|
+
/** Emit all opt-in lesson HTML artifacts from Astro's completed static output. */
|
|
32
|
+
declare function writeStandaloneLessonExports(directory: URL, manifest: Manifest): Promise<void>;
|
|
33
|
+
|
|
34
|
+
export { type StandaloneLessonInput, buildStandaloneLessonHtml, writeStandaloneLessonExports };
|