@lemonppt/renderer 0.1.0

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/dist/render.js ADDED
@@ -0,0 +1,752 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { renderSlide } from '@lemonppt/templates';
3
+ import ReactDOMServer from 'react-dom/server';
4
+ import { editorScript } from './editor-script.js';
5
+ export function renderDeck(goal, options = {}) {
6
+ const { width = 1280, height = 720, editable = false } = options;
7
+ const slideCount = goal.slides.length;
8
+ const slideElements = goal.slides.map((slide, index) => {
9
+ const element = renderSlide(slide, { slideIdx: index, editable });
10
+ const stateClass = index === 0 ? 'active' : '';
11
+ return (_jsx("div", { className: `lp-slide-wrapper ${stateClass}`, "data-slide-index": index, "data-layout": slide.layout, style: {
12
+ width: `${width}px`,
13
+ height: `${height}px`,
14
+ boxSizing: 'border-box',
15
+ }, children: element ?? (_jsxs("div", { className: "lp-error", children: ["\u672A\u627E\u5230\u7248\u5F0F: ", slide.layout] })) }, index));
16
+ });
17
+ const slidesMarkup = ReactDOMServer.renderToStaticMarkup(_jsx("div", { className: "lp-deck", "data-theme": goal.theme, "data-editable": editable ? 'true' : undefined, style: {
18
+ width: `${width}px`,
19
+ height: `${height}px`,
20
+ }, children: slideElements }));
21
+ const slideHtmls = goal.slides.map((slide, index) => {
22
+ const element = renderSlide(slide, { slideIdx: index, editable });
23
+ return ReactDOMServer.renderToStaticMarkup(_jsx("div", { style: {
24
+ width: `${width}px`,
25
+ height: `${height}px`,
26
+ boxSizing: 'border-box',
27
+ }, children: element ?? (_jsxs("div", { className: "lp-error", children: ["\u672A\u627E\u5230\u7248\u5F0F: ", slide.layout] })) }));
28
+ });
29
+ const navMarkup = buildNavMarkup(slideCount);
30
+ const scriptMarkup = editable ? buildEditorScriptMarkup(goal) : buildScriptMarkup();
31
+ const editorBarMarkup = editable ? buildEditorBarMarkup() : '';
32
+ const leftPanelMarkup = editable ? buildLeftPanelMarkup(goal, slideHtmls, width, height) : '';
33
+ const rightPanelMarkup = editable ? buildRightPanelMarkup() : '';
34
+ const bodyClass = editable ? 'lp-editor-body' : '';
35
+ const bodyMarkup = editable
36
+ ? `<div class="lp-editor-root">
37
+ ${editorBarMarkup}
38
+ <div class="lp-editor-workspace">
39
+ ${leftPanelMarkup}
40
+ <main class="lp-editor-stage">
41
+ <div class="lp-editor-stage-scaler">
42
+ ${slidesMarkup}
43
+ <div class="lp-editor-page-counter"><span id="lp-current">1</span> / ${slideCount}</div>
44
+ </div>
45
+ <div class="lp-editor-zoom-bar">
46
+ <button id="lp-zoom-out" title="缩小">-</button>
47
+ <input type="range" id="lp-zoom-slider" min="35" max="150" value="100">
48
+ <button id="lp-zoom-in" title="放大">+</button>
49
+ <button id="lp-zoom-fit" title="适应画布">适应</button>
50
+ <span id="lp-zoom-value">100%</span>
51
+ </div>
52
+ </main>
53
+ ${rightPanelMarkup}
54
+ </div>
55
+ ${scriptMarkup}
56
+ </div>`
57
+ : `${slidesMarkup}${navMarkup}${scriptMarkup}`;
58
+ const html = `<!DOCTYPE html>
59
+ <html lang="${goal.language ?? 'zh'}">
60
+ <head>
61
+ <meta charset="UTF-8">
62
+ <meta name="viewport" content="width=${width}, initial-scale=1.0">
63
+ <title>${escapeHtml(goal.title)}</title>
64
+ <link rel="stylesheet" href="./assets/${goal.theme || 'minimal'}.css">
65
+ <style>
66
+ * { margin: 0; padding: 0; box-sizing: border-box; }
67
+ html, body {
68
+ width: 100%;
69
+ height: 100%;
70
+ background: #111;
71
+ }
72
+ body {
73
+ display: flex;
74
+ align-items: center;
75
+ justify-content: center;
76
+ }
77
+ .lp-editor-body {
78
+ display: block;
79
+ }
80
+ .lp-deck {
81
+ box-shadow: 0 24px 80px rgba(0,0,0,0.5);
82
+ border-radius: 12px;
83
+ overflow: hidden;
84
+ }
85
+ .lp-error {
86
+ width: 100%;
87
+ height: 100%;
88
+ display: flex;
89
+ align-items: center;
90
+ justify-content: center;
91
+ background: #fee2e2;
92
+ color: #991b1b;
93
+ font-family: system-ui, sans-serif;
94
+ }
95
+ .lp-editor-root {
96
+ display: flex;
97
+ flex-direction: column;
98
+ width: 100vw;
99
+ height: 100vh;
100
+ background: #1a1a1a;
101
+ color: #e5e5e5;
102
+ overflow: hidden;
103
+ }
104
+ .lp-editor-root .lp-editor-bar {
105
+ position: relative !important;
106
+ top: auto !important;
107
+ left: auto !important;
108
+ transform: none !important;
109
+ width: 100% !important;
110
+ border-radius: 0 !important;
111
+ border-bottom: 1px solid #333 !important;
112
+ background: #1e1e1e !important;
113
+ color: #e5e5e5 !important;
114
+ padding: 0 16px !important;
115
+ display: flex !important;
116
+ align-items: center !important;
117
+ gap: 12px !important;
118
+ }
119
+ .lp-editor-root .lp-editor-title {
120
+ margin-right: auto;
121
+ }
122
+ .lp-editor-root .lp-editor-btn {
123
+ background: #2a2a2a;
124
+ color: #e5e5e5;
125
+ border: 1px solid #444;
126
+ }
127
+ .lp-editor-root .lp-editor-btn:hover:not(:disabled) {
128
+ background: #333;
129
+ }
130
+ .lp-editor-root .lp-editor-btn:disabled {
131
+ opacity: 0.5;
132
+ }
133
+ .lp-editor-workspace {
134
+ display: flex;
135
+ flex: 1;
136
+ min-height: 0;
137
+ }
138
+ .lp-editor-left-panel {
139
+ width: 240px;
140
+ flex: none;
141
+ background: #1e1e1e;
142
+ border-right: 1px solid #333;
143
+ overflow-y: auto;
144
+ padding: 12px;
145
+ }
146
+ .lp-editor-right-panel {
147
+ width: 280px;
148
+ flex: none;
149
+ background: #1e1e1e;
150
+ border-left: 1px solid #333;
151
+ overflow-y: auto;
152
+ padding: 16px;
153
+ }
154
+ .lp-editor-stage {
155
+ flex: 1;
156
+ display: flex;
157
+ align-items: center;
158
+ justify-content: center;
159
+ overflow: hidden;
160
+ position: relative;
161
+ }
162
+ .lp-editor-stage-scaler {
163
+ transform-origin: center center;
164
+ transition: transform 0.2s ease;
165
+ position: relative;
166
+ }
167
+ .lp-editor-page-counter {
168
+ position: absolute;
169
+ bottom: -32px;
170
+ left: 50%;
171
+ transform: translateX(-50%);
172
+ color: #888;
173
+ font-size: 13px;
174
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
175
+ }
176
+ .lp-editor-thumbnails {
177
+ display: flex;
178
+ flex-direction: column;
179
+ gap: 10px;
180
+ }
181
+ .lp-thumbnail {
182
+ width: 100%;
183
+ aspect-ratio: 16 / 9;
184
+ border: 2px solid transparent;
185
+ border-radius: 6px;
186
+ background: #2a2a2a;
187
+ cursor: pointer;
188
+ display: flex;
189
+ flex-direction: column;
190
+ justify-content: center;
191
+ padding: 10px;
192
+ text-align: left;
193
+ color: #bbb;
194
+ transition: border-color 0.15s, background 0.15s;
195
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
196
+ }
197
+ .lp-thumbnail:hover {
198
+ background: #333;
199
+ }
200
+ .lp-thumbnail.active {
201
+ border-color: #3b82f6;
202
+ background: #26354f;
203
+ color: #fff;
204
+ }
205
+ .lp-thumbnail-index {
206
+ font-size: 11px;
207
+ color: #888;
208
+ margin-bottom: 4px;
209
+ }
210
+ .lp-thumbnail-title {
211
+ font-size: 13px;
212
+ font-weight: 500;
213
+ line-height: 1.3;
214
+ overflow: hidden;
215
+ text-overflow: ellipsis;
216
+ display: -webkit-box;
217
+ -webkit-line-clamp: 2;
218
+ -webkit-box-orient: vertical;
219
+ }
220
+ .lp-thumbnail-layout {
221
+ font-size: 11px;
222
+ color: #666;
223
+ margin-top: 4px;
224
+ }
225
+ .lp-property-header {
226
+ font-size: 14px;
227
+ font-weight: 600;
228
+ margin-bottom: 12px;
229
+ color: #fff;
230
+ }
231
+ .lp-property-empty {
232
+ color: #888;
233
+ font-size: 13px;
234
+ line-height: 1.5;
235
+ }
236
+ .lp-property-group {
237
+ margin-bottom: 16px;
238
+ }
239
+ .lp-property-label {
240
+ font-size: 12px;
241
+ color: #999;
242
+ margin-bottom: 6px;
243
+ display: block;
244
+ }
245
+ .lp-property-input,
246
+ .lp-property-textarea {
247
+ width: 100%;
248
+ background: #2a2a2a;
249
+ border: 1px solid #444;
250
+ border-radius: 4px;
251
+ color: #e5e5e5;
252
+ padding: 8px 10px;
253
+ font-size: 13px;
254
+ font-family: inherit;
255
+ }
256
+ .lp-property-input:focus,
257
+ .lp-property-textarea:focus {
258
+ outline: none;
259
+ border-color: #3b82f6;
260
+ }
261
+ .lp-property-textarea {
262
+ min-height: 120px;
263
+ resize: vertical;
264
+ }
265
+ .lp-selected {
266
+ outline: 2px solid #3b82f6 !important;
267
+ outline-offset: 2px !important;
268
+ }
269
+ .lp-editor-root .lp-nav,
270
+ .lp-editor-root .lp-page-counter,
271
+ .lp-editor-root .lp-hint {
272
+ display: none !important;
273
+ }
274
+ .lp-thumbnail {
275
+ position: relative;
276
+ overflow: hidden;
277
+ padding: 0;
278
+ justify-content: flex-end;
279
+ background: var(--lp-bg, #222);
280
+ }
281
+ .lp-thumbnail-render {
282
+ position: absolute;
283
+ inset: 0;
284
+ z-index: 0;
285
+ }
286
+ .lp-thumbnail-scaler {
287
+ position: relative;
288
+ pointer-events: none;
289
+ }
290
+ .lp-thumbnail-scrim {
291
+ position: absolute;
292
+ inset: 0;
293
+ z-index: 1;
294
+ background: linear-gradient(to bottom, rgba(0,0,0,0.15) 0%, rgba(0,0,0,0.55) 100%);
295
+ pointer-events: none;
296
+ }
297
+ .lp-thumbnail-content {
298
+ position: relative;
299
+ z-index: 2;
300
+ padding: 10px;
301
+ color: #fff;
302
+ text-shadow: 0 1px 2px rgba(0,0,0,0.8);
303
+ pointer-events: none;
304
+ }
305
+ .lp-thumbnail-index {
306
+ font-size: 11px;
307
+ color: rgba(255,255,255,0.75);
308
+ margin-bottom: 4px;
309
+ }
310
+ .lp-thumbnail-title {
311
+ font-size: 13px;
312
+ font-weight: 500;
313
+ line-height: 1.3;
314
+ overflow: hidden;
315
+ text-overflow: ellipsis;
316
+ display: -webkit-box;
317
+ -webkit-line-clamp: 2;
318
+ -webkit-box-orient: vertical;
319
+ }
320
+ .lp-thumbnail-layout {
321
+ font-size: 11px;
322
+ color: rgba(255,255,255,0.65);
323
+ margin-top: 4px;
324
+ }
325
+ .lp-thumbnail.active {
326
+ border-color: #3b82f6;
327
+ background: #26354f;
328
+ }
329
+ .lp-thumbnail.active .lp-thumbnail-scrim {
330
+ background: linear-gradient(to bottom, rgba(59,130,246,0.15) 0%, rgba(59,130,246,0.45) 100%);
331
+ }
332
+ .lp-editor-zoom-bar {
333
+ position: absolute;
334
+ bottom: 16px;
335
+ left: 50%;
336
+ transform: translateX(-50%);
337
+ z-index: 10;
338
+ display: flex;
339
+ align-items: center;
340
+ gap: 8px;
341
+ background: #1e1e1e;
342
+ border: 1px solid #333;
343
+ border-radius: 8px;
344
+ padding: 6px 10px;
345
+ font-size: 12px;
346
+ color: #bbb;
347
+ }
348
+ .lp-editor-zoom-bar button {
349
+ background: #2a2a2a;
350
+ border: 1px solid #444;
351
+ color: #e5e5e5;
352
+ border-radius: 4px;
353
+ padding: 4px 8px;
354
+ cursor: pointer;
355
+ }
356
+ .lp-editor-zoom-bar button:hover {
357
+ background: #333;
358
+ }
359
+ .lp-editor-zoom-bar input[type="range"] {
360
+ width: 100px;
361
+ }
362
+ .lp-property-section {
363
+ margin-bottom: 18px;
364
+ padding-bottom: 14px;
365
+ border-bottom: 1px solid #333;
366
+ }
367
+ .lp-property-section:last-child {
368
+ border-bottom: none;
369
+ }
370
+ .lp-property-section-title {
371
+ font-size: 12px;
372
+ font-weight: 600;
373
+ color: #fff;
374
+ margin-bottom: 10px;
375
+ text-transform: uppercase;
376
+ letter-spacing: 0.05em;
377
+ }
378
+ .lp-property-help {
379
+ font-size: 12px;
380
+ color: #888;
381
+ margin-bottom: 10px;
382
+ line-height: 1.4;
383
+ }
384
+ .lp-property-field {
385
+ margin-bottom: 12px;
386
+ }
387
+ .lp-property-field:last-child {
388
+ margin-bottom: 0;
389
+ }
390
+ .lp-property-input,
391
+ .lp-property-textarea {
392
+ width: 100%;
393
+ background: #2a2a2a;
394
+ border: 1px solid #444;
395
+ border-radius: 6px;
396
+ color: #e5e5e5;
397
+ padding: 8px 10px;
398
+ font-size: 13px;
399
+ font-family: inherit;
400
+ }
401
+ .lp-property-input:focus,
402
+ .lp-property-textarea:focus {
403
+ outline: none;
404
+ border-color: #3b82f6;
405
+ }
406
+ .lp-property-textarea {
407
+ min-height: 80px;
408
+ resize: vertical;
409
+ }
410
+ .lp-property-toggle {
411
+ display: flex;
412
+ align-items: center;
413
+ justify-content: space-between;
414
+ cursor: pointer;
415
+ }
416
+ .lp-property-toggle-track {
417
+ width: 38px;
418
+ height: 20px;
419
+ background: #444;
420
+ border-radius: 10px;
421
+ position: relative;
422
+ transition: background 0.2s;
423
+ }
424
+ .lp-property-toggle input:checked + .lp-property-toggle-track {
425
+ background: #3b82f6;
426
+ }
427
+ .lp-property-toggle-thumb {
428
+ position: absolute;
429
+ top: 2px;
430
+ left: 2px;
431
+ width: 16px;
432
+ height: 16px;
433
+ background: #fff;
434
+ border-radius: 50%;
435
+ transition: transform 0.2s;
436
+ }
437
+ .lp-property-toggle input:checked + .lp-property-toggle-track .lp-property-toggle-thumb {
438
+ transform: translateX(18px);
439
+ }
440
+ .lp-property-toggle input {
441
+ position: absolute;
442
+ opacity: 0;
443
+ width: 0;
444
+ height: 0;
445
+ }
446
+ .lp-property-segmented {
447
+ display: flex;
448
+ background: #2a2a2a;
449
+ border: 1px solid #444;
450
+ border-radius: 6px;
451
+ overflow: hidden;
452
+ }
453
+ .lp-property-segmented button {
454
+ flex: 1;
455
+ background: transparent;
456
+ border: none;
457
+ border-right: 1px solid #444;
458
+ color: #bbb;
459
+ padding: 6px 0;
460
+ font-size: 12px;
461
+ cursor: pointer;
462
+ }
463
+ .lp-property-segmented button:last-child {
464
+ border-right: none;
465
+ }
466
+ .lp-property-segmented button.active {
467
+ background: #3b82f6;
468
+ color: #fff;
469
+ }
470
+ .lp-property-color-grid {
471
+ display: grid;
472
+ grid-template-columns: repeat(5, 1fr);
473
+ gap: 8px;
474
+ }
475
+ .lp-property-color-btn {
476
+ width: 100%;
477
+ aspect-ratio: 1;
478
+ border-radius: 50%;
479
+ border: 2px solid transparent;
480
+ cursor: pointer;
481
+ }
482
+ .lp-property-color-btn.active {
483
+ border-color: #fff;
484
+ }
485
+ .lp-property-range {
486
+ width: 100%;
487
+ }
488
+ .lp-property-array {
489
+ display: flex;
490
+ flex-direction: column;
491
+ gap: 10px;
492
+ }
493
+ .lp-property-array-item {
494
+ background: #252525;
495
+ border: 1px solid #383838;
496
+ border-radius: 8px;
497
+ padding: 10px;
498
+ }
499
+ .lp-property-array-header {
500
+ display: flex;
501
+ align-items: center;
502
+ justify-content: space-between;
503
+ margin-bottom: 8px;
504
+ font-size: 12px;
505
+ color: #aaa;
506
+ }
507
+ .lp-property-btn {
508
+ width: 100%;
509
+ padding: 8px;
510
+ background: #2a2a2a;
511
+ border: 1px solid #444;
512
+ border-radius: 6px;
513
+ color: #e5e5e5;
514
+ font-size: 13px;
515
+ cursor: pointer;
516
+ }
517
+ .lp-property-btn:hover {
518
+ background: #333;
519
+ }
520
+ .lp-property-btn-primary {
521
+ background: #3b82f6;
522
+ border-color: #3b82f6;
523
+ color: #fff;
524
+ }
525
+ .lp-property-btn-primary:hover {
526
+ background: #2563eb;
527
+ }
528
+ .lp-property-btn-danger {
529
+ border-color: #7f1d1d;
530
+ color: #fca5a5;
531
+ }
532
+ .lp-property-btn-danger:hover {
533
+ background: #450a0a;
534
+ }
535
+ .lp-property-btn-sm {
536
+ padding: 4px 8px;
537
+ font-size: 12px;
538
+ }
539
+ .lp-property-empty-state {
540
+ color: #888;
541
+ font-size: 13px;
542
+ line-height: 1.5;
543
+ text-align: center;
544
+ padding: 20px 0;
545
+ }
546
+ @media print {
547
+ html, body {
548
+ display: block;
549
+ background: #fff;
550
+ }
551
+ .lp-editor-bar,
552
+ .lp-nav,
553
+ .lp-page-counter,
554
+ .lp-hint,
555
+ .lp-editor-left-panel,
556
+ .lp-editor-right-panel,
557
+ .lp-editor-zoom-bar,
558
+ .lp-editor-stage-scaler > .lp-editor-page-counter {
559
+ display: none !important;
560
+ }
561
+ .lp-editor-root,
562
+ .lp-editor-workspace,
563
+ .lp-editor-stage {
564
+ display: block !important;
565
+ width: auto !important;
566
+ height: auto !important;
567
+ overflow: visible !important;
568
+ }
569
+ .lp-editor-stage-scaler {
570
+ transform: none !important;
571
+ }
572
+ .lp-deck {
573
+ box-shadow: none;
574
+ border-radius: 0;
575
+ width: 100% !important;
576
+ height: auto !important;
577
+ overflow: visible;
578
+ }
579
+ .lp-slide-wrapper {
580
+ position: relative;
581
+ transform: none !important;
582
+ opacity: 1 !important;
583
+ width: 100% !important;
584
+ height: ${height}px !important;
585
+ page-break-after: always;
586
+ break-after: page;
587
+ z-index: auto !important;
588
+ }
589
+ .lp-slide-wrapper:last-child {
590
+ page-break-after: auto;
591
+ break-after: auto;
592
+ }
593
+ }
594
+ </style>
595
+ </head>
596
+ <body class="${bodyClass}">
597
+ ${bodyMarkup}
598
+ </body>
599
+ </html>`;
600
+ return {
601
+ html,
602
+ assets: ['./assets/minimal.css'],
603
+ };
604
+ }
605
+ function buildNavMarkup(slideCount) {
606
+ if (slideCount <= 1)
607
+ return '';
608
+ const dots = Array.from({ length: slideCount }, (_, i) => `<span class="lp-nav-dot ${i === 0 ? 'active' : ''}" data-index="${i}"></span>`).join('');
609
+ return `<div class="lp-nav" aria-label="幻灯片导航">
610
+ <button class="lp-nav-btn" id="lp-prev" aria-label="上一页">‹</button>
611
+ <div class="lp-nav-dots">${dots}</div>
612
+ <button class="lp-nav-btn" id="lp-next" aria-label="下一页">›</button>
613
+ </div>
614
+ <div class="lp-page-counter"><span id="lp-current">1</span> / ${slideCount}</div>
615
+ <div class="lp-hint">← → 翻页</div>`;
616
+ }
617
+ function buildEditorBarMarkup() {
618
+ return `<div class="lp-editor-bar">
619
+ <span class="lp-editor-title">lemonPPT 编辑器</span>
620
+ <label class="lp-theme-switcher-label">
621
+ 主题
622
+ <select id="lp-theme-switcher">
623
+ <option value="minimal">极简白</option>
624
+ <option value="dark-tech">深色科技</option>
625
+ <option value="warm-business">暖色商务</option>
626
+ </select>
627
+ </label>
628
+ <button class="lp-editor-btn" id="lp-play" title="播放演示">▶ 播放</button>
629
+ <button class="lp-editor-btn" id="lp-undo" title="撤销 (Ctrl+Z)">↩ 撤销</button>
630
+ <button class="lp-editor-btn" id="lp-redo" title="重做 (Ctrl+Y)">↪ 重做</button>
631
+ <button class="lp-editor-btn" id="lp-download-goal" title="下载 goal.json">下载 goal.json</button>
632
+ <button class="lp-editor-btn" id="lp-export-pptx" title="导出 PPTX">导出 PPTX</button>
633
+ <button class="lp-editor-btn" id="lp-export-pdf" title="导出 PDF">导出 PDF</button>
634
+ </div>`;
635
+ }
636
+ function buildLeftPanelMarkup(goal, slideHtmls, slideWidth, slideHeight) {
637
+ const thumbInnerWidth = 216;
638
+ const scale = thumbInnerWidth / slideWidth;
639
+ const buttons = goal.slides.map((slide, index) => {
640
+ const label = getSlideLabel(slide);
641
+ const activeClass = index === 0 ? 'active' : '';
642
+ const slideHtml = slideHtmls[index] || '';
643
+ return `<button class="lp-thumbnail ${activeClass}" data-index="${index}" type="button">
644
+ <div class="lp-thumbnail-render">
645
+ <div class="lp-thumbnail-scaler" style="width:${slideWidth}px;height:${slideHeight}px;transform:scale(${scale});transform-origin:top left;">
646
+ ${slideHtml}
647
+ </div>
648
+ </div>
649
+ <div class="lp-thumbnail-scrim"></div>
650
+ <div class="lp-thumbnail-content">
651
+ <div class="lp-thumbnail-index">${index + 1} / ${goal.slides.length}</div>
652
+ <div class="lp-thumbnail-title">${escapeHtml(label)}</div>
653
+ <div class="lp-thumbnail-layout">${escapeHtml(slide.layout)}</div>
654
+ </div>
655
+ </button>`;
656
+ }).join('');
657
+ return `<aside class="lp-editor-left-panel" aria-label="幻灯片缩略图">
658
+ <div class="lp-editor-thumbnails">
659
+ ${buttons}
660
+ </div>
661
+ </aside>`;
662
+ }
663
+ function getSlideLabel(slide) {
664
+ const props = slide.props;
665
+ if (!props)
666
+ return slide.layout;
667
+ const candidates = [
668
+ props.title,
669
+ props.name,
670
+ props.kicker,
671
+ props.quote,
672
+ props.heading,
673
+ (props.items && Array.isArray(props.items) ? props.items[0] : undefined),
674
+ (props.features && Array.isArray(props.features) ? props.features[0]?.title : undefined),
675
+ (props.members && Array.isArray(props.members) ? props.members[0]?.name : undefined),
676
+ ];
677
+ for (const value of candidates) {
678
+ if (value != null && String(value).trim())
679
+ return String(value).trim();
680
+ }
681
+ return slide.layout;
682
+ }
683
+ function buildRightPanelMarkup() {
684
+ return `<aside class="lp-editor-right-panel" aria-label="属性面板">
685
+ <div class="lp-property-header">属性</div>
686
+ <div id="lp-property-content">
687
+ <div class="lp-property-empty">点击幻灯片中的文本或图片以编辑属性</div>
688
+ </div>
689
+ </aside>`;
690
+ }
691
+ function buildScriptMarkup() {
692
+ return `<script>
693
+ (function () {
694
+ const slides = document.querySelectorAll('.lp-slide-wrapper');
695
+ const dots = document.querySelectorAll('.lp-nav-dot');
696
+ const prevBtn = document.getElementById('lp-prev');
697
+ const nextBtn = document.getElementById('lp-next');
698
+ const currentLabel = document.getElementById('lp-current');
699
+ let current = 0;
700
+
701
+ function updateClasses() {
702
+ slides.forEach((slide, index) => {
703
+ slide.classList.remove('active', 'prev');
704
+ if (index === current) slide.classList.add('active');
705
+ else if (index < current) slide.classList.add('prev');
706
+ });
707
+ dots.forEach((dot, index) => {
708
+ dot.classList.toggle('active', index === current);
709
+ });
710
+ if (currentLabel) currentLabel.textContent = String(current + 1);
711
+ if (prevBtn) prevBtn.disabled = current === 0;
712
+ if (nextBtn) nextBtn.disabled = current === slides.length - 1;
713
+ }
714
+
715
+ function goTo(index) {
716
+ if (index < 0 || index >= slides.length) return;
717
+ current = index;
718
+ updateClasses();
719
+ }
720
+
721
+ if (prevBtn) prevBtn.addEventListener('click', () => goTo(current - 1));
722
+ if (nextBtn) nextBtn.addEventListener('click', () => goTo(current + 1));
723
+ dots.forEach((dot) => {
724
+ dot.addEventListener('click', () => goTo(Number(dot.dataset.index)));
725
+ });
726
+ document.addEventListener('keydown', (e) => {
727
+ if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown') goTo(current + 1);
728
+ if (e.key === 'ArrowLeft' || e.key === 'PageUp') goTo(current - 1);
729
+ if (e.key === 'Home') goTo(0);
730
+ if (e.key === 'End') goTo(slides.length - 1);
731
+ });
732
+
733
+ updateClasses();
734
+ })();
735
+ </script>`;
736
+ }
737
+ function buildEditorScriptMarkup(goal) {
738
+ const goalJson = JSON.stringify(goal, null, 2).replace(/</g, '\\u003c');
739
+ return `<script>
740
+ window.__lemonPPT_goal = ${goalJson};
741
+ ${editorScript}
742
+ </script>`;
743
+ }
744
+ function escapeHtml(text) {
745
+ return text
746
+ .replace(/&/g, '&amp;')
747
+ .replace(/</g, '&lt;')
748
+ .replace(/>/g, '&gt;')
749
+ .replace(/"/g, '&quot;')
750
+ .replace(/'/g, '&#039;');
751
+ }
752
+ //# sourceMappingURL=render.js.map