deckrun 1.4.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 +205 -32
- package/dist/editor-content.js +85 -0
- package/dist/editor.js +401 -23
- package/dist/fragments.js +71 -0
- package/dist/generate.js +1032 -37
- package/dist/index.js +186 -20
- 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 +45 -7
- package/dist/rich-content.js +170 -0
- package/dist/themes.js +2 -0
- 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,11 +26,13 @@ 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">
|
|
@@ -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; }
|
|
@@ -341,7 +349,8 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
341
349
|
outline: none;
|
|
342
350
|
}
|
|
343
351
|
|
|
344
|
-
#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; }
|
|
345
354
|
|
|
346
355
|
/* Markdown tokens — color only to guarantee pixel-identical alignment */
|
|
347
356
|
.t-h1 { color: var(--accent); }
|
|
@@ -394,6 +403,22 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
394
403
|
color: var(--overlay1);
|
|
395
404
|
}
|
|
396
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
|
+
|
|
397
422
|
/* ── Dropdown menu ────────────────────────────────────────────────────── */
|
|
398
423
|
.menu { position: relative; display: inline-flex; }
|
|
399
424
|
.menu__chev { font-size: 9px; color: var(--overlay0); }
|
|
@@ -443,6 +468,21 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
443
468
|
white-space: nowrap;
|
|
444
469
|
}
|
|
445
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
|
+
|
|
446
486
|
.seg { display: flex; border: 1px solid var(--surface0); border-radius: 7px; overflow: hidden; }
|
|
447
487
|
.seg button { font-size: 11px; padding: 3px 10px; color: var(--overlay1); }
|
|
448
488
|
.seg button.is-on { background: var(--surface0); color: var(--text); }
|
|
@@ -529,6 +569,19 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
529
569
|
#tipbar button { color: var(--overlay0); padding: 0 3px; }
|
|
530
570
|
#tipbar button:hover { color: var(--text); }
|
|
531
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
|
+
|
|
532
585
|
/* ── Command palette ──────────────────────────────────────────────────── */
|
|
533
586
|
#palette, #guide, #library { position: fixed; inset: 0; z-index: 40; display: none; }
|
|
534
587
|
#palette.is-on, #guide.is-on, #library.is-on { display: block; }
|
|
@@ -1014,7 +1067,8 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1014
1067
|
}
|
|
1015
1068
|
|
|
1016
1069
|
[data-doc-kind="html"] #btn-guide,
|
|
1017
|
-
[data-doc-kind="html"] #btn-palette
|
|
1070
|
+
[data-doc-kind="html"] #btn-palette,
|
|
1071
|
+
[data-doc-kind="html"] #template-wrap {
|
|
1018
1072
|
display: none !important;
|
|
1019
1073
|
}
|
|
1020
1074
|
|
|
@@ -1067,6 +1121,30 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1067
1121
|
}
|
|
1068
1122
|
.start-card__acts button:hover { border-color: var(--accent); color: var(--text); }
|
|
1069
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
|
+
|
|
1070
1148
|
.start-card__url { display: flex; gap: 6px; flex: 1 1 auto; min-width: 0; }
|
|
1071
1149
|
.start-card__url input {
|
|
1072
1150
|
flex: 1 1 auto;
|
|
@@ -1122,6 +1200,21 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1122
1200
|
</button>
|
|
1123
1201
|
</div>
|
|
1124
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>
|
|
1125
1218
|
<button class="btn" id="btn-theme" title="Pick a theme">
|
|
1126
1219
|
<span class="th-dot" id="theme-dot"></span>
|
|
1127
1220
|
<span id="theme-label">theme</span>
|
|
@@ -1167,6 +1260,7 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1167
1260
|
<button class="step" id="btn-prev" title="Previous slide">←</button>
|
|
1168
1261
|
<span id="prev-count">slide 0 / 0</span>
|
|
1169
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>
|
|
1170
1264
|
<span class="spacer"></span>
|
|
1171
1265
|
<span id="prev-scale"></span>
|
|
1172
1266
|
<div class="seg">
|
|
@@ -1192,6 +1286,7 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1192
1286
|
<span id="words">0 words</span>
|
|
1193
1287
|
<span id="chip-slides">0 slides</span>
|
|
1194
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>
|
|
1195
1290
|
<span id="tipbar">
|
|
1196
1291
|
<span class="tag">tip</span>
|
|
1197
1292
|
<span id="tip-text"></span>
|
|
@@ -1275,6 +1370,16 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1275
1370
|
<button id="start-md-blank">start blank</button>
|
|
1276
1371
|
<button id="start-md-upload">upload .md</button>
|
|
1277
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>
|
|
1278
1383
|
</div>
|
|
1279
1384
|
<div class="start-card" id="start-html-card">
|
|
1280
1385
|
<span class="start-card__title">HTML document</span>
|
|
@@ -1298,7 +1403,7 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1298
1403
|
<div id="toasts"></div>
|
|
1299
1404
|
<input type="file" id="file-any" accept=".md,.markdown,text/markdown,text/plain,.html,.htm,text/html" hidden>
|
|
1300
1405
|
|
|
1301
|
-
<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>
|
|
1302
1407
|
<script>
|
|
1303
1408
|
(function () {
|
|
1304
1409
|
'use strict';
|
|
@@ -1315,6 +1420,8 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1315
1420
|
size: 'deckrun.size.v1',
|
|
1316
1421
|
head: 'deckrun.font.head.v1',
|
|
1317
1422
|
body: 'deckrun.font.body.v1',
|
|
1423
|
+
template:'deckrun.template.v1',
|
|
1424
|
+
transition:'deckrun.transition.v1',
|
|
1318
1425
|
split: 'deckrun.split.v1',
|
|
1319
1426
|
mode: 'deckrun.mode.v1',
|
|
1320
1427
|
nudge: 'deckrun.nudges.v1',
|
|
@@ -1399,11 +1506,20 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1399
1506
|
}
|
|
1400
1507
|
|
|
1401
1508
|
/** Returns the new deck's id, or null if this browser refused to store it. */
|
|
1402
|
-
function createDeck(name, content, kind) {
|
|
1509
|
+
function createDeck(name, content, kind, template, transition) {
|
|
1403
1510
|
var id = newDeckId();
|
|
1404
1511
|
if (!lsSet(K.deck + id, content)) return null;
|
|
1405
1512
|
var list = loadIndex();
|
|
1406
|
-
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
|
+
});
|
|
1407
1523
|
if (!saveIndex(list)) { lsDel(K.deck + id); return null; }
|
|
1408
1524
|
return id;
|
|
1409
1525
|
}
|
|
@@ -1447,6 +1563,8 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1447
1563
|
if (list[i].id === state.deckId) {
|
|
1448
1564
|
list[i].name = $('docname').value.trim() || 'untitled';
|
|
1449
1565
|
list[i].kind = state.kind;
|
|
1566
|
+
list[i].template = state.template;
|
|
1567
|
+
list[i].transition = state.transition;
|
|
1450
1568
|
if (state.kind === 'html') {
|
|
1451
1569
|
list[i].slides = 0;
|
|
1452
1570
|
list[i].chars = srcHtml.value.length;
|
|
@@ -1513,6 +1631,22 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1513
1631
|
return SIZE_BY_ID[id] ? id : (SIZE_BY_ID[D.size] ? D.size : 'm');
|
|
1514
1632
|
}
|
|
1515
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
|
+
|
|
1516
1650
|
var FACES = D.faces;
|
|
1517
1651
|
var FACE_BY_ID = {};
|
|
1518
1652
|
FACES.forEach(function (f) { FACE_BY_ID[f.id] = f; });
|
|
@@ -1546,6 +1680,8 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1546
1680
|
size: pickSize(lsGet(K.size, D.size)),
|
|
1547
1681
|
head: pickFont(lsGet(K.head, D.fonts.head)),
|
|
1548
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)),
|
|
1549
1685
|
frameReady: false,
|
|
1550
1686
|
overflow: {},
|
|
1551
1687
|
dismissed: {},
|
|
@@ -1561,6 +1697,75 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1561
1697
|
if (saved && saved.length) saved.forEach(function (id) { state.dismissed[id] = true; });
|
|
1562
1698
|
} catch (e) {}
|
|
1563
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
|
+
|
|
1564
1769
|
// ── Toasts ─────────────────────────────────────────────────────────────
|
|
1565
1770
|
function toast(message, kind, actionLabel, action) {
|
|
1566
1771
|
var el = document.createElement('div');
|
|
@@ -1768,6 +1973,10 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1768
1973
|
elCount.textContent = 'slide ' + (n ? state.index + 1 : 0) + ' / ' + n;
|
|
1769
1974
|
$('btn-prev').disabled = state.index <= 0;
|
|
1770
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
|
+
);
|
|
1771
1980
|
|
|
1772
1981
|
var note = state.notes[state.index];
|
|
1773
1982
|
if (note) { elNotesText.textContent = note; elNotes.classList.add('is-on'); }
|
|
@@ -1795,6 +2004,7 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
1795
2004
|
}
|
|
1796
2005
|
}
|
|
1797
2006
|
post({ type: 'index', index: state.index });
|
|
2007
|
+
announceIndex(state.index);
|
|
1798
2008
|
renderCounts();
|
|
1799
2009
|
}
|
|
1800
2010
|
|
|
@@ -2269,7 +2479,7 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2269
2479
|
function exportHtml() {
|
|
2270
2480
|
if (isEmpty()) { toast('Nothing to export yet.', 'warn'); return; }
|
|
2271
2481
|
var builder = state.kind === 'html' ? buildHtmlDoc : buildDeck;
|
|
2272
|
-
builder(false)
|
|
2482
|
+
builder(false, true)
|
|
2273
2483
|
.then(function (data) { return fetch(data.path); })
|
|
2274
2484
|
.then(function (r) {
|
|
2275
2485
|
if (!r.ok) throw new Error('server said ' + r.status);
|
|
@@ -2300,7 +2510,16 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2300
2510
|
var pdfUrl = state.kind === 'html' ? '/__pdf-doc' : '/__pdf';
|
|
2301
2511
|
var pdfBody = state.kind === 'html'
|
|
2302
2512
|
? { html: srcHtml.value, title: $('docname').value }
|
|
2303
|
-
: {
|
|
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
|
+
};
|
|
2304
2523
|
|
|
2305
2524
|
fetch(pdfUrl, {
|
|
2306
2525
|
method: 'POST',
|
|
@@ -2357,7 +2576,7 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2357
2576
|
});
|
|
2358
2577
|
}
|
|
2359
2578
|
|
|
2360
|
-
function buildDeck(forPrint) {
|
|
2579
|
+
function buildDeck(forPrint, standalone) {
|
|
2361
2580
|
return fetch('/__present', {
|
|
2362
2581
|
method: 'POST',
|
|
2363
2582
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -2367,8 +2586,11 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2367
2586
|
size: state.size,
|
|
2368
2587
|
head: state.head,
|
|
2369
2588
|
body: state.body,
|
|
2589
|
+
template: state.template,
|
|
2590
|
+
transition: state.transition,
|
|
2370
2591
|
title: $('docname').value,
|
|
2371
|
-
print: !!forPrint
|
|
2592
|
+
print: !!forPrint,
|
|
2593
|
+
standalone: !!standalone
|
|
2372
2594
|
})
|
|
2373
2595
|
}).then(function (r) {
|
|
2374
2596
|
if (!r.ok) throw new Error('server said ' + r.status);
|
|
@@ -2394,11 +2616,13 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2394
2616
|
|
|
2395
2617
|
function present() {
|
|
2396
2618
|
if (isEmpty()) { toast('Nothing to present yet.', 'warn'); return; }
|
|
2619
|
+
expectingDeck = true;
|
|
2397
2620
|
var builder = state.kind === 'html' ? buildHtmlDoc : buildDeck;
|
|
2398
2621
|
var tab = window.open('about:blank', '_blank');
|
|
2399
2622
|
builder(false)
|
|
2400
2623
|
.then(function (data) {
|
|
2401
|
-
var url = location.origin + data.path
|
|
2624
|
+
var url = location.origin + data.path +
|
|
2625
|
+
(data.path.indexOf('?') >= 0 ? '&' : '?') + 'ps=' + followSid;
|
|
2402
2626
|
if (tab) tab.location.replace(url);
|
|
2403
2627
|
else toast('Allow pop-ups to present in a new tab.', 'warn', 'present here', function () { location.href = url; });
|
|
2404
2628
|
})
|
|
@@ -2435,6 +2659,30 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2435
2659
|
paintSizeSeg();
|
|
2436
2660
|
}
|
|
2437
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
|
+
|
|
2438
2686
|
/**
|
|
2439
2687
|
* Heading and body faces are chosen separately, and either can be handed
|
|
2440
2688
|
* back to the theme by picking nothing. Passing null is what clears it.
|
|
@@ -2460,6 +2708,8 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2460
2708
|
root.dataset.theme = state.theme;
|
|
2461
2709
|
root.dataset.decor = decorOf(state.theme);
|
|
2462
2710
|
root.dataset.size = state.size;
|
|
2711
|
+
root.dataset.template = state.template;
|
|
2712
|
+
root.dataset.transition = state.transition;
|
|
2463
2713
|
if (state.head) root.dataset.head = state.head; else delete root.dataset.head;
|
|
2464
2714
|
if (state.body) root.dataset.body = state.body; else delete root.dataset.body;
|
|
2465
2715
|
}
|
|
@@ -2471,7 +2721,9 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2471
2721
|
theme: state.theme,
|
|
2472
2722
|
size: state.size,
|
|
2473
2723
|
head: state.head,
|
|
2474
|
-
body: state.body
|
|
2724
|
+
body: state.body,
|
|
2725
|
+
template: state.template,
|
|
2726
|
+
transition: state.transition
|
|
2475
2727
|
}, '*');
|
|
2476
2728
|
}
|
|
2477
2729
|
} catch (e) {}
|
|
@@ -2484,10 +2736,13 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2484
2736
|
theme: state.theme,
|
|
2485
2737
|
size: state.size,
|
|
2486
2738
|
head: state.head,
|
|
2487
|
-
body: state.body
|
|
2739
|
+
body: state.body,
|
|
2740
|
+
template: state.template,
|
|
2741
|
+
transition: state.transition
|
|
2488
2742
|
});
|
|
2489
2743
|
syncHtmlFrameTheme();
|
|
2490
2744
|
paintThemeButton();
|
|
2745
|
+
paintTemplateMenu();
|
|
2491
2746
|
}
|
|
2492
2747
|
|
|
2493
2748
|
function faceLabel(id) {
|
|
@@ -2551,6 +2806,45 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2551
2806
|
$('btn-theme').title = t ? t.label + ' — ' + t.blurb : 'Pick a theme';
|
|
2552
2807
|
}
|
|
2553
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
|
+
|
|
2554
2848
|
// ── Theme picker ───────────────────────────────────────────────────────
|
|
2555
2849
|
function buildSizeSeg(hostId, showBlurb) {
|
|
2556
2850
|
var host = $(hostId);
|
|
@@ -2772,6 +3066,8 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2772
3066
|
state.deckId = id;
|
|
2773
3067
|
lsSet(K.current, id);
|
|
2774
3068
|
setDocKind(meta.kind || 'markdown');
|
|
3069
|
+
setTemplate(meta.template || state.template, true);
|
|
3070
|
+
setTransition(meta.transition || state.transition, true);
|
|
2775
3071
|
$('docname').value = meta.name;
|
|
2776
3072
|
updateDocTitle();
|
|
2777
3073
|
updateDeckCount();
|
|
@@ -2894,7 +3190,13 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2894
3190
|
dupe.addEventListener('click', function (e) {
|
|
2895
3191
|
e.stopPropagation();
|
|
2896
3192
|
if (deck.id === state.deckId) { duplicateDeck(); renderLibrary(); return; }
|
|
2897
|
-
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
|
+
);
|
|
2898
3200
|
if (!id) { noRoom(); return; }
|
|
2899
3201
|
updateDeckCount();
|
|
2900
3202
|
renderLibrary();
|
|
@@ -2937,7 +3239,48 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2937
3239
|
// ── Start screen ─────────────────────────────────────────────────────
|
|
2938
3240
|
// Shown on a true first run, and whenever "new" is chosen explicitly from
|
|
2939
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
|
+
|
|
2940
3280
|
function showStartScreen() {
|
|
3281
|
+
startTemplate = state.template;
|
|
3282
|
+
startTransition = state.transition;
|
|
3283
|
+
paintStartOptions();
|
|
2941
3284
|
$('start-lib').classList.toggle('is-disabled', !loadIndex().length);
|
|
2942
3285
|
$('screen-start').classList.add('is-on');
|
|
2943
3286
|
updateDocTitle();
|
|
@@ -2956,7 +3299,9 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
2956
3299
|
}
|
|
2957
3300
|
|
|
2958
3301
|
function startNewMarkdown() {
|
|
2959
|
-
|
|
3302
|
+
setTemplate(startTemplate, true);
|
|
3303
|
+
setTransition(startTransition, true);
|
|
3304
|
+
var id = createDeck(uniqueName('untitled'), D.welcome, 'markdown', startTemplate, startTransition);
|
|
2960
3305
|
hideStartScreenSilently();
|
|
2961
3306
|
if (!id) {
|
|
2962
3307
|
// Storage refuses even a first write: use the deck unsaved rather than
|
|
@@ -3021,7 +3366,13 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
3021
3366
|
var name = uniqueName(body.title || url.hostname);
|
|
3022
3367
|
var kind = body.kind || (body.markdown ? 'markdown' : 'html');
|
|
3023
3368
|
var content = kind === 'markdown' ? (body.markdown || body.content || '') : (body.html || body.content || '');
|
|
3024
|
-
var id = createDeck(
|
|
3369
|
+
var id = createDeck(
|
|
3370
|
+
name,
|
|
3371
|
+
content,
|
|
3372
|
+
kind,
|
|
3373
|
+
kind === 'markdown' ? startTemplate : state.template,
|
|
3374
|
+
kind === 'markdown' ? startTransition : state.transition
|
|
3375
|
+
);
|
|
3025
3376
|
if (!id) { noRoom(); return; }
|
|
3026
3377
|
input.value = '';
|
|
3027
3378
|
hideStartScreenSilently();
|
|
@@ -3062,6 +3413,8 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
3062
3413
|
var parseTimer = null;
|
|
3063
3414
|
|
|
3064
3415
|
function onInput() {
|
|
3416
|
+
// Typing takes the editor back from the deck.
|
|
3417
|
+
if (following) { following = false; followChipEl.classList.add('hidden'); }
|
|
3065
3418
|
paint();
|
|
3066
3419
|
syncScroll();
|
|
3067
3420
|
updateCaretUi();
|
|
@@ -3145,13 +3498,19 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
3145
3498
|
});
|
|
3146
3499
|
|
|
3147
3500
|
/** An opened file lands as a new deck, so nothing in the library is lost. */
|
|
3148
|
-
function loadMarkdownFile(file) {
|
|
3501
|
+
function loadMarkdownFile(file, chosenTemplate, chosenTransition) {
|
|
3149
3502
|
var fr = new FileReader();
|
|
3150
3503
|
fr.onload = function () {
|
|
3151
3504
|
saveNow();
|
|
3152
3505
|
var markdown = String(fr.result).replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');
|
|
3153
3506
|
var name = uniqueName(file.name.replace(/\\.(md|markdown|txt)$/i, '') || 'untitled');
|
|
3154
|
-
var id = createDeck(
|
|
3507
|
+
var id = createDeck(
|
|
3508
|
+
name,
|
|
3509
|
+
markdown,
|
|
3510
|
+
'markdown',
|
|
3511
|
+
chosenTemplate || state.template,
|
|
3512
|
+
chosenTransition || state.transition
|
|
3513
|
+
);
|
|
3155
3514
|
if (!id) { noRoom(); return; }
|
|
3156
3515
|
hideStartScreenSilently();
|
|
3157
3516
|
openDeck(id);
|
|
@@ -3181,7 +3540,14 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
3181
3540
|
$('file-any').addEventListener('change', function (e) {
|
|
3182
3541
|
var f = e.target.files[0];
|
|
3183
3542
|
if (f) {
|
|
3184
|
-
|
|
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
|
+
}
|
|
3185
3551
|
else if (/\\.html?$/i.test(f.name)) loadHtmlFile(f);
|
|
3186
3552
|
else toast('Unrecognized file type.', 'warn');
|
|
3187
3553
|
}
|
|
@@ -3267,6 +3633,13 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
3267
3633
|
setFont(btn.dataset.slot, btn.dataset.font || null, true);
|
|
3268
3634
|
}, 'button[data-slot]');
|
|
3269
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
|
+
|
|
3270
3643
|
document.addEventListener('click', function (e) {
|
|
3271
3644
|
if (openMenu && !openMenu.contains(e.target)) closeMenu();
|
|
3272
3645
|
});
|
|
@@ -3366,7 +3739,7 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
3366
3739
|
if (key === 'k' && !e.shiftKey && md) { e.preventDefault(); openPalette(); }
|
|
3367
3740
|
else if (key === 'o') { e.preventDefault(); openLibrary(); }
|
|
3368
3741
|
else if (key === '/' && md) { e.preventDefault(); runAction('guide'); }
|
|
3369
|
-
else if (key === 's' && e.shiftKey) { e.preventDefault(); exportPdf(); }
|
|
3742
|
+
else if (key === 'p' || (key === 's' && e.shiftKey)) { e.preventDefault(); exportPdf(); }
|
|
3370
3743
|
else if (key === 's') { e.preventDefault(); saveNow(); download(); }
|
|
3371
3744
|
else if (key === 'enter') { e.preventDefault(); present(); }
|
|
3372
3745
|
else if (key === 'd' && md) { e.preventDefault(); insertSnippet(bySnippetId['slide-break']); }
|
|
@@ -3476,6 +3849,7 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
3476
3849
|
} else if (m.type === 'overflow') {
|
|
3477
3850
|
var had = !!state.overflow[m.index];
|
|
3478
3851
|
state.overflow[m.index] = m.overflow;
|
|
3852
|
+
renderCounts();
|
|
3479
3853
|
if (had !== m.overflow) evalNudges();
|
|
3480
3854
|
}
|
|
3481
3855
|
});
|
|
@@ -3493,8 +3867,12 @@ button { font: inherit; color: inherit; background: none; border: none; cursor:
|
|
|
3493
3867
|
|
|
3494
3868
|
document.documentElement.dataset.theme = state.theme;
|
|
3495
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;
|
|
3496
3872
|
buildSizeSeg('topbar-size', false);
|
|
3497
3873
|
buildFontMenu();
|
|
3874
|
+
buildTemplateMenu();
|
|
3875
|
+
buildStartOptions();
|
|
3498
3876
|
paintSizeSeg();
|
|
3499
3877
|
paintThemeButton();
|
|
3500
3878
|
$('seg-single').classList.toggle('is-on', state.mode === 'single');
|