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.
Files changed (58) hide show
  1. package/README.md +10 -5
  2. package/dist/builder.d.ts +6 -20
  3. package/dist/builder.d.ts.map +1 -1
  4. package/dist/builder.js +38 -97
  5. package/dist/cli/dev.d.ts +2 -0
  6. package/dist/cli/dev.d.ts.map +1 -0
  7. package/dist/cli/dev.js +92 -0
  8. package/dist/cli/generate.d.ts +2 -0
  9. package/dist/cli/generate.d.ts.map +1 -0
  10. package/dist/cli/generate.js +171 -0
  11. package/dist/cli/init.d.ts +2 -0
  12. package/dist/cli/init.d.ts.map +1 -0
  13. package/dist/cli/init.js +89 -0
  14. package/dist/cli.d.ts +3 -0
  15. package/dist/cli.d.ts.map +1 -0
  16. package/dist/cli.js +27 -0
  17. package/dist/client/app.js +282 -107
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1 -1
  21. package/dist/renderer/blocks.d.ts.map +1 -1
  22. package/dist/renderer/blocks.js +88 -16
  23. package/dist/renderer/html-neo.d.ts +7 -0
  24. package/dist/renderer/html-neo.d.ts.map +1 -0
  25. package/dist/renderer/html-neo.js +173 -0
  26. package/dist/renderer/html.d.ts.map +1 -1
  27. package/dist/renderer/html.js +36 -7
  28. package/dist/renderer/index-neo.d.ts +4 -0
  29. package/dist/renderer/index-neo.d.ts.map +1 -0
  30. package/dist/renderer/index-neo.js +469 -0
  31. package/dist/renderer/index.d.ts +1 -2
  32. package/dist/renderer/index.d.ts.map +1 -1
  33. package/dist/renderer/index.js +29 -379
  34. package/dist/renderer/markdown.d.ts +1 -1
  35. package/dist/renderer/markdown.d.ts.map +1 -1
  36. package/dist/renderer/markdown.js +3 -3
  37. package/dist/renderer/utils.d.ts +1 -1
  38. package/dist/renderer/utils.d.ts.map +1 -1
  39. package/dist/renderer/utils.js +41 -34
  40. package/dist/styles/theme-neo.css +1369 -0
  41. package/dist/styles/theme.css +412 -127
  42. package/dist/types.d.ts +8 -10
  43. package/dist/types.d.ts.map +1 -1
  44. package/package.json +8 -7
  45. package/src/builder.ts +49 -125
  46. package/src/cli/dev.ts +102 -0
  47. package/src/cli/generate.ts +191 -0
  48. package/src/cli/init.ts +97 -0
  49. package/src/cli.ts +29 -0
  50. package/src/client/app.js +282 -107
  51. package/src/index.ts +1 -1
  52. package/src/renderer/blocks.ts +89 -15
  53. package/src/renderer/html.ts +36 -7
  54. package/src/renderer/index.ts +30 -394
  55. package/src/renderer/markdown.ts +3 -2
  56. package/src/renderer/utils.ts +43 -36
  57. package/src/styles/theme.css +412 -127
  58. package/src/types.ts +8 -12
@@ -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, headings } = mdToHtml(md);
66
- const label = block.title || title || headings[0]?.text || (typeof block.src === "string" && !block.src.includes(".md") ? block.src.replace(/^#+\s*/, '') : "Heading");
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 === 2 ? "heading" : "section"
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, headings } = mdToHtml(md);
88
- const label = block.label || title || headings[0]?.text || (typeof block.src === "string" && !block.src.includes(".md") ? block.src.replace(/^#+\s*/, '') : "Section");
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
- const parsed = JSON.parse(trimmed);
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("\n");
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(fit.scale, fit.scale);
451
+ ctx.scale(cachedFit.scale, cachedFit.scale);
424
452
 
425
- loopFn(ctx, fit.width, fit.height);
453
+ loopFn(ctx, cachedFit.width, cachedFit.height);
426
454
 
427
455
  ctx.restore();
428
456
  }
429
- requestAnimationFrame(loop);
457
+ if (window.__loop) {
458
+ loopId = requestAnimationFrame(loop);
459
+ } else {
460
+ loopId = null;
461
+ }
430
462
  }
431
- loop();
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>`;
@@ -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
- : "bk-nav-sub";
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 ?? "auto";
50
- const schemeAttr = theme === "auto" ? "" : `data-theme="${theme}"`;
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>