@dustfeather/deckrun 2.0.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/LICENSE +21 -0
- package/README.md +1073 -0
- package/THIRD-PARTY-NOTICES.md +38 -0
- package/dist/editor-content.js +485 -0
- package/dist/editor.js +3916 -0
- package/dist/fragments.js +71 -0
- package/dist/generate.js +3488 -0
- package/dist/highlights.js +833 -0
- package/dist/index.js +1020 -0
- package/dist/lint.js +330 -0
- package/dist/parser.js +221 -0
- package/dist/pdf.js +200 -0
- package/dist/presentation-options.js +289 -0
- package/dist/preview.js +400 -0
- package/dist/rich-content.js +195 -0
- package/dist/safe-fetch.js +173 -0
- package/dist/sanitize.js +102 -0
- package/dist/themes.js +1041 -0
- package/dist/titles.js +30 -0
- package/package.json +64 -0
package/dist/preview.js
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import { RESET_CSS, SLIDE_CSS, DECOR_CSS } from "./generate.js";
|
|
2
|
+
import { findFont, FONT_IDS, fontOverrideCss, DEFAULT_THEME, decorMapJson, decorOf, googleFontsHref, hljsHref, hljsIntegrity, hljsMapJson, HLJS_SCRIPT, themeSwitchableCss, } from "./themes.js";
|
|
3
|
+
import { DEFAULT_TEMPLATE, DEFAULT_TRANSITION, resolveTemplateName, resolveTransitionName, TEMPLATE_CSS, TRANSITION_CSS, } from "./presentation-options.js";
|
|
4
|
+
import { RICH_CONTENT_CSS, RICH_CONTENT_RUNTIME, richContentHead, } from "./rich-content.js";
|
|
5
|
+
import { FRAGMENT_CSS, FRAGMENT_RUNTIME } from "./fragments.js";
|
|
6
|
+
/** Virtual viewport the preview renders at, so `vw` sizing matches a projector. */
|
|
7
|
+
export const PREVIEW_WIDTH = 1600;
|
|
8
|
+
export const PREVIEW_HEIGHT = 900;
|
|
9
|
+
/**
|
|
10
|
+
* The document loaded into the editor's preview iframe. It carries the deck's
|
|
11
|
+
* own stylesheet, so what the editor shows is what `deckrun file.md` renders.
|
|
12
|
+
* Slides arrive over postMessage; nothing is fetched or parsed in here.
|
|
13
|
+
*/
|
|
14
|
+
export function generatePreviewHtml(initialTheme = DEFAULT_THEME, fonts = {}, initialTemplate = DEFAULT_TEMPLATE, initialTransition = DEFAULT_TRANSITION) {
|
|
15
|
+
const template = resolveTemplateName(initialTemplate);
|
|
16
|
+
const transition = resolveTransitionName(initialTransition);
|
|
17
|
+
const head = findFont(fonts.head);
|
|
18
|
+
const body = findFont(fonts.body);
|
|
19
|
+
const fontAttrs = (head ? ` data-head="${head}"` : "") + (body ? ` data-body="${body}"` : "");
|
|
20
|
+
return `<!DOCTYPE html>
|
|
21
|
+
<html lang="en" data-theme="${initialTheme}" data-decor="${decorOf(initialTheme)}" data-template="${template}" data-transition="${transition}"${fontAttrs}>
|
|
22
|
+
<head>
|
|
23
|
+
<meta charset="UTF-8">
|
|
24
|
+
<title>preview · deckrun</title>
|
|
25
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
26
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
27
|
+
<link href="${googleFontsHref()}" rel="stylesheet">
|
|
28
|
+
<link rel="stylesheet" id="hljs-theme" href="${hljsHref(initialTheme)}" integrity="${hljsIntegrity(initialTheme)}" crossorigin="anonymous">
|
|
29
|
+
<script src="${HLJS_SCRIPT.href}" integrity="${HLJS_SCRIPT.integrity}" crossorigin="anonymous"></script>
|
|
30
|
+
${richContentHead({ math: true, mermaid: true }, "local")}
|
|
31
|
+
<style>
|
|
32
|
+
${RESET_CSS}
|
|
33
|
+
|
|
34
|
+
${themeSwitchableCss()}
|
|
35
|
+
|
|
36
|
+
${fontOverrideCss()}
|
|
37
|
+
|
|
38
|
+
${SLIDE_CSS}
|
|
39
|
+
|
|
40
|
+
${TEMPLATE_CSS}
|
|
41
|
+
|
|
42
|
+
${TRANSITION_CSS}
|
|
43
|
+
|
|
44
|
+
${FRAGMENT_CSS}
|
|
45
|
+
|
|
46
|
+
${RICH_CONTENT_CSS}
|
|
47
|
+
|
|
48
|
+
${DECOR_CSS}
|
|
49
|
+
|
|
50
|
+
/* ── Preview overrides ────────────────────────────────────────────────── */
|
|
51
|
+
html, body { overflow: hidden; }
|
|
52
|
+
body.is-grid, body.is-grid #presentation { overflow-y: auto; height: auto; min-height: 100%; }
|
|
53
|
+
|
|
54
|
+
.slide {
|
|
55
|
+
transition: none !important;
|
|
56
|
+
transform: none !important;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* The deck staggers each block in as a slide opens. Here the slide is rebuilt
|
|
60
|
+
on every keystroke, so the same animation would flicker while typing. */
|
|
61
|
+
.slide.is-active .slide__content > * { animation: none !important; }
|
|
62
|
+
|
|
63
|
+
#presentation { background: transparent; }
|
|
64
|
+
|
|
65
|
+
/* Grid of every slide, laid out at the same virtual width as a single slide. */
|
|
66
|
+
body.is-grid #presentation {
|
|
67
|
+
position: static;
|
|
68
|
+
display: grid;
|
|
69
|
+
grid-template-columns: repeat(auto-fill, minmax(440px, 1fr));
|
|
70
|
+
gap: 34px;
|
|
71
|
+
padding: 34px;
|
|
72
|
+
width: 100%;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.pv-thumb {
|
|
76
|
+
position: relative;
|
|
77
|
+
aspect-ratio: 16 / 9;
|
|
78
|
+
border: 1px solid var(--surface0);
|
|
79
|
+
border-radius: 12px;
|
|
80
|
+
overflow: hidden;
|
|
81
|
+
background: var(--base);
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
transition: border-color 0.15s ease, transform 0.15s ease;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.pv-thumb:hover { border-color: var(--accent); transform: translateY(-3px); box-shadow: var(--shadow-md); }
|
|
87
|
+
.pv-thumb.is-current { border-color: var(--accent-2); box-shadow: 0 0 0 1px var(--accent-2); }
|
|
88
|
+
|
|
89
|
+
.pv-thumb__inner {
|
|
90
|
+
position: absolute;
|
|
91
|
+
top: 0;
|
|
92
|
+
left: 0;
|
|
93
|
+
width: ${PREVIEW_WIDTH}px;
|
|
94
|
+
height: ${PREVIEW_HEIGHT}px;
|
|
95
|
+
transform-origin: top left;
|
|
96
|
+
pointer-events: none;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.pv-thumb__num {
|
|
100
|
+
position: absolute;
|
|
101
|
+
bottom: 8px;
|
|
102
|
+
right: 12px;
|
|
103
|
+
z-index: 2;
|
|
104
|
+
font-family: var(--font-mono);
|
|
105
|
+
font-size: 15px;
|
|
106
|
+
color: var(--overlay1);
|
|
107
|
+
background: var(--crust-overlay);
|
|
108
|
+
border-radius: 5px;
|
|
109
|
+
padding: 2px 8px;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* Empty state */
|
|
113
|
+
#pv-empty {
|
|
114
|
+
position: absolute;
|
|
115
|
+
inset: 0;
|
|
116
|
+
display: none;
|
|
117
|
+
align-items: center;
|
|
118
|
+
justify-content: center;
|
|
119
|
+
font-family: var(--font-mono);
|
|
120
|
+
font-size: 26px;
|
|
121
|
+
color: var(--overlay0);
|
|
122
|
+
letter-spacing: 0.04em;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
body.is-empty #pv-empty { display: flex; }
|
|
126
|
+
</style>
|
|
127
|
+
</head>
|
|
128
|
+
<body>
|
|
129
|
+
<div id="backdrop" aria-hidden="true"></div>
|
|
130
|
+
<div id="presentation"></div>
|
|
131
|
+
<div id="pv-empty">nothing to preview yet</div>
|
|
132
|
+
<script>
|
|
133
|
+
${FRAGMENT_RUNTIME}
|
|
134
|
+
|
|
135
|
+
${RICH_CONTENT_RUNTIME}
|
|
136
|
+
</script>
|
|
137
|
+
<script>
|
|
138
|
+
(function () {
|
|
139
|
+
'use strict';
|
|
140
|
+
|
|
141
|
+
var VW = ${PREVIEW_WIDTH};
|
|
142
|
+
var HLJS = ${hljsMapJson()};
|
|
143
|
+
var DECOR = ${decorMapJson()};
|
|
144
|
+
var FONTS = ${JSON.stringify(FONT_IDS)};
|
|
145
|
+
|
|
146
|
+
var stage = document.getElementById('presentation');
|
|
147
|
+
var slides = [];
|
|
148
|
+
var mode = 'single';
|
|
149
|
+
var index = 0;
|
|
150
|
+
|
|
151
|
+
function send(msg) {
|
|
152
|
+
// Targeted rather than '*': with a wildcard, a document that navigates
|
|
153
|
+
// this frame off-origin keeps receiving every later payload, which
|
|
154
|
+
// includes the whole deck.
|
|
155
|
+
if (window.parent !== window) window.parent.postMessage(msg, location.origin);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function applyFont(slot, id) {
|
|
159
|
+
if (FONTS.indexOf(id) !== -1) document.documentElement.dataset[slot] = id;
|
|
160
|
+
else delete document.documentElement.dataset[slot];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function highlight(root) {
|
|
164
|
+
if (!window.hljs) return;
|
|
165
|
+
var blocks = root.querySelectorAll('pre code:not(.language-mermaid):not(.lang-mermaid)');
|
|
166
|
+
for (var i = 0; i < blocks.length; i++) {
|
|
167
|
+
if (!blocks[i].dataset.highlighted) {
|
|
168
|
+
try { window.hljs.highlightElement(blocks[i]); } catch (e) {}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Report whether the visible slide clips its own content. */
|
|
174
|
+
function reportOverflow() {
|
|
175
|
+
if (mode !== 'single') return;
|
|
176
|
+
var content = stage.querySelector('.slide__content');
|
|
177
|
+
var over = false;
|
|
178
|
+
if (content) {
|
|
179
|
+
over = content.scrollHeight - content.clientHeight > 6 ||
|
|
180
|
+
content.scrollWidth - content.clientWidth > 6;
|
|
181
|
+
|
|
182
|
+
// KaTeX display equations and Mermaid hosts deliberately hide their own
|
|
183
|
+
// overflow to keep a projected slide tidy. Inspect them separately so a
|
|
184
|
+
// clipped formula or diagram still triggers the editor's overflow nudge.
|
|
185
|
+
var rich = content.querySelectorAll('.katex-display, .mermaid');
|
|
186
|
+
for (var i = 0; !over && i < rich.length; i++) {
|
|
187
|
+
over = rich[i].scrollHeight - rich[i].clientHeight > 6 ||
|
|
188
|
+
rich[i].scrollWidth - rich[i].clientWidth > 6;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
send({ type: 'overflow', index: index, overflow: over });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function renderSingle() {
|
|
195
|
+
document.body.classList.remove('is-grid');
|
|
196
|
+
stage.innerHTML = slides[index] || '';
|
|
197
|
+
var el = stage.querySelector('.slide');
|
|
198
|
+
if (el) el.classList.add('is-active');
|
|
199
|
+
if (window.deckrunPrepareFragments) window.deckrunPrepareFragments(stage, true);
|
|
200
|
+
highlight(stage);
|
|
201
|
+
var rich = window.deckrunRenderRichContent ? window.deckrunRenderRichContent(stage) : Promise.resolve();
|
|
202
|
+
rich.then(function () { requestAnimationFrame(reportOverflow); });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function renderGrid() {
|
|
206
|
+
document.body.classList.add('is-grid');
|
|
207
|
+
stage.innerHTML = '';
|
|
208
|
+
var frag = document.createDocumentFragment();
|
|
209
|
+
slides.forEach(function (html, i) {
|
|
210
|
+
var thumb = document.createElement('div');
|
|
211
|
+
thumb.className = 'pv-thumb' + (i === index ? ' is-current' : '');
|
|
212
|
+
thumb.dataset.index = String(i);
|
|
213
|
+
|
|
214
|
+
var inner = document.createElement('div');
|
|
215
|
+
inner.className = 'pv-thumb__inner';
|
|
216
|
+
inner.innerHTML = html;
|
|
217
|
+
var el = inner.querySelector('.slide');
|
|
218
|
+
if (el) el.classList.add('is-active');
|
|
219
|
+
|
|
220
|
+
var num = document.createElement('span');
|
|
221
|
+
num.className = 'pv-thumb__num';
|
|
222
|
+
num.textContent = String(i + 1);
|
|
223
|
+
|
|
224
|
+
thumb.appendChild(inner);
|
|
225
|
+
thumb.appendChild(num);
|
|
226
|
+
frag.appendChild(thumb);
|
|
227
|
+
});
|
|
228
|
+
stage.appendChild(frag);
|
|
229
|
+
scaleThumbs();
|
|
230
|
+
if (window.deckrunPrepareFragments) window.deckrunPrepareFragments(stage, true);
|
|
231
|
+
highlight(stage);
|
|
232
|
+
var rich = window.deckrunRenderRichContent ? window.deckrunRenderRichContent(stage) : Promise.resolve();
|
|
233
|
+
rich.then(scaleThumbs);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function scaleThumbs() {
|
|
237
|
+
var thumbs = stage.querySelectorAll('.pv-thumb');
|
|
238
|
+
for (var i = 0; i < thumbs.length; i++) {
|
|
239
|
+
var inner = thumbs[i].querySelector('.pv-thumb__inner');
|
|
240
|
+
if (inner) inner.style.transform = 'scale(' + (thumbs[i].clientWidth / VW) + ')';
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function render() {
|
|
245
|
+
document.body.classList.toggle('is-empty', slides.length === 0);
|
|
246
|
+
if (slides.length === 0) { stage.innerHTML = ''; return; }
|
|
247
|
+
if (index >= slides.length) index = slides.length - 1;
|
|
248
|
+
if (index < 0) index = 0;
|
|
249
|
+
if (mode === 'grid') renderGrid(); else renderSingle();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
stage.addEventListener('click', function (e) {
|
|
253
|
+
if (mode !== 'grid') return;
|
|
254
|
+
var thumb = e.target.closest ? e.target.closest('.pv-thumb') : null;
|
|
255
|
+
if (thumb) send({ type: 'goto', index: parseInt(thumb.dataset.index, 10) });
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
window.addEventListener('keydown', function (e) {
|
|
259
|
+
var mod = e.metaKey || e.ctrlKey;
|
|
260
|
+
if (mod) {
|
|
261
|
+
var k = e.key.toLowerCase();
|
|
262
|
+
if (k === 'enter') { e.preventDefault(); send({ type: 'action', action: 'present' }); }
|
|
263
|
+
else if (k === 'k' && !e.shiftKey) { e.preventDefault(); send({ type: 'action', action: 'palette' }); }
|
|
264
|
+
else if (k === 'g') { e.preventDefault(); send({ type: 'action', action: 'grid' }); }
|
|
265
|
+
else if (k === 'o') { e.preventDefault(); send({ type: 'action', action: 'decks' }); }
|
|
266
|
+
else if (k === '/') { e.preventDefault(); send({ type: 'action', action: 'guide' }); }
|
|
267
|
+
else if (k === 'p' || (k === 's' && e.shiftKey)) { e.preventDefault(); send({ type: 'action', action: 'pdf' }); }
|
|
268
|
+
else if (k === 's') { e.preventDefault(); send({ type: 'action', action: 'download' }); }
|
|
269
|
+
else if (k === 'l' && e.shiftKey) { e.preventDefault(); send({ type: 'action', action: 'theme' }); }
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (mode === 'grid') {
|
|
274
|
+
var thumbs = stage.querySelectorAll('.pv-thumb');
|
|
275
|
+
if (!thumbs.length) return;
|
|
276
|
+
var cols = 1;
|
|
277
|
+
if (thumbs.length > 1) {
|
|
278
|
+
var firstTop = thumbs[0].offsetTop;
|
|
279
|
+
for (var c = 1; c < thumbs.length; c++) {
|
|
280
|
+
if (thumbs[c].offsetTop !== firstTop) { cols = c; break; }
|
|
281
|
+
}
|
|
282
|
+
if (cols === 1 && thumbs.length > 1 && thumbs[1].offsetTop === firstTop) {
|
|
283
|
+
cols = thumbs.length;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
var step = 0;
|
|
287
|
+
if (e.key === 'ArrowRight') step = 1;
|
|
288
|
+
else if (e.key === 'ArrowLeft') step = -1;
|
|
289
|
+
else if (e.key === 'ArrowDown') step = cols;
|
|
290
|
+
else if (e.key === 'ArrowUp') step = -cols;
|
|
291
|
+
else if (e.key === 'Home') { e.preventDefault(); send({ type: 'goto', index: 0 }); return; }
|
|
292
|
+
else if (e.key === 'End') { e.preventDefault(); send({ type: 'goto', index: slides.length - 1 }); return; }
|
|
293
|
+
else if (e.key === 'Enter') { e.preventDefault(); send({ type: 'goto', index: index }); return; }
|
|
294
|
+
else if (e.key === 'Escape') { e.preventDefault(); send({ type: 'goto', index: index }); return; }
|
|
295
|
+
else return;
|
|
296
|
+
|
|
297
|
+
e.preventDefault();
|
|
298
|
+
var next = Math.max(0, Math.min(slides.length - 1, index + step));
|
|
299
|
+
index = next;
|
|
300
|
+
var cur = stage.querySelector('.pv-thumb.is-current');
|
|
301
|
+
if (cur) cur.classList.remove('is-current');
|
|
302
|
+
var nextThumb = stage.querySelector('.pv-thumb[data-index="' + index + '"]');
|
|
303
|
+
if (nextThumb) {
|
|
304
|
+
nextThumb.classList.add('is-current');
|
|
305
|
+
nextThumb.scrollIntoView({ block: 'nearest' });
|
|
306
|
+
}
|
|
307
|
+
send({ type: 'index-select', index: index });
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Single mode: Alt navigation or standard keys
|
|
312
|
+
if (e.altKey) {
|
|
313
|
+
if (e.key === 'ArrowDown' || e.key === 'ArrowRight' || e.key === 'PageDown') {
|
|
314
|
+
e.preventDefault();
|
|
315
|
+
send({ type: 'nav', delta: 1 });
|
|
316
|
+
} else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft' || e.key === 'PageUp') {
|
|
317
|
+
e.preventDefault();
|
|
318
|
+
send({ type: 'nav', delta: -1 });
|
|
319
|
+
}
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (e.key === 'ArrowRight' || e.key === 'ArrowDown' || e.key === ' ' || e.key === 'PageDown') {
|
|
324
|
+
e.preventDefault();
|
|
325
|
+
send({ type: 'nav', delta: 1 });
|
|
326
|
+
} else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp' || e.key === 'PageUp') {
|
|
327
|
+
e.preventDefault();
|
|
328
|
+
send({ type: 'nav', delta: -1 });
|
|
329
|
+
} else if (e.key === 'Home') {
|
|
330
|
+
e.preventDefault();
|
|
331
|
+
send({ type: 'goto', index: 0 });
|
|
332
|
+
} else if (e.key === 'End') {
|
|
333
|
+
e.preventDefault();
|
|
334
|
+
send({ type: 'goto', index: slides.length - 1 });
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
window.addEventListener('resize', function () {
|
|
339
|
+
if (mode === 'grid') scaleThumbs();
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
window.addEventListener('message', function (e) {
|
|
343
|
+
// Only the editor that served this frame may drive it. Without this, any
|
|
344
|
+
// page could frame /__preview and post its own render payload, which
|
|
345
|
+
// goes straight to innerHTML.
|
|
346
|
+
if (e.origin !== location.origin) return;
|
|
347
|
+
var m = e.data || {};
|
|
348
|
+
if (m.type === 'render') {
|
|
349
|
+
var sameSet = m.slides && slides.length === m.slides.length &&
|
|
350
|
+
m.slides.every(function (h, i) { return h === slides[i]; });
|
|
351
|
+
slides = m.slides || [];
|
|
352
|
+
var modeChanged = m.mode !== mode;
|
|
353
|
+
var indexChanged = m.index !== index;
|
|
354
|
+
mode = m.mode || 'single';
|
|
355
|
+
index = typeof m.index === 'number' ? m.index : 0;
|
|
356
|
+
if (sameSet && !modeChanged && !indexChanged) { reportOverflow(); return; }
|
|
357
|
+
render();
|
|
358
|
+
} else if (m.type === 'theme') {
|
|
359
|
+
if (HLJS[m.theme]) {
|
|
360
|
+
document.documentElement.dataset.theme = m.theme;
|
|
361
|
+
document.documentElement.dataset.decor = DECOR[m.theme];
|
|
362
|
+
var link = document.getElementById('hljs-theme');
|
|
363
|
+
if (link) {
|
|
364
|
+
// Digest first, so the swapped-in sheet is verified as well.
|
|
365
|
+
link.integrity = HLJS[m.theme].integrity || '';
|
|
366
|
+
link.crossOrigin = 'anonymous';
|
|
367
|
+
link.href = HLJS[m.theme].href;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
// An empty string clears the override and hands the slot back to the
|
|
371
|
+
// theme, which delete does and an assignment of '' would not.
|
|
372
|
+
applyFont('head', m.head);
|
|
373
|
+
applyFont('body', m.body);
|
|
374
|
+
if (m.template) document.documentElement.dataset.template = m.template;
|
|
375
|
+
if (m.transition) document.documentElement.dataset.transition = m.transition;
|
|
376
|
+
// A different face changes how tall a slide's content runs, so the
|
|
377
|
+
// editor's overflow warning has to be re-measured against it.
|
|
378
|
+
render();
|
|
379
|
+
} else if (m.type === 'index') {
|
|
380
|
+
index = m.index;
|
|
381
|
+
if (mode === 'grid') {
|
|
382
|
+
var cur = stage.querySelector('.pv-thumb.is-current');
|
|
383
|
+
if (cur) cur.classList.remove('is-current');
|
|
384
|
+
var next = stage.querySelector('.pv-thumb[data-index="' + index + '"]');
|
|
385
|
+
if (next) {
|
|
386
|
+
next.classList.add('is-current');
|
|
387
|
+
next.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
|
388
|
+
}
|
|
389
|
+
} else {
|
|
390
|
+
render();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
send({ type: 'ready' });
|
|
396
|
+
})();
|
|
397
|
+
</script>
|
|
398
|
+
</body>
|
|
399
|
+
</html>`;
|
|
400
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
export function richContentFeatures(slides) {
|
|
2
|
+
let math = false;
|
|
3
|
+
let mermaid = false;
|
|
4
|
+
for (const slide of slides) {
|
|
5
|
+
if (!math && (slide.html.includes('class="math-source"') || slide.html.includes("math-source"))) {
|
|
6
|
+
math = true;
|
|
7
|
+
}
|
|
8
|
+
if (!mermaid &&
|
|
9
|
+
(slide.html.includes("language-mermaid") ||
|
|
10
|
+
slide.html.includes("lang-mermaid") ||
|
|
11
|
+
slide.html.includes('class="mermaid"'))) {
|
|
12
|
+
mermaid = true;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return { math, mermaid };
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The head tags for math and diagram rendering.
|
|
19
|
+
*
|
|
20
|
+
* The CDN variants carry Subresource Integrity digests. A standalone export
|
|
21
|
+
* runs third-party JavaScript on whatever machine it is opened on, including
|
|
22
|
+
* the machines of everyone the deck is sent to, so the bytes are pinned.
|
|
23
|
+
*/
|
|
24
|
+
export function richContentHead(features, source = "local") {
|
|
25
|
+
const parts = [];
|
|
26
|
+
if (features.math) {
|
|
27
|
+
if (source === "cdn") {
|
|
28
|
+
parts.push('<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.18.4/dist/katex.min.css"' +
|
|
29
|
+
' integrity="sha384-u1zONI5gPXUx0UKI62c75/zww972y0v2rSK5ZYlVdS6xEuWDeZWUI66v6t1gvlXJ"' +
|
|
30
|
+
' crossorigin="anonymous">');
|
|
31
|
+
parts.push('<script src="https://cdn.jsdelivr.net/npm/katex@0.18.4/dist/katex.min.js"' +
|
|
32
|
+
' integrity="sha384-ykMNcWQhhTUb0YV9SPpPUFURHZ+tWmubkakGBP+OgNK/UXdO2gtzglWx0Rj9hnO3"' +
|
|
33
|
+
' crossorigin="anonymous"></script>');
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
parts.push('<link rel="stylesheet" href="/__vendor/katex.min.css">');
|
|
37
|
+
parts.push('<script src="/__vendor/katex.min.js"></script>');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (features.mermaid) {
|
|
41
|
+
if (source === "cdn") {
|
|
42
|
+
parts.push('<script src="https://cdn.jsdelivr.net/npm/mermaid@11.17.2/dist/mermaid.min.js"' +
|
|
43
|
+
' integrity="sha384-EOXBFmc3gx5mb+vn0vPvvGqACToJD24hhacX5Yx+8NUUQrHIle/Qi5Bg9o3zKwW2"' +
|
|
44
|
+
' crossorigin="anonymous"></script>');
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
parts.push('<script src="/__vendor/mermaid.min.js"></script>');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return parts.join("\n ");
|
|
51
|
+
}
|
|
52
|
+
export const RICH_CONTENT_CSS = `/* ── Rich Content (Math & Diagrams) ─────────────────────────────────────── */
|
|
53
|
+
|
|
54
|
+
.math-source {
|
|
55
|
+
font-family: inherit;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
div.math-source {
|
|
59
|
+
display: flex;
|
|
60
|
+
justify-content: center;
|
|
61
|
+
margin: 1.2em 0;
|
|
62
|
+
overflow-x: auto;
|
|
63
|
+
overflow-y: hidden;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
span.math-source {
|
|
67
|
+
display: inline;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.katex-display {
|
|
71
|
+
margin: 0.8em 0;
|
|
72
|
+
overflow-x: auto;
|
|
73
|
+
overflow-y: hidden;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.katex {
|
|
77
|
+
font-size: 1.15em;
|
|
78
|
+
text-rendering: auto;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.math-error {
|
|
82
|
+
color: var(--maroon, #f38ba8);
|
|
83
|
+
background: var(--surface0, rgba(255, 0, 0, 0.1));
|
|
84
|
+
padding: 2px 6px;
|
|
85
|
+
border-radius: 4px;
|
|
86
|
+
font-family: var(--font-mono, monospace);
|
|
87
|
+
font-size: 0.85em;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.mermaid {
|
|
91
|
+
display: flex;
|
|
92
|
+
justify-content: center;
|
|
93
|
+
align-items: center;
|
|
94
|
+
margin: 1.2em auto;
|
|
95
|
+
max-width: 100%;
|
|
96
|
+
overflow: hidden;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.mermaid svg {
|
|
100
|
+
max-width: 100%;
|
|
101
|
+
height: auto;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.mermaid-error {
|
|
105
|
+
color: var(--maroon, #f38ba8);
|
|
106
|
+
background: var(--surface0, rgba(255, 0, 0, 0.1));
|
|
107
|
+
border: 1px solid var(--maroon, #f38ba8);
|
|
108
|
+
border-radius: 6px;
|
|
109
|
+
padding: 12px 16px;
|
|
110
|
+
font-family: var(--font-mono, monospace);
|
|
111
|
+
font-size: 0.9em;
|
|
112
|
+
white-space: pre-wrap;
|
|
113
|
+
margin: 1em 0;
|
|
114
|
+
}
|
|
115
|
+
`;
|
|
116
|
+
export const RICH_CONTENT_RUNTIME = `(function () {
|
|
117
|
+
window.deckrunRenderRichContent = function (root) {
|
|
118
|
+
if (!root) return Promise.resolve();
|
|
119
|
+
|
|
120
|
+
// 1. Render KaTeX math
|
|
121
|
+
if (window.katex) {
|
|
122
|
+
var mathNodes = root.querySelectorAll('.math-source:not([data-rendered])');
|
|
123
|
+
for (var i = 0; i < mathNodes.length; i++) {
|
|
124
|
+
var el = mathNodes[i];
|
|
125
|
+
var tex = el.textContent || '';
|
|
126
|
+
var isDisplay = el.dataset.display === 'true';
|
|
127
|
+
try {
|
|
128
|
+
window.katex.render(tex, el, {
|
|
129
|
+
displayMode: isDisplay,
|
|
130
|
+
throwOnError: false,
|
|
131
|
+
output: 'htmlAndMathml'
|
|
132
|
+
});
|
|
133
|
+
el.setAttribute('data-rendered', 'true');
|
|
134
|
+
} catch (err) {
|
|
135
|
+
// The message derives from the deck's own TeX, so it is written as
|
|
136
|
+
// text rather than parsed as markup.
|
|
137
|
+
var errSpan = document.createElement('span');
|
|
138
|
+
errSpan.className = 'math-error';
|
|
139
|
+
errSpan.textContent = (err && err.message) ? String(err.message) : 'Math rendering error';
|
|
140
|
+
el.textContent = '';
|
|
141
|
+
el.appendChild(errSpan);
|
|
142
|
+
el.setAttribute('data-rendered', 'true');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 2. Render Mermaid diagrams
|
|
148
|
+
var mermaidPromises = [];
|
|
149
|
+
var codeBlocks = root.querySelectorAll('pre code.language-mermaid, pre code.lang-mermaid');
|
|
150
|
+
if (codeBlocks.length > 0 && window.mermaid) {
|
|
151
|
+
try {
|
|
152
|
+
// 'strict' is Mermaid's default and the only level that runs its
|
|
153
|
+
// DOMPurify pass over the generated SVG. Under 'loose' that pass is
|
|
154
|
+
// skipped entirely and click directives are live, so a mermaid
|
|
155
|
+
// block in someone else's deck injects raw markup and event
|
|
156
|
+
// handlers into the page — an injection path of its own, since
|
|
157
|
+
// the SVG is produced after any sanitizing of the Markdown output.
|
|
158
|
+
window.mermaid.initialize({
|
|
159
|
+
startOnLoad: false,
|
|
160
|
+
theme: 'dark',
|
|
161
|
+
securityLevel: 'strict'
|
|
162
|
+
});
|
|
163
|
+
} catch (e) {}
|
|
164
|
+
|
|
165
|
+
for (var j = 0; j < codeBlocks.length; j++) {
|
|
166
|
+
(function (codeEl) {
|
|
167
|
+
var preEl = codeEl.closest('pre');
|
|
168
|
+
if (!preEl || preEl.dataset.rendered) return;
|
|
169
|
+
preEl.dataset.rendered = 'true';
|
|
170
|
+
var code = codeEl.textContent || '';
|
|
171
|
+
var container = document.createElement('div');
|
|
172
|
+
container.className = 'mermaid';
|
|
173
|
+
preEl.parentNode.insertBefore(container, preEl);
|
|
174
|
+
preEl.style.display = 'none';
|
|
175
|
+
|
|
176
|
+
var id = 'mermaid-' + Math.random().toString(36).slice(2, 10);
|
|
177
|
+
var p = window.mermaid.render(id, code)
|
|
178
|
+
.then(function (res) {
|
|
179
|
+
container.innerHTML = res.svg;
|
|
180
|
+
preEl.remove();
|
|
181
|
+
})
|
|
182
|
+
.catch(function (err) {
|
|
183
|
+
container.className = 'mermaid-error';
|
|
184
|
+
container.textContent = 'Mermaid Error: ' + (err && err.message ? err.message : String(err));
|
|
185
|
+
preEl.remove();
|
|
186
|
+
});
|
|
187
|
+
mermaidPromises.push(p);
|
|
188
|
+
})(codeBlocks[j]);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return Promise.all(mermaidPromises).then(function () {});
|
|
193
|
+
};
|
|
194
|
+
})();
|
|
195
|
+
`;
|