yumiamd 0.1.10 → 0.1.13
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 +19 -7
- package/dist/bin.js +1 -1
- package/dist/{chunk-PQYPVPV2.js → chunk-6GEDN7OB.js} +711 -35
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -1
- package/package.json +12 -10
package/README.md
CHANGED
|
@@ -49,23 +49,35 @@ pnpm add yumiamd
|
|
|
49
49
|
Once installed globally or locally, the `yumia` / `yumiamd` commands are available in your terminal:
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
|
-
# Initialize a new presentation template
|
|
53
|
-
yumia init my-presentation
|
|
52
|
+
# Initialize a new presentation template (supports --theme and custom color overrides)
|
|
53
|
+
yumia init my-presentation --theme cyberpunk --primary "#FF2E88"
|
|
54
|
+
|
|
55
|
+
# Start instant live-reload dev server with HTML preview (zero config!)
|
|
56
|
+
yumia dev presentation.yumia.md --open
|
|
57
|
+
|
|
58
|
+
# Compile to native editable PowerPoint (.pptx)
|
|
59
|
+
yumia build presentation.yumia.md --out dist/presentation.pptx
|
|
60
|
+
|
|
61
|
+
# Compile to crisp vector PDF document (.pdf)
|
|
62
|
+
yumia build presentation.yumia.md --format pdf --out dist/presentation.pdf
|
|
63
|
+
|
|
64
|
+
# Compile to standalone interactive HTML5 presentation deck (.html)
|
|
65
|
+
yumia build presentation.yumia.md --format html --out dist/presentation.html
|
|
66
|
+
|
|
67
|
+
# Watch presentation file and recompile automatically on save
|
|
68
|
+
yumia watch presentation.yumia.md --format pdf
|
|
54
69
|
|
|
55
70
|
# Validate markdown syntax and AST structure (supports --json for CI & AI agents)
|
|
56
71
|
yumia validate presentation.yumia.md --json
|
|
57
72
|
|
|
58
|
-
# Lint presentation for
|
|
59
|
-
yumia lint presentation.yumia.md --
|
|
73
|
+
# Lint presentation for overflow, high density, and layout issues
|
|
74
|
+
yumia lint presentation.yumia.md --strict
|
|
60
75
|
|
|
61
76
|
# Inspect parsed AST and computed layout bounding boxes
|
|
62
77
|
yumia inspect presentation.yumia.md --layout
|
|
63
78
|
|
|
64
79
|
# Export machine-readable JSON schema for LLMs & AI prompt generation
|
|
65
80
|
yumia schema
|
|
66
|
-
|
|
67
|
-
# Compile to native editable PowerPoint (.pptx)
|
|
68
|
-
yumia build presentation.yumia.md --out dist/presentation.pptx
|
|
69
81
|
```
|
|
70
82
|
|
|
71
83
|
---
|
package/dist/bin.js
CHANGED
|
@@ -2393,15 +2393,16 @@ var HtmlRenderer = class {
|
|
|
2393
2393
|
right: 24px;
|
|
2394
2394
|
display: flex;
|
|
2395
2395
|
align-items: center;
|
|
2396
|
-
gap:
|
|
2397
|
-
background: rgba(15, 23, 42, 0.
|
|
2396
|
+
gap: 10px;
|
|
2397
|
+
background: rgba(15, 23, 42, 0.88);
|
|
2398
2398
|
backdrop-filter: blur(12px);
|
|
2399
|
-
padding:
|
|
2399
|
+
padding: 6px 14px;
|
|
2400
2400
|
border-radius: 30px;
|
|
2401
2401
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
2402
2402
|
z-index: 1000;
|
|
2403
2403
|
font-size: 13px;
|
|
2404
2404
|
color: #e2e8f0;
|
|
2405
|
+
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5);
|
|
2405
2406
|
}
|
|
2406
2407
|
|
|
2407
2408
|
.yumia-btn {
|
|
@@ -2409,17 +2410,18 @@ var HtmlRenderer = class {
|
|
|
2409
2410
|
border: none;
|
|
2410
2411
|
color: #f8fafc;
|
|
2411
2412
|
cursor: pointer;
|
|
2412
|
-
padding:
|
|
2413
|
+
padding: 5px 9px;
|
|
2413
2414
|
border-radius: 6px;
|
|
2414
2415
|
font-size: 14px;
|
|
2415
2416
|
display: flex;
|
|
2416
2417
|
align-items: center;
|
|
2417
2418
|
justify-content: center;
|
|
2418
|
-
transition: background 0.15s ease;
|
|
2419
|
+
transition: background 0.15s ease, transform 0.1s ease;
|
|
2419
2420
|
}
|
|
2420
2421
|
|
|
2421
2422
|
.yumia-btn:hover {
|
|
2422
2423
|
background: rgba(255, 255, 255, 0.15);
|
|
2424
|
+
transform: translateY(-1px);
|
|
2423
2425
|
}
|
|
2424
2426
|
|
|
2425
2427
|
.yumia-progress-bar {
|
|
@@ -2437,47 +2439,291 @@ var HtmlRenderer = class {
|
|
|
2437
2439
|
bottom: 0;
|
|
2438
2440
|
left: 0;
|
|
2439
2441
|
right: 0;
|
|
2440
|
-
max-height:
|
|
2441
|
-
background: rgba(10, 10, 18, 0.
|
|
2442
|
+
max-height: 240px;
|
|
2443
|
+
background: rgba(10, 10, 18, 0.96);
|
|
2442
2444
|
backdrop-filter: blur(16px);
|
|
2443
|
-
border-top:
|
|
2444
|
-
padding:
|
|
2445
|
+
border-top: 2px solid var(--yumia-primary);
|
|
2446
|
+
padding: 18px 28px;
|
|
2445
2447
|
overflow-y: auto;
|
|
2446
2448
|
display: none;
|
|
2447
2449
|
z-index: 2000;
|
|
2448
|
-
font-size:
|
|
2450
|
+
font-size: 15px;
|
|
2449
2451
|
line-height: 1.6;
|
|
2450
2452
|
color: #cbd5e1;
|
|
2453
|
+
box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.5);
|
|
2451
2454
|
}
|
|
2452
2455
|
|
|
2453
2456
|
.yumia-notes-drawer.open {
|
|
2454
2457
|
display: block;
|
|
2455
2458
|
}
|
|
2459
|
+
|
|
2460
|
+
/* Slide Overview Modal */
|
|
2461
|
+
.yumia-overview-modal {
|
|
2462
|
+
position: fixed;
|
|
2463
|
+
top: 0;
|
|
2464
|
+
left: 0;
|
|
2465
|
+
width: 100vw;
|
|
2466
|
+
height: 100vh;
|
|
2467
|
+
background: rgba(5, 5, 10, 0.94);
|
|
2468
|
+
backdrop-filter: blur(20px);
|
|
2469
|
+
z-index: 3000;
|
|
2470
|
+
display: none;
|
|
2471
|
+
flex-direction: column;
|
|
2472
|
+
padding: 40px;
|
|
2473
|
+
overflow-y: auto;
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
.yumia-overview-modal.open {
|
|
2477
|
+
display: flex;
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
.yumia-overview-header {
|
|
2481
|
+
display: flex;
|
|
2482
|
+
justify-content: space-between;
|
|
2483
|
+
align-items: center;
|
|
2484
|
+
margin-bottom: 30px;
|
|
2485
|
+
color: #fff;
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
.yumia-overview-grid {
|
|
2489
|
+
display: grid;
|
|
2490
|
+
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
2491
|
+
gap: 24px;
|
|
2492
|
+
width: 100%;
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
.yumia-overview-card {
|
|
2496
|
+
aspect-ratio: var(--yumia-ratio);
|
|
2497
|
+
background-color: var(--yumia-bg);
|
|
2498
|
+
border: 2px solid rgba(255, 255, 255, 0.1);
|
|
2499
|
+
border-radius: 8px;
|
|
2500
|
+
padding: 16px;
|
|
2501
|
+
cursor: pointer;
|
|
2502
|
+
position: relative;
|
|
2503
|
+
overflow: hidden;
|
|
2504
|
+
transform-origin: center;
|
|
2505
|
+
transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
|
|
2506
|
+
display: flex;
|
|
2507
|
+
flex-direction: column;
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
.yumia-overview-card:hover {
|
|
2511
|
+
transform: scale(1.04);
|
|
2512
|
+
border-color: var(--yumia-primary);
|
|
2513
|
+
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.8), 0 0 15px var(--yumia-primary);
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
.yumia-overview-card.current {
|
|
2517
|
+
border-color: var(--yumia-accent);
|
|
2518
|
+
box-shadow: 0 0 0 2px var(--yumia-accent);
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
.yumia-overview-badge {
|
|
2522
|
+
position: absolute;
|
|
2523
|
+
top: 8px;
|
|
2524
|
+
right: 8px;
|
|
2525
|
+
background: rgba(0, 0, 0, 0.6);
|
|
2526
|
+
color: #fff;
|
|
2527
|
+
font-size: 11px;
|
|
2528
|
+
font-weight: 700;
|
|
2529
|
+
padding: 2px 8px;
|
|
2530
|
+
border-radius: 10px;
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
/* Speaker View Layout (When loaded in speaker mode ?speaker=true) */
|
|
2534
|
+
.speaker-layout {
|
|
2535
|
+
display: grid;
|
|
2536
|
+
grid-template-rows: 60px 1fr;
|
|
2537
|
+
width: 100vw;
|
|
2538
|
+
height: 100vh;
|
|
2539
|
+
background: #09090f;
|
|
2540
|
+
color: #f8fafc;
|
|
2541
|
+
overflow: hidden;
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
.speaker-topbar {
|
|
2545
|
+
display: flex;
|
|
2546
|
+
justify-content: space-between;
|
|
2547
|
+
align-items: center;
|
|
2548
|
+
padding: 0 24px;
|
|
2549
|
+
background: #11111b;
|
|
2550
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2553
|
+
.speaker-timer-group {
|
|
2554
|
+
display: flex;
|
|
2555
|
+
align-items: center;
|
|
2556
|
+
gap: 16px;
|
|
2557
|
+
font-family: var(--yumia-font-code);
|
|
2558
|
+
font-size: 18px;
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
.speaker-timer-btn {
|
|
2562
|
+
background: rgba(255, 255, 255, 0.1);
|
|
2563
|
+
color: #fff;
|
|
2564
|
+
border: none;
|
|
2565
|
+
padding: 4px 10px;
|
|
2566
|
+
border-radius: 4px;
|
|
2567
|
+
cursor: pointer;
|
|
2568
|
+
font-size: 12px;
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
.speaker-timer-btn:hover {
|
|
2572
|
+
background: var(--yumia-primary);
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
.speaker-main-grid {
|
|
2576
|
+
display: grid;
|
|
2577
|
+
grid-template-columns: 55% 45%;
|
|
2578
|
+
gap: 20px;
|
|
2579
|
+
padding: 20px;
|
|
2580
|
+
height: calc(100vh - 60px);
|
|
2581
|
+
box-sizing: border-box;
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
.speaker-pane {
|
|
2585
|
+
background: #151522;
|
|
2586
|
+
border-radius: 10px;
|
|
2587
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
2588
|
+
padding: 16px;
|
|
2589
|
+
display: flex;
|
|
2590
|
+
flex-direction: column;
|
|
2591
|
+
overflow: hidden;
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
.speaker-pane-title {
|
|
2595
|
+
font-size: 12px;
|
|
2596
|
+
font-weight: 700;
|
|
2597
|
+
text-transform: uppercase;
|
|
2598
|
+
letter-spacing: 0.05em;
|
|
2599
|
+
color: var(--yumia-muted);
|
|
2600
|
+
margin-bottom: 12px;
|
|
2601
|
+
display: flex;
|
|
2602
|
+
justify-content: space-between;
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
.speaker-preview-box {
|
|
2606
|
+
flex: 1;
|
|
2607
|
+
position: relative;
|
|
2608
|
+
display: flex;
|
|
2609
|
+
align-items: center;
|
|
2610
|
+
justify-content: center;
|
|
2611
|
+
background: #000;
|
|
2612
|
+
border-radius: 8px;
|
|
2613
|
+
overflow: hidden;
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
.speaker-preview-box .yumia-slide-wrapper {
|
|
2617
|
+
display: flex !important;
|
|
2618
|
+
transform: scale(0.6);
|
|
2619
|
+
width: 160% !important;
|
|
2620
|
+
height: 160% !important;
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
.speaker-notes-box {
|
|
2624
|
+
flex: 1;
|
|
2625
|
+
background: #11111a;
|
|
2626
|
+
border-radius: 8px;
|
|
2627
|
+
padding: 16px;
|
|
2628
|
+
font-size: 16px;
|
|
2629
|
+
line-height: 1.7;
|
|
2630
|
+
color: #e2e8f0;
|
|
2631
|
+
overflow-y: auto;
|
|
2632
|
+
}
|
|
2456
2633
|
</style>
|
|
2457
2634
|
</head>
|
|
2458
2635
|
<body>
|
|
2459
|
-
<div id="
|
|
2460
|
-
|
|
2636
|
+
<div id="deck-container">
|
|
2637
|
+
<div id="yumia-deck">
|
|
2638
|
+
${slidesHtml}
|
|
2639
|
+
</div>
|
|
2640
|
+
|
|
2641
|
+
<div class="yumia-controls" id="deck-controls">
|
|
2642
|
+
<button class="yumia-btn" id="btn-prev" title="Previous Slide (\u2190)">\u25C0</button>
|
|
2643
|
+
<span id="slide-indicator">1 / ${presentation.slides.length}</span>
|
|
2644
|
+
<button class="yumia-btn" id="btn-next" title="Next Slide (\u2192)">\u25B6</button>
|
|
2645
|
+
<button class="yumia-btn" id="btn-overview" title="Slide Overview (ESC / O)">\u25A6</button>
|
|
2646
|
+
<button class="yumia-btn" id="btn-speaker" title="Speaker View (S)">\u{1F3A4}</button>
|
|
2647
|
+
<button class="yumia-btn" id="btn-notes" title="Toggle Notes (N)">\u{1F4DD}</button>
|
|
2648
|
+
<button class="yumia-btn" id="btn-fs" title="Fullscreen (F)">\u26F6</button>
|
|
2649
|
+
</div>
|
|
2650
|
+
|
|
2651
|
+
<div id="notes-drawer" class="yumia-notes-drawer"></div>
|
|
2652
|
+
|
|
2653
|
+
<div id="overview-modal" class="yumia-overview-modal">
|
|
2654
|
+
<div class="yumia-overview-header">
|
|
2655
|
+
<h2>Slide Overview</h2>
|
|
2656
|
+
<button class="yumia-btn" id="btn-close-overview" style="font-size: 18px;">\u2715 Close (ESC)</button>
|
|
2657
|
+
</div>
|
|
2658
|
+
<div class="yumia-overview-grid" id="overview-grid"></div>
|
|
2659
|
+
</div>
|
|
2461
2660
|
</div>
|
|
2462
2661
|
|
|
2463
|
-
<div class="yumia-controls">
|
|
2464
|
-
<button class="yumia-btn" id="btn-prev" title="Previous Slide (\u2190)">\u25C0</button>
|
|
2465
|
-
<span id="slide-indicator">1 / ${presentation.slides.length}</span>
|
|
2466
|
-
<button class="yumia-btn" id="btn-next" title="Next Slide (\u2192)">\u25B6</button>
|
|
2467
|
-
<button class="yumia-btn" id="btn-notes" title="Toggle Notes (N)">\u{1F4DD}</button>
|
|
2468
|
-
<button class="yumia-btn" id="btn-fs" title="Fullscreen (F)">\u26F6</button>
|
|
2469
|
-
</div>
|
|
2470
|
-
|
|
2471
|
-
<div id="notes-drawer" class="yumia-notes-drawer"></div>
|
|
2472
|
-
|
|
2473
2662
|
<script>
|
|
2474
2663
|
(function() {
|
|
2475
|
-
const
|
|
2664
|
+
const isSpeakerMode = new URLSearchParams(window.location.search).get('speaker') === 'true';
|
|
2665
|
+
const slides = Array.from(document.querySelectorAll('.yumia-slide-wrapper'));
|
|
2476
2666
|
const indicator = document.getElementById('slide-indicator');
|
|
2477
2667
|
const notesDrawer = document.getElementById('notes-drawer');
|
|
2668
|
+
const overviewModal = document.getElementById('overview-modal');
|
|
2669
|
+
const overviewGrid = document.getElementById('overview-grid');
|
|
2478
2670
|
let currentIdx = 0;
|
|
2479
2671
|
|
|
2480
|
-
|
|
2672
|
+
// Real-time synchronization channel
|
|
2673
|
+
let syncChannel = null;
|
|
2674
|
+
try {
|
|
2675
|
+
syncChannel = new BroadcastChannel('yumia_presentation_sync');
|
|
2676
|
+
syncChannel.onmessage = function(e) {
|
|
2677
|
+
if (e.data && typeof e.data.index === 'number') {
|
|
2678
|
+
goToSlide(e.data.index, false);
|
|
2679
|
+
}
|
|
2680
|
+
};
|
|
2681
|
+
} catch (err) {
|
|
2682
|
+
// Fallback if BroadcastChannel unavailable
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
function buildOverviewGrid() {
|
|
2686
|
+
if (!overviewGrid) return;
|
|
2687
|
+
overviewGrid.innerHTML = '';
|
|
2688
|
+
slides.forEach((slide, idx) => {
|
|
2689
|
+
const card = document.createElement('div');
|
|
2690
|
+
card.className = 'yumia-overview-card' + (idx === currentIdx ? ' current' : '');
|
|
2691
|
+
const badge = document.createElement('span');
|
|
2692
|
+
badge.className = 'yumia-overview-badge';
|
|
2693
|
+
badge.textContent = (idx + 1);
|
|
2694
|
+
|
|
2695
|
+
const heading = slide.querySelector('h1, h2, h3');
|
|
2696
|
+
const titleText = heading ? heading.textContent : 'Slide ' + (idx + 1);
|
|
2697
|
+
|
|
2698
|
+
const titleDiv = document.createElement('div');
|
|
2699
|
+
titleDiv.style.fontWeight = '600';
|
|
2700
|
+
titleDiv.style.fontSize = '14px';
|
|
2701
|
+
titleDiv.style.color = 'var(--yumia-primary)';
|
|
2702
|
+
titleDiv.textContent = titleText;
|
|
2703
|
+
|
|
2704
|
+
card.appendChild(badge);
|
|
2705
|
+
card.appendChild(titleDiv);
|
|
2706
|
+
|
|
2707
|
+
card.addEventListener('click', () => {
|
|
2708
|
+
goToSlide(idx);
|
|
2709
|
+
toggleOverview(false);
|
|
2710
|
+
});
|
|
2711
|
+
overviewGrid.appendChild(card);
|
|
2712
|
+
});
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
function toggleOverview(force) {
|
|
2716
|
+
if (!overviewModal) return;
|
|
2717
|
+
const isOpen = force !== undefined ? force : !overviewModal.classList.contains('open');
|
|
2718
|
+
if (isOpen) {
|
|
2719
|
+
buildOverviewGrid();
|
|
2720
|
+
overviewModal.classList.add('open');
|
|
2721
|
+
} else {
|
|
2722
|
+
overviewModal.classList.remove('open');
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
function goToSlide(newIdx, broadcast = true) {
|
|
2481
2727
|
if (newIdx < 0 || newIdx >= slides.length) return;
|
|
2482
2728
|
slides[currentIdx]?.classList.remove('active');
|
|
2483
2729
|
currentIdx = newIdx;
|
|
@@ -2490,23 +2736,166 @@ var HtmlRenderer = class {
|
|
|
2490
2736
|
const currentSlide = slides[currentIdx];
|
|
2491
2737
|
const notes = currentSlide ? currentSlide.getAttribute('data-notes') : '';
|
|
2492
2738
|
if (notesDrawer) {
|
|
2493
|
-
notesDrawer.innerHTML = notes ? '<strong>Speaker Notes:</strong><br>' + notes : '<em>No notes for this slide.</em>';
|
|
2739
|
+
notesDrawer.innerHTML = notes ? '<strong>Speaker Notes:</strong><br>' + notes : '<em>No speaker notes for this slide.</em>';
|
|
2494
2740
|
}
|
|
2495
2741
|
|
|
2496
2742
|
window.location.hash = '#' + (currentIdx + 1);
|
|
2743
|
+
|
|
2744
|
+
if (broadcast && syncChannel) {
|
|
2745
|
+
syncChannel.postMessage({ index: currentIdx });
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2749
|
+
function openSpeakerWindow() {
|
|
2750
|
+
const url = new URL(window.location.href);
|
|
2751
|
+
url.searchParams.set('speaker', 'true');
|
|
2752
|
+
window.open(url.toString(), 'yumia_speaker_' + Date.now(), 'width=1180,height=760,menubar=no,toolbar=no');
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
function initSpeakerLayout() {
|
|
2756
|
+
document.body.innerHTML = \`
|
|
2757
|
+
<div class="speaker-layout">
|
|
2758
|
+
<div class="speaker-topbar">
|
|
2759
|
+
<div style="font-weight:700; color:var(--yumia-primary); display:flex; align-items:center; gap:8px;">
|
|
2760
|
+
<span>\u{1F399}\uFE0F YumiaMD Speaker View</span>
|
|
2761
|
+
</div>
|
|
2762
|
+
<div class="speaker-timer-group">
|
|
2763
|
+
<span id="speaker-clock">00:00:00</span>
|
|
2764
|
+
<span style="color:var(--yumia-muted);">|</span>
|
|
2765
|
+
<span id="speaker-timer" style="color:var(--yumia-accent);">00:00</span>
|
|
2766
|
+
<button class="speaker-timer-btn" id="btn-timer-toggle">Pause</button>
|
|
2767
|
+
<button class="speaker-timer-btn" id="btn-timer-reset">Reset</button>
|
|
2768
|
+
</div>
|
|
2769
|
+
<div>
|
|
2770
|
+
<span id="speaker-slide-num" style="font-weight:600;">1 / \${slides.length}</span>
|
|
2771
|
+
</div>
|
|
2772
|
+
</div>
|
|
2773
|
+
<div class="speaker-main-grid">
|
|
2774
|
+
<div class="speaker-pane">
|
|
2775
|
+
<div class="speaker-pane-title">Current Slide</div>
|
|
2776
|
+
<div class="speaker-preview-box" id="speaker-current-box"></div>
|
|
2777
|
+
</div>
|
|
2778
|
+
<div style="display:grid; grid-template-rows: 45% 55%; gap:20px;">
|
|
2779
|
+
<div class="speaker-pane">
|
|
2780
|
+
<div class="speaker-pane-title">Next Slide Preview</div>
|
|
2781
|
+
<div class="speaker-preview-box" id="speaker-next-box"></div>
|
|
2782
|
+
</div>
|
|
2783
|
+
<div class="speaker-pane">
|
|
2784
|
+
<div class="speaker-pane-title">Speaker Notes</div>
|
|
2785
|
+
<div class="speaker-notes-box" id="speaker-notes-content"></div>
|
|
2786
|
+
</div>
|
|
2787
|
+
</div>
|
|
2788
|
+
</div>
|
|
2789
|
+
</div>
|
|
2790
|
+
\`;
|
|
2791
|
+
|
|
2792
|
+
// Live Clock
|
|
2793
|
+
setInterval(() => {
|
|
2794
|
+
const now = new Date();
|
|
2795
|
+
const clockEl = document.getElementById('speaker-clock');
|
|
2796
|
+
if (clockEl) clockEl.textContent = now.toLocaleTimeString();
|
|
2797
|
+
}, 1000);
|
|
2798
|
+
|
|
2799
|
+
// Elapsed Timer
|
|
2800
|
+
let elapsedSeconds = 0;
|
|
2801
|
+
let timerRunning = true;
|
|
2802
|
+
let timerInterval = setInterval(() => {
|
|
2803
|
+
if (!timerRunning) return;
|
|
2804
|
+
elapsedSeconds++;
|
|
2805
|
+
const mins = String(Math.floor(elapsedSeconds / 60)).padStart(2, '0');
|
|
2806
|
+
const secs = String(elapsedSeconds % 60).padStart(2, '0');
|
|
2807
|
+
const timerEl = document.getElementById('speaker-timer');
|
|
2808
|
+
if (timerEl) timerEl.textContent = \`\${mins}:\${secs}\`;
|
|
2809
|
+
}, 1000);
|
|
2810
|
+
|
|
2811
|
+
document.getElementById('btn-timer-toggle')?.addEventListener('click', (e) => {
|
|
2812
|
+
timerRunning = !timerRunning;
|
|
2813
|
+
e.target.textContent = timerRunning ? 'Pause' : 'Start';
|
|
2814
|
+
});
|
|
2815
|
+
|
|
2816
|
+
document.getElementById('btn-timer-reset')?.addEventListener('click', () => {
|
|
2817
|
+
elapsedSeconds = 0;
|
|
2818
|
+
const timerEl = document.getElementById('speaker-timer');
|
|
2819
|
+
if (timerEl) timerEl.textContent = '00:00';
|
|
2820
|
+
});
|
|
2821
|
+
|
|
2822
|
+
function updateSpeakerView(idx) {
|
|
2823
|
+
currentIdx = idx;
|
|
2824
|
+
const numEl = document.getElementById('speaker-slide-num');
|
|
2825
|
+
if (numEl) numEl.textContent = (currentIdx + 1) + ' / ' + slides.length;
|
|
2826
|
+
|
|
2827
|
+
// Render current slide
|
|
2828
|
+
const curBox = document.getElementById('speaker-current-box');
|
|
2829
|
+
if (curBox && slides[currentIdx]) {
|
|
2830
|
+
curBox.innerHTML = slides[currentIdx].outerHTML;
|
|
2831
|
+
curBox.firstElementChild?.classList.add('active');
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
// Render next slide
|
|
2835
|
+
const nextBox = document.getElementById('speaker-next-box');
|
|
2836
|
+
if (nextBox) {
|
|
2837
|
+
if (slides[currentIdx + 1]) {
|
|
2838
|
+
nextBox.innerHTML = slides[currentIdx + 1].outerHTML;
|
|
2839
|
+
nextBox.firstElementChild?.classList.add('active');
|
|
2840
|
+
} else {
|
|
2841
|
+
nextBox.innerHTML = '<div style="color:var(--yumia-muted); font-size:14px;">End of presentation</div>';
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
|
|
2845
|
+
// Render notes
|
|
2846
|
+
const notesBox = document.getElementById('speaker-notes-content');
|
|
2847
|
+
if (notesBox && slides[currentIdx]) {
|
|
2848
|
+
const rawNotes = slides[currentIdx].getAttribute('data-notes');
|
|
2849
|
+
notesBox.innerHTML = rawNotes ? rawNotes : '<em style="color:var(--yumia-muted);">No notes provided for this slide.</em>';
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
window.addEventListener('keydown', (e) => {
|
|
2854
|
+
if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown') {
|
|
2855
|
+
e.preventDefault();
|
|
2856
|
+
if (currentIdx < slides.length - 1) {
|
|
2857
|
+
goToSlide(currentIdx + 1);
|
|
2858
|
+
updateSpeakerView(currentIdx);
|
|
2859
|
+
}
|
|
2860
|
+
} else if (e.key === 'ArrowLeft' || e.key === 'Backspace' || e.key === 'PageUp') {
|
|
2861
|
+
e.preventDefault();
|
|
2862
|
+
if (currentIdx > 0) {
|
|
2863
|
+
goToSlide(currentIdx - 1);
|
|
2864
|
+
updateSpeakerView(currentIdx);
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
});
|
|
2868
|
+
|
|
2869
|
+
if (syncChannel) {
|
|
2870
|
+
syncChannel.onmessage = (e) => {
|
|
2871
|
+
if (e.data && typeof e.data.index === 'number') {
|
|
2872
|
+
updateSpeakerView(e.data.index);
|
|
2873
|
+
}
|
|
2874
|
+
};
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
const initialHash = parseInt(window.location.hash.replace('#', ''), 10) - 1;
|
|
2878
|
+
updateSpeakerView(isNaN(initialHash) ? 0 : initialHash);
|
|
2497
2879
|
}
|
|
2498
2880
|
|
|
2499
2881
|
function init() {
|
|
2882
|
+
if (isSpeakerMode) {
|
|
2883
|
+
initSpeakerLayout();
|
|
2884
|
+
return;
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2500
2887
|
const hash = window.location.hash.replace('#', '');
|
|
2501
2888
|
const initialIdx = parseInt(hash, 10) - 1;
|
|
2502
|
-
|
|
2889
|
+
goToSlide(isNaN(initialIdx) ? 0 : initialIdx, false);
|
|
2503
2890
|
}
|
|
2504
2891
|
|
|
2505
2892
|
window.deckController = {
|
|
2506
2893
|
init: init,
|
|
2507
|
-
next: function() {
|
|
2508
|
-
prev: function() {
|
|
2894
|
+
next: function() { goToSlide(currentIdx + 1); },
|
|
2895
|
+
prev: function() { goToSlide(currentIdx - 1); },
|
|
2509
2896
|
toggleNotes: function() { notesDrawer?.classList.toggle('open'); },
|
|
2897
|
+
toggleOverview: function() { toggleOverview(); },
|
|
2898
|
+
openSpeaker: openSpeakerWindow,
|
|
2510
2899
|
toggleFs: function() {
|
|
2511
2900
|
if (!document.fullscreenElement) {
|
|
2512
2901
|
document.documentElement.requestFullscreen().catch(() => {});
|
|
@@ -2519,19 +2908,33 @@ var HtmlRenderer = class {
|
|
|
2519
2908
|
document.getElementById('btn-next')?.addEventListener('click', window.deckController.next);
|
|
2520
2909
|
document.getElementById('btn-prev')?.addEventListener('click', window.deckController.prev);
|
|
2521
2910
|
document.getElementById('btn-notes')?.addEventListener('click', window.deckController.toggleNotes);
|
|
2911
|
+
document.getElementById('btn-overview')?.addEventListener('click', window.deckController.toggleOverview);
|
|
2912
|
+
document.getElementById('btn-close-overview')?.addEventListener('click', () => toggleOverview(false));
|
|
2913
|
+
document.getElementById('btn-speaker')?.addEventListener('click', window.deckController.openSpeaker);
|
|
2522
2914
|
document.getElementById('btn-fs')?.addEventListener('click', window.deckController.toggleFs);
|
|
2523
2915
|
|
|
2524
2916
|
window.addEventListener('keydown', function(e) {
|
|
2525
|
-
if (
|
|
2917
|
+
if (overviewModal && overviewModal.classList.contains('open')) {
|
|
2918
|
+
if (e.key === 'Escape' || e.key.toLowerCase() === 'o') {
|
|
2919
|
+
toggleOverview(false);
|
|
2920
|
+
}
|
|
2921
|
+
return;
|
|
2922
|
+
}
|
|
2923
|
+
|
|
2924
|
+
if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown' || e.key.toLowerCase() === 'l') {
|
|
2526
2925
|
e.preventDefault();
|
|
2527
2926
|
window.deckController.next();
|
|
2528
|
-
} else if (e.key === 'ArrowLeft' || e.key === 'Backspace' || e.key === 'PageUp') {
|
|
2927
|
+
} else if (e.key === 'ArrowLeft' || e.key === 'Backspace' || e.key === 'PageUp' || e.key.toLowerCase() === 'h') {
|
|
2529
2928
|
e.preventDefault();
|
|
2530
2929
|
window.deckController.prev();
|
|
2531
2930
|
} else if (e.key.toLowerCase() === 'f') {
|
|
2532
2931
|
window.deckController.toggleFs();
|
|
2932
|
+
} else if (e.key.toLowerCase() === 's') {
|
|
2933
|
+
window.deckController.openSpeaker();
|
|
2533
2934
|
} else if (e.key.toLowerCase() === 'n') {
|
|
2534
2935
|
window.deckController.toggleNotes();
|
|
2936
|
+
} else if (e.key === 'Escape' || e.key.toLowerCase() === 'o') {
|
|
2937
|
+
window.deckController.toggleOverview();
|
|
2535
2938
|
}
|
|
2536
2939
|
});
|
|
2537
2940
|
|
|
@@ -2667,6 +3070,268 @@ var HtmlRenderer = class {
|
|
|
2667
3070
|
}
|
|
2668
3071
|
};
|
|
2669
3072
|
|
|
3073
|
+
// ../renderer-pdf/dist/renderer.js
|
|
3074
|
+
import PDFDocument from "pdfkit";
|
|
3075
|
+
var PdfRenderer = class {
|
|
3076
|
+
name = "PdfRenderer";
|
|
3077
|
+
targetFormat = "pdf";
|
|
3078
|
+
async render(presentation, context = {}) {
|
|
3079
|
+
const colorOverrides = presentation.metadata.colors ? { colors: presentation.metadata.colors } : void 0;
|
|
3080
|
+
const resolvedTheme = resolveTheme(presentation.metadata.theme, colorOverrides);
|
|
3081
|
+
const theme = context.theme || resolvedTheme || defaultTheme;
|
|
3082
|
+
const is43 = presentation.metadata.aspectRatio === "4:3";
|
|
3083
|
+
const pageWidth = is43 ? 720 : 960;
|
|
3084
|
+
const pageHeight = 540;
|
|
3085
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
3086
|
+
try {
|
|
3087
|
+
const doc = new PDFDocument({
|
|
3088
|
+
autoFirstPage: false,
|
|
3089
|
+
margin: 0,
|
|
3090
|
+
info: {
|
|
3091
|
+
Title: presentation.metadata.title || "YumiaMD Presentation",
|
|
3092
|
+
Author: presentation.metadata.author || "YumiaMD",
|
|
3093
|
+
Creator: "YumiaMD Vector PDF Compiler"
|
|
3094
|
+
}
|
|
3095
|
+
});
|
|
3096
|
+
const chunks = [];
|
|
3097
|
+
doc.on("data", (chunk) => chunks.push(chunk));
|
|
3098
|
+
doc.on("end", () => {
|
|
3099
|
+
const buffer = Buffer.concat(chunks);
|
|
3100
|
+
resolvePromise({
|
|
3101
|
+
format: "pdf",
|
|
3102
|
+
data: new Uint8Array(buffer),
|
|
3103
|
+
pageCount: presentation.slides.length,
|
|
3104
|
+
slideCount: presentation.slides.length
|
|
3105
|
+
});
|
|
3106
|
+
});
|
|
3107
|
+
doc.on("error", (err) => rejectPromise(err));
|
|
3108
|
+
const totalSlides = presentation.slides.length;
|
|
3109
|
+
for (let i = 0; i < totalSlides; i++) {
|
|
3110
|
+
const slide = presentation.slides[i];
|
|
3111
|
+
this.renderSlide(doc, slide, i + 1, totalSlides, pageWidth, pageHeight, theme);
|
|
3112
|
+
}
|
|
3113
|
+
doc.end();
|
|
3114
|
+
} catch (err) {
|
|
3115
|
+
rejectPromise(err);
|
|
3116
|
+
}
|
|
3117
|
+
});
|
|
3118
|
+
}
|
|
3119
|
+
renderSlide(doc, slide, slideNum, totalSlides, pageWidth, pageHeight, theme) {
|
|
3120
|
+
doc.addPage({
|
|
3121
|
+
size: [pageWidth, pageHeight],
|
|
3122
|
+
margins: { top: 0, bottom: 0, left: 0, right: 0 }
|
|
3123
|
+
});
|
|
3124
|
+
doc.rect(0, 0, pageWidth, pageHeight).fill(theme.colors.background);
|
|
3125
|
+
const padX = pageWidth * 0.06;
|
|
3126
|
+
const padY = pageHeight * 0.08;
|
|
3127
|
+
const contentWidth = pageWidth - padX * 2;
|
|
3128
|
+
let cursorY = padY;
|
|
3129
|
+
for (const element of slide.elements) {
|
|
3130
|
+
cursorY = this.renderElement(doc, element, padX, cursorY, contentWidth, theme);
|
|
3131
|
+
cursorY += 12;
|
|
3132
|
+
}
|
|
3133
|
+
const progressWidth = slideNum / totalSlides * pageWidth;
|
|
3134
|
+
doc.rect(0, pageHeight - 4, progressWidth, 4).fill(theme.colors.primary);
|
|
3135
|
+
doc.font("Helvetica").fontSize(10).fillColor(theme.colors.muted || "#888888").text(`${slideNum} / ${totalSlides}`, pageWidth - padX - 60, pageHeight - 24, {
|
|
3136
|
+
width: 60,
|
|
3137
|
+
align: "right"
|
|
3138
|
+
});
|
|
3139
|
+
}
|
|
3140
|
+
renderElement(doc, element, x, y, width, theme) {
|
|
3141
|
+
switch (element.type) {
|
|
3142
|
+
case "heading": {
|
|
3143
|
+
const h = element;
|
|
3144
|
+
const fontSize = h.level === 1 ? 28 : h.level === 2 ? 22 : 18;
|
|
3145
|
+
const color = h.level === 1 ? theme.colors.primary : theme.colors.text;
|
|
3146
|
+
doc.font("Helvetica-Bold").fontSize(fontSize).fillColor(color);
|
|
3147
|
+
doc.text(this.stripFormatting(h.text), x, y, { width, lineGap: 4 });
|
|
3148
|
+
const height = doc.heightOfString(this.stripFormatting(h.text), { width });
|
|
3149
|
+
return y + height;
|
|
3150
|
+
}
|
|
3151
|
+
case "paragraph": {
|
|
3152
|
+
const p = element;
|
|
3153
|
+
doc.font("Helvetica").fontSize(14).fillColor(theme.colors.text);
|
|
3154
|
+
doc.text(this.stripFormatting(p.text), x, y, { width, lineGap: 4 });
|
|
3155
|
+
const height = doc.heightOfString(this.stripFormatting(p.text), { width });
|
|
3156
|
+
return y + height;
|
|
3157
|
+
}
|
|
3158
|
+
case "list": {
|
|
3159
|
+
const l = element;
|
|
3160
|
+
let currentY = y;
|
|
3161
|
+
const itemGap = 6;
|
|
3162
|
+
l.items.forEach((item, idx) => {
|
|
3163
|
+
const bullet = l.ordered ? `${idx + 1}.` : "\u2022";
|
|
3164
|
+
doc.font("Helvetica-Bold").fontSize(13).fillColor(theme.colors.primary).text(bullet, x, currentY, { width: 18 });
|
|
3165
|
+
doc.font("Helvetica").fontSize(13).fillColor(theme.colors.text).text(this.stripFormatting(item.text), x + 20, currentY, {
|
|
3166
|
+
width: width - 20,
|
|
3167
|
+
lineGap: 3
|
|
3168
|
+
});
|
|
3169
|
+
const itemHeight = Math.max(18, doc.heightOfString(this.stripFormatting(item.text), { width: width - 20 }));
|
|
3170
|
+
currentY += itemHeight + itemGap;
|
|
3171
|
+
});
|
|
3172
|
+
return currentY;
|
|
3173
|
+
}
|
|
3174
|
+
case "quote": {
|
|
3175
|
+
const q = element;
|
|
3176
|
+
const quoteText = `\u201C${this.stripFormatting(q.text)}\u201D`;
|
|
3177
|
+
const authorText = q.author ? `\u2014 ${this.stripFormatting(q.author)}` : "";
|
|
3178
|
+
doc.font("Helvetica-Oblique").fontSize(13).fillColor(theme.colors.muted || "#aaaaaa");
|
|
3179
|
+
const textHeight = doc.heightOfString(quoteText, { width: width - 24 });
|
|
3180
|
+
const authorHeight = authorText ? 18 : 0;
|
|
3181
|
+
const totalHeight = textHeight + authorHeight + 16;
|
|
3182
|
+
doc.roundedRect(x, y, width, totalHeight, 6).fill(theme.colors.surface || "rgba(255,255,255,0.05)");
|
|
3183
|
+
doc.rect(x, y, 4, totalHeight).fill(theme.colors.accent || theme.colors.primary);
|
|
3184
|
+
doc.font("Helvetica-Oblique").fontSize(13).fillColor(theme.colors.text).text(quoteText, x + 16, y + 8, { width: width - 28 });
|
|
3185
|
+
if (authorText) {
|
|
3186
|
+
doc.font("Helvetica").fontSize(11).fillColor(theme.colors.muted || "#888888").text(authorText, x + 16, y + 8 + textHeight + 4, { width: width - 28 });
|
|
3187
|
+
}
|
|
3188
|
+
return y + totalHeight;
|
|
3189
|
+
}
|
|
3190
|
+
case "code": {
|
|
3191
|
+
const c = element;
|
|
3192
|
+
const codeText = c.code;
|
|
3193
|
+
doc.font("Courier").fontSize(11);
|
|
3194
|
+
const textHeight = doc.heightOfString(codeText, { width: width - 24 });
|
|
3195
|
+
const boxHeight = textHeight + 20;
|
|
3196
|
+
doc.roundedRect(x, y, width, boxHeight, 6).fill("#0a0a10").strokeColor(theme.colors.border || "rgba(255,255,255,0.1)").stroke();
|
|
3197
|
+
doc.font("Courier").fontSize(11).fillColor(theme.colors.accent || "#38bdf8").text(codeText, x + 12, y + 10, { width: width - 24 });
|
|
3198
|
+
return y + boxHeight;
|
|
3199
|
+
}
|
|
3200
|
+
case "card": {
|
|
3201
|
+
const card = element;
|
|
3202
|
+
const variantColor = this.getVariantColor(card.variant, theme);
|
|
3203
|
+
const cardPad = 14;
|
|
3204
|
+
let cardCursorY = y + cardPad;
|
|
3205
|
+
if (card.title) {
|
|
3206
|
+
doc.font("Helvetica-Bold").fontSize(15).fillColor(variantColor).text(this.stripFormatting(card.title), x + cardPad, cardCursorY, {
|
|
3207
|
+
width: width - cardPad * 2
|
|
3208
|
+
});
|
|
3209
|
+
cardCursorY += 22;
|
|
3210
|
+
}
|
|
3211
|
+
if (card.elements) {
|
|
3212
|
+
for (const child of card.elements) {
|
|
3213
|
+
cardCursorY = this.renderElement(doc, child, x + cardPad, cardCursorY, width - cardPad * 2, theme);
|
|
3214
|
+
cardCursorY += 8;
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
const totalCardHeight = Math.max(70, cardCursorY - y + cardPad);
|
|
3218
|
+
doc.save();
|
|
3219
|
+
doc.roundedRect(x, y, width, totalCardHeight, 8).fill(theme.colors.surface || "rgba(255,255,255,0.06)");
|
|
3220
|
+
doc.roundedRect(x, y, width, totalCardHeight, 8).lineWidth(1.5).strokeColor(variantColor).stroke();
|
|
3221
|
+
doc.restore();
|
|
3222
|
+
let renderTop = y + cardPad;
|
|
3223
|
+
if (card.title) {
|
|
3224
|
+
doc.font("Helvetica-Bold").fontSize(15).fillColor(variantColor).text(this.stripFormatting(card.title), x + cardPad, renderTop, {
|
|
3225
|
+
width: width - cardPad * 2
|
|
3226
|
+
});
|
|
3227
|
+
renderTop += 22;
|
|
3228
|
+
}
|
|
3229
|
+
if (card.elements) {
|
|
3230
|
+
for (const child of card.elements) {
|
|
3231
|
+
renderTop = this.renderElement(doc, child, x + cardPad, renderTop, width - cardPad * 2, theme);
|
|
3232
|
+
renderTop += 8;
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
return y + totalCardHeight;
|
|
3236
|
+
}
|
|
3237
|
+
case "metric": {
|
|
3238
|
+
const m = element;
|
|
3239
|
+
const variantColor = this.getVariantColor(m.variant, theme);
|
|
3240
|
+
const boxHeight = 90;
|
|
3241
|
+
doc.roundedRect(x, y, width, boxHeight, 8).fill(theme.colors.surface || "rgba(255,255,255,0.06)");
|
|
3242
|
+
doc.roundedRect(x, y, width, boxHeight, 8).lineWidth(1.5).strokeColor(variantColor).stroke();
|
|
3243
|
+
doc.font("Helvetica-Bold").fontSize(11).fillColor(theme.colors.muted || "#888888").text(m.label.toUpperCase(), x + 16, y + 14, { width: width - 32 });
|
|
3244
|
+
const displayVal = m.unit ? `${m.value} ${m.unit}` : m.value;
|
|
3245
|
+
doc.font("Helvetica-Bold").fontSize(28).fillColor(theme.colors.primary).text(displayVal, x + 16, y + 32, { width: width - 32 });
|
|
3246
|
+
if (m.change) {
|
|
3247
|
+
const changeColor = m.change.startsWith("+") ? "#10b981" : "#ef4444";
|
|
3248
|
+
doc.font("Helvetica-Bold").fontSize(12).fillColor(changeColor).text(m.change, x + width - 90, y + 38, { width: 74, align: "right" });
|
|
3249
|
+
}
|
|
3250
|
+
return y + boxHeight;
|
|
3251
|
+
}
|
|
3252
|
+
case "columns": {
|
|
3253
|
+
const cols = element;
|
|
3254
|
+
const colCount = cols.columns.length;
|
|
3255
|
+
const gap = 16;
|
|
3256
|
+
const availableWidth = width - gap * (colCount - 1);
|
|
3257
|
+
let ratios = Array(colCount).fill(1);
|
|
3258
|
+
if (cols.ratios) {
|
|
3259
|
+
const parsed = cols.ratios.split(":").map((r) => parseFloat(r) || 1);
|
|
3260
|
+
if (parsed.length === colCount)
|
|
3261
|
+
ratios = parsed;
|
|
3262
|
+
}
|
|
3263
|
+
const totalRatio = ratios.reduce((a, b) => a + b, 0);
|
|
3264
|
+
let curX = x;
|
|
3265
|
+
let maxY = y;
|
|
3266
|
+
for (let i = 0; i < colCount; i++) {
|
|
3267
|
+
const col = cols.columns[i];
|
|
3268
|
+
const colWidth = ratios[i] / totalRatio * availableWidth;
|
|
3269
|
+
let colCursorY = y;
|
|
3270
|
+
for (const child of col.elements) {
|
|
3271
|
+
colCursorY = this.renderElement(doc, child, curX, colCursorY, colWidth, theme);
|
|
3272
|
+
colCursorY += 8;
|
|
3273
|
+
}
|
|
3274
|
+
if (colCursorY > maxY)
|
|
3275
|
+
maxY = colCursorY;
|
|
3276
|
+
curX += colWidth + gap;
|
|
3277
|
+
}
|
|
3278
|
+
return maxY;
|
|
3279
|
+
}
|
|
3280
|
+
case "table": {
|
|
3281
|
+
const t = element;
|
|
3282
|
+
const headers = t.headers || [];
|
|
3283
|
+
const rows = t.rows || [];
|
|
3284
|
+
const colCount = Math.max(headers.length, ...rows.map((r) => r.length), 1);
|
|
3285
|
+
const colWidth = width / colCount;
|
|
3286
|
+
const rowHeight = 26;
|
|
3287
|
+
let curY = y;
|
|
3288
|
+
if (headers.length > 0) {
|
|
3289
|
+
doc.rect(x, curY, width, rowHeight).fill(theme.colors.primary);
|
|
3290
|
+
headers.forEach((h, idx) => {
|
|
3291
|
+
doc.font("Helvetica-Bold").fontSize(12).fillColor("#ffffff").text(this.stripFormatting(h), x + idx * colWidth + 6, curY + 6, {
|
|
3292
|
+
width: colWidth - 12
|
|
3293
|
+
});
|
|
3294
|
+
});
|
|
3295
|
+
curY += rowHeight;
|
|
3296
|
+
}
|
|
3297
|
+
rows.forEach((row, rowIdx) => {
|
|
3298
|
+
const isEven = rowIdx % 2 === 0;
|
|
3299
|
+
const rowBg = isEven ? theme.colors.surface || "rgba(255,255,255,0.04)" : "rgba(255,255,255,0.01)";
|
|
3300
|
+
doc.rect(x, curY, width, rowHeight).fill(rowBg);
|
|
3301
|
+
row.forEach((cell, idx) => {
|
|
3302
|
+
doc.font("Helvetica").fontSize(11).fillColor(theme.colors.text).text(this.stripFormatting(cell), x + idx * colWidth + 6, curY + 6, {
|
|
3303
|
+
width: colWidth - 12
|
|
3304
|
+
});
|
|
3305
|
+
});
|
|
3306
|
+
curY += rowHeight;
|
|
3307
|
+
});
|
|
3308
|
+
doc.rect(x, y, width, curY - y).strokeColor(theme.colors.border || "rgba(255,255,255,0.1)").stroke();
|
|
3309
|
+
return curY;
|
|
3310
|
+
}
|
|
3311
|
+
default:
|
|
3312
|
+
return y;
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
getVariantColor(variant, theme) {
|
|
3316
|
+
switch (variant) {
|
|
3317
|
+
case "warning":
|
|
3318
|
+
return theme.colors.warning || "#f59e0b";
|
|
3319
|
+
case "info":
|
|
3320
|
+
return theme.colors.info || "#3b82f6";
|
|
3321
|
+
case "success":
|
|
3322
|
+
return theme.colors.success || "#10b981";
|
|
3323
|
+
case "danger":
|
|
3324
|
+
return theme.colors.danger || "#ef4444";
|
|
3325
|
+
case "primary":
|
|
3326
|
+
default:
|
|
3327
|
+
return theme.colors.primary;
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
3330
|
+
stripFormatting(text) {
|
|
3331
|
+
return text.replace(/\*\*(.*?)\*\*/g, "$1").replace(/\*(.*?)\*/g, "$1").replace(/`(.*?)`/g, "$1");
|
|
3332
|
+
}
|
|
3333
|
+
};
|
|
3334
|
+
|
|
2670
3335
|
// src/dev-server.ts
|
|
2671
3336
|
import { createServer } from "http";
|
|
2672
3337
|
import { readFileSync, watch } from "fs";
|
|
@@ -2766,7 +3431,7 @@ function startDevServer(filePath, options = {}) {
|
|
|
2766
3431
|
// src/cli.ts
|
|
2767
3432
|
import { mkdirSync, readFileSync as readFileSync2, watch as fsWatch, writeFileSync } from "fs";
|
|
2768
3433
|
import { basename, dirname, extname, join, resolve as resolve2 } from "path";
|
|
2769
|
-
var VERSION = "0.1.
|
|
3434
|
+
var VERSION = "0.1.13";
|
|
2770
3435
|
function printHelp() {
|
|
2771
3436
|
return `
|
|
2772
3437
|
YumiaMD \u2014 Markdown-based presentation compiler (v${VERSION})
|
|
@@ -2782,7 +3447,7 @@ Commands:
|
|
|
2782
3447
|
lint <file> Analyze presentation for layout overflows and accessibility
|
|
2783
3448
|
inspect <file> Inspect the AST and geometric layout tree
|
|
2784
3449
|
schema Output machine-readable JSON schema for AI agents
|
|
2785
|
-
build <file> Compile a presentation to PowerPoint (.pptx) or HTML (.html)
|
|
3450
|
+
build <file> Compile a presentation to PowerPoint (.pptx), PDF (.pdf), or HTML (.html)
|
|
2786
3451
|
|
|
2787
3452
|
Theming & Color Options:
|
|
2788
3453
|
--theme, -t <name> Base theme: default | cyberpunk | minimal | corporate | terminal | academic
|
|
@@ -2797,7 +3462,7 @@ Server & Compiler Options:
|
|
|
2797
3462
|
--open Open default browser automatically in dev mode
|
|
2798
3463
|
--watch, -w Watch for file changes during compilation
|
|
2799
3464
|
--out, -o <file> Specify output file path (default: dist/<name>.<format>)
|
|
2800
|
-
--format, -f <fmt> Target output format: pptx (default) | html
|
|
3465
|
+
--format, -f <fmt> Target output format: pptx (default) | pdf | html
|
|
2801
3466
|
--strict Enforce zero warnings in 'lint' (exits with code 1 on warning)
|
|
2802
3467
|
--json Output results formatted as JSON for CI/CD and AI tools
|
|
2803
3468
|
--layout Show computed geometric bounding boxes in 'inspect'
|
|
@@ -3120,7 +3785,8 @@ ${summary}${isStrict && report.warnings.length > 0 ? " (failed due to --strict)"
|
|
|
3120
3785
|
try {
|
|
3121
3786
|
const resolvedInput = resolve2(process.cwd(), target);
|
|
3122
3787
|
const isHtml = cliFormat === "html";
|
|
3123
|
-
const
|
|
3788
|
+
const isPdf = cliFormat === "pdf";
|
|
3789
|
+
const targetExtension = isHtml ? ".html" : isPdf ? ".pdf" : ".pptx";
|
|
3124
3790
|
let outputPath = "";
|
|
3125
3791
|
const outFlagIndex = args.findIndex((a) => a === "--out" || a === "-o");
|
|
3126
3792
|
if (outFlagIndex !== -1 && args[outFlagIndex + 1]) {
|
|
@@ -3149,6 +3815,14 @@ ${summary}${isStrict && report.warnings.length > 0 ? " (failed due to --strict)"
|
|
|
3149
3815
|
});
|
|
3150
3816
|
writeFileSync(outputPath, result2.html, "utf-8");
|
|
3151
3817
|
return { slideCount: result2.slideCount, format: result2.format };
|
|
3818
|
+
} else if (isPdf) {
|
|
3819
|
+
const pdfRenderer = new PdfRenderer();
|
|
3820
|
+
const result2 = await compiler.compile(source, pdfRenderer, {
|
|
3821
|
+
...renderTheme ? { renderContext: { theme: renderTheme } } : {}
|
|
3822
|
+
});
|
|
3823
|
+
const buffer = result2.data instanceof Uint8Array ? Buffer.from(result2.data) : Buffer.from(new Uint8Array(result2.data));
|
|
3824
|
+
writeFileSync(outputPath, buffer);
|
|
3825
|
+
return { slideCount: result2.slideCount, format: result2.format };
|
|
3152
3826
|
} else {
|
|
3153
3827
|
const pptxRenderer = new PptxRenderer();
|
|
3154
3828
|
const result2 = await compiler.compile(source, pptxRenderer, {
|
|
@@ -3186,9 +3860,10 @@ ${summary}${isStrict && report.warnings.length > 0 ? " (failed due to --strict)"
|
|
|
3186
3860
|
};
|
|
3187
3861
|
}
|
|
3188
3862
|
const watchMsg = command === "watch" || isWatch ? " [Watching for changes...]" : "";
|
|
3863
|
+
const formatDesc = isHtml ? "interactive HTML slides" : isPdf ? "vector PDF slides" : "native editable slides";
|
|
3189
3864
|
return {
|
|
3190
3865
|
exitCode: 0,
|
|
3191
|
-
output: `\u2713 Successfully compiled '${target}' \u2794 '${outputPath}' (${result.slideCount} ${
|
|
3866
|
+
output: `\u2713 Successfully compiled '${target}' \u2794 '${outputPath}' (${result.slideCount} ${formatDesc})${watchMsg}`
|
|
3192
3867
|
};
|
|
3193
3868
|
} catch (err) {
|
|
3194
3869
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -3243,6 +3918,7 @@ export {
|
|
|
3243
3918
|
parseInlineMarkdown,
|
|
3244
3919
|
PptxRenderer,
|
|
3245
3920
|
HtmlRenderer,
|
|
3921
|
+
PdfRenderer,
|
|
3246
3922
|
startDevServer,
|
|
3247
3923
|
VERSION,
|
|
3248
3924
|
printHelp,
|
package/dist/index.d.ts
CHANGED
|
@@ -7,8 +7,9 @@ export * from '@yumiamd/layout';
|
|
|
7
7
|
export * from '@yumiamd/renderer';
|
|
8
8
|
export * from '@yumiamd/renderer-pptx';
|
|
9
9
|
export * from '@yumiamd/renderer-html';
|
|
10
|
+
export * from '@yumiamd/renderer-pdf';
|
|
10
11
|
|
|
11
|
-
declare const VERSION = "0.1.
|
|
12
|
+
declare const VERSION = "0.1.13";
|
|
12
13
|
declare function printHelp(): string;
|
|
13
14
|
declare function runCli(argv: string[]): Promise<{
|
|
14
15
|
exitCode: number;
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
DefaultLayoutEngine,
|
|
4
4
|
DefaultYumiaParser,
|
|
5
5
|
HtmlRenderer,
|
|
6
|
+
PdfRenderer,
|
|
6
7
|
PptxRenderer,
|
|
7
8
|
THEME_REGISTRY,
|
|
8
9
|
VERSION,
|
|
@@ -38,12 +39,13 @@ import {
|
|
|
38
39
|
runCli,
|
|
39
40
|
startDevServer,
|
|
40
41
|
terminalTheme
|
|
41
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-6GEDN7OB.js";
|
|
42
43
|
export {
|
|
43
44
|
DEFAULT_VIEWPORT,
|
|
44
45
|
DefaultLayoutEngine,
|
|
45
46
|
DefaultYumiaParser,
|
|
46
47
|
HtmlRenderer,
|
|
48
|
+
PdfRenderer,
|
|
47
49
|
PptxRenderer,
|
|
48
50
|
THEME_REGISTRY,
|
|
49
51
|
VERSION,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yumiamd",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "Command line interface and compiler for YumiaMD presentations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -16,18 +16,20 @@
|
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
+
"pdfkit": "^0.20.2",
|
|
19
20
|
"pptxgenjs": "^4.0.1"
|
|
20
21
|
},
|
|
21
22
|
"devDependencies": {
|
|
22
23
|
"tsup": "^8.5.1",
|
|
23
|
-
"@yumiamd/
|
|
24
|
-
"@yumiamd/
|
|
25
|
-
"@yumiamd/
|
|
26
|
-
"@yumiamd/
|
|
27
|
-
"@yumiamd/
|
|
28
|
-
"@yumiamd/renderer-
|
|
29
|
-
"@yumiamd/renderer-
|
|
30
|
-
"@yumiamd/
|
|
24
|
+
"@yumiamd/core": "0.1.13",
|
|
25
|
+
"@yumiamd/parser": "0.1.13",
|
|
26
|
+
"@yumiamd/renderer": "0.1.13",
|
|
27
|
+
"@yumiamd/ast": "0.1.13",
|
|
28
|
+
"@yumiamd/layout": "0.1.13",
|
|
29
|
+
"@yumiamd/renderer-html": "0.1.13",
|
|
30
|
+
"@yumiamd/renderer-pdf": "0.1.13",
|
|
31
|
+
"@yumiamd/renderer-pptx": "0.1.13",
|
|
32
|
+
"@yumiamd/theme": "0.1.13"
|
|
31
33
|
},
|
|
32
34
|
"keywords": [
|
|
33
35
|
"yumia",
|
|
@@ -53,7 +55,7 @@
|
|
|
53
55
|
"LICENSE"
|
|
54
56
|
],
|
|
55
57
|
"scripts": {
|
|
56
|
-
"build": "tsup src/bin.ts src/index.ts --format esm --dts --clean --external pptxgenjs",
|
|
58
|
+
"build": "tsup src/bin.ts src/index.ts --format esm --dts --clean --external pptxgenjs,pdfkit",
|
|
57
59
|
"typecheck": "tsc --noEmit"
|
|
58
60
|
}
|
|
59
61
|
}
|