mr-md 1.0.0 → 1.0.1
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/dist/builder.d.ts +4 -3
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +55 -18
- package/dist/renderer/blocks.d.ts.map +1 -1
- package/dist/renderer/blocks.js +8 -3
- package/dist/renderer/utils.d.ts.map +1 -1
- package/dist/renderer/utils.js +13 -2
- package/package.json +1 -1
- package/src/builder.ts +61 -18
- package/src/renderer/blocks.ts +8 -3
- package/src/renderer/utils.ts +14 -2
package/dist/builder.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export declare class LessonBuilder {
|
|
|
4
4
|
private blocks;
|
|
5
5
|
private options;
|
|
6
6
|
private _rawOptions;
|
|
7
|
-
constructor(title: string, options?: BuildOptions);
|
|
7
|
+
constructor(title: string, options?: BuildOptions, callerDir?: string);
|
|
8
8
|
/**
|
|
9
9
|
* Sets the URL slug for the generated HTML file.
|
|
10
10
|
* Automatically generated from the title by default.
|
|
@@ -26,7 +26,7 @@ export declare class LessonBuilder {
|
|
|
26
26
|
/** Curated production defaults for the generated lesson shell. */
|
|
27
27
|
preset(preset: NonNullable<BuildOptions["preset"]>): this;
|
|
28
28
|
/** @internal Used by ChapterBuilder to push down shared config */
|
|
29
|
-
_inheritOptions(parentOpts: BuildOptions): void;
|
|
29
|
+
_inheritOptions(parentOpts: BuildOptions, parentRawOpts?: BuildOptions): void;
|
|
30
30
|
/** @internal Used by ChapterBuilder */
|
|
31
31
|
_setParentSlug(slug: string): void;
|
|
32
32
|
/** @internal Used by ChapterBuilder */
|
|
@@ -146,7 +146,8 @@ export declare class ChapterBuilder {
|
|
|
146
146
|
private meta;
|
|
147
147
|
private lessonBuilders;
|
|
148
148
|
private options;
|
|
149
|
-
|
|
149
|
+
private _rawOptions;
|
|
150
|
+
constructor(title: string, options?: BuildOptions, callerDir?: string);
|
|
150
151
|
slug(slug: string): this;
|
|
151
152
|
description(text: string): this;
|
|
152
153
|
status(status: "completed" | "active" | "locked"): this;
|
package/dist/builder.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAEX,gBAAgB,EAEhB,YAAY,EACZ,YAAY,EACZ,OAAO,EAGP,UAAU,EAEV,cAAc,EAId,YAAY,EACZ,MAAM,EACN,UAAU,EAGV,YAAY,EACZ,SAAS,EAIT,iBAAiB,EAEjB,cAAc,EACd,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"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"}
|
package/dist/builder.js
CHANGED
|
@@ -1,24 +1,56 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import { render, renderChapter } from "./renderer/index.js";
|
|
4
|
+
function getCallerDir() {
|
|
5
|
+
const err = new Error();
|
|
6
|
+
const stack = err.stack?.split("\n");
|
|
7
|
+
if (!stack || stack.length < 2)
|
|
8
|
+
return undefined;
|
|
9
|
+
let builderFilePath;
|
|
10
|
+
for (let i = 1; i < stack.length; i++) {
|
|
11
|
+
const line = stack[i];
|
|
12
|
+
const match = line.match(/\((.*?):\d+:\d+\)/) || line.match(/at (.*?):\d+:\d+/);
|
|
13
|
+
if (match) {
|
|
14
|
+
let p = match[1];
|
|
15
|
+
if (p.startsWith("file://")) {
|
|
16
|
+
p = p.replace(/^file:\/\//, "");
|
|
17
|
+
}
|
|
18
|
+
if (p.startsWith("/") && p[2] === ":") {
|
|
19
|
+
p = p.substring(1); // Handle Windows paths like /C:/
|
|
20
|
+
}
|
|
21
|
+
if (!builderFilePath) {
|
|
22
|
+
builderFilePath = p;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (p === builderFilePath) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
return path.dirname(p);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
4
33
|
// ─── LessonBuilder ────────────────────────────────────────────────────────────
|
|
5
34
|
export class LessonBuilder {
|
|
6
35
|
meta;
|
|
7
36
|
blocks = [];
|
|
8
37
|
options;
|
|
9
38
|
_rawOptions;
|
|
10
|
-
constructor(title, options = {}) {
|
|
39
|
+
constructor(title, options = {}, callerDir) {
|
|
11
40
|
this._rawOptions = options;
|
|
41
|
+
let slug = title
|
|
42
|
+
.toLowerCase()
|
|
43
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
44
|
+
.replace(/(^-|-$)/g, "");
|
|
45
|
+
if (!slug)
|
|
46
|
+
slug = "lesson";
|
|
12
47
|
this.meta = {
|
|
13
48
|
title,
|
|
14
|
-
slug
|
|
15
|
-
.toLowerCase()
|
|
16
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
17
|
-
.replace(/(^-|-$)/g, ""),
|
|
49
|
+
slug,
|
|
18
50
|
};
|
|
19
51
|
this.options = {
|
|
20
52
|
outDir: options.outDir ?? "./out",
|
|
21
|
-
contentBase: options.contentBase ?? ".",
|
|
53
|
+
contentBase: options.contentBase ?? callerDir ?? ".",
|
|
22
54
|
theme: options.theme ?? "auto",
|
|
23
55
|
palette: options.palette ?? "ink",
|
|
24
56
|
strict: options.strict ?? true,
|
|
@@ -75,11 +107,11 @@ export class LessonBuilder {
|
|
|
75
107
|
return this;
|
|
76
108
|
}
|
|
77
109
|
/** @internal Used by ChapterBuilder to push down shared config */
|
|
78
|
-
_inheritOptions(parentOpts) {
|
|
110
|
+
_inheritOptions(parentOpts, parentRawOpts) {
|
|
79
111
|
this.options = {
|
|
80
|
-
outDir: this._rawOptions.outDir ?? parentOpts.outDir ?? this.options.outDir,
|
|
112
|
+
outDir: this._rawOptions.outDir ?? (parentRawOpts?.outDir || parentOpts.outDir) ?? this.options.outDir,
|
|
81
113
|
contentBase: this._rawOptions.contentBase ??
|
|
82
|
-
|
|
114
|
+
parentRawOpts?.contentBase ??
|
|
83
115
|
this.options.contentBase,
|
|
84
116
|
theme: this._rawOptions.theme ?? parentOpts.theme ?? this.options.theme,
|
|
85
117
|
palette: this._rawOptions.palette ?? parentOpts.palette ?? this.options.palette,
|
|
@@ -359,7 +391,7 @@ export class LessonBuilder {
|
|
|
359
391
|
* @example const l = lesson("Introduction to Kinematics").markdown("intro.md");
|
|
360
392
|
*/
|
|
361
393
|
export function lesson(title, options = {}) {
|
|
362
|
-
return new LessonBuilder(title, options);
|
|
394
|
+
return new LessonBuilder(title, options, getCallerDir());
|
|
363
395
|
}
|
|
364
396
|
function normalizeSimulationOptions(opts, legacyHeight, fileConfig = null) {
|
|
365
397
|
let inline;
|
|
@@ -448,17 +480,22 @@ export class ChapterBuilder {
|
|
|
448
480
|
meta;
|
|
449
481
|
lessonBuilders = [];
|
|
450
482
|
options;
|
|
451
|
-
|
|
483
|
+
_rawOptions;
|
|
484
|
+
constructor(title, options = {}, callerDir) {
|
|
485
|
+
this._rawOptions = options;
|
|
486
|
+
let slug = title
|
|
487
|
+
.toLowerCase()
|
|
488
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
489
|
+
.replace(/(^-|-$)/g, "");
|
|
490
|
+
if (!slug)
|
|
491
|
+
slug = "chapter";
|
|
452
492
|
this.meta = {
|
|
453
493
|
title,
|
|
454
|
-
slug
|
|
455
|
-
.toLowerCase()
|
|
456
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
457
|
-
.replace(/(^-|-$)/g, ""),
|
|
494
|
+
slug,
|
|
458
495
|
};
|
|
459
496
|
this.options = {
|
|
460
497
|
outDir: options.outDir ?? "./out",
|
|
461
|
-
contentBase: options.contentBase ?? ".",
|
|
498
|
+
contentBase: options.contentBase ?? callerDir ?? ".",
|
|
462
499
|
theme: options.theme ?? "auto",
|
|
463
500
|
palette: options.palette ?? "ink",
|
|
464
501
|
strict: options.strict ?? true,
|
|
@@ -484,7 +521,7 @@ export class ChapterBuilder {
|
|
|
484
521
|
return this;
|
|
485
522
|
}
|
|
486
523
|
lesson(lessonBuilder) {
|
|
487
|
-
lessonBuilder._inheritOptions(this.options);
|
|
524
|
+
lessonBuilder._inheritOptions(this.options, this._rawOptions);
|
|
488
525
|
lessonBuilder._setParentSlug(this.meta.slug);
|
|
489
526
|
this.lessonBuilders.push(lessonBuilder);
|
|
490
527
|
return this;
|
|
@@ -541,5 +578,5 @@ export class ChapterBuilder {
|
|
|
541
578
|
* .build();
|
|
542
579
|
*/
|
|
543
580
|
export function chapter(title, options = {}) {
|
|
544
|
-
return new ChapterBuilder(title, options);
|
|
581
|
+
return new ChapterBuilder(title, options, getCallerDir());
|
|
545
582
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blocks.d.ts","sourceRoot":"","sources":["../../src/renderer/blocks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAA0B,MAAM,aAAa,CAAC;AAC/E,OAAO,EACN,WAAW,EAKX,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,KAAK,OAAO,EAAmC,MAAM,YAAY,CAAC;AAG3E,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO3C;AACD,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3C;AAID,iBAAS,WAAW,CACnB,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,YAAY,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAuBrC;AAED,iBAAS,gBAAgB,CACxB,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,YAAY,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"blocks.d.ts","sourceRoot":"","sources":["../../src/renderer/blocks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAA0B,MAAM,aAAa,CAAC;AAC/E,OAAO,EACN,WAAW,EAKX,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,KAAK,OAAO,EAAmC,MAAM,YAAY,CAAC;AAG3E,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO3C;AACD,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3C;AAID,iBAAS,WAAW,CACnB,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,YAAY,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAuBrC;AAED,iBAAS,gBAAgB,CACxB,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,YAAY,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAqQrC;AAuID,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,CAAC"}
|
package/dist/renderer/blocks.js
CHANGED
|
@@ -85,8 +85,9 @@ function renderBlockInner(block, idx, options) {
|
|
|
85
85
|
}
|
|
86
86
|
case "code": {
|
|
87
87
|
const raw = resolveContent(block.src, options, "text"); // Could be file or inline
|
|
88
|
+
const isInlineCode = typeof block.src === "string" && (block.src.includes("\n") || block.src.includes(" "));
|
|
88
89
|
const lang = block.lang ??
|
|
89
|
-
(typeof block.src === "string" && block.src.includes(".")
|
|
90
|
+
(typeof block.src === "string" && !isInlineCode && block.src.includes(".")
|
|
90
91
|
? (block.src.split(".").pop() ?? "")
|
|
91
92
|
: "");
|
|
92
93
|
let highlighted = escHtml(raw);
|
|
@@ -106,7 +107,7 @@ function renderBlockInner(block, idx, options) {
|
|
|
106
107
|
};
|
|
107
108
|
}
|
|
108
109
|
case "simulation": {
|
|
109
|
-
const propsJson =
|
|
110
|
+
const propsJson = escapeScriptJson(block.props ?? {});
|
|
110
111
|
const simSrc = resolveContent(block.src, options, "js");
|
|
111
112
|
const simConfig = { js: simSrc, loop: false, dependencies: block.dependencies };
|
|
112
113
|
return {
|
|
@@ -200,7 +201,11 @@ function renderBlockInner(block, idx, options) {
|
|
|
200
201
|
let quiz = { questions: [] };
|
|
201
202
|
const rawJson = resolveContent(block.src, options, "json");
|
|
202
203
|
try {
|
|
203
|
-
|
|
204
|
+
const trimmed = rawJson.trim();
|
|
205
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
206
|
+
throw new Error("Quiz file not found or invalid JSON format");
|
|
207
|
+
}
|
|
208
|
+
quiz = JSON.parse(trimmed);
|
|
204
209
|
}
|
|
205
210
|
catch (e) {
|
|
206
211
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/renderer/utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,OAAO;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;CACrC;AAID,iBAAS,cAAc,CACtB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,YAAY,EACrB,YAAY,GAAE,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,MAAe,GAClD,MAAM,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/renderer/utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,OAAO;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;CACrC;AAID,iBAAS,cAAc,CACtB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,YAAY,EACrB,YAAY,GAAE,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,MAAe,GAClD,MAAM,CAmCR;AAED,iBAAS,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,MAAM,CAwCnE;AAED,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,CAAC"}
|
package/dist/renderer/utils.js
CHANGED
|
@@ -4,6 +4,12 @@ import * as path from "path";
|
|
|
4
4
|
function resolveContent(src, options, expectedType = "text") {
|
|
5
5
|
if (src.includes("\n"))
|
|
6
6
|
return src;
|
|
7
|
+
if (/^https?:\/\//.test(src)) {
|
|
8
|
+
if (options.strict !== false) {
|
|
9
|
+
throw new Error(`Remote URLs are not yet supported for content files: ${src}`);
|
|
10
|
+
}
|
|
11
|
+
return src;
|
|
12
|
+
}
|
|
7
13
|
const isLikelyFilePath = (expectedType !== "text" && src.endsWith(`.${expectedType}`)) ||
|
|
8
14
|
src.startsWith("/") ||
|
|
9
15
|
src.startsWith("./") ||
|
|
@@ -24,9 +30,14 @@ function resolveContent(src, options, expectedType = "text") {
|
|
|
24
30
|
return src;
|
|
25
31
|
}
|
|
26
32
|
function resolveAssetSrc(src, options) {
|
|
27
|
-
if (/^(https?:|data
|
|
33
|
+
if (/^(https?:|data:)/.test(src))
|
|
34
|
+
return src;
|
|
35
|
+
const isWebAbsolute = src.startsWith("/") && !fs.existsSync(src);
|
|
36
|
+
if (isWebAbsolute)
|
|
28
37
|
return src;
|
|
29
|
-
const filePath = path.
|
|
38
|
+
const filePath = path.isAbsolute(src)
|
|
39
|
+
? src
|
|
40
|
+
: path.resolve(options.contentBase ?? ".", src);
|
|
30
41
|
if (!fs.existsSync(filePath)) {
|
|
31
42
|
if (options.strict !== false)
|
|
32
43
|
throw new Error(`Missing media asset: ${filePath}`);
|
package/package.json
CHANGED
package/src/builder.ts
CHANGED
|
@@ -31,6 +31,40 @@ import type {
|
|
|
31
31
|
YouTubeOptions,
|
|
32
32
|
} from "./types.js";
|
|
33
33
|
|
|
34
|
+
function getCallerDir(): string | undefined {
|
|
35
|
+
const err = new Error();
|
|
36
|
+
const stack = err.stack?.split("\n");
|
|
37
|
+
if (!stack || stack.length < 2) return undefined;
|
|
38
|
+
|
|
39
|
+
let builderFilePath: string | undefined;
|
|
40
|
+
|
|
41
|
+
for (let i = 1; i < stack.length; i++) {
|
|
42
|
+
const line = stack[i];
|
|
43
|
+
const match = line.match(/\((.*?):\d+:\d+\)/) || line.match(/at (.*?):\d+:\d+/);
|
|
44
|
+
if (match) {
|
|
45
|
+
let p = match[1];
|
|
46
|
+
if (p.startsWith("file://")) {
|
|
47
|
+
p = p.replace(/^file:\/\//, "");
|
|
48
|
+
}
|
|
49
|
+
if (p.startsWith("/") && p[2] === ":") {
|
|
50
|
+
p = p.substring(1); // Handle Windows paths like /C:/
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!builderFilePath) {
|
|
54
|
+
builderFilePath = p;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (p === builderFilePath) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return path.dirname(p);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
34
68
|
// ─── LessonBuilder ────────────────────────────────────────────────────────────
|
|
35
69
|
|
|
36
70
|
export class LessonBuilder {
|
|
@@ -39,18 +73,22 @@ export class LessonBuilder {
|
|
|
39
73
|
private options: BuildOptions;
|
|
40
74
|
private _rawOptions: BuildOptions;
|
|
41
75
|
|
|
42
|
-
constructor(title: string, options: BuildOptions = {}) {
|
|
76
|
+
constructor(title: string, options: BuildOptions = {}, callerDir?: string) {
|
|
43
77
|
this._rawOptions = options;
|
|
78
|
+
|
|
79
|
+
let slug = title
|
|
80
|
+
.toLowerCase()
|
|
81
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
82
|
+
.replace(/(^-|-$)/g, "");
|
|
83
|
+
if (!slug) slug = "lesson";
|
|
84
|
+
|
|
44
85
|
this.meta = {
|
|
45
86
|
title,
|
|
46
|
-
slug
|
|
47
|
-
.toLowerCase()
|
|
48
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
49
|
-
.replace(/(^-|-$)/g, ""),
|
|
87
|
+
slug,
|
|
50
88
|
};
|
|
51
89
|
this.options = {
|
|
52
90
|
outDir: options.outDir ?? "./out",
|
|
53
|
-
contentBase: options.contentBase ?? ".",
|
|
91
|
+
contentBase: options.contentBase ?? callerDir ?? ".",
|
|
54
92
|
theme: options.theme ?? "auto",
|
|
55
93
|
palette: options.palette ?? "ink",
|
|
56
94
|
strict: options.strict ?? true,
|
|
@@ -115,13 +153,13 @@ export class LessonBuilder {
|
|
|
115
153
|
}
|
|
116
154
|
|
|
117
155
|
/** @internal Used by ChapterBuilder to push down shared config */
|
|
118
|
-
_inheritOptions(parentOpts: BuildOptions) {
|
|
156
|
+
_inheritOptions(parentOpts: BuildOptions, parentRawOpts?: BuildOptions) {
|
|
119
157
|
this.options = {
|
|
120
158
|
outDir:
|
|
121
|
-
this._rawOptions.outDir ?? parentOpts.outDir ?? this.options.outDir,
|
|
159
|
+
this._rawOptions.outDir ?? (parentRawOpts?.outDir || parentOpts.outDir) ?? this.options.outDir,
|
|
122
160
|
contentBase:
|
|
123
161
|
this._rawOptions.contentBase ??
|
|
124
|
-
|
|
162
|
+
parentRawOpts?.contentBase ??
|
|
125
163
|
this.options.contentBase,
|
|
126
164
|
theme: this._rawOptions.theme ?? parentOpts.theme ?? this.options.theme,
|
|
127
165
|
palette:
|
|
@@ -476,7 +514,7 @@ export function lesson(
|
|
|
476
514
|
title: string,
|
|
477
515
|
options: BuildOptions = {},
|
|
478
516
|
): LessonBuilder {
|
|
479
|
-
return new LessonBuilder(title, options);
|
|
517
|
+
return new LessonBuilder(title, options, getCallerDir());
|
|
480
518
|
}
|
|
481
519
|
|
|
482
520
|
function normalizeSimulationOptions(
|
|
@@ -594,18 +632,23 @@ export class ChapterBuilder {
|
|
|
594
632
|
private meta: ChapterMeta;
|
|
595
633
|
private lessonBuilders: LessonBuilder[] = [];
|
|
596
634
|
private options: BuildOptions;
|
|
635
|
+
private _rawOptions: BuildOptions;
|
|
636
|
+
|
|
637
|
+
constructor(title: string, options: BuildOptions = {}, callerDir?: string) {
|
|
638
|
+
this._rawOptions = options;
|
|
639
|
+
let slug = title
|
|
640
|
+
.toLowerCase()
|
|
641
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
642
|
+
.replace(/(^-|-$)/g, "");
|
|
643
|
+
if (!slug) slug = "chapter";
|
|
597
644
|
|
|
598
|
-
constructor(title: string, options: BuildOptions = {}) {
|
|
599
645
|
this.meta = {
|
|
600
646
|
title,
|
|
601
|
-
slug
|
|
602
|
-
.toLowerCase()
|
|
603
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
604
|
-
.replace(/(^-|-$)/g, ""),
|
|
647
|
+
slug,
|
|
605
648
|
};
|
|
606
649
|
this.options = {
|
|
607
650
|
outDir: options.outDir ?? "./out",
|
|
608
|
-
contentBase: options.contentBase ?? ".",
|
|
651
|
+
contentBase: options.contentBase ?? callerDir ?? ".",
|
|
609
652
|
theme: options.theme ?? "auto",
|
|
610
653
|
palette: options.palette ?? "ink",
|
|
611
654
|
strict: options.strict ?? true,
|
|
@@ -635,7 +678,7 @@ export class ChapterBuilder {
|
|
|
635
678
|
}
|
|
636
679
|
|
|
637
680
|
lesson(lessonBuilder: LessonBuilder): this {
|
|
638
|
-
lessonBuilder._inheritOptions(this.options);
|
|
681
|
+
lessonBuilder._inheritOptions(this.options, this._rawOptions);
|
|
639
682
|
lessonBuilder._setParentSlug(this.meta.slug);
|
|
640
683
|
this.lessonBuilders.push(lessonBuilder);
|
|
641
684
|
return this;
|
|
@@ -704,5 +747,5 @@ export function chapter(
|
|
|
704
747
|
title: string,
|
|
705
748
|
options: BuildOptions = {},
|
|
706
749
|
): ChapterBuilder {
|
|
707
|
-
return new ChapterBuilder(title, options);
|
|
750
|
+
return new ChapterBuilder(title, options, getCallerDir());
|
|
708
751
|
}
|
package/src/renderer/blocks.ts
CHANGED
|
@@ -114,9 +114,10 @@ function renderBlockInner(
|
|
|
114
114
|
|
|
115
115
|
case "code": {
|
|
116
116
|
const raw = resolveContent(block.src, options, "text"); // Could be file or inline
|
|
117
|
+
const isInlineCode = typeof block.src === "string" && (block.src.includes("\n") || block.src.includes(" "));
|
|
117
118
|
const lang =
|
|
118
119
|
block.lang ??
|
|
119
|
-
(typeof block.src === "string" && block.src.includes(".")
|
|
120
|
+
(typeof block.src === "string" && !isInlineCode && block.src.includes(".")
|
|
120
121
|
? (block.src.split(".").pop() ?? "")
|
|
121
122
|
: "");
|
|
122
123
|
let highlighted = escHtml(raw);
|
|
@@ -136,7 +137,7 @@ function renderBlockInner(
|
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
case "simulation": {
|
|
139
|
-
const propsJson =
|
|
140
|
+
const propsJson = escapeScriptJson(block.props ?? {});
|
|
140
141
|
const simSrc = resolveContent(block.src, options, "js");
|
|
141
142
|
const simConfig = { js: simSrc, loop: false, dependencies: block.dependencies };
|
|
142
143
|
return {
|
|
@@ -278,7 +279,11 @@ function renderBlockInner(
|
|
|
278
279
|
let quiz: QuizFile = { questions: [] };
|
|
279
280
|
const rawJson = resolveContent(block.src, options, "json");
|
|
280
281
|
try {
|
|
281
|
-
|
|
282
|
+
const trimmed = rawJson.trim();
|
|
283
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
284
|
+
throw new Error("Quiz file not found or invalid JSON format");
|
|
285
|
+
}
|
|
286
|
+
quiz = JSON.parse(trimmed);
|
|
282
287
|
} catch (e) {
|
|
283
288
|
const msg = e instanceof Error ? e.message : String(e);
|
|
284
289
|
if (options.strict !== false) {
|
package/src/renderer/utils.ts
CHANGED
|
@@ -17,6 +17,13 @@ function resolveContent(
|
|
|
17
17
|
): string {
|
|
18
18
|
if (src.includes("\n")) return src;
|
|
19
19
|
|
|
20
|
+
if (/^https?:\/\//.test(src)) {
|
|
21
|
+
if (options.strict !== false) {
|
|
22
|
+
throw new Error(`Remote URLs are not yet supported for content files: ${src}`);
|
|
23
|
+
}
|
|
24
|
+
return src;
|
|
25
|
+
}
|
|
26
|
+
|
|
20
27
|
const isLikelyFilePath =
|
|
21
28
|
(expectedType !== "text" && src.endsWith(`.${expectedType}`)) ||
|
|
22
29
|
src.startsWith("/") ||
|
|
@@ -45,9 +52,14 @@ function resolveContent(
|
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
function resolveAssetSrc(src: string, options: BuildOptions): string {
|
|
48
|
-
if (/^(https?:|data
|
|
55
|
+
if (/^(https?:|data:)/.test(src)) return src;
|
|
56
|
+
|
|
57
|
+
const isWebAbsolute = src.startsWith("/") && !fs.existsSync(src);
|
|
58
|
+
if (isWebAbsolute) return src;
|
|
49
59
|
|
|
50
|
-
const filePath = path.
|
|
60
|
+
const filePath = path.isAbsolute(src)
|
|
61
|
+
? src
|
|
62
|
+
: path.resolve(options.contentBase ?? ".", src);
|
|
51
63
|
if (!fs.existsSync(filePath)) {
|
|
52
64
|
if (options.strict !== false)
|
|
53
65
|
throw new Error(`Missing media asset: ${filePath}`);
|