mr-md 2.0.0-beta → 2.1.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Mr Markdown
2
2
 
3
- Mr Markdown is an opinionated TypeScript SDK for building interactive, single-file learning pages. It is designed for lessons that mix prose, LaTeX, simulations, media, video, and quizzes without making every author build layout and interaction chrome from scratch.
3
+ Mr Markdown is an opinionated TypeScript SDK for building interactive learning pages. It is designed for lessons that mix prose, LaTeX, simulations, media, video, and quizzes without making every author build layout and interaction chrome from scratch.
4
4
 
5
5
  ## Documentation
6
6
 
@@ -1 +1 @@
1
- {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"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"}
package/dist/builder.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
3
  import { render, renderChapter } from "./renderer/index.js";
4
+ import { fileURLToPath } from "url";
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
4
7
  function getCallerDir() {
5
8
  const err = new Error();
6
9
  const stack = err.stack?.split("\n");
@@ -16,7 +19,7 @@ function getCallerDir() {
16
19
  p = p.replace(/^file:\/\//, "");
17
20
  }
18
21
  if (p.startsWith("/") && p[2] === ":") {
19
- p = p.substring(1); // Handle Windows paths like /C:/
22
+ p = p.substring(1);
20
23
  }
21
24
  if (!builderFilePath) {
22
25
  builderFilePath = p;
@@ -30,6 +33,51 @@ function getCallerDir() {
30
33
  }
31
34
  return undefined;
32
35
  }
36
+ function copyAssets(outDir) {
37
+ const assetsDir = path.join(outDir, "assets");
38
+ if (!fs.existsSync(assetsDir))
39
+ fs.mkdirSync(assetsDir, { recursive: true });
40
+ const srcStylesDir = path.join(__dirname, "styles");
41
+ const srcClientDir = path.join(__dirname, "client");
42
+ const fallbackStylesDir = path.join(__dirname, "../src/styles");
43
+ const fallbackClientDir = path.join(__dirname, "../src/client");
44
+ const copyDir = (src, dest) => {
45
+ if (!fs.existsSync(src))
46
+ return;
47
+ for (const file of fs.readdirSync(src)) {
48
+ fs.copyFileSync(path.join(src, file), path.join(dest, file));
49
+ }
50
+ };
51
+ if (fs.existsSync(srcStylesDir))
52
+ copyDir(srcStylesDir, assetsDir);
53
+ else
54
+ copyDir(fallbackStylesDir, assetsDir);
55
+ if (fs.existsSync(srcClientDir))
56
+ copyDir(srcClientDir, assetsDir);
57
+ else
58
+ copyDir(fallbackClientDir, assetsDir);
59
+ }
60
+ function mergeOptions(options, callerDir) {
61
+ const merged = {
62
+ outDir: options.outDir ?? (callerDir ? path.join(callerDir, "out") : path.join(process.cwd(), "out")),
63
+ contentBase: options.contentBase ?? callerDir ?? process.cwd(),
64
+ theme: options.theme ?? "auto",
65
+ palette: options.palette ?? "ink",
66
+ strict: options.strict ?? process.env.NODE_ENV !== "development",
67
+ standalone: options.standalone ?? true,
68
+ };
69
+ for (const [k, v] of Object.entries(options)) {
70
+ if (v !== undefined && k !== "preset") {
71
+ merged[k] = v;
72
+ }
73
+ }
74
+ merged.preset = {
75
+ layout: options.preset?.layout ?? "lesson",
76
+ density: options.preset?.density ?? "comfortable",
77
+ tone: options.preset?.tone ?? "scholarly",
78
+ };
79
+ return merged;
80
+ }
33
81
  // ─── LessonBuilder ────────────────────────────────────────────────────────────
34
82
  export class LessonBuilder {
35
83
  meta;
@@ -48,20 +96,7 @@ export class LessonBuilder {
48
96
  title,
49
97
  slug,
50
98
  };
51
- this.options = {
52
- outDir: options.outDir ?? (callerDir ? path.join(callerDir, "out") : "./out"),
53
- contentBase: options.contentBase ?? callerDir ?? ".",
54
- theme: options.theme ?? "auto",
55
- palette: options.palette ?? "ink",
56
- strict: options.strict ?? process.env.NODE_ENV !== "development",
57
- preset: {
58
- layout: "lesson",
59
- density: "comfortable",
60
- tone: "scholarly",
61
- ...options.preset,
62
- },
63
- ...options,
64
- };
99
+ this.options = mergeOptions(options, callerDir);
65
100
  }
66
101
  // ── Meta setters ────────────────────────────────────────────────────────────
67
102
  /**
@@ -377,6 +412,9 @@ export class LessonBuilder {
377
412
  fs.mkdirSync(outDir, { recursive: true });
378
413
  const outPath = path.join(outDir, `${this.meta.slug}.html`);
379
414
  fs.writeFileSync(outPath, html, "utf-8");
415
+ if (this.options.standalone === false) {
416
+ copyAssets(outDir);
417
+ }
380
418
  const relPath = path.relative(process.cwd(), outPath);
381
419
  console.log(` ✓ Built lesson (${this.blocks.length} blocks) → ${relPath}`);
382
420
  return outPath;
@@ -504,20 +542,7 @@ export class ChapterBuilder {
504
542
  title,
505
543
  slug,
506
544
  };
507
- this.options = {
508
- outDir: options.outDir ?? (callerDir ? path.join(callerDir, "out") : "./out"),
509
- contentBase: options.contentBase ?? callerDir ?? ".",
510
- theme: options.theme ?? "auto",
511
- palette: options.palette ?? "ink",
512
- strict: options.strict ?? process.env.NODE_ENV !== "development",
513
- preset: {
514
- layout: "lesson",
515
- density: "comfortable",
516
- tone: "scholarly",
517
- ...options.preset,
518
- },
519
- ...options,
520
- };
545
+ this.options = mergeOptions(options, callerDir);
521
546
  }
522
547
  slug(slug) {
523
548
  this.meta.slug = slug;
@@ -565,6 +590,9 @@ export class ChapterBuilder {
565
590
  fs.mkdirSync(outDir, { recursive: true });
566
591
  const outPath = path.join(outDir, `${this.meta.slug}.html`);
567
592
  fs.writeFileSync(outPath, html, "utf-8");
593
+ if (this.options.standalone === false) {
594
+ copyAssets(outDir);
595
+ }
568
596
  const relPath = path.relative(process.cwd(), outPath);
569
597
  console.log(` ✓ Built chapter (${this.lessonBuilders.length} lessons) → ${relPath}`);
570
598
  return outPath;
@@ -15,11 +15,10 @@ function bkSimDoc(js, props, loop, dependencies) {
15
15
  ";window.bkSetupCalled=false;" +
16
16
  'window.bkCanvasPoint=function(e,c){const r=(c||e.currentTarget||e.target).getBoundingClientRect(),w=(c&&c.__bkLogicalW)||800,h=(c&&c.__bkLogicalH)||500;return{x:(e.clientX-r.left)*w/r.width,y:(e.clientY-r.top)*h/r.height}};' +
17
17
  'window.bkFitCanvas=function(c,reqW,reqH,o){if(!c)return{scale:1,width:reqW,height:reqH,cssScale:1};const d=window.devicePixelRatio||1;const w=reqW;const h=reqH;c.__bkLogicalW=w;c.__bkLogicalH=h;c.style.width=w+"px";c.style.height=h+"px";c.style.position="relative";c.style.left="auto";c.style.top="auto";c.style.transformOrigin="center center";const sx=window.innerWidth/w,sy=window.innerHeight/h,cssS=Math.max(sx,sy);c.style.transform="scale("+cssS+")";const pw=Math.max(1,Math.round(w*d)),ph=Math.max(1,Math.round(h*d));if(!o||o.bitmap!==false){if(c.width!==pw||c.height!==ph){c.width=pw;c.height=ph}}return{scale:d,width:w,height:h,cssScale:cssS}};' +
18
- 'window.bkSetup=function(w,h,f){window.bkSetupCalled=true;const c=document.getElementById("c");if(!c)return;const ctx=c.getContext("2d");let loopId=null;let fit=window.bkFitCanvas(c,w,h);function l(){if(window.innerWidth>=32&&window.innerHeight>=32){ctx.save();ctx.scale(fit.scale,fit.scale);f(ctx,fit.width,fit.height);ctx.restore()}if(window.__loop){loopId=requestAnimationFrame(l)}else{loopId=null}}function i(){if(window.innerWidth>=32&&window.innerHeight>=32){fit=window.bkFitCanvas(c,w,h);l()}else{requestAnimationFrame(i)}}i();window.addEventListener("resize",function(){fit=window.bkFitCanvas(c,w,h);if(!window.__loop&&window.innerWidth>=32&&window.innerHeight>=32&&!loopId){ctx.save();ctx.scale(fit.scale,fit.scale);f(ctx,fit.width,fit.height);ctx.restore()}});window.addEventListener("message",function(event){if(!event.data)return;if(event.data.type==="bk:play"){window.__loop=true;if(!loopId)loopId=requestAnimationFrame(l)}else if(event.data.type==="bk:pause"){window.__loop=false}});};' +
19
- 'window.addEventListener("message",function(event){if(!event.data||event.data.type!=="bk:set-props")return;window.__simProps=Object.assign({},window.__simProps,event.data.props);window.dispatchEvent(new CustomEvent("bk:props",{detail:window.__simProps}));});' +
18
+ 'window.bkSetup=function(w,h,f){window.bkSetupCalled=true;const c=document.getElementById("c");if(!c)return;const ctx=c.getContext("2d");let loopId=null;let fit=window.bkFitCanvas(c,w,h);function l(){if(window.innerWidth>=32&&window.innerHeight>=32){ctx.save();ctx.scale(fit.scale,fit.scale);f(ctx,fit.width,fit.height);ctx.restore()}if(window.__loop){loopId=requestAnimationFrame(l)}else{loopId=null}}function i(){if(window.innerWidth>=32&&window.innerHeight>=32){fit=window.bkFitCanvas(c,w,h);l()}else{requestAnimationFrame(i)}}i();window.addEventListener("resize",function(){fit=window.bkFitCanvas(c,w,h);if(!window.__loop&&window.innerWidth>=32&&window.innerHeight>=32&&!loopId){ctx.save();ctx.scale(fit.scale,fit.scale);f(ctx,fit.width,fit.height);ctx.restore()}});window.addEventListener("message",function(event){if(event.source!==window.parent)return;if(!event.data)return;if(event.data.type==="bk:play"){window.__loop=true;if(!loopId)loopId=requestAnimationFrame(l)}else if(event.data.type==="bk:pause"){window.__loop=false}else if(event.data.type==="bk:set-props"){window.__simProps=Object.assign({},window.__simProps,event.data.props);window.dispatchEvent(new CustomEvent("bk:props",{detail:window.__simProps}));}});};' +
20
19
  "try{" +
21
20
  js +
22
- '}catch(e){console.error("Simulation Error:",e);document.body.innerHTML="<div style=\'padding: 20px; color: red; font-family: monospace;\'>Error: "+e.message+"</div>"}' +
21
+ '}catch(e){console.error("Simulation Error:",e);const d=document.createElement("div");d.style.cssText="padding:20px;color:red;font-family:monospace";d.textContent="Error: "+e.message;document.body.innerHTML="";document.body.appendChild(d);}' +
23
22
  "if(!window.bkSetupCalled){function fallbackScale(){window.bkFitCanvas(document.getElementById('c'),800,500,{bitmap:false});}fallbackScale();window.addEventListener('resize', fallbackScale);}" +
24
23
  "</" + "script></body></html>"
25
24
  );
@@ -76,27 +75,14 @@ function bkWireMaximizeControls() {
76
75
  }
77
76
 
78
77
  function bkWireInteractiveFrames() {
79
- const activate = (e) => {
78
+ const interactiveHandler = (e) => {
80
79
  const obj = e.target.closest?.(".bk-object");
81
- if (!obj) return;
82
- const frame = obj.querySelector(".bk-embed-interactive");
83
- if (frame) {
84
- frame.classList.add("is-interactive");
85
- const iframe = frame.querySelector("iframe");
86
- if (iframe && iframe.contentWindow) {
87
- iframe.contentWindow.postMessage({ type: "bk:play" }, "*");
88
- }
89
- }
90
- };
91
- document.addEventListener("pointerdown", activate, { passive: true });
92
- document.addEventListener("focusin", activate, { passive: true });
80
+ const activateFrame = obj ? obj.querySelector(".bk-embed-interactive") : null;
93
81
 
94
- const exitInteractive = (e) => {
95
82
  document
96
83
  .querySelectorAll(".bk-embed-interactive.is-interactive")
97
84
  .forEach((frame) => {
98
- const container = frame.closest(".bk-object") || frame;
99
- if (!container.contains(e.target)) {
85
+ if (frame !== activateFrame) {
100
86
  frame.classList.remove("is-interactive");
101
87
  const iframe = frame.querySelector("iframe");
102
88
  if (iframe && iframe.contentWindow) {
@@ -104,9 +90,17 @@ function bkWireInteractiveFrames() {
104
90
  }
105
91
  }
106
92
  });
93
+
94
+ if (activateFrame && !activateFrame.classList.contains("is-interactive")) {
95
+ activateFrame.classList.add("is-interactive");
96
+ const iframe = activateFrame.querySelector("iframe");
97
+ if (iframe && iframe.contentWindow) {
98
+ iframe.contentWindow.postMessage({ type: "bk:play" }, "*");
99
+ }
100
+ }
107
101
  };
108
- document.addEventListener("pointerdown", exitInteractive, { passive: true });
109
- document.addEventListener("focusin", exitInteractive, { passive: true });
102
+ document.addEventListener("pointerdown", interactiveHandler, { passive: true });
103
+ document.addEventListener("focusin", interactiveHandler, { passive: true });
110
104
 
111
105
  const obs = new IntersectionObserver((entries) => {
112
106
  entries.forEach((e) => {
@@ -192,6 +186,9 @@ function bkBroadcastTheme(targetWindow) {
192
186
  }
193
187
 
194
188
  window.addEventListener("message", function(e) {
189
+ const isValidSource = Array.from(document.querySelectorAll("iframe")).some(f => f.contentWindow === e.source);
190
+ if (!isValidSource) return;
191
+
195
192
  if (e.data && e.data.type === "bk:request-theme") {
196
193
  // Small delay to ensure CSS has applied if this happens right on load
197
194
  requestAnimationFrame(() => bkBroadcastTheme(e.source));
@@ -266,13 +263,16 @@ function bkWireThemeControls() {
266
263
 
267
264
  if (savedTheme) {
268
265
  updateThemeBtn(savedTheme);
266
+ root.setAttribute("data-theme", savedTheme);
269
267
  }
270
268
  if (savedPalette) {
271
269
  const normalizedPalette = savedPalette === "green" ? "field" : savedPalette;
272
270
  updatePaletteBtn(normalizedPalette);
271
+ root.setAttribute("data-palette", normalizedPalette);
273
272
  }
274
273
  if (savedUi) {
275
274
  updateUiBtn(savedUi);
275
+ root.setAttribute("data-ui", savedUi);
276
276
  }
277
277
 
278
278
  button &&
@@ -344,15 +344,44 @@ function bkWireThemeControls() {
344
344
  }
345
345
 
346
346
  // Quiz interaction
347
- // biome-ignore lint/correctness/noUnusedVariables: Used in generated HTML
348
347
  function bkAnswer(btn, qid) {
349
- // biome-ignore lint/correctness/noUnusedVariables: Kept for clarity
350
- const isCorrect = btn.dataset.correct === "true";
351
348
  const question = document.getElementById(qid);
349
+ if (!question) return;
350
+ const quiz = question.closest(".bk-quiz");
351
+ const dataEl = quiz ? quiz.querySelector(".bk-quiz-data") : null;
352
+ let isCorrect = false;
353
+
354
+ if (dataEl) {
355
+ try {
356
+ const answers = JSON.parse(dataEl.textContent || "[]");
357
+ // qid is format "quiz-IDX-qQI"
358
+ const match = qid.match(/-q(\d+)$/);
359
+ if (match) {
360
+ const qi = parseInt(match[1], 10);
361
+ const optIdx = parseInt(btn.dataset.optIdx, 10);
362
+ isCorrect = answers[qi] === optIdx;
363
+ }
364
+ } catch(e) {}
365
+ }
366
+
352
367
  question.querySelectorAll(".bk-opt").forEach((b) => {
353
- if (b.dataset.correct === "true") {
368
+ b.disabled = true; // Disable buttons for screen readers
369
+ const optIdx = parseInt(b.dataset.optIdx, 10);
370
+ // If we know the answer, highlight it
371
+ if (dataEl) {
372
+ try {
373
+ const answers = JSON.parse(dataEl.textContent || "[]");
374
+ const match = qid.match(/-q(\d+)$/);
375
+ if (match && answers[parseInt(match[1], 10)] === optIdx) {
376
+ b.classList.add("correct");
377
+ return;
378
+ }
379
+ } catch(e) {}
380
+ }
381
+
382
+ if (b === btn && isCorrect) {
354
383
  b.classList.add("correct");
355
- } else if (b === btn) {
384
+ } else if (b === btn && !isCorrect) {
356
385
  b.classList.add("wrong");
357
386
  } else {
358
387
  b.classList.add("disabled");
@@ -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,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAA;CAAE,CAuBxC;AAED,iBAAS,gBAAgB,CACxB,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,YAAY,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAA;CAAE,CAiRxC;AA2MD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,CAAC"}
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,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAA;CAAE,CAuBxC;AAED,iBAAS,gBAAgB,CACxB,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,YAAY,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAA;CAAE,CA4RxC;AAyMD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,CAAC"}
@@ -176,7 +176,17 @@ function renderBlockInner(block, idx, options) {
176
176
  loading="lazy"
177
177
  style="width:100%;height:100%;border:none;display:block;">
178
178
  </iframe>
179
- </div>`, "neutral"),
179
+ </div>
180
+ <script>
181
+ if (!window._bkYtBlurSetup) {
182
+ window._bkYtBlurSetup = true;
183
+ window.addEventListener('mousemove', function() {
184
+ if (document.activeElement && document.activeElement.tagName === 'IFRAME') {
185
+ document.activeElement.blur();
186
+ }
187
+ }, { passive: true });
188
+ }
189
+ </script>`, "neutral"),
180
190
  };
181
191
  }
182
192
  case "latex": {
@@ -191,7 +201,7 @@ function renderBlockInner(block, idx, options) {
191
201
  case "columns": {
192
202
  return {
193
203
  html: blockChrome("Columns", block.label, block.caption, `<div class="bk-columns" style="grid-template-columns:${block.columns
194
- .map((column) => column.width ?? "minmax(0, 1fr)")
204
+ .map((column) => escAttr(column.width ?? "minmax(0, 1fr)"))
195
205
  .join(" ")}">
196
206
  ${block.columns
197
207
  .map((column) => {
@@ -236,6 +246,7 @@ function renderBlockInner(block, idx, options) {
236
246
  <div class="bk-quiz-body">
237
247
  ${quiz.questions.map((q, qi) => renderQuestion(q, `quiz-${idx}`, qi)).join("\n")}
238
248
  </div>
249
+ <script type="application/json" class="bk-quiz-data">${escapeScriptJson(quiz.questions.map(q => q.answer))}</script>
239
250
  </div>`,
240
251
  navItems: [{
241
252
  id: `quiz-${idx}`,
@@ -254,7 +265,7 @@ function renderQuestion(q, quizId, qi) {
254
265
  const qid = `${quizId}-q${qi}`;
255
266
  const options = q.options
256
267
  .map((opt, oi) => `
257
- <button class="bk-opt" data-correct="${oi === q.answer}" onclick="bkAnswer(this,'${qid}')">
268
+ <button class="bk-opt" data-opt-idx="${oi}" onclick="bkAnswer(this,'${escAttr(qid)}')">
258
269
  <span class="bk-opt-dot"></span><span class="bk-opt-text">${mdInline(opt)}</span>
259
270
  </button>`)
260
271
  .join("");
@@ -426,7 +437,11 @@ try {
426
437
  ${js}
427
438
  } catch (e) {
428
439
  console.error("Simulation Error:", e);
429
- document.body.innerHTML = '<div style="padding:20px;color:red;font-family:monospace">Error: ' + e.message + '</div>';
440
+ const errDiv = document.createElement('div');
441
+ errDiv.style.cssText = "padding:20px;color:red;font-family:monospace";
442
+ errDiv.textContent = 'Error: ' + e.message;
443
+ document.body.innerHTML = '';
444
+ document.body.appendChild(errDiv);
430
445
  }
431
446
  if (!window.bkSetupCalled) {
432
447
  function fallbackScale() {
@@ -437,12 +452,6 @@ if (!window.bkSetupCalled) {
437
452
  }
438
453
  </script>
439
454
  </body></html>`;
440
- // We use double quotes for the srcdoc attribute, so we must escape them.
441
- return doc
442
- .replace(/&/g, "&amp;")
443
- .replace(/"/g, "&quot;")
444
- .replace(/'/g, "&#39;")
445
- .replace(/</g, "&lt;")
446
- .replace(/>/g, "&gt;");
455
+ return escAttr(doc);
447
456
  }
448
457
  export { blockChrome, renderBlock, renderBlockInner };
@@ -1,6 +1,7 @@
1
1
  import type { BuildOptions, Lesson } from "../types.js";
2
2
  import type { NavItem } from "./utils.js";
3
3
  declare function renderNavItem(item: NavItem): string;
4
+ export declare function renderLayout(title: string, description: string | undefined, navHtml: string, contentHtml: string, opts: BuildOptions, extraSidebar?: string): string;
4
5
  declare function renderPage(lesson: Lesson, navItems: NavItem[], bodyHtml: string, opts: BuildOptions): string;
5
6
  declare function pageCSS(): string;
6
7
  declare function clientScript(): string;
@@ -1 +1 @@
1
- {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../src/renderer/html.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAExD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAI1C,iBAAS,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAU5C;AAsBD,iBAAS,UAAU,CAClB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,OAAO,EAAE,EACnB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,YAAY,GAChB,MAAM,CA6HR;AAID,iBAAS,OAAO,IAAI,MAAM,CAKzB;AAID,iBAAS,YAAY,IAAI,MAAM,CAE9B;AAED,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../src/renderer/html.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAExD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAI1C,iBAAS,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAU5C;AAsBD,wBAAgB,YAAY,CAC3B,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,YAAY,EAClB,YAAY,GAAE,MAAW,GACvB,MAAM,CA6GR;AAED,iBAAS,UAAU,CAClB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,OAAO,EAAE,EACnB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,YAAY,GAChB,MAAM,CA8BR;AAID,iBAAS,OAAO,IAAI,MAAM,CAKzB;AAID,iBAAS,YAAY,IAAI,MAAM,CAE9B;AAED,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,CAAC"}
@@ -34,7 +34,7 @@ function renderEndNav(lesson) {
34
34
  ` : `<div class="bk-end-link" style="visibility:hidden"></div>`}
35
35
  </nav>`;
36
36
  }
37
- function renderPage(lesson, navItems, bodyHtml, opts) {
37
+ export function renderLayout(title, description, navHtml, contentHtml, opts, extraSidebar = "") {
38
38
  const theme = opts.theme ?? "light";
39
39
  const schemeAttr = `data-theme="${theme}"`;
40
40
  const preset = opts.preset ?? {};
@@ -43,8 +43,7 @@ function renderPage(lesson, navItems, bodyHtml, opts) {
43
43
  const tone = preset.tone ?? "scholarly";
44
44
  const palette = opts.palette ?? "ink";
45
45
  const ui = opts.ui ?? "standard";
46
- const navHtml = navItems.map(renderNavItem).join("\n");
47
- const endNavHtml = renderEndNav(lesson);
46
+ const safeFont = opts.font ? opts.font.replace(/[;{}<>\\]/g, "") : "";
48
47
  return `<!DOCTYPE html>
49
48
  <html lang="en" data-palette="${palette}" data-ui="${ui}" ${schemeAttr}>
50
49
  <head>
@@ -61,26 +60,21 @@ function renderPage(lesson, navItems, bodyHtml, opts) {
61
60
  </script>
62
61
  <meta charset="UTF-8">
63
62
  <meta name="viewport" content="width=device-width, initial-scale=1">
64
- <title>${escHtml(lesson.meta.title)}</title>
65
- ${lesson.meta.description ? `<meta name="description" content="${escHtml(lesson.meta.description)}">` : ""}
63
+ <title>${escHtml(title)}</title>
64
+ ${description ? `<meta name="description" content="${escHtml(description)}">` : ""}
66
65
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.47/dist/katex.min.css">
67
66
  <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">
68
67
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.11.1/styles/github-dark.min.css">
69
68
  ${opts.head ?? ""}
70
- <style>
71
- ${opts.font ? `:root { --font-sans: ${opts.font}, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }` : ""}
72
- ${pageCSS()}
73
- </style>
69
+ ${opts.standalone === false ? `<link rel="stylesheet" href="assets/theme.css?v=${Date.now()}">` : `<style>\n${safeFont ? `:root { --font-sans: ${safeFont}, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }` : ""}\n${pageCSS()}\n</style>`}
74
70
  </head>
75
71
  <body class="bk-layout-${layout} bk-density-${density} bk-tone-${tone}">
76
72
  <div class="bk-shell">
77
73
  <aside class="bk-sidebar">
78
74
  <div class="bk-sidebar-inner">
79
75
  <div class="bk-sidebar-header">
80
- ${lesson.meta.parentSlug ? `<div style="margin-top: 8px;"><a href="${lesson.meta.parentSlug}.html" class="bk-back-link" aria-label="Back to Chapter" style="margin-bottom: 12px;"><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="M19 12H5"/><path d="M12 19l-7-7 7-7"/></svg>Back to Chapter</a></div>` : `<div style="margin-top: 8px;"></div>`}
81
- <div class="bk-sidebar-title">${escHtml(lesson.meta.title)}</div>
82
- ${lesson.meta.author ? `<div class="bk-sidebar-author">By ${escHtml(lesson.meta.author)}</div>` : ""}
83
- ${lesson.meta.tags?.length ? `<div class="bk-tag-row">${lesson.meta.tags.map((tag) => `<span>${escHtml(tag)}</span>`).join("")}</div>` : ""}
76
+ ${extraSidebar}
77
+ <div class="bk-sidebar-title">${escHtml(title)}</div>
84
78
  </div>
85
79
  <nav class="bk-nav">${navHtml}</nav>
86
80
  <div class="bk-sidebar-footer">
@@ -142,6 +136,22 @@ ${pageCSS()}
142
136
  <button class="bk-sidebar-expand" id="bk-sidebar-expand" type="button" aria-label="Expand sidebar">
143
137
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6"/></svg>
144
138
  </button>
139
+ ${contentHtml}
140
+ </main>
141
+ </div>
142
+ ${opts.standalone === false ? `<script src="assets/app.js?v=${Date.now()}"></script>` : `<script>\n${clientScript()}\n</script>`}
143
+ </body>
144
+ </html>`;
145
+ }
146
+ function renderPage(lesson, navItems, bodyHtml, opts) {
147
+ const navHtml = navItems.map(renderNavItem).join("\n");
148
+ const endNavHtml = renderEndNav(lesson);
149
+ const extraSidebar = `
150
+ ${lesson.meta.parentSlug ? `<div style="margin-top: 8px;"><a href="${lesson.meta.parentSlug}.html" class="bk-back-link" aria-label="Back to Chapter" style="margin-bottom: 12px;"><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="M19 12H5"/><path d="M12 19l-7-7 7-7"/></svg>Back to Chapter</a></div>` : `<div style="margin-top: 8px;"></div>`}
151
+ `;
152
+ const authorHtml = lesson.meta.author ? `<div class="bk-sidebar-author">By ${escHtml(lesson.meta.author)}</div>` : "";
153
+ const tagsHtml = lesson.meta.tags?.length ? `<div class="bk-tag-row">${lesson.meta.tags.map((tag) => `<span>${escHtml(tag)}</span>`).join("")}</div>` : "";
154
+ const contentHtml = `
145
155
  <article class="bk-content">
146
156
  <header class="bk-hero">
147
157
  <p class="bk-eyebrow">Interactive Lesson</p>
@@ -151,13 +161,8 @@ ${pageCSS()}
151
161
  ${bodyHtml}
152
162
  ${endNavHtml}
153
163
  </article>
154
- </main>
155
- </div>
156
- <script>
157
- ${clientScript()}
158
- </script>
159
- </body>
160
- </html>`;
164
+ `;
165
+ return renderLayout(lesson.meta.title, lesson.meta.description, navHtml, contentHtml, opts, extraSidebar + authorHtml + tagsHtml);
161
166
  }
162
167
  // ─── CSS ──────────────────────────────────────────────────────────────────────
163
168
  function pageCSS() {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/renderer/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAOjE,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,YAAiB,GAAG,MAAM,CAatE;AAID,wBAAgB,aAAa,CAC5B,OAAO,EAAE,OAAO,EAChB,IAAI,GAAE,YAAiB,GACrB,MAAM,CAqYR"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/renderer/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAOjE,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,YAAiB,GAAG,MAAM,CAatE;AAID,wBAAgB,aAAa,CAC5B,OAAO,EAAE,OAAO,EAChB,IAAI,GAAE,YAAiB,GACrB,MAAM,CAuTR"}
@@ -1,5 +1,5 @@
1
1
  import { escAttr, escHtml, renderBlock } from "./blocks.js";
2
- import { clientScript, pageCSS, renderPage } from "./html.js"; // Used in renderChapter
2
+ import { renderPage, renderLayout } from "./html.js"; // Used in renderChapter
3
3
  // ─── Main render function ─────────────────────────────────────────────────────
4
4
  export function render(lesson, opts = {}) {
5
5
  const bodyItems = [];
@@ -296,89 +296,7 @@ export function renderChapter(chapter, opts = {}) {
296
296
  }
297
297
  }
298
298
  `;
299
- return `<!DOCTYPE html>
300
- <html lang="en" data-palette="${palette}" data-ui="${ui}" ${schemeAttr}>
301
- <head>
302
- <meta charset="UTF-8">
303
- <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">
304
- <meta name="viewport" content="width=device-width, initial-scale=1">
305
- <title>${escHtml(chapter.meta.title)}</title>
306
- ${chapter.meta.description ? `<meta name="description" content="${escHtml(chapter.meta.description)}">` : ""}
307
- ${opts.head ?? ""}
308
- <style>
309
- ${opts.font ? `:root { --font-sans: ${opts.font}, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }` : ""}
310
- ${pageCSS()}
311
- ${chapterStyles}
312
- </style>
313
- </head>
314
- <body class="bk-layout-${layout} bk-density-${density} bk-tone-${tone}">
315
- <div class="bk-shell">
316
- <aside class="bk-sidebar">
317
- <div class="bk-sidebar-inner">
318
- <div class="bk-sidebar-header">
319
- <div style="margin-top: 8px;"></div>
320
- <div class="bk-sidebar-title">${escHtml(chapter.meta.title)}</div>
321
- </div>
322
- <nav class="bk-nav">${navHtml}</nav>
323
- <div class="bk-sidebar-footer">
324
- <button class="bk-icon-btn bk-settings-button" id="bk-settings-button" type="button" aria-expanded="false" aria-controls="bk-theme-panel" title="Display settings">
325
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
326
- <span class="bk-sr-only">Display settings</span>
327
- </button>
328
- <div class="bk-theme-panel" id="bk-theme-panel" aria-label="Display settings" hidden>
329
- <div class="bk-theme-row">
330
- <span>Theme</span>
331
- <div class="bk-segmented-control" id="bk-theme-icons">
332
- <button type="button" class="bk-segment-btn ${theme === "light" ? "active" : ""}" data-theme="light" title="Light" aria-label="Light theme">
333
- <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>
334
- </button>
335
- <button type="button" class="bk-segment-btn ${theme === "auto" ? "active" : (!theme ? "active" : "")}" data-theme="auto" title="System" aria-label="System theme">
336
- <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>
337
- </button>
338
- <button type="button" class="bk-segment-btn ${theme === "dark" ? "active" : ""}" data-theme="dark" title="Dark" aria-label="Dark theme">
339
- <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>
340
- </button>
341
- </div>
342
- </div>
343
- <div class="bk-theme-row">
344
- <span>Palette</span>
345
- <div class="bk-segmented-control" id="bk-palette-icons">
346
- <button type="button" class="bk-segment-btn ${palette === "ink" ? "active" : ""}" data-palette="ink" title="Ink" aria-label="Ink palette">
347
- <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="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z"></path></svg>
348
- </button>
349
- <button type="button" class="bk-segment-btn ${palette === "field" ? "active" : ""}" data-palette="field" title="Field" aria-label="Field palette">
350
- <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="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10Z"></path><path d="M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 12"></path></svg>
351
- </button>
352
- <button type="button" class="bk-segment-btn ${palette === "ember" ? "active" : ""}" data-palette="ember" title="Ember" aria-label="Ember palette">
353
- <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="M8.5 14.5A2.5 2.5 0 0011 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 11-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 002.5 2.5z"></path></svg>
354
- </button>
355
- </div>
356
- </div>
357
- <div class="bk-theme-row">
358
- <span>UI</span>
359
- <div class="bk-segmented-control" id="bk-ui-icons">
360
- <button type="button" class="bk-segment-btn ${ui === 'standard' ? 'active' : ''}" data-ui="standard" title="Standard" aria-label="Standard UI">
361
- <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>
362
- </button>
363
- <button type="button" class="bk-segment-btn ${ui === 'neo' ? 'active' : ''}" data-ui="neo" title="Neo Brutalist" aria-label="Neo UI">
364
- <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>
365
- </button>
366
- <button type="button" class="bk-segment-btn ${ui === 'playful' ? 'active' : ''}" data-ui="playful" title="Playful" aria-label="Playful UI">
367
- <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>
368
- </button>
369
- </div>
370
- </div>
371
- </div>
372
- </div>
373
- </div>
374
- </aside>
375
- <button class="bk-sidebar-collapse-floating" id="bk-sidebar-collapse" aria-label="Collapse sidebar">
376
- <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="M15 18l-6-6 6-6"/></svg>
377
- </button>
378
- <main class="bk-main">
379
- <button class="bk-sidebar-expand" id="bk-sidebar-expand" type="button" aria-label="Expand sidebar">
380
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6"/></svg>
381
- </button>
299
+ const contentHtml = `
382
300
  <article class="bk-content" style="max-width: 1000px; margin: 0 auto;">
383
301
  <header class="bk-hero" style="border-bottom: none;">
384
302
  <p class="bk-eyebrow">Chapter</p>
@@ -387,11 +305,7 @@ ${chapterStyles}
387
305
  </header>
388
306
  ${timelineHtml}
389
307
  </article>
390
- </main>
391
- </div>
392
- <script>
393
- ${clientScript()}
394
- </script>
395
- </body>
396
- </html>`;
308
+ `;
309
+ const headHtml = `<style>${chapterStyles}</style>`;
310
+ return renderLayout(chapter.meta.title, chapter.meta.description, navHtml, contentHtml, { ...opts, head: (opts.head ?? "") + headHtml });
397
311
  }
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/renderer/markdown.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAYzC,iBAAS,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,CAmFtH;AAeD,iBAAS,WAAW,CACnB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,IAAI,EAAE,MAAM,EACZ,MAAM,SAAY,EAClB,aAAa,UAAO,EACpB,EAAE,CAAC,EAAE,MAAM,GACT,MAAM,CAYR;AAED,iBAAS,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAmBtC;AAED,iBAAS,wBAAwB,CAChC,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,CAAC,GAC3C,MAAM,CAiCR;AAED,iBAAS,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAIhD;AAED,OAAO,EACN,WAAW,EACX,gBAAgB,EAChB,QAAQ,EACR,QAAQ,EACR,wBAAwB,GACxB,CAAC"}
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/renderer/markdown.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAYzC,iBAAS,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,CAoFtH;AAeD,iBAAS,WAAW,CACnB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,IAAI,EAAE,MAAM,EACZ,MAAM,SAAY,EAClB,aAAa,UAAO,EACpB,EAAE,CAAC,EAAE,MAAM,GACT,MAAM,CAYR;AAED,iBAAS,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAmBtC;AAED,iBAAS,wBAAwB,CAChC,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,CAAC,GAC3C,MAAM,CAwER;AAED,iBAAS,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAIhD;AAED,OAAO,EACN,WAAW,EACX,gBAAgB,EAChB,QAAQ,EACR,QAAQ,EACR,wBAAwB,GACxB,CAAC"}