mr-md 1.0.1 → 1.0.3

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,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);
@@ -379,3 +379,369 @@ ${clientScript()}
379
379
  </body>
380
380
  </html>`;
381
381
  }
382
+ // ─── Course Rendering ────────────────────────────────────────────────────────
383
+ export function renderCourse(course, opts = {}) {
384
+ const theme = opts.theme ?? "auto";
385
+ const schemeAttr = theme === "auto" ? "" : `data-theme="${theme}"`;
386
+ const preset = opts.preset ?? {};
387
+ const layout = preset.layout ?? "lesson";
388
+ const density = preset.density ?? "comfortable";
389
+ const tone = preset.tone ?? "scholarly";
390
+ const palette = opts.palette ?? "ink";
391
+ const navHtml = course.chapters
392
+ .map((c) => `<a href="${escAttr(c.meta.slug)}.html" class="bk-nav-item bk-nav-chapter">${escHtml(c.meta.title)}</a>`)
393
+ .join("\n");
394
+ const timelineHtml = `
395
+ <div class="bk-chapter-timeline-wrapper">
396
+ <div class="bk-chapter-timeline">
397
+ ${course.chapters
398
+ .map((chapter, idx) => `
399
+ <a href="${escAttr(chapter.meta.slug)}.html" class="bk-timeline-card bk-status-${chapter.meta.status ?? "unread"}">
400
+ <div class="bk-timeline-node"></div>
401
+ <div class="bk-timeline-content">
402
+ <h3 class="bk-timeline-title" style="view-transition-name: title-${escAttr(chapter.meta.slug)}">${escHtml(chapter.meta.title)}</h3>
403
+ ${chapter.meta.description ? `<p class="bk-timeline-desc">${escHtml(chapter.meta.description)}</p>` : ""}
404
+ <span class="bk-timeline-action">${chapter.meta.status === "completed" ? "Review" : "Start Chapter"} <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="M5 12h14m-7-7 7 7-7 7"/></svg></span>
405
+ </div>
406
+ </a>
407
+ `)
408
+ .join("")}
409
+ </div>
410
+ </div>
411
+ <script>
412
+ if (!CSS.supports('(animation-timeline: view()) and (animation-range: entry)')) {
413
+ const observer = new IntersectionObserver(
414
+ (entries) => {
415
+ for (const entry of entries) {
416
+ if (entry.isIntersecting) {
417
+ entry.target.classList.add('js-visible');
418
+ entry.target.classList.remove('js-hidden');
419
+ observer.unobserve(entry.target);
420
+ }
421
+ }
422
+ },
423
+ { threshold: 0.1 }
424
+ );
425
+ document.querySelectorAll('.bk-timeline-card').forEach((el) => {
426
+ el.classList.add('js-hidden');
427
+ observer.observe(el);
428
+ });
429
+ }
430
+ </script>`;
431
+ const chapterStyles = `
432
+ .bk-chapter-timeline-wrapper {
433
+ position: relative;
434
+ width: 100%;
435
+ padding: 1rem 0;
436
+ z-index: 1;
437
+ }
438
+
439
+ .bk-chapter-timeline {
440
+ display: flex;
441
+ flex-direction: column;
442
+ gap: 3rem;
443
+ padding: 3rem 0;
444
+ position: relative;
445
+ max-width: 800px;
446
+ margin: 0 auto;
447
+ }
448
+
449
+ /* Minimalist vertical connecting line */
450
+ .bk-chapter-timeline::before {
451
+ content: '';
452
+ position: absolute;
453
+ left: 20px;
454
+ top: 3rem;
455
+ bottom: 3rem;
456
+ width: 1px;
457
+ background: var(--line);
458
+ z-index: 0;
459
+ transform: translateX(-50%);
460
+ }
461
+
462
+ .bk-timeline-card {
463
+ display: flex;
464
+ align-items: stretch;
465
+ gap: 2rem;
466
+ text-decoration: none !important;
467
+ border: none !important;
468
+ color: inherit;
469
+ position: relative;
470
+ z-index: 1;
471
+ opacity: 1;
472
+ transform: none;
473
+ }
474
+
475
+ /* Subtle scroll-driven animations */
476
+ @media (prefers-reduced-motion: no-preference) {
477
+ @supports ((animation-timeline: view()) and (animation-range: entry)) {
478
+ .bk-timeline-card {
479
+ animation-name: slide-fade-in;
480
+ animation-fill-mode: both;
481
+ animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
482
+ animation-timeline: view(block);
483
+ animation-range: entry 5% cover 20%;
484
+ }
485
+ @keyframes slide-fade-in {
486
+ 0% { opacity: 0; transform: translateY(20px); }
487
+ 100% { opacity: 1; transform: translateY(0); }
488
+ }
489
+ }
490
+ }
491
+
492
+ /* Fallback for browsers without animation-timeline support */
493
+ .bk-timeline-card.js-hidden {
494
+ opacity: 0;
495
+ transform: translateY(20px);
496
+ }
497
+ .bk-timeline-card.js-visible {
498
+ opacity: 1;
499
+ transform: translateY(0);
500
+ transition: opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1), transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
501
+ }
502
+
503
+ .bk-timeline-node {
504
+ width: 40px;
505
+ height: 40px;
506
+ border-radius: 50%;
507
+ background: var(--paper);
508
+ border: 1px solid var(--accent);
509
+ display: flex;
510
+ align-items: center;
511
+ justify-content: center;
512
+ flex-shrink: 0;
513
+ transition: all 0.3s ease;
514
+ position: relative;
515
+ box-shadow: 0 2px 4px rgba(0,0,0,0.02);
516
+ }
517
+ .bk-timeline-node::after {
518
+ content: '';
519
+ width: 10px;
520
+ height: 10px;
521
+ border-radius: 50%;
522
+ background: var(--accent);
523
+ transition: all 0.3s ease;
524
+ }
525
+ .bk-timeline-card:hover .bk-timeline-node {
526
+ background: var(--accent);
527
+ }
528
+ .bk-timeline-card:hover .bk-timeline-node::after {
529
+ background: var(--paper);
530
+ }
531
+
532
+ .bk-timeline-content {
533
+ background: var(--paper);
534
+ padding: 2rem;
535
+ border-radius: 12px;
536
+ border: 1px solid var(--line);
537
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.02);
538
+ flex: 1;
539
+ transition: all 0.3s ease;
540
+ display: flex;
541
+ flex-direction: column;
542
+ justify-content: center;
543
+ position: relative;
544
+ }
545
+ .bk-timeline-card:hover .bk-timeline-content {
546
+ border-color: color-mix(in srgb, var(--accent) 30%, var(--line));
547
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.04);
548
+ transform: translateY(-2px);
549
+ }
550
+
551
+ .bk-timeline-title {
552
+ margin: 0 0 0.5rem 0;
553
+ font-family: var(--font-display);
554
+ font-size: 1.6rem;
555
+ color: var(--ink);
556
+ width: fit-content;
557
+ transition: color 0.2s ease;
558
+ }
559
+ .bk-timeline-card:hover .bk-timeline-title {
560
+ color: var(--accent);
561
+ }
562
+
563
+ .bk-timeline-desc {
564
+ margin: 0 0 1.5rem 0;
565
+ color: var(--muted);
566
+ line-height: 1.6;
567
+ font-size: 1.05rem;
568
+ }
569
+
570
+ .bk-timeline-action {
571
+ font-weight: 500;
572
+ color: var(--accent);
573
+ display: inline-flex;
574
+ align-items: center;
575
+ gap: 0.4rem;
576
+ font-size: 0.95rem;
577
+ align-self: flex-start;
578
+ transition: all 0.2s ease;
579
+ border-bottom: 1px solid transparent;
580
+ }
581
+ .bk-timeline-card:hover .bk-timeline-action {
582
+ gap: 0.6rem;
583
+ border-bottom-color: var(--accent);
584
+ }
585
+
586
+ .bk-status-unread .bk-timeline-node {
587
+ border: 1px solid var(--line-strong);
588
+ }
589
+ .bk-status-unread .bk-timeline-node::after {
590
+ background: var(--line-strong);
591
+ transform: scale(0.8);
592
+ }
593
+ .bk-status-unread .bk-timeline-content {
594
+ background: transparent;
595
+ box-shadow: none;
596
+ border: 1px solid transparent;
597
+ }
598
+ .bk-status-unread .bk-timeline-title {
599
+ color: var(--muted);
600
+ }
601
+ .bk-status-unread .bk-timeline-desc {
602
+ color: color-mix(in srgb, var(--muted) 80%, transparent);
603
+ }
604
+ .bk-status-unread .bk-timeline-action {
605
+ color: var(--muted);
606
+ }
607
+ .bk-status-unread:hover .bk-timeline-node {
608
+ border-color: var(--ink);
609
+ background: var(--paper);
610
+ }
611
+ .bk-status-unread:hover .bk-timeline-node::after {
612
+ background: var(--ink);
613
+ transform: scale(1);
614
+ }
615
+ .bk-status-unread:hover .bk-timeline-content {
616
+ background: var(--paper);
617
+ border-color: var(--line);
618
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.02);
619
+ }
620
+ .bk-status-unread:hover .bk-timeline-title {
621
+ color: var(--ink);
622
+ }
623
+ .bk-status-unread:hover .bk-timeline-action {
624
+ color: var(--ink);
625
+ border-bottom-color: var(--ink);
626
+ }
627
+
628
+ @media (prefers-color-scheme: dark) {
629
+ .bk-timeline-content {
630
+ background: var(--panel);
631
+ }
632
+ .bk-status-unread .bk-timeline-content {
633
+ background: transparent;
634
+ }
635
+ .bk-status-unread:hover .bk-timeline-content {
636
+ background: var(--panel);
637
+ }
638
+ }
639
+
640
+ @media (max-width: 600px) {
641
+ .bk-chapter-timeline::before {
642
+ left: 16px;
643
+ }
644
+ .bk-timeline-card {
645
+ gap: 1.5rem;
646
+ }
647
+ .bk-timeline-node {
648
+ width: 32px;
649
+ height: 32px;
650
+ }
651
+ .bk-timeline-node::after {
652
+ width: 8px;
653
+ height: 8px;
654
+ }
655
+ .bk-timeline-content {
656
+ padding: 1.5rem;
657
+ border-radius: 10px;
658
+ }
659
+ .bk-timeline-title {
660
+ font-size: 1.4rem;
661
+ }
662
+ }
663
+ `;
664
+ return `<!DOCTYPE html>
665
+ <html lang="en" data-palette="${palette}" ${schemeAttr}>
666
+ <head>
667
+ <meta charset="UTF-8">
668
+ <meta name="viewport" content="width=device-width, initial-scale=1">
669
+ <title>${escHtml(course.meta.title)}</title>
670
+ ${course.meta.description ? `<meta name="description" content="${escHtml(course.meta.description)}">` : ""}
671
+ ${opts.head ?? ""}
672
+ <style>
673
+ ${opts.font ? `:root { --font-sans: ${opts.font}, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }` : ""}
674
+ ${pageCSS()}
675
+ ${chapterStyles}
676
+ </style>
677
+ </head>
678
+ <body class="bk-layout-${layout} bk-density-${density} bk-tone-${tone}">
679
+ <div class="bk-shell">
680
+ <aside class="bk-sidebar">
681
+ <div class="bk-sidebar-inner">
682
+ <div class="bk-sidebar-header">
683
+ <div style="margin-top: 8px;"></div>
684
+ <div class="bk-sidebar-title">${escHtml(course.meta.title)}</div>
685
+ </div>
686
+ <nav class="bk-nav">${navHtml}</nav>
687
+ <div class="bk-sidebar-footer">
688
+ <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">
689
+ <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>
690
+ <span class="bk-sr-only">Display settings</span>
691
+ </button>
692
+ <div class="bk-theme-panel" id="bk-theme-panel" aria-label="Display settings" hidden>
693
+ <div class="bk-theme-row">
694
+ <span>Theme</span>
695
+ <div class="bk-segmented-control" id="bk-theme-icons">
696
+ <button type="button" class="bk-segment-btn ${theme === "light" ? "active" : ""}" data-theme="light" title="Light" aria-label="Light theme">
697
+ <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>
698
+ </button>
699
+ <button type="button" class="bk-segment-btn ${theme === "dark" ? "active" : ""}" data-theme="dark" title="Dark" aria-label="Dark theme">
700
+ <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>
701
+ </button>
702
+ <button type="button" class="bk-segment-btn ${theme === "auto" ? "active" : ""}" data-theme="auto" title="System" aria-label="System theme">
703
+ <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>
704
+ </button>
705
+ </div>
706
+ </div>
707
+ <div class="bk-theme-row">
708
+ <span>Palette</span>
709
+ <div class="bk-segmented-control" id="bk-palette-icons">
710
+ <button type="button" class="bk-segment-btn ${palette === "ink" ? "active" : ""}" data-palette="ink" title="Ink" aria-label="Ink palette">
711
+ <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>
712
+ </button>
713
+ <button type="button" class="bk-segment-btn ${palette === "field" ? "active" : ""}" data-palette="field" title="Field" aria-label="Field palette">
714
+ <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>
715
+ </button>
716
+ <button type="button" class="bk-segment-btn ${palette === "ember" ? "active" : ""}" data-palette="ember" title="Ember" aria-label="Ember palette">
717
+ <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>
718
+ </button>
719
+ </div>
720
+ </div>
721
+ </div>
722
+ </div>
723
+ </div>
724
+ </aside>
725
+ <button class="bk-sidebar-collapse-floating" id="bk-sidebar-collapse" aria-label="Collapse sidebar">
726
+ <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>
727
+ </button>
728
+ <main class="bk-main">
729
+ <button class="bk-sidebar-expand" id="bk-sidebar-expand" type="button" aria-label="Expand sidebar">
730
+ <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>
731
+ </button>
732
+ <article class="bk-content" style="max-width: 1000px; margin: 0 auto;">
733
+ <header class="bk-hero" style="border-bottom: none;">
734
+ <p class="bk-eyebrow">Course</p>
735
+ <h1>${escHtml(course.meta.title)}</h1>
736
+ ${course.meta.description ? `<p class="bk-deck">${escHtml(course.meta.description)}</p>` : ""}
737
+ </header>
738
+ ${timelineHtml}
739
+ </article>
740
+ </main>
741
+ </div>
742
+ <script>
743
+ ${clientScript()}
744
+ </script>
745
+ </body>
746
+ </html>`;
747
+ }
@@ -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/dist/types.d.ts CHANGED
@@ -204,4 +204,13 @@ export interface Chapter {
204
204
  meta: ChapterMeta;
205
205
  lessons: Lesson[];
206
206
  }
207
+ export interface CourseMeta {
208
+ title: string;
209
+ slug: string;
210
+ description?: string;
211
+ }
212
+ export interface Course {
213
+ meta: CourseMeta;
214
+ chapters: Chapter[];
215
+ }
207
216
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mr-md",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
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",
@@ -22,8 +22,8 @@
22
22
  "scripts": {
23
23
  "build": "tsc -p tsconfig.build.json && mkdir -p dist/styles dist/client && cp -r src/styles/* dist/styles/ && cp -r src/client/* dist/client/",
24
24
  "prepublishOnly": "bun run build",
25
- "build:example": "bun demo/demo-1/course.ts",
26
- "serve:example": "bunx serve demo/demo-1/out",
25
+ "build:example": "bun demo/src/course.ts",
26
+ "serve:example": "bunx serve demo/out",
27
27
  "typecheck": "bunx tsc --noEmit",
28
28
  "test": "bun test && bun run typecheck",
29
29
  "test:watch": "bun test --watch",
package/src/builder.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
- import { render, renderChapter } from "./renderer/index.js";
3
+ import { render, renderChapter, renderCourse } from "./renderer/index.js";
4
4
  import type {
5
5
  AnimationBlock,
6
6
  AnimationOptions,
@@ -667,6 +667,32 @@ export class ChapterBuilder {
667
667
  return this;
668
668
  }
669
669
 
670
+ /** @internal Used by CourseBuilder to push down shared config */
671
+ _inheritOptions(parentOpts: BuildOptions, parentRawOpts?: BuildOptions) {
672
+ this.options = {
673
+ outDir:
674
+ this._rawOptions.outDir ?? (parentRawOpts?.outDir || parentOpts.outDir) ?? this.options.outDir,
675
+ contentBase:
676
+ this._rawOptions.contentBase ??
677
+ parentRawOpts?.contentBase ??
678
+ this.options.contentBase,
679
+ theme: this._rawOptions.theme ?? parentOpts.theme ?? this.options.theme,
680
+ palette:
681
+ this._rawOptions.palette ?? parentOpts.palette ?? this.options.palette,
682
+ strict:
683
+ this._rawOptions.strict ?? parentOpts.strict ?? this.options.strict,
684
+ preset: {
685
+ ...parentOpts.preset,
686
+ ...this._rawOptions.preset,
687
+ ...this.options.preset,
688
+ },
689
+ };
690
+
691
+ for (const lb of this.lessonBuilders) {
692
+ lb._inheritOptions(this.options, this._rawOptions);
693
+ }
694
+ }
695
+
670
696
  description(text: string): this {
671
697
  this.meta.description = text;
672
698
  return this;
@@ -749,3 +775,83 @@ export function chapter(
749
775
  ): ChapterBuilder {
750
776
  return new ChapterBuilder(title, options, getCallerDir());
751
777
  }
778
+
779
+ // ─── CourseBuilder ──────────────────────────────────────────────────────────
780
+
781
+ export class CourseBuilder {
782
+ private meta: import("./types.js").CourseMeta;
783
+ private chapterBuilders: ChapterBuilder[] = [];
784
+ private options: BuildOptions;
785
+ private _rawOptions: BuildOptions;
786
+
787
+ constructor(title: string, options: BuildOptions = {}, callerDir?: string) {
788
+ this._rawOptions = options;
789
+ let slug = title
790
+ .toLowerCase()
791
+ .replace(/[^a-z0-9]+/g, "-")
792
+ .replace(/(^-|-$)/g, "");
793
+ if (!slug) slug = "course";
794
+
795
+ this.meta = { title, slug };
796
+ this.options = {
797
+ outDir: options.outDir ?? "./out",
798
+ contentBase: options.contentBase ?? callerDir ?? ".",
799
+ theme: options.theme ?? "auto",
800
+ palette: options.palette ?? "ink",
801
+ strict: options.strict ?? true,
802
+ preset: {
803
+ layout: "lesson",
804
+ density: "comfortable",
805
+ tone: "scholarly",
806
+ ...options.preset,
807
+ },
808
+ ...options,
809
+ };
810
+ }
811
+
812
+ slug(slug: string): this {
813
+ this.meta.slug = slug;
814
+ return this;
815
+ }
816
+
817
+ description(text: string): this {
818
+ this.meta.description = text;
819
+ return this;
820
+ }
821
+
822
+ chapter(chapterBuilder: ChapterBuilder): this {
823
+ chapterBuilder._inheritOptions(this.options, this._rawOptions);
824
+ this.chapterBuilders.push(chapterBuilder);
825
+ return this;
826
+ }
827
+
828
+ build(): string {
829
+ const chapters: Chapter[] = [];
830
+ for (const cb of this.chapterBuilders) {
831
+ cb.build();
832
+ chapters.push(cb.toJSON());
833
+ }
834
+
835
+ const courseData: import("./types.js").Course = { meta: this.meta, chapters };
836
+ const html = renderCourse(courseData, this.options);
837
+
838
+ const outDir = path.resolve(this.options.outDir as string);
839
+ if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
840
+
841
+ const outPath = path.join(outDir, `${this.meta.slug}.html`);
842
+ fs.writeFileSync(outPath, html, "utf-8");
843
+
844
+ const relPath = path.relative(process.cwd(), outPath);
845
+ console.log(
846
+ ` ✓ Built course (${this.chapterBuilders.length} chapters) → ${relPath}`,
847
+ );
848
+ return outPath;
849
+ }
850
+ }
851
+
852
+ export function course(
853
+ title: string,
854
+ options: BuildOptions = {},
855
+ ): CourseBuilder {
856
+ return new CourseBuilder(title, options, getCallerDir());
857
+ }
package/src/client/app.js CHANGED
@@ -1,5 +1,5 @@
1
1
  function bkSimDoc(js, props, loop, dependencies) {
2
- const scriptTags = (dependencies || []).map(url => '<script src="' + url.replace(/"/g, '&quot;') + '"></' + 'script>').join("\\n");
2
+ const scriptTags = (dependencies || []).map(url => '<script src="' + url.replace(/"/g, '&quot;') + '"></' + 'script>').join("\n");
3
3
  return (
4
4
  "<!DOCTYPE html><html><head>" +
5
5
  scriptTags +
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { ChapterBuilder, chapter, LessonBuilder, lesson } from "./builder.js";
1
+ export { ChapterBuilder, chapter, CourseBuilder, course, LessonBuilder, lesson } from "./builder.js";
2
2
  export { render } from "./renderer/index.js";
3
3
  export type {
4
4
  AnimationBlock,