pindeck 2.8.2 → 2.10.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/README.md +211 -23
- package/cli.mjs +6 -3
- package/lib/capabilities.mjs +118 -0
- package/lib/claude.mjs +8 -3
- package/lib/deck-files.mjs +94 -0
- package/lib/eids.mjs +101 -8
- package/lib/frameworks.mjs +59 -0
- package/lib/instructions.mjs +74 -0
- package/lib/pdf-render.jxa.js +5 -3
- package/lib/pdf.mjs +4 -2
- package/lib/platform-notice.mjs +6 -6
- package/lib/pptx-render.mjs +78 -9
- package/lib/system-prompt.md +5 -3
- package/lib/waveform.mjs +9 -3
- package/package.json +6 -3
- package/public/bridge.js +248 -69
- package/public/editor.css +1 -0
- package/public/editor.js +82 -24
- package/public/index.html +2 -2
- package/server.mjs +630 -155
package/public/bridge.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
main the visible slide in the editor: navigation, selection, drag and resize, text editing, pins
|
|
5
5
|
measure hidden 1280x720 copy used to measure any slide without moving the user's view
|
|
6
6
|
shot headless screenshot of one slide, with pin markers
|
|
7
|
-
dump headless measurement; writes the result into the DOM for --dump-dom
|
|
7
|
+
dump headless measurement; writes the result into the DOM for --dump-dom
|
|
8
|
+
pdf print layout for the PDF export (decks made without reveal.js; reveal.js has its own) */
|
|
8
9
|
|
|
9
10
|
(function () {
|
|
10
11
|
const params = new URLSearchParams(location.search);
|
|
@@ -14,64 +15,69 @@
|
|
|
14
15
|
const deckMeta = (n) => document.querySelector(`meta[name="pindeck:${n}"]`)?.content?.trim();
|
|
15
16
|
const FOOTER_TOP = deckMeta("footer-top") ? +deckMeta("footer-top") : null;
|
|
16
17
|
const LOCKED_SEL = deckMeta("locked") || ".slide-foot, .logo";
|
|
18
|
+
// A deck made without reveal.js names its slides with <meta name="pindeck:slides"> (a CSS selector)
|
|
19
|
+
const SLIDE_SEL = deckMeta("slides") || null;
|
|
20
|
+
const SLIDE = SLIDE_SEL ? `:is(${SLIDE_SEL})` : ".reveal .slides section"; // in CSS and closest()
|
|
21
|
+
const SLIDE_OF = SLIDE_SEL || "section"; // the slide an element is on
|
|
17
22
|
const host = () => (window.parent !== window ? window.parent.__editorHost : null);
|
|
18
|
-
let W = 1280, H = 720;
|
|
23
|
+
let W = 1280, H = 720, deck = null; // deck: reveal.js or a selector deck (see "Decks" below)
|
|
19
24
|
|
|
20
25
|
/* ---- Styles for editor-only overlays ---------------------------------------- */
|
|
21
26
|
const css = document.createElement("style");
|
|
22
|
-
// --u = screen pixels per slide pixel, inverted: overlays keep a constant on-screen size at any
|
|
27
|
+
// --pindeck-u = screen pixels per slide pixel, inverted: overlays keep a constant on-screen size at any
|
|
28
|
+
// zoom (a name of its own: decks use --u for their own units)
|
|
23
29
|
css.textContent = `
|
|
24
|
-
.__pin { position:absolute; z-index:1000; width:calc(26px*var(--u,1)); height:calc(26px*var(--u,1));
|
|
25
|
-
margin:calc(-13px*var(--u,1)) 0 0 calc(-13px*var(--u,1)); border-radius:50%;
|
|
26
|
-
background:#ED8C01; color:#fff; font:700 calc(14px*var(--u,1))/calc(26px*var(--u,1)) system-ui, sans-serif; text-align:center;
|
|
27
|
-
box-shadow:0 0 0 calc(2px*var(--u,1)) #fff, 0 calc(2px*var(--u,1)) calc(6px*var(--u,1)) rgba(0,0,0,.45);
|
|
30
|
+
.__pin { position:absolute; z-index:1000; width:calc(26px*var(--pindeck-u,1)); height:calc(26px*var(--pindeck-u,1));
|
|
31
|
+
margin:calc(-13px*var(--pindeck-u,1)) 0 0 calc(-13px*var(--pindeck-u,1)); border-radius:50%;
|
|
32
|
+
background:#ED8C01; color:#fff; font:700 calc(14px*var(--pindeck-u,1))/calc(26px*var(--pindeck-u,1)) system-ui, sans-serif; text-align:center;
|
|
33
|
+
box-shadow:0 0 0 calc(2px*var(--pindeck-u,1)) #fff, 0 calc(2px*var(--pindeck-u,1)) calc(6px*var(--pindeck-u,1)) rgba(0,0,0,.45);
|
|
28
34
|
cursor:pointer; pointer-events:auto; }
|
|
29
|
-
.__hover { position:absolute; z-index:999; pointer-events:none; outline:calc(2px*var(--u,1)) dashed #ED8C01;
|
|
30
|
-
outline-offset:calc(2px*var(--u,1)); background:rgba(237,140,1,.06); }
|
|
31
|
-
.__hover span { position:absolute; left:calc(-2px*var(--u,1)); background:#ED8C01; color:#fff; white-space:nowrap;
|
|
32
|
-
font:600 calc(11px*var(--u,1))/calc(17px*var(--u,1)) system-ui, sans-serif; padding:0 calc(5px*var(--u,1));
|
|
33
|
-
border-radius:calc(3px*var(--u,1)); }
|
|
34
|
-
.__hover span.above { top:calc(-21px*var(--u,1)); } .__hover span.below { bottom:calc(-21px*var(--u,1)); }
|
|
35
|
+
.__hover { position:absolute; z-index:999; pointer-events:none; outline:calc(2px*var(--pindeck-u,1)) dashed #ED8C01;
|
|
36
|
+
outline-offset:calc(2px*var(--pindeck-u,1)); background:rgba(237,140,1,.06); }
|
|
37
|
+
.__hover span { position:absolute; left:calc(-2px*var(--pindeck-u,1)); background:#ED8C01; color:#fff; white-space:nowrap;
|
|
38
|
+
font:600 calc(11px*var(--pindeck-u,1))/calc(17px*var(--pindeck-u,1)) system-ui, sans-serif; padding:0 calc(5px*var(--pindeck-u,1));
|
|
39
|
+
border-radius:calc(3px*var(--pindeck-u,1)); }
|
|
40
|
+
.__hover span.above { top:calc(-21px*var(--pindeck-u,1)); } .__hover span.below { bottom:calc(-21px*var(--pindeck-u,1)); }
|
|
35
41
|
.__hover.locked { outline-color:#888; background:rgba(0,0,0,.04); }
|
|
36
42
|
.__hover.locked span { background:#888; }
|
|
37
|
-
.__flash { position:absolute; z-index:998; pointer-events:none; outline:calc(3px*var(--u,1)) solid #ED8C01;
|
|
38
|
-
outline-offset:calc(3px*var(--u,1)); animation:__flash 1.4s ease-out forwards; }
|
|
43
|
+
.__flash { position:absolute; z-index:998; pointer-events:none; outline:calc(3px*var(--pindeck-u,1)) solid #ED8C01;
|
|
44
|
+
outline-offset:calc(3px*var(--pindeck-u,1)); animation:__flash 1.4s ease-out forwards; }
|
|
39
45
|
@keyframes __flash { 0%,60% { opacity:1 } 100% { opacity:0 } }
|
|
40
|
-
html.__pinmode
|
|
41
|
-
html.__pinmode
|
|
42
|
-
.__hover.sel { outline:calc(1px*var(--u,1)) solid #1a73e8; outline-offset:0; background:none; }
|
|
46
|
+
html.__pinmode ${SLIDE}, html.__pinmode ${SLIDE} * { cursor:crosshair !important; }
|
|
47
|
+
html.__pinmode ${SLIDE} .__pin { cursor:pointer !important; }
|
|
48
|
+
.__hover.sel { outline:calc(1px*var(--pindeck-u,1)) solid #1a73e8; outline-offset:0; background:none; }
|
|
43
49
|
.__hover.sel span { background:#1a73e8; }
|
|
44
|
-
.__sel { position:absolute; z-index:1001; pointer-events:none; outline:calc(1.5px*var(--u,1)) solid #1a73e8; }
|
|
45
|
-
.__sel i { position:absolute; width:calc(9px*var(--u,1)); height:calc(9px*var(--u,1)); margin:calc(-5px*var(--u,1)) 0 0 calc(-5px*var(--u,1));
|
|
46
|
-
background:#fff; border:calc(1.5px*var(--u,1)) solid #1a73e8; box-sizing:border-box; pointer-events:auto; }
|
|
50
|
+
.__sel { position:absolute; z-index:1001; pointer-events:none; outline:calc(1.5px*var(--pindeck-u,1)) solid #1a73e8; }
|
|
51
|
+
.__sel i { position:absolute; width:calc(9px*var(--pindeck-u,1)); height:calc(9px*var(--pindeck-u,1)); margin:calc(-5px*var(--pindeck-u,1)) 0 0 calc(-5px*var(--pindeck-u,1));
|
|
52
|
+
background:#fff; border:calc(1.5px*var(--pindeck-u,1)) solid #1a73e8; box-sizing:border-box; pointer-events:auto; }
|
|
47
53
|
.__sel i[data-h="nw"] { left:0; top:0; cursor:nwse-resize; } .__sel i[data-h="n"] { left:50%; top:0; cursor:ns-resize; }
|
|
48
54
|
.__sel i[data-h="ne"] { left:100%; top:0; cursor:nesw-resize; } .__sel i[data-h="e"] { left:100%; top:50%; cursor:ew-resize; }
|
|
49
55
|
.__sel i[data-h="se"] { left:100%; top:100%; cursor:nwse-resize; } .__sel i[data-h="s"] { left:50%; top:100%; cursor:ns-resize; }
|
|
50
56
|
.__sel i[data-h="sw"] { left:0; top:100%; cursor:nesw-resize; } .__sel i[data-h="w"] { left:0; top:50%; cursor:ew-resize; }
|
|
51
57
|
.__sel.readonly i { display:none; }
|
|
52
|
-
.__sel-label { position:absolute; left:calc(-1.5px*var(--u,1)); white-space:nowrap; background:#1a73e8; color:#fff;
|
|
53
|
-
font:600 calc(11px*var(--u,1))/calc(17px*var(--u,1)) system-ui, sans-serif; padding:0 calc(5px*var(--u,1));
|
|
54
|
-
border-radius:calc(3px*var(--u,1)); font-variant-numeric:tabular-nums; }
|
|
55
|
-
.__sel-label.above { top:calc(-21px*var(--u,1)); } .__sel-label.below { bottom:calc(-21px*var(--u,1)); }
|
|
58
|
+
.__sel-label { position:absolute; left:calc(-1.5px*var(--pindeck-u,1)); white-space:nowrap; background:#1a73e8; color:#fff;
|
|
59
|
+
font:600 calc(11px*var(--pindeck-u,1))/calc(17px*var(--pindeck-u,1)) system-ui, sans-serif; padding:0 calc(5px*var(--pindeck-u,1));
|
|
60
|
+
border-radius:calc(3px*var(--pindeck-u,1)); font-variant-numeric:tabular-nums; }
|
|
61
|
+
.__sel-label.above { top:calc(-21px*var(--pindeck-u,1)); } .__sel-label.below { bottom:calc(-21px*var(--pindeck-u,1)); }
|
|
56
62
|
.__guide { position:absolute; z-index:1002; pointer-events:none; background:#e5399d; }
|
|
57
|
-
.__guide.v { top:0; height:100%; width:calc(1px*var(--u,1)); }
|
|
58
|
-
.__guide.h { left:0; width:100%; height:calc(1px*var(--u,1)); }
|
|
59
|
-
html.__selmode
|
|
60
|
-
html.__selmode
|
|
61
|
-
html.__selmode
|
|
62
|
-
.__editing { outline:calc(2px*var(--u,1)) solid #1a73e8 !important; outline-offset:calc(3px*var(--u,1));
|
|
63
|
+
.__guide.v { top:0; height:100%; width:calc(1px*var(--pindeck-u,1)); }
|
|
64
|
+
.__guide.h { left:0; width:100%; height:calc(1px*var(--pindeck-u,1)); }
|
|
65
|
+
html.__selmode ${SLIDE} * { cursor:default; }
|
|
66
|
+
html.__selmode ${SLIDE} .__selected, html.__selmode.__dragging * { cursor:move !important; }
|
|
67
|
+
html.__selmode ${SLIDE} .__sel i { cursor:inherit; }
|
|
68
|
+
.__editing { outline:calc(2px*var(--pindeck-u,1)) solid #1a73e8 !important; outline-offset:calc(3px*var(--pindeck-u,1));
|
|
63
69
|
background:rgba(26,115,232,.04); cursor:text !important; caret-color:#1a73e8; }
|
|
64
70
|
.__editing * { cursor:text !important; }
|
|
65
|
-
.__hidden-banner { position:absolute; z-index:997; top:calc(26px*var(--u,1)); right:calc(26px*var(--u,1)); pointer-events:none;
|
|
66
|
-
background:#ED8C01; color:#fff; font:600 calc(12px*var(--u,1))/calc(22px*var(--u,1)) system-ui, sans-serif;
|
|
67
|
-
padding:0 calc(10px*var(--u,1)); border-radius:calc(11px*var(--u,1)); box-shadow:0 calc(2px*var(--u,1)) calc(8px*var(--u,1)) rgba(0,0,0,.25); }
|
|
71
|
+
.__hidden-banner { position:absolute; z-index:997; top:calc(26px*var(--pindeck-u,1)); right:calc(26px*var(--pindeck-u,1)); pointer-events:none;
|
|
72
|
+
background:#ED8C01; color:#fff; font:600 calc(12px*var(--pindeck-u,1))/calc(22px*var(--pindeck-u,1)) system-ui, sans-serif;
|
|
73
|
+
padding:0 calc(10px*var(--pindeck-u,1)); border-radius:calc(11px*var(--pindeck-u,1)); box-shadow:0 calc(2px*var(--pindeck-u,1)) calc(8px*var(--pindeck-u,1)) rgba(0,0,0,.25); }
|
|
68
74
|
.__edit-hint { position:absolute; z-index:1003; pointer-events:none; white-space:nowrap; background:#1a73e8; color:#fff;
|
|
69
|
-
font:600 calc(11px*var(--u,1))/calc(17px*var(--u,1)) system-ui, sans-serif; padding:0 calc(5px*var(--u,1));
|
|
70
|
-
border-radius:calc(3px*var(--u,1)); }`;
|
|
75
|
+
font:600 calc(11px*var(--pindeck-u,1))/calc(17px*var(--pindeck-u,1)) system-ui, sans-serif; padding:0 calc(5px*var(--pindeck-u,1));
|
|
76
|
+
border-radius:calc(3px*var(--pindeck-u,1)); }`;
|
|
71
77
|
document.head.appendChild(css);
|
|
72
78
|
|
|
73
79
|
/* ---- Geometry ------------------------------------------------------------------ */
|
|
74
|
-
const slides = () =>
|
|
80
|
+
const slides = () => deck.slides();
|
|
75
81
|
const indexOf = (sec) => slides().indexOf(sec);
|
|
76
82
|
const r1 = (v) => Math.round(v);
|
|
77
83
|
function frame(sec) {
|
|
@@ -122,7 +128,7 @@
|
|
|
122
128
|
hasFooter: !!sec.querySelector(".slide-foot"), hidden };
|
|
123
129
|
}
|
|
124
130
|
function info() {
|
|
125
|
-
return { slides: slides().map((s, i) => slideInfo(s, i)), current: indexOf(
|
|
131
|
+
return { slides: slides().map((s, i) => slideInfo(s, i)), current: indexOf(deck.current()) };
|
|
126
132
|
}
|
|
127
133
|
|
|
128
134
|
/* ---- Layout measurement ------------------------------------------------------------- */
|
|
@@ -138,7 +144,7 @@
|
|
|
138
144
|
const hidden = cs.display === "none" || cs.visibility === "hidden" || +cs.opacity === 0 || (b.w === 0 && b.h === 0);
|
|
139
145
|
const leaf = !el.querySelector("[data-eid]");
|
|
140
146
|
const own = el === sec ? "" : textOf(el, !leaf);
|
|
141
|
-
const tag = el.tagName.toLowerCase() + (el.classList.length ? "." + [...el.classList].filter((c) => !/^(fragment|visible|current-fragment|present|past|future)$/.test(c)).join(".") : "");
|
|
147
|
+
const tag = el.tagName.toLowerCase() + (el.classList.length ? "." + [...el.classList].filter((c) => !/^(fragment|visible|current-fragment|present|past|future)$/.test(c) && !c.startsWith("__")).join(".") : "");
|
|
142
148
|
const e = { eid: el.dataset.eid || si.eid, tag: tag.replace(/\.$/, ""), depth, ...b };
|
|
143
149
|
if (own && el.tagName !== "IMG" && el !== sec) e.font = fontOf(el);
|
|
144
150
|
if (own) e.text = own;
|
|
@@ -218,13 +224,8 @@
|
|
|
218
224
|
const nextFrame = () => Promise.resolve();
|
|
219
225
|
|
|
220
226
|
/* ---- Navigation ------------------------------------------------------------------------ */
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (!sec) return;
|
|
224
|
-
const ix = Reveal.getIndices(sec);
|
|
225
|
-
Reveal.slide(ix.h, ix.v);
|
|
226
|
-
}
|
|
227
|
-
function indices(index) { const sec = slides()[index]; return sec ? Reveal.getIndices(sec) : { h: 0, v: 0 }; }
|
|
227
|
+
const goto = (index) => deck.goto(index);
|
|
228
|
+
const presentHash = (index) => deck.hash(index); // the slide's address in the deck opened on its own
|
|
228
229
|
|
|
229
230
|
/* ---- Pins and hover ---------------------------------------------------------------------- */
|
|
230
231
|
let mode = "select", pins = [], editable = true; // mode: "select" (drag, resize) or "pin"
|
|
@@ -249,12 +250,12 @@
|
|
|
249
250
|
}
|
|
250
251
|
function targetOf(t) {
|
|
251
252
|
if (!t || !t.closest) return null;
|
|
252
|
-
const sec = t.closest(
|
|
253
|
+
const sec = t.closest(SLIDE);
|
|
253
254
|
if (!sec || t.closest(".__pin, .__hover, .__sel, .__guide")) return null;
|
|
254
255
|
const foot = t.closest(".slide-foot");
|
|
255
256
|
const el = foot ? sec : t.closest("[data-eid]") || sec;
|
|
256
257
|
const locked = foot ? "footer, set by brand.css (locked)" : el !== sec && el.matches(LOCKED_SEL) ? "positioned by brand.css (locked)" : null;
|
|
257
|
-
return { sec: el === sec ? sec : el.closest(
|
|
258
|
+
return { sec: el === sec ? sec : el.closest(SLIDE_OF), el, locked, foot };
|
|
258
259
|
}
|
|
259
260
|
function onPointerDown(e) {
|
|
260
261
|
if (MODE !== "main" || e.button !== 0) return;
|
|
@@ -309,7 +310,7 @@
|
|
|
309
310
|
document.querySelectorAll(".__pin").forEach((p) => p.remove());
|
|
310
311
|
for (const p of pins) {
|
|
311
312
|
const sec = byEid(p.slide.eid);
|
|
312
|
-
if (!sec || sec.
|
|
313
|
+
if (!sec || !sec.matches(SLIDE_OF)) continue;
|
|
313
314
|
let x = p.point.x, y = p.point.y;
|
|
314
315
|
const el = p.eid !== p.slide.eid ? byEid(p.eid, sec) : null;
|
|
315
316
|
if (el && el.getClientRects().length) {
|
|
@@ -328,8 +329,8 @@
|
|
|
328
329
|
function highlight(eid) {
|
|
329
330
|
const el = byEid(eid);
|
|
330
331
|
if (!el) return;
|
|
331
|
-
const sec = el.
|
|
332
|
-
if (sec !==
|
|
332
|
+
const sec = el.closest(SLIDE_OF);
|
|
333
|
+
if (sec !== deck.current()) return;
|
|
333
334
|
const b = boxOf(el, sec);
|
|
334
335
|
const f = document.createElement("div");
|
|
335
336
|
f.className = "__flash";
|
|
@@ -396,8 +397,8 @@
|
|
|
396
397
|
}
|
|
397
398
|
function selectEid(eid) {
|
|
398
399
|
const el = eid && byEid(eid);
|
|
399
|
-
const sec = el?.closest(
|
|
400
|
-
if (!el || sec !==
|
|
400
|
+
const sec = el?.closest(SLIDE_OF);
|
|
401
|
+
if (!el || sec !== deck.current()) return select(null, null);
|
|
401
402
|
select(el, sec);
|
|
402
403
|
}
|
|
403
404
|
|
|
@@ -698,8 +699,8 @@
|
|
|
698
699
|
const h = document.createElement("div");
|
|
699
700
|
h.className = "__edit-hint";
|
|
700
701
|
h.textContent = "Editing · Enter to save · Esc to cancel · Shift+Enter new line";
|
|
701
|
-
Object.assign(h.style, b.y < 30 ? { left: b.x + "px", top: `calc(${b.y + b.h}px + 6px*var(--u,1))` }
|
|
702
|
-
: { left: b.x + "px", top: `calc(${b.y}px - 22px*var(--u,1))` });
|
|
702
|
+
Object.assign(h.style, b.y < 30 ? { left: b.x + "px", top: `calc(${b.y + b.h}px + 6px*var(--pindeck-u,1))` }
|
|
703
|
+
: { left: b.x + "px", top: `calc(${b.y}px - 22px*var(--pindeck-u,1))` });
|
|
703
704
|
editing.sec.appendChild(h);
|
|
704
705
|
}
|
|
705
706
|
function onEditKey(e) {
|
|
@@ -783,7 +784,7 @@
|
|
|
783
784
|
|
|
784
785
|
/* A free id for a new bullet on the same slide ("summary/li-4"); the server double-checks. */
|
|
785
786
|
function newBulletEid(el) {
|
|
786
|
-
const prefix = el.closest(
|
|
787
|
+
const prefix = el.closest(SLIDE_OF).dataset.eid;
|
|
787
788
|
const used = new Set([...document.querySelectorAll("[data-eid]")].map((e) => e.dataset.eid));
|
|
788
789
|
let id = `${prefix}/li`, k = 2;
|
|
789
790
|
while (used.has(id)) id = `${prefix}/li-${k++}`;
|
|
@@ -843,18 +844,183 @@
|
|
|
843
844
|
})));
|
|
844
845
|
}
|
|
845
846
|
|
|
847
|
+
/* ---- Decks -----------------------------------------------------------------------------------------
|
|
848
|
+
What the editor needs from a deck: its slides, the current one, going to a slide, the slide size and
|
|
849
|
+
scale, and events when the slide or the scale changes. reveal.js provides all of it. */
|
|
850
|
+
function revealDeck() {
|
|
851
|
+
const ix = (index) => { const sec = Reveal.getSlides()[index]; return sec ? Reveal.getIndices(sec) : { h: 0, v: 0 }; };
|
|
852
|
+
return {
|
|
853
|
+
slides: () => Reveal.getSlides(),
|
|
854
|
+
current: () => Reveal.getCurrentSlide(),
|
|
855
|
+
goto(index) { if (Reveal.getSlides()[index]) { const i = ix(index); Reveal.slide(i.h, i.v); } },
|
|
856
|
+
hash(index) { const i = ix(index); return `#/${i.h}${i.v ? "/" + i.v : ""}`; },
|
|
857
|
+
size: () => ({ width: Reveal.getConfig().width, height: Reveal.getConfig().height }),
|
|
858
|
+
scale: () => Reveal.getScale() || 1,
|
|
859
|
+
on: (ev, fn) => Reveal.on(ev === "slide" ? "slidechanged" : ev, fn),
|
|
860
|
+
prepare() { // no transitions, every build step shown; the measure and headless copies fill their frame
|
|
861
|
+
const base = { transition: "none", backgroundTransition: "none", fragments: false };
|
|
862
|
+
if (MODE === "main") Reveal.configure({ ...base });
|
|
863
|
+
else Reveal.configure({ ...base, margin: 0, keyboard: false });
|
|
864
|
+
},
|
|
865
|
+
viewport(width, height) { // lay the deck out for this size whatever the window's
|
|
866
|
+
Object.assign(Reveal.getRevealElement().style, { width: width + "px", height: height + "px" });
|
|
867
|
+
Reveal.layout();
|
|
868
|
+
},
|
|
869
|
+
ready: (fn) => (Reveal.isReady() ? fn() : Reveal.on("ready", fn)),
|
|
870
|
+
keys() {}, // reveal.js's own keyboard handling stays
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/* A deck made without reveal.js, whose slides <meta name="pindeck:slides"> names (and its size
|
|
875
|
+
<meta name="pindeck:size">). PinDeck shows the slides itself, one at a time and scaled to the frame,
|
|
876
|
+
over whatever the deck's own script does: the elements around the slides lose their transforms, a
|
|
877
|
+
backdrop covers the rest of the page, and the deck's script gets no keys while the editor has them.
|
|
878
|
+
The classes and styles added here live in the page only; the deck file is never written from the DOM. */
|
|
879
|
+
function selectorDeck() {
|
|
880
|
+
const root = document.documentElement;
|
|
881
|
+
const found = () => { const all = [...document.querySelectorAll(SLIDE_SEL)]; return all.filter((s) => !all.some((o) => o !== s && s.contains(o))); };
|
|
882
|
+
let list = [], cur = 0, scale = 1, sz = null, view = null;
|
|
883
|
+
const listeners = { slide: [], resize: [] };
|
|
884
|
+
const emit = (ev) => listeners[ev].forEach((fn) => fn());
|
|
885
|
+
function mark() { // the slides, and the elements between them and <body>
|
|
886
|
+
list = found();
|
|
887
|
+
for (const s of list) {
|
|
888
|
+
s.classList.add("__pindeck_slide");
|
|
889
|
+
for (let a = s.parentElement; a && a !== document.body && a !== root; a = a.parentElement) a.classList.add("__pindeck_anc");
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
function size() { // the first call also finds the slides
|
|
893
|
+
if (sz) return sz;
|
|
894
|
+
mark();
|
|
895
|
+
// the server refuses a deck without a readable size, so the fallback is only for a tag removed meanwhile
|
|
896
|
+
const m = /^(\d{2,5})\s*[x×*,\s]\s*(\d{2,5})$/i.exec(deckMeta("size") || "");
|
|
897
|
+
sz = m ? { width: +m[1], height: +m[2] } : { width: 1280, height: 720 };
|
|
898
|
+
return sz;
|
|
899
|
+
}
|
|
900
|
+
// what takes a slide and its containers out of the deck's own layout (scaling, stacking, page views)
|
|
901
|
+
const LOOSE = "transform:none !important; translate:none !important; scale:none !important; rotate:none !important; " +
|
|
902
|
+
"zoom:1 !important; filter:none !important; backdrop-filter:none !important; perspective:none !important; " +
|
|
903
|
+
"contain:none !important; will-change:auto !important; opacity:1 !important; isolation:auto !important; " +
|
|
904
|
+
"mix-blend-mode:normal !important; clip-path:none !important; transition:none !important;";
|
|
905
|
+
function addStyle(text) { const st = document.createElement("style"); st.textContent = text; document.head.appendChild(st); }
|
|
906
|
+
function fit() {
|
|
907
|
+
const { width, height } = size(), m = MODE === "main" ? 0.04 : 0; // the margin reveal.js leaves in the editor
|
|
908
|
+
const vw = view?.width ?? innerWidth, vh = view?.height ?? innerHeight;
|
|
909
|
+
scale = Math.min(vw * (1 - m) / width, vh * (1 - m) / height) || 1;
|
|
910
|
+
root.style.setProperty("--pindeck-s", String(scale));
|
|
911
|
+
root.style.setProperty("--pindeck-x", `${(vw - width * scale) / 2}px`);
|
|
912
|
+
root.style.setProperty("--pindeck-y", `${(vh - height * scale) / 2}px`);
|
|
913
|
+
}
|
|
914
|
+
// a slide the deck's own CSS hides with display: none is shown as a block
|
|
915
|
+
function display(s) { s.classList.remove("__pindeck_block"); if (getComputedStyle(s).display === "none") s.classList.add("__pindeck_block"); }
|
|
916
|
+
function show(i) {
|
|
917
|
+
const s = list[i];
|
|
918
|
+
if (!s) return false;
|
|
919
|
+
const changed = i !== cur;
|
|
920
|
+
list[cur]?.classList.remove("__pindeck_cur");
|
|
921
|
+
cur = i;
|
|
922
|
+
s.classList.add("__pindeck_cur");
|
|
923
|
+
s.removeAttribute("inert"); s.removeAttribute("hidden"); // the deck's script marks the slides it does not show
|
|
924
|
+
display(s);
|
|
925
|
+
return changed;
|
|
926
|
+
}
|
|
927
|
+
return {
|
|
928
|
+
slides: () => list,
|
|
929
|
+
current: () => list[cur],
|
|
930
|
+
goto(index) { if (show(index)) emit("slide"); },
|
|
931
|
+
hash(index) { const s = list[index]; return s?.id ? `#${encodeURIComponent(s.id)}` : `#${index + 1}`; },
|
|
932
|
+
size,
|
|
933
|
+
scale: () => scale,
|
|
934
|
+
on: (ev, fn) => listeners[ev]?.push(fn),
|
|
935
|
+
prepare() {
|
|
936
|
+
const { width, height } = size();
|
|
937
|
+
addStyle(`
|
|
938
|
+
html.__pindeck_deck, html.__pindeck_deck body { overflow:hidden !important; }
|
|
939
|
+
html.__pindeck_deck .__pindeck_anc { ${LOOSE} position:static !important; z-index:auto !important; }
|
|
940
|
+
html.__pindeck_deck .__pindeck_slide:not(.__pindeck_cur) { visibility:hidden !important; }
|
|
941
|
+
html.__pindeck_deck .__pindeck_slide.__pindeck_cur { ${LOOSE} position:fixed !important; inset:0 auto auto 0 !important;
|
|
942
|
+
margin:0 !important; width:${width}px !important; height:${height}px !important; min-width:0 !important; min-height:0 !important;
|
|
943
|
+
max-width:none !important; max-height:none !important; transform-origin:0 0 !important;
|
|
944
|
+
transform:translate(var(--pindeck-x, 0px), var(--pindeck-y, 0px)) scale(var(--pindeck-s, 1)) !important;
|
|
945
|
+
visibility:visible !important; z-index:2147483001 !important; }
|
|
946
|
+
html.__pindeck_deck .__pindeck_slide.__pindeck_block { display:block !important; }
|
|
947
|
+
html.__pindeck_deck .__pindeck_backdrop { position:fixed; inset:0; z-index:2147483000; background:var(--pindeck-bg, #fff); }
|
|
948
|
+
html.__pindeck_deck aside.notes { display:none !important; }`);
|
|
949
|
+
// the backdrop has the page's own background
|
|
950
|
+
const bg = [document.body, root].map((e) => getComputedStyle(e).backgroundColor).find((c) => !/^(transparent|rgba\(.*,\s*0\))$/.test(c));
|
|
951
|
+
if (bg) root.style.setProperty("--pindeck-bg", bg);
|
|
952
|
+
const backdrop = document.createElement("div");
|
|
953
|
+
backdrop.className = "__pindeck_backdrop";
|
|
954
|
+
document.body.appendChild(backdrop);
|
|
955
|
+
root.classList.add("__pindeck_deck");
|
|
956
|
+
fit();
|
|
957
|
+
show(cur);
|
|
958
|
+
addEventListener("resize", () => { fit(); emit("resize"); });
|
|
959
|
+
// the deck's script may hide the slide again (when it changes slides itself)
|
|
960
|
+
new MutationObserver((recs) => { if (recs.some((r) => r.target === list[cur])) { list[cur].removeAttribute("inert"); list[cur].removeAttribute("hidden"); display(list[cur]); } })
|
|
961
|
+
.observe(document.body, { subtree: true, attributes: true, attributeFilter: ["inert", "hidden", "style"] });
|
|
962
|
+
},
|
|
963
|
+
viewport(width, height) { view = { width, height }; fit(); }, // lay the slide out for this size whatever the window's
|
|
964
|
+
ready: (fn) => (document.readyState === "complete" ? fn() : addEventListener("load", () => fn(), { once: true })),
|
|
965
|
+
keys() { // after the editor's own key handler: slide keys change the slide here, and the deck's script gets none
|
|
966
|
+
const field = (t) => t.closest?.("input, textarea, select, [contenteditable]");
|
|
967
|
+
const STEP = { ArrowRight: 1, ArrowDown: 1, PageDown: 1, ArrowLeft: -1, ArrowUp: -1, PageUp: -1 };
|
|
968
|
+
addEventListener("keydown", (e) => {
|
|
969
|
+
if (field(e.target)) return;
|
|
970
|
+
if (!e.metaKey && !e.ctrlKey && !e.altKey) {
|
|
971
|
+
const k = e.key, step = k === " " ? (e.shiftKey ? -1 : 1) : STEP[k];
|
|
972
|
+
const to = step ? cur + step : k === "Home" ? 0 : k === "End" ? list.length - 1 : null;
|
|
973
|
+
if (to != null) { e.preventDefault(); this.goto(Math.max(0, Math.min(list.length - 1, to))); }
|
|
974
|
+
}
|
|
975
|
+
e.stopImmediatePropagation();
|
|
976
|
+
}, true);
|
|
977
|
+
for (const t of ["keyup", "keypress"]) addEventListener(t, (e) => { if (!field(e.target)) e.stopImmediatePropagation(); }, true);
|
|
978
|
+
},
|
|
979
|
+
/* ?editor=pdf: every slide as a page of its own size, for PinDeck's PDF export (lib/pdf.mjs prints
|
|
980
|
+
this page's DOM as it is once laid out, so the layout is all CSS) */
|
|
981
|
+
print() {
|
|
982
|
+
const { width, height } = size();
|
|
983
|
+
const all = params.get("showHiddenSlides") === "true";
|
|
984
|
+
const shown = list.filter((s) => all || s.getAttribute("data-visibility") !== "hidden");
|
|
985
|
+
for (const s of list) { s.classList.add("pdf-page"); if (!shown.includes(s)) s.classList.add("__pindeck_skip"); }
|
|
986
|
+
shown.at(-1)?.classList.add("__pindeck_last");
|
|
987
|
+
const OUT = ":not(.__pindeck_anc):not(.__pindeck_slide)";
|
|
988
|
+
addStyle(`
|
|
989
|
+
@page { size:${width}px ${height}px; margin:0; }
|
|
990
|
+
html.__pindeck_print, html.__pindeck_print body { margin:0 !important; padding:0 !important; width:auto !important;
|
|
991
|
+
height:auto !important; overflow:visible !important; }
|
|
992
|
+
html.__pindeck_print .__pindeck_anc { ${LOOSE} position:static !important; display:block !important; width:auto !important;
|
|
993
|
+
height:auto !important; margin:0 !important; padding:0 !important; border:0 !important; overflow:visible !important; }
|
|
994
|
+
html.__pindeck_print body > ${OUT}:not(svg), html.__pindeck_print .__pindeck_anc > ${OUT}:not(svg) { display:none !important; }
|
|
995
|
+
html.__pindeck_print body > svg${OUT}, html.__pindeck_print .__pindeck_anc > svg${OUT} { position:absolute !important;
|
|
996
|
+
width:0 !important; height:0 !important; overflow:hidden !important; }
|
|
997
|
+
html.__pindeck_print .__pindeck_slide { ${LOOSE} position:relative !important; inset:auto !important; margin:0 !important;
|
|
998
|
+
width:${width}px !important; height:${height}px !important; max-width:none !important; max-height:none !important;
|
|
999
|
+
visibility:visible !important; overflow:hidden !important; break-inside:avoid; break-after:page; page-break-after:always; }
|
|
1000
|
+
html.__pindeck_print .__pindeck_slide.__pindeck_last { break-after:auto; page-break-after:auto; }
|
|
1001
|
+
html.__pindeck_print .__pindeck_slide.__pindeck_block { display:block !important; }
|
|
1002
|
+
html.__pindeck_print .__pindeck_skip { display:none !important; }
|
|
1003
|
+
html.__pindeck_print aside.notes { display:none !important; }`);
|
|
1004
|
+
root.classList.add("__pindeck_print");
|
|
1005
|
+
shown.forEach(display);
|
|
1006
|
+
},
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
|
|
846
1010
|
/* ---- Start ------------------------------------------------------------------------------------- */
|
|
847
1011
|
async function start() {
|
|
848
|
-
W
|
|
849
|
-
const
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
if (MODE === "main") Reveal.configure({ ...base });
|
|
853
|
-
else Reveal.configure({ ...base, margin: 0, keyboard: false });
|
|
1012
|
+
({ width: W, height: H } = deck.size());
|
|
1013
|
+
const setUnit = () => document.documentElement.style.setProperty("--pindeck-u", String(1 / (deck.scale() || 1)));
|
|
1014
|
+
deck.on("resize", setUnit);
|
|
1015
|
+
deck.prepare();
|
|
854
1016
|
setUnit();
|
|
855
1017
|
await document.fonts.ready;
|
|
856
1018
|
|
|
857
1019
|
if (MODE === "shot" || MODE === "dump") {
|
|
1020
|
+
// the size of headless Chrome's window, which the page itself gets only once the screenshot is
|
|
1021
|
+
// taken: lay the deck out for it now rather than letterboxed into the shorter viewport
|
|
1022
|
+
const vw = +params.get("width"), vh = +params.get("height");
|
|
1023
|
+
if (vw > 0 && vh > 0) { deck.viewport(vw, vh); setUnit(); }
|
|
858
1024
|
const sec = byEid(params.get("slide") || "") || slides()[0];
|
|
859
1025
|
goto(indexOf(sec));
|
|
860
1026
|
await nextFrame();
|
|
@@ -872,11 +1038,11 @@
|
|
|
872
1038
|
}
|
|
873
1039
|
|
|
874
1040
|
const api = {
|
|
875
|
-
info, goto,
|
|
1041
|
+
info, goto, presentHash, highlight, reloadCss,
|
|
876
1042
|
setMode, selectEid, nudge, editSelection,
|
|
877
1043
|
// a new slide: select its title and open it for typing
|
|
878
1044
|
editTitle() {
|
|
879
|
-
const sec =
|
|
1045
|
+
const sec = deck.current();
|
|
880
1046
|
const h = sec?.querySelector("h1[data-eid], h2[data-eid], h3[data-eid]");
|
|
881
1047
|
if (!h || mode !== "select") return;
|
|
882
1048
|
select(h, sec);
|
|
@@ -942,22 +1108,35 @@
|
|
|
942
1108
|
host()?.key(k, { meta, shift: e.shiftKey });
|
|
943
1109
|
}
|
|
944
1110
|
}, true);
|
|
945
|
-
|
|
946
|
-
|
|
1111
|
+
deck.on("slide", () => {
|
|
1112
|
+
if (editing) finishEdit(true); hideHover(); if (sel) select(null, null);
|
|
1113
|
+
// a deck made without reveal.js lays out only the current slide at the slide size: pins on the
|
|
1114
|
+
// others were placed in another layout
|
|
1115
|
+
if (SLIDE_SEL) renderPins(pins);
|
|
1116
|
+
host()?.slideChanged(indexOf(deck.current()));
|
|
1117
|
+
});
|
|
1118
|
+
deck.on("resize", () => { if (sel) drawSel(); });
|
|
947
1119
|
setMode("select");
|
|
948
1120
|
// a banner on hidden slides, so nobody edits one without noticing (only in the editor view)
|
|
949
1121
|
for (const sec of slides()) {
|
|
950
1122
|
if (sec.getAttribute("data-visibility") !== "hidden") continue;
|
|
951
1123
|
const b = document.createElement("div");
|
|
952
1124
|
b.className = "__hidden-banner";
|
|
953
|
-
b.textContent = "Hidden slide · skipped when presenting and in PDFs";
|
|
1125
|
+
b.textContent = SLIDE_SEL ? "Hidden slide · skipped in PDFs" : "Hidden slide · skipped when presenting and in PDFs";
|
|
954
1126
|
sec.appendChild(b);
|
|
955
1127
|
}
|
|
956
1128
|
const startAt = +params.get("start");
|
|
957
1129
|
if (startAt) goto(startAt);
|
|
1130
|
+
deck.keys();
|
|
958
1131
|
}
|
|
959
1132
|
host()?.bridgeReady(MODE, api, window);
|
|
960
1133
|
}
|
|
961
|
-
if (
|
|
962
|
-
|
|
1134
|
+
if (SLIDE_SEL) {
|
|
1135
|
+
deck = selectorDeck();
|
|
1136
|
+
deck.ready(() => (MODE === "pdf" ? deck.print() : start()));
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
if (typeof Reveal === "undefined" || MODE === "pdf") return; // reveal.js lays out its own print view
|
|
1140
|
+
deck = revealDeck();
|
|
1141
|
+
deck.ready(start);
|
|
963
1142
|
})();
|
package/public/editor.css
CHANGED
|
@@ -65,6 +65,7 @@ code { font: 12.5px ui-monospace, SFMono-Regular, Menlo, monospace; background:
|
|
|
65
65
|
}
|
|
66
66
|
.pos { font-variant-numeric: tabular-nums; color: var(--muted); min-width: 52px; text-align: center; }
|
|
67
67
|
.deck-name { min-width: 0; margin-left: 8px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
68
|
+
.server-note { flex: none; margin-left: 8px; padding: 1px 6px; border-radius: 4px; font-size: 12px; color: #6b4a12; background: #fbf0dc; cursor: help; }
|
|
68
69
|
.toggle { display: inline-flex; align-items: center; gap: 6px; }
|
|
69
70
|
.toggle .dot { display: inline-block; width: 18px; height: 18px; border-radius: 50%; background: var(--orange); color: #fff;
|
|
70
71
|
font-size: 11px; font-weight: 700; line-height: 18px; text-align: center; }
|