mr-md 1.0.4 → 2.0.0-beta
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/README.md +10 -5
- package/dist/builder.d.ts +6 -20
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +38 -97
- package/dist/cli/dev.d.ts +2 -0
- package/dist/cli/dev.d.ts.map +1 -0
- package/dist/cli/dev.js +92 -0
- package/dist/cli/generate.d.ts +2 -0
- package/dist/cli/generate.d.ts.map +1 -0
- package/dist/cli/generate.js +171 -0
- package/dist/cli/init.d.ts +2 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +89 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +27 -0
- package/dist/client/app.js +282 -107
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/renderer/blocks.d.ts.map +1 -1
- package/dist/renderer/blocks.js +88 -16
- package/dist/renderer/html-neo.d.ts +7 -0
- package/dist/renderer/html-neo.d.ts.map +1 -0
- package/dist/renderer/html-neo.js +173 -0
- package/dist/renderer/html.d.ts.map +1 -1
- package/dist/renderer/html.js +36 -7
- package/dist/renderer/index-neo.d.ts +4 -0
- package/dist/renderer/index-neo.d.ts.map +1 -0
- package/dist/renderer/index-neo.js +469 -0
- package/dist/renderer/index.d.ts +1 -2
- package/dist/renderer/index.d.ts.map +1 -1
- package/dist/renderer/index.js +29 -379
- package/dist/renderer/markdown.d.ts +1 -1
- package/dist/renderer/markdown.d.ts.map +1 -1
- package/dist/renderer/markdown.js +3 -3
- package/dist/renderer/utils.d.ts +1 -1
- package/dist/renderer/utils.d.ts.map +1 -1
- package/dist/renderer/utils.js +41 -34
- package/dist/styles/theme-neo.css +1369 -0
- package/dist/styles/theme.css +412 -127
- package/dist/types.d.ts +8 -10
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -7
- package/src/builder.ts +49 -125
- package/src/cli/dev.ts +102 -0
- package/src/cli/generate.ts +191 -0
- package/src/cli/init.ts +97 -0
- package/src/cli.ts +29 -0
- package/src/client/app.js +282 -107
- package/src/index.ts +1 -1
- package/src/renderer/blocks.ts +89 -15
- package/src/renderer/html.ts +36 -7
- package/src/renderer/index.ts +30 -394
- package/src/renderer/markdown.ts +3 -2
- package/src/renderer/utils.ts +43 -36
- package/src/styles/theme.css +412 -127
- package/src/types.ts +8 -12
package/src/renderer/blocks.ts
CHANGED
|
@@ -62,8 +62,8 @@ function renderBlockInner(
|
|
|
62
62
|
switch (block.type) {
|
|
63
63
|
case "heading": {
|
|
64
64
|
const md = resolveContent(block.src, options, "md");
|
|
65
|
-
const { html, title
|
|
66
|
-
const label = block.title || title ||
|
|
65
|
+
const { html, title } = mdToHtml(md);
|
|
66
|
+
const label = block.title || title || (typeof block.src === "string" && !block.src.includes(".md") ? block.src : "Heading");
|
|
67
67
|
const id = `heading-${idx}`;
|
|
68
68
|
return {
|
|
69
69
|
html: `<section id="${id}" class="bk-section bk-heading">${html}</section>`,
|
|
@@ -77,15 +77,15 @@ function renderBlockInner(
|
|
|
77
77
|
const navItems: NavItem[] = headings.map(h => ({
|
|
78
78
|
id: h.id,
|
|
79
79
|
label: h.text,
|
|
80
|
-
kind: h.level ===
|
|
80
|
+
kind: h.level === 1 ? "heading" : "section"
|
|
81
81
|
}));
|
|
82
82
|
return { html: `<div class="bk-markdown">${html}</div>`, navItems: navItems.length > 0 ? navItems : undefined };
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
case "section": {
|
|
86
86
|
const md = resolveContent(block.src, options, "md");
|
|
87
|
-
const { html, title
|
|
88
|
-
const label = block.label || title ||
|
|
87
|
+
const { html, title } = mdToHtml(md);
|
|
88
|
+
const label = block.label || title || (typeof block.src === "string" && !block.src.includes(".md") ? block.src : "Section");
|
|
89
89
|
const id = `section-${idx}`;
|
|
90
90
|
return {
|
|
91
91
|
html: `<section id="${id}" class="bk-section bk-subsection">${html}</section>`,
|
|
@@ -145,6 +145,8 @@ function renderBlockInner(
|
|
|
145
145
|
const propsJson = escapeScriptJson(block.props ?? {});
|
|
146
146
|
const simSrc = resolveContent(block.src, options, "js");
|
|
147
147
|
const simConfig = { js: simSrc, loop: false, dependencies: block.dependencies };
|
|
148
|
+
const id = `sim-${idx}`;
|
|
149
|
+
const label = block.label || "Interactive Simulation";
|
|
148
150
|
return {
|
|
149
151
|
html: blockChrome(
|
|
150
152
|
block.controls === "observe" ? "Simulation" : "Interactive Lab",
|
|
@@ -157,12 +159,16 @@ function renderBlockInner(
|
|
|
157
159
|
</div>
|
|
158
160
|
<iframe srcdoc="${iframeDoc(simSrc, propsJson, false, block.dependencies)}"
|
|
159
161
|
sandbox="allow-scripts"
|
|
162
|
+
loading="lazy"
|
|
160
163
|
style="width:100%;height:100%;border:none;display:block;">
|
|
161
164
|
</iframe>
|
|
162
165
|
</div>
|
|
163
166
|
<script type="application/json" class="bk-sim-config">${escapeScriptJson(simConfig)}</script>`,
|
|
164
167
|
block.accent ?? "blue",
|
|
168
|
+
true,
|
|
169
|
+
id
|
|
165
170
|
),
|
|
171
|
+
navItems: [{ id, label, kind: "simulation" }],
|
|
166
172
|
};
|
|
167
173
|
}
|
|
168
174
|
|
|
@@ -173,12 +179,13 @@ function renderBlockInner(
|
|
|
173
179
|
"Animation",
|
|
174
180
|
block.label,
|
|
175
181
|
block.caption,
|
|
176
|
-
`<div class="bk-embed-frame bk-embed-interactive">
|
|
182
|
+
`<div class="bk-embed-frame bk-embed-interactive" data-is-animation="true">
|
|
177
183
|
<div class="bk-embed-overlay" tabindex="0" role="button" aria-label="Activate interactive animation">
|
|
178
184
|
<span class="bk-embed-overlay-text">Click to interact</span>
|
|
179
185
|
</div>
|
|
180
186
|
<iframe srcdoc="${iframeDoc(animSrc, "{}", block.loop)}"
|
|
181
187
|
sandbox="allow-scripts"
|
|
188
|
+
loading="lazy"
|
|
182
189
|
style="width:100%;height:100%;border:none;display:block;">
|
|
183
190
|
</iframe>
|
|
184
191
|
</div>`,
|
|
@@ -288,8 +295,7 @@ function renderBlockInner(
|
|
|
288
295
|
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
289
296
|
throw new Error("Quiz file not found or invalid JSON format");
|
|
290
297
|
}
|
|
291
|
-
|
|
292
|
-
quiz = Array.isArray(parsed) ? { questions: parsed } : parsed;
|
|
298
|
+
quiz = JSON.parse(trimmed);
|
|
293
299
|
} catch (e) {
|
|
294
300
|
const msg = e instanceof Error ? e.message : String(e);
|
|
295
301
|
if (options.strict !== false) {
|
|
@@ -352,7 +358,7 @@ function renderQuestion(q: QuizQuestion, quizId: string, qi: number): string {
|
|
|
352
358
|
|
|
353
359
|
// Wraps a JS string in a minimal iframe document
|
|
354
360
|
function iframeDoc(js: string, props: string, loop?: boolean, dependencies?: string[]): string {
|
|
355
|
-
const scriptTags = (dependencies ?? []).map((url) => `<script src="${escAttr(url)}"></script>`).join("
|
|
361
|
+
const scriptTags = (dependencies ?? []).map((url) => `<script src="${escAttr(url)}"></script>`).join("\\n");
|
|
356
362
|
const doc = `<!DOCTYPE html><html><head>
|
|
357
363
|
${scriptTags}
|
|
358
364
|
<style>
|
|
@@ -366,6 +372,26 @@ ${scriptTags}
|
|
|
366
372
|
window.__simProps=${props};
|
|
367
373
|
window.__loop=${loop ?? false};
|
|
368
374
|
window.bkSetupCalled = false;
|
|
375
|
+
window.__bkTheme = { colors: {}, theme: "light", palette: "ink", ui: "standard" };
|
|
376
|
+
window.bkColor = function(name) { return window.__bkTheme.colors[name] || "#000000"; };
|
|
377
|
+
window.bkUi = function() { return window.__bkTheme.ui; };
|
|
378
|
+
window.bkThemeMode = function() {
|
|
379
|
+
const rootTheme = window.__bkTheme.theme;
|
|
380
|
+
if (rootTheme === "auto") {
|
|
381
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? "dark" : "light";
|
|
382
|
+
}
|
|
383
|
+
return rootTheme;
|
|
384
|
+
};
|
|
385
|
+
window.addEventListener("message", function(e) {
|
|
386
|
+
if (e.data && e.data.type === "bk:theme-sync") {
|
|
387
|
+
window.__bkTheme = e.data.state;
|
|
388
|
+
window.dispatchEvent(new CustomEvent("bk:theme-changed", { detail: window.__bkTheme }));
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
if (window.parent && window.parent !== window) {
|
|
392
|
+
window.parent.postMessage({ type: "bk:request-theme" }, "*");
|
|
393
|
+
}
|
|
394
|
+
|
|
369
395
|
window.bkCanvasPoint = function(event, canvas) {
|
|
370
396
|
const c = canvas || event.currentTarget || event.target;
|
|
371
397
|
const rect = c.getBoundingClientRect();
|
|
@@ -416,19 +442,67 @@ window.bkSetup = function(requestedW, requestedH, loopFn) {
|
|
|
416
442
|
if (!canvas) return;
|
|
417
443
|
const ctx = canvas.getContext("2d");
|
|
418
444
|
|
|
445
|
+
let loopId = null;
|
|
446
|
+
let cachedFit = window.bkFitCanvas(canvas, requestedW, requestedH);
|
|
447
|
+
|
|
419
448
|
function loop() {
|
|
420
|
-
const fit = window.bkFitCanvas(canvas, requestedW, requestedH);
|
|
421
449
|
if (window.innerWidth >= 32 && window.innerHeight >= 32) {
|
|
422
450
|
ctx.save();
|
|
423
|
-
ctx.scale(
|
|
451
|
+
ctx.scale(cachedFit.scale, cachedFit.scale);
|
|
424
452
|
|
|
425
|
-
loopFn(ctx,
|
|
453
|
+
loopFn(ctx, cachedFit.width, cachedFit.height);
|
|
426
454
|
|
|
427
455
|
ctx.restore();
|
|
428
456
|
}
|
|
429
|
-
|
|
457
|
+
if (window.__loop) {
|
|
458
|
+
loopId = requestAnimationFrame(loop);
|
|
459
|
+
} else {
|
|
460
|
+
loopId = null;
|
|
461
|
+
}
|
|
430
462
|
}
|
|
431
|
-
|
|
463
|
+
|
|
464
|
+
function initDraw() {
|
|
465
|
+
if (window.innerWidth >= 32 && window.innerHeight >= 32) {
|
|
466
|
+
cachedFit = window.bkFitCanvas(canvas, requestedW, requestedH);
|
|
467
|
+
loop();
|
|
468
|
+
} else {
|
|
469
|
+
requestAnimationFrame(initDraw);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
initDraw();
|
|
473
|
+
|
|
474
|
+
window.addEventListener("resize", () => {
|
|
475
|
+
cachedFit = window.bkFitCanvas(canvas, requestedW, requestedH);
|
|
476
|
+
if (!window.__loop && window.innerWidth >= 32 && window.innerHeight >= 32) {
|
|
477
|
+
if (!loopId) {
|
|
478
|
+
ctx.save();
|
|
479
|
+
ctx.scale(cachedFit.scale, cachedFit.scale);
|
|
480
|
+
loopFn(ctx, cachedFit.width, cachedFit.height);
|
|
481
|
+
ctx.restore();
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
window.addEventListener("bk:theme-changed", () => {
|
|
487
|
+
if (!window.__loop && window.innerWidth >= 32 && window.innerHeight >= 32) {
|
|
488
|
+
if (!loopId) {
|
|
489
|
+
ctx.save();
|
|
490
|
+
ctx.scale(cachedFit.scale, cachedFit.scale);
|
|
491
|
+
loopFn(ctx, cachedFit.width, cachedFit.height);
|
|
492
|
+
ctx.restore();
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
window.addEventListener("message", (event) => {
|
|
498
|
+
if (!event.data) return;
|
|
499
|
+
if (event.data.type === "bk:play") {
|
|
500
|
+
window.__loop = true;
|
|
501
|
+
if (!loopId) loopId = requestAnimationFrame(loop);
|
|
502
|
+
} else if (event.data.type === "bk:pause") {
|
|
503
|
+
window.__loop = false; // will stop at next frame
|
|
504
|
+
}
|
|
505
|
+
});
|
|
432
506
|
};
|
|
433
507
|
|
|
434
508
|
window.addEventListener("message", (event) => {
|
|
@@ -445,9 +519,9 @@ try {
|
|
|
445
519
|
if (!window.bkSetupCalled) {
|
|
446
520
|
function fallbackScale() {
|
|
447
521
|
window.bkFitCanvas(document.getElementById("c"), 800, 500, { bitmap: false });
|
|
448
|
-
requestAnimationFrame(fallbackScale);
|
|
449
522
|
}
|
|
450
523
|
fallbackScale();
|
|
524
|
+
window.addEventListener("resize", fallbackScale);
|
|
451
525
|
}
|
|
452
526
|
</script>
|
|
453
527
|
</body></html>`;
|
package/src/renderer/html.ts
CHANGED
|
@@ -16,7 +16,9 @@ function renderNavItem(item: NavItem): string {
|
|
|
16
16
|
? "bk-nav-heading"
|
|
17
17
|
: item.kind === "quiz"
|
|
18
18
|
? "bk-nav-quiz"
|
|
19
|
-
: "
|
|
19
|
+
: item.kind === "simulation"
|
|
20
|
+
? "bk-nav-sim"
|
|
21
|
+
: "bk-nav-sub";
|
|
20
22
|
return `<a href="#${item.id}" class="bk-nav-item ${kindClass}" data-id="${item.id}">${escHtml(item.label)}</a>`;
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -46,24 +48,37 @@ function renderPage(
|
|
|
46
48
|
bodyHtml: string,
|
|
47
49
|
opts: BuildOptions,
|
|
48
50
|
): string {
|
|
49
|
-
const theme = opts.theme ?? "
|
|
50
|
-
const schemeAttr =
|
|
51
|
+
const theme = opts.theme ?? "light";
|
|
52
|
+
const schemeAttr = `data-theme="${theme}"`;
|
|
51
53
|
const preset = opts.preset ?? {};
|
|
52
54
|
const layout = preset.layout ?? "lesson";
|
|
53
55
|
const density = preset.density ?? "comfortable";
|
|
54
56
|
const tone = preset.tone ?? "scholarly";
|
|
55
57
|
const palette = opts.palette ?? "ink";
|
|
58
|
+
const ui = opts.ui ?? "standard";
|
|
56
59
|
const navHtml = navItems.map(renderNavItem).join("\n");
|
|
57
60
|
const endNavHtml = renderEndNav(lesson);
|
|
58
61
|
|
|
59
62
|
return `<!DOCTYPE html>
|
|
60
|
-
<html lang="en" data-palette="${palette}" ${schemeAttr}>
|
|
63
|
+
<html lang="en" data-palette="${palette}" data-ui="${ui}" ${schemeAttr}>
|
|
61
64
|
<head>
|
|
65
|
+
<script>
|
|
66
|
+
(function() {
|
|
67
|
+
var t = localStorage.getItem("bk-theme");
|
|
68
|
+
var p = localStorage.getItem("bk-palette");
|
|
69
|
+
var u = localStorage.getItem("bk-ui");
|
|
70
|
+
var root = document.documentElement;
|
|
71
|
+
if (t) root.setAttribute("data-theme", t);
|
|
72
|
+
if (p) root.setAttribute("data-palette", p === "green" ? "field" : p);
|
|
73
|
+
if (u) root.setAttribute("data-ui", u);
|
|
74
|
+
})();
|
|
75
|
+
</script>
|
|
62
76
|
<meta charset="UTF-8">
|
|
63
77
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
64
78
|
<title>${escHtml(lesson.meta.title)}</title>
|
|
65
79
|
${lesson.meta.description ? `<meta name="description" content="${escHtml(lesson.meta.description)}">` : ""}
|
|
66
80
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.47/dist/katex.min.css">
|
|
81
|
+
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,650;9..144,760&family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&family=Archivo:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&family=Syne:wght@600;700;800&family=Playfair+Display:ital,wght@0,400..700;1,400..700&family=Lora:ital,wght@0,400..700;1,400..700&display=swap" rel="stylesheet">
|
|
67
82
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.11.1/styles/github-dark.min.css">
|
|
68
83
|
${opts.head ?? ""}
|
|
69
84
|
<style>
|
|
@@ -94,12 +109,12 @@ ${pageCSS()}
|
|
|
94
109
|
<button type="button" class="bk-segment-btn ${theme === "light" ? "active" : ""}" data-theme="light" title="Light" aria-label="Light theme">
|
|
95
110
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>
|
|
96
111
|
</button>
|
|
112
|
+
<button type="button" class="bk-segment-btn ${theme === "auto" ? "active" : (!theme ? "active" : "")}" data-theme="auto" title="System" aria-label="System theme">
|
|
113
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line></svg>
|
|
114
|
+
</button>
|
|
97
115
|
<button type="button" class="bk-segment-btn ${theme === "dark" ? "active" : ""}" data-theme="dark" title="Dark" aria-label="Dark theme">
|
|
98
116
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>
|
|
99
117
|
</button>
|
|
100
|
-
<button type="button" class="bk-segment-btn ${theme === "auto" ? "active" : ""}" data-theme="auto" title="System" aria-label="System theme">
|
|
101
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line></svg>
|
|
102
|
-
</button>
|
|
103
118
|
</div>
|
|
104
119
|
</div>
|
|
105
120
|
<div class="bk-theme-row">
|
|
@@ -116,6 +131,20 @@ ${pageCSS()}
|
|
|
116
131
|
</button>
|
|
117
132
|
</div>
|
|
118
133
|
</div>
|
|
134
|
+
<div class="bk-theme-row">
|
|
135
|
+
<span>UI</span>
|
|
136
|
+
<div class="bk-segmented-control" id="bk-ui-icons">
|
|
137
|
+
<button type="button" class="bk-segment-btn ${ui === 'standard' ? 'active' : ''}" data-ui="standard" title="Standard" aria-label="Standard UI">
|
|
138
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="4" ry="4"></rect><line x1="9" y1="3" x2="9" y2="21"></line></svg>
|
|
139
|
+
</button>
|
|
140
|
+
<button type="button" class="bk-segment-btn ${ui === 'neo' ? 'active' : ''}" data-ui="neo" title="Neo Brutalist" aria-label="Neo UI">
|
|
141
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><rect x="3" y="3" width="18" height="18"></rect><path d="M3 10h18"></path><path d="M10 10v11"></path></svg>
|
|
142
|
+
</button>
|
|
143
|
+
<button type="button" class="bk-segment-btn ${ui === 'playful' ? 'active' : ''}" data-ui="playful" title="Playful" aria-label="Playful UI">
|
|
144
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="6" ry="6"></rect><circle cx="8.5" cy="8.5" r="1.5" fill="currentColor"></circle><circle cx="15.5" cy="15.5" r="1.5" fill="currentColor"></circle></svg>
|
|
145
|
+
</button>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
119
148
|
</div>
|
|
120
149
|
</div>
|
|
121
150
|
</div>
|