explico 0.4.0 → 0.6.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 +382 -1
- package/README.md +103 -8
- package/dist/compiler/astro.d.ts +9 -1
- package/dist/compiler/astro.js +182 -3
- package/dist/compiler/cli.d.ts +13 -0
- package/dist/compiler/cli.js +28 -4
- package/dist/compiler/collections.d.ts +3 -2
- package/dist/compiler/collections.js +28 -1
- package/dist/compiler/course-config.d.ts +88 -1
- package/dist/compiler/course-config.js +35 -1
- package/dist/compiler/exports/standalone-html.d.ts +34 -0
- package/dist/compiler/exports/standalone-html.js +803 -0
- package/dist/compiler/manifest.d.ts +98 -6
- package/dist/compiler/manifest.js +154 -17
- 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.d.mts +10 -15
- package/dist/reference/equations.mjs +67 -14
- package/dist/reference/gate-abbreviations.d.ts +17 -0
- package/dist/reference/gate-abbreviations.js +64 -0
- package/dist/reference/gate-heading-length.d.ts +12 -0
- package/dist/reference/gate-heading-length.js +79 -0
- package/dist/reference/gate-math.d.ts +32 -5
- package/dist/reference/gate-math.js +6 -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 +5 -5
- package/dist/reference/keyed-jsx-floats.d.mts +7 -12
- package/dist/reference/keyed-jsx-floats.mjs +50 -25
- 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 +42 -0
- package/dist/reference/math-glyphs.mjs +548 -40
- 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-citation.d.mts +5 -1
- package/dist/reference/remark-citation.mjs +0 -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 +57 -9
- package/dist/reference/remark-notation.mjs +354 -42
- 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 +53 -4
- package/docs/authoring/README.md +10 -0
- package/docs/authoring/agents.md +17 -2
- package/docs/authoring/components.md +138 -0
- package/docs/authoring/migrating.md +275 -7
- package/docs/authoring/notation.md +615 -1
- package/docs/authoring/structure-rules.md +69 -0
- package/docs/authoring/structure.md +120 -8
- package/docs/authoring/visualizations.md +36 -3
- package/package.json +5 -2
- package/src/compiler/astro.ts +208 -3
- package/src/compiler/cli.ts +29 -2
- package/src/compiler/collections.ts +45 -4
- package/src/compiler/course-config.ts +132 -0
- package/src/compiler/exports/standalone-html.ts +963 -0
- package/src/compiler/manifest.ts +281 -22
- 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 +409 -103
- 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 +260 -83
- 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/LayoutSocialIcons.astro +41 -2
- package/src/components/starlight/LessonExportActions.astro +436 -0
- package/src/components/starlight/LessonFooter.astro +30 -3
- package/src/components/starlight/section-parts.ts +31 -0
- package/src/components/table/Table.astro +293 -7
- package/src/components/table/TableOverflowExpander.astro +489 -0
- package/src/content-config.ts +36 -1
- 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.mjs +67 -14
- package/src/reference/gate-abbreviations.ts +128 -0
- package/src/reference/gate-heading-length.ts +131 -0
- package/src/reference/gate-math.ts +32 -7
- package/src/reference/gate-prose-notation.ts +224 -0
- package/src/reference/keyed-float-validate.ts +5 -5
- package/src/reference/keyed-jsx-floats.mjs +50 -25
- 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 +548 -40
- 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-citation.mjs +0 -0
- package/src/reference/remark-memo.mjs +339 -0
- package/src/reference/remark-notation.mjs +354 -42
- 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 +996 -50
package/dist/compiler/astro.js
CHANGED
|
@@ -1,9 +1,82 @@
|
|
|
1
|
-
import {
|
|
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";
|
|
2
6
|
import {
|
|
3
7
|
compileManifest,
|
|
4
8
|
writeManifest
|
|
5
9
|
} from "./manifest.js";
|
|
10
|
+
import { missingNotationPayloadKeys } from "../reference/page-notation.js";
|
|
11
|
+
import { writeStandaloneLessonExports } from "./exports/standalone-html.js";
|
|
6
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
|
+
}
|
|
7
80
|
function assertLocalNotationParity(lessonId, authoredKeys, renderedKeys) {
|
|
8
81
|
const authored = [...new Set(authoredKeys)].sort();
|
|
9
82
|
const rendered = [...new Set(renderedKeys)].sort();
|
|
@@ -14,6 +87,27 @@ function assertLocalNotationParity(lessonId, authoredKeys, renderedKeys) {
|
|
|
14
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.`
|
|
15
88
|
);
|
|
16
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
|
+
}
|
|
17
111
|
function replaceInPlace(target, source) {
|
|
18
112
|
if (Array.isArray(target) && Array.isArray(source)) {
|
|
19
113
|
target.splice(0, target.length, ...source);
|
|
@@ -37,15 +131,36 @@ function replaceInPlace(target, source) {
|
|
|
37
131
|
}
|
|
38
132
|
}
|
|
39
133
|
function explicoContentRefresh(courseConfig, manifest) {
|
|
134
|
+
let developmentConfig;
|
|
40
135
|
return {
|
|
41
136
|
name: "explico-content-refresh",
|
|
42
137
|
hooks: {
|
|
43
|
-
"astro:
|
|
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 }) => {
|
|
44
144
|
if (watchedServers.has(server)) return;
|
|
45
145
|
watchedServers.add(server);
|
|
46
146
|
const contentRoot = `${resolve(process.cwd(), "content")}${sep}`;
|
|
47
147
|
let timer;
|
|
48
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
|
+
};
|
|
49
164
|
const refresh = (path) => {
|
|
50
165
|
const absolute = resolve(path);
|
|
51
166
|
if (!absolute.startsWith(contentRoot) || restarting) return;
|
|
@@ -69,11 +184,75 @@ function explicoContentRefresh(courseConfig, manifest) {
|
|
|
69
184
|
server.watcher.on("add", refresh);
|
|
70
185
|
server.watcher.on("change", refresh);
|
|
71
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);
|
|
72
249
|
}
|
|
73
250
|
}
|
|
74
251
|
};
|
|
75
252
|
}
|
|
76
253
|
export {
|
|
77
254
|
assertLocalNotationParity,
|
|
78
|
-
|
|
255
|
+
assertNotationPayloadHtml,
|
|
256
|
+
explicoContentRefresh,
|
|
257
|
+
matchStandaloneDownload
|
|
79
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,11 +128,18 @@ function processorFor(manifest) {
|
|
|
121
128
|
// shape here that it does there (D7/F-tables-figures parity).
|
|
122
129
|
remarkMdx,
|
|
123
130
|
remarkMath,
|
|
131
|
+
remarkShortNote,
|
|
132
|
+
remarkMemo,
|
|
124
133
|
// `manifest` carries the number tables *and* the part/offset placement
|
|
125
134
|
// together, so this check numbers a page exactly as the Astro build
|
|
126
135
|
// does. Passing the tables alone silently numbered pages by `##` section
|
|
127
136
|
// here while cross-page references read the part-scheme manifest.
|
|
128
|
-
|
|
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)],
|
|
129
143
|
[remarkCitation, { sources: manifest.sources }]
|
|
130
144
|
],
|
|
131
145
|
rehypePlugins: [
|
|
@@ -299,6 +313,9 @@ function contentStatus(manifest) {
|
|
|
299
313
|
...manifest.diagnostics.equations.map((d) => d.severity),
|
|
300
314
|
...manifest.diagnostics.tables.map((d) => d.severity),
|
|
301
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),
|
|
302
319
|
...manifest.diagnostics.consistency.map(() => "warning"),
|
|
303
320
|
...manifest.diagnostics.knowledge.map((d) => d.severity)
|
|
304
321
|
];
|
|
@@ -314,6 +331,10 @@ function contentStatus(manifest) {
|
|
|
314
331
|
(count, lesson) => count + lesson.figures.length,
|
|
315
332
|
0
|
|
316
333
|
);
|
|
334
|
+
const keyedDiagrams = manifest.lessons.reduce(
|
|
335
|
+
(count, lesson) => count + lesson.diagrams.length,
|
|
336
|
+
0
|
|
337
|
+
);
|
|
317
338
|
const assessments = new Set(
|
|
318
339
|
manifest.lessons.flatMap((lesson) => lesson.assessments)
|
|
319
340
|
).size;
|
|
@@ -328,7 +349,8 @@ function contentStatus(manifest) {
|
|
|
328
349
|
notationDefinitions: manifest.notation.definitions.length,
|
|
329
350
|
keyedEquations,
|
|
330
351
|
keyedTables,
|
|
331
|
-
keyedFigures
|
|
352
|
+
keyedFigures,
|
|
353
|
+
keyedDiagrams
|
|
332
354
|
},
|
|
333
355
|
draftDebt: { lessons: draftLessons, notation: draftNotation },
|
|
334
356
|
orphans,
|
|
@@ -384,6 +406,8 @@ function contentContext(manifest, rawId) {
|
|
|
384
406
|
equations: lesson.equations.map((equation) => ({ ...equation })),
|
|
385
407
|
tables: lesson.tables.map((table) => ({ ...table })),
|
|
386
408
|
figures: lesson.figures.map((figure) => ({ ...figure })),
|
|
409
|
+
diagrams: lesson.diagrams.map((diagram) => ({ ...diagram })),
|
|
410
|
+
knowledgeChecks: lesson.knowledgeChecks.map((check) => ({ ...check })),
|
|
387
411
|
sources: lesson.sources.map((id) => {
|
|
388
412
|
const record = sourceById.get(id);
|
|
389
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,
|
|
@@ -44,6 +44,37 @@ interface CourseOperators {
|
|
|
44
44
|
/** Baseline operators this course wants explained anyway. */
|
|
45
45
|
readonly deny?: readonly string[];
|
|
46
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
|
+
}
|
|
47
78
|
/** One person or team credited for the study material. */
|
|
48
79
|
interface CourseAuthor {
|
|
49
80
|
readonly name: string;
|
|
@@ -76,6 +107,51 @@ interface CourseAbout {
|
|
|
76
107
|
/** Link to the published content changelog (repo blob, docs page, …). */
|
|
77
108
|
readonly changelogHref?: string;
|
|
78
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* A course's chosen initial open/closed state for the engine's own built-in
|
|
112
|
+
* collapsible sections — the "Page notation" panel (`NotationLayer.astro`)
|
|
113
|
+
* and the generated "References" section (`remark-citation.mjs`). Each is
|
|
114
|
+
* independent of `defaultCollapsible.sections`/`.subsections` (an author's
|
|
115
|
+
* own `##` headings): both of these are engine-authored, not written by a
|
|
116
|
+
* lesson, so they need their own default rather than inheriting the site's
|
|
117
|
+
* general heading default.
|
|
118
|
+
*
|
|
119
|
+
* `true` starts open, `false` (or omitted) starts closed — the engine's own
|
|
120
|
+
* default is closed either way, matching a long panel a reader usually
|
|
121
|
+
* doesn't need open on arrival.
|
|
122
|
+
*/
|
|
123
|
+
interface CourseDisclosureDefaults {
|
|
124
|
+
/** Initial state of the "Page notation" panel. Default: closed. */
|
|
125
|
+
readonly notation?: boolean;
|
|
126
|
+
/** Initial state of the generated "References" section. Default: closed. */
|
|
127
|
+
readonly references?: boolean;
|
|
128
|
+
}
|
|
129
|
+
/** The stable, non-optional form `NormalizedLessonExports` mirrors. */
|
|
130
|
+
interface NormalizedDisclosureDefaults {
|
|
131
|
+
readonly notation: boolean;
|
|
132
|
+
readonly references: boolean;
|
|
133
|
+
}
|
|
134
|
+
/** Validate and canonicalize `CourseConfig.disclosureDefaults`. Absent (or
|
|
135
|
+
* an absent sub-field) resolves to closed, the engine's own default. */
|
|
136
|
+
declare function normalizeDisclosureDefaults(config: CourseConfig): NormalizedDisclosureDefaults;
|
|
137
|
+
/** Optional, build-time lesson artifacts exposed to readers. */
|
|
138
|
+
interface LessonExportsConfig {
|
|
139
|
+
/** Emit one self-contained, offline-capable HTML file per lesson. */
|
|
140
|
+
readonly standaloneHtml?: boolean;
|
|
141
|
+
/** Offer the browser's native print dialog with explico's lesson print layout. */
|
|
142
|
+
readonly print?: boolean;
|
|
143
|
+
/**
|
|
144
|
+
* Published course root used for links that intentionally leave the exported
|
|
145
|
+
* lesson. Required at export time when such a link is present.
|
|
146
|
+
*/
|
|
147
|
+
readonly canonicalBaseUrl?: string;
|
|
148
|
+
}
|
|
149
|
+
/** The stable, non-optional export settings carried by the manifest. */
|
|
150
|
+
interface NormalizedLessonExports {
|
|
151
|
+
readonly standaloneHtml: boolean;
|
|
152
|
+
readonly print: boolean;
|
|
153
|
+
readonly canonicalBaseUrl?: string;
|
|
154
|
+
}
|
|
79
155
|
interface CourseConfig {
|
|
80
156
|
readonly title: string;
|
|
81
157
|
readonly description: string;
|
|
@@ -96,7 +172,18 @@ interface CourseConfig {
|
|
|
96
172
|
readonly about: CourseAbout;
|
|
97
173
|
/** Edits to the shipped named-operator baseline (rule N8). Optional. */
|
|
98
174
|
readonly operators?: CourseOperators;
|
|
175
|
+
/** Edits to the shipped roman-label baseline (rule N10). Optional. */
|
|
176
|
+
readonly romanLabels?: CourseRomanLabels;
|
|
177
|
+
/** Edits to the shipped abbreviation baseline (rule N14). Optional. */
|
|
178
|
+
readonly abbreviations?: CourseAbbreviations;
|
|
179
|
+
/** Per-lesson export actions and downloadable artifacts. Disabled when omitted. */
|
|
180
|
+
readonly lessonExports?: LessonExportsConfig;
|
|
181
|
+
/** Initial open/closed state for the engine's own built-in collapsible
|
|
182
|
+
* sections ("Page notation", "References"). Closed when omitted. */
|
|
183
|
+
readonly disclosureDefaults?: CourseDisclosureDefaults;
|
|
99
184
|
}
|
|
185
|
+
/** Validate and canonicalize the build-facing lesson-export configuration. */
|
|
186
|
+
declare function normalizeLessonExports(config: CourseConfig): NormalizedLessonExports;
|
|
100
187
|
/**
|
|
101
188
|
* The course's parts, from whichever field the config uses. Throws rather than
|
|
102
189
|
* silently producing a course with no sidebar groups — a config with neither
|
|
@@ -104,4 +191,4 @@ interface CourseConfig {
|
|
|
104
191
|
*/
|
|
105
192
|
declare function coursePartsOf(config: CourseConfig): readonly CoursePart[];
|
|
106
193
|
|
|
107
|
-
export { type CourseAbout, type CourseAuthor, type CourseConfig, type CourseConventions, type CourseOperators, type CoursePart, type CourseSection, coursePartsOf };
|
|
194
|
+
export { type CourseAbbreviations, type CourseAbout, type CourseAuthor, type CourseConfig, type CourseConventions, type CourseDisclosureDefaults, type CourseOperators, type CoursePart, type CourseRomanLabels, type CourseSection, type LessonExportsConfig, type NormalizedDisclosureDefaults, type NormalizedLessonExports, coursePartsOf, normalizeDisclosureDefaults, normalizeLessonExports };
|
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
function normalizeDisclosureDefaults(config) {
|
|
2
|
+
return {
|
|
3
|
+
notation: config.disclosureDefaults?.notation === true,
|
|
4
|
+
references: config.disclosureDefaults?.references === true
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
function normalizeLessonExports(config) {
|
|
8
|
+
const standaloneHtml = config.lessonExports?.standaloneHtml === true;
|
|
9
|
+
const print = config.lessonExports?.print === true;
|
|
10
|
+
const rawBase = config.lessonExports?.canonicalBaseUrl;
|
|
11
|
+
if (rawBase === void 0) return { standaloneHtml, print };
|
|
12
|
+
let parsed;
|
|
13
|
+
try {
|
|
14
|
+
parsed = new URL(rawBase);
|
|
15
|
+
} catch {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`CourseConfig lessonExports.canonicalBaseUrl must be an absolute http(s) URL; received ${JSON.stringify(rawBase)}.`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`CourseConfig lessonExports.canonicalBaseUrl must use http(s); received ${JSON.stringify(rawBase)}.`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
if (parsed.search || parsed.hash) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
"CourseConfig lessonExports.canonicalBaseUrl cannot contain a query or fragment."
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
if (!parsed.pathname.endsWith("/")) parsed.pathname += "/";
|
|
31
|
+
return { standaloneHtml, print, canonicalBaseUrl: parsed.toString() };
|
|
32
|
+
}
|
|
1
33
|
function coursePartsOf(config) {
|
|
2
34
|
const parts = config.parts ?? config.sections;
|
|
3
35
|
if (!parts || parts.length === 0) {
|
|
@@ -8,5 +40,7 @@ function coursePartsOf(config) {
|
|
|
8
40
|
return parts;
|
|
9
41
|
}
|
|
10
42
|
export {
|
|
11
|
-
coursePartsOf
|
|
43
|
+
coursePartsOf,
|
|
44
|
+
normalizeDisclosureDefaults,
|
|
45
|
+
normalizeLessonExports
|
|
12
46
|
};
|
|
@@ -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 };
|