yumiamd 0.1.10 → 0.1.14
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 +4 -2
- package/dist/{chunk-PQYPVPV2.js → chunk-ZOJEPOPI.js} +749 -41
- package/dist/index.d.ts +3 -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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runCli
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZOJEPOPI.js";
|
|
5
5
|
|
|
6
6
|
// src/bin.ts
|
|
7
7
|
var result = await runCli(process.argv);
|
|
@@ -12,4 +12,6 @@ if (result.output) {
|
|
|
12
12
|
console.error(result.output);
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
-
|
|
15
|
+
if (!result.keepAlive) {
|
|
16
|
+
process.exit(result.exitCode);
|
|
17
|
+
}
|
|
@@ -1541,11 +1541,33 @@ var CSS_NAMED_COLORS = {
|
|
|
1541
1541
|
function cleanFontFace(fontString) {
|
|
1542
1542
|
if (!fontString)
|
|
1543
1543
|
return "Segoe UI";
|
|
1544
|
-
const
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1544
|
+
const fonts = fontString.split(",").map((f) => f.trim().replace(/^['"]|['"]$/g, ""));
|
|
1545
|
+
const safeList = [
|
|
1546
|
+
"Segoe UI",
|
|
1547
|
+
"Arial",
|
|
1548
|
+
"Calibri",
|
|
1549
|
+
"Helvetica",
|
|
1550
|
+
"Verdana",
|
|
1551
|
+
"Tahoma",
|
|
1552
|
+
"Trebuchet MS",
|
|
1553
|
+
"Consolas",
|
|
1554
|
+
"Courier New",
|
|
1555
|
+
"Courier",
|
|
1556
|
+
"Georgia",
|
|
1557
|
+
"Times New Roman"
|
|
1558
|
+
];
|
|
1559
|
+
for (const font of fonts) {
|
|
1560
|
+
if (safeList.includes(font)) {
|
|
1561
|
+
return font;
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
if (fontString.toLowerCase().includes("mono") || fontString.toLowerCase().includes("code")) {
|
|
1565
|
+
return "Consolas";
|
|
1566
|
+
}
|
|
1567
|
+
if (fontString.toLowerCase().includes("serif") && !fontString.toLowerCase().includes("sans")) {
|
|
1568
|
+
return "Georgia";
|
|
1569
|
+
}
|
|
1570
|
+
return "Segoe UI";
|
|
1549
1571
|
}
|
|
1550
1572
|
function parseInlineMarkdown(rawText, baseOptions) {
|
|
1551
1573
|
const chunks = [];
|
|
@@ -2393,15 +2415,16 @@ var HtmlRenderer = class {
|
|
|
2393
2415
|
right: 24px;
|
|
2394
2416
|
display: flex;
|
|
2395
2417
|
align-items: center;
|
|
2396
|
-
gap:
|
|
2397
|
-
background: rgba(15, 23, 42, 0.
|
|
2418
|
+
gap: 10px;
|
|
2419
|
+
background: rgba(15, 23, 42, 0.88);
|
|
2398
2420
|
backdrop-filter: blur(12px);
|
|
2399
|
-
padding:
|
|
2421
|
+
padding: 6px 14px;
|
|
2400
2422
|
border-radius: 30px;
|
|
2401
2423
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
2402
2424
|
z-index: 1000;
|
|
2403
2425
|
font-size: 13px;
|
|
2404
2426
|
color: #e2e8f0;
|
|
2427
|
+
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5);
|
|
2405
2428
|
}
|
|
2406
2429
|
|
|
2407
2430
|
.yumia-btn {
|
|
@@ -2409,17 +2432,18 @@ var HtmlRenderer = class {
|
|
|
2409
2432
|
border: none;
|
|
2410
2433
|
color: #f8fafc;
|
|
2411
2434
|
cursor: pointer;
|
|
2412
|
-
padding:
|
|
2435
|
+
padding: 5px 9px;
|
|
2413
2436
|
border-radius: 6px;
|
|
2414
2437
|
font-size: 14px;
|
|
2415
2438
|
display: flex;
|
|
2416
2439
|
align-items: center;
|
|
2417
2440
|
justify-content: center;
|
|
2418
|
-
transition: background 0.15s ease;
|
|
2441
|
+
transition: background 0.15s ease, transform 0.1s ease;
|
|
2419
2442
|
}
|
|
2420
2443
|
|
|
2421
2444
|
.yumia-btn:hover {
|
|
2422
2445
|
background: rgba(255, 255, 255, 0.15);
|
|
2446
|
+
transform: translateY(-1px);
|
|
2423
2447
|
}
|
|
2424
2448
|
|
|
2425
2449
|
.yumia-progress-bar {
|
|
@@ -2437,47 +2461,291 @@ var HtmlRenderer = class {
|
|
|
2437
2461
|
bottom: 0;
|
|
2438
2462
|
left: 0;
|
|
2439
2463
|
right: 0;
|
|
2440
|
-
max-height:
|
|
2441
|
-
background: rgba(10, 10, 18, 0.
|
|
2464
|
+
max-height: 240px;
|
|
2465
|
+
background: rgba(10, 10, 18, 0.96);
|
|
2442
2466
|
backdrop-filter: blur(16px);
|
|
2443
|
-
border-top:
|
|
2444
|
-
padding:
|
|
2467
|
+
border-top: 2px solid var(--yumia-primary);
|
|
2468
|
+
padding: 18px 28px;
|
|
2445
2469
|
overflow-y: auto;
|
|
2446
2470
|
display: none;
|
|
2447
2471
|
z-index: 2000;
|
|
2448
|
-
font-size:
|
|
2472
|
+
font-size: 15px;
|
|
2449
2473
|
line-height: 1.6;
|
|
2450
2474
|
color: #cbd5e1;
|
|
2475
|
+
box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.5);
|
|
2451
2476
|
}
|
|
2452
2477
|
|
|
2453
2478
|
.yumia-notes-drawer.open {
|
|
2454
2479
|
display: block;
|
|
2455
2480
|
}
|
|
2481
|
+
|
|
2482
|
+
/* Slide Overview Modal */
|
|
2483
|
+
.yumia-overview-modal {
|
|
2484
|
+
position: fixed;
|
|
2485
|
+
top: 0;
|
|
2486
|
+
left: 0;
|
|
2487
|
+
width: 100vw;
|
|
2488
|
+
height: 100vh;
|
|
2489
|
+
background: rgba(5, 5, 10, 0.94);
|
|
2490
|
+
backdrop-filter: blur(20px);
|
|
2491
|
+
z-index: 3000;
|
|
2492
|
+
display: none;
|
|
2493
|
+
flex-direction: column;
|
|
2494
|
+
padding: 40px;
|
|
2495
|
+
overflow-y: auto;
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
.yumia-overview-modal.open {
|
|
2499
|
+
display: flex;
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
.yumia-overview-header {
|
|
2503
|
+
display: flex;
|
|
2504
|
+
justify-content: space-between;
|
|
2505
|
+
align-items: center;
|
|
2506
|
+
margin-bottom: 30px;
|
|
2507
|
+
color: #fff;
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
.yumia-overview-grid {
|
|
2511
|
+
display: grid;
|
|
2512
|
+
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
2513
|
+
gap: 24px;
|
|
2514
|
+
width: 100%;
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
.yumia-overview-card {
|
|
2518
|
+
aspect-ratio: var(--yumia-ratio);
|
|
2519
|
+
background-color: var(--yumia-bg);
|
|
2520
|
+
border: 2px solid rgba(255, 255, 255, 0.1);
|
|
2521
|
+
border-radius: 8px;
|
|
2522
|
+
padding: 16px;
|
|
2523
|
+
cursor: pointer;
|
|
2524
|
+
position: relative;
|
|
2525
|
+
overflow: hidden;
|
|
2526
|
+
transform-origin: center;
|
|
2527
|
+
transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
|
|
2528
|
+
display: flex;
|
|
2529
|
+
flex-direction: column;
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
.yumia-overview-card:hover {
|
|
2533
|
+
transform: scale(1.04);
|
|
2534
|
+
border-color: var(--yumia-primary);
|
|
2535
|
+
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.8), 0 0 15px var(--yumia-primary);
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
.yumia-overview-card.current {
|
|
2539
|
+
border-color: var(--yumia-accent);
|
|
2540
|
+
box-shadow: 0 0 0 2px var(--yumia-accent);
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
.yumia-overview-badge {
|
|
2544
|
+
position: absolute;
|
|
2545
|
+
top: 8px;
|
|
2546
|
+
right: 8px;
|
|
2547
|
+
background: rgba(0, 0, 0, 0.6);
|
|
2548
|
+
color: #fff;
|
|
2549
|
+
font-size: 11px;
|
|
2550
|
+
font-weight: 700;
|
|
2551
|
+
padding: 2px 8px;
|
|
2552
|
+
border-radius: 10px;
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
/* Speaker View Layout (When loaded in speaker mode ?speaker=true) */
|
|
2556
|
+
.speaker-layout {
|
|
2557
|
+
display: grid;
|
|
2558
|
+
grid-template-rows: 60px 1fr;
|
|
2559
|
+
width: 100vw;
|
|
2560
|
+
height: 100vh;
|
|
2561
|
+
background: #09090f;
|
|
2562
|
+
color: #f8fafc;
|
|
2563
|
+
overflow: hidden;
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
.speaker-topbar {
|
|
2567
|
+
display: flex;
|
|
2568
|
+
justify-content: space-between;
|
|
2569
|
+
align-items: center;
|
|
2570
|
+
padding: 0 24px;
|
|
2571
|
+
background: #11111b;
|
|
2572
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
.speaker-timer-group {
|
|
2576
|
+
display: flex;
|
|
2577
|
+
align-items: center;
|
|
2578
|
+
gap: 16px;
|
|
2579
|
+
font-family: var(--yumia-font-code);
|
|
2580
|
+
font-size: 18px;
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
.speaker-timer-btn {
|
|
2584
|
+
background: rgba(255, 255, 255, 0.1);
|
|
2585
|
+
color: #fff;
|
|
2586
|
+
border: none;
|
|
2587
|
+
padding: 4px 10px;
|
|
2588
|
+
border-radius: 4px;
|
|
2589
|
+
cursor: pointer;
|
|
2590
|
+
font-size: 12px;
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
.speaker-timer-btn:hover {
|
|
2594
|
+
background: var(--yumia-primary);
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
.speaker-main-grid {
|
|
2598
|
+
display: grid;
|
|
2599
|
+
grid-template-columns: 55% 45%;
|
|
2600
|
+
gap: 20px;
|
|
2601
|
+
padding: 20px;
|
|
2602
|
+
height: calc(100vh - 60px);
|
|
2603
|
+
box-sizing: border-box;
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
.speaker-pane {
|
|
2607
|
+
background: #151522;
|
|
2608
|
+
border-radius: 10px;
|
|
2609
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
2610
|
+
padding: 16px;
|
|
2611
|
+
display: flex;
|
|
2612
|
+
flex-direction: column;
|
|
2613
|
+
overflow: hidden;
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
.speaker-pane-title {
|
|
2617
|
+
font-size: 12px;
|
|
2618
|
+
font-weight: 700;
|
|
2619
|
+
text-transform: uppercase;
|
|
2620
|
+
letter-spacing: 0.05em;
|
|
2621
|
+
color: var(--yumia-muted);
|
|
2622
|
+
margin-bottom: 12px;
|
|
2623
|
+
display: flex;
|
|
2624
|
+
justify-content: space-between;
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
.speaker-preview-box {
|
|
2628
|
+
flex: 1;
|
|
2629
|
+
position: relative;
|
|
2630
|
+
display: flex;
|
|
2631
|
+
align-items: center;
|
|
2632
|
+
justify-content: center;
|
|
2633
|
+
background: #000;
|
|
2634
|
+
border-radius: 8px;
|
|
2635
|
+
overflow: hidden;
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
.speaker-preview-box .yumia-slide-wrapper {
|
|
2639
|
+
display: flex !important;
|
|
2640
|
+
transform: scale(0.6);
|
|
2641
|
+
width: 160% !important;
|
|
2642
|
+
height: 160% !important;
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
.speaker-notes-box {
|
|
2646
|
+
flex: 1;
|
|
2647
|
+
background: #11111a;
|
|
2648
|
+
border-radius: 8px;
|
|
2649
|
+
padding: 16px;
|
|
2650
|
+
font-size: 16px;
|
|
2651
|
+
line-height: 1.7;
|
|
2652
|
+
color: #e2e8f0;
|
|
2653
|
+
overflow-y: auto;
|
|
2654
|
+
}
|
|
2456
2655
|
</style>
|
|
2457
2656
|
</head>
|
|
2458
2657
|
<body>
|
|
2459
|
-
<div id="
|
|
2460
|
-
|
|
2658
|
+
<div id="deck-container">
|
|
2659
|
+
<div id="yumia-deck">
|
|
2660
|
+
${slidesHtml}
|
|
2661
|
+
</div>
|
|
2662
|
+
|
|
2663
|
+
<div class="yumia-controls" id="deck-controls">
|
|
2664
|
+
<button class="yumia-btn" id="btn-prev" title="Previous Slide (\u2190)">\u25C0</button>
|
|
2665
|
+
<span id="slide-indicator">1 / ${presentation.slides.length}</span>
|
|
2666
|
+
<button class="yumia-btn" id="btn-next" title="Next Slide (\u2192)">\u25B6</button>
|
|
2667
|
+
<button class="yumia-btn" id="btn-overview" title="Slide Overview (ESC / O)">\u25A6</button>
|
|
2668
|
+
<button class="yumia-btn" id="btn-speaker" title="Speaker View (S)">\u{1F3A4}</button>
|
|
2669
|
+
<button class="yumia-btn" id="btn-notes" title="Toggle Notes (N)">\u{1F4DD}</button>
|
|
2670
|
+
<button class="yumia-btn" id="btn-fs" title="Fullscreen (F)">\u26F6</button>
|
|
2671
|
+
</div>
|
|
2672
|
+
|
|
2673
|
+
<div id="notes-drawer" class="yumia-notes-drawer"></div>
|
|
2674
|
+
|
|
2675
|
+
<div id="overview-modal" class="yumia-overview-modal">
|
|
2676
|
+
<div class="yumia-overview-header">
|
|
2677
|
+
<h2>Slide Overview</h2>
|
|
2678
|
+
<button class="yumia-btn" id="btn-close-overview" style="font-size: 18px;">\u2715 Close (ESC)</button>
|
|
2679
|
+
</div>
|
|
2680
|
+
<div class="yumia-overview-grid" id="overview-grid"></div>
|
|
2681
|
+
</div>
|
|
2461
2682
|
</div>
|
|
2462
2683
|
|
|
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
2684
|
<script>
|
|
2474
2685
|
(function() {
|
|
2475
|
-
const
|
|
2686
|
+
const isSpeakerMode = new URLSearchParams(window.location.search).get('speaker') === 'true';
|
|
2687
|
+
const slides = Array.from(document.querySelectorAll('.yumia-slide-wrapper'));
|
|
2476
2688
|
const indicator = document.getElementById('slide-indicator');
|
|
2477
2689
|
const notesDrawer = document.getElementById('notes-drawer');
|
|
2690
|
+
const overviewModal = document.getElementById('overview-modal');
|
|
2691
|
+
const overviewGrid = document.getElementById('overview-grid');
|
|
2478
2692
|
let currentIdx = 0;
|
|
2479
2693
|
|
|
2480
|
-
|
|
2694
|
+
// Real-time synchronization channel
|
|
2695
|
+
let syncChannel = null;
|
|
2696
|
+
try {
|
|
2697
|
+
syncChannel = new BroadcastChannel('yumia_presentation_sync');
|
|
2698
|
+
syncChannel.onmessage = function(e) {
|
|
2699
|
+
if (e.data && typeof e.data.index === 'number') {
|
|
2700
|
+
goToSlide(e.data.index, false);
|
|
2701
|
+
}
|
|
2702
|
+
};
|
|
2703
|
+
} catch (err) {
|
|
2704
|
+
// Fallback if BroadcastChannel unavailable
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
function buildOverviewGrid() {
|
|
2708
|
+
if (!overviewGrid) return;
|
|
2709
|
+
overviewGrid.innerHTML = '';
|
|
2710
|
+
slides.forEach((slide, idx) => {
|
|
2711
|
+
const card = document.createElement('div');
|
|
2712
|
+
card.className = 'yumia-overview-card' + (idx === currentIdx ? ' current' : '');
|
|
2713
|
+
const badge = document.createElement('span');
|
|
2714
|
+
badge.className = 'yumia-overview-badge';
|
|
2715
|
+
badge.textContent = (idx + 1);
|
|
2716
|
+
|
|
2717
|
+
const heading = slide.querySelector('h1, h2, h3');
|
|
2718
|
+
const titleText = heading ? heading.textContent : 'Slide ' + (idx + 1);
|
|
2719
|
+
|
|
2720
|
+
const titleDiv = document.createElement('div');
|
|
2721
|
+
titleDiv.style.fontWeight = '600';
|
|
2722
|
+
titleDiv.style.fontSize = '14px';
|
|
2723
|
+
titleDiv.style.color = 'var(--yumia-primary)';
|
|
2724
|
+
titleDiv.textContent = titleText;
|
|
2725
|
+
|
|
2726
|
+
card.appendChild(badge);
|
|
2727
|
+
card.appendChild(titleDiv);
|
|
2728
|
+
|
|
2729
|
+
card.addEventListener('click', () => {
|
|
2730
|
+
goToSlide(idx);
|
|
2731
|
+
toggleOverview(false);
|
|
2732
|
+
});
|
|
2733
|
+
overviewGrid.appendChild(card);
|
|
2734
|
+
});
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
function toggleOverview(force) {
|
|
2738
|
+
if (!overviewModal) return;
|
|
2739
|
+
const isOpen = force !== undefined ? force : !overviewModal.classList.contains('open');
|
|
2740
|
+
if (isOpen) {
|
|
2741
|
+
buildOverviewGrid();
|
|
2742
|
+
overviewModal.classList.add('open');
|
|
2743
|
+
} else {
|
|
2744
|
+
overviewModal.classList.remove('open');
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
function goToSlide(newIdx, broadcast = true) {
|
|
2481
2749
|
if (newIdx < 0 || newIdx >= slides.length) return;
|
|
2482
2750
|
slides[currentIdx]?.classList.remove('active');
|
|
2483
2751
|
currentIdx = newIdx;
|
|
@@ -2490,23 +2758,166 @@ var HtmlRenderer = class {
|
|
|
2490
2758
|
const currentSlide = slides[currentIdx];
|
|
2491
2759
|
const notes = currentSlide ? currentSlide.getAttribute('data-notes') : '';
|
|
2492
2760
|
if (notesDrawer) {
|
|
2493
|
-
notesDrawer.innerHTML = notes ? '<strong>Speaker Notes:</strong><br>' + notes : '<em>No notes for this slide.</em>';
|
|
2761
|
+
notesDrawer.innerHTML = notes ? '<strong>Speaker Notes:</strong><br>' + notes : '<em>No speaker notes for this slide.</em>';
|
|
2494
2762
|
}
|
|
2495
2763
|
|
|
2496
2764
|
window.location.hash = '#' + (currentIdx + 1);
|
|
2765
|
+
|
|
2766
|
+
if (broadcast && syncChannel) {
|
|
2767
|
+
syncChannel.postMessage({ index: currentIdx });
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
function openSpeakerWindow() {
|
|
2772
|
+
const url = new URL(window.location.href);
|
|
2773
|
+
url.searchParams.set('speaker', 'true');
|
|
2774
|
+
window.open(url.toString(), 'yumia_speaker_' + Date.now(), 'width=1180,height=760,menubar=no,toolbar=no');
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2777
|
+
function initSpeakerLayout() {
|
|
2778
|
+
document.body.innerHTML = \`
|
|
2779
|
+
<div class="speaker-layout">
|
|
2780
|
+
<div class="speaker-topbar">
|
|
2781
|
+
<div style="font-weight:700; color:var(--yumia-primary); display:flex; align-items:center; gap:8px;">
|
|
2782
|
+
<span>\u{1F399}\uFE0F YumiaMD Speaker View</span>
|
|
2783
|
+
</div>
|
|
2784
|
+
<div class="speaker-timer-group">
|
|
2785
|
+
<span id="speaker-clock">00:00:00</span>
|
|
2786
|
+
<span style="color:var(--yumia-muted);">|</span>
|
|
2787
|
+
<span id="speaker-timer" style="color:var(--yumia-accent);">00:00</span>
|
|
2788
|
+
<button class="speaker-timer-btn" id="btn-timer-toggle">Pause</button>
|
|
2789
|
+
<button class="speaker-timer-btn" id="btn-timer-reset">Reset</button>
|
|
2790
|
+
</div>
|
|
2791
|
+
<div>
|
|
2792
|
+
<span id="speaker-slide-num" style="font-weight:600;">1 / \${slides.length}</span>
|
|
2793
|
+
</div>
|
|
2794
|
+
</div>
|
|
2795
|
+
<div class="speaker-main-grid">
|
|
2796
|
+
<div class="speaker-pane">
|
|
2797
|
+
<div class="speaker-pane-title">Current Slide</div>
|
|
2798
|
+
<div class="speaker-preview-box" id="speaker-current-box"></div>
|
|
2799
|
+
</div>
|
|
2800
|
+
<div style="display:grid; grid-template-rows: 45% 55%; gap:20px;">
|
|
2801
|
+
<div class="speaker-pane">
|
|
2802
|
+
<div class="speaker-pane-title">Next Slide Preview</div>
|
|
2803
|
+
<div class="speaker-preview-box" id="speaker-next-box"></div>
|
|
2804
|
+
</div>
|
|
2805
|
+
<div class="speaker-pane">
|
|
2806
|
+
<div class="speaker-pane-title">Speaker Notes</div>
|
|
2807
|
+
<div class="speaker-notes-box" id="speaker-notes-content"></div>
|
|
2808
|
+
</div>
|
|
2809
|
+
</div>
|
|
2810
|
+
</div>
|
|
2811
|
+
</div>
|
|
2812
|
+
\`;
|
|
2813
|
+
|
|
2814
|
+
// Live Clock
|
|
2815
|
+
setInterval(() => {
|
|
2816
|
+
const now = new Date();
|
|
2817
|
+
const clockEl = document.getElementById('speaker-clock');
|
|
2818
|
+
if (clockEl) clockEl.textContent = now.toLocaleTimeString();
|
|
2819
|
+
}, 1000);
|
|
2820
|
+
|
|
2821
|
+
// Elapsed Timer
|
|
2822
|
+
let elapsedSeconds = 0;
|
|
2823
|
+
let timerRunning = true;
|
|
2824
|
+
let timerInterval = setInterval(() => {
|
|
2825
|
+
if (!timerRunning) return;
|
|
2826
|
+
elapsedSeconds++;
|
|
2827
|
+
const mins = String(Math.floor(elapsedSeconds / 60)).padStart(2, '0');
|
|
2828
|
+
const secs = String(elapsedSeconds % 60).padStart(2, '0');
|
|
2829
|
+
const timerEl = document.getElementById('speaker-timer');
|
|
2830
|
+
if (timerEl) timerEl.textContent = \`\${mins}:\${secs}\`;
|
|
2831
|
+
}, 1000);
|
|
2832
|
+
|
|
2833
|
+
document.getElementById('btn-timer-toggle')?.addEventListener('click', (e) => {
|
|
2834
|
+
timerRunning = !timerRunning;
|
|
2835
|
+
e.target.textContent = timerRunning ? 'Pause' : 'Start';
|
|
2836
|
+
});
|
|
2837
|
+
|
|
2838
|
+
document.getElementById('btn-timer-reset')?.addEventListener('click', () => {
|
|
2839
|
+
elapsedSeconds = 0;
|
|
2840
|
+
const timerEl = document.getElementById('speaker-timer');
|
|
2841
|
+
if (timerEl) timerEl.textContent = '00:00';
|
|
2842
|
+
});
|
|
2843
|
+
|
|
2844
|
+
function updateSpeakerView(idx) {
|
|
2845
|
+
currentIdx = idx;
|
|
2846
|
+
const numEl = document.getElementById('speaker-slide-num');
|
|
2847
|
+
if (numEl) numEl.textContent = (currentIdx + 1) + ' / ' + slides.length;
|
|
2848
|
+
|
|
2849
|
+
// Render current slide
|
|
2850
|
+
const curBox = document.getElementById('speaker-current-box');
|
|
2851
|
+
if (curBox && slides[currentIdx]) {
|
|
2852
|
+
curBox.innerHTML = slides[currentIdx].outerHTML;
|
|
2853
|
+
curBox.firstElementChild?.classList.add('active');
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
// Render next slide
|
|
2857
|
+
const nextBox = document.getElementById('speaker-next-box');
|
|
2858
|
+
if (nextBox) {
|
|
2859
|
+
if (slides[currentIdx + 1]) {
|
|
2860
|
+
nextBox.innerHTML = slides[currentIdx + 1].outerHTML;
|
|
2861
|
+
nextBox.firstElementChild?.classList.add('active');
|
|
2862
|
+
} else {
|
|
2863
|
+
nextBox.innerHTML = '<div style="color:var(--yumia-muted); font-size:14px;">End of presentation</div>';
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2867
|
+
// Render notes
|
|
2868
|
+
const notesBox = document.getElementById('speaker-notes-content');
|
|
2869
|
+
if (notesBox && slides[currentIdx]) {
|
|
2870
|
+
const rawNotes = slides[currentIdx].getAttribute('data-notes');
|
|
2871
|
+
notesBox.innerHTML = rawNotes ? rawNotes : '<em style="color:var(--yumia-muted);">No notes provided for this slide.</em>';
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
window.addEventListener('keydown', (e) => {
|
|
2876
|
+
if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown') {
|
|
2877
|
+
e.preventDefault();
|
|
2878
|
+
if (currentIdx < slides.length - 1) {
|
|
2879
|
+
goToSlide(currentIdx + 1);
|
|
2880
|
+
updateSpeakerView(currentIdx);
|
|
2881
|
+
}
|
|
2882
|
+
} else if (e.key === 'ArrowLeft' || e.key === 'Backspace' || e.key === 'PageUp') {
|
|
2883
|
+
e.preventDefault();
|
|
2884
|
+
if (currentIdx > 0) {
|
|
2885
|
+
goToSlide(currentIdx - 1);
|
|
2886
|
+
updateSpeakerView(currentIdx);
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
});
|
|
2890
|
+
|
|
2891
|
+
if (syncChannel) {
|
|
2892
|
+
syncChannel.onmessage = (e) => {
|
|
2893
|
+
if (e.data && typeof e.data.index === 'number') {
|
|
2894
|
+
updateSpeakerView(e.data.index);
|
|
2895
|
+
}
|
|
2896
|
+
};
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2899
|
+
const initialHash = parseInt(window.location.hash.replace('#', ''), 10) - 1;
|
|
2900
|
+
updateSpeakerView(isNaN(initialHash) ? 0 : initialHash);
|
|
2497
2901
|
}
|
|
2498
2902
|
|
|
2499
2903
|
function init() {
|
|
2904
|
+
if (isSpeakerMode) {
|
|
2905
|
+
initSpeakerLayout();
|
|
2906
|
+
return;
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2500
2909
|
const hash = window.location.hash.replace('#', '');
|
|
2501
2910
|
const initialIdx = parseInt(hash, 10) - 1;
|
|
2502
|
-
|
|
2911
|
+
goToSlide(isNaN(initialIdx) ? 0 : initialIdx, false);
|
|
2503
2912
|
}
|
|
2504
2913
|
|
|
2505
2914
|
window.deckController = {
|
|
2506
2915
|
init: init,
|
|
2507
|
-
next: function() {
|
|
2508
|
-
prev: function() {
|
|
2916
|
+
next: function() { goToSlide(currentIdx + 1); },
|
|
2917
|
+
prev: function() { goToSlide(currentIdx - 1); },
|
|
2509
2918
|
toggleNotes: function() { notesDrawer?.classList.toggle('open'); },
|
|
2919
|
+
toggleOverview: function() { toggleOverview(); },
|
|
2920
|
+
openSpeaker: openSpeakerWindow,
|
|
2510
2921
|
toggleFs: function() {
|
|
2511
2922
|
if (!document.fullscreenElement) {
|
|
2512
2923
|
document.documentElement.requestFullscreen().catch(() => {});
|
|
@@ -2519,19 +2930,33 @@ var HtmlRenderer = class {
|
|
|
2519
2930
|
document.getElementById('btn-next')?.addEventListener('click', window.deckController.next);
|
|
2520
2931
|
document.getElementById('btn-prev')?.addEventListener('click', window.deckController.prev);
|
|
2521
2932
|
document.getElementById('btn-notes')?.addEventListener('click', window.deckController.toggleNotes);
|
|
2933
|
+
document.getElementById('btn-overview')?.addEventListener('click', window.deckController.toggleOverview);
|
|
2934
|
+
document.getElementById('btn-close-overview')?.addEventListener('click', () => toggleOverview(false));
|
|
2935
|
+
document.getElementById('btn-speaker')?.addEventListener('click', window.deckController.openSpeaker);
|
|
2522
2936
|
document.getElementById('btn-fs')?.addEventListener('click', window.deckController.toggleFs);
|
|
2523
2937
|
|
|
2524
2938
|
window.addEventListener('keydown', function(e) {
|
|
2525
|
-
if (
|
|
2939
|
+
if (overviewModal && overviewModal.classList.contains('open')) {
|
|
2940
|
+
if (e.key === 'Escape' || e.key.toLowerCase() === 'o') {
|
|
2941
|
+
toggleOverview(false);
|
|
2942
|
+
}
|
|
2943
|
+
return;
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown' || e.key.toLowerCase() === 'l') {
|
|
2526
2947
|
e.preventDefault();
|
|
2527
2948
|
window.deckController.next();
|
|
2528
|
-
} else if (e.key === 'ArrowLeft' || e.key === 'Backspace' || e.key === 'PageUp') {
|
|
2949
|
+
} else if (e.key === 'ArrowLeft' || e.key === 'Backspace' || e.key === 'PageUp' || e.key.toLowerCase() === 'h') {
|
|
2529
2950
|
e.preventDefault();
|
|
2530
2951
|
window.deckController.prev();
|
|
2531
2952
|
} else if (e.key.toLowerCase() === 'f') {
|
|
2532
2953
|
window.deckController.toggleFs();
|
|
2954
|
+
} else if (e.key.toLowerCase() === 's') {
|
|
2955
|
+
window.deckController.openSpeaker();
|
|
2533
2956
|
} else if (e.key.toLowerCase() === 'n') {
|
|
2534
2957
|
window.deckController.toggleNotes();
|
|
2958
|
+
} else if (e.key === 'Escape' || e.key.toLowerCase() === 'o') {
|
|
2959
|
+
window.deckController.toggleOverview();
|
|
2535
2960
|
}
|
|
2536
2961
|
});
|
|
2537
2962
|
|
|
@@ -2667,6 +3092,268 @@ var HtmlRenderer = class {
|
|
|
2667
3092
|
}
|
|
2668
3093
|
};
|
|
2669
3094
|
|
|
3095
|
+
// ../renderer-pdf/dist/renderer.js
|
|
3096
|
+
import PDFDocument from "pdfkit";
|
|
3097
|
+
var PdfRenderer = class {
|
|
3098
|
+
name = "PdfRenderer";
|
|
3099
|
+
targetFormat = "pdf";
|
|
3100
|
+
async render(presentation, context = {}) {
|
|
3101
|
+
const colorOverrides = presentation.metadata.colors ? { colors: presentation.metadata.colors } : void 0;
|
|
3102
|
+
const resolvedTheme = resolveTheme(presentation.metadata.theme, colorOverrides);
|
|
3103
|
+
const theme = context.theme || resolvedTheme || defaultTheme;
|
|
3104
|
+
const is43 = presentation.metadata.aspectRatio === "4:3";
|
|
3105
|
+
const pageWidth = is43 ? 720 : 960;
|
|
3106
|
+
const pageHeight = 540;
|
|
3107
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
3108
|
+
try {
|
|
3109
|
+
const doc = new PDFDocument({
|
|
3110
|
+
autoFirstPage: false,
|
|
3111
|
+
margin: 0,
|
|
3112
|
+
info: {
|
|
3113
|
+
Title: presentation.metadata.title || "YumiaMD Presentation",
|
|
3114
|
+
Author: presentation.metadata.author || "YumiaMD",
|
|
3115
|
+
Creator: "YumiaMD Vector PDF Compiler"
|
|
3116
|
+
}
|
|
3117
|
+
});
|
|
3118
|
+
const chunks = [];
|
|
3119
|
+
doc.on("data", (chunk) => chunks.push(chunk));
|
|
3120
|
+
doc.on("end", () => {
|
|
3121
|
+
const buffer = Buffer.concat(chunks);
|
|
3122
|
+
resolvePromise({
|
|
3123
|
+
format: "pdf",
|
|
3124
|
+
data: new Uint8Array(buffer),
|
|
3125
|
+
pageCount: presentation.slides.length,
|
|
3126
|
+
slideCount: presentation.slides.length
|
|
3127
|
+
});
|
|
3128
|
+
});
|
|
3129
|
+
doc.on("error", (err) => rejectPromise(err));
|
|
3130
|
+
const totalSlides = presentation.slides.length;
|
|
3131
|
+
for (let i = 0; i < totalSlides; i++) {
|
|
3132
|
+
const slide = presentation.slides[i];
|
|
3133
|
+
this.renderSlide(doc, slide, i + 1, totalSlides, pageWidth, pageHeight, theme);
|
|
3134
|
+
}
|
|
3135
|
+
doc.end();
|
|
3136
|
+
} catch (err) {
|
|
3137
|
+
rejectPromise(err);
|
|
3138
|
+
}
|
|
3139
|
+
});
|
|
3140
|
+
}
|
|
3141
|
+
renderSlide(doc, slide, slideNum, totalSlides, pageWidth, pageHeight, theme) {
|
|
3142
|
+
doc.addPage({
|
|
3143
|
+
size: [pageWidth, pageHeight],
|
|
3144
|
+
margins: { top: 0, bottom: 0, left: 0, right: 0 }
|
|
3145
|
+
});
|
|
3146
|
+
doc.rect(0, 0, pageWidth, pageHeight).fill(theme.colors.background);
|
|
3147
|
+
const padX = pageWidth * 0.06;
|
|
3148
|
+
const padY = pageHeight * 0.08;
|
|
3149
|
+
const contentWidth = pageWidth - padX * 2;
|
|
3150
|
+
let cursorY = padY;
|
|
3151
|
+
for (const element of slide.elements) {
|
|
3152
|
+
cursorY = this.renderElement(doc, element, padX, cursorY, contentWidth, theme);
|
|
3153
|
+
cursorY += 12;
|
|
3154
|
+
}
|
|
3155
|
+
const progressWidth = slideNum / totalSlides * pageWidth;
|
|
3156
|
+
doc.rect(0, pageHeight - 4, progressWidth, 4).fill(theme.colors.primary);
|
|
3157
|
+
doc.font("Helvetica").fontSize(10).fillColor(theme.colors.muted || "#888888").text(`${slideNum} / ${totalSlides}`, pageWidth - padX - 60, pageHeight - 24, {
|
|
3158
|
+
width: 60,
|
|
3159
|
+
align: "right"
|
|
3160
|
+
});
|
|
3161
|
+
}
|
|
3162
|
+
renderElement(doc, element, x, y, width, theme) {
|
|
3163
|
+
switch (element.type) {
|
|
3164
|
+
case "heading": {
|
|
3165
|
+
const h = element;
|
|
3166
|
+
const fontSize = h.level === 1 ? 28 : h.level === 2 ? 22 : 18;
|
|
3167
|
+
const color = h.level === 1 ? theme.colors.primary : theme.colors.text;
|
|
3168
|
+
doc.font("Helvetica-Bold").fontSize(fontSize).fillColor(color);
|
|
3169
|
+
doc.text(this.stripFormatting(h.text), x, y, { width, lineGap: 4 });
|
|
3170
|
+
const height = doc.heightOfString(this.stripFormatting(h.text), { width });
|
|
3171
|
+
return y + height;
|
|
3172
|
+
}
|
|
3173
|
+
case "paragraph": {
|
|
3174
|
+
const p = element;
|
|
3175
|
+
doc.font("Helvetica").fontSize(14).fillColor(theme.colors.text);
|
|
3176
|
+
doc.text(this.stripFormatting(p.text), x, y, { width, lineGap: 4 });
|
|
3177
|
+
const height = doc.heightOfString(this.stripFormatting(p.text), { width });
|
|
3178
|
+
return y + height;
|
|
3179
|
+
}
|
|
3180
|
+
case "list": {
|
|
3181
|
+
const l = element;
|
|
3182
|
+
let currentY = y;
|
|
3183
|
+
const itemGap = 6;
|
|
3184
|
+
l.items.forEach((item, idx) => {
|
|
3185
|
+
const bullet = l.ordered ? `${idx + 1}.` : "\u2022";
|
|
3186
|
+
doc.font("Helvetica-Bold").fontSize(13).fillColor(theme.colors.primary).text(bullet, x, currentY, { width: 18 });
|
|
3187
|
+
doc.font("Helvetica").fontSize(13).fillColor(theme.colors.text).text(this.stripFormatting(item.text), x + 20, currentY, {
|
|
3188
|
+
width: width - 20,
|
|
3189
|
+
lineGap: 3
|
|
3190
|
+
});
|
|
3191
|
+
const itemHeight = Math.max(18, doc.heightOfString(this.stripFormatting(item.text), { width: width - 20 }));
|
|
3192
|
+
currentY += itemHeight + itemGap;
|
|
3193
|
+
});
|
|
3194
|
+
return currentY;
|
|
3195
|
+
}
|
|
3196
|
+
case "quote": {
|
|
3197
|
+
const q = element;
|
|
3198
|
+
const quoteText = `\u201C${this.stripFormatting(q.text)}\u201D`;
|
|
3199
|
+
const authorText = q.author ? `\u2014 ${this.stripFormatting(q.author)}` : "";
|
|
3200
|
+
doc.font("Helvetica-Oblique").fontSize(13).fillColor(theme.colors.muted || "#aaaaaa");
|
|
3201
|
+
const textHeight = doc.heightOfString(quoteText, { width: width - 24 });
|
|
3202
|
+
const authorHeight = authorText ? 18 : 0;
|
|
3203
|
+
const totalHeight = textHeight + authorHeight + 16;
|
|
3204
|
+
doc.roundedRect(x, y, width, totalHeight, 6).fill(theme.colors.surface || "rgba(255,255,255,0.05)");
|
|
3205
|
+
doc.rect(x, y, 4, totalHeight).fill(theme.colors.accent || theme.colors.primary);
|
|
3206
|
+
doc.font("Helvetica-Oblique").fontSize(13).fillColor(theme.colors.text).text(quoteText, x + 16, y + 8, { width: width - 28 });
|
|
3207
|
+
if (authorText) {
|
|
3208
|
+
doc.font("Helvetica").fontSize(11).fillColor(theme.colors.muted || "#888888").text(authorText, x + 16, y + 8 + textHeight + 4, { width: width - 28 });
|
|
3209
|
+
}
|
|
3210
|
+
return y + totalHeight;
|
|
3211
|
+
}
|
|
3212
|
+
case "code": {
|
|
3213
|
+
const c = element;
|
|
3214
|
+
const codeText = c.code;
|
|
3215
|
+
doc.font("Courier").fontSize(11);
|
|
3216
|
+
const textHeight = doc.heightOfString(codeText, { width: width - 24 });
|
|
3217
|
+
const boxHeight = textHeight + 20;
|
|
3218
|
+
doc.roundedRect(x, y, width, boxHeight, 6).fill("#0a0a10").strokeColor(theme.colors.border || "rgba(255,255,255,0.1)").stroke();
|
|
3219
|
+
doc.font("Courier").fontSize(11).fillColor(theme.colors.accent || "#38bdf8").text(codeText, x + 12, y + 10, { width: width - 24 });
|
|
3220
|
+
return y + boxHeight;
|
|
3221
|
+
}
|
|
3222
|
+
case "card": {
|
|
3223
|
+
const card = element;
|
|
3224
|
+
const variantColor = this.getVariantColor(card.variant, theme);
|
|
3225
|
+
const cardPad = 14;
|
|
3226
|
+
let cardCursorY = y + cardPad;
|
|
3227
|
+
if (card.title) {
|
|
3228
|
+
doc.font("Helvetica-Bold").fontSize(15).fillColor(variantColor).text(this.stripFormatting(card.title), x + cardPad, cardCursorY, {
|
|
3229
|
+
width: width - cardPad * 2
|
|
3230
|
+
});
|
|
3231
|
+
cardCursorY += 22;
|
|
3232
|
+
}
|
|
3233
|
+
if (card.elements) {
|
|
3234
|
+
for (const child of card.elements) {
|
|
3235
|
+
cardCursorY = this.renderElement(doc, child, x + cardPad, cardCursorY, width - cardPad * 2, theme);
|
|
3236
|
+
cardCursorY += 8;
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
const totalCardHeight = Math.max(70, cardCursorY - y + cardPad);
|
|
3240
|
+
doc.save();
|
|
3241
|
+
doc.roundedRect(x, y, width, totalCardHeight, 8).fill(theme.colors.surface || "rgba(255,255,255,0.06)");
|
|
3242
|
+
doc.roundedRect(x, y, width, totalCardHeight, 8).lineWidth(1.5).strokeColor(variantColor).stroke();
|
|
3243
|
+
doc.restore();
|
|
3244
|
+
let renderTop = y + cardPad;
|
|
3245
|
+
if (card.title) {
|
|
3246
|
+
doc.font("Helvetica-Bold").fontSize(15).fillColor(variantColor).text(this.stripFormatting(card.title), x + cardPad, renderTop, {
|
|
3247
|
+
width: width - cardPad * 2
|
|
3248
|
+
});
|
|
3249
|
+
renderTop += 22;
|
|
3250
|
+
}
|
|
3251
|
+
if (card.elements) {
|
|
3252
|
+
for (const child of card.elements) {
|
|
3253
|
+
renderTop = this.renderElement(doc, child, x + cardPad, renderTop, width - cardPad * 2, theme);
|
|
3254
|
+
renderTop += 8;
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3257
|
+
return y + totalCardHeight;
|
|
3258
|
+
}
|
|
3259
|
+
case "metric": {
|
|
3260
|
+
const m = element;
|
|
3261
|
+
const variantColor = this.getVariantColor(m.variant, theme);
|
|
3262
|
+
const boxHeight = 90;
|
|
3263
|
+
doc.roundedRect(x, y, width, boxHeight, 8).fill(theme.colors.surface || "rgba(255,255,255,0.06)");
|
|
3264
|
+
doc.roundedRect(x, y, width, boxHeight, 8).lineWidth(1.5).strokeColor(variantColor).stroke();
|
|
3265
|
+
doc.font("Helvetica-Bold").fontSize(11).fillColor(theme.colors.muted || "#888888").text(m.label.toUpperCase(), x + 16, y + 14, { width: width - 32 });
|
|
3266
|
+
const displayVal = m.unit ? `${m.value} ${m.unit}` : m.value;
|
|
3267
|
+
doc.font("Helvetica-Bold").fontSize(28).fillColor(theme.colors.primary).text(displayVal, x + 16, y + 32, { width: width - 32 });
|
|
3268
|
+
if (m.change) {
|
|
3269
|
+
const changeColor = m.change.startsWith("+") ? "#10b981" : "#ef4444";
|
|
3270
|
+
doc.font("Helvetica-Bold").fontSize(12).fillColor(changeColor).text(m.change, x + width - 90, y + 38, { width: 74, align: "right" });
|
|
3271
|
+
}
|
|
3272
|
+
return y + boxHeight;
|
|
3273
|
+
}
|
|
3274
|
+
case "columns": {
|
|
3275
|
+
const cols = element;
|
|
3276
|
+
const colCount = cols.columns.length;
|
|
3277
|
+
const gap = 16;
|
|
3278
|
+
const availableWidth = width - gap * (colCount - 1);
|
|
3279
|
+
let ratios = Array(colCount).fill(1);
|
|
3280
|
+
if (cols.ratios) {
|
|
3281
|
+
const parsed = cols.ratios.split(":").map((r) => parseFloat(r) || 1);
|
|
3282
|
+
if (parsed.length === colCount)
|
|
3283
|
+
ratios = parsed;
|
|
3284
|
+
}
|
|
3285
|
+
const totalRatio = ratios.reduce((a, b) => a + b, 0);
|
|
3286
|
+
let curX = x;
|
|
3287
|
+
let maxY = y;
|
|
3288
|
+
for (let i = 0; i < colCount; i++) {
|
|
3289
|
+
const col = cols.columns[i];
|
|
3290
|
+
const colWidth = ratios[i] / totalRatio * availableWidth;
|
|
3291
|
+
let colCursorY = y;
|
|
3292
|
+
for (const child of col.elements) {
|
|
3293
|
+
colCursorY = this.renderElement(doc, child, curX, colCursorY, colWidth, theme);
|
|
3294
|
+
colCursorY += 8;
|
|
3295
|
+
}
|
|
3296
|
+
if (colCursorY > maxY)
|
|
3297
|
+
maxY = colCursorY;
|
|
3298
|
+
curX += colWidth + gap;
|
|
3299
|
+
}
|
|
3300
|
+
return maxY;
|
|
3301
|
+
}
|
|
3302
|
+
case "table": {
|
|
3303
|
+
const t = element;
|
|
3304
|
+
const headers = t.headers || [];
|
|
3305
|
+
const rows = t.rows || [];
|
|
3306
|
+
const colCount = Math.max(headers.length, ...rows.map((r) => r.length), 1);
|
|
3307
|
+
const colWidth = width / colCount;
|
|
3308
|
+
const rowHeight = 26;
|
|
3309
|
+
let curY = y;
|
|
3310
|
+
if (headers.length > 0) {
|
|
3311
|
+
doc.rect(x, curY, width, rowHeight).fill(theme.colors.primary);
|
|
3312
|
+
headers.forEach((h, idx) => {
|
|
3313
|
+
doc.font("Helvetica-Bold").fontSize(12).fillColor("#ffffff").text(this.stripFormatting(h), x + idx * colWidth + 6, curY + 6, {
|
|
3314
|
+
width: colWidth - 12
|
|
3315
|
+
});
|
|
3316
|
+
});
|
|
3317
|
+
curY += rowHeight;
|
|
3318
|
+
}
|
|
3319
|
+
rows.forEach((row, rowIdx) => {
|
|
3320
|
+
const isEven = rowIdx % 2 === 0;
|
|
3321
|
+
const rowBg = isEven ? theme.colors.surface || "rgba(255,255,255,0.04)" : "rgba(255,255,255,0.01)";
|
|
3322
|
+
doc.rect(x, curY, width, rowHeight).fill(rowBg);
|
|
3323
|
+
row.forEach((cell, idx) => {
|
|
3324
|
+
doc.font("Helvetica").fontSize(11).fillColor(theme.colors.text).text(this.stripFormatting(cell), x + idx * colWidth + 6, curY + 6, {
|
|
3325
|
+
width: colWidth - 12
|
|
3326
|
+
});
|
|
3327
|
+
});
|
|
3328
|
+
curY += rowHeight;
|
|
3329
|
+
});
|
|
3330
|
+
doc.rect(x, y, width, curY - y).strokeColor(theme.colors.border || "rgba(255,255,255,0.1)").stroke();
|
|
3331
|
+
return curY;
|
|
3332
|
+
}
|
|
3333
|
+
default:
|
|
3334
|
+
return y;
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
getVariantColor(variant, theme) {
|
|
3338
|
+
switch (variant) {
|
|
3339
|
+
case "warning":
|
|
3340
|
+
return theme.colors.warning || "#f59e0b";
|
|
3341
|
+
case "info":
|
|
3342
|
+
return theme.colors.info || "#3b82f6";
|
|
3343
|
+
case "success":
|
|
3344
|
+
return theme.colors.success || "#10b981";
|
|
3345
|
+
case "danger":
|
|
3346
|
+
return theme.colors.danger || "#ef4444";
|
|
3347
|
+
case "primary":
|
|
3348
|
+
default:
|
|
3349
|
+
return theme.colors.primary;
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
stripFormatting(text) {
|
|
3353
|
+
return text.replace(/\*\*(.*?)\*\*/g, "$1").replace(/\*(.*?)\*/g, "$1").replace(/`(.*?)`/g, "$1").replace(/[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{1F900}-\u{1F9FF}\u{1F600}-\u{1F64F}\u{1F680}-\u{1F6FF}\u{2300}-\u{23FF}\u{2B50}]/gu, "").replace(/\u200D/g, "").replace(/\uFE0F/g, "").replace(/[“”]/g, '"').replace(/[‘’]/g, "'").replace(/[—–]/g, "-").trim();
|
|
3354
|
+
}
|
|
3355
|
+
};
|
|
3356
|
+
|
|
2670
3357
|
// src/dev-server.ts
|
|
2671
3358
|
import { createServer } from "http";
|
|
2672
3359
|
import { readFileSync, watch } from "fs";
|
|
@@ -2730,6 +3417,14 @@ function startDevServer(filePath, options = {}) {
|
|
|
2730
3417
|
});
|
|
2731
3418
|
server.listen(port, () => {
|
|
2732
3419
|
const url = `http://localhost:${port}`;
|
|
3420
|
+
if (options.open) {
|
|
3421
|
+
const startCmd = process.platform === "win32" ? `start ${url}` : process.platform === "darwin" ? `open ${url}` : `xdg-open ${url}`;
|
|
3422
|
+
import("child_process").then(({ exec }) => {
|
|
3423
|
+
exec(startCmd, () => {
|
|
3424
|
+
});
|
|
3425
|
+
}).catch(() => {
|
|
3426
|
+
});
|
|
3427
|
+
}
|
|
2733
3428
|
try {
|
|
2734
3429
|
watcher = watch(resolvedPath, () => {
|
|
2735
3430
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
@@ -2766,7 +3461,7 @@ function startDevServer(filePath, options = {}) {
|
|
|
2766
3461
|
// src/cli.ts
|
|
2767
3462
|
import { mkdirSync, readFileSync as readFileSync2, watch as fsWatch, writeFileSync } from "fs";
|
|
2768
3463
|
import { basename, dirname, extname, join, resolve as resolve2 } from "path";
|
|
2769
|
-
var VERSION = "0.1.
|
|
3464
|
+
var VERSION = "0.1.14";
|
|
2770
3465
|
function printHelp() {
|
|
2771
3466
|
return `
|
|
2772
3467
|
YumiaMD \u2014 Markdown-based presentation compiler (v${VERSION})
|
|
@@ -2782,7 +3477,7 @@ Commands:
|
|
|
2782
3477
|
lint <file> Analyze presentation for layout overflows and accessibility
|
|
2783
3478
|
inspect <file> Inspect the AST and geometric layout tree
|
|
2784
3479
|
schema Output machine-readable JSON schema for AI agents
|
|
2785
|
-
build <file> Compile a presentation to PowerPoint (.pptx) or HTML (.html)
|
|
3480
|
+
build <file> Compile a presentation to PowerPoint (.pptx), PDF (.pdf), or HTML (.html)
|
|
2786
3481
|
|
|
2787
3482
|
Theming & Color Options:
|
|
2788
3483
|
--theme, -t <name> Base theme: default | cyberpunk | minimal | corporate | terminal | academic
|
|
@@ -2797,7 +3492,7 @@ Server & Compiler Options:
|
|
|
2797
3492
|
--open Open default browser automatically in dev mode
|
|
2798
3493
|
--watch, -w Watch for file changes during compilation
|
|
2799
3494
|
--out, -o <file> Specify output file path (default: dist/<name>.<format>)
|
|
2800
|
-
--format, -f <fmt> Target output format: pptx (default) | html
|
|
3495
|
+
--format, -f <fmt> Target output format: pptx (default) | pdf | html
|
|
2801
3496
|
--strict Enforce zero warnings in 'lint' (exits with code 1 on warning)
|
|
2802
3497
|
--json Output results formatted as JSON for CI/CD and AI tools
|
|
2803
3498
|
--layout Show computed geometric bounding boxes in 'inspect'
|
|
@@ -2966,7 +3661,8 @@ Opening slide introducing the presentation deck.
|
|
|
2966
3661
|
exitCode: 0,
|
|
2967
3662
|
output: `\u{1F680} YumiaMD Live Dev Server running at: ${devInstance.url}
|
|
2968
3663
|
Watching: ${target}
|
|
2969
|
-
Hot-reloading active via SSE. Press Ctrl+C to stop
|
|
3664
|
+
Hot-reloading active via SSE. Press Ctrl+C to stop.`,
|
|
3665
|
+
keepAlive: true
|
|
2970
3666
|
};
|
|
2971
3667
|
} catch (err) {
|
|
2972
3668
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -3120,7 +3816,8 @@ ${summary}${isStrict && report.warnings.length > 0 ? " (failed due to --strict)"
|
|
|
3120
3816
|
try {
|
|
3121
3817
|
const resolvedInput = resolve2(process.cwd(), target);
|
|
3122
3818
|
const isHtml = cliFormat === "html";
|
|
3123
|
-
const
|
|
3819
|
+
const isPdf = cliFormat === "pdf";
|
|
3820
|
+
const targetExtension = isHtml ? ".html" : isPdf ? ".pdf" : ".pptx";
|
|
3124
3821
|
let outputPath = "";
|
|
3125
3822
|
const outFlagIndex = args.findIndex((a) => a === "--out" || a === "-o");
|
|
3126
3823
|
if (outFlagIndex !== -1 && args[outFlagIndex + 1]) {
|
|
@@ -3149,6 +3846,14 @@ ${summary}${isStrict && report.warnings.length > 0 ? " (failed due to --strict)"
|
|
|
3149
3846
|
});
|
|
3150
3847
|
writeFileSync(outputPath, result2.html, "utf-8");
|
|
3151
3848
|
return { slideCount: result2.slideCount, format: result2.format };
|
|
3849
|
+
} else if (isPdf) {
|
|
3850
|
+
const pdfRenderer = new PdfRenderer();
|
|
3851
|
+
const result2 = await compiler.compile(source, pdfRenderer, {
|
|
3852
|
+
...renderTheme ? { renderContext: { theme: renderTheme } } : {}
|
|
3853
|
+
});
|
|
3854
|
+
const buffer = result2.data instanceof Uint8Array ? Buffer.from(result2.data) : Buffer.from(new Uint8Array(result2.data));
|
|
3855
|
+
writeFileSync(outputPath, buffer);
|
|
3856
|
+
return { slideCount: result2.slideCount, format: result2.format };
|
|
3152
3857
|
} else {
|
|
3153
3858
|
const pptxRenderer = new PptxRenderer();
|
|
3154
3859
|
const result2 = await compiler.compile(source, pptxRenderer, {
|
|
@@ -3186,9 +3891,11 @@ ${summary}${isStrict && report.warnings.length > 0 ? " (failed due to --strict)"
|
|
|
3186
3891
|
};
|
|
3187
3892
|
}
|
|
3188
3893
|
const watchMsg = command === "watch" || isWatch ? " [Watching for changes...]" : "";
|
|
3894
|
+
const formatDesc = isHtml ? "interactive HTML slides" : isPdf ? "vector PDF slides" : "native editable slides";
|
|
3189
3895
|
return {
|
|
3190
3896
|
exitCode: 0,
|
|
3191
|
-
output: `\u2713 Successfully compiled '${target}' \u2794 '${outputPath}' (${result.slideCount} ${
|
|
3897
|
+
output: `\u2713 Successfully compiled '${target}' \u2794 '${outputPath}' (${result.slideCount} ${formatDesc})${watchMsg}`,
|
|
3898
|
+
keepAlive: command === "watch" || isWatch
|
|
3192
3899
|
};
|
|
3193
3900
|
} catch (err) {
|
|
3194
3901
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -3243,6 +3950,7 @@ export {
|
|
|
3243
3950
|
parseInlineMarkdown,
|
|
3244
3951
|
PptxRenderer,
|
|
3245
3952
|
HtmlRenderer,
|
|
3953
|
+
PdfRenderer,
|
|
3246
3954
|
startDevServer,
|
|
3247
3955
|
VERSION,
|
|
3248
3956
|
printHelp,
|
package/dist/index.d.ts
CHANGED
|
@@ -7,12 +7,14 @@ 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.14";
|
|
12
13
|
declare function printHelp(): string;
|
|
13
14
|
declare function runCli(argv: string[]): Promise<{
|
|
14
15
|
exitCode: number;
|
|
15
16
|
output: string;
|
|
17
|
+
keepAlive?: boolean;
|
|
16
18
|
}>;
|
|
17
19
|
|
|
18
20
|
interface DevServerOptions {
|
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-ZOJEPOPI.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.14",
|
|
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/ast": "0.1.
|
|
24
|
-
"@yumiamd/
|
|
25
|
-
"@yumiamd/core": "0.1.
|
|
26
|
-
"@yumiamd/
|
|
27
|
-
"@yumiamd/renderer": "0.1.
|
|
28
|
-
"@yumiamd/renderer-pptx": "0.1.
|
|
29
|
-
"@yumiamd/renderer-html": "0.1.
|
|
30
|
-
"@yumiamd/
|
|
24
|
+
"@yumiamd/ast": "0.1.14",
|
|
25
|
+
"@yumiamd/parser": "0.1.14",
|
|
26
|
+
"@yumiamd/core": "0.1.14",
|
|
27
|
+
"@yumiamd/layout": "0.1.14",
|
|
28
|
+
"@yumiamd/renderer": "0.1.14",
|
|
29
|
+
"@yumiamd/renderer-pptx": "0.1.14",
|
|
30
|
+
"@yumiamd/renderer-html": "0.1.14",
|
|
31
|
+
"@yumiamd/renderer-pdf": "0.1.14",
|
|
32
|
+
"@yumiamd/theme": "0.1.14"
|
|
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
|
}
|