dsh-ppt 0.4.5 → 0.5.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/CHANGELOG.md +8 -0
- package/README.en.md +7 -1
- package/README.md +7 -1
- package/package.json +1 -1
- package/skills/dsh-ppt/scripts/deck-core.mjs +208 -75
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0(2026-09-27)
|
|
4
|
+
|
|
5
|
+
- 包含新的演讲者预览、缩略图和计时控件。浏览器不支持或拒绝全屏时显示操作提示,不再无声失败。
|
|
6
|
+
|
|
7
|
+
- **演讲者视图升级**:独立窗口内显示真实当前页与下一页预览(复用幻灯片渲染)、全片缩略图条(点击跳页)、wall clock + 计时(P 暂停 / T 重置)、备注字号 +/-、黑屏 / 白屏(B / W)、结束放映一键退出。
|
|
8
|
+
- **通信更稳**:演讲者窗口改用 Blob URL + postMessage 同步,不再依赖同源 document 直连,file:// 打开的 deck 也能正常联动;窗口被直接关闭后 0.8 秒内自动恢复普通模式。
|
|
9
|
+
- **观众端**:黑屏 / 白屏时隐藏 HUD;观众端备注与 HUD 默认隐藏逻辑保持不变。
|
|
10
|
+
- 播放器保留 0.4.5 的全部修复,70 项测试通过。
|
|
3
11
|
## 0.4.5(2026-09-23)
|
|
4
12
|
|
|
5
13
|
- **播放器交互修复(由合成用户复测驱动)**:备注面板不再遮挡页脚 HUD,新增关闭按钮并支持 Esc;无备注页显示「本页无备注」占位;`G` 改为缩略图网格总览(当前页高亮、页码同步、点击跳页、Esc 退出、页码角标);`?` 打开快捷键帮助;表格页补页面标题、数字列右对齐、表格边界与内容列对齐、表头对比度达标;窄屏字号下限 14px、HUD 按钮 44px;新增环绕提示、ArrowUp/Down 翻页、数字 1-9 跳页、focus-visible 与 reduced-motion 处理。
|
package/README.en.md
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
# dsh-ppt
|
|
2
|
+
|
|
3
|
+
## 0.5.0 update (2026-09-27)
|
|
4
|
+
|
|
5
|
+
Includes presenter previews, thumbnails and timer controls. Unsupported or rejected fullscreen requests now show an actionable message instead of silently failing.
|
|
6
|
+
|
|
7
|
+
Validation host: Harness 0.1.7-rc.2 built from official sources, retaining the local tool-scheduler fix. Build and automated checks pass; interactive coverage and external-service limits are recorded in this release round.
|
|
2
8
|
|
|
3
9
|
   
