@markdstage/markdstage 3.8.0 → 3.8.1
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markdstage/markdstage",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.1",
|
|
4
4
|
"description": "Present, validate, inspect, capture, and export MarkdStage Markdown decks from the command line — no Copilot canvas required.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "runceel",
|
|
@@ -116,12 +116,12 @@
|
|
|
116
116
|
<div class="presenter-previews">
|
|
117
117
|
<section class="presenter-preview presenter-preview-current" aria-labelledby="presenterCurrentLabel">
|
|
118
118
|
<h2 id="presenterCurrentLabel">Current slide</h2>
|
|
119
|
-
<
|
|
119
|
+
<div id="presenterCurrentViewport" class="presenter-frame slide-viewport"></div>
|
|
120
120
|
</section>
|
|
121
121
|
<div class="presenter-sidebar">
|
|
122
122
|
<section class="presenter-preview" aria-labelledby="presenterNextLabel">
|
|
123
123
|
<h2 id="presenterNextLabel">Next slide</h2>
|
|
124
|
-
<
|
|
124
|
+
<div id="presenterNextViewport" class="presenter-frame slide-viewport"></div>
|
|
125
125
|
<p id="presenterNextEmpty" class="presenter-next-empty" hidden>There is no next slide</p>
|
|
126
126
|
</section>
|
|
127
127
|
<section class="presenter-notes" aria-labelledby="presenterNotesLabel">
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
stripSpeakerNotes,
|
|
21
21
|
} from "./speaker-notes.mjs";
|
|
22
22
|
import { splitImportPath } from "./import-path.mjs";
|
|
23
|
+
import { createSlideViewport, OUTPUT_WIDTH, OUTPUT_HEIGHT } from "./slide-viewport.mjs";
|
|
23
24
|
|
|
24
25
|
// Client-side slide renderer for the MarkdStage canvas.
|
|
25
26
|
//
|
|
@@ -105,6 +106,11 @@ let previewMode = false;
|
|
|
105
106
|
let previewOffset = 0;
|
|
106
107
|
let navigationEnabled = true;
|
|
107
108
|
let fixedPreviewMode = false;
|
|
109
|
+
let surfaceMode = false;
|
|
110
|
+
let outputViewport = null;
|
|
111
|
+
let currentViewport = null;
|
|
112
|
+
let nextViewport = null;
|
|
113
|
+
let surfaceDiagnostic = null;
|
|
108
114
|
let moreControlsOpen = false;
|
|
109
115
|
// Markdown for the most recently rendered slide, retained for editing-mode rerenders.
|
|
110
116
|
let lastMarkdown = "";
|
|
@@ -122,8 +128,6 @@ let layoutFrame = 0;
|
|
|
122
128
|
// past clientHeight, which is invisible but still enough for `overflow:auto` to
|
|
123
129
|
// draw a scrollbar.
|
|
124
130
|
const SCROLL_EPSILON = 2;
|
|
125
|
-
const OUTPUT_WIDTH = 1280;
|
|
126
|
-
const OUTPUT_HEIGHT = 720;
|
|
127
131
|
const PPTX_LAYOUT_NAMES = ["title", "default", "center", "section", "backcover"];
|
|
128
132
|
const LAYOUT_HINT_LIMIT = 5;
|
|
129
133
|
|
|
@@ -268,11 +272,8 @@ function elementPath(element, root) {
|
|
|
268
272
|
return parts.join(" > ");
|
|
269
273
|
}
|
|
270
274
|
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
// layout space. Every rect-derived delta is divided by this factor before the two kinds of
|
|
274
|
-
// measurement are combined, keeping the preview, PDF, and PNG diagnostics in one coordinate
|
|
275
|
-
// system.
|
|
275
|
+
// Normalize any content-authored transform before combining rect and scroll metrics.
|
|
276
|
+
// Display scaling happens outside the fixed browsing context and is not measured here.
|
|
276
277
|
function layoutScale(deck) {
|
|
277
278
|
const width = deck.offsetWidth;
|
|
278
279
|
if (!width) return 1;
|
|
@@ -430,7 +431,9 @@ function updateFixedPreviewWarning() {
|
|
|
430
431
|
const warning = document.getElementById("layoutWarning");
|
|
431
432
|
const button = document.getElementById("navFixedPreview");
|
|
432
433
|
const empty = document.body.classList.contains("markdstage-empty");
|
|
433
|
-
|
|
434
|
+
const diagnostic = fixedPreviewMode && !presenterMode &&
|
|
435
|
+
(surfaceDiagnostic || (layoutTarget && collectSlideLayout(layoutTarget, navIndex)));
|
|
436
|
+
if (!fixedPreviewMode || !diagnostic || empty || presenterMode) {
|
|
434
437
|
document.body.classList.remove("fixed-preview-overflow");
|
|
435
438
|
if (warning) {
|
|
436
439
|
warning.hidden = true;
|
|
@@ -441,7 +444,6 @@ function updateFixedPreviewWarning() {
|
|
|
441
444
|
return;
|
|
442
445
|
}
|
|
443
446
|
|
|
444
|
-
const diagnostic = collectSlideLayout(layoutTarget, navIndex);
|
|
445
447
|
document.body.classList.toggle("fixed-preview-overflow", diagnostic.pdfClipped);
|
|
446
448
|
if (button) button.dataset.state = diagnostic.pdfClipped ? "error" : "active";
|
|
447
449
|
syncMoreControls();
|
|
@@ -462,20 +464,17 @@ function updateFixedPreviewWarning() {
|
|
|
462
464
|
warning.hidden = false;
|
|
463
465
|
}
|
|
464
466
|
|
|
465
|
-
function updateFixedPreviewScale() {
|
|
466
|
-
if (!fixedPreviewMode) return;
|
|
467
|
-
const availableWidth = Math.max(1, window.innerWidth - 24);
|
|
468
|
-
const availableHeight = Math.max(1, window.innerHeight - 88);
|
|
469
|
-
const scale = Math.min(availableWidth / OUTPUT_WIDTH, availableHeight / OUTPUT_HEIGHT, 1);
|
|
470
|
-
document.body.style.setProperty("--fixed-preview-scale", String(scale));
|
|
471
|
-
}
|
|
472
|
-
|
|
473
467
|
function refreshLayout() {
|
|
474
468
|
const target = layoutTarget;
|
|
475
469
|
if (!target || !target.deck.isConnected) return;
|
|
476
470
|
if (target.autoSize) applyAutoSize(target.deck, target.bodyEl);
|
|
477
471
|
updateBodyScroll(target.bodyEl);
|
|
478
472
|
updateFixedPreviewWarning();
|
|
473
|
+
if (surfaceMode && !document.body.classList.contains("mermaid-loading")) {
|
|
474
|
+
window.frameElement?.dispatchEvent(new CustomEvent("slide-layout", {
|
|
475
|
+
detail: collectSlideLayout(target, navIndex),
|
|
476
|
+
}));
|
|
477
|
+
}
|
|
479
478
|
}
|
|
480
479
|
|
|
481
480
|
// Coalesce the (re)layout into one frame: several triggers — render, resize,
|
|
@@ -965,6 +964,19 @@ function renderSlide(markdown) {
|
|
|
965
964
|
architectureEditors = [];
|
|
966
965
|
applyCustomThemeCss(customThemeCss);
|
|
967
966
|
const token = ++renderToken;
|
|
967
|
+
if (!surfaceMode && (fixedPreviewMode || presenterViewOpen)) {
|
|
968
|
+
const { meta } = splitFrontMatter(lastMarkdown);
|
|
969
|
+
document.title = meta.title || meta.deck || "Slide";
|
|
970
|
+
document.documentElement.setAttribute("data-theme",
|
|
971
|
+
deckThemeLocked ? deckTheme : normalizeTheme(meta.theme || deckTheme));
|
|
972
|
+
layoutTarget = null;
|
|
973
|
+
surfaceDiagnostic = null;
|
|
974
|
+
if (layoutFrame) cancelAnimationFrame(layoutFrame);
|
|
975
|
+
layoutFrame = 0;
|
|
976
|
+
if (!presenterViewRequested || !presenterViewAvailable || navTotal <= 0) updateSlideViewports();
|
|
977
|
+
updateFixedPreviewWarning();
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
968
980
|
const slide = createSlide(markdown, deckTheme);
|
|
969
981
|
document.title = slide.title;
|
|
970
982
|
document.documentElement.setAttribute("data-theme", slide.theme);
|
|
@@ -1000,13 +1012,23 @@ function renderSlide(markdown) {
|
|
|
1000
1012
|
.catch((error) => {
|
|
1001
1013
|
if (token !== renderToken) return;
|
|
1002
1014
|
console.error(error.message);
|
|
1003
|
-
|
|
1015
|
+
if (surfaceMode) {
|
|
1016
|
+
window.frameElement?.dispatchEvent(new CustomEvent("slide-error", { detail: error.message }));
|
|
1017
|
+
} else {
|
|
1018
|
+
showExportNotification("error", error.message);
|
|
1019
|
+
}
|
|
1004
1020
|
});
|
|
1005
|
-
const mermaid =
|
|
1021
|
+
const mermaid = Promise.resolve(document.fonts?.ready).then(() => {
|
|
1022
|
+
if (token === renderToken) return runMermaid(slide.bodyEl, slide.deck, token, false);
|
|
1023
|
+
}).finally(() => {
|
|
1006
1024
|
if (token === renderToken) scheduleLayoutRefresh();
|
|
1007
1025
|
});
|
|
1008
|
-
Promise.all([mermaid, images]).finally(() => {
|
|
1009
|
-
|
|
1026
|
+
Promise.all([mermaid, images]).finally(async () => {
|
|
1027
|
+
await afterLayout();
|
|
1028
|
+
if (token === renderToken) {
|
|
1029
|
+
document.body.classList.remove("mermaid-loading");
|
|
1030
|
+
refreshLayout();
|
|
1031
|
+
}
|
|
1010
1032
|
});
|
|
1011
1033
|
}
|
|
1012
1034
|
|
|
@@ -3149,7 +3171,7 @@ async function fetchDeck() {
|
|
|
3149
3171
|
function setArchitectureEditMode(enabled) {
|
|
3150
3172
|
const next = Boolean(enabled) && architectureEditAvailable && !presenterMode;
|
|
3151
3173
|
if (next === architectureEditMode) return false;
|
|
3152
|
-
if (next && fixedPreviewMode) setFixedPreviewMode(false);
|
|
3174
|
+
if (next && fixedPreviewMode) setFixedPreviewMode(false, { rerender: false });
|
|
3153
3175
|
architectureEditMode = next;
|
|
3154
3176
|
document.body.classList.toggle("architecture-edit-mode", next);
|
|
3155
3177
|
updateArchitectureEditButton(next);
|
|
@@ -3255,7 +3277,8 @@ async function requestArchitectureEditMode(enabled) {
|
|
|
3255
3277
|
}
|
|
3256
3278
|
|
|
3257
3279
|
function architectureDiagramOptions() {
|
|
3258
|
-
|
|
3280
|
+
const slideDocument = document.getElementById("outputFrame")?.contentDocument || document;
|
|
3281
|
+
return [...slideDocument.querySelectorAll(".architecture-diagram[data-architecture-block]")].map(
|
|
3259
3282
|
(wrapper, index) => ({
|
|
3260
3283
|
block: Number(wrapper.dataset.architectureBlock),
|
|
3261
3284
|
title: wrapper.dataset.architectureTitle || `Diagram ${index + 1}`,
|
|
@@ -3581,6 +3604,10 @@ async function fetchState() {
|
|
|
3581
3604
|
// re-fetches /state for an instant update (without waiting for the SSE nudge).
|
|
3582
3605
|
async function navigate(payload) {
|
|
3583
3606
|
if (!navigationEnabled) return;
|
|
3607
|
+
if (surfaceMode) {
|
|
3608
|
+
window.frameElement?.dispatchEvent(new CustomEvent("slide-navigate", { detail: payload }));
|
|
3609
|
+
return;
|
|
3610
|
+
}
|
|
3584
3611
|
try {
|
|
3585
3612
|
const res = await fetch("./navigate", {
|
|
3586
3613
|
method: "POST",
|
|
@@ -3900,26 +3927,29 @@ async function exportFromCanvas(format, { mermaidImageFallback = false } = {}) {
|
|
|
3900
3927
|
}
|
|
3901
3928
|
}
|
|
3902
3929
|
|
|
3903
|
-
function setFixedPreviewMode(enabled) {
|
|
3904
|
-
fixedPreviewMode = Boolean(enabled);
|
|
3930
|
+
function setFixedPreviewMode(enabled, { rerender = true } = {}) {
|
|
3931
|
+
fixedPreviewMode = presenterMode || Boolean(enabled);
|
|
3905
3932
|
document.body.classList.toggle("fixed-preview-mode", fixedPreviewMode);
|
|
3906
|
-
document.body.classList.toggle("
|
|
3933
|
+
document.body.classList.toggle("responsive-preview-mode", !fixedPreviewMode);
|
|
3907
3934
|
const button = document.getElementById("navFixedPreview");
|
|
3908
3935
|
if (button) {
|
|
3909
3936
|
button.setAttribute("aria-pressed", fixedPreviewMode ? "true" : "false");
|
|
3910
3937
|
button.dataset.state = fixedPreviewMode ? "active" : "";
|
|
3911
3938
|
button.title = fixedPreviewMode
|
|
3912
|
-
? "
|
|
3939
|
+
? "Switch to responsive editing layout (not output-equivalent)"
|
|
3913
3940
|
: "Preview PDF layout at 16:9";
|
|
3941
|
+
button.querySelector(".nav-more-label").textContent =
|
|
3942
|
+
fixedPreviewMode ? "Output preview" : "Responsive editing";
|
|
3914
3943
|
}
|
|
3915
3944
|
syncMoreControls();
|
|
3916
|
-
if (fixedPreviewMode) {
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
document.
|
|
3945
|
+
if (!fixedPreviewMode) {
|
|
3946
|
+
outputViewport?.dispose();
|
|
3947
|
+
outputViewport = null;
|
|
3948
|
+
document.getElementById("stage").classList.remove("slide-viewport");
|
|
3920
3949
|
document.body.classList.remove("fixed-preview-overflow");
|
|
3921
3950
|
}
|
|
3922
|
-
|
|
3951
|
+
surfaceDiagnostic = null;
|
|
3952
|
+
if (rerender && currentVersion >= 0) renderSlide(lastMarkdown);
|
|
3923
3953
|
updateFixedPreviewWarning();
|
|
3924
3954
|
}
|
|
3925
3955
|
|
|
@@ -3927,6 +3957,103 @@ function toggleFixedPreviewMode() {
|
|
|
3927
3957
|
setFixedPreviewMode(!fixedPreviewMode);
|
|
3928
3958
|
}
|
|
3929
3959
|
|
|
3960
|
+
function slideViewportState(markdown, index, interactive) {
|
|
3961
|
+
return {
|
|
3962
|
+
markdown, index, total: navTotal, theme: deckTheme, themeLocked: deckThemeLocked,
|
|
3963
|
+
customThemeCss, customThemeMeta, navigationEnabled: interactive,
|
|
3964
|
+
};
|
|
3965
|
+
}
|
|
3966
|
+
|
|
3967
|
+
function mountSlideViewport(host, id, title, interactive) {
|
|
3968
|
+
return createSlideViewport(host, {
|
|
3969
|
+
id, title,
|
|
3970
|
+
onNavigate: (payload) => { if (interactive) navigate(payload); },
|
|
3971
|
+
onPointer: () => { if (interactive) setMoreControlsOpen(false); },
|
|
3972
|
+
onError: (message) => showExportNotification("error", message),
|
|
3973
|
+
onKey: (detail) => {
|
|
3974
|
+
if (!interactive) return;
|
|
3975
|
+
const event = new KeyboardEvent("keydown", { ...detail, bubbles: true, cancelable: true });
|
|
3976
|
+
document.dispatchEvent(event);
|
|
3977
|
+
detail.handled = event.defaultPrevented;
|
|
3978
|
+
},
|
|
3979
|
+
onLayout: (diagnostic) => {
|
|
3980
|
+
if (id === "presenterNext") return;
|
|
3981
|
+
surfaceDiagnostic = diagnostic;
|
|
3982
|
+
document.body.classList.remove("mermaid-loading");
|
|
3983
|
+
updateFixedPreviewWarning();
|
|
3984
|
+
},
|
|
3985
|
+
});
|
|
3986
|
+
}
|
|
3987
|
+
|
|
3988
|
+
function updateSlideViewports() {
|
|
3989
|
+
const stage = document.getElementById("stage");
|
|
3990
|
+
document.body.classList.add("mermaid-loading");
|
|
3991
|
+
if (presenterViewOpen) {
|
|
3992
|
+
stage.replaceChildren();
|
|
3993
|
+
currentViewport ??= mountSlideViewport(
|
|
3994
|
+
document.getElementById("presenterCurrentViewport"), "presenterCurrent", "Current slide", true,
|
|
3995
|
+
);
|
|
3996
|
+
currentViewport.setState(slideViewportState(lastMarkdown, navIndex, true));
|
|
3997
|
+
const hasNext = navMode !== "deck" || navIndex < navTotal - 1;
|
|
3998
|
+
if (hasNext) {
|
|
3999
|
+
const index = Math.min(navIndex + 1, navTotal - 1);
|
|
4000
|
+
nextViewport ??= mountSlideViewport(
|
|
4001
|
+
document.getElementById("presenterNextViewport"), "presenterNext", "Next slide", false,
|
|
4002
|
+
);
|
|
4003
|
+
nextViewport.setState(slideViewportState(deckSlides[index] || "", index, false));
|
|
4004
|
+
}
|
|
4005
|
+
} else {
|
|
4006
|
+
if (!outputViewport) {
|
|
4007
|
+
stage.replaceChildren();
|
|
4008
|
+
stage.classList.add("slide-viewport");
|
|
4009
|
+
outputViewport = mountSlideViewport(stage, "outputFrame", "Slide", navigationEnabled);
|
|
4010
|
+
}
|
|
4011
|
+
outputViewport.setState(slideViewportState(lastMarkdown, navIndex, navigationEnabled));
|
|
4012
|
+
}
|
|
4013
|
+
}
|
|
4014
|
+
|
|
4015
|
+
function initSlideSurface() {
|
|
4016
|
+
surfaceMode = true;
|
|
4017
|
+
presenterMode = true;
|
|
4018
|
+
document.body.classList.add("surface-mode", "fixed-output-mode", "presenter-mode", "preview-mode");
|
|
4019
|
+
let previousState = "";
|
|
4020
|
+
window.__markdstageSurface = {
|
|
4021
|
+
render(state) {
|
|
4022
|
+
const key = JSON.stringify(state);
|
|
4023
|
+
if (key === previousState) {
|
|
4024
|
+
refreshLayout();
|
|
4025
|
+
return;
|
|
4026
|
+
}
|
|
4027
|
+
previousState = key;
|
|
4028
|
+
deckTheme = state.theme;
|
|
4029
|
+
deckThemeLocked = state.themeLocked;
|
|
4030
|
+
applyCustomThemeCss(state.customThemeCss);
|
|
4031
|
+
customThemeMeta = state.customThemeMeta;
|
|
4032
|
+
navIndex = state.index;
|
|
4033
|
+
navTotal = state.total;
|
|
4034
|
+
navigationEnabled = state.navigationEnabled;
|
|
4035
|
+
renderSlide(state.markdown);
|
|
4036
|
+
},
|
|
4037
|
+
};
|
|
4038
|
+
wirePointerNavigation();
|
|
4039
|
+
document.addEventListener("pointerdown", () => {
|
|
4040
|
+
window.frameElement?.dispatchEvent(new CustomEvent("slide-pointer"));
|
|
4041
|
+
});
|
|
4042
|
+
document.addEventListener("keydown", (event) => {
|
|
4043
|
+
if (!navigationEnabled || event.defaultPrevented) return;
|
|
4044
|
+
const target = event.target;
|
|
4045
|
+
if (target?.closest("input, textarea, select, [contenteditable], button, a")) return;
|
|
4046
|
+
const detail = {
|
|
4047
|
+
key: event.key, code: event.code, repeat: event.repeat,
|
|
4048
|
+
ctrlKey: event.ctrlKey, altKey: event.altKey, metaKey: event.metaKey, shiftKey: event.shiftKey,
|
|
4049
|
+
};
|
|
4050
|
+
window.frameElement?.dispatchEvent(new CustomEvent("slide-key", { detail }));
|
|
4051
|
+
if (detail.handled) event.preventDefault();
|
|
4052
|
+
});
|
|
4053
|
+
window.addEventListener("resize", scheduleLayoutRefresh);
|
|
4054
|
+
document.fonts?.addEventListener("loadingdone", scheduleLayoutRefresh);
|
|
4055
|
+
}
|
|
4056
|
+
|
|
3930
4057
|
function updateNav() {
|
|
3931
4058
|
const nav = document.getElementById("nav");
|
|
3932
4059
|
if (!nav) return;
|
|
@@ -3956,14 +4083,9 @@ function openPresenterView() {
|
|
|
3956
4083
|
document.body.classList.add("presenter-view-mode");
|
|
3957
4084
|
const view = document.getElementById("presenterView");
|
|
3958
4085
|
if (view) view.hidden = false;
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
current.setAttribute("src", "./?preview=1&offset=0&navigate=1");
|
|
3963
|
-
}
|
|
3964
|
-
if (next && !next.getAttribute("src")) {
|
|
3965
|
-
next.setAttribute("src", "./?preview=1&offset=1");
|
|
3966
|
-
}
|
|
4086
|
+
outputViewport?.dispose();
|
|
4087
|
+
outputViewport = null;
|
|
4088
|
+
renderSlide(lastMarkdown);
|
|
3967
4089
|
updatePresenterView();
|
|
3968
4090
|
}
|
|
3969
4091
|
|
|
@@ -3972,6 +4094,11 @@ function closePresenterView() {
|
|
|
3972
4094
|
document.body.classList.remove("presenter-view-mode");
|
|
3973
4095
|
const view = document.getElementById("presenterView");
|
|
3974
4096
|
if (view) view.hidden = true;
|
|
4097
|
+
currentViewport?.dispose();
|
|
4098
|
+
nextViewport?.dispose();
|
|
4099
|
+
currentViewport = null;
|
|
4100
|
+
nextViewport = null;
|
|
4101
|
+
renderSlide(lastMarkdown);
|
|
3975
4102
|
document.getElementById("navMore")?.focus();
|
|
3976
4103
|
}
|
|
3977
4104
|
|
|
@@ -3984,7 +4111,7 @@ function updatePresenterView() {
|
|
|
3984
4111
|
if (prev) prev.disabled = navMode === "deck" && navIndex <= 0;
|
|
3985
4112
|
if (next) next.disabled = navMode === "deck" && navIndex >= navTotal - 1;
|
|
3986
4113
|
const hasNext = navMode !== "deck" || navIndex < navTotal - 1;
|
|
3987
|
-
const nextFrame = document.getElementById("
|
|
4114
|
+
const nextFrame = document.getElementById("presenterNextViewport");
|
|
3988
4115
|
const nextEmpty = document.getElementById("presenterNextEmpty");
|
|
3989
4116
|
if (nextFrame) nextFrame.hidden = !hasNext;
|
|
3990
4117
|
if (nextEmpty) nextEmpty.hidden = hasNext;
|
|
@@ -4512,6 +4639,10 @@ function init() {
|
|
|
4512
4639
|
} catch (_) {}
|
|
4513
4640
|
|
|
4514
4641
|
const params = new URLSearchParams(window.location.search);
|
|
4642
|
+
if (params.get("surface") === "1") {
|
|
4643
|
+
initSlideSurface();
|
|
4644
|
+
return;
|
|
4645
|
+
}
|
|
4515
4646
|
presenterViewRequested = params.get("presenter") === "1";
|
|
4516
4647
|
if (params.get("pptx") === "1") {
|
|
4517
4648
|
initPptx(params).catch(reportPptxBootstrapFailure);
|
|
@@ -4546,15 +4677,12 @@ function init() {
|
|
|
4546
4677
|
requestArchitectureEditMode(true);
|
|
4547
4678
|
}
|
|
4548
4679
|
|
|
4549
|
-
//
|
|
4550
|
-
//
|
|
4551
|
-
|
|
4552
|
-
if (
|
|
4553
|
-
!presenterMode &&
|
|
4554
|
-
!presenterViewRequested &&
|
|
4555
|
-
params.get("responsive") !== "1"
|
|
4556
|
-
) {
|
|
4680
|
+
// Only the explicitly requested editing layout is responsive. Audience and
|
|
4681
|
+
// Desktop previews use the same isolated surface as Canvas and CLI previews.
|
|
4682
|
+
if (presenterMode || params.get("responsive") !== "1") {
|
|
4557
4683
|
setFixedPreviewMode(true);
|
|
4684
|
+
} else {
|
|
4685
|
+
setFixedPreviewMode(false);
|
|
4558
4686
|
}
|
|
4559
4687
|
|
|
4560
4688
|
updateArchitectureEditButton();
|
|
@@ -4564,7 +4692,6 @@ function init() {
|
|
|
4564
4692
|
wirePreviewKeyboardNavigation();
|
|
4565
4693
|
}
|
|
4566
4694
|
window.addEventListener("resize", () => {
|
|
4567
|
-
updateFixedPreviewScale();
|
|
4568
4695
|
scheduleLayoutRefresh();
|
|
4569
4696
|
});
|
|
4570
4697
|
if (document.fonts?.ready) {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export const OUTPUT_WIDTH = 1280;
|
|
2
|
+
export const OUTPUT_HEIGHT = 720;
|
|
3
|
+
|
|
4
|
+
// Scale the browsing context, not its contents: SVG text measurement and viewport
|
|
5
|
+
// units must see the same untransformed geometry at every display size.
|
|
6
|
+
export function createSlideViewport(host, { id, title, onNavigate, onKey, onPointer, onLayout, onError }) {
|
|
7
|
+
const frame = document.createElement("iframe");
|
|
8
|
+
frame.id = id;
|
|
9
|
+
frame.title = title;
|
|
10
|
+
frame.className = "slide-viewport-frame";
|
|
11
|
+
frame.width = OUTPUT_WIDTH;
|
|
12
|
+
frame.height = OUTPUT_HEIGHT;
|
|
13
|
+
frame.src = "./?surface=1";
|
|
14
|
+
let state = null;
|
|
15
|
+
let loaded = false;
|
|
16
|
+
const render = () => {
|
|
17
|
+
if (loaded && state) frame.contentWindow.__markdstageSurface.render(state);
|
|
18
|
+
};
|
|
19
|
+
frame.addEventListener("load", () => {
|
|
20
|
+
loaded = true;
|
|
21
|
+
render();
|
|
22
|
+
});
|
|
23
|
+
frame.addEventListener("slide-navigate", (event) => onNavigate?.(event.detail));
|
|
24
|
+
frame.addEventListener("slide-key", (event) => onKey?.(event.detail));
|
|
25
|
+
frame.addEventListener("slide-pointer", () => onPointer?.());
|
|
26
|
+
frame.addEventListener("slide-layout", (event) => onLayout?.(event.detail));
|
|
27
|
+
frame.addEventListener("slide-error", (event) => onError?.(event.detail));
|
|
28
|
+
const resize = () => {
|
|
29
|
+
const scale = Math.min(host.clientWidth / OUTPUT_WIDTH, host.clientHeight / OUTPUT_HEIGHT);
|
|
30
|
+
frame.style.transform = `translate(-50%, -50%) scale(${scale})`;
|
|
31
|
+
};
|
|
32
|
+
const observer = new ResizeObserver(resize);
|
|
33
|
+
host.appendChild(frame);
|
|
34
|
+
observer.observe(host);
|
|
35
|
+
resize();
|
|
36
|
+
return {
|
|
37
|
+
setState(next) {
|
|
38
|
+
state = next;
|
|
39
|
+
frame.tabIndex = next.navigationEnabled ? 0 : -1;
|
|
40
|
+
render();
|
|
41
|
+
},
|
|
42
|
+
dispose() {
|
|
43
|
+
observer.disconnect();
|
|
44
|
+
frame.remove();
|
|
45
|
+
state = null;
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -95,10 +95,14 @@
|
|
|
95
95
|
html,body{height:100%;margin:0;overflow:hidden}
|
|
96
96
|
#stage{height:100%}
|
|
97
97
|
body.fixed-preview-mode #stage{
|
|
98
|
-
position:fixed;
|
|
99
|
-
transform:translate(-50%,-50%) scale(var(--fixed-preview-scale,1));
|
|
100
|
-
transform-origin:center;box-shadow:0 18px 60px rgba(0,0,0,.38);
|
|
98
|
+
position:fixed;inset:12px 12px 76px;height:auto;
|
|
101
99
|
}
|
|
100
|
+
.slide-viewport{position:relative;overflow:hidden;}
|
|
101
|
+
.slide-viewport-frame{position:absolute;left:50%;top:50%;width:1280px;height:720px;
|
|
102
|
+
max-width:none;max-height:none;border:0;transform-origin:center;display:block;}
|
|
103
|
+
body.presenter-mode.fixed-preview-mode #stage{inset:0;background:#000;}
|
|
104
|
+
body.surface-mode #stage{width:1280px;height:720px;}
|
|
105
|
+
body.surface-mode #nav,body.surface-mode .layout-warning{display:none!important;}
|
|
102
106
|
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
|
|
103
107
|
clip:rect(0,0,0,0);white-space:nowrap;border:0;}
|
|
104
108
|
body{
|
|
@@ -493,6 +497,7 @@ body.presenter-mode #navPresenterView{display:none;}
|
|
|
493
497
|
body.presenter-mode #navExport,body.presenter-mode #navExportPptx{display:none;}
|
|
494
498
|
body.presenter-mode #navImport{display:none;}
|
|
495
499
|
body.presenter-mode #navSourceMode{display:none;}
|
|
500
|
+
body.presenter-mode #navFixedPreview{display:none;}
|
|
496
501
|
body.preview-mode .nav,body.preview-mode .overview{display:none!important;}
|
|
497
502
|
/* When no deck is loaded, leave only the load button as the available action. */
|
|
498
503
|
.nav.nav-empty .nav-main,.nav.nav-empty #navMore{display:none;}
|
|
@@ -634,7 +639,7 @@ body.presenter-view-mode .layout-warning{display:none;}
|
|
|
634
639
|
overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
635
640
|
|
|
636
641
|
/* ===== Fixed 16:9 output surface ===== */
|
|
637
|
-
/* PDF, PNG capture, and
|
|
642
|
+
/* PDF, PNG capture, and all fixed slide viewports share these rules. At the
|
|
638
643
|
CSS reference pixel density, 1280x720 equals the PDF's 13.333333in x 7.5in. */
|
|
639
644
|
body.fixed-output-mode .deck{
|
|
640
645
|
width:1280px;height:720px;min-width:1280px;max-width:1280px;
|