@nowline/preview-shell 0.4.2 → 0.5.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/dist/markup.d.ts +34 -6
- package/dist/markup.d.ts.map +1 -1
- package/dist/markup.js +73 -41
- package/dist/markup.js.map +1 -1
- package/dist/mount.d.ts +59 -15
- package/dist/mount.d.ts.map +1 -1
- package/dist/mount.js +598 -101
- package/dist/mount.js.map +1 -1
- package/dist/styles.d.ts +9 -8
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +286 -57
- package/dist/styles.js.map +1 -1
- package/package.json +2 -2
- package/src/markup.ts +110 -47
- package/src/mount.ts +740 -106
- package/src/styles.ts +286 -57
package/dist/mount.js
CHANGED
|
@@ -12,14 +12,23 @@ import { buildViewport } from './markup.js';
|
|
|
12
12
|
import { PREVIEW_SHELL_CSS } from './styles.js';
|
|
13
13
|
let stylesheetMounted = false;
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* scopes every selector under `.nl-preview-root` so multiple shells
|
|
18
|
-
* can coexist with workbench / app styles.
|
|
15
|
+
* Last chrome position saved after a drag, shared across all shells in
|
|
16
|
+
* this JS session. Persists for the lifetime of the page.
|
|
19
17
|
*/
|
|
18
|
+
let savedChromePosition = null;
|
|
20
19
|
function ensureStylesheet(doc) {
|
|
21
20
|
if (stylesheetMounted)
|
|
22
21
|
return;
|
|
22
|
+
// A CSP-restricted consumer (e.g. the VS Code webview, whose
|
|
23
|
+
// `style-src` is nonce-only with no `'unsafe-inline'`) pre-injects
|
|
24
|
+
// this stylesheet into the host HTML with a nonce, marked by the
|
|
25
|
+
// `data-nl-preview-shell` attribute. A second `<style>` created here
|
|
26
|
+
// would carry no nonce and be refused by the CSP, leaving the
|
|
27
|
+
// toolbar unstyled — so defer to the host-provided copy when present.
|
|
28
|
+
if (doc.querySelector('style[data-nl-preview-shell]')) {
|
|
29
|
+
stylesheetMounted = true;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
23
32
|
const style = doc.createElement('style');
|
|
24
33
|
style.setAttribute('data-nl-preview-shell', '');
|
|
25
34
|
style.textContent = PREVIEW_SHELL_CSS;
|
|
@@ -38,9 +47,15 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
38
47
|
naturalHeight: 0,
|
|
39
48
|
defaultFit: options.initialFit ?? 'fitPage',
|
|
40
49
|
activeFit: options.initialFit === 'fitWidth' ? 'fitWidth' : 'fitPage',
|
|
50
|
+
isDirty: false,
|
|
51
|
+
lastViewportWidth: 0,
|
|
52
|
+
lastViewportHeight: 0,
|
|
41
53
|
showMinimap: options.showMinimap !== false,
|
|
42
54
|
minimapDismissedThisSession: false,
|
|
43
55
|
firstRender: true,
|
|
56
|
+
mode: 'dark',
|
|
57
|
+
locale: options.locale ?? (typeof navigator !== 'undefined' ? navigator.language : 'en'),
|
|
58
|
+
export: { format: 'svg' },
|
|
44
59
|
view: {
|
|
45
60
|
theme: 'auto',
|
|
46
61
|
now: 'today',
|
|
@@ -53,16 +68,166 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
53
68
|
target.addEventListener(type, handler, opts);
|
|
54
69
|
cleanups.push(() => target.removeEventListener(type, handler, opts));
|
|
55
70
|
}
|
|
56
|
-
// =====
|
|
71
|
+
// ===== Mode resolution =====
|
|
72
|
+
function resolveSystemMode() {
|
|
73
|
+
const body = doc.body;
|
|
74
|
+
if (body) {
|
|
75
|
+
if (body.classList.contains('vscode-dark') ||
|
|
76
|
+
body.classList.contains('vscode-high-contrast-dark'))
|
|
77
|
+
return 'dark';
|
|
78
|
+
if (body.classList.contains('vscode-light') ||
|
|
79
|
+
body.classList.contains('vscode-high-contrast-light') ||
|
|
80
|
+
body.classList.contains('vscode-high-contrast'))
|
|
81
|
+
return 'light';
|
|
82
|
+
}
|
|
83
|
+
if (typeof win.matchMedia === 'function') {
|
|
84
|
+
return win.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
85
|
+
}
|
|
86
|
+
return 'dark';
|
|
87
|
+
}
|
|
88
|
+
function applyMode(resolved) {
|
|
89
|
+
state.mode = resolved;
|
|
90
|
+
rootEl.setAttribute('data-nl-mode', resolved);
|
|
91
|
+
}
|
|
92
|
+
const rawMode = options.mode ?? 'system';
|
|
93
|
+
applyMode(rawMode === 'system' ? resolveSystemMode() : rawMode);
|
|
94
|
+
if (rawMode === 'system') {
|
|
95
|
+
if (typeof MutationObserver !== 'undefined' && doc.body) {
|
|
96
|
+
const bodyObs = new MutationObserver(() => applyMode(resolveSystemMode()));
|
|
97
|
+
bodyObs.observe(doc.body, { attributes: true, attributeFilter: ['class'] });
|
|
98
|
+
cleanups.push(() => bodyObs.disconnect());
|
|
99
|
+
}
|
|
100
|
+
const mq = typeof win.matchMedia === 'function'
|
|
101
|
+
? win.matchMedia('(prefers-color-scheme: dark)')
|
|
102
|
+
: null;
|
|
103
|
+
if (mq) {
|
|
104
|
+
const onMqChange = () => {
|
|
105
|
+
const body = doc.body;
|
|
106
|
+
const hasVsCode = body &&
|
|
107
|
+
(body.classList.contains('vscode-dark') ||
|
|
108
|
+
body.classList.contains('vscode-light') ||
|
|
109
|
+
body.classList.contains('vscode-high-contrast') ||
|
|
110
|
+
body.classList.contains('vscode-high-contrast-dark') ||
|
|
111
|
+
body.classList.contains('vscode-high-contrast-light'));
|
|
112
|
+
if (!hasVsCode)
|
|
113
|
+
applyMode(mq.matches ? 'dark' : 'light');
|
|
114
|
+
};
|
|
115
|
+
mq.addEventListener('change', onMqChange);
|
|
116
|
+
cleanups.push(() => mq.removeEventListener('change', onMqChange));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// ===== Floating UI fade =====
|
|
57
120
|
let fadeTimer = null;
|
|
58
|
-
function
|
|
121
|
+
function showFloatingUI() {
|
|
59
122
|
els.chrome.classList.remove('faded');
|
|
123
|
+
els.minimap.classList.remove('faded');
|
|
60
124
|
if (fadeTimer)
|
|
61
125
|
clearTimeout(fadeTimer);
|
|
62
|
-
fadeTimer = setTimeout(() =>
|
|
126
|
+
fadeTimer = setTimeout(() => {
|
|
127
|
+
els.chrome.classList.add('faded');
|
|
128
|
+
els.minimap.classList.add('faded');
|
|
129
|
+
}, 2000);
|
|
130
|
+
}
|
|
131
|
+
on(doc, 'mousemove', showFloatingUI);
|
|
132
|
+
showFloatingUI();
|
|
133
|
+
// ===== Collapse / restore =====
|
|
134
|
+
// « collapses the toolbar to a translucent puck (just the drag grip
|
|
135
|
+
// + a » restore arrow); » expands it back to the full toolbar.
|
|
136
|
+
on(els.collapseBtn, 'click', (e) => {
|
|
137
|
+
e.stopPropagation();
|
|
138
|
+
closeAllMenus();
|
|
139
|
+
els.chrome.classList.add('collapsed');
|
|
140
|
+
repositionChrome();
|
|
141
|
+
});
|
|
142
|
+
on(els.restoreBtn, 'click', (e) => {
|
|
143
|
+
e.stopPropagation();
|
|
144
|
+
els.chrome.classList.remove('collapsed');
|
|
145
|
+
repositionChrome();
|
|
146
|
+
});
|
|
147
|
+
// ===== Toolbar positioning + drag =====
|
|
148
|
+
// The chrome defaults to the upper-right corner and tracks it on
|
|
149
|
+
// resize (`chromePinnedRight`). Once dragged it becomes free-floating
|
|
150
|
+
// and is merely clamped back into view. Either way the position is
|
|
151
|
+
// always expressed as explicit `left`/`top` so a narrowing viewport
|
|
152
|
+
// shifts the toolbar left instead of squishing the row.
|
|
153
|
+
let chromePinnedRight = savedChromePosition === null;
|
|
154
|
+
let chromeDragStart = null;
|
|
155
|
+
function getGutter() {
|
|
156
|
+
const val = getComputedStyle(rootEl).getPropertyValue('--nl-preview-gutter').trim();
|
|
157
|
+
return parseInt(val, 10) || 8;
|
|
158
|
+
}
|
|
159
|
+
function repositionChrome() {
|
|
160
|
+
const g = getGutter();
|
|
161
|
+
const cw = els.chrome.offsetWidth;
|
|
162
|
+
const ch = els.chrome.offsetHeight;
|
|
163
|
+
const maxLeft = Math.max(g, rootEl.clientWidth - cw - g);
|
|
164
|
+
const maxTop = Math.max(g, rootEl.clientHeight - ch - g);
|
|
165
|
+
let left;
|
|
166
|
+
let top;
|
|
167
|
+
if (chromePinnedRight) {
|
|
168
|
+
left = rootEl.clientWidth - cw - g;
|
|
169
|
+
top = g;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
left = parseInt(els.chrome.style.left, 10);
|
|
173
|
+
top = parseInt(els.chrome.style.top, 10);
|
|
174
|
+
if (Number.isNaN(left))
|
|
175
|
+
left = savedChromePosition?.left ?? g;
|
|
176
|
+
if (Number.isNaN(top))
|
|
177
|
+
top = savedChromePosition?.top ?? g;
|
|
178
|
+
}
|
|
179
|
+
els.chrome.style.right = 'auto';
|
|
180
|
+
els.chrome.style.left = `${Math.max(g, Math.min(left, maxLeft))}px`;
|
|
181
|
+
els.chrome.style.top = `${Math.max(g, Math.min(top, maxTop))}px`;
|
|
63
182
|
}
|
|
64
|
-
|
|
65
|
-
|
|
183
|
+
function ensureChromeLeftTopAnchoring() {
|
|
184
|
+
if (els.chrome.style.left !== '')
|
|
185
|
+
return;
|
|
186
|
+
els.chrome.style.right = 'auto';
|
|
187
|
+
els.chrome.style.left = `${els.chrome.offsetLeft}px`;
|
|
188
|
+
els.chrome.style.top = `${els.chrome.offsetTop}px`;
|
|
189
|
+
}
|
|
190
|
+
on(els.toolbarHandle, 'pointerdown', (e) => {
|
|
191
|
+
e.preventDefault();
|
|
192
|
+
chromePinnedRight = false;
|
|
193
|
+
ensureChromeLeftTopAnchoring();
|
|
194
|
+
chromeDragStart = {
|
|
195
|
+
pointerX: e.clientX,
|
|
196
|
+
pointerY: e.clientY,
|
|
197
|
+
chromeLeft: parseInt(els.chrome.style.left, 10) || 0,
|
|
198
|
+
chromeTop: parseInt(els.chrome.style.top, 10) || 0,
|
|
199
|
+
};
|
|
200
|
+
els.chrome.classList.add('dragging');
|
|
201
|
+
els.toolbarHandle.setPointerCapture(e.pointerId);
|
|
202
|
+
});
|
|
203
|
+
on(els.toolbarHandle, 'pointermove', (e) => {
|
|
204
|
+
if (!chromeDragStart)
|
|
205
|
+
return;
|
|
206
|
+
const dx = e.clientX - chromeDragStart.pointerX;
|
|
207
|
+
const dy = e.clientY - chromeDragStart.pointerY;
|
|
208
|
+
const rawLeft = chromeDragStart.chromeLeft + dx;
|
|
209
|
+
const rawTop = chromeDragStart.chromeTop + dy;
|
|
210
|
+
const g = getGutter();
|
|
211
|
+
const maxLeft = Math.max(g, rootEl.clientWidth - els.chrome.offsetWidth - g);
|
|
212
|
+
const maxTop = Math.max(g, rootEl.clientHeight - els.chrome.offsetHeight - g);
|
|
213
|
+
els.chrome.style.left = `${Math.max(g, Math.min(rawLeft, maxLeft))}px`;
|
|
214
|
+
els.chrome.style.top = `${Math.max(g, Math.min(rawTop, maxTop))}px`;
|
|
215
|
+
});
|
|
216
|
+
function endChromeDrag() {
|
|
217
|
+
if (!chromeDragStart)
|
|
218
|
+
return;
|
|
219
|
+
chromeDragStart = null;
|
|
220
|
+
els.chrome.classList.remove('dragging');
|
|
221
|
+
savedChromePosition = {
|
|
222
|
+
left: parseInt(els.chrome.style.left, 10) || 0,
|
|
223
|
+
top: parseInt(els.chrome.style.top, 10) || 0,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
on(els.toolbarHandle, 'pointerup', endChromeDrag);
|
|
227
|
+
on(els.toolbarHandle, 'pointercancel', () => {
|
|
228
|
+
chromeDragStart = null;
|
|
229
|
+
els.chrome.classList.remove('dragging');
|
|
230
|
+
});
|
|
66
231
|
// ===== Transform =====
|
|
67
232
|
function applyTransform() {
|
|
68
233
|
const w = Math.max(1, Math.round(state.naturalWidth * state.scale));
|
|
@@ -99,6 +264,7 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
99
264
|
if (!state.naturalWidth || !state.naturalHeight)
|
|
100
265
|
return;
|
|
101
266
|
state.activeFit = 'fitPage';
|
|
267
|
+
state.isDirty = false;
|
|
102
268
|
const sx = els.viewport.clientWidth / state.naturalWidth;
|
|
103
269
|
const sy = els.viewport.clientHeight / state.naturalHeight;
|
|
104
270
|
setScale(Math.min(sx, sy));
|
|
@@ -109,12 +275,14 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
109
275
|
if (!state.naturalWidth)
|
|
110
276
|
return;
|
|
111
277
|
state.activeFit = 'fitWidth';
|
|
278
|
+
state.isDirty = false;
|
|
112
279
|
setScale(els.viewport.clientWidth / state.naturalWidth);
|
|
113
280
|
els.viewport.scrollLeft = 0;
|
|
114
281
|
els.viewport.scrollTop = 0;
|
|
115
282
|
}
|
|
116
283
|
function actualSize() {
|
|
117
284
|
state.activeFit = 'manual';
|
|
285
|
+
state.isDirty = true;
|
|
118
286
|
setScale(1);
|
|
119
287
|
}
|
|
120
288
|
function applyDefaultFit() {
|
|
@@ -126,10 +294,26 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
126
294
|
fitPage();
|
|
127
295
|
}
|
|
128
296
|
function reapplyActiveFit() {
|
|
129
|
-
|
|
297
|
+
const newW = els.viewport.clientWidth;
|
|
298
|
+
const newH = els.viewport.clientHeight;
|
|
299
|
+
if (state.isDirty) {
|
|
300
|
+
const oldW = state.lastViewportWidth || newW;
|
|
301
|
+
const oldH = state.lastViewportHeight || newH;
|
|
302
|
+
if (state.scale > 0 && (oldW !== newW || oldH !== newH)) {
|
|
303
|
+
const cx = (els.viewport.scrollLeft + oldW / 2) / state.scale;
|
|
304
|
+
const cy = (els.viewport.scrollTop + oldH / 2) / state.scale;
|
|
305
|
+
els.viewport.scrollLeft = Math.max(0, cx * state.scale - newW / 2);
|
|
306
|
+
els.viewport.scrollTop = Math.max(0, cy * state.scale - newH / 2);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
else if (state.activeFit === 'fitPage') {
|
|
130
310
|
fitPage();
|
|
131
|
-
|
|
311
|
+
}
|
|
312
|
+
else if (state.activeFit === 'fitWidth') {
|
|
132
313
|
fitWidth();
|
|
314
|
+
}
|
|
315
|
+
state.lastViewportWidth = newW;
|
|
316
|
+
state.lastViewportHeight = newH;
|
|
133
317
|
}
|
|
134
318
|
// ===== Render handling =====
|
|
135
319
|
function extractNaturalSize(svgEl) {
|
|
@@ -221,6 +405,8 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
221
405
|
els.diagSummary.classList.add('warn-only');
|
|
222
406
|
if (!errors && !warnings)
|
|
223
407
|
els.diagSummary.classList.add('clean');
|
|
408
|
+
// Hide Open Problems link when there are no issues
|
|
409
|
+
els.openProblems.hidden = errors + warnings === 0;
|
|
224
410
|
const tbody = els.diagTbody;
|
|
225
411
|
while (tbody.firstChild)
|
|
226
412
|
tbody.removeChild(tbody.firstChild);
|
|
@@ -286,8 +472,8 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
286
472
|
}
|
|
287
473
|
svgEl.setAttribute('preserveAspectRatio', 'xMidYMid meet');
|
|
288
474
|
}
|
|
289
|
-
const maxW =
|
|
290
|
-
const maxH =
|
|
475
|
+
const maxW = 120;
|
|
476
|
+
const maxH = 90;
|
|
291
477
|
if (state.naturalWidth && state.naturalHeight) {
|
|
292
478
|
const ratio = state.naturalWidth / state.naturalHeight;
|
|
293
479
|
let w = maxW;
|
|
@@ -361,17 +547,31 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
361
547
|
miniDragging = false;
|
|
362
548
|
});
|
|
363
549
|
on(els.viewport, 'scroll', updateMinimapRect);
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
550
|
+
let resizeTimer = null;
|
|
551
|
+
function handleResize() {
|
|
552
|
+
if (resizeTimer)
|
|
553
|
+
clearTimeout(resizeTimer);
|
|
554
|
+
resizeTimer = setTimeout(() => {
|
|
555
|
+
resizeTimer = null;
|
|
556
|
+
reapplyActiveFit();
|
|
557
|
+
updateMinimapRect();
|
|
558
|
+
updateMinimapVisibility();
|
|
559
|
+
repositionChrome();
|
|
560
|
+
}, 50);
|
|
561
|
+
}
|
|
562
|
+
on(win, 'resize', handleResize);
|
|
563
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
564
|
+
const ro = new ResizeObserver(handleResize);
|
|
565
|
+
ro.observe(els.viewport);
|
|
566
|
+
cleanups.push(() => ro.disconnect());
|
|
567
|
+
}
|
|
369
568
|
// ===== Wheel zoom =====
|
|
370
569
|
on(els.viewport, 'wheel', (e) => {
|
|
371
570
|
if (!(e.ctrlKey || e.metaKey))
|
|
372
571
|
return;
|
|
373
572
|
e.preventDefault();
|
|
374
573
|
state.activeFit = 'manual';
|
|
574
|
+
state.isDirty = true;
|
|
375
575
|
const rect = els.viewport.getBoundingClientRect();
|
|
376
576
|
const anchorX = e.clientX - rect.left;
|
|
377
577
|
const anchorY = e.clientY - rect.top;
|
|
@@ -420,6 +620,7 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
420
620
|
on(els.viewport, 'mousedown', (e) => {
|
|
421
621
|
if (!spaceDown)
|
|
422
622
|
return;
|
|
623
|
+
state.isDirty = true;
|
|
423
624
|
dragOrigin = {
|
|
424
625
|
x: e.clientX,
|
|
425
626
|
y: e.clientY,
|
|
@@ -444,96 +645,123 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
444
645
|
// ===== Toolbar buttons =====
|
|
445
646
|
on(els.zoomOut, 'click', () => {
|
|
446
647
|
state.activeFit = 'manual';
|
|
648
|
+
state.isDirty = true;
|
|
447
649
|
setScale(state.scale / 1.1);
|
|
448
650
|
});
|
|
449
651
|
on(els.zoomIn, 'click', () => {
|
|
450
652
|
state.activeFit = 'manual';
|
|
653
|
+
state.isDirty = true;
|
|
451
654
|
setScale(state.scale * 1.1);
|
|
452
655
|
});
|
|
453
656
|
on(els.zoomReset, 'click', actualSize);
|
|
454
657
|
on(els.fitWidth, 'click', fitWidth);
|
|
455
658
|
on(els.fitPage, 'click', fitPage);
|
|
456
|
-
// =====
|
|
457
|
-
function
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
els.viewMenu.hidden = true;
|
|
463
|
-
menu.hidden = !menu.hidden;
|
|
464
|
-
});
|
|
659
|
+
// ===== Menu state helpers =====
|
|
660
|
+
function closeSubMenus() {
|
|
661
|
+
els.formatMenu.hidden = true;
|
|
662
|
+
els.themeMenu.hidden = true;
|
|
663
|
+
els.nowPicker.hidden = true;
|
|
664
|
+
els.linksMenu.hidden = true;
|
|
465
665
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
on(doc, 'click', () => {
|
|
470
|
-
els.saveMenu.hidden = true;
|
|
471
|
-
els.copyMenu.hidden = true;
|
|
472
|
-
els.viewMenu.hidden = true;
|
|
473
|
-
});
|
|
474
|
-
for (const m of [els.saveMenu, els.copyMenu, els.viewMenu]) {
|
|
475
|
-
on(m, 'click', (e) => e.stopPropagation());
|
|
476
|
-
}
|
|
477
|
-
// ===== View options =====
|
|
478
|
-
function refreshViewMenu() {
|
|
479
|
-
const items = els.viewMenu.querySelectorAll('.view-opt');
|
|
480
|
-
for (const item of Array.from(items)) {
|
|
481
|
-
const opt = item.getAttribute('data-opt');
|
|
482
|
-
const value = item.getAttribute('data-value');
|
|
483
|
-
let active = false;
|
|
484
|
-
if (opt === 'theme')
|
|
485
|
-
active = state.view.theme === value;
|
|
486
|
-
else if (opt === 'now')
|
|
487
|
-
active = state.view.now === value;
|
|
488
|
-
else if (opt === 'showLinks')
|
|
489
|
-
active = state.view.showLinks;
|
|
490
|
-
item.setAttribute('data-active', active ? 'true' : 'false');
|
|
491
|
-
}
|
|
666
|
+
function closeAllMenus() {
|
|
667
|
+
els.moreMenu.hidden = true;
|
|
668
|
+
closeSubMenus();
|
|
492
669
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
670
|
+
/**
|
|
671
|
+
* Keep a just-opened flyout (more-menu, a sub-dropdown, or the
|
|
672
|
+
* calendar) inside the preview root + gutters. `defaultSide` is the
|
|
673
|
+
* edge the panel is anchored to in CSS; if opening that way would
|
|
674
|
+
* spill past the opposite gutter the panel is flipped, then any
|
|
675
|
+
* residual horizontal overflow is nudged back with a transform and
|
|
676
|
+
* excess height is capped with an internal scroll.
|
|
677
|
+
*
|
|
678
|
+
* `clampVertical` is off for the more-menu so capping its height
|
|
679
|
+
* never clips the calendar that pops out of it.
|
|
680
|
+
*/
|
|
681
|
+
function placeFlyout(panel, defaultSide, clampVertical = true) {
|
|
682
|
+
panel.classList.remove('flip');
|
|
683
|
+
panel.style.maxHeight = '';
|
|
684
|
+
panel.style.overflowY = '';
|
|
685
|
+
panel.style.transform = '';
|
|
686
|
+
const root = rootEl.getBoundingClientRect();
|
|
687
|
+
// Skip when the host hasn't laid out yet (e.g. happy-dom tests).
|
|
688
|
+
if (root.width === 0 && root.height === 0)
|
|
507
689
|
return;
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
690
|
+
const g = getGutter();
|
|
691
|
+
let box = panel.getBoundingClientRect();
|
|
692
|
+
const fitsWidth = box.width <= root.width - 2 * g;
|
|
693
|
+
if (fitsWidth && defaultSide === 'left' && box.right > root.right - g) {
|
|
694
|
+
panel.classList.add('flip');
|
|
513
695
|
}
|
|
514
|
-
else if (
|
|
515
|
-
|
|
516
|
-
state.view.overridden.now = true;
|
|
696
|
+
else if (fitsWidth && defaultSide === 'right' && box.left < root.left + g) {
|
|
697
|
+
panel.classList.add('flip');
|
|
517
698
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
699
|
+
box = panel.getBoundingClientRect();
|
|
700
|
+
let dx = 0;
|
|
701
|
+
if (box.right > root.right - g)
|
|
702
|
+
dx = root.right - g - box.right;
|
|
703
|
+
if (box.left + dx < root.left + g)
|
|
704
|
+
dx = root.left + g - box.left;
|
|
705
|
+
if (dx !== 0)
|
|
706
|
+
panel.style.transform = `translateX(${Math.round(dx)}px)`;
|
|
707
|
+
if (clampVertical) {
|
|
708
|
+
box = panel.getBoundingClientRect();
|
|
709
|
+
const avail = root.bottom - g - box.top;
|
|
710
|
+
if (box.height > avail && avail > 80) {
|
|
711
|
+
panel.style.maxHeight = `${Math.round(avail)}px`;
|
|
712
|
+
panel.style.overflowY = 'auto';
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
on(doc, 'click', closeAllMenus);
|
|
717
|
+
on(els.moreMenu, 'click', (e) => e.stopPropagation());
|
|
718
|
+
// ===== More-menu toggle =====
|
|
719
|
+
on(els.moreToggle, 'click', (e) => {
|
|
720
|
+
e.stopPropagation();
|
|
721
|
+
const opening = els.moreMenu.hidden;
|
|
722
|
+
closeAllMenus();
|
|
723
|
+
if (opening) {
|
|
724
|
+
els.moreMenu.hidden = false;
|
|
725
|
+
placeFlyout(els.moreMenu, 'right', /* clampVertical */ false);
|
|
521
726
|
}
|
|
522
|
-
refreshViewMenu();
|
|
523
|
-
els.viewMenu.hidden = true;
|
|
524
|
-
postViewOverrides();
|
|
525
727
|
});
|
|
526
|
-
// =====
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
els.
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
728
|
+
// ===== Format dropdown =====
|
|
729
|
+
on(els.formatToggle, 'click', (e) => {
|
|
730
|
+
e.stopPropagation();
|
|
731
|
+
const opening = els.formatMenu.hidden;
|
|
732
|
+
closeSubMenus();
|
|
733
|
+
if (opening) {
|
|
734
|
+
els.formatMenu.hidden = false;
|
|
735
|
+
placeFlyout(els.formatMenu, 'left');
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
on(els.formatMenu, 'click', (e) => {
|
|
739
|
+
e.stopPropagation();
|
|
740
|
+
const btn = e.target.closest('.format-opt');
|
|
741
|
+
if (!btn)
|
|
742
|
+
return;
|
|
743
|
+
const value = btn.getAttribute('data-value');
|
|
744
|
+
if (!value)
|
|
745
|
+
return;
|
|
746
|
+
state.export.format = value;
|
|
747
|
+
refreshFormatToggle();
|
|
748
|
+
els.formatMenu.hidden = true;
|
|
749
|
+
});
|
|
750
|
+
function refreshFormatToggle() {
|
|
751
|
+
els.formatToggle.textContent = `${state.export.format.toUpperCase()} \u25be`;
|
|
752
|
+
for (const btn of Array.from(els.formatMenu.querySelectorAll('.format-opt'))) {
|
|
753
|
+
btn.setAttribute('data-active', (btn.getAttribute('data-value') === state.export.format).toString());
|
|
754
|
+
}
|
|
536
755
|
}
|
|
756
|
+
// ===== Copy / export actions =====
|
|
757
|
+
on(els.copyAction, 'click', () => {
|
|
758
|
+
closeAllMenus();
|
|
759
|
+
void handleExportAction(`copy-${state.export.format}`);
|
|
760
|
+
});
|
|
761
|
+
on(els.exportAction, 'click', () => {
|
|
762
|
+
closeAllMenus();
|
|
763
|
+
void handleExportAction(`save-${state.export.format}`);
|
|
764
|
+
});
|
|
537
765
|
async function handleExportAction(action) {
|
|
538
766
|
if (!state.svgString)
|
|
539
767
|
return;
|
|
@@ -619,15 +847,264 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
619
847
|
img.src = url;
|
|
620
848
|
});
|
|
621
849
|
}
|
|
622
|
-
// =====
|
|
850
|
+
// ===== Theme dropdown =====
|
|
851
|
+
function buildThemeMenu(themes) {
|
|
852
|
+
els.themeMenu.innerHTML = '';
|
|
853
|
+
const autoLi = doc.createElement('li');
|
|
854
|
+
const autoBtn = doc.createElement('button');
|
|
855
|
+
autoBtn.className = 'btn theme-opt';
|
|
856
|
+
autoBtn.setAttribute('data-value', 'auto');
|
|
857
|
+
autoBtn.textContent = 'Auto';
|
|
858
|
+
autoLi.appendChild(autoBtn);
|
|
859
|
+
els.themeMenu.appendChild(autoLi);
|
|
860
|
+
for (const theme of themes) {
|
|
861
|
+
const li = doc.createElement('li');
|
|
862
|
+
const btn = doc.createElement('button');
|
|
863
|
+
btn.className = 'btn theme-opt';
|
|
864
|
+
btn.setAttribute('data-value', theme);
|
|
865
|
+
const chip = doc.createElement('code');
|
|
866
|
+
chip.className = 'code-chip';
|
|
867
|
+
chip.textContent = theme;
|
|
868
|
+
btn.appendChild(chip);
|
|
869
|
+
li.appendChild(btn);
|
|
870
|
+
els.themeMenu.appendChild(li);
|
|
871
|
+
}
|
|
872
|
+
refreshThemeToggle();
|
|
873
|
+
}
|
|
874
|
+
on(els.themeToggle, 'click', (e) => {
|
|
875
|
+
e.stopPropagation();
|
|
876
|
+
const opening = els.themeMenu.hidden;
|
|
877
|
+
closeSubMenus();
|
|
878
|
+
if (opening) {
|
|
879
|
+
els.themeMenu.hidden = false;
|
|
880
|
+
placeFlyout(els.themeMenu, 'left');
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
on(els.themeMenu, 'click', (e) => {
|
|
884
|
+
e.stopPropagation();
|
|
885
|
+
const btn = e.target.closest('.theme-opt');
|
|
886
|
+
if (!btn)
|
|
887
|
+
return;
|
|
888
|
+
const value = btn.getAttribute('data-value');
|
|
889
|
+
if (!value)
|
|
890
|
+
return;
|
|
891
|
+
state.view.theme = value;
|
|
892
|
+
state.view.overridden.theme = true;
|
|
893
|
+
refreshThemeToggle();
|
|
894
|
+
els.themeMenu.hidden = true;
|
|
895
|
+
postViewOverrides();
|
|
896
|
+
});
|
|
897
|
+
function refreshThemeToggle() {
|
|
898
|
+
if (state.view.theme === 'auto') {
|
|
899
|
+
els.themeToggle.textContent = `Auto \u25be`;
|
|
900
|
+
}
|
|
901
|
+
else {
|
|
902
|
+
els.themeToggle.innerHTML = '';
|
|
903
|
+
const chip = doc.createElement('code');
|
|
904
|
+
chip.className = 'code-chip';
|
|
905
|
+
chip.textContent = state.view.theme;
|
|
906
|
+
els.themeToggle.appendChild(chip);
|
|
907
|
+
els.themeToggle.appendChild(doc.createTextNode(` \u25be`));
|
|
908
|
+
}
|
|
909
|
+
for (const btn of Array.from(els.themeMenu.querySelectorAll('.theme-opt'))) {
|
|
910
|
+
btn.setAttribute('data-active', (btn.getAttribute('data-value') === state.view.theme).toString());
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
// ===== Now picker (calendar) =====
|
|
914
|
+
let calendarYear = new Date().getFullYear();
|
|
915
|
+
let calendarMonth = new Date().getMonth();
|
|
916
|
+
function buildCalendar() {
|
|
917
|
+
const picker = els.nowPicker;
|
|
918
|
+
picker.innerHTML = '';
|
|
919
|
+
const year = calendarYear;
|
|
920
|
+
const month = calendarMonth;
|
|
921
|
+
const nav = doc.createElement('div');
|
|
922
|
+
nav.className = 'cal-nav';
|
|
923
|
+
const prevBtn = doc.createElement('button');
|
|
924
|
+
prevBtn.className = 'btn cal-nav-btn';
|
|
925
|
+
prevBtn.textContent = '\u2039';
|
|
926
|
+
prevBtn.addEventListener('click', (e) => {
|
|
927
|
+
e.stopPropagation();
|
|
928
|
+
calendarMonth--;
|
|
929
|
+
if (calendarMonth < 0) {
|
|
930
|
+
calendarMonth = 11;
|
|
931
|
+
calendarYear--;
|
|
932
|
+
}
|
|
933
|
+
buildCalendar();
|
|
934
|
+
});
|
|
935
|
+
const heading = doc.createElement('span');
|
|
936
|
+
heading.className = 'cal-heading';
|
|
937
|
+
heading.textContent = new Intl.DateTimeFormat(state.locale, {
|
|
938
|
+
month: 'long',
|
|
939
|
+
year: 'numeric',
|
|
940
|
+
}).format(new Date(year, month, 1));
|
|
941
|
+
const nextBtn = doc.createElement('button');
|
|
942
|
+
nextBtn.className = 'btn cal-nav-btn';
|
|
943
|
+
nextBtn.textContent = '\u203a';
|
|
944
|
+
nextBtn.addEventListener('click', (e) => {
|
|
945
|
+
e.stopPropagation();
|
|
946
|
+
calendarMonth++;
|
|
947
|
+
if (calendarMonth > 11) {
|
|
948
|
+
calendarMonth = 0;
|
|
949
|
+
calendarYear++;
|
|
950
|
+
}
|
|
951
|
+
buildCalendar();
|
|
952
|
+
});
|
|
953
|
+
nav.appendChild(prevBtn);
|
|
954
|
+
nav.appendChild(heading);
|
|
955
|
+
nav.appendChild(nextBtn);
|
|
956
|
+
picker.appendChild(nav);
|
|
957
|
+
const grid = doc.createElement('div');
|
|
958
|
+
grid.className = 'cal-grid';
|
|
959
|
+
for (const d of ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']) {
|
|
960
|
+
const cell = doc.createElement('span');
|
|
961
|
+
cell.className = 'cal-dow';
|
|
962
|
+
cell.textContent = d;
|
|
963
|
+
grid.appendChild(cell);
|
|
964
|
+
}
|
|
965
|
+
const firstDow = new Date(year, month, 1).getDay();
|
|
966
|
+
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
|
967
|
+
for (let i = 0; i < firstDow; i++) {
|
|
968
|
+
const empty = doc.createElement('span');
|
|
969
|
+
empty.className = 'cal-empty';
|
|
970
|
+
grid.appendChild(empty);
|
|
971
|
+
}
|
|
972
|
+
const now = new Date();
|
|
973
|
+
const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
|
974
|
+
for (let d = 1; d <= daysInMonth; d++) {
|
|
975
|
+
const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
|
|
976
|
+
const btn = doc.createElement('button');
|
|
977
|
+
btn.className = 'btn cal-day';
|
|
978
|
+
btn.textContent = String(d);
|
|
979
|
+
if (dateStr === todayStr)
|
|
980
|
+
btn.classList.add('is-today');
|
|
981
|
+
if (state.view.now === dateStr)
|
|
982
|
+
btn.classList.add('is-selected');
|
|
983
|
+
btn.addEventListener('click', (e) => {
|
|
984
|
+
e.stopPropagation();
|
|
985
|
+
applyNow(dateStr);
|
|
986
|
+
});
|
|
987
|
+
grid.appendChild(btn);
|
|
988
|
+
}
|
|
989
|
+
picker.appendChild(grid);
|
|
990
|
+
const footer = doc.createElement('div');
|
|
991
|
+
footer.className = 'cal-footer';
|
|
992
|
+
const todayBtnEl = doc.createElement('button');
|
|
993
|
+
todayBtnEl.className = 'btn cal-footer-btn';
|
|
994
|
+
todayBtnEl.textContent = 'Today';
|
|
995
|
+
todayBtnEl.addEventListener('click', (e) => {
|
|
996
|
+
e.stopPropagation();
|
|
997
|
+
applyNow('today');
|
|
998
|
+
});
|
|
999
|
+
const noneBtnEl = doc.createElement('button');
|
|
1000
|
+
noneBtnEl.className = 'btn cal-footer-btn';
|
|
1001
|
+
noneBtnEl.textContent = 'None';
|
|
1002
|
+
noneBtnEl.addEventListener('click', (e) => {
|
|
1003
|
+
e.stopPropagation();
|
|
1004
|
+
applyNow('hide');
|
|
1005
|
+
});
|
|
1006
|
+
footer.appendChild(todayBtnEl);
|
|
1007
|
+
footer.appendChild(noneBtnEl);
|
|
1008
|
+
picker.appendChild(footer);
|
|
1009
|
+
}
|
|
1010
|
+
function applyNow(value) {
|
|
1011
|
+
state.view.now = value;
|
|
1012
|
+
state.view.overridden.now = true;
|
|
1013
|
+
refreshNowToggle();
|
|
1014
|
+
els.nowPicker.hidden = true;
|
|
1015
|
+
postViewOverrides();
|
|
1016
|
+
}
|
|
1017
|
+
on(els.nowToggle, 'click', (e) => {
|
|
1018
|
+
e.stopPropagation();
|
|
1019
|
+
const opening = els.nowPicker.hidden;
|
|
1020
|
+
closeSubMenus();
|
|
1021
|
+
if (opening) {
|
|
1022
|
+
buildCalendar();
|
|
1023
|
+
els.nowPicker.hidden = false;
|
|
1024
|
+
placeFlyout(els.nowPicker, 'left');
|
|
1025
|
+
}
|
|
1026
|
+
});
|
|
1027
|
+
function refreshNowToggle() {
|
|
1028
|
+
let label;
|
|
1029
|
+
if (state.view.now === 'today') {
|
|
1030
|
+
label = 'Today';
|
|
1031
|
+
}
|
|
1032
|
+
else if (state.view.now === 'hide') {
|
|
1033
|
+
label = 'None';
|
|
1034
|
+
}
|
|
1035
|
+
else {
|
|
1036
|
+
try {
|
|
1037
|
+
label = new Intl.DateTimeFormat(state.locale, { dateStyle: 'medium' }).format(new Date(`${state.view.now}T00:00:00`));
|
|
1038
|
+
}
|
|
1039
|
+
catch {
|
|
1040
|
+
label = state.view.now;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
els.nowLabel.textContent = label;
|
|
1044
|
+
}
|
|
1045
|
+
// ===== Show-links dropdown =====
|
|
1046
|
+
on(els.linksToggle, 'click', (e) => {
|
|
1047
|
+
e.stopPropagation();
|
|
1048
|
+
const opening = els.linksMenu.hidden;
|
|
1049
|
+
closeSubMenus();
|
|
1050
|
+
if (opening) {
|
|
1051
|
+
els.linksMenu.hidden = false;
|
|
1052
|
+
placeFlyout(els.linksMenu, 'left');
|
|
1053
|
+
}
|
|
1054
|
+
});
|
|
1055
|
+
on(els.linksMenu, 'click', (e) => {
|
|
1056
|
+
e.stopPropagation();
|
|
1057
|
+
const btn = e.target.closest('.links-opt');
|
|
1058
|
+
if (!btn)
|
|
1059
|
+
return;
|
|
1060
|
+
const value = btn.getAttribute('data-value');
|
|
1061
|
+
if (value === null)
|
|
1062
|
+
return;
|
|
1063
|
+
state.view.showLinks = value === 'true';
|
|
1064
|
+
state.view.overridden.showLinks = true;
|
|
1065
|
+
refreshLinksToggle();
|
|
1066
|
+
els.linksMenu.hidden = true;
|
|
1067
|
+
postViewOverrides();
|
|
1068
|
+
});
|
|
1069
|
+
function refreshLinksToggle() {
|
|
1070
|
+
els.linksToggle.textContent = `${state.view.showLinks ? 'Yes' : 'No'} \u25be`;
|
|
1071
|
+
for (const btn of Array.from(els.linksMenu.querySelectorAll('.links-opt'))) {
|
|
1072
|
+
btn.setAttribute('data-active', (btn.getAttribute('data-value') === (state.view.showLinks ? 'true' : 'false')).toString());
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
// ===== View-options helpers =====
|
|
1076
|
+
function postViewOverrides() {
|
|
1077
|
+
const overrides = {};
|
|
1078
|
+
if (state.view.overridden.theme)
|
|
1079
|
+
overrides.theme = state.view.theme;
|
|
1080
|
+
if (state.view.overridden.now)
|
|
1081
|
+
overrides.now = state.view.now;
|
|
1082
|
+
if (state.view.overridden.showLinks)
|
|
1083
|
+
overrides.showLinks = state.view.showLinks;
|
|
1084
|
+
options.onViewOptions?.(overrides);
|
|
1085
|
+
}
|
|
1086
|
+
function refreshAll() {
|
|
1087
|
+
refreshThemeToggle();
|
|
1088
|
+
refreshNowToggle();
|
|
1089
|
+
refreshLinksToggle();
|
|
1090
|
+
refreshFormatToggle();
|
|
1091
|
+
}
|
|
1092
|
+
// ===== Theme control visibility =====
|
|
1093
|
+
if (options.themeControl === 'hide') {
|
|
1094
|
+
const themeRow = els.themeMenu.closest('.theme-control-row');
|
|
1095
|
+
if (themeRow)
|
|
1096
|
+
themeRow.style.display = 'none';
|
|
1097
|
+
}
|
|
1098
|
+
// ===== Apply initial theme menu + baselines =====
|
|
1099
|
+
buildThemeMenu(options.availableThemes ?? ['light', 'dark', 'grayscale']);
|
|
623
1100
|
if (options.viewBaseline) {
|
|
624
1101
|
applyBaseline(state, options.viewBaseline, true);
|
|
625
|
-
refreshViewMenu();
|
|
626
|
-
}
|
|
627
|
-
else {
|
|
628
|
-
refreshViewMenu();
|
|
629
1102
|
}
|
|
1103
|
+
refreshAll();
|
|
630
1104
|
updateMinimapVisibility();
|
|
1105
|
+
// Anchor the toolbar explicitly (default: upper-right) so resize
|
|
1106
|
+
// shifts it left rather than the CSS `right` anchor squishing it.
|
|
1107
|
+
repositionChrome();
|
|
631
1108
|
// ===== Imperative API =====
|
|
632
1109
|
return {
|
|
633
1110
|
setSvg,
|
|
@@ -635,7 +1112,7 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
635
1112
|
setFatal: showFatal,
|
|
636
1113
|
setViewBaseline(baseline, resetOverrides = false) {
|
|
637
1114
|
applyBaseline(state, baseline, resetOverrides);
|
|
638
|
-
|
|
1115
|
+
refreshAll();
|
|
639
1116
|
},
|
|
640
1117
|
setDefaultFit(fit) {
|
|
641
1118
|
state.defaultFit = fit;
|
|
@@ -645,6 +1122,17 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
645
1122
|
state.minimapDismissedThisSession = false;
|
|
646
1123
|
updateMinimapVisibility();
|
|
647
1124
|
},
|
|
1125
|
+
setMode(mode) {
|
|
1126
|
+
const resolved = mode === 'system' ? resolveSystemMode() : mode;
|
|
1127
|
+
applyMode(resolved);
|
|
1128
|
+
},
|
|
1129
|
+
setAvailableThemes(themes) {
|
|
1130
|
+
buildThemeMenu(themes);
|
|
1131
|
+
},
|
|
1132
|
+
setLocale(locale) {
|
|
1133
|
+
state.locale = locale;
|
|
1134
|
+
refreshNowToggle();
|
|
1135
|
+
},
|
|
648
1136
|
fitPage,
|
|
649
1137
|
fitWidth,
|
|
650
1138
|
actualSize,
|
|
@@ -653,15 +1141,19 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
653
1141
|
},
|
|
654
1142
|
setZoom(scale) {
|
|
655
1143
|
state.activeFit = 'manual';
|
|
1144
|
+
state.isDirty = true;
|
|
656
1145
|
setScale(scale);
|
|
657
1146
|
},
|
|
658
1147
|
dispose() {
|
|
659
1148
|
if (fadeTimer)
|
|
660
1149
|
clearTimeout(fadeTimer);
|
|
1150
|
+
if (resizeTimer)
|
|
1151
|
+
clearTimeout(resizeTimer);
|
|
661
1152
|
for (const c of cleanups)
|
|
662
1153
|
c();
|
|
663
1154
|
cleanups.length = 0;
|
|
664
1155
|
rootEl.classList.remove('nl-preview-root');
|
|
1156
|
+
rootEl.removeAttribute('data-nl-mode');
|
|
665
1157
|
rootEl.innerHTML = '';
|
|
666
1158
|
},
|
|
667
1159
|
};
|
|
@@ -671,10 +1163,15 @@ function applyBaseline(state, baseline, resetOverrides) {
|
|
|
671
1163
|
state.view.theme = baseline.theme;
|
|
672
1164
|
}
|
|
673
1165
|
if (baseline.now !== undefined && !state.view.overridden.now) {
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
1166
|
+
if (baseline.now === 'none' || baseline.now === 'hide') {
|
|
1167
|
+
state.view.now = 'hide';
|
|
1168
|
+
}
|
|
1169
|
+
else if (typeof baseline.now === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(baseline.now)) {
|
|
1170
|
+
state.view.now = baseline.now;
|
|
1171
|
+
}
|
|
1172
|
+
else {
|
|
1173
|
+
state.view.now = 'today';
|
|
1174
|
+
}
|
|
678
1175
|
}
|
|
679
1176
|
if (baseline.showLinks !== undefined && !state.view.overridden.showLinks) {
|
|
680
1177
|
state.view.showLinks = baseline.showLinks !== false;
|