deckrun 1.3.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 +892 -0
- package/dist/editor-content.js +400 -0
- package/dist/editor.js +3592 -0
- package/dist/generate.js +2394 -0
- package/dist/index.js +538 -0
- package/dist/parser.js +65 -0
- package/dist/pdf.js +196 -0
- package/dist/preview.js +277 -0
- package/dist/themes.js +971 -0
- package/package.json +26 -0
package/dist/pdf.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import { access, mkdtemp, readFile, rm, stat } from "fs/promises";
|
|
3
|
+
import { constants } from "fs";
|
|
4
|
+
import { tmpdir } from "os";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
/**
|
|
7
|
+
* PDF export without the print dialog.
|
|
8
|
+
*
|
|
9
|
+
* The deck already prints correctly, but only if the person exporting picks
|
|
10
|
+
* landscape and turns on background graphics. Driving a headless browser
|
|
11
|
+
* ourselves removes that step: same renderer, same stylesheet, no choices.
|
|
12
|
+
*
|
|
13
|
+
* Nothing is installed for this. It uses a Chromium-family browser that is
|
|
14
|
+
* already on the machine, and the caller falls back to the print dialog when
|
|
15
|
+
* there is not one.
|
|
16
|
+
*/
|
|
17
|
+
const CANDIDATES = {
|
|
18
|
+
darwin: [
|
|
19
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
20
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
21
|
+
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
22
|
+
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
|
23
|
+
"/Applications/Arc.app/Contents/MacOS/Arc",
|
|
24
|
+
"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
|
|
25
|
+
],
|
|
26
|
+
linux: [
|
|
27
|
+
"/usr/bin/google-chrome",
|
|
28
|
+
"/usr/bin/google-chrome-stable",
|
|
29
|
+
"/usr/bin/chromium",
|
|
30
|
+
"/usr/bin/chromium-browser",
|
|
31
|
+
"/usr/bin/microsoft-edge",
|
|
32
|
+
"/usr/bin/brave-browser",
|
|
33
|
+
"/snap/bin/chromium",
|
|
34
|
+
"/opt/google/chrome/chrome",
|
|
35
|
+
],
|
|
36
|
+
win32: [
|
|
37
|
+
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
|
38
|
+
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
|
|
39
|
+
"C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
40
|
+
"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
41
|
+
"C:\\Program Files\\Chromium\\Application\\chrome.exe",
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
/** Env overrides, checked before the well-known locations. */
|
|
45
|
+
const ENV_KEYS = [
|
|
46
|
+
"DECKRUN_BROWSER",
|
|
47
|
+
"CHROME_PATH",
|
|
48
|
+
"PUPPETEER_EXECUTABLE_PATH",
|
|
49
|
+
];
|
|
50
|
+
let cached;
|
|
51
|
+
async function isExecutable(path) {
|
|
52
|
+
try {
|
|
53
|
+
await access(path, constants.X_OK);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** Path to a usable browser, or null when the machine has none we recognise. */
|
|
61
|
+
export async function findBrowser() {
|
|
62
|
+
if (cached !== undefined)
|
|
63
|
+
return cached;
|
|
64
|
+
for (const key of ENV_KEYS) {
|
|
65
|
+
const value = process.env[key];
|
|
66
|
+
if (value && (await isExecutable(value))) {
|
|
67
|
+
cached = value;
|
|
68
|
+
return cached;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
for (const path of CANDIDATES[process.platform] ?? []) {
|
|
72
|
+
if (await isExecutable(path)) {
|
|
73
|
+
cached = path;
|
|
74
|
+
return cached;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
cached = null;
|
|
78
|
+
return cached;
|
|
79
|
+
}
|
|
80
|
+
export class PdfError extends Error {
|
|
81
|
+
}
|
|
82
|
+
const RENDER_TIMEOUT_MS = 45_000;
|
|
83
|
+
const POLL_MS = 150;
|
|
84
|
+
function sleep(ms) {
|
|
85
|
+
return new Promise((done) => setTimeout(done, ms));
|
|
86
|
+
}
|
|
87
|
+
function killTree(child) {
|
|
88
|
+
try {
|
|
89
|
+
// Chrome leaves helper processes behind, so kill the whole group where the
|
|
90
|
+
// platform has them.
|
|
91
|
+
if (process.platform !== "win32" && child.pid)
|
|
92
|
+
process.kill(-child.pid, "SIGKILL");
|
|
93
|
+
else
|
|
94
|
+
child.kill("SIGKILL");
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Already gone.
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Waits for the PDF itself rather than for the browser to exit.
|
|
102
|
+
*
|
|
103
|
+
* Chrome writes the file in about a second and then lingers, running update
|
|
104
|
+
* checks and other background work, so waiting on process exit would stall for
|
|
105
|
+
* as long as the timeout allows. A size that stops changing means the write is
|
|
106
|
+
* finished.
|
|
107
|
+
*/
|
|
108
|
+
async function waitForPdf(out, hasExited, spawnError) {
|
|
109
|
+
const deadline = Date.now() + RENDER_TIMEOUT_MS;
|
|
110
|
+
let lastSize = -1;
|
|
111
|
+
while (Date.now() < deadline) {
|
|
112
|
+
const failure = spawnError();
|
|
113
|
+
if (failure)
|
|
114
|
+
throw failure;
|
|
115
|
+
let size = -1;
|
|
116
|
+
try {
|
|
117
|
+
size = (await stat(out)).size;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Not written yet.
|
|
121
|
+
}
|
|
122
|
+
if (size > 0 && size === lastSize)
|
|
123
|
+
return readFile(out);
|
|
124
|
+
lastSize = size;
|
|
125
|
+
if (hasExited() && size <= 0) {
|
|
126
|
+
// One last look, in case the write landed as the process was leaving.
|
|
127
|
+
try {
|
|
128
|
+
if ((await stat(out)).size > 0)
|
|
129
|
+
return readFile(out);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// Nothing there.
|
|
133
|
+
}
|
|
134
|
+
throw new PdfError("the browser exited without producing a PDF");
|
|
135
|
+
}
|
|
136
|
+
await sleep(POLL_MS);
|
|
137
|
+
}
|
|
138
|
+
throw new PdfError("the browser took too long to render the deck");
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Renders a served deck to PDF bytes.
|
|
142
|
+
*
|
|
143
|
+
* `--virtual-time-budget` matters: without it the browser prints before the
|
|
144
|
+
* webfont and the syntax highlighter have arrived, and the PDF comes out in a
|
|
145
|
+
* fallback font with plain code blocks.
|
|
146
|
+
*/
|
|
147
|
+
export async function renderPdf(url, browser) {
|
|
148
|
+
const dir = await mkdtemp(join(tmpdir(), "deckrun-pdf-"));
|
|
149
|
+
const out = join(dir, "deck.pdf");
|
|
150
|
+
const args = [
|
|
151
|
+
"--headless",
|
|
152
|
+
"--disable-gpu",
|
|
153
|
+
"--hide-scrollbars",
|
|
154
|
+
"--no-first-run",
|
|
155
|
+
"--no-default-browser-check",
|
|
156
|
+
"--disable-extensions",
|
|
157
|
+
"--disable-sync",
|
|
158
|
+
"--disable-default-apps",
|
|
159
|
+
"--disable-component-update",
|
|
160
|
+
"--no-service-autorun",
|
|
161
|
+
"--mute-audio",
|
|
162
|
+
// Never touch the browser profile the person is actually using.
|
|
163
|
+
`--user-data-dir=${join(dir, "profile")}`,
|
|
164
|
+
"--virtual-time-budget=5000",
|
|
165
|
+
// Header/footer flag names differ across versions; unknown switches are ignored.
|
|
166
|
+
"--no-pdf-header-footer",
|
|
167
|
+
"--print-to-pdf-no-header",
|
|
168
|
+
`--print-to-pdf=${out}`,
|
|
169
|
+
url,
|
|
170
|
+
];
|
|
171
|
+
let exited = false;
|
|
172
|
+
let failure = null;
|
|
173
|
+
const child = spawn(browser, args, {
|
|
174
|
+
stdio: "ignore",
|
|
175
|
+
detached: process.platform !== "win32",
|
|
176
|
+
});
|
|
177
|
+
child.on("exit", () => { exited = true; });
|
|
178
|
+
child.on("error", (err) => {
|
|
179
|
+
failure = new PdfError(`could not run ${browser}: ${err.message}`);
|
|
180
|
+
exited = true;
|
|
181
|
+
});
|
|
182
|
+
try {
|
|
183
|
+
return await waitForPdf(out, () => exited, () => failure);
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
killTree(child);
|
|
187
|
+
await rm(dir, { recursive: true, force: true }).catch(() => { });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/** One render at a time, so a stuck or repeated request cannot spawn a fleet. */
|
|
191
|
+
let queue = Promise.resolve();
|
|
192
|
+
export function renderPdfSerial(url, browser) {
|
|
193
|
+
const run = queue.then(() => renderPdf(url, browser), () => renderPdf(url, browser));
|
|
194
|
+
queue = run.catch(() => { });
|
|
195
|
+
return run;
|
|
196
|
+
}
|
package/dist/preview.js
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { RESET_CSS, SLIDE_CSS, DECOR_CSS } from "./generate.js";
|
|
2
|
+
import { findFont, FONT_IDS, fontOverrideCss, SIZE_IDS, DEFAULT_SIZE, DEFAULT_THEME, decorMapJson, decorOf, googleFontsHref, hljsHref, hljsMapJson, resolveSizeName, sizeSwitchableCss, themeSwitchableCss, } from "./themes.js";
|
|
3
|
+
/** Virtual viewport the preview renders at, so `vw` sizing matches a projector. */
|
|
4
|
+
export const PREVIEW_WIDTH = 1600;
|
|
5
|
+
export const PREVIEW_HEIGHT = 900;
|
|
6
|
+
/**
|
|
7
|
+
* The document loaded into the editor's preview iframe. It carries the deck's
|
|
8
|
+
* own stylesheet, so what the editor shows is what `deckrun file.md` renders.
|
|
9
|
+
* Slides arrive over postMessage; nothing is fetched or parsed in here.
|
|
10
|
+
*/
|
|
11
|
+
export function generatePreviewHtml(initialTheme = DEFAULT_THEME, initialSize = DEFAULT_SIZE, fonts = {}) {
|
|
12
|
+
const size = resolveSizeName(initialSize);
|
|
13
|
+
const head = findFont(fonts.head);
|
|
14
|
+
const body = findFont(fonts.body);
|
|
15
|
+
const fontAttrs = (head ? ` data-head="${head}"` : "") + (body ? ` data-body="${body}"` : "");
|
|
16
|
+
return `<!DOCTYPE html>
|
|
17
|
+
<html lang="en" data-theme="${initialTheme}" data-decor="${decorOf(initialTheme)}" data-size="${size}"${fontAttrs}>
|
|
18
|
+
<head>
|
|
19
|
+
<meta charset="UTF-8">
|
|
20
|
+
<title>preview</title>
|
|
21
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
22
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
23
|
+
<link href="${googleFontsHref()}" rel="stylesheet">
|
|
24
|
+
<link rel="stylesheet" id="hljs-theme" href="${hljsHref(initialTheme)}">
|
|
25
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
|
26
|
+
<style>
|
|
27
|
+
${RESET_CSS}
|
|
28
|
+
|
|
29
|
+
${themeSwitchableCss()}
|
|
30
|
+
|
|
31
|
+
${sizeSwitchableCss()}
|
|
32
|
+
|
|
33
|
+
${fontOverrideCss()}
|
|
34
|
+
|
|
35
|
+
${SLIDE_CSS}
|
|
36
|
+
|
|
37
|
+
${DECOR_CSS}
|
|
38
|
+
|
|
39
|
+
/* ── Preview overrides ────────────────────────────────────────────────── */
|
|
40
|
+
html, body { overflow: hidden; }
|
|
41
|
+
body.is-grid, body.is-grid #presentation { overflow-y: auto; height: auto; min-height: 100%; }
|
|
42
|
+
|
|
43
|
+
.slide {
|
|
44
|
+
transition: none !important;
|
|
45
|
+
transform: none !important;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* The deck staggers each block in as a slide opens. Here the slide is rebuilt
|
|
49
|
+
on every keystroke, so the same animation would flicker while typing. */
|
|
50
|
+
.slide.is-active .slide__content > * { animation: none !important; }
|
|
51
|
+
|
|
52
|
+
#presentation { background: transparent; }
|
|
53
|
+
|
|
54
|
+
/* Grid of every slide, laid out at the same virtual width as a single slide. */
|
|
55
|
+
body.is-grid #presentation {
|
|
56
|
+
position: static;
|
|
57
|
+
display: grid;
|
|
58
|
+
grid-template-columns: repeat(auto-fill, minmax(440px, 1fr));
|
|
59
|
+
gap: 34px;
|
|
60
|
+
padding: 34px;
|
|
61
|
+
width: 100%;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.pv-thumb {
|
|
65
|
+
position: relative;
|
|
66
|
+
aspect-ratio: 16 / 9;
|
|
67
|
+
border: 1px solid var(--surface0);
|
|
68
|
+
border-radius: 12px;
|
|
69
|
+
overflow: hidden;
|
|
70
|
+
background: var(--base);
|
|
71
|
+
cursor: pointer;
|
|
72
|
+
transition: border-color 0.15s ease, transform 0.15s ease;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.pv-thumb:hover { border-color: var(--accent); transform: translateY(-3px); box-shadow: var(--shadow-md); }
|
|
76
|
+
.pv-thumb.is-current { border-color: var(--accent-2); box-shadow: 0 0 0 1px var(--accent-2); }
|
|
77
|
+
|
|
78
|
+
.pv-thumb__inner {
|
|
79
|
+
position: absolute;
|
|
80
|
+
top: 0;
|
|
81
|
+
left: 0;
|
|
82
|
+
width: ${PREVIEW_WIDTH}px;
|
|
83
|
+
height: ${PREVIEW_HEIGHT}px;
|
|
84
|
+
transform-origin: top left;
|
|
85
|
+
pointer-events: none;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.pv-thumb__num {
|
|
89
|
+
position: absolute;
|
|
90
|
+
bottom: 8px;
|
|
91
|
+
right: 12px;
|
|
92
|
+
z-index: 2;
|
|
93
|
+
font-family: var(--font-mono);
|
|
94
|
+
font-size: 15px;
|
|
95
|
+
color: var(--overlay1);
|
|
96
|
+
background: var(--crust-overlay);
|
|
97
|
+
border-radius: 5px;
|
|
98
|
+
padding: 2px 8px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Empty state */
|
|
102
|
+
#pv-empty {
|
|
103
|
+
position: absolute;
|
|
104
|
+
inset: 0;
|
|
105
|
+
display: none;
|
|
106
|
+
align-items: center;
|
|
107
|
+
justify-content: center;
|
|
108
|
+
font-family: var(--font-mono);
|
|
109
|
+
font-size: 26px;
|
|
110
|
+
color: var(--overlay0);
|
|
111
|
+
letter-spacing: 0.04em;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
body.is-empty #pv-empty { display: flex; }
|
|
115
|
+
</style>
|
|
116
|
+
</head>
|
|
117
|
+
<body>
|
|
118
|
+
<div id="backdrop" aria-hidden="true"></div>
|
|
119
|
+
<div id="presentation"></div>
|
|
120
|
+
<div id="pv-empty">nothing to preview yet</div>
|
|
121
|
+
<script>
|
|
122
|
+
(function () {
|
|
123
|
+
'use strict';
|
|
124
|
+
|
|
125
|
+
var VW = ${PREVIEW_WIDTH};
|
|
126
|
+
var HLJS = ${hljsMapJson()};
|
|
127
|
+
var DECOR = ${decorMapJson()};
|
|
128
|
+
var SIZES = ${JSON.stringify(SIZE_IDS)};
|
|
129
|
+
var FONTS = ${JSON.stringify(FONT_IDS)};
|
|
130
|
+
|
|
131
|
+
var stage = document.getElementById('presentation');
|
|
132
|
+
var slides = [];
|
|
133
|
+
var mode = 'single';
|
|
134
|
+
var index = 0;
|
|
135
|
+
|
|
136
|
+
function send(msg) {
|
|
137
|
+
if (window.parent !== window) window.parent.postMessage(msg, '*');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function applyFont(slot, id) {
|
|
141
|
+
if (FONTS.indexOf(id) !== -1) document.documentElement.dataset[slot] = id;
|
|
142
|
+
else delete document.documentElement.dataset[slot];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function highlight(root) {
|
|
146
|
+
if (!window.hljs) return;
|
|
147
|
+
var blocks = root.querySelectorAll('pre code');
|
|
148
|
+
for (var i = 0; i < blocks.length; i++) {
|
|
149
|
+
if (!blocks[i].dataset.highlighted) {
|
|
150
|
+
try { window.hljs.highlightElement(blocks[i]); } catch (e) {}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Report whether the visible slide clips its own content. */
|
|
156
|
+
function reportOverflow() {
|
|
157
|
+
if (mode !== 'single') return;
|
|
158
|
+
var content = stage.querySelector('.slide__content');
|
|
159
|
+
var over = false;
|
|
160
|
+
if (content) over = content.scrollHeight - content.clientHeight > 6;
|
|
161
|
+
send({ type: 'overflow', index: index, overflow: over });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function renderSingle() {
|
|
165
|
+
document.body.classList.remove('is-grid');
|
|
166
|
+
stage.innerHTML = slides[index] || '';
|
|
167
|
+
var el = stage.querySelector('.slide');
|
|
168
|
+
if (el) el.classList.add('is-active');
|
|
169
|
+
highlight(stage);
|
|
170
|
+
requestAnimationFrame(reportOverflow);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function renderGrid() {
|
|
174
|
+
document.body.classList.add('is-grid');
|
|
175
|
+
stage.innerHTML = '';
|
|
176
|
+
var frag = document.createDocumentFragment();
|
|
177
|
+
slides.forEach(function (html, i) {
|
|
178
|
+
var thumb = document.createElement('div');
|
|
179
|
+
thumb.className = 'pv-thumb' + (i === index ? ' is-current' : '');
|
|
180
|
+
thumb.dataset.index = String(i);
|
|
181
|
+
|
|
182
|
+
var inner = document.createElement('div');
|
|
183
|
+
inner.className = 'pv-thumb__inner';
|
|
184
|
+
inner.innerHTML = html;
|
|
185
|
+
var el = inner.querySelector('.slide');
|
|
186
|
+
if (el) el.classList.add('is-active');
|
|
187
|
+
|
|
188
|
+
var num = document.createElement('span');
|
|
189
|
+
num.className = 'pv-thumb__num';
|
|
190
|
+
num.textContent = String(i + 1);
|
|
191
|
+
|
|
192
|
+
thumb.appendChild(inner);
|
|
193
|
+
thumb.appendChild(num);
|
|
194
|
+
frag.appendChild(thumb);
|
|
195
|
+
});
|
|
196
|
+
stage.appendChild(frag);
|
|
197
|
+
scaleThumbs();
|
|
198
|
+
highlight(stage);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function scaleThumbs() {
|
|
202
|
+
var thumbs = stage.querySelectorAll('.pv-thumb');
|
|
203
|
+
for (var i = 0; i < thumbs.length; i++) {
|
|
204
|
+
var inner = thumbs[i].querySelector('.pv-thumb__inner');
|
|
205
|
+
if (inner) inner.style.transform = 'scale(' + (thumbs[i].clientWidth / VW) + ')';
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function render() {
|
|
210
|
+
document.body.classList.toggle('is-empty', slides.length === 0);
|
|
211
|
+
if (slides.length === 0) { stage.innerHTML = ''; return; }
|
|
212
|
+
if (index >= slides.length) index = slides.length - 1;
|
|
213
|
+
if (index < 0) index = 0;
|
|
214
|
+
if (mode === 'grid') renderGrid(); else renderSingle();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
stage.addEventListener('click', function (e) {
|
|
218
|
+
if (mode !== 'grid') return;
|
|
219
|
+
var thumb = e.target.closest ? e.target.closest('.pv-thumb') : null;
|
|
220
|
+
if (thumb) send({ type: 'goto', index: parseInt(thumb.dataset.index, 10) });
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
window.addEventListener('resize', function () {
|
|
224
|
+
if (mode === 'grid') scaleThumbs();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
window.addEventListener('message', function (e) {
|
|
228
|
+
var m = e.data || {};
|
|
229
|
+
if (m.type === 'render') {
|
|
230
|
+
var sameSet = m.slides && slides.length === m.slides.length &&
|
|
231
|
+
m.slides.every(function (h, i) { return h === slides[i]; });
|
|
232
|
+
slides = m.slides || [];
|
|
233
|
+
var modeChanged = m.mode !== mode;
|
|
234
|
+
var indexChanged = m.index !== index;
|
|
235
|
+
mode = m.mode || 'single';
|
|
236
|
+
index = typeof m.index === 'number' ? m.index : 0;
|
|
237
|
+
if (sameSet && !modeChanged && !indexChanged) { reportOverflow(); return; }
|
|
238
|
+
render();
|
|
239
|
+
} else if (m.type === 'theme') {
|
|
240
|
+
if (HLJS[m.theme]) {
|
|
241
|
+
document.documentElement.dataset.theme = m.theme;
|
|
242
|
+
document.documentElement.dataset.decor = DECOR[m.theme];
|
|
243
|
+
var link = document.getElementById('hljs-theme');
|
|
244
|
+
if (link) link.href = HLJS[m.theme];
|
|
245
|
+
}
|
|
246
|
+
if (SIZES.indexOf(m.size) !== -1) {
|
|
247
|
+
document.documentElement.dataset.size = m.size;
|
|
248
|
+
}
|
|
249
|
+
// An empty string clears the override and hands the slot back to the
|
|
250
|
+
// theme, which delete does and an assignment of '' would not.
|
|
251
|
+
applyFont('head', m.head);
|
|
252
|
+
applyFont('body', m.body);
|
|
253
|
+
// Type size and face both change how tall a slide's content runs, so the
|
|
254
|
+
// editor's overflow warning has to be re-measured against them.
|
|
255
|
+
requestAnimationFrame(reportOverflow);
|
|
256
|
+
} else if (m.type === 'index') {
|
|
257
|
+
index = m.index;
|
|
258
|
+
if (mode === 'grid') {
|
|
259
|
+
var cur = stage.querySelector('.pv-thumb.is-current');
|
|
260
|
+
if (cur) cur.classList.remove('is-current');
|
|
261
|
+
var next = stage.querySelector('.pv-thumb[data-index="' + index + '"]');
|
|
262
|
+
if (next) {
|
|
263
|
+
next.classList.add('is-current');
|
|
264
|
+
next.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
render();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
send({ type: 'ready' });
|
|
273
|
+
})();
|
|
274
|
+
</script>
|
|
275
|
+
</body>
|
|
276
|
+
</html>`;
|
|
277
|
+
}
|