deckrun 1.3.0 → 1.6.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 +217 -35
- package/dist/editor-content.js +86 -1
- package/dist/editor.js +528 -216
- package/dist/fragments.js +71 -0
- package/dist/generate.js +1040 -45
- package/dist/index.js +353 -53
- package/dist/lint.js +218 -0
- package/dist/parser.js +85 -0
- package/dist/pdf.js +5 -1
- package/dist/presentation-options.js +287 -0
- package/dist/preview.js +125 -7
- package/dist/rich-content.js +170 -0
- package/dist/themes.js +8 -5
- package/package.json +5 -2
package/dist/editor.js
CHANGED
|
@@ -2,7 +2,8 @@ import { RESET_CSS } from "./generate.js";
|
|
|
2
2
|
import { DEFAULT_SIZE, DEFAULT_THEME, decorOf, findFont, fontSummaries, googleFontsHref, resolveSizeName, sizeSummaries, themeSummaries, themeSwitchableCss, } from "./themes.js";
|
|
3
3
|
import { SNIPPETS, SNIPPET_GROUPS, TIPS, WELCOME_DECK, } from "./editor-content.js";
|
|
4
4
|
import { PREVIEW_WIDTH, PREVIEW_HEIGHT } from "./preview.js";
|
|
5
|
-
|
|
5
|
+
import { DEFAULT_TEMPLATE, DEFAULT_TRANSITION, resolveTemplateName, resolveTransitionName, templateSummaries, transitionSummaries, } from "./presentation-options.js";
|
|
6
|
+
function bootstrapJson(theme, size, fonts, template, transition) {
|
|
6
7
|
const payload = {
|
|
7
8
|
theme,
|
|
8
9
|
themes: themeSummaries(),
|
|
@@ -10,6 +11,10 @@ function bootstrapJson(theme, size, fonts) {
|
|
|
10
11
|
sizes: sizeSummaries(),
|
|
11
12
|
fonts,
|
|
12
13
|
faces: fontSummaries(),
|
|
14
|
+
template,
|
|
15
|
+
templates: templateSummaries(),
|
|
16
|
+
transition,
|
|
17
|
+
transitions: transitionSummaries(),
|
|
13
18
|
width: PREVIEW_WIDTH,
|
|
14
19
|
height: PREVIEW_HEIGHT,
|
|
15
20
|
groups: SNIPPET_GROUPS,
|
|
@@ -21,15 +26,17 @@ function bootstrapJson(theme, size, fonts) {
|
|
|
21
26
|
return JSON.stringify(payload).replace(/</g, "\\u003c");
|
|
22
27
|
}
|
|
23
28
|
/** The Markdown editor served when `deckrun` is launched without a file. */
|
|
24
|
-
export function generateEditorHtml(theme = DEFAULT_THEME, sizeInput = DEFAULT_SIZE, fontInput = {}) {
|
|
29
|
+
export function generateEditorHtml(theme = DEFAULT_THEME, sizeInput = DEFAULT_SIZE, fontInput = {}, templateInput = DEFAULT_TEMPLATE, transitionInput = DEFAULT_TRANSITION) {
|
|
25
30
|
const size = resolveSizeName(sizeInput);
|
|
31
|
+
const template = resolveTemplateName(templateInput);
|
|
32
|
+
const transition = resolveTransitionName(transitionInput);
|
|
26
33
|
const fonts = { head: findFont(fontInput.head), body: findFont(fontInput.body) };
|
|
27
34
|
return `<!DOCTYPE html>
|
|
28
|
-
<html lang="en" data-theme="${theme}" data-decor="${decorOf(theme)}">
|
|
35
|
+
<html lang="en" data-theme="${theme}" data-decor="${decorOf(theme)}" data-size="${size}" data-template="${template}" data-transition="${transition}">
|
|
29
36
|
<head>
|
|
30
37
|
<meta charset="UTF-8">
|
|
31
38
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
32
|
-
<title>deckrun editor</title>
|
|
39
|
+
<title>deckrun · editor</title>
|
|
33
40
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
34
41
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
35
42
|
<link href="${googleFontsHref()}" rel="stylesheet">
|
|
@@ -50,7 +57,8 @@ html, body {
|
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }
|
|
53
|
-
::selection { background: var(--
|
|
60
|
+
::selection { background: var(--selection-bg, var(--accent-line, rgba(56, 139, 253, 0.35))); color: var(--selection-text, inherit); }
|
|
61
|
+
::-moz-selection { background: var(--selection-bg, var(--accent-line, rgba(56, 139, 253, 0.35))); color: var(--selection-text, inherit); }
|
|
54
62
|
|
|
55
63
|
::-webkit-scrollbar { width: 9px; height: 9px; }
|
|
56
64
|
::-webkit-scrollbar-track { background: transparent; }
|
|
@@ -305,21 +313,30 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
305
313
|
position: absolute;
|
|
306
314
|
inset: 0;
|
|
307
315
|
margin: 0;
|
|
308
|
-
padding: 18px 20px
|
|
316
|
+
padding: 18px 20px;
|
|
309
317
|
font-family: var(--font-mono);
|
|
310
318
|
font-size: 13.5px;
|
|
311
319
|
font-weight: 400;
|
|
320
|
+
font-style: normal;
|
|
312
321
|
line-height: 1.75;
|
|
313
322
|
letter-spacing: 0;
|
|
323
|
+
word-spacing: 0;
|
|
314
324
|
white-space: pre-wrap;
|
|
315
325
|
overflow-wrap: break-word;
|
|
316
326
|
word-break: break-word;
|
|
317
327
|
tab-size: 2;
|
|
318
328
|
border: 0;
|
|
329
|
+
box-sizing: border-box;
|
|
319
330
|
background: transparent;
|
|
331
|
+
scrollbar-width: none;
|
|
332
|
+
-ms-overflow-style: none;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
#hl::-webkit-scrollbar, #src::-webkit-scrollbar {
|
|
336
|
+
display: none;
|
|
320
337
|
}
|
|
321
338
|
|
|
322
|
-
#hl { z-index: 1; overflow: hidden; pointer-events: none; color: var(--subtext0);
|
|
339
|
+
#hl { z-index: 1; overflow: hidden; pointer-events: none; color: var(--subtext0); }
|
|
323
340
|
#measure { z-index: 0; visibility: hidden; overflow: hidden; }
|
|
324
341
|
|
|
325
342
|
#src {
|
|
@@ -332,38 +349,29 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
332
349
|
outline: none;
|
|
333
350
|
}
|
|
334
351
|
|
|
335
|
-
#src::selection { background: var(--
|
|
352
|
+
#src::selection { background: var(--selection-bg, var(--accent-line, rgba(56, 139, 253, 0.35))); color: transparent; }
|
|
353
|
+
#src::-moz-selection { background: var(--selection-bg, var(--accent-line, rgba(56, 139, 253, 0.35))); color: transparent; }
|
|
336
354
|
|
|
337
|
-
/* Markdown tokens */
|
|
338
|
-
.t-h1 { color: var(--accent);
|
|
339
|
-
.t-h2 { color: var(--accent-2);
|
|
340
|
-
.t-h3 { color: var(--accent-3);
|
|
355
|
+
/* Markdown tokens — color only to guarantee pixel-identical alignment */
|
|
356
|
+
.t-h1 { color: var(--accent); }
|
|
357
|
+
.t-h2 { color: var(--accent-2); }
|
|
358
|
+
.t-h3 { color: var(--accent-3); }
|
|
341
359
|
.t-h4 { color: var(--teal); }
|
|
342
|
-
.t-strong { color: var(--peach);
|
|
343
|
-
.t-em { color: var(--subtext1);
|
|
360
|
+
.t-strong { color: var(--peach); }
|
|
361
|
+
.t-em { color: var(--subtext1); }
|
|
344
362
|
.t-code { color: var(--green); }
|
|
345
363
|
.t-fence { color: var(--overlay1); }
|
|
346
364
|
.t-codeline { color: var(--subtext1); }
|
|
347
|
-
.t-quote { color: var(--subtext0);
|
|
365
|
+
.t-quote { color: var(--subtext0); }
|
|
348
366
|
.t-marker { color: var(--accent); }
|
|
349
367
|
.t-link { color: var(--blue); }
|
|
350
368
|
.t-url { color: var(--overlay0); }
|
|
351
369
|
.t-img { color: var(--sapphire); }
|
|
352
|
-
.t-dir { color: var(--yellow);
|
|
353
|
-
.t-note { color: var(--overlay0);
|
|
370
|
+
.t-dir { color: var(--yellow); }
|
|
371
|
+
.t-note { color: var(--overlay0); }
|
|
354
372
|
.t-html { color: var(--pink); }
|
|
355
373
|
.t-table { color: var(--lavender); }
|
|
356
|
-
|
|
357
|
-
.t-sep { position: relative; color: var(--accent); counter-increment: sld; }
|
|
358
|
-
.t-sep::after {
|
|
359
|
-
content: 'slide ' counter(sld);
|
|
360
|
-
position: absolute;
|
|
361
|
-
left: calc(100% + 14px);
|
|
362
|
-
top: 0;
|
|
363
|
-
white-space: nowrap;
|
|
364
|
-
color: var(--overlay0);
|
|
365
|
-
font-size: 11px;
|
|
366
|
-
}
|
|
374
|
+
.t-sep { color: var(--accent); }
|
|
367
375
|
|
|
368
376
|
/* ── Drop target ──────────────────────────────────────────────────────── */
|
|
369
377
|
#pane-edit.is-dropping::after {
|
|
@@ -382,56 +390,6 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
382
390
|
pointer-events: none;
|
|
383
391
|
}
|
|
384
392
|
|
|
385
|
-
/* ── Contextual nudge ─────────────────────────────────────────────────── */
|
|
386
|
-
#nudge {
|
|
387
|
-
position: absolute;
|
|
388
|
-
left: 14px;
|
|
389
|
-
right: 14px;
|
|
390
|
-
bottom: 14px;
|
|
391
|
-
z-index: 7;
|
|
392
|
-
display: flex;
|
|
393
|
-
align-items: flex-start;
|
|
394
|
-
gap: 12px;
|
|
395
|
-
padding: 11px 13px;
|
|
396
|
-
background: var(--base);
|
|
397
|
-
border: 1px solid var(--surface1);
|
|
398
|
-
border-left: 2px solid var(--accent);
|
|
399
|
-
border-radius: 9px;
|
|
400
|
-
box-shadow: var(--shadow-md);
|
|
401
|
-
opacity: 0;
|
|
402
|
-
transform: translateY(10px);
|
|
403
|
-
pointer-events: none;
|
|
404
|
-
transition: opacity 0.22s ease, transform 0.22s ease;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
#nudge.is-on { opacity: 1; transform: translateY(0); pointer-events: all; }
|
|
408
|
-
#nudge__body { flex: 1 1 auto; }
|
|
409
|
-
#nudge__label {
|
|
410
|
-
font-size: 10px;
|
|
411
|
-
letter-spacing: 0.14em;
|
|
412
|
-
text-transform: uppercase;
|
|
413
|
-
color: var(--accent);
|
|
414
|
-
margin-bottom: 4px;
|
|
415
|
-
}
|
|
416
|
-
#nudge__text { font-size: 12.5px; color: var(--subtext1); line-height: 1.6; }
|
|
417
|
-
#nudge__text code {
|
|
418
|
-
font: inherit;
|
|
419
|
-
color: var(--green);
|
|
420
|
-
background: var(--surface-soft);
|
|
421
|
-
border-radius: 4px;
|
|
422
|
-
padding: 0.05em 0.3em;
|
|
423
|
-
}
|
|
424
|
-
#nudge__acts { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
|
|
425
|
-
.nudge-btn {
|
|
426
|
-
font-size: 11px;
|
|
427
|
-
color: var(--subtext0);
|
|
428
|
-
border: 1px solid var(--surface1);
|
|
429
|
-
border-radius: 6px;
|
|
430
|
-
padding: 4px 8px;
|
|
431
|
-
}
|
|
432
|
-
.nudge-btn:hover { border-color: var(--accent); color: var(--text); }
|
|
433
|
-
.nudge-btn--x { padding: 4px 7px; color: var(--overlay0); }
|
|
434
|
-
|
|
435
393
|
/* ── Preview ──────────────────────────────────────────────────────────── */
|
|
436
394
|
#prev-head {
|
|
437
395
|
display: flex;
|
|
@@ -445,6 +403,22 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
445
403
|
color: var(--overlay1);
|
|
446
404
|
}
|
|
447
405
|
|
|
406
|
+
#overflow-badge {
|
|
407
|
+
display: none;
|
|
408
|
+
align-items: center;
|
|
409
|
+
gap: 5px;
|
|
410
|
+
padding: 2px 7px;
|
|
411
|
+
border: 1px solid var(--yellow);
|
|
412
|
+
border-radius: 999px;
|
|
413
|
+
color: var(--yellow);
|
|
414
|
+
background: var(--surface-soft);
|
|
415
|
+
font-size: 9px;
|
|
416
|
+
letter-spacing: 0.1em;
|
|
417
|
+
text-transform: uppercase;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
#overflow-badge.is-on { display: inline-flex; }
|
|
421
|
+
|
|
448
422
|
/* ── Dropdown menu ────────────────────────────────────────────────────── */
|
|
449
423
|
.menu { position: relative; display: inline-flex; }
|
|
450
424
|
.menu__chev { font-size: 9px; color: var(--overlay0); }
|
|
@@ -494,6 +468,21 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
494
468
|
white-space: nowrap;
|
|
495
469
|
}
|
|
496
470
|
|
|
471
|
+
.menu__pop--template { min-width: 390px; padding: 7px; }
|
|
472
|
+
.menu-section + .menu-section { margin-top: 6px; padding-top: 7px; border-top: 1px solid var(--surface0); }
|
|
473
|
+
.menu-section__title {
|
|
474
|
+
padding: 2px 10px 5px;
|
|
475
|
+
font-size: 9px;
|
|
476
|
+
letter-spacing: 0.16em;
|
|
477
|
+
text-transform: uppercase;
|
|
478
|
+
color: var(--overlay0);
|
|
479
|
+
}
|
|
480
|
+
.menu__pop--template button.is-on {
|
|
481
|
+
background: var(--accent-soft);
|
|
482
|
+
border-left-color: var(--accent);
|
|
483
|
+
}
|
|
484
|
+
.menu__pop--template button.is-on .menu__name { color: var(--accent); }
|
|
485
|
+
|
|
497
486
|
.seg { display: flex; border: 1px solid var(--surface0); border-radius: 7px; overflow: hidden; }
|
|
498
487
|
.seg button { font-size: 11px; padding: 3px 10px; color: var(--overlay1); }
|
|
499
488
|
.seg button.is-on { background: var(--surface0); color: var(--text); }
|
|
@@ -580,6 +569,19 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
580
569
|
#tipbar button { color: var(--overlay0); padding: 0 3px; }
|
|
581
570
|
#tipbar button:hover { color: var(--text); }
|
|
582
571
|
|
|
572
|
+
#follow-chip {
|
|
573
|
+
flex: 0 0 auto;
|
|
574
|
+
border: 1px solid var(--surface0);
|
|
575
|
+
border-radius: 5px;
|
|
576
|
+
padding: 1px 7px;
|
|
577
|
+
color: var(--accent2);
|
|
578
|
+
cursor: pointer;
|
|
579
|
+
user-select: none;
|
|
580
|
+
}
|
|
581
|
+
#follow-chip:hover { border-color: var(--accent2); }
|
|
582
|
+
#follow-chip b { font-weight: 600; }
|
|
583
|
+
#follow-chip.hidden { display: none; }
|
|
584
|
+
|
|
583
585
|
/* ── Command palette ──────────────────────────────────────────────────── */
|
|
584
586
|
#palette, #guide, #library { position: fixed; inset: 0; z-index: 40; display: none; }
|
|
585
587
|
#palette.is-on, #guide.is-on, #library.is-on { display: block; }
|
|
@@ -1065,7 +1067,8 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1065
1067
|
}
|
|
1066
1068
|
|
|
1067
1069
|
[data-doc-kind="html"] #btn-guide,
|
|
1068
|
-
[data-doc-kind="html"] #btn-palette
|
|
1070
|
+
[data-doc-kind="html"] #btn-palette,
|
|
1071
|
+
[data-doc-kind="html"] #template-wrap {
|
|
1069
1072
|
display: none !important;
|
|
1070
1073
|
}
|
|
1071
1074
|
|
|
@@ -1118,6 +1121,30 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1118
1121
|
}
|
|
1119
1122
|
.start-card__acts button:hover { border-color: var(--accent); color: var(--text); }
|
|
1120
1123
|
|
|
1124
|
+
.start-options {
|
|
1125
|
+
margin-top: 12px;
|
|
1126
|
+
padding-top: 11px;
|
|
1127
|
+
border-top: 1px solid var(--surface0);
|
|
1128
|
+
}
|
|
1129
|
+
.start-options__row { display: flex; align-items: center; gap: 8px; margin-top: 7px; }
|
|
1130
|
+
.start-options__label {
|
|
1131
|
+
flex: 0 0 72px;
|
|
1132
|
+
font-size: 9px;
|
|
1133
|
+
letter-spacing: 0.12em;
|
|
1134
|
+
text-transform: uppercase;
|
|
1135
|
+
color: var(--overlay0);
|
|
1136
|
+
}
|
|
1137
|
+
.start-options__choices { display: flex; flex-wrap: wrap; gap: 5px; }
|
|
1138
|
+
.start-choice {
|
|
1139
|
+
padding: 4px 8px;
|
|
1140
|
+
font-size: 10.5px;
|
|
1141
|
+
color: var(--subtext0);
|
|
1142
|
+
border: 1px solid var(--surface1);
|
|
1143
|
+
border-radius: 6px;
|
|
1144
|
+
}
|
|
1145
|
+
.start-choice:hover { color: var(--text); border-color: var(--accent); }
|
|
1146
|
+
.start-choice.is-on { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
|
|
1147
|
+
|
|
1121
1148
|
.start-card__url { display: flex; gap: 6px; flex: 1 1 auto; min-width: 0; }
|
|
1122
1149
|
.start-card__url input {
|
|
1123
1150
|
flex: 1 1 auto;
|
|
@@ -1173,6 +1200,21 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1173
1200
|
</button>
|
|
1174
1201
|
</div>
|
|
1175
1202
|
</span>
|
|
1203
|
+
<span class="menu" id="template-wrap">
|
|
1204
|
+
<button class="btn" id="btn-template" aria-haspopup="true" aria-expanded="false" title="Composition template and slide transition">
|
|
1205
|
+
<span id="template-label">template</span> <span class="menu__chev">▾</span>
|
|
1206
|
+
</button>
|
|
1207
|
+
<div class="menu__pop menu__pop--template" id="template-menu">
|
|
1208
|
+
<div class="menu-section">
|
|
1209
|
+
<div class="menu-section__title">template</div>
|
|
1210
|
+
<div id="template-list"></div>
|
|
1211
|
+
</div>
|
|
1212
|
+
<div class="menu-section">
|
|
1213
|
+
<div class="menu-section__title">transition</div>
|
|
1214
|
+
<div id="transition-list"></div>
|
|
1215
|
+
</div>
|
|
1216
|
+
</div>
|
|
1217
|
+
</span>
|
|
1176
1218
|
<button class="btn" id="btn-theme" title="Pick a theme">
|
|
1177
1219
|
<span class="th-dot" id="theme-dot"></span>
|
|
1178
1220
|
<span id="theme-label">theme</span>
|
|
@@ -1208,16 +1250,6 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1208
1250
|
<pre id="measure" aria-hidden="true"></pre>
|
|
1209
1251
|
<textarea id="src" spellcheck="false" autocomplete="off" autocapitalize="off" wrap="soft" aria-label="Markdown source"></textarea>
|
|
1210
1252
|
</div>
|
|
1211
|
-
<div id="nudge">
|
|
1212
|
-
<div id="nudge__body">
|
|
1213
|
-
<div id="nudge__label">try this</div>
|
|
1214
|
-
<div id="nudge__text"></div>
|
|
1215
|
-
</div>
|
|
1216
|
-
<div id="nudge__acts">
|
|
1217
|
-
<button class="nudge-btn" id="nudge-do">insert</button>
|
|
1218
|
-
<button class="nudge-btn nudge-btn--x" id="nudge-x" title="Dismiss">×</button>
|
|
1219
|
-
</div>
|
|
1220
|
-
</div>
|
|
1221
1253
|
<textarea id="src-html" spellcheck="false" autocomplete="off" autocapitalize="off" wrap="off" aria-label="HTML source"></textarea>
|
|
1222
1254
|
</section>
|
|
1223
1255
|
|
|
@@ -1228,6 +1260,7 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1228
1260
|
<button class="step" id="btn-prev" title="Previous slide">←</button>
|
|
1229
1261
|
<span id="prev-count">slide 0 / 0</span>
|
|
1230
1262
|
<button class="step" id="btn-next" title="Next slide">→</button>
|
|
1263
|
+
<span id="overflow-badge" title="This slide is clipped. Shorten it or split it with three dashes.">overflow</span>
|
|
1231
1264
|
<span class="spacer"></span>
|
|
1232
1265
|
<span id="prev-scale"></span>
|
|
1233
1266
|
<div class="seg">
|
|
@@ -1253,6 +1286,7 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1253
1286
|
<span id="words">0 words</span>
|
|
1254
1287
|
<span id="chip-slides">0 slides</span>
|
|
1255
1288
|
<span id="save-state"></span>
|
|
1289
|
+
<span id="follow-chip" class="hidden" title="The editor is mirroring the presented deck. Click to stop following.">following deck · <b id="follow-slide">1</b></span>
|
|
1256
1290
|
<span id="tipbar">
|
|
1257
1291
|
<span class="tag">tip</span>
|
|
1258
1292
|
<span id="tip-text"></span>
|
|
@@ -1336,6 +1370,16 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1336
1370
|
<button id="start-md-blank">start blank</button>
|
|
1337
1371
|
<button id="start-md-upload">upload .md</button>
|
|
1338
1372
|
</span>
|
|
1373
|
+
<div class="start-options">
|
|
1374
|
+
<div class="start-options__row">
|
|
1375
|
+
<span class="start-options__label">template</span>
|
|
1376
|
+
<span class="start-options__choices" id="start-template-list"></span>
|
|
1377
|
+
</div>
|
|
1378
|
+
<div class="start-options__row">
|
|
1379
|
+
<span class="start-options__label">transition</span>
|
|
1380
|
+
<span class="start-options__choices" id="start-transition-list"></span>
|
|
1381
|
+
</div>
|
|
1382
|
+
</div>
|
|
1339
1383
|
</div>
|
|
1340
1384
|
<div class="start-card" id="start-html-card">
|
|
1341
1385
|
<span class="start-card__title">HTML document</span>
|
|
@@ -1359,7 +1403,7 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1359
1403
|
<div id="toasts"></div>
|
|
1360
1404
|
<input type="file" id="file-any" accept=".md,.markdown,text/markdown,text/plain,.html,.htm,text/html" hidden>
|
|
1361
1405
|
|
|
1362
|
-
<script type="application/json" id="bootstrap">${bootstrapJson(theme, size, fonts)}</script>
|
|
1406
|
+
<script type="application/json" id="bootstrap">${bootstrapJson(theme, size, fonts, template, transition)}</script>
|
|
1363
1407
|
<script>
|
|
1364
1408
|
(function () {
|
|
1365
1409
|
'use strict';
|
|
@@ -1376,6 +1420,8 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1376
1420
|
size: 'deckrun.size.v1',
|
|
1377
1421
|
head: 'deckrun.font.head.v1',
|
|
1378
1422
|
body: 'deckrun.font.body.v1',
|
|
1423
|
+
template:'deckrun.template.v1',
|
|
1424
|
+
transition:'deckrun.transition.v1',
|
|
1379
1425
|
split: 'deckrun.split.v1',
|
|
1380
1426
|
mode: 'deckrun.mode.v1',
|
|
1381
1427
|
nudge: 'deckrun.nudges.v1',
|
|
@@ -1460,11 +1506,20 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1460
1506
|
}
|
|
1461
1507
|
|
|
1462
1508
|
/** Returns the new deck's id, or null if this browser refused to store it. */
|
|
1463
|
-
function createDeck(name, content, kind) {
|
|
1509
|
+
function createDeck(name, content, kind, template, transition) {
|
|
1464
1510
|
var id = newDeckId();
|
|
1465
1511
|
if (!lsSet(K.deck + id, content)) return null;
|
|
1466
1512
|
var list = loadIndex();
|
|
1467
|
-
list.unshift({
|
|
1513
|
+
list.unshift({
|
|
1514
|
+
id: id,
|
|
1515
|
+
name: name || 'untitled',
|
|
1516
|
+
kind: kind || 'markdown',
|
|
1517
|
+
slides: 0,
|
|
1518
|
+
chars: content.length,
|
|
1519
|
+
template: template || (typeof state !== 'undefined' && state.template ? state.template : D.template),
|
|
1520
|
+
transition: transition || (typeof state !== 'undefined' && state.transition ? state.transition : D.transition),
|
|
1521
|
+
at: Date.now()
|
|
1522
|
+
});
|
|
1468
1523
|
if (!saveIndex(list)) { lsDel(K.deck + id); return null; }
|
|
1469
1524
|
return id;
|
|
1470
1525
|
}
|
|
@@ -1488,6 +1543,19 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1488
1543
|
return days + 'd ago';
|
|
1489
1544
|
}
|
|
1490
1545
|
|
|
1546
|
+
function updateDocTitle() {
|
|
1547
|
+
if ($('screen-start') && $('screen-start').classList.contains('is-on')) {
|
|
1548
|
+
document.title = 'deckrun \u00b7 start';
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
if ($('library') && $('library').classList.contains('is-on')) {
|
|
1552
|
+
document.title = 'deckrun \u00b7 library';
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
var name = ($('docname').value || '').trim();
|
|
1556
|
+
document.title = name ? name + ' \u00b7 deckrun' : 'deckrun \u00b7 editor';
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1491
1559
|
/** One deck's metadata is refreshed from the editor on every save. */
|
|
1492
1560
|
function touchCurrent() {
|
|
1493
1561
|
var list = loadIndex();
|
|
@@ -1495,6 +1563,8 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1495
1563
|
if (list[i].id === state.deckId) {
|
|
1496
1564
|
list[i].name = $('docname').value.trim() || 'untitled';
|
|
1497
1565
|
list[i].kind = state.kind;
|
|
1566
|
+
list[i].template = state.template;
|
|
1567
|
+
list[i].transition = state.transition;
|
|
1498
1568
|
if (state.kind === 'html') {
|
|
1499
1569
|
list[i].slides = 0;
|
|
1500
1570
|
list[i].chars = srcHtml.value.length;
|
|
@@ -1506,6 +1576,7 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1506
1576
|
break;
|
|
1507
1577
|
}
|
|
1508
1578
|
}
|
|
1579
|
+
updateDocTitle();
|
|
1509
1580
|
return saveIndex(list);
|
|
1510
1581
|
}
|
|
1511
1582
|
|
|
@@ -1560,6 +1631,22 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1560
1631
|
return SIZE_BY_ID[id] ? id : (SIZE_BY_ID[D.size] ? D.size : 'm');
|
|
1561
1632
|
}
|
|
1562
1633
|
|
|
1634
|
+
var TEMPLATES = D.templates;
|
|
1635
|
+
var TEMPLATE_BY_ID = {};
|
|
1636
|
+
TEMPLATES.forEach(function (item) { TEMPLATE_BY_ID[item.id] = item; });
|
|
1637
|
+
|
|
1638
|
+
function pickTemplate(id) {
|
|
1639
|
+
return TEMPLATE_BY_ID[id] ? id : (TEMPLATE_BY_ID[D.template] ? D.template : 'classic');
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
var TRANSITIONS = D.transitions;
|
|
1643
|
+
var TRANSITION_BY_ID = {};
|
|
1644
|
+
TRANSITIONS.forEach(function (item) { TRANSITION_BY_ID[item.id] = item; });
|
|
1645
|
+
|
|
1646
|
+
function pickTransition(id) {
|
|
1647
|
+
return TRANSITION_BY_ID[id] ? id : (TRANSITION_BY_ID[D.transition] ? D.transition : 'slide');
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1563
1650
|
var FACES = D.faces;
|
|
1564
1651
|
var FACE_BY_ID = {};
|
|
1565
1652
|
FACES.forEach(function (f) { FACE_BY_ID[f.id] = f; });
|
|
@@ -1577,7 +1664,6 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1577
1664
|
var elChipSlides = $('chip-slides'), elSave = $('save-state'), elPos = $('pos'), elWords = $('words');
|
|
1578
1665
|
var elCount = $('prev-count'), elScale = $('prev-scale');
|
|
1579
1666
|
var elNotes = $('notes'), elNotesText = $('notes__text');
|
|
1580
|
-
var elNudge = $('nudge'), elNudgeText = $('nudge__text'), elNudgeDo = $('nudge-do'), elNudgeX = $('nudge-x');
|
|
1581
1667
|
var palette = $('palette'), palInput = $('pal-input'), palList = $('pal-list');
|
|
1582
1668
|
var guide = $('guide'), guideBody = $('guide-body');
|
|
1583
1669
|
var srcHtml = $('src-html'), frameHtml = $('frame-html');
|
|
@@ -1594,6 +1680,8 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1594
1680
|
size: pickSize(lsGet(K.size, D.size)),
|
|
1595
1681
|
head: pickFont(lsGet(K.head, D.fonts.head)),
|
|
1596
1682
|
body: pickFont(lsGet(K.body, D.fonts.body)),
|
|
1683
|
+
template: pickTemplate(lsGet(K.template, D.template)),
|
|
1684
|
+
transition: pickTransition(lsGet(K.transition, D.transition)),
|
|
1597
1685
|
frameReady: false,
|
|
1598
1686
|
overflow: {},
|
|
1599
1687
|
dismissed: {},
|
|
@@ -1609,6 +1697,75 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1609
1697
|
if (saved && saved.length) saved.forEach(function (id) { state.dismissed[id] = true; });
|
|
1610
1698
|
} catch (e) {}
|
|
1611
1699
|
|
|
1700
|
+
// ── Following the presented deck ───────────────────────────────────────
|
|
1701
|
+
// Presenting hands the deck tab this editor's session id (ps=... in the
|
|
1702
|
+
// URL), and the editor listens on that same channel: the preview and the
|
|
1703
|
+
// notes panel mirror the deck's current slide, so the editor window is the
|
|
1704
|
+
// presenter's screen and the deck tab is the projector. No second window
|
|
1705
|
+
// is involved.
|
|
1706
|
+
var FOLLOW_KEY = 'deckrun.presenter.session';
|
|
1707
|
+
var followSid = null;
|
|
1708
|
+
try { followSid = sessionStorage.getItem(FOLLOW_KEY); } catch (e) {}
|
|
1709
|
+
if (!followSid) {
|
|
1710
|
+
followSid = (window.crypto && crypto.randomUUID)
|
|
1711
|
+
? crypto.randomUUID()
|
|
1712
|
+
: 'e' + Date.now().toString(36) + Math.random().toString(36).slice(2);
|
|
1713
|
+
try { sessionStorage.setItem(FOLLOW_KEY, followSid); } catch (e) {}
|
|
1714
|
+
}
|
|
1715
|
+
var followChan = (typeof BroadcastChannel !== 'undefined')
|
|
1716
|
+
? new BroadcastChannel('deckrun:' + followSid)
|
|
1717
|
+
: null;
|
|
1718
|
+
|
|
1719
|
+
var following = false; // the editor is mirroring the deck right now
|
|
1720
|
+
var expectingDeck = false; // a present just went out: follow the deck that answers
|
|
1721
|
+
var deckNotes = []; // the deck's own notes, authoritative while following
|
|
1722
|
+
var followChipEl = $('follow-chip');
|
|
1723
|
+
var followSlideEl = $('follow-slide');
|
|
1724
|
+
|
|
1725
|
+
/** The editor drives the deck as well as the preview: wherever the
|
|
1726
|
+
presenter lands in the editor, the deck goes there too. The deck echoes
|
|
1727
|
+
its state back over the same channel, which is what keeps both in step. */
|
|
1728
|
+
function announceIndex(i) {
|
|
1729
|
+
if (followChan) followChan.postMessage({ id: followSid, type: 'goto', index: i });
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
function followDeck(i) {
|
|
1733
|
+
var n = state.slides.length;
|
|
1734
|
+
if (!n) return;
|
|
1735
|
+
i = Math.max(0, Math.min(n - 1, i));
|
|
1736
|
+
var changed = i !== state.index;
|
|
1737
|
+
state.index = i;
|
|
1738
|
+
state.notes = deckNotes;
|
|
1739
|
+
followSlideEl.textContent = String(i + 1);
|
|
1740
|
+
if (changed) pushFrame();
|
|
1741
|
+
renderCounts();
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
if (followChan) {
|
|
1745
|
+
followChan.onmessage = function (e) {
|
|
1746
|
+
var m = e.data || {};
|
|
1747
|
+
if (m.id !== followSid) return;
|
|
1748
|
+
if (m.type === 'init') {
|
|
1749
|
+
deckNotes = m.notes || [];
|
|
1750
|
+
if (expectingDeck) {
|
|
1751
|
+
expectingDeck = false;
|
|
1752
|
+
following = true;
|
|
1753
|
+
}
|
|
1754
|
+
followChipEl.classList.toggle('hidden', !following);
|
|
1755
|
+
} else if (m.type === 'state') {
|
|
1756
|
+
if (following) followDeck(m.index);
|
|
1757
|
+
}
|
|
1758
|
+
};
|
|
1759
|
+
setInterval(function () {
|
|
1760
|
+
followChan.postMessage({ id: followSid, type: 'ready' });
|
|
1761
|
+
}, 2000);
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
followChipEl.addEventListener('click', function () {
|
|
1765
|
+
following = false;
|
|
1766
|
+
followChipEl.classList.add('hidden');
|
|
1767
|
+
});
|
|
1768
|
+
|
|
1612
1769
|
// ── Toasts ─────────────────────────────────────────────────────────────
|
|
1613
1770
|
function toast(message, kind, actionLabel, action) {
|
|
1614
1771
|
var el = document.createElement('div');
|
|
@@ -1701,7 +1858,7 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1701
1858
|
out.push('<span class="t-sep">' + esc(line) + '</span>');
|
|
1702
1859
|
continue;
|
|
1703
1860
|
}
|
|
1704
|
-
if (/^\\s
|
|
1861
|
+
if (/^\\s*<!--/.test(line)) {
|
|
1705
1862
|
out.push('<span class="t-note">' + esc(line) + '</span>');
|
|
1706
1863
|
continue;
|
|
1707
1864
|
}
|
|
@@ -1712,7 +1869,7 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1712
1869
|
out.push('<span class="t-h' + lvl + '">' + inlineTokens(line) + '</span>');
|
|
1713
1870
|
continue;
|
|
1714
1871
|
}
|
|
1715
|
-
if (/^\\s
|
|
1872
|
+
if (/^\\s*>/.test(line)) {
|
|
1716
1873
|
out.push('<span class="t-quote">' + inlineTokens(line) + '</span>');
|
|
1717
1874
|
continue;
|
|
1718
1875
|
}
|
|
@@ -1816,6 +1973,10 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1816
1973
|
elCount.textContent = 'slide ' + (n ? state.index + 1 : 0) + ' / ' + n;
|
|
1817
1974
|
$('btn-prev').disabled = state.index <= 0;
|
|
1818
1975
|
$('btn-next').disabled = state.index >= n - 1;
|
|
1976
|
+
$('overflow-badge').classList.toggle(
|
|
1977
|
+
'is-on',
|
|
1978
|
+
state.mode === 'single' && !!state.overflow[state.index]
|
|
1979
|
+
);
|
|
1819
1980
|
|
|
1820
1981
|
var note = state.notes[state.index];
|
|
1821
1982
|
if (note) { elNotesText.textContent = note; elNotes.classList.add('is-on'); }
|
|
@@ -1843,6 +2004,7 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1843
2004
|
}
|
|
1844
2005
|
}
|
|
1845
2006
|
post({ type: 'index', index: state.index });
|
|
2007
|
+
announceIndex(state.index);
|
|
1846
2008
|
renderCounts();
|
|
1847
2009
|
}
|
|
1848
2010
|
|
|
@@ -1850,12 +2012,6 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1850
2012
|
var before = src.value.slice(0, src.selectionStart);
|
|
1851
2013
|
var lines = before.split('\\n');
|
|
1852
2014
|
elPos.textContent = 'Ln ' + lines.length + ', Col ' + (lines[lines.length - 1].length + 1);
|
|
1853
|
-
var i = caretSlide();
|
|
1854
|
-
if (i !== state.index) {
|
|
1855
|
-
state.index = i;
|
|
1856
|
-
post({ type: 'index', index: i });
|
|
1857
|
-
renderCounts();
|
|
1858
|
-
}
|
|
1859
2015
|
}
|
|
1860
2016
|
|
|
1861
2017
|
// ── Parse round-trip: the server owns the Markdown, so what you see here
|
|
@@ -2081,58 +2237,8 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2081
2237
|
var bySnippetId = {};
|
|
2082
2238
|
D.snippets.forEach(function (s) { bySnippetId[s.id] = s; });
|
|
2083
2239
|
|
|
2084
|
-
function evalNudges() {
|
|
2085
|
-
|
|
2086
|
-
var ctx = docContext();
|
|
2087
|
-
for (var i = 0; i < NUDGE_RULES.length; i++) {
|
|
2088
|
-
var rule = NUDGE_RULES[i];
|
|
2089
|
-
if (state.dismissed[rule.id]) continue;
|
|
2090
|
-
var message = rule.test(ctx);
|
|
2091
|
-
if (!message) continue;
|
|
2092
|
-
showNudge(rule, message);
|
|
2093
|
-
return;
|
|
2094
|
-
}
|
|
2095
|
-
hideNudge();
|
|
2096
|
-
}
|
|
2097
|
-
|
|
2098
|
-
function showNudge(rule, message) {
|
|
2099
|
-
if (state.activeNudge && state.activeNudge.rule.id === rule.id &&
|
|
2100
|
-
state.activeNudge.message === message) return;
|
|
2101
|
-
if (state.activeNudge && Date.now() - state.nudgeShownAt < 2500) return;
|
|
2102
|
-
state.activeNudge = { rule: rule, message: message };
|
|
2103
|
-
state.nudgeShownAt = Date.now();
|
|
2104
|
-
elNudgeText.innerHTML = message;
|
|
2105
|
-
elNudgeDo.textContent = rule.action === 'download' ? 'download' : 'insert';
|
|
2106
|
-
elNudge.classList.add('is-on');
|
|
2107
|
-
}
|
|
2108
|
-
|
|
2109
|
-
function hideNudge() {
|
|
2110
|
-
state.activeNudge = null;
|
|
2111
|
-
elNudge.classList.remove('is-on');
|
|
2112
|
-
}
|
|
2113
|
-
|
|
2114
|
-
elNudgeDo.addEventListener('click', function () {
|
|
2115
|
-
var active = state.activeNudge;
|
|
2116
|
-
if (!active) return;
|
|
2117
|
-
if (active.rule.action) runAction(active.rule.action);
|
|
2118
|
-
else if (bySnippetId[active.rule.snippet]) insertSnippet(bySnippetId[active.rule.snippet]);
|
|
2119
|
-
hideNudge();
|
|
2120
|
-
});
|
|
2121
|
-
|
|
2122
|
-
elNudgeX.addEventListener('click', function () {
|
|
2123
|
-
var active = state.activeNudge;
|
|
2124
|
-
if (active) {
|
|
2125
|
-
state.dismissed[active.rule.id] = true;
|
|
2126
|
-
// A tip stays dismissed for good; a real problem may nag again next session.
|
|
2127
|
-
if (!active.rule.sticky) {
|
|
2128
|
-
var keep = Object.keys(state.dismissed).filter(function (id) {
|
|
2129
|
-
return !NUDGE_RULES.some(function (r) { return r.id === id && r.sticky; });
|
|
2130
|
-
});
|
|
2131
|
-
lsSet(K.nudge, JSON.stringify(keep));
|
|
2132
|
-
}
|
|
2133
|
-
}
|
|
2134
|
-
hideNudge();
|
|
2135
|
-
});
|
|
2240
|
+
function evalNudges() {}
|
|
2241
|
+
function hideNudge() {}
|
|
2136
2242
|
|
|
2137
2243
|
// ── Tips carousel ──────────────────────────────────────────────────────
|
|
2138
2244
|
var tipTimer = null;
|
|
@@ -2330,6 +2436,7 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2330
2436
|
guide.classList.remove('is-on');
|
|
2331
2437
|
$('library').classList.remove('is-on');
|
|
2332
2438
|
if ($('themes').classList.contains('is-on')) closeThemes(true);
|
|
2439
|
+
updateDocTitle();
|
|
2333
2440
|
if (!keepFocus) src.focus();
|
|
2334
2441
|
}
|
|
2335
2442
|
|
|
@@ -2372,7 +2479,7 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2372
2479
|
function exportHtml() {
|
|
2373
2480
|
if (isEmpty()) { toast('Nothing to export yet.', 'warn'); return; }
|
|
2374
2481
|
var builder = state.kind === 'html' ? buildHtmlDoc : buildDeck;
|
|
2375
|
-
builder(false)
|
|
2482
|
+
builder(false, true)
|
|
2376
2483
|
.then(function (data) { return fetch(data.path); })
|
|
2377
2484
|
.then(function (r) {
|
|
2378
2485
|
if (!r.ok) throw new Error('server said ' + r.status);
|
|
@@ -2403,7 +2510,16 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2403
2510
|
var pdfUrl = state.kind === 'html' ? '/__pdf-doc' : '/__pdf';
|
|
2404
2511
|
var pdfBody = state.kind === 'html'
|
|
2405
2512
|
? { html: srcHtml.value, title: $('docname').value }
|
|
2406
|
-
: {
|
|
2513
|
+
: {
|
|
2514
|
+
markdown: src.value,
|
|
2515
|
+
theme: state.theme,
|
|
2516
|
+
size: state.size,
|
|
2517
|
+
head: state.head,
|
|
2518
|
+
body: state.body,
|
|
2519
|
+
template: state.template,
|
|
2520
|
+
transition: state.transition,
|
|
2521
|
+
title: $('docname').value
|
|
2522
|
+
};
|
|
2407
2523
|
|
|
2408
2524
|
fetch(pdfUrl, {
|
|
2409
2525
|
method: 'POST',
|
|
@@ -2460,7 +2576,7 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2460
2576
|
});
|
|
2461
2577
|
}
|
|
2462
2578
|
|
|
2463
|
-
function buildDeck(forPrint) {
|
|
2579
|
+
function buildDeck(forPrint, standalone) {
|
|
2464
2580
|
return fetch('/__present', {
|
|
2465
2581
|
method: 'POST',
|
|
2466
2582
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -2470,8 +2586,11 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2470
2586
|
size: state.size,
|
|
2471
2587
|
head: state.head,
|
|
2472
2588
|
body: state.body,
|
|
2589
|
+
template: state.template,
|
|
2590
|
+
transition: state.transition,
|
|
2473
2591
|
title: $('docname').value,
|
|
2474
|
-
print: !!forPrint
|
|
2592
|
+
print: !!forPrint,
|
|
2593
|
+
standalone: !!standalone
|
|
2475
2594
|
})
|
|
2476
2595
|
}).then(function (r) {
|
|
2477
2596
|
if (!r.ok) throw new Error('server said ' + r.status);
|
|
@@ -2497,11 +2616,13 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2497
2616
|
|
|
2498
2617
|
function present() {
|
|
2499
2618
|
if (isEmpty()) { toast('Nothing to present yet.', 'warn'); return; }
|
|
2619
|
+
expectingDeck = true;
|
|
2500
2620
|
var builder = state.kind === 'html' ? buildHtmlDoc : buildDeck;
|
|
2501
2621
|
var tab = window.open('about:blank', '_blank');
|
|
2502
2622
|
builder(false)
|
|
2503
2623
|
.then(function (data) {
|
|
2504
|
-
var url = location.origin + data.path
|
|
2624
|
+
var url = location.origin + data.path +
|
|
2625
|
+
(data.path.indexOf('?') >= 0 ? '&' : '?') + 'ps=' + followSid;
|
|
2505
2626
|
if (tab) tab.location.replace(url);
|
|
2506
2627
|
else toast('Allow pop-ups to present in a new tab.', 'warn', 'present here', function () { location.href = url; });
|
|
2507
2628
|
})
|
|
@@ -2538,6 +2659,30 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2538
2659
|
paintSizeSeg();
|
|
2539
2660
|
}
|
|
2540
2661
|
|
|
2662
|
+
function setTemplate(next, remember) {
|
|
2663
|
+
var id = pickTemplate(next);
|
|
2664
|
+
state.template = id;
|
|
2665
|
+
document.documentElement.dataset.template = id;
|
|
2666
|
+
if (remember !== false) {
|
|
2667
|
+
lsSet(K.template, id);
|
|
2668
|
+
scheduleSave();
|
|
2669
|
+
}
|
|
2670
|
+
pushLook();
|
|
2671
|
+
paintTemplateMenu();
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
function setTransition(next, remember) {
|
|
2675
|
+
var id = pickTransition(next);
|
|
2676
|
+
state.transition = id;
|
|
2677
|
+
document.documentElement.dataset.transition = id;
|
|
2678
|
+
if (remember !== false) {
|
|
2679
|
+
lsSet(K.transition, id);
|
|
2680
|
+
scheduleSave();
|
|
2681
|
+
}
|
|
2682
|
+
pushLook();
|
|
2683
|
+
paintTemplateMenu();
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2541
2686
|
/**
|
|
2542
2687
|
* Heading and body faces are chosen separately, and either can be handed
|
|
2543
2688
|
* back to the theme by picking nothing. Passing null is what clears it.
|
|
@@ -2563,6 +2708,8 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2563
2708
|
root.dataset.theme = state.theme;
|
|
2564
2709
|
root.dataset.decor = decorOf(state.theme);
|
|
2565
2710
|
root.dataset.size = state.size;
|
|
2711
|
+
root.dataset.template = state.template;
|
|
2712
|
+
root.dataset.transition = state.transition;
|
|
2566
2713
|
if (state.head) root.dataset.head = state.head; else delete root.dataset.head;
|
|
2567
2714
|
if (state.body) root.dataset.body = state.body; else delete root.dataset.body;
|
|
2568
2715
|
}
|
|
@@ -2574,7 +2721,9 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2574
2721
|
theme: state.theme,
|
|
2575
2722
|
size: state.size,
|
|
2576
2723
|
head: state.head,
|
|
2577
|
-
body: state.body
|
|
2724
|
+
body: state.body,
|
|
2725
|
+
template: state.template,
|
|
2726
|
+
transition: state.transition
|
|
2578
2727
|
}, '*');
|
|
2579
2728
|
}
|
|
2580
2729
|
} catch (e) {}
|
|
@@ -2587,10 +2736,13 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2587
2736
|
theme: state.theme,
|
|
2588
2737
|
size: state.size,
|
|
2589
2738
|
head: state.head,
|
|
2590
|
-
body: state.body
|
|
2739
|
+
body: state.body,
|
|
2740
|
+
template: state.template,
|
|
2741
|
+
transition: state.transition
|
|
2591
2742
|
});
|
|
2592
2743
|
syncHtmlFrameTheme();
|
|
2593
2744
|
paintThemeButton();
|
|
2745
|
+
paintTemplateMenu();
|
|
2594
2746
|
}
|
|
2595
2747
|
|
|
2596
2748
|
function faceLabel(id) {
|
|
@@ -2654,6 +2806,45 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2654
2806
|
$('btn-theme').title = t ? t.label + ' — ' + t.blurb : 'Pick a theme';
|
|
2655
2807
|
}
|
|
2656
2808
|
|
|
2809
|
+
function optionRow(kind, item) {
|
|
2810
|
+
var b = document.createElement('button');
|
|
2811
|
+
b.dataset[kind] = item.id;
|
|
2812
|
+
var name = document.createElement('span');
|
|
2813
|
+
name.className = 'menu__name';
|
|
2814
|
+
name.textContent = item.label;
|
|
2815
|
+
var hint = document.createElement('span');
|
|
2816
|
+
hint.className = 'menu__hint';
|
|
2817
|
+
hint.textContent = item.blurb;
|
|
2818
|
+
b.appendChild(name);
|
|
2819
|
+
b.appendChild(hint);
|
|
2820
|
+
return b;
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2823
|
+
function buildTemplateMenu() {
|
|
2824
|
+
var templateHost = $('template-list');
|
|
2825
|
+
var transitionHost = $('transition-list');
|
|
2826
|
+
templateHost.innerHTML = '';
|
|
2827
|
+
transitionHost.innerHTML = '';
|
|
2828
|
+
TEMPLATES.forEach(function (item) { templateHost.appendChild(optionRow('template', item)); });
|
|
2829
|
+
TRANSITIONS.forEach(function (item) { transitionHost.appendChild(optionRow('transition', item)); });
|
|
2830
|
+
paintTemplateMenu();
|
|
2831
|
+
}
|
|
2832
|
+
|
|
2833
|
+
function paintTemplateMenu() {
|
|
2834
|
+
var menu = $('template-menu');
|
|
2835
|
+
if (!menu) return;
|
|
2836
|
+
Array.prototype.forEach.call(menu.querySelectorAll('[data-template]'), function (b) {
|
|
2837
|
+
b.classList.toggle('is-on', b.dataset.template === state.template);
|
|
2838
|
+
});
|
|
2839
|
+
Array.prototype.forEach.call(menu.querySelectorAll('[data-transition]'), function (b) {
|
|
2840
|
+
b.classList.toggle('is-on', b.dataset.transition === state.transition);
|
|
2841
|
+
});
|
|
2842
|
+
var current = TEMPLATE_BY_ID[state.template];
|
|
2843
|
+
$('template-label').textContent = current ? current.label : 'template';
|
|
2844
|
+
$('btn-template').title = (current ? current.blurb : 'Composition template') +
|
|
2845
|
+
' · transition ' + state.transition;
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2657
2848
|
// ── Theme picker ───────────────────────────────────────────────────────
|
|
2658
2849
|
function buildSizeSeg(hostId, showBlurb) {
|
|
2659
2850
|
var host = $(hostId);
|
|
@@ -2801,7 +2992,7 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2801
2992
|
// Hovering previews too, so the mouse gets the same instant feedback
|
|
2802
2993
|
// as the arrow keys.
|
|
2803
2994
|
card.addEventListener('mouseenter', function () { selectTheme(themeCards.indexOf(card)); });
|
|
2804
|
-
card.addEventListener('click', function () { commitTheme(); });
|
|
2995
|
+
card.addEventListener('click', function () { selectTheme(themeCards.indexOf(card)); commitTheme(); });
|
|
2805
2996
|
grid.appendChild(card);
|
|
2806
2997
|
themeCards.push(card);
|
|
2807
2998
|
});
|
|
@@ -2875,7 +3066,10 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2875
3066
|
state.deckId = id;
|
|
2876
3067
|
lsSet(K.current, id);
|
|
2877
3068
|
setDocKind(meta.kind || 'markdown');
|
|
3069
|
+
setTemplate(meta.template || state.template, true);
|
|
3070
|
+
setTransition(meta.transition || state.transition, true);
|
|
2878
3071
|
$('docname').value = meta.name;
|
|
3072
|
+
updateDocTitle();
|
|
2879
3073
|
updateDeckCount();
|
|
2880
3074
|
elSave.className = '';
|
|
2881
3075
|
elSave.textContent = '';
|
|
@@ -2996,7 +3190,13 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2996
3190
|
dupe.addEventListener('click', function (e) {
|
|
2997
3191
|
e.stopPropagation();
|
|
2998
3192
|
if (deck.id === state.deckId) { duplicateDeck(); renderLibrary(); return; }
|
|
2999
|
-
var id = createDeck(
|
|
3193
|
+
var id = createDeck(
|
|
3194
|
+
uniqueName(deck.name + ' copy'),
|
|
3195
|
+
readDeck(deck.id),
|
|
3196
|
+
deck.kind || 'markdown',
|
|
3197
|
+
deck.template || state.template,
|
|
3198
|
+
deck.transition || state.transition
|
|
3199
|
+
);
|
|
3000
3200
|
if (!id) { noRoom(); return; }
|
|
3001
3201
|
updateDeckCount();
|
|
3002
3202
|
renderLibrary();
|
|
@@ -3029,6 +3229,7 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
3029
3229
|
for (var i = 0; i < list.length; i++) if (list[i].id === state.deckId) libSel = i;
|
|
3030
3230
|
renderLibrary();
|
|
3031
3231
|
$('library').classList.add('is-on');
|
|
3232
|
+
updateDocTitle();
|
|
3032
3233
|
}
|
|
3033
3234
|
|
|
3034
3235
|
$('lib-new').addEventListener('click', function () { closeOverlays(); showStartScreen(); });
|
|
@@ -3038,13 +3239,56 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
3038
3239
|
// ── Start screen ─────────────────────────────────────────────────────
|
|
3039
3240
|
// Shown on a true first run, and whenever "new" is chosen explicitly from
|
|
3040
3241
|
// the library — never on an ordinary launch that has a deck to resume.
|
|
3242
|
+
var startTemplate = state.template;
|
|
3243
|
+
var startTransition = state.transition;
|
|
3244
|
+
|
|
3245
|
+
function paintStartOptions() {
|
|
3246
|
+
Array.prototype.forEach.call($('start-template-list').querySelectorAll('[data-template]'), function (b) {
|
|
3247
|
+
b.classList.toggle('is-on', b.dataset.template === startTemplate);
|
|
3248
|
+
});
|
|
3249
|
+
Array.prototype.forEach.call($('start-transition-list').querySelectorAll('[data-transition]'), function (b) {
|
|
3250
|
+
b.classList.toggle('is-on', b.dataset.transition === startTransition);
|
|
3251
|
+
});
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
function buildStartOptions() {
|
|
3255
|
+
var templateHost = $('start-template-list');
|
|
3256
|
+
var transitionHost = $('start-transition-list');
|
|
3257
|
+
templateHost.innerHTML = '';
|
|
3258
|
+
transitionHost.innerHTML = '';
|
|
3259
|
+
TEMPLATES.forEach(function (item) {
|
|
3260
|
+
var b = document.createElement('button');
|
|
3261
|
+
b.className = 'start-choice';
|
|
3262
|
+
b.dataset.template = item.id;
|
|
3263
|
+
b.textContent = item.label;
|
|
3264
|
+
b.title = item.blurb;
|
|
3265
|
+
b.addEventListener('click', function () { startTemplate = item.id; paintStartOptions(); });
|
|
3266
|
+
templateHost.appendChild(b);
|
|
3267
|
+
});
|
|
3268
|
+
TRANSITIONS.forEach(function (item) {
|
|
3269
|
+
var b = document.createElement('button');
|
|
3270
|
+
b.className = 'start-choice';
|
|
3271
|
+
b.dataset.transition = item.id;
|
|
3272
|
+
b.textContent = item.label;
|
|
3273
|
+
b.title = item.blurb;
|
|
3274
|
+
b.addEventListener('click', function () { startTransition = item.id; paintStartOptions(); });
|
|
3275
|
+
transitionHost.appendChild(b);
|
|
3276
|
+
});
|
|
3277
|
+
paintStartOptions();
|
|
3278
|
+
}
|
|
3279
|
+
|
|
3041
3280
|
function showStartScreen() {
|
|
3281
|
+
startTemplate = state.template;
|
|
3282
|
+
startTransition = state.transition;
|
|
3283
|
+
paintStartOptions();
|
|
3042
3284
|
$('start-lib').classList.toggle('is-disabled', !loadIndex().length);
|
|
3043
3285
|
$('screen-start').classList.add('is-on');
|
|
3286
|
+
updateDocTitle();
|
|
3044
3287
|
}
|
|
3045
3288
|
|
|
3046
3289
|
function hideStartScreenSilently() {
|
|
3047
3290
|
$('screen-start').classList.remove('is-on');
|
|
3291
|
+
updateDocTitle();
|
|
3048
3292
|
}
|
|
3049
3293
|
|
|
3050
3294
|
/** Escape/backdrop close. With nothing open yet, that would be a dead end,
|
|
@@ -3055,7 +3299,9 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
3055
3299
|
}
|
|
3056
3300
|
|
|
3057
3301
|
function startNewMarkdown() {
|
|
3058
|
-
|
|
3302
|
+
setTemplate(startTemplate, true);
|
|
3303
|
+
setTransition(startTransition, true);
|
|
3304
|
+
var id = createDeck(uniqueName('untitled'), D.welcome, 'markdown', startTemplate, startTransition);
|
|
3059
3305
|
hideStartScreenSilently();
|
|
3060
3306
|
if (!id) {
|
|
3061
3307
|
// Storage refuses even a first write: use the deck unsaved rather than
|
|
@@ -3091,7 +3337,7 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
3091
3337
|
openLibrary();
|
|
3092
3338
|
}
|
|
3093
3339
|
|
|
3094
|
-
/** Fetches a public
|
|
3340
|
+
/** Fetches a public URL server-side (sidesteps CORS) and opens it as a new Markdown deck or HTML doc. */
|
|
3095
3341
|
function loadHtmlUrl() {
|
|
3096
3342
|
var input = $('start-html-url');
|
|
3097
3343
|
var raw = input.value.trim();
|
|
@@ -3118,12 +3364,20 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
3118
3364
|
.then(function (body) {
|
|
3119
3365
|
saveNow();
|
|
3120
3366
|
var name = uniqueName(body.title || url.hostname);
|
|
3121
|
-
var
|
|
3367
|
+
var kind = body.kind || (body.markdown ? 'markdown' : 'html');
|
|
3368
|
+
var content = kind === 'markdown' ? (body.markdown || body.content || '') : (body.html || body.content || '');
|
|
3369
|
+
var id = createDeck(
|
|
3370
|
+
name,
|
|
3371
|
+
content,
|
|
3372
|
+
kind,
|
|
3373
|
+
kind === 'markdown' ? startTemplate : state.template,
|
|
3374
|
+
kind === 'markdown' ? startTransition : state.transition
|
|
3375
|
+
);
|
|
3122
3376
|
if (!id) { noRoom(); return; }
|
|
3123
3377
|
input.value = '';
|
|
3124
3378
|
hideStartScreenSilently();
|
|
3125
3379
|
openDeck(id);
|
|
3126
|
-
toast('Loaded ' +
|
|
3380
|
+
toast('Loaded ' + (kind === 'markdown' ? 'Markdown deck' : 'HTML doc') + ' from ' + url.hostname);
|
|
3127
3381
|
})
|
|
3128
3382
|
.catch(function (err) { toast('Could not load URL: ' + err.message, 'err'); })
|
|
3129
3383
|
.then(function () { btn.disabled = false; });
|
|
@@ -3148,6 +3402,7 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
3148
3402
|
else if (name === 'grid') setMode(state.mode === 'grid' ? 'single' : 'grid');
|
|
3149
3403
|
else if (name === 'theme') openThemes();
|
|
3150
3404
|
else if (name === 'guide') { guide.classList.add('is-on'); }
|
|
3405
|
+
else if (name === 'palette') openPalette();
|
|
3151
3406
|
else if (name === 'decks') openLibrary();
|
|
3152
3407
|
else if (name === 'new') newDeck();
|
|
3153
3408
|
else if (name === 'duplicate') duplicateDeck();
|
|
@@ -3158,6 +3413,8 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
3158
3413
|
var parseTimer = null;
|
|
3159
3414
|
|
|
3160
3415
|
function onInput() {
|
|
3416
|
+
// Typing takes the editor back from the deck.
|
|
3417
|
+
if (following) { following = false; followChipEl.classList.add('hidden'); }
|
|
3161
3418
|
paint();
|
|
3162
3419
|
syncScroll();
|
|
3163
3420
|
updateCaretUi();
|
|
@@ -3179,26 +3436,6 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
3179
3436
|
e.preventDefault();
|
|
3180
3437
|
typeText(' ');
|
|
3181
3438
|
onInput();
|
|
3182
|
-
return;
|
|
3183
|
-
}
|
|
3184
|
-
// Continue the list you are in.
|
|
3185
|
-
if (e.key === 'Enter' && !e.shiftKey && !e.metaKey && !e.ctrlKey) {
|
|
3186
|
-
var upto = src.value.slice(0, src.selectionStart);
|
|
3187
|
-
var line = upto.slice(upto.lastIndexOf('\\n') + 1);
|
|
3188
|
-
var m = line.match(/^(\\s*)([-*+]|(\\d+)[.)])(\\s+)(.*)$/);
|
|
3189
|
-
if (m && src.selectionStart === src.selectionEnd) {
|
|
3190
|
-
e.preventDefault();
|
|
3191
|
-
if (!m[5]) {
|
|
3192
|
-
// Empty item: end the list instead of nesting further.
|
|
3193
|
-
var start = src.selectionStart - line.length;
|
|
3194
|
-
src.setSelectionRange(start, src.selectionStart);
|
|
3195
|
-
typeText('\\n');
|
|
3196
|
-
} else {
|
|
3197
|
-
var marker = m[3] ? (parseInt(m[3], 10) + 1) + m[2].slice(String(m[3]).length) : m[2];
|
|
3198
|
-
typeText('\\n' + m[1] + marker + m[4]);
|
|
3199
|
-
}
|
|
3200
|
-
onInput();
|
|
3201
|
-
}
|
|
3202
3439
|
}
|
|
3203
3440
|
});
|
|
3204
3441
|
|
|
@@ -3261,13 +3498,19 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
3261
3498
|
});
|
|
3262
3499
|
|
|
3263
3500
|
/** An opened file lands as a new deck, so nothing in the library is lost. */
|
|
3264
|
-
function loadMarkdownFile(file) {
|
|
3501
|
+
function loadMarkdownFile(file, chosenTemplate, chosenTransition) {
|
|
3265
3502
|
var fr = new FileReader();
|
|
3266
3503
|
fr.onload = function () {
|
|
3267
3504
|
saveNow();
|
|
3268
3505
|
var markdown = String(fr.result).replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');
|
|
3269
3506
|
var name = uniqueName(file.name.replace(/\\.(md|markdown|txt)$/i, '') || 'untitled');
|
|
3270
|
-
var id = createDeck(
|
|
3507
|
+
var id = createDeck(
|
|
3508
|
+
name,
|
|
3509
|
+
markdown,
|
|
3510
|
+
'markdown',
|
|
3511
|
+
chosenTemplate || state.template,
|
|
3512
|
+
chosenTransition || state.transition
|
|
3513
|
+
);
|
|
3271
3514
|
if (!id) { noRoom(); return; }
|
|
3272
3515
|
hideStartScreenSilently();
|
|
3273
3516
|
openDeck(id);
|
|
@@ -3297,14 +3540,24 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
3297
3540
|
$('file-any').addEventListener('change', function (e) {
|
|
3298
3541
|
var f = e.target.files[0];
|
|
3299
3542
|
if (f) {
|
|
3300
|
-
|
|
3543
|
+
var fromStart = $('screen-start').classList.contains('is-on');
|
|
3544
|
+
if (/\\.(md|markdown|txt)$/i.test(f.name)) {
|
|
3545
|
+
loadMarkdownFile(
|
|
3546
|
+
f,
|
|
3547
|
+
fromStart ? startTemplate : state.template,
|
|
3548
|
+
fromStart ? startTransition : state.transition
|
|
3549
|
+
);
|
|
3550
|
+
}
|
|
3301
3551
|
else if (/\\.html?$/i.test(f.name)) loadHtmlFile(f);
|
|
3302
3552
|
else toast('Unrecognized file type.', 'warn');
|
|
3303
3553
|
}
|
|
3304
3554
|
e.target.value = '';
|
|
3305
3555
|
});
|
|
3306
3556
|
|
|
3307
|
-
$('docname').addEventListener('input',
|
|
3557
|
+
$('docname').addEventListener('input', function () {
|
|
3558
|
+
updateDocTitle();
|
|
3559
|
+
scheduleSave();
|
|
3560
|
+
});
|
|
3308
3561
|
|
|
3309
3562
|
$('btn-guide').addEventListener('click', function () { runAction('guide'); });
|
|
3310
3563
|
$('btn-palette').addEventListener('click', openPalette);
|
|
@@ -3380,6 +3633,13 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
3380
3633
|
setFont(btn.dataset.slot, btn.dataset.font || null, true);
|
|
3381
3634
|
}, 'button[data-slot]');
|
|
3382
3635
|
|
|
3636
|
+
// Template and transition compose, so the menu remains open while either
|
|
3637
|
+
// choice is changed and the existing Markdown reflows immediately.
|
|
3638
|
+
bindMenu('btn-template', 'template-menu', function (btn) {
|
|
3639
|
+
if (btn.dataset.template) setTemplate(btn.dataset.template, true);
|
|
3640
|
+
if (btn.dataset.transition) setTransition(btn.dataset.transition, true);
|
|
3641
|
+
}, 'button[data-template], button[data-transition]');
|
|
3642
|
+
|
|
3383
3643
|
document.addEventListener('click', function (e) {
|
|
3384
3644
|
if (openMenu && !openMenu.contains(e.target)) closeMenu();
|
|
3385
3645
|
});
|
|
@@ -3415,12 +3675,25 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
3415
3675
|
themeCards[next].scrollIntoView({ block: 'nearest' });
|
|
3416
3676
|
}, true);
|
|
3417
3677
|
$('btn-present').addEventListener('click', present);
|
|
3418
|
-
$('btn-prev').addEventListener('click', function () { setIndex(state.index - 1,
|
|
3419
|
-
$('btn-next').addEventListener('click', function () { setIndex(state.index + 1,
|
|
3678
|
+
$('btn-prev').addEventListener('click', function () { setIndex(state.index - 1, false); });
|
|
3679
|
+
$('btn-next').addEventListener('click', function () { setIndex(state.index + 1, false); });
|
|
3420
3680
|
$('seg-single').addEventListener('click', function () { setMode('single'); });
|
|
3421
3681
|
$('seg-grid').addEventListener('click', function () { setMode('grid'); });
|
|
3422
3682
|
|
|
3683
|
+
$('pane-prev').addEventListener('mousedown', function () {
|
|
3684
|
+
if (document.activeElement && isEditingText()) {
|
|
3685
|
+
document.activeElement.blur();
|
|
3686
|
+
}
|
|
3687
|
+
});
|
|
3688
|
+
|
|
3423
3689
|
// ── Global keys ────────────────────────────────────────────────────────
|
|
3690
|
+
function isEditingText() {
|
|
3691
|
+
var el = document.activeElement;
|
|
3692
|
+
if (!el) return false;
|
|
3693
|
+
var tag = el.tagName ? el.tagName.toLowerCase() : '';
|
|
3694
|
+
return tag === 'textarea' || tag === 'input' || el.isContentEditable;
|
|
3695
|
+
}
|
|
3696
|
+
|
|
3424
3697
|
document.addEventListener('keydown', function (e) {
|
|
3425
3698
|
var mod = e.metaKey || e.ctrlKey;
|
|
3426
3699
|
|
|
@@ -3458,31 +3731,59 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
3458
3731
|
}
|
|
3459
3732
|
return;
|
|
3460
3733
|
}
|
|
3461
|
-
if (!mod) return;
|
|
3462
|
-
|
|
3463
|
-
var key = e.key.toLowerCase();
|
|
3464
|
-
|
|
3465
|
-
var md = state.kind === 'markdown';
|
|
3466
|
-
|
|
3467
|
-
if (key === 'k' && !e.shiftKey && md) { e.preventDefault(); openPalette(); }
|
|
3468
|
-
else if (key === 'o') { e.preventDefault(); openLibrary(); }
|
|
3469
|
-
else if (key === '/' && md) { e.preventDefault(); runAction('guide'); }
|
|
3470
|
-
else if (key === 's' && e.shiftKey) { e.preventDefault(); exportPdf(); }
|
|
3471
|
-
else if (key === 's') { e.preventDefault(); saveNow(); download(); }
|
|
3472
|
-
else if (key === 'enter') { e.preventDefault(); present(); }
|
|
3473
|
-
else if (key === 'd' && md) { e.preventDefault(); insertSnippet(bySnippetId['slide-break']); }
|
|
3474
|
-
else if (key === 'b' && md) { e.preventDefault(); insertSnippet(bySnippetId.bold); }
|
|
3475
|
-
else if (key === 'i' && md) { e.preventDefault(); insertSnippet(bySnippetId.italic); }
|
|
3476
|
-
else if (key === 'e' && md) { e.preventDefault(); insertSnippet(bySnippetId['inline-code']); }
|
|
3477
|
-
else if (key === 'g' && md) { e.preventDefault(); runAction('grid'); }
|
|
3478
|
-
else if (key === 'l' && e.shiftKey) { e.preventDefault(); runAction('theme'); }
|
|
3479
|
-
});
|
|
3480
3734
|
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3735
|
+
if (mod) {
|
|
3736
|
+
var key = e.key.toLowerCase();
|
|
3737
|
+
var md = state.kind === 'markdown';
|
|
3738
|
+
|
|
3739
|
+
if (key === 'k' && !e.shiftKey && md) { e.preventDefault(); openPalette(); }
|
|
3740
|
+
else if (key === 'o') { e.preventDefault(); openLibrary(); }
|
|
3741
|
+
else if (key === '/' && md) { e.preventDefault(); runAction('guide'); }
|
|
3742
|
+
else if (key === 'p' || (key === 's' && e.shiftKey)) { e.preventDefault(); exportPdf(); }
|
|
3743
|
+
else if (key === 's') { e.preventDefault(); saveNow(); download(); }
|
|
3744
|
+
else if (key === 'enter') { e.preventDefault(); present(); }
|
|
3745
|
+
else if (key === 'd' && md) { e.preventDefault(); insertSnippet(bySnippetId['slide-break']); }
|
|
3746
|
+
else if (key === 'b' && md) { e.preventDefault(); insertSnippet(bySnippetId.bold); }
|
|
3747
|
+
else if (key === 'i' && md) { e.preventDefault(); insertSnippet(bySnippetId.italic); }
|
|
3748
|
+
else if (key === 'e' && md) { e.preventDefault(); insertSnippet(bySnippetId['inline-code']); }
|
|
3749
|
+
else if (key === 'g' && md) { e.preventDefault(); runAction('grid'); }
|
|
3750
|
+
else if (key === 'l' && e.shiftKey) { e.preventDefault(); runAction('theme'); }
|
|
3751
|
+
return;
|
|
3752
|
+
}
|
|
3753
|
+
|
|
3754
|
+
if (palette.classList.contains('is-on') || guide.classList.contains('is-on') ||
|
|
3755
|
+
$('library').classList.contains('is-on') || menuIsOpen()) {
|
|
3756
|
+
return;
|
|
3757
|
+
}
|
|
3758
|
+
|
|
3759
|
+
// Alt + arrows/page keys hops between slides (works anywhere, including inside the editor).
|
|
3760
|
+
if (e.altKey) {
|
|
3761
|
+
if (e.key === 'ArrowDown' || e.key === 'ArrowRight' || e.key === 'PageDown') {
|
|
3762
|
+
e.preventDefault();
|
|
3763
|
+
setIndex(state.index + 1, false);
|
|
3764
|
+
} else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft' || e.key === 'PageUp') {
|
|
3765
|
+
e.preventDefault();
|
|
3766
|
+
setIndex(state.index - 1, false);
|
|
3767
|
+
}
|
|
3768
|
+
return;
|
|
3769
|
+
}
|
|
3770
|
+
|
|
3771
|
+
// Direct arrow keys / page keys when not typing into a text field.
|
|
3772
|
+
if (!isEditingText()) {
|
|
3773
|
+
if (e.key === 'ArrowRight' || e.key === 'ArrowDown' || e.key === ' ' || e.key === 'PageDown') {
|
|
3774
|
+
e.preventDefault();
|
|
3775
|
+
setIndex(state.index + 1, false);
|
|
3776
|
+
} else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp' || e.key === 'PageUp') {
|
|
3777
|
+
e.preventDefault();
|
|
3778
|
+
setIndex(state.index - 1, false);
|
|
3779
|
+
} else if (e.key === 'Home') {
|
|
3780
|
+
e.preventDefault();
|
|
3781
|
+
setIndex(0, false);
|
|
3782
|
+
} else if (e.key === 'End') {
|
|
3783
|
+
e.preventDefault();
|
|
3784
|
+
setIndex(state.slides.length - 1, false);
|
|
3785
|
+
}
|
|
3786
|
+
}
|
|
3486
3787
|
});
|
|
3487
3788
|
|
|
3488
3789
|
// ── Split pane ─────────────────────────────────────────────────────────
|
|
@@ -3539,9 +3840,16 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
3539
3840
|
} else if (m.type === 'goto') {
|
|
3540
3841
|
setMode('single');
|
|
3541
3842
|
setIndex(m.index, true);
|
|
3843
|
+
} else if (m.type === 'nav') {
|
|
3844
|
+
setIndex(state.index + m.delta, false);
|
|
3845
|
+
} else if (m.type === 'index-select') {
|
|
3846
|
+
setIndex(m.index, false);
|
|
3847
|
+
} else if (m.type === 'action') {
|
|
3848
|
+
runAction(m.action);
|
|
3542
3849
|
} else if (m.type === 'overflow') {
|
|
3543
3850
|
var had = !!state.overflow[m.index];
|
|
3544
3851
|
state.overflow[m.index] = m.overflow;
|
|
3852
|
+
renderCounts();
|
|
3545
3853
|
if (had !== m.overflow) evalNudges();
|
|
3546
3854
|
}
|
|
3547
3855
|
});
|
|
@@ -3559,8 +3867,12 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
3559
3867
|
|
|
3560
3868
|
document.documentElement.dataset.theme = state.theme;
|
|
3561
3869
|
document.documentElement.dataset.decor = THEME_BY_ID[state.theme].decor;
|
|
3870
|
+
document.documentElement.dataset.template = state.template;
|
|
3871
|
+
document.documentElement.dataset.transition = state.transition;
|
|
3562
3872
|
buildSizeSeg('topbar-size', false);
|
|
3563
3873
|
buildFontMenu();
|
|
3874
|
+
buildTemplateMenu();
|
|
3875
|
+
buildStartOptions();
|
|
3564
3876
|
paintSizeSeg();
|
|
3565
3877
|
paintThemeButton();
|
|
3566
3878
|
$('seg-single').classList.toggle('is-on', state.mode === 'single');
|