mr-md 1.0.1 → 1.0.2

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.
@@ -5,11 +5,11 @@ export declare function escHtml(str: string): string;
5
5
  export declare function escAttr(str: string): string;
6
6
  declare function renderBlock(block: Block, idx: number, options: BuildOptions): {
7
7
  html: string;
8
- navItem?: NavItem;
8
+ navItems?: NavItem[];
9
9
  };
10
10
  declare function renderBlockInner(block: Block, idx: number, options: BuildOptions): {
11
11
  html: string;
12
- navItem?: NavItem;
12
+ navItems?: NavItem[];
13
13
  };
14
14
  export { blockChrome, renderBlock, renderBlockInner };
15
15
  //# sourceMappingURL=blocks.d.ts.map
@@ -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,CAqQrC;AAuID,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,CA0QxC;AAuID,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,CAAC"}
@@ -42,13 +42,18 @@ function renderBlockInner(block, idx, options) {
42
42
  const id = `heading-${idx}`;
43
43
  return {
44
44
  html: `<section id="${id}" class="bk-section bk-heading">${html}</section>`,
45
- navItem: { id, label, kind: "heading" },
45
+ navItems: [{ id, label, kind: "heading" }],
46
46
  };
47
47
  }
48
48
  case "markdown": {
49
49
  const md = resolveContent(block.src, options, "md");
50
- const { html } = mdToHtml(md);
51
- return { html: `<div class="bk-markdown">${html}</div>` };
50
+ const { html, headings } = mdToHtml(md);
51
+ const navItems = headings.map(h => ({
52
+ id: h.id,
53
+ label: h.text,
54
+ kind: h.level === 2 ? "heading" : "section"
55
+ }));
56
+ return { html: `<div class="bk-markdown">${html}</div>`, navItems: navItems.length > 0 ? navItems : undefined };
52
57
  }
53
58
  case "section": {
54
59
  const md = resolveContent(block.src, options, "md");
@@ -57,7 +62,7 @@ function renderBlockInner(block, idx, options) {
57
62
  const id = `section-${idx}`;
58
63
  return {
59
64
  html: `<section id="${id}" class="bk-section bk-subsection">${html}</section>`,
60
- navItem: { id, label, kind: "section" },
65
+ navItems: [{ id, label, kind: "section" }],
61
66
  };
62
67
  }
63
68
  case "important":
@@ -227,11 +232,11 @@ function renderBlockInner(block, idx, options) {
227
232
  ${quiz.questions.map((q, qi) => renderQuestion(q, `quiz-${idx}`, qi)).join("\n")}
228
233
  </div>
229
234
  </div>`,
230
- navItem: {
231
- id: `quiz-${idx}`,
232
- label: block.label ?? "Questions",
233
- kind: "quiz",
234
- },
235
+ navItems: [{
236
+ id: `quiz-${idx}`,
237
+ label: block.label ?? "Questions",
238
+ kind: "quiz",
239
+ }],
235
240
  };
236
241
  }
237
242
  case "divider":
@@ -5,10 +5,10 @@ export function render(lesson, opts = {}) {
5
5
  const bodyItems = [];
6
6
  const structuredNavItems = [];
7
7
  lesson.blocks.forEach((block, idx) => {
8
- const { html, navItem } = renderBlock(block, idx, opts);
8
+ const { html, navItems } = renderBlock(block, idx, opts);
9
9
  bodyItems.push(html);
10
- if (navItem) {
11
- structuredNavItems.push(navItem);
10
+ if (navItems) {
11
+ structuredNavItems.push(...navItems);
12
12
  }
13
13
  });
14
14
  return renderPage(lesson, structuredNavItems, bodyItems.join("\n"), opts);
@@ -2,6 +2,11 @@ import type { Block } from "../types.js";
2
2
  declare function mdToHtml(md: string): {
3
3
  html: string;
4
4
  title: string;
5
+ headings: {
6
+ id: string;
7
+ text: string;
8
+ level: number;
9
+ }[];
5
10
  };
6
11
  declare function blockChrome(kind: string, label: string | undefined, caption: string | undefined, body: string, accent?: string, allowMaximize?: boolean): string;
7
12
  declare function mdInline(text: string): string;
@@ -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,CAAA;CAAE,CAsE7D;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,GAClB,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,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,GAClB,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"}
@@ -41,7 +41,18 @@ function mdToHtml(md) {
41
41
  codeBlocks.forEach((match, id) => {
42
42
  processedMd = processedMd.replace(`@@BK_CODE_${id}@@`, () => match);
43
43
  });
44
- let html = marked.parse(processedMd);
44
+ const headings = [];
45
+ let headingIdCounter = 0;
46
+ const renderer = new marked.Renderer();
47
+ renderer.heading = ({ tokens, depth, text }) => {
48
+ const id = `bk-heading-${headingIdCounter++}`;
49
+ if (depth === 2 || depth === 3) {
50
+ const plainText = text.replace(/<[^>]+>/g, "");
51
+ headings.push({ id, text: plainText, level: depth });
52
+ }
53
+ return `<h${depth} id="${id}" class="bk-heading-${depth}">${text}</h${depth}>`;
54
+ };
55
+ let html = marked.parse(processedMd, { renderer });
45
56
  // Restore math
46
57
  mathBlocks.forEach((tex, id) => {
47
58
  const rendered = katex.renderToString(tex, {
@@ -60,7 +71,7 @@ function mdToHtml(md) {
60
71
  });
61
72
  html = html.replace(`@@BK_MATH_INLINE_${id}@@`, () => rendered);
62
73
  });
63
- return { html, title };
74
+ return { html, title, headings };
64
75
  }
65
76
  function escHtml(s) {
66
77
  return s
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mr-md",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Mr Markdown is an opinionated TypeScript SDK for building interactive, single-file learning pages.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -29,7 +29,7 @@ function renderBlock(
29
29
  block: Block,
30
30
  idx: number,
31
31
  options: BuildOptions,
32
- ): { html: string; navItem?: NavItem } {
32
+ ): { html: string; navItems?: NavItem[] } {
33
33
  try {
34
34
  const result = renderBlockInner(block, idx, options);
35
35
  if (
@@ -58,7 +58,7 @@ function renderBlockInner(
58
58
  block: Block,
59
59
  idx: number,
60
60
  options: BuildOptions,
61
- ): { html: string; navItem?: NavItem } {
61
+ ): { html: string; navItems?: NavItem[] } {
62
62
  switch (block.type) {
63
63
  case "heading": {
64
64
  const md = resolveContent(block.src, options, "md");
@@ -67,14 +67,19 @@ function renderBlockInner(
67
67
  const id = `heading-${idx}`;
68
68
  return {
69
69
  html: `<section id="${id}" class="bk-section bk-heading">${html}</section>`,
70
- navItem: { id, label, kind: "heading" },
70
+ navItems: [{ id, label, kind: "heading" }],
71
71
  };
72
72
  }
73
73
 
74
74
  case "markdown": {
75
75
  const md = resolveContent(block.src, options, "md");
76
- const { html } = mdToHtml(md);
77
- return { html: `<div class="bk-markdown">${html}</div>` };
76
+ const { html, headings } = mdToHtml(md);
77
+ const navItems: NavItem[] = headings.map(h => ({
78
+ id: h.id,
79
+ label: h.text,
80
+ kind: h.level === 2 ? "heading" : "section"
81
+ }));
82
+ return { html: `<div class="bk-markdown">${html}</div>`, navItems: navItems.length > 0 ? navItems : undefined };
78
83
  }
79
84
 
80
85
  case "section": {
@@ -84,7 +89,7 @@ function renderBlockInner(
84
89
  const id = `section-${idx}`;
85
90
  return {
86
91
  html: `<section id="${id}" class="bk-section bk-subsection">${html}</section>`,
87
- navItem: { id, label, kind: "section" },
92
+ navItems: [{ id, label, kind: "section" }],
88
93
  };
89
94
  }
90
95
 
@@ -305,11 +310,11 @@ function renderBlockInner(
305
310
  ${quiz.questions.map((q, qi) => renderQuestion(q, `quiz-${idx}`, qi)).join("\n")}
306
311
  </div>
307
312
  </div>`,
308
- navItem: {
313
+ navItems: [{
309
314
  id: `quiz-${idx}`,
310
315
  label: block.label ?? "Questions",
311
316
  kind: "quiz",
312
- },
317
+ }],
313
318
  };
314
319
  }
315
320
 
@@ -10,10 +10,10 @@ export function render(lesson: Lesson, opts: BuildOptions = {}): string {
10
10
  const structuredNavItems: NavItem[] = [];
11
11
 
12
12
  lesson.blocks.forEach((block, idx) => {
13
- const { html, navItem } = renderBlock(block, idx, opts);
13
+ const { html, navItems } = renderBlock(block, idx, opts);
14
14
  bodyItems.push(html);
15
- if (navItem) {
16
- structuredNavItems.push(navItem);
15
+ if (navItems) {
16
+ structuredNavItems.push(...navItems);
17
17
  }
18
18
  });
19
19
 
@@ -14,7 +14,7 @@ marked.use(markedHighlight({
14
14
 
15
15
  // ─── Markdown Rendering (using Marked + KaTeX) ───────────────────────────────
16
16
 
17
- function mdToHtml(md: string): { html: string; title: string } {
17
+ function mdToHtml(md: string): { html: string; title: string; headings: { id: string; text: string; level: number }[] } {
18
18
  let title = "";
19
19
 
20
20
  // Extract first H1 or H2 as title
@@ -55,7 +55,20 @@ function mdToHtml(md: string): { html: string; title: string } {
55
55
  processedMd = processedMd.replace(`@@BK_CODE_${id}@@`, () => match);
56
56
  });
57
57
 
58
- let html = marked.parse(processedMd) as string;
58
+ const headings: { id: string; text: string; level: number }[] = [];
59
+ let headingIdCounter = 0;
60
+
61
+ const renderer = new marked.Renderer();
62
+ renderer.heading = ({ tokens, depth, text }) => {
63
+ const id = `bk-heading-${headingIdCounter++}`;
64
+ if (depth === 2 || depth === 3) {
65
+ const plainText = text.replace(/<[^>]+>/g, "");
66
+ headings.push({ id, text: plainText, level: depth });
67
+ }
68
+ return `<h${depth} id="${id}" class="bk-heading-${depth}">${text}</h${depth}>`;
69
+ };
70
+
71
+ let html = marked.parse(processedMd, { renderer }) as string;
59
72
 
60
73
  // Restore math
61
74
  mathBlocks.forEach((tex, id) => {
@@ -83,7 +96,7 @@ function mdToHtml(md: string): { html: string; title: string } {
83
96
  html = html.replace(`@@BK_MATH_INLINE_${id}@@`, () => rendered);
84
97
  });
85
98
 
86
- return { html, title };
99
+ return { html, title, headings };
87
100
  }
88
101
 
89
102
  function escHtml(s: string): string {