|
|
4
10
|
|
|
@@ -85,7 +91,7 @@ Existing artifacts are not overwritten by default: if any member of the trio alr
|
|
|
85
91
|
|
|
86
92
|
| File | Purpose |
|
|
87
93
|
| --- | --- |
|
|
88
|
-
| `*.html` | Standalone slideshow: arrow keys, V presenter view (separate window with
|
|
94
|
+
| `*.html` | Standalone slideshow: arrow keys, V presenter view (separate window with real current/next previews, thumbnail strip, notes, timer, progress, pause, font size, blank screen), S notes, G thumbnail overview, F fullscreen, P print, ? shortcuts, Esc close |
|
|
89
95
|
| `*.pptx` | Editable 16:9 presentation (hand-written OOXML, zip via `node:zlib`, no third-party deps) |
|
|
90
96
|
| `*.json` | Structured manifest (version, theme, language, slides) |
|
|
91
97
|
|
package/README.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
[English](README.en.md)
|
|
2
2
|
|
|
3
3
|
# dsh-ppt
|
|
4
|
+
|
|
5
|
+
## 0.5.0 更新(2026-09-27)
|
|
6
|
+
|
|
7
|
+
包含新的演讲者预览、缩略图和计时控件。浏览器不支持或拒绝全屏时显示操作提示,不再无声失败。
|
|
8
|
+
|
|
9
|
+
验证宿主:官方源码构建的 Harness 0.1.7-rc.2(保留本地工具调度器修复)。构建与自动测试通过;实际操作和外部服务限制见本轮验收记录。
|
|
4
10
|
|
|
5
11
|
> **一句话 / 一篇文档 → 完整演示文稿**:HTML 网页放映 + PPTX 导出,5 套视觉主题,页间转场 + 要点入场动画,中英双语。
|
|
6
12
|
|
|
@@ -78,7 +84,7 @@ node <skill-dir>/scripts/build-deck.mjs \
|
|
|
78
84
|
|
|
79
85
|
| 文件 | 用途 |
|
|
80
86
|
| --- | --- |
|
|
81
|
-
| `*.html` | 独立网页放映:← → 翻页,V
|
|
87
|
+
| `*.html` | 独立网页放映:← → 翻页,V 演讲者视图(独立窗口:真实当前页/下一页预览、缩略图跳页、备注、计时、进度、暂停、字号、黑/白屏),S 备注,G 缩略图总览,F 全屏,P 打印,? 快捷键,Esc 关闭 |
|
|
82
88
|
| `*.pptx` | 16:9 可编辑演示文稿(OOXML 由插件手写,zip 用 node:zlib,无第三方依赖) |
|
|
83
89
|
| `*.json` | 结构化 manifest(版本、主题、语言、每页内容) |
|
|
84
90
|
|
package/package.json
CHANGED
|
@@ -171,6 +171,7 @@ export const LANGUAGES = {
|
|
|
171
171
|
presenterHint: '备注:未开演讲者窗口时按 S 显示在顶部;打开后只在演讲者窗口显示,观众看不到',
|
|
172
172
|
wrapHint: '已环绕到',
|
|
173
173
|
popupBlocked: '浏览器拦截了演讲者窗口,请允许弹出窗口后按 V 重试',
|
|
174
|
+
fullscreenUnavailable: '当前窗口无法进入全屏,请在浏览器中独立打开演示文件后重试。',
|
|
174
175
|
notesCloseLabel: '关闭备注',
|
|
175
176
|
overviewHint: '点击缩略图跳页,方向键切换,Enter 进入,Esc 退出'
|
|
176
177
|
},
|
|
@@ -206,6 +207,7 @@ export const LANGUAGES = {
|
|
|
206
207
|
presenterHint: 'Notes: S shows them on top until a presenter window is open; afterwards only in the presenter window.',
|
|
207
208
|
wrapHint: 'Wrapped to',
|
|
208
209
|
popupBlocked: 'The presenter window was blocked. Allow pop-ups and press V to retry.',
|
|
210
|
+
fullscreenUnavailable: 'Fullscreen is unavailable here. Open the presentation in a browser tab and try again.',
|
|
209
211
|
notesCloseLabel: 'Close notes',
|
|
210
212
|
overviewHint: 'Click a thumbnail to jump; arrows move, Enter opens, Esc exits'
|
|
211
213
|
},
|
|
@@ -241,6 +243,7 @@ export const LANGUAGES = {
|
|
|
241
243
|
presenterHint: '备注:未开演讲者窗口时按 S 显示在顶部;打开后只在演讲者窗口显示 · Notes: S shows on top until a presenter window is open',
|
|
242
244
|
wrapHint: '已环绕到 · Wrapped to',
|
|
243
245
|
popupBlocked: '浏览器拦截了演讲者窗口 · Presenter window blocked; allow pop-ups and press V',
|
|
246
|
+
fullscreenUnavailable: '当前窗口无法全屏,请独立打开演示文件 · Open this presentation in a browser tab for fullscreen.',
|
|
244
247
|
notesCloseLabel: '关闭备注 · Close notes',
|
|
245
248
|
overviewHint: '点击缩略图跳页 · Click to jump, arrows move, Enter opens, Esc exits'
|
|
246
249
|
},
|
|
@@ -1157,6 +1160,10 @@ body.notes-open #notes-panel{display:block}
|
|
|
1157
1160
|
padding:9px 18px;font-size:13px;
|
|
1158
1161
|
}
|
|
1159
1162
|
#toast[hidden]{display:none}
|
|
1163
|
+
#blank{position:fixed;inset:0;z-index:80;display:none;pointer-events:none}
|
|
1164
|
+
body.blank-black #blank{display:block;background:#000}
|
|
1165
|
+
body.blank-white #blank{display:block;background:#fff}
|
|
1166
|
+
body.blank-black #hud,body.blank-white #hud{visibility:hidden;pointer-events:none}
|
|
1160
1167
|
:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
|
|
1161
1168
|
body.overview #stage{
|
|
1162
1169
|
display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:18px;
|
|
@@ -1239,11 +1246,12 @@ ${slides}
|
|
|
1239
1246
|
</div>
|
|
1240
1247
|
<div id="help-panel" hidden>
|
|
1241
1248
|
<div class="notes-label">${escapeHtml(ui.helpTitle)}</div>
|
|
1242
|
-
<p><kbd>←</kbd> <kbd>→</kbd> / <kbd>Space</kbd> 翻页 · <kbd>Home</kbd> / <kbd>End</kbd> 首末页 · <kbd>S</kbd> ${escapeHtml(ui.notesToggle)} · <kbd>V</kbd> ${escapeHtml(ui.presenterToggle)} · <kbd>G</kbd> ${escapeHtml(ui.overviewToggle)} · <kbd>F</kbd> 全屏 · <kbd>P</kbd> 打印 · <kbd>Esc</kbd> ${escapeHtml(ui.closeLabel)} · <kbd>?</kbd> ${escapeHtml(ui.helpTitle)}</p>
|
|
1249
|
+
<p><kbd>←</kbd> <kbd>→</kbd> / <kbd>Space</kbd> 翻页 · <kbd>Home</kbd> / <kbd>End</kbd> 首末页 · <kbd>S</kbd> ${escapeHtml(ui.notesToggle)} · <kbd>V</kbd> ${escapeHtml(ui.presenterToggle)} · <kbd>G</kbd> ${escapeHtml(ui.overviewToggle)} · <kbd>F</kbd> 全屏 · <kbd>P</kbd> 打印 · <kbd>B</kbd> 黑屏 · <kbd>W</kbd> 白屏 · <kbd>Esc</kbd> ${escapeHtml(ui.closeLabel)} · <kbd>?</kbd> ${escapeHtml(ui.helpTitle)}</p>
|
|
1243
1250
|
<p>${escapeHtml(ui.presenterHint)}</p>
|
|
1244
1251
|
<p>${escapeHtml(ui.overviewHint)}</p>
|
|
1245
1252
|
</div>
|
|
1246
1253
|
<div id="toast" role="status" hidden></div>
|
|
1254
|
+
<div id="blank" aria-hidden="true"></div>
|
|
1247
1255
|
<script>
|
|
1248
1256
|
(() => {
|
|
1249
1257
|
const frames = Array.from(document.querySelectorAll('.slide-frame'));
|
|
@@ -1262,6 +1270,7 @@ ${slides}
|
|
|
1262
1270
|
const toastEl = document.getElementById('toast');
|
|
1263
1271
|
let index = 0;
|
|
1264
1272
|
let presenterWin = null;
|
|
1273
|
+
let presenterInitSent = false;
|
|
1265
1274
|
let toastTimer = 0;
|
|
1266
1275
|
const total = slides.length;
|
|
1267
1276
|
const ui = ${JSON.stringify(ui).replace(/</g, '\\u003c')};
|
|
@@ -1292,13 +1301,40 @@ ${slides}
|
|
|
1292
1301
|
updatePresenter();
|
|
1293
1302
|
}
|
|
1294
1303
|
|
|
1295
|
-
function
|
|
1296
|
-
if (presenterOpen()
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1304
|
+
function sendInit() {
|
|
1305
|
+
if (!presenterOpen()) return;
|
|
1306
|
+
try {
|
|
1307
|
+
presenterWin.postMessage({
|
|
1308
|
+
__dshPpt: 'init',
|
|
1309
|
+
title: (document.title.split(' · ')[1] || document.title),
|
|
1310
|
+
css: document.querySelector('style') ? document.querySelector('style').textContent : '',
|
|
1311
|
+
total: total,
|
|
1312
|
+
slides: slides.map(function (slide) { return slide.outerHTML; }),
|
|
1313
|
+
}, '*');
|
|
1314
|
+
} catch (_) { /* popup closing */ }
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
function sendState() {
|
|
1318
|
+
if (!presenterOpen()) return;
|
|
1319
|
+
const next = (index + 1) % total;
|
|
1320
|
+
try {
|
|
1321
|
+
presenterWin.postMessage({
|
|
1322
|
+
__dshPpt: 'state',
|
|
1323
|
+
index: index,
|
|
1324
|
+
total: total,
|
|
1325
|
+
title: titleOf(index),
|
|
1326
|
+
nextTitle: titleOf(next),
|
|
1327
|
+
notes: notesOf(index),
|
|
1328
|
+
nextNotes: notesOf(next),
|
|
1329
|
+
blank: document.body.classList.contains('blank-black') ? 'black' : (document.body.classList.contains('blank-white') ? 'white' : null),
|
|
1330
|
+
}, '*');
|
|
1331
|
+
} catch (_) { /* popup closing */ }
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
function updatePresenter() { sendState(); }
|
|
1335
|
+
|
|
1301
1336
|
function go(next) {
|
|
1337
|
+
if (document.body.classList.contains('blank-black') || document.body.classList.contains('blank-white')) setBlank(null);
|
|
1302
1338
|
const wrapped = next < 0 || next >= total;
|
|
1303
1339
|
index = (next + total) % total;
|
|
1304
1340
|
slides.forEach(function (slide, i) {
|
|
@@ -1342,71 +1378,167 @@ ${slides}
|
|
|
1342
1378
|
}
|
|
1343
1379
|
}
|
|
1344
1380
|
|
|
1345
|
-
function presenterHtml() {
|
|
1346
|
-
const
|
|
1347
|
-
const
|
|
1348
|
-
const
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
+ '#
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1381
|
+
function presenterHtml() {
|
|
1382
|
+
const labelCurrent = ${JSON.stringify(ui.currentLabel)};
|
|
1383
|
+
const labelNext = ${JSON.stringify(ui.nextLabel)};
|
|
1384
|
+
const labelNotes = ${JSON.stringify(ui.notesLabel)};
|
|
1385
|
+
const noNotes = ${JSON.stringify(ui.noNotes)};
|
|
1386
|
+
const hintText = ${JSON.stringify('← → 翻页 · P 暂停 · T 重置 · +/- 字号 · B/W 黑/白屏 · Esc 结束')};
|
|
1387
|
+
const deckTitle = ${JSON.stringify(manifest.title).replace(/</g, '\\u003c')};
|
|
1388
|
+
const langAttr = ${JSON.stringify(lang.attr)};
|
|
1389
|
+
const cs = getComputedStyle(document.documentElement);
|
|
1390
|
+
const val = function (name, fallback) { const v = cs.getPropertyValue(name).trim(); return v || fallback; };
|
|
1391
|
+
const css = [
|
|
1392
|
+
':root{--bg:' + val('--bg', '#070B14') + ';--panel:' + val('--panel', '#0D1424') + ';--fg:' + val('--fg', '#E8F1FF') + ';--muted:' + val('--muted', '#7E8BA8') + ';--accent:' + val('--accent', '#7C3AED') + ';--accent2:' + val('--accent2', '#06B6D4') + ';--font-heading:' + val('--font-heading', 'sans-serif') + ';--font-body:' + val('--font-body', 'sans-serif') + '}',
|
|
1393
|
+
'*{box-sizing:border-box}',
|
|
1394
|
+
'body{margin:0;background:var(--bg);color:var(--fg);font-family:var(--font-body);height:100vh;display:flex;flex-direction:column;overflow:hidden}',
|
|
1395
|
+
'header{display:flex;align-items:center;gap:14px;padding:9px 16px;border-bottom:1px solid color-mix(in srgb, var(--muted) 40%, transparent);font-size:13px;color:var(--muted)}',
|
|
1396
|
+
'header #ptitle{color:var(--fg);font-weight:700;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
1397
|
+
'header #pclock{font-variant-numeric:tabular-nums}',
|
|
1398
|
+
'main{flex:1;display:grid;grid-template-columns:1.12fr .88fr;gap:12px;padding:12px 16px;min-height:0}',
|
|
1399
|
+
'.col{display:flex;flex-direction:column;gap:10px;min-height:0}',
|
|
1400
|
+
'.card{background:var(--panel);border:1px solid color-mix(in srgb, var(--muted) 30%, transparent);border-radius:12px;padding:10px 12px;min-height:0;display:flex;flex-direction:column;gap:6px}',
|
|
1401
|
+
'.card-notes{flex:1}',
|
|
1402
|
+
'.lbl{color:var(--accent);font-size:11px;font-weight:700;letter-spacing:.12em;text-transform:uppercase}',
|
|
1403
|
+
'.pv{position:relative;width:100%;aspect-ratio:16/9;overflow:hidden;border-radius:8px;background:var(--bg);border:1px solid color-mix(in srgb, var(--muted) 25%, transparent);--pv-scale:.3}',
|
|
1404
|
+
'.pv-slide{position:absolute;left:0;top:0;transform:translate(var(--pv-x,0px),var(--pv-y,0px)) scale(var(--pv-scale,.25));transform-origin:top left}',
|
|
1405
|
+
'.pv-slide .slide{display:flex !important;position:absolute;left:0;top:0;width:100vw;height:100vh;animation:none !important}',
|
|
1406
|
+
'.pv-slide .bullets li{opacity:1 !important;animation:none !important}',
|
|
1407
|
+
'#thumbs{display:flex;gap:8px;overflow-x:auto;padding-bottom:2px}',
|
|
1408
|
+
'#thumbs .thumb{position:relative;flex:0 0 128px;aspect-ratio:16/9;border-radius:8px;overflow:hidden;border:1px solid color-mix(in srgb, var(--muted) 35%, transparent);cursor:pointer;background:var(--bg);--pv-scale:.09}',
|
|
1409
|
+
'#thumbs .thumb.is-current{outline:2px solid var(--accent);outline-offset:1px}',
|
|
1410
|
+
'#pnotes{font-size:var(--notes-size,19px);line-height:1.6;white-space:pre-wrap;overflow:auto;flex:1}',
|
|
1411
|
+
'footer{display:flex;align-items:center;gap:8px;padding:9px 16px;border-top:1px solid color-mix(in srgb, var(--muted) 40%, transparent);flex-wrap:wrap}',
|
|
1412
|
+
'button{background:color-mix(in srgb, var(--panel) 90%, transparent);color:var(--fg);border:1px solid color-mix(in srgb, var(--muted) 45%, transparent);border-radius:99px;padding:6px 12px;min-height:34px;font:inherit;font-size:13px;cursor:pointer}',
|
|
1413
|
+
'button:hover{border-color:var(--accent);color:var(--accent)}',
|
|
1414
|
+
'button[aria-pressed="true"]{background:var(--accent);color:var(--bg);border-color:var(--accent)}',
|
|
1415
|
+
'#ptimer{font-variant-numeric:tabular-nums;font-size:20px;font-weight:700;color:var(--accent);margin-left:6px}',
|
|
1416
|
+
'.bar{flex:1;min-width:100px;height:8px;background:color-mix(in srgb, var(--muted) 28%, transparent);border-radius:99px;overflow:hidden}',
|
|
1417
|
+
'#pprog{height:100%;width:0;background:var(--accent);transition:width .2s}',
|
|
1418
|
+
'.hint{color:var(--muted);font-size:11px;width:100%}',
|
|
1419
|
+
].join('');
|
|
1420
|
+
const js = [
|
|
1421
|
+
'var total=0,index=0,elapsed=0,started=Date.now(),paused=false,notesSize=19,slides=[],cssText="",curBlank=null,initialized=false,readyTries=0;',
|
|
1422
|
+
'function q(id){return document.getElementById(id)}',
|
|
1423
|
+
'function post(m){try{if(window.opener)window.opener.postMessage(m,"*")}catch(e){}}',
|
|
1424
|
+
'function fmt(ms){var s=Math.max(0,Math.floor(ms/1000)),m=Math.floor(s/60);s=s%60;var h=Math.floor(m/60);m=m%60;return (h>0?(h<10?"0":"")+h+":":"")+(m<10?"0":"")+m+":"+(s<10?"0":"")+s}',
|
|
1425
|
+
'function clock(){var d=new Date();return ("0"+d.getHours()).slice(-2)+":"+("0"+d.getMinutes()).slice(-2)+":"+("0"+d.getSeconds()).slice(-2)}',
|
|
1426
|
+
'function tick(){if(!paused)elapsed=Date.now()-started;q("ptimer").textContent=fmt(elapsed);q("pclock").textContent=clock();if(window.requestAnimationFrame)requestAnimationFrame(tick);else setTimeout(tick,250)}',
|
|
1427
|
+
'function fitBox(box){var rw=box.clientWidth||320,rh=box.clientHeight||180;var s=Math.min(rw/window.innerWidth,rh/window.innerHeight);var ox=(rw-window.innerWidth*s)/2,oy=(rh-window.innerHeight*s)/2;box.style.setProperty("--pv-scale",String(s));box.style.setProperty("--pv-x",ox+"px");box.style.setProperty("--pv-y",oy+"px")}',
|
|
1428
|
+
'function fit(){fitBox(q("pv-cur"));fitBox(q("pv-next"));var th=q("thumbs").children;for(var i=0;i<th.length;i++){fitBox(th[i])}}',
|
|
1429
|
+
'function setSlide(box,html){box.innerHTML="";var f=document.createElement("div");f.className="pv-slide";f.innerHTML=html;box.appendChild(f)}',
|
|
1430
|
+
'function buildThumbs(){var box=q("thumbs");box.innerHTML="";slides.forEach(function(html,i){var t=document.createElement("div");t.className="thumb";var f=document.createElement("div");f.className="pv-slide";f.innerHTML=html;t.appendChild(f);t.addEventListener("click",function(){post({__dshPpt:"cmd",kind:"go",index:i})});box.appendChild(t)});fit()}',
|
|
1431
|
+
'function renderState(st){index=st.index;total=st.total||total;q("pcounter").textContent=(index+1)+" / "+total;setSlide(q("pv-cur"),slides[index]||"");setSlide(q("pv-next"),slides[(index+1)%total]||"");q("pnotes").textContent=st.notes||' + JSON.stringify(ui.noNotes) + ';q("pprog").style.width=((index+1)/total*100)+"%";curBlank=st.blank||null;q("pv-black").setAttribute("aria-pressed",curBlank==="black"?"true":"false");q("pv-white").setAttribute("aria-pressed",curBlank==="white"?"true":"false");q("pv-black").textContent=curBlank==="black"?"取消黑屏":"黑屏";q("pv-white").textContent=curBlank==="white"?"取消白屏":"白屏";var th=q("thumbs").children;for(var i=0;i<th.length;i++){th[i].classList.toggle("is-current",i===index)}fit()}',
|
|
1432
|
+
'function togglePause(){paused=!paused;if(!paused)started=Date.now()-elapsed;var b=q("pause");b.setAttribute("aria-pressed",paused?"true":"false");b.textContent=paused?"继续":"暂停"}',
|
|
1433
|
+
'function resetTimer(){started=Date.now();elapsed=0}',
|
|
1434
|
+
'function applyNotesSize(){document.documentElement.style.setProperty("--notes-size",notesSize+"px")}',
|
|
1435
|
+
'window.addEventListener("message",function(e){var d=e.data||{};if(d.__dshPpt==="init"&&!initialized){initialized=true;slides=d.slides||[];total=d.total||slides.length;cssText=d.css||"";var st=document.createElement("style");st.textContent=cssText;document.head.appendChild(st);q("ptitle").textContent=d.title||"";buildThumbs()}else if(d.__dshPpt==="state"){renderState(d)}else if(d.__dshPpt==="close"){window.close()}});',
|
|
1436
|
+
'window.addEventListener("resize",fit);',
|
|
1437
|
+
'window.addEventListener("beforeunload",function(){post({__dshPpt:"cmd",kind:"closed"})});',
|
|
1438
|
+
'document.addEventListener("keydown",function(e){var k=e.key;if(k==="ArrowRight"||k===" "){e.preventDefault();post({__dshPpt:"cmd",kind:"go",delta:1})}else if(k==="ArrowLeft"){e.preventDefault();post({__dshPpt:"cmd",kind:"go",delta:-1})}else if(k==="Escape"){post({__dshPpt:"cmd",kind:"end"});window.close()}else if(k.toLowerCase()==="p"){togglePause()}else if(k.toLowerCase()==="t"){resetTimer()}else if(k==="+"||k==="="){notesSize=Math.min(34,notesSize+2);applyNotesSize()}else if(k==="-"){notesSize=Math.max(12,notesSize-2);applyNotesSize()}else if(k.toLowerCase()==="b"){post({__dshPpt:"cmd",kind:"blank",color:curBlank==="black"?null:"black"})}else if(k.toLowerCase()==="w"){post({__dshPpt:"cmd",kind:"blank",color:curBlank==="white"?null:"white"})}});',
|
|
1439
|
+
'q("prev").addEventListener("click",function(){post({__dshPpt:"cmd",kind:"go",delta:-1})});',
|
|
1440
|
+
'q("next").addEventListener("click",function(){post({__dshPpt:"cmd",kind:"go",delta:1})});',
|
|
1441
|
+
'q("pause").addEventListener("click",togglePause);',
|
|
1442
|
+
'q("reset").addEventListener("click",resetTimer);',
|
|
1443
|
+
'q("font-plus").addEventListener("click",function(){notesSize=Math.min(34,notesSize+2);applyNotesSize()});',
|
|
1444
|
+
'q("font-minus").addEventListener("click",function(){notesSize=Math.max(12,notesSize-2);applyNotesSize()});',
|
|
1445
|
+
'q("pv-black").addEventListener("click",function(){post({__dshPpt:"cmd",kind:"blank",color:curBlank==="black"?null:"black"})});',
|
|
1446
|
+
'q("pv-white").addEventListener("click",function(){post({__dshPpt:"cmd",kind:"blank",color:curBlank==="white"?null:"white"})});',
|
|
1447
|
+
'q("end").addEventListener("click",function(){post({__dshPpt:"cmd",kind:"end"});window.close()});',
|
|
1448
|
+
'applyNotesSize();tick();function ready(){if(initialized)return;post({__dshPpt:"ready"});readyTries++;if(readyTries<5)setTimeout(ready,700)}setTimeout(ready,250);',
|
|
1449
|
+
].join('');
|
|
1450
|
+
return '<!DOCTYPE html><html lang="' + langAttr + '"><head><meta charset="UTF-8"><title>' + deckTitle + ' · 演讲者视图</title><style>' + css + '</style></head><body>'
|
|
1451
|
+
+ '<header><div id="ptitle"></div><div id="pcounter"></div><div id="pclock" title="本地时间"></div></header>'
|
|
1452
|
+
+ '<main><div class="col">'
|
|
1453
|
+
+ '<div class="card"><div class="lbl">' + labelCurrent + '</div><div class="pv" id="pv-cur"></div></div>'
|
|
1454
|
+
+ '<div class="card"><div class="lbl">' + labelNext + '</div><div class="pv" id="pv-next"></div></div>'
|
|
1455
|
+
+ '<div class="card"><div class="lbl">SLIDES</div><div id="thumbs"></div></div>'
|
|
1456
|
+
+ '</div><div class="col"><div class="card card-notes"><div class="lbl">' + labelNotes + '</div><div id="pnotes"></div></div></div></main>'
|
|
1457
|
+
+ '<footer><button id="prev" title="上一页 (←)">◀</button><button id="next" title="下一页 (→)">▶</button><button id="pause" title="暂停 (P)" aria-pressed="false">暂停</button><button id="reset" title="重置计时 (T)">重置计时</button><button id="font-minus" title="备注字号减小 (-)">A−</button><button id="font-plus" title="备注字号增大 (+)">A+</button><button id="pv-black" title="黑屏 (B)">黑屏</button><button id="pv-white" title="白屏 (W)">白屏</button><button id="end" title="结束放映 (Esc)">结束放映</button><div id="ptimer">00:00</div><div class="bar"><div id="pprog"></div></div><div class="hint">' + hintText + '</div></footer>'
|
|
1458
|
+
+ '<scr' + 'ipt>' + js + '</scr' + 'ipt></body></html>';
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
async function toggleFullscreen() {
|
|
1391
1462
|
try {
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
function closePresenter() {
|
|
1402
|
-
if (presenterOpen()) { try { presenterWin.close(); } catch (_) { /* already gone */ } }
|
|
1403
|
-
document.body.classList.remove('presenting');
|
|
1463
|
+
if (document.fullscreenElement) { await document.exitFullscreen?.(); return; }
|
|
1464
|
+
if (!document.documentElement.requestFullscreen || document.fullscreenEnabled === false) {
|
|
1465
|
+
toast(ui.fullscreenUnavailable);
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
await document.documentElement.requestFullscreen();
|
|
1469
|
+
if (!document.fullscreenElement) toast(ui.fullscreenUnavailable);
|
|
1470
|
+
} catch (_) { toast(ui.fullscreenUnavailable); }
|
|
1404
1471
|
}
|
|
1405
1472
|
|
|
1406
|
-
|
|
1407
|
-
if (
|
|
1408
|
-
|
|
1409
|
-
|
|
1473
|
+
function openPresenter() {
|
|
1474
|
+
if (presenterOpen()) { try { presenterWin.focus(); } catch (_) { /* gone */ } return; }
|
|
1475
|
+
let url = '';
|
|
1476
|
+
try {
|
|
1477
|
+
url = URL.createObjectURL(new Blob([presenterHtml()], { type: 'text/html' }));
|
|
1478
|
+
} catch (_) { toast(ui.popupBlocked); return; }
|
|
1479
|
+
presenterInitSent = false;
|
|
1480
|
+
presenterWin = window.open(url, 'dsh-ppt-presenter', 'width=1180,height=760');
|
|
1481
|
+
if (!presenterWin) {
|
|
1482
|
+
try { URL.revokeObjectURL(url); } catch (_) { /* ignore */ }
|
|
1483
|
+
toast(ui.popupBlocked);
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
document.body.classList.add('presenting');
|
|
1487
|
+
if (document.activeElement && typeof document.activeElement.blur === 'function') document.activeElement.blur();
|
|
1488
|
+
setTimeout(function () { try { URL.revokeObjectURL(url); } catch (_) { /* ignore */ } }, 5000);
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
function closePresenter() {
|
|
1492
|
+
if (presenterOpen()) {
|
|
1493
|
+
try { presenterWin.postMessage({ __dshPpt: 'close' }, '*'); presenterWin.close(); } catch (_) { /* gone */ }
|
|
1494
|
+
}
|
|
1495
|
+
document.body.classList.remove('presenting');
|
|
1496
|
+
presenterInitSent = false;
|
|
1497
|
+
if (document.fullscreenElement) { try { document.exitFullscreen?.(); } catch (_) { /* ignore */ } }
|
|
1498
|
+
setBlank(null);
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
function setBlank(color) {
|
|
1502
|
+
document.body.classList.remove('blank-black', 'blank-white');
|
|
1503
|
+
if (color === 'black') document.body.classList.add('blank-black');
|
|
1504
|
+
else if (color === 'white') document.body.classList.add('blank-white');
|
|
1505
|
+
sendState();
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
setInterval(function () {
|
|
1509
|
+
if (!presenterOpen() && document.body.classList.contains('presenting')) {
|
|
1510
|
+
document.body.classList.remove('presenting');
|
|
1511
|
+
if (document.fullscreenElement) { try { document.exitFullscreen?.(); } catch (_) { /* ignore */ } }
|
|
1512
|
+
setBlank(null);
|
|
1513
|
+
}
|
|
1514
|
+
}, 800);
|
|
1515
|
+
|
|
1516
|
+
window.addEventListener('message', function (event) {
|
|
1517
|
+
if (event.source !== presenterWin) return;
|
|
1518
|
+
const data = event.data || {};
|
|
1519
|
+
if (data.__dshPpt === 'cmd' && (data.kind === 'end' || data.kind === 'closed')) { closePresenter(); return; }
|
|
1520
|
+
if (!presenterOpen()) return;
|
|
1521
|
+
if (data.__dshPpt === 'ready') {
|
|
1522
|
+
if (!presenterInitSent) { presenterInitSent = true; sendInit(); }
|
|
1523
|
+
sendState();
|
|
1524
|
+
return;
|
|
1525
|
+
}
|
|
1526
|
+
if (data.__dshPpt !== 'cmd') return;
|
|
1527
|
+
if (data.kind === 'go') {
|
|
1528
|
+
setBlank(null);
|
|
1529
|
+
if (typeof data.index === 'number') go(data.index);
|
|
1530
|
+
else go(index + (data.delta || 0));
|
|
1531
|
+
sendState();
|
|
1532
|
+
} else if (data.kind === 'blank') {
|
|
1533
|
+
setBlank(data.color || null);
|
|
1534
|
+
} else if (data.kind === 'end') {
|
|
1535
|
+
closePresenter();
|
|
1536
|
+
} else if (data.kind === 'closed') {
|
|
1537
|
+
document.body.classList.remove('presenting');
|
|
1538
|
+
setBlank(null);
|
|
1539
|
+
}
|
|
1540
|
+
});
|
|
1541
|
+
|
|
1410
1542
|
document.addEventListener('keydown', (event) => {
|
|
1411
1543
|
const key = event.key;
|
|
1412
1544
|
const lower = key.toLowerCase();
|
|
@@ -1417,6 +1549,7 @@ ${slides}
|
|
|
1417
1549
|
if (!helpPanel.hidden) { helpPanel.hidden = true; return; }
|
|
1418
1550
|
if (document.body.classList.contains('overview')) { setOverview(false); return; }
|
|
1419
1551
|
if (notesOpen()) { setNotes(false); return; }
|
|
1552
|
+
if (document.body.classList.contains('presenting') || document.body.classList.contains('blank-black') || document.body.classList.contains('blank-white')) { closePresenter(); return; }
|
|
1420
1553
|
if (document.fullscreenElement) { document.exitFullscreen?.(); return; }
|
|
1421
1554
|
return;
|
|
1422
1555
|
}
|
|
@@ -1435,8 +1568,7 @@ ${slides}
|
|
|
1435
1568
|
} else if (key === 'Enter' && inOverview) {
|
|
1436
1569
|
event.preventDefault(); setOverview(false);
|
|
1437
1570
|
} else if (lower === 'f') {
|
|
1438
|
-
|
|
1439
|
-
else document.exitFullscreen?.();
|
|
1571
|
+
void toggleFullscreen();
|
|
1440
1572
|
} else if (lower === 'g') {
|
|
1441
1573
|
setOverview(!inOverview);
|
|
1442
1574
|
} else if (lower === 's') {
|
|
@@ -1445,6 +1577,10 @@ ${slides}
|
|
|
1445
1577
|
} else if (lower === 'v') {
|
|
1446
1578
|
if (presenterOpen()) closePresenter();
|
|
1447
1579
|
else openPresenter();
|
|
1580
|
+
} else if (lower === 'b') {
|
|
1581
|
+
setBlank(document.body.classList.contains('blank-black') ? null : 'black');
|
|
1582
|
+
} else if (lower === 'w') {
|
|
1583
|
+
setBlank(document.body.classList.contains('blank-white') ? null : 'white');
|
|
1448
1584
|
} else if (lower === 'p') {
|
|
1449
1585
|
window.print();
|
|
1450
1586
|
} else if (key === '?') {
|
|
@@ -1467,10 +1603,7 @@ ${slides}
|
|
|
1467
1603
|
if (Math.abs(delta) > 48 && !document.body.classList.contains('overview')) go(index + (delta < 0 ? 1 : -1));
|
|
1468
1604
|
}, { passive: true });
|
|
1469
1605
|
|
|
1470
|
-
fullscreenBtn.addEventListener('click', () => {
|
|
1471
|
-
if (!document.fullscreenElement) document.documentElement.requestFullscreen?.();
|
|
1472
|
-
else document.exitFullscreen?.();
|
|
1473
|
-
});
|
|
1606
|
+
fullscreenBtn.addEventListener('click', () => { void toggleFullscreen(); });
|
|
1474
1607
|
notesBtn.addEventListener('click', () => {
|
|
1475
1608
|
if (document.body.classList.contains('presenting')) { toast(ui.presenterHint); return; }
|
|
1476
1609
|
setNotes(!notesOpen());
|