hono-decks 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +287 -0
- package/dist/advanced.d.ts +62 -0
- package/dist/advanced.js +3162 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +2331 -0
- package/dist/cli.d.ts +20 -0
- package/dist/cli.js +2334 -0
- package/dist/client.d.ts +12 -0
- package/dist/client.js +22 -0
- package/dist/define-decks-U4NxIs66.d.ts +587 -0
- package/dist/jsx-renderer-BO-N4tMZ.d.ts +20 -0
- package/dist/mod.d.ts +39 -0
- package/dist/mod.js +1071 -0
- package/dist/node.d.ts +181 -0
- package/dist/node.js +5625 -0
- package/dist/vite.d.ts +19 -0
- package/dist/vite.js +2175 -0
- package/package.json +93 -0
package/dist/advanced.js
ADDED
|
@@ -0,0 +1,3162 @@
|
|
|
1
|
+
// src/source/manifest-source.ts
|
|
2
|
+
function manifestDeckSource(manifest) {
|
|
3
|
+
const decks = new Map(manifest.decks.map((deck) => [deck.slug, deck]));
|
|
4
|
+
return {
|
|
5
|
+
async listDecks() {
|
|
6
|
+
return manifest.decks.map((deck) => ({
|
|
7
|
+
slug: deck.slug,
|
|
8
|
+
title: deck.meta.title,
|
|
9
|
+
description: deck.meta.description,
|
|
10
|
+
draft: deck.meta.draft,
|
|
11
|
+
sourcePath: deck.sourcePath
|
|
12
|
+
}));
|
|
13
|
+
},
|
|
14
|
+
async getCompiledDeck(_c, slug) {
|
|
15
|
+
return decks.get(slug) ?? null;
|
|
16
|
+
},
|
|
17
|
+
async getAsset(_c, slug, assetPath) {
|
|
18
|
+
const deck = decks.get(slug);
|
|
19
|
+
if (!deck) return null;
|
|
20
|
+
const asset = findLocalAsset(deck.assets, slug, assetPath);
|
|
21
|
+
if (!asset || asset.body == null) return null;
|
|
22
|
+
return new Response(asset.body, {
|
|
23
|
+
headers: localAssetHeaders(asset)
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function localAssetHeaders(asset) {
|
|
29
|
+
const headers = {};
|
|
30
|
+
if (asset.contentType) headers["content-type"] = asset.contentType;
|
|
31
|
+
if (asset.cacheControl !== false) headers["cache-control"] = asset.cacheControl ?? "public, max-age=300";
|
|
32
|
+
return headers;
|
|
33
|
+
}
|
|
34
|
+
function findLocalAsset(assets, slug, assetPath) {
|
|
35
|
+
const normalized = assetPath.replace(/^\/+/, "");
|
|
36
|
+
return assets.find((asset) => {
|
|
37
|
+
if (asset.type !== "local") return false;
|
|
38
|
+
const suffix = `/${slug}/assets/${normalized}`;
|
|
39
|
+
return asset.publicPath.endsWith(suffix);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/server/router.ts
|
|
44
|
+
import { Hono } from "hono";
|
|
45
|
+
import { raw as raw2 } from "hono/html";
|
|
46
|
+
|
|
47
|
+
// src/deck/model.ts
|
|
48
|
+
var SLIDE_TRANSITIONS = [
|
|
49
|
+
"none",
|
|
50
|
+
"fade",
|
|
51
|
+
"fade-out",
|
|
52
|
+
"slide-left",
|
|
53
|
+
"slide-right",
|
|
54
|
+
"slide-up",
|
|
55
|
+
"slide-down",
|
|
56
|
+
"view-transition"
|
|
57
|
+
];
|
|
58
|
+
var RenderError = class extends Error {
|
|
59
|
+
constructor(message, code, cause) {
|
|
60
|
+
super(message);
|
|
61
|
+
this.code = code;
|
|
62
|
+
this.cause = cause;
|
|
63
|
+
this.name = "RenderError";
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// src/renderer/asset-rewrite.ts
|
|
68
|
+
function rewriteLocalAssetUrls(html, assets) {
|
|
69
|
+
const localAssets = assets.filter((asset) => asset.type === "local");
|
|
70
|
+
if (localAssets.length === 0) return html;
|
|
71
|
+
return html.replace(/\b(src|href)=["']([^"']+)["']/g, (match, attr, value) => {
|
|
72
|
+
const asset = findAssetForHtmlUrl(localAssets, value);
|
|
73
|
+
return asset ? `${attr}="${escapeHtml(asset.publicPath)}"` : match;
|
|
74
|
+
}).replace(/<dd>([^<]+)<\/dd>/g, (match, value) => {
|
|
75
|
+
const asset = findAssetForHtmlUrl(localAssets, decodeHtml(value));
|
|
76
|
+
return asset ? `<dd>${escapeHtml(asset.publicPath)}</dd>` : match;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function backgroundStyle(value, assets) {
|
|
80
|
+
const asset = findAssetForHtmlUrl(assets.filter((candidate) => candidate.type === "local"), value);
|
|
81
|
+
const url = asset?.publicPath ?? value;
|
|
82
|
+
return `background-image:url("${escapeCssUrl(url)}")`;
|
|
83
|
+
}
|
|
84
|
+
function findAssetForHtmlUrl(assets, value) {
|
|
85
|
+
const normalized = decodeURIComponent(value).replace(/^\.?\//, "");
|
|
86
|
+
return assets.find((asset) => {
|
|
87
|
+
const assetPath = localAssetRelativePath(asset);
|
|
88
|
+
return normalized === assetPath || normalized === `assets/${assetPath}`;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function localAssetRelativePath(asset) {
|
|
92
|
+
const marker = "/assets/";
|
|
93
|
+
const normalized = asset.sourcePath.replaceAll("\\", "/");
|
|
94
|
+
const markerIndex = normalized.indexOf(marker);
|
|
95
|
+
return markerIndex === -1 ? normalized.split("/").at(-1) ?? normalized : normalized.slice(markerIndex + marker.length);
|
|
96
|
+
}
|
|
97
|
+
function escapeCssUrl(value) {
|
|
98
|
+
return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
|
|
99
|
+
}
|
|
100
|
+
function escapeHtml(value) {
|
|
101
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
102
|
+
}
|
|
103
|
+
function decodeHtml(value) {
|
|
104
|
+
return value.replaceAll(""", '"').replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/renderer/jsx-renderer.ts
|
|
108
|
+
import { jsx } from "hono/jsx/jsx-runtime";
|
|
109
|
+
import { HtmlEscapedCallbackPhase, resolveCallback } from "hono/utils/html";
|
|
110
|
+
var builtInSlideComponents = defineSlideComponents({
|
|
111
|
+
Fire: (props) => {
|
|
112
|
+
if (props.order !== void 0) {
|
|
113
|
+
throw new Error('The Fire "order" prop is not supported. Fires reveal in source order.');
|
|
114
|
+
}
|
|
115
|
+
const effect = stringProp(props.effect);
|
|
116
|
+
const at = fireAtProp(props.at);
|
|
117
|
+
return jsx("div", {
|
|
118
|
+
"data-hono-decks-fire": true,
|
|
119
|
+
...at ? { "data-fire-at": at } : {},
|
|
120
|
+
...effect ? { "data-fire-effect": safeToken(effect).toLowerCase() } : {},
|
|
121
|
+
children: props.children
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
Hero: (props) => jsx("section", {
|
|
125
|
+
class: `mdx-hero${props.featured === true ? " is-featured" : ""}${props.image || props.src ? " has-image" : ""}`,
|
|
126
|
+
"data-component": "Hero",
|
|
127
|
+
children: [
|
|
128
|
+
heroImage(props),
|
|
129
|
+
jsx("div", {
|
|
130
|
+
class: "mdx-hero-copy",
|
|
131
|
+
children: [
|
|
132
|
+
typeof props.eyebrow === "string" && props.eyebrow ? jsx("p", { class: "mdx-hero-eyebrow", children: props.eyebrow }) : "",
|
|
133
|
+
typeof props.title === "string" && props.title ? jsx("h1", { children: props.title }) : "",
|
|
134
|
+
typeof props.subtitle === "string" && props.subtitle ? jsx("p", { class: "mdx-hero-subtitle", children: props.subtitle }) : typeof props.description === "string" && props.description ? jsx("p", { class: "mdx-hero-subtitle", children: props.description }) : ""
|
|
135
|
+
]
|
|
136
|
+
})
|
|
137
|
+
]
|
|
138
|
+
}),
|
|
139
|
+
CodeBlock: (props) => {
|
|
140
|
+
const lang = stringProp(props.lang);
|
|
141
|
+
const filename = stringProp(props.filename ?? props.title);
|
|
142
|
+
const highlight = stringProp(props.highlight);
|
|
143
|
+
const code = codeBlockText(props.children);
|
|
144
|
+
const highlightedHtml = typeof props.highlightedHtml === "string" ? props.highlightedHtml : void 0;
|
|
145
|
+
return jsx("figure", {
|
|
146
|
+
class: "hono-decks-code-block",
|
|
147
|
+
...lang ? { "data-lang": lang } : {},
|
|
148
|
+
...filename ? { "data-filename": filename } : {},
|
|
149
|
+
...highlight ? { "data-highlight": highlight } : {},
|
|
150
|
+
children: [
|
|
151
|
+
filename ? jsx("figcaption", { class: "hono-decks-code-caption", children: filename }) : "",
|
|
152
|
+
highlightedHtml ? jsx("div", {
|
|
153
|
+
class: "hono-decks-code-highlight",
|
|
154
|
+
dangerouslySetInnerHTML: { __html: highlightedHtml }
|
|
155
|
+
}) : jsx("pre", {
|
|
156
|
+
children: jsx("code", {
|
|
157
|
+
...lang ? { class: `language-${safeToken(lang)}`, "data-lang": lang } : {},
|
|
158
|
+
children: code
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
]
|
|
162
|
+
});
|
|
163
|
+
},
|
|
164
|
+
EmbedFrame: (props) => {
|
|
165
|
+
const src = stringProp(props.src);
|
|
166
|
+
const provider = stringProp(props.provider)?.trim().toLowerCase();
|
|
167
|
+
const title = stringProp(props.title) ?? "Embedded content";
|
|
168
|
+
const aspectRatio = stringProp(props.aspectRatio) ?? "16 / 9";
|
|
169
|
+
const loading = stringProp(props.loading) ?? "lazy";
|
|
170
|
+
const allow = stringProp(props.allow) ?? "fullscreen; picture-in-picture";
|
|
171
|
+
const sandbox = props.sandbox === false ? void 0 : stringProp(props.sandbox) ?? "allow-scripts allow-same-origin allow-presentation allow-popups";
|
|
172
|
+
const referrerPolicy = stringProp(props.referrerPolicy ?? props.referrerpolicy) ?? "strict-origin-when-cross-origin";
|
|
173
|
+
const fallbackHref = stringProp(props.fallbackHref ?? props.fallbackUrl ?? props.href) ?? src;
|
|
174
|
+
const fallback = codeBlockText(props.children) || "Open embed";
|
|
175
|
+
const printPoster = provider === "youtube" ? youtubePrintPoster(src) : void 0;
|
|
176
|
+
return jsx("figure", {
|
|
177
|
+
class: "hono-decks-embed-frame",
|
|
178
|
+
"data-component": "EmbedFrame",
|
|
179
|
+
...provider ? { "data-provider": safeToken(provider).toLowerCase() } : {},
|
|
180
|
+
children: [
|
|
181
|
+
jsx("div", {
|
|
182
|
+
class: "hono-decks-embed-viewport",
|
|
183
|
+
style: `aspect-ratio:${safeAspectRatio(aspectRatio)}`,
|
|
184
|
+
children: [
|
|
185
|
+
src ? jsx("iframe", {
|
|
186
|
+
src,
|
|
187
|
+
title,
|
|
188
|
+
loading,
|
|
189
|
+
referrerpolicy: referrerPolicy,
|
|
190
|
+
...sandbox ? { sandbox } : {},
|
|
191
|
+
...allow ? { allow } : {},
|
|
192
|
+
allowfullscreen: true
|
|
193
|
+
}) : "",
|
|
194
|
+
fallbackHref ? jsx("a", {
|
|
195
|
+
class: "hono-decks-embed-print-fallback",
|
|
196
|
+
href: fallbackHref,
|
|
197
|
+
children: [
|
|
198
|
+
printPoster ? jsx("img", {
|
|
199
|
+
class: "hono-decks-embed-print-poster",
|
|
200
|
+
src: printPoster,
|
|
201
|
+
alt: `${title} thumbnail`
|
|
202
|
+
}) : "",
|
|
203
|
+
jsx("span", { children: fallback })
|
|
204
|
+
]
|
|
205
|
+
}) : ""
|
|
206
|
+
]
|
|
207
|
+
}),
|
|
208
|
+
src ? jsx("figcaption", {
|
|
209
|
+
class: "hono-decks-embed-fallback",
|
|
210
|
+
children: jsx("a", { href: fallbackHref, target: "_blank", rel: "noreferrer", children: fallback })
|
|
211
|
+
}) : ""
|
|
212
|
+
]
|
|
213
|
+
});
|
|
214
|
+
},
|
|
215
|
+
SocialEmbed: (props) => {
|
|
216
|
+
const href = stringProp(props.href ?? props.url);
|
|
217
|
+
const provider = stringProp(props.provider ?? props.service);
|
|
218
|
+
const author = stringProp(props.author);
|
|
219
|
+
const label = stringProp(props.label) ?? (provider ? `Open on ${provider}` : "Open social post");
|
|
220
|
+
const quote = codeBlockText(props.children);
|
|
221
|
+
return jsx("figure", {
|
|
222
|
+
class: "hono-decks-social-embed",
|
|
223
|
+
"data-component": "SocialEmbed",
|
|
224
|
+
...provider ? { "data-provider": safeToken(provider).toLowerCase() } : {},
|
|
225
|
+
children: jsx("blockquote", {
|
|
226
|
+
class: "hono-decks-social-card",
|
|
227
|
+
...href ? { cite: href } : {},
|
|
228
|
+
children: [
|
|
229
|
+
quote ? jsx("p", { children: quote }) : "",
|
|
230
|
+
jsx("footer", {
|
|
231
|
+
children: [
|
|
232
|
+
author ? jsx("span", { class: "hono-decks-social-author", children: author }) : "",
|
|
233
|
+
href ? jsx("a", {
|
|
234
|
+
href,
|
|
235
|
+
target: "_blank",
|
|
236
|
+
rel: "noreferrer",
|
|
237
|
+
children: label
|
|
238
|
+
}) : ""
|
|
239
|
+
]
|
|
240
|
+
})
|
|
241
|
+
]
|
|
242
|
+
})
|
|
243
|
+
});
|
|
244
|
+
},
|
|
245
|
+
TweetEmbed: (props) => {
|
|
246
|
+
const href = stringProp(props.href ?? props.url);
|
|
247
|
+
const label = stringProp(props.label) ?? "Open post on X";
|
|
248
|
+
return jsx("figure", {
|
|
249
|
+
class: "hono-decks-tweet-embed",
|
|
250
|
+
"data-component": "TweetEmbed",
|
|
251
|
+
children: [
|
|
252
|
+
jsx("blockquote", {
|
|
253
|
+
class: "twitter-tweet",
|
|
254
|
+
"data-dnt": "true",
|
|
255
|
+
children: href ? jsx("a", {
|
|
256
|
+
href,
|
|
257
|
+
target: "_blank",
|
|
258
|
+
rel: "noreferrer",
|
|
259
|
+
children: label
|
|
260
|
+
}) : label
|
|
261
|
+
}),
|
|
262
|
+
href ? jsx("script", {
|
|
263
|
+
async: true,
|
|
264
|
+
src: "https://platform.twitter.com/widgets.js",
|
|
265
|
+
charset: "utf-8"
|
|
266
|
+
}) : "",
|
|
267
|
+
href ? jsx("a", {
|
|
268
|
+
class: "hono-decks-tweet-print-fallback",
|
|
269
|
+
href,
|
|
270
|
+
children: label
|
|
271
|
+
}) : ""
|
|
272
|
+
]
|
|
273
|
+
});
|
|
274
|
+
},
|
|
275
|
+
LinkCard: (props) => {
|
|
276
|
+
const href = stringProp(props.href ?? props.url);
|
|
277
|
+
const title = stringProp(props.title) ?? href ?? "Link";
|
|
278
|
+
const description = stringProp(props.description);
|
|
279
|
+
const image = stringProp(props.image ?? props.imageUrl);
|
|
280
|
+
const siteName = stringProp(props.siteName ?? props.site);
|
|
281
|
+
const label = codeBlockText(props.children) || "Open link";
|
|
282
|
+
return jsx("figure", {
|
|
283
|
+
class: "hono-decks-link-card",
|
|
284
|
+
"data-component": "LinkCard",
|
|
285
|
+
children: href ? jsx("a", {
|
|
286
|
+
class: "hono-decks-link-card-anchor",
|
|
287
|
+
href,
|
|
288
|
+
target: "_blank",
|
|
289
|
+
rel: "noreferrer",
|
|
290
|
+
children: [
|
|
291
|
+
image ? jsx("img", { class: "hono-decks-link-card-image", src: image, alt: title }) : "",
|
|
292
|
+
jsx("span", {
|
|
293
|
+
class: "hono-decks-link-card-body",
|
|
294
|
+
children: [
|
|
295
|
+
siteName ? jsx("span", { class: "hono-decks-link-card-site", children: siteName }) : "",
|
|
296
|
+
jsx("span", { class: "hono-decks-link-card-title", children: title }),
|
|
297
|
+
description ? jsx("span", { class: "hono-decks-link-card-description", children: description }) : "",
|
|
298
|
+
jsx("span", { class: "hono-decks-link-card-label", children: label })
|
|
299
|
+
]
|
|
300
|
+
})
|
|
301
|
+
]
|
|
302
|
+
}) : [
|
|
303
|
+
image ? jsx("img", { class: "hono-decks-link-card-image", src: image, alt: title }) : "",
|
|
304
|
+
siteName ? jsx("span", { class: "hono-decks-link-card-site", children: siteName }) : "",
|
|
305
|
+
jsx("span", { class: "hono-decks-link-card-title", children: title }),
|
|
306
|
+
description ? jsx("span", { class: "hono-decks-link-card-description", children: description }) : ""
|
|
307
|
+
]
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
function defineSlideComponents(input) {
|
|
312
|
+
const registry = {};
|
|
313
|
+
for (const [name, value] of Object.entries(input)) {
|
|
314
|
+
registry[name] = typeof value === "function" ? { component: value } : value;
|
|
315
|
+
}
|
|
316
|
+
return registry;
|
|
317
|
+
}
|
|
318
|
+
function createMdxComponents(registry, input = {}) {
|
|
319
|
+
const components = {
|
|
320
|
+
img: (props) => jsx("img", rewriteAssetProps(props, input.assets)),
|
|
321
|
+
a: (props) => jsx("a", rewriteAssetProps(props, input.assets))
|
|
322
|
+
};
|
|
323
|
+
for (const [name, definition] of Object.entries(registry)) {
|
|
324
|
+
components[name] = (props = {}) => renderRegisteredComponent(name, definition, props, props.children, input.assets);
|
|
325
|
+
}
|
|
326
|
+
return components;
|
|
327
|
+
}
|
|
328
|
+
function heroImage(props) {
|
|
329
|
+
const image = typeof props.image === "string" ? props.image : typeof props.src === "string" ? props.src : void 0;
|
|
330
|
+
if (!image) return "";
|
|
331
|
+
const alt = typeof props.alt === "string" && props.alt ? props.alt : typeof props.title === "string" ? props.title : "Hero image";
|
|
332
|
+
return jsx("img", { class: "mdx-hero-image", src: image, alt });
|
|
333
|
+
}
|
|
334
|
+
function stringProp(value) {
|
|
335
|
+
if (typeof value === "string") return value;
|
|
336
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
337
|
+
return void 0;
|
|
338
|
+
}
|
|
339
|
+
function fireAtProp(value) {
|
|
340
|
+
if (value === void 0 || value === null) return void 0;
|
|
341
|
+
if (typeof value === "number") {
|
|
342
|
+
if (Number.isInteger(value) && value >= 0) return String(value);
|
|
343
|
+
throw new Error('The Fire "at" prop accepts a non-negative integer or a relative string such as "+1".');
|
|
344
|
+
}
|
|
345
|
+
if (typeof value === "string") {
|
|
346
|
+
const at = value.trim();
|
|
347
|
+
if (/^(?:\d+|[+-]\d+)$/.test(at)) return at;
|
|
348
|
+
}
|
|
349
|
+
throw new Error('The Fire "at" prop accepts a non-negative integer or a relative string such as "+1".');
|
|
350
|
+
}
|
|
351
|
+
function safeToken(value) {
|
|
352
|
+
return value.trim().replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "text";
|
|
353
|
+
}
|
|
354
|
+
function safeAspectRatio(value) {
|
|
355
|
+
return /^\s*\d+(\.\d+)?\s*(\/|:)\s*\d+(\.\d+)?\s*$/.test(value) ? value.replace(":", " / ") : "16 / 9";
|
|
356
|
+
}
|
|
357
|
+
function youtubePrintPoster(src) {
|
|
358
|
+
if (!src) return void 0;
|
|
359
|
+
try {
|
|
360
|
+
const url = new URL(src);
|
|
361
|
+
if (!["youtube.com", "www.youtube.com", "youtube-nocookie.com", "www.youtube-nocookie.com"].includes(url.hostname)) {
|
|
362
|
+
return void 0;
|
|
363
|
+
}
|
|
364
|
+
const match = url.pathname.match(/^\/embed\/([A-Za-z0-9_-]+)$/);
|
|
365
|
+
return match ? `https://i.ytimg.com/vi/${match[1]}/hqdefault.jpg` : void 0;
|
|
366
|
+
} catch {
|
|
367
|
+
return void 0;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function codeBlockText(value) {
|
|
371
|
+
if (Array.isArray(value)) return value.map(codeBlockText).join("");
|
|
372
|
+
if (value === null || value === void 0 || typeof value === "boolean") return "";
|
|
373
|
+
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
374
|
+
if (isJsxValue(value)) return codeBlockText(value.props?.children);
|
|
375
|
+
return String(value);
|
|
376
|
+
}
|
|
377
|
+
function renderSlideNodes(nodes, input = {}) {
|
|
378
|
+
return normalizeVoidElementSpacing(nodes.map((node) => String(renderSlideNode(node, input))).join(""));
|
|
379
|
+
}
|
|
380
|
+
async function renderSlideNodesAsync(nodes, input = {}) {
|
|
381
|
+
const rendered = await Promise.all(nodes.map((node) => renderJsxValue(renderSlideNode(node, input))));
|
|
382
|
+
return normalizeVoidElementSpacing(rendered.join(""));
|
|
383
|
+
}
|
|
384
|
+
function renderSlideNode(node, input) {
|
|
385
|
+
switch (node.type) {
|
|
386
|
+
case "text":
|
|
387
|
+
return String(node.value);
|
|
388
|
+
case "code":
|
|
389
|
+
return jsx("pre", {
|
|
390
|
+
children: jsx("code", {
|
|
391
|
+
...node.lang ? { "data-lang": node.lang } : {},
|
|
392
|
+
children: node.value
|
|
393
|
+
})
|
|
394
|
+
});
|
|
395
|
+
case "element":
|
|
396
|
+
return jsx(node.tag, {
|
|
397
|
+
...rewriteAssetProps(node.props, input.assets),
|
|
398
|
+
children: node.children.map((child) => renderSlideNode(child, input))
|
|
399
|
+
});
|
|
400
|
+
case "component":
|
|
401
|
+
return renderComponentNode(node, input);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function renderComponentNode(node, input) {
|
|
405
|
+
const definition = input.components?.[node.name];
|
|
406
|
+
if (!definition) return renderComponentPlaceholder(node);
|
|
407
|
+
const children = node.children.map((child) => renderSlideNode(child, input));
|
|
408
|
+
return renderRegisteredComponent(node.name, definition, node.props, children, input.assets);
|
|
409
|
+
}
|
|
410
|
+
function renderRegisteredComponent(name, definition, props, children, assets = []) {
|
|
411
|
+
const rewritten = rewriteAssetProps(props, assets);
|
|
412
|
+
const componentProps = stripRuntimeProps(rewritten);
|
|
413
|
+
const element = definition.component({ ...componentProps, children });
|
|
414
|
+
if (!definition.client && rewritten.client !== true) return element;
|
|
415
|
+
return jsx("div", {
|
|
416
|
+
"data-hono-decks-island": definition.clientId ?? name,
|
|
417
|
+
"data-hono-decks-props": JSON.stringify(serializableProps(name, componentProps)),
|
|
418
|
+
children: element
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
async function renderJsxValue(value) {
|
|
422
|
+
const resolved = value instanceof Promise ? await value : value;
|
|
423
|
+
if (typeof resolved === "string") return resolved;
|
|
424
|
+
if (resolved === null || resolved === void 0 || typeof resolved === "boolean") return "";
|
|
425
|
+
if (typeof resolved === "number") return String(resolved);
|
|
426
|
+
return await resolveCallback(resolved, HtmlEscapedCallbackPhase.Stringify, false, {});
|
|
427
|
+
}
|
|
428
|
+
function renderComponentPlaceholder(node) {
|
|
429
|
+
return jsx("div", {
|
|
430
|
+
class: "mdx-component",
|
|
431
|
+
"data-component": node.name,
|
|
432
|
+
children: [
|
|
433
|
+
jsx("strong", { children: `<${node.name} />` }),
|
|
434
|
+
Object.keys(node.props).length > 0 ? jsx("dl", {
|
|
435
|
+
children: Object.entries(node.props).map(
|
|
436
|
+
([key, value]) => jsx("div", {
|
|
437
|
+
children: [jsx("dt", { children: key }), jsx("dd", { children: String(value) })]
|
|
438
|
+
})
|
|
439
|
+
)
|
|
440
|
+
}) : ""
|
|
441
|
+
]
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
function stripRuntimeProps(props) {
|
|
445
|
+
const clean = {};
|
|
446
|
+
for (const [key, value] of Object.entries(props)) {
|
|
447
|
+
if (key === "client" || key === "client:island" || key === "children" || value === void 0) continue;
|
|
448
|
+
clean[key] = value;
|
|
449
|
+
}
|
|
450
|
+
return clean;
|
|
451
|
+
}
|
|
452
|
+
function rewriteAssetProps(props, assets = []) {
|
|
453
|
+
const rewritten = { ...props };
|
|
454
|
+
for (const key of ["src", "href", "image", "background"]) {
|
|
455
|
+
const value = rewritten[key];
|
|
456
|
+
if (typeof value !== "string") continue;
|
|
457
|
+
const asset = findAssetForHtmlUrl2(assets, value);
|
|
458
|
+
if (asset) rewritten[key] = asset.publicPath;
|
|
459
|
+
}
|
|
460
|
+
return rewritten;
|
|
461
|
+
}
|
|
462
|
+
function serializableProps(componentName, props) {
|
|
463
|
+
const result = {};
|
|
464
|
+
for (const [key, value] of Object.entries(props)) {
|
|
465
|
+
result[key] = serializeClientIslandProp(value, `${componentName}.${key}`);
|
|
466
|
+
}
|
|
467
|
+
return result;
|
|
468
|
+
}
|
|
469
|
+
function serializeClientIslandProp(value, path) {
|
|
470
|
+
if (value === null) return null;
|
|
471
|
+
if (typeof value === "string" || typeof value === "boolean") return value;
|
|
472
|
+
if (typeof value === "number") {
|
|
473
|
+
if (!Number.isFinite(value)) {
|
|
474
|
+
throw new Error(`Client island prop "${path}" must be JSON-serializable; non-finite numbers are not supported.`);
|
|
475
|
+
}
|
|
476
|
+
return value;
|
|
477
|
+
}
|
|
478
|
+
if (typeof value === "function") {
|
|
479
|
+
throw new Error(`Client island prop "${path}" must be JSON-serializable; functions cannot be passed to the client.`);
|
|
480
|
+
}
|
|
481
|
+
if (value === void 0 || typeof value === "symbol" || typeof value === "bigint") {
|
|
482
|
+
throw new Error(`Client island prop "${path}" must be JSON-serializable; ${typeof value} values are not supported.`);
|
|
483
|
+
}
|
|
484
|
+
if (isJsxValue(value)) {
|
|
485
|
+
throw new Error(`Client island prop "${path}" must be JSON-serializable; JSX values cannot be passed to the client.`);
|
|
486
|
+
}
|
|
487
|
+
if (value instanceof Date) {
|
|
488
|
+
throw new Error(`Client island prop "${path}" must be JSON-serializable; Date values must be converted to strings.`);
|
|
489
|
+
}
|
|
490
|
+
if (Array.isArray(value)) return value.map((item, index) => serializeClientIslandProp(item, `${path}[${index}]`));
|
|
491
|
+
if (!isPlainObject(value)) {
|
|
492
|
+
throw new Error(`Client island prop "${path}" must be JSON-serializable; class instances are not supported.`);
|
|
493
|
+
}
|
|
494
|
+
const result = {};
|
|
495
|
+
for (const [key, item] of Object.entries(value)) {
|
|
496
|
+
result[key] = serializeClientIslandProp(item, `${path}.${key}`);
|
|
497
|
+
}
|
|
498
|
+
return result;
|
|
499
|
+
}
|
|
500
|
+
function isJsxValue(value) {
|
|
501
|
+
return typeof value === "object" && value !== null && "tag" in value && "props" in value && "type" in value;
|
|
502
|
+
}
|
|
503
|
+
function isPlainObject(value) {
|
|
504
|
+
if (typeof value !== "object" || value === null) return false;
|
|
505
|
+
const prototype = Object.getPrototypeOf(value);
|
|
506
|
+
return prototype === Object.prototype || prototype === null;
|
|
507
|
+
}
|
|
508
|
+
function findAssetForHtmlUrl2(assets, value) {
|
|
509
|
+
const normalized = decodeURIComponent(value).replace(/^\.?\//, "");
|
|
510
|
+
return assets.filter((asset) => asset.type === "local").find((asset) => {
|
|
511
|
+
const assetPath = localAssetRelativePath2(asset.sourcePath);
|
|
512
|
+
return normalized === assetPath || normalized === `assets/${assetPath}`;
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
function localAssetRelativePath2(sourcePath) {
|
|
516
|
+
const marker = "/assets/";
|
|
517
|
+
const normalized = sourcePath.replaceAll("\\", "/");
|
|
518
|
+
const markerIndex = normalized.indexOf(marker);
|
|
519
|
+
return markerIndex === -1 ? normalized.split("/").at(-1) ?? normalized : normalized.slice(markerIndex + marker.length);
|
|
520
|
+
}
|
|
521
|
+
function normalizeVoidElementSpacing(html) {
|
|
522
|
+
return html.replace(/<(img|br|hr)([^>]*)\/>/g, "<$1$2 />");
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// src/renderer/control-icons.ts
|
|
526
|
+
import { jsx as jsx2 } from "hono/jsx/jsx-runtime";
|
|
527
|
+
function renderControlIcon(name, className = "hono-decks-control-icon") {
|
|
528
|
+
return jsx2("svg", {
|
|
529
|
+
class: className,
|
|
530
|
+
"data-hono-decks-control-icon": true,
|
|
531
|
+
viewBox: "0 0 24 24",
|
|
532
|
+
"aria-hidden": "true",
|
|
533
|
+
focusable: "false",
|
|
534
|
+
fill: "none",
|
|
535
|
+
"stroke-width": "2",
|
|
536
|
+
"stroke-linecap": "round",
|
|
537
|
+
"stroke-linejoin": "round",
|
|
538
|
+
children: controlIconPaths(name)
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
function renderControlIconHtml(name, className = "hono-decks-control-icon") {
|
|
542
|
+
return `<svg class="${className}" data-hono-decks-control-icon viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${controlIconPathHtml(name)}</svg>`;
|
|
543
|
+
}
|
|
544
|
+
function controlIconLabel(name) {
|
|
545
|
+
switch (name) {
|
|
546
|
+
case "deck-list":
|
|
547
|
+
return "Deck list";
|
|
548
|
+
case "home":
|
|
549
|
+
return "Home";
|
|
550
|
+
case "viewer":
|
|
551
|
+
return "Viewer";
|
|
552
|
+
case "projection":
|
|
553
|
+
return "Projection";
|
|
554
|
+
case "presenter":
|
|
555
|
+
return "Presenter";
|
|
556
|
+
case "previous":
|
|
557
|
+
return "Previous slide";
|
|
558
|
+
case "next":
|
|
559
|
+
return "Next slide";
|
|
560
|
+
case "fullscreen":
|
|
561
|
+
return "Toggle fullscreen";
|
|
562
|
+
case "print":
|
|
563
|
+
return "Print view";
|
|
564
|
+
case "details":
|
|
565
|
+
return "Details";
|
|
566
|
+
case "export-pdf":
|
|
567
|
+
return "Export PDF";
|
|
568
|
+
case "export-png":
|
|
569
|
+
return "Export PNG";
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
function controlIconPathHtml(name) {
|
|
573
|
+
switch (name) {
|
|
574
|
+
case "deck-list":
|
|
575
|
+
return '<path d="M4 5h16" /><path d="M4 12h16" /><path d="M4 19h16" />';
|
|
576
|
+
case "home":
|
|
577
|
+
return '<path d="M3 11l9-8 9 8" /><path d="M5 10v10h14V10" /><path d="M9 20v-6h6v6" />';
|
|
578
|
+
case "viewer":
|
|
579
|
+
return '<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z" /><circle cx="12" cy="12" r="3" />';
|
|
580
|
+
case "projection":
|
|
581
|
+
return '<path d="M4 5h16v10H4z" /><path d="M8 19h8" /><path d="M12 15v4" />';
|
|
582
|
+
case "presenter":
|
|
583
|
+
return '<path d="M4 5h16v10H4z" /><path d="M8 21l4-6 4 6" /><path d="M9 9h6" />';
|
|
584
|
+
case "previous":
|
|
585
|
+
return '<path d="M15 6l-6 6 6 6" />';
|
|
586
|
+
case "next":
|
|
587
|
+
return '<path d="M9 6l6 6-6 6" />';
|
|
588
|
+
case "fullscreen":
|
|
589
|
+
return '<path d="M8 3H5a2 2 0 0 0-2 2v3" /><path d="M16 3h3a2 2 0 0 1 2 2v3" /><path d="M8 21H5a2 2 0 0 1-2-2v-3" /><path d="M16 21h3a2 2 0 0 0 2-2v-3" />';
|
|
590
|
+
case "print":
|
|
591
|
+
return '<path d="M6 9V3h12v6" /><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2" /><path d="M6 14h12v7H6z" />';
|
|
592
|
+
case "details":
|
|
593
|
+
return '<circle cx="12" cy="12" r="9" /><path d="M12 11v5" /><path d="M12 8h.01" />';
|
|
594
|
+
case "export-pdf":
|
|
595
|
+
return '<path d="M14 3v4a2 2 0 0 0 2 2h4" /><path d="M5 3h9l6 6v12H5z" /><path d="M8 15h8" /><path d="M8 18h5" />';
|
|
596
|
+
case "export-png":
|
|
597
|
+
return '<rect x="4" y="5" width="16" height="14" rx="2" /><path d="M8 14l2.5-2.5L14 15l2-2 2 2" /><circle cx="9" cy="9" r="1" />';
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
function controlIconPaths(name) {
|
|
601
|
+
switch (name) {
|
|
602
|
+
case "deck-list":
|
|
603
|
+
return [
|
|
604
|
+
jsx2("path", { d: "M4 5h16" }),
|
|
605
|
+
jsx2("path", { d: "M4 12h16" }),
|
|
606
|
+
jsx2("path", { d: "M4 19h16" })
|
|
607
|
+
];
|
|
608
|
+
case "home":
|
|
609
|
+
return [
|
|
610
|
+
jsx2("path", { d: "M3 11l9-8 9 8" }),
|
|
611
|
+
jsx2("path", { d: "M5 10v10h14V10" }),
|
|
612
|
+
jsx2("path", { d: "M9 20v-6h6v6" })
|
|
613
|
+
];
|
|
614
|
+
case "viewer":
|
|
615
|
+
return [
|
|
616
|
+
jsx2("path", { d: "M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z" }),
|
|
617
|
+
jsx2("circle", { cx: "12", cy: "12", r: "3" })
|
|
618
|
+
];
|
|
619
|
+
case "projection":
|
|
620
|
+
return [
|
|
621
|
+
jsx2("path", { d: "M4 5h16v10H4z" }),
|
|
622
|
+
jsx2("path", { d: "M8 19h8" }),
|
|
623
|
+
jsx2("path", { d: "M12 15v4" })
|
|
624
|
+
];
|
|
625
|
+
case "presenter":
|
|
626
|
+
return [
|
|
627
|
+
jsx2("path", { d: "M4 5h16v10H4z" }),
|
|
628
|
+
jsx2("path", { d: "M8 21l4-6 4 6" }),
|
|
629
|
+
jsx2("path", { d: "M9 9h6" })
|
|
630
|
+
];
|
|
631
|
+
case "previous":
|
|
632
|
+
return [jsx2("path", { d: "M15 6l-6 6 6 6" })];
|
|
633
|
+
case "next":
|
|
634
|
+
return [jsx2("path", { d: "M9 6l6 6-6 6" })];
|
|
635
|
+
case "fullscreen":
|
|
636
|
+
return [
|
|
637
|
+
jsx2("path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }),
|
|
638
|
+
jsx2("path", { d: "M16 3h3a2 2 0 0 1 2 2v3" }),
|
|
639
|
+
jsx2("path", { d: "M8 21H5a2 2 0 0 1-2-2v-3" }),
|
|
640
|
+
jsx2("path", { d: "M16 21h3a2 2 0 0 0 2-2v-3" })
|
|
641
|
+
];
|
|
642
|
+
case "print":
|
|
643
|
+
return [
|
|
644
|
+
jsx2("path", { d: "M6 9V3h12v6" }),
|
|
645
|
+
jsx2("path", { d: "M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2" }),
|
|
646
|
+
jsx2("path", { d: "M6 14h12v7H6z" })
|
|
647
|
+
];
|
|
648
|
+
case "details":
|
|
649
|
+
return [
|
|
650
|
+
jsx2("circle", { cx: "12", cy: "12", r: "9" }),
|
|
651
|
+
jsx2("path", { d: "M12 11v5" }),
|
|
652
|
+
jsx2("path", { d: "M12 8h.01" })
|
|
653
|
+
];
|
|
654
|
+
case "export-pdf":
|
|
655
|
+
return [
|
|
656
|
+
jsx2("path", { d: "M14 3v4a2 2 0 0 0 2 2h4" }),
|
|
657
|
+
jsx2("path", { d: "M5 3h9l6 6v12H5z" }),
|
|
658
|
+
jsx2("path", { d: "M8 15h8" }),
|
|
659
|
+
jsx2("path", { d: "M8 18h5" })
|
|
660
|
+
];
|
|
661
|
+
case "export-png":
|
|
662
|
+
return [
|
|
663
|
+
jsx2("rect", { x: "4", y: "5", width: "16", height: "14", rx: "2" }),
|
|
664
|
+
jsx2("path", { d: "M8 14l2.5-2.5L14 15l2-2 2 2" }),
|
|
665
|
+
jsx2("circle", { cx: "9", cy: "9", r: "1" })
|
|
666
|
+
];
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// src/server/document.ts
|
|
671
|
+
async function resolveDeckDocument(input, options, overrides) {
|
|
672
|
+
const page = {
|
|
673
|
+
lang: options?.lang,
|
|
674
|
+
nonce: options?.nonce,
|
|
675
|
+
head: options?.head,
|
|
676
|
+
...options?.surfaces?.[input.surface],
|
|
677
|
+
...definedDocumentOptions(overrides)
|
|
678
|
+
};
|
|
679
|
+
const langValue = await resolveDocumentValue(page.lang, input);
|
|
680
|
+
const nonce = await resolveDocumentValue(page.nonce, input);
|
|
681
|
+
const headValue = await resolveDocumentValue(page.head, input);
|
|
682
|
+
return {
|
|
683
|
+
lang: langValue?.trim() || "ja",
|
|
684
|
+
...nonce ? { nonce } : {},
|
|
685
|
+
head: headValue === void 0 || headValue === null || headValue === false ? "" : await renderJsxValue(headValue)
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
function documentNonceAttribute(nonce) {
|
|
689
|
+
return nonce ? ` nonce="${escapeHtml2(nonce)}"` : "";
|
|
690
|
+
}
|
|
691
|
+
function definedDocumentOptions(options) {
|
|
692
|
+
if (!options) return {};
|
|
693
|
+
return Object.fromEntries(Object.entries(options).filter(([, value]) => value !== void 0));
|
|
694
|
+
}
|
|
695
|
+
async function resolveDocumentValue(value, input) {
|
|
696
|
+
return typeof value === "function" ? await value(input) : await value;
|
|
697
|
+
}
|
|
698
|
+
function escapeHtml2(value) {
|
|
699
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// src/renderer/presentation-script.ts
|
|
703
|
+
function renderLiveReloadScript(eventsPath, nonce) {
|
|
704
|
+
return `<script${documentNonceAttribute(nonce)}>
|
|
705
|
+
(() => {
|
|
706
|
+
const eventsUrl = ${JSON.stringify(eventsPath)};
|
|
707
|
+
const events = new EventSource(eventsUrl);
|
|
708
|
+
events.addEventListener("deck:updated", () => location.reload());
|
|
709
|
+
})();
|
|
710
|
+
</script>`;
|
|
711
|
+
}
|
|
712
|
+
function renderClientEntryScript(clientEntry, nonce) {
|
|
713
|
+
return `<script type="module" src="${escapeHtml3(clientEntry)}"${documentNonceAttribute(nonce)}></script>`;
|
|
714
|
+
}
|
|
715
|
+
function renderPresentationScript(nonce) {
|
|
716
|
+
return `<script${documentNonceAttribute(nonce)}>
|
|
717
|
+
(() => {
|
|
718
|
+
const slides = Array.from(document.querySelectorAll(".slide"));
|
|
719
|
+
const stage = document.querySelector("[data-hono-decks-stage]");
|
|
720
|
+
const deck = document.querySelector("[data-hono-decks-deck]");
|
|
721
|
+
const DESIGN_WIDTH = 1920;
|
|
722
|
+
const DESIGN_HEIGHT = 1080;
|
|
723
|
+
let index = 0;
|
|
724
|
+
let previousIndex = 0;
|
|
725
|
+
let stepIndex = 0;
|
|
726
|
+
let stepCount = 0;
|
|
727
|
+
let isTransitioning = false;
|
|
728
|
+
let pendingNavigation = null;
|
|
729
|
+
let transitionToken = 0;
|
|
730
|
+
|
|
731
|
+
function fitDeck() {
|
|
732
|
+
if (!(stage instanceof HTMLElement) || !(deck instanceof HTMLElement)) return;
|
|
733
|
+
const bounds = stage.getBoundingClientRect();
|
|
734
|
+
const scale = Math.min(bounds.width / DESIGN_WIDTH, bounds.height / DESIGN_HEIGHT);
|
|
735
|
+
deck.style.transform = "scale(" + scale + ")";
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function publishState() {
|
|
739
|
+
const state = { type: "hono-decks:state", index, stepIndex, stepCount, slideCount: slides.length };
|
|
740
|
+
writePaginationState();
|
|
741
|
+
if (window.parent !== window) window.parent.postMessage(state, "*");
|
|
742
|
+
if (window.opener && window.opener !== window) window.opener.postMessage(state, window.location.origin);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function readInitialState() {
|
|
746
|
+
const params = new URLSearchParams(window.location.search);
|
|
747
|
+
const slide = Number(params.get("slide"));
|
|
748
|
+
const step = Number(params.get("step"));
|
|
749
|
+
const initialIndex = Number.isInteger(slide) ? Math.max(0, Math.min(slides.length - 1, slide - 1)) : 0;
|
|
750
|
+
const initialStepIndex = Number.isInteger(step) ? Math.max(0, step) : 0;
|
|
751
|
+
return { index: initialIndex, stepIndex: initialStepIndex };
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function writePaginationState() {
|
|
755
|
+
const url = new URL(window.location.href);
|
|
756
|
+
const params = url.searchParams;
|
|
757
|
+
params.set("slide", String(index + 1));
|
|
758
|
+
params.set("step", String(stepIndex));
|
|
759
|
+
window.history.replaceState(null, "", url);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function slideFires(slide) {
|
|
763
|
+
return Array.from(slide?.querySelectorAll("[data-hono-decks-fire]") ?? []);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
function fireSchedule(slide) {
|
|
767
|
+
let cursor = 0;
|
|
768
|
+
const entries = slideFires(slide).map((fire) => {
|
|
769
|
+
const rawAt = fire.getAttribute("data-fire-at");
|
|
770
|
+
let position;
|
|
771
|
+
if (rawAt !== null && /^d+$/.test(rawAt)) {
|
|
772
|
+
position = Number(rawAt);
|
|
773
|
+
} else {
|
|
774
|
+
const offset = rawAt !== null && /^[+-]d+$/.test(rawAt) ? Number(rawAt) : 1;
|
|
775
|
+
cursor += offset;
|
|
776
|
+
position = cursor;
|
|
777
|
+
}
|
|
778
|
+
return { fire, position };
|
|
779
|
+
});
|
|
780
|
+
return { entries, stepCount: Math.max(0, ...entries.map(({ position }) => position)) };
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
function fireCountForSlide(slideIndex) {
|
|
784
|
+
return fireSchedule(slides[slideIndex]).stepCount;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function setFiresVisible(fires, visible) {
|
|
788
|
+
fires.forEach((fire) => {
|
|
789
|
+
fire.toggleAttribute("data-fire-hidden", !visible);
|
|
790
|
+
fire.setAttribute("aria-hidden", visible ? "false" : "true");
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function updateFires(nextStepIndex) {
|
|
795
|
+
const schedule = fireSchedule(slides[index]);
|
|
796
|
+
stepCount = schedule.stepCount;
|
|
797
|
+
stepIndex = Math.max(0, Math.min(stepCount, nextStepIndex));
|
|
798
|
+
schedule.entries.forEach(({ fire, position }) => {
|
|
799
|
+
const visible = position <= stepIndex;
|
|
800
|
+
fire.toggleAttribute("data-fire-hidden", !visible);
|
|
801
|
+
fire.setAttribute("aria-hidden", visible ? "false" : "true");
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function activeTransitionTiming(slide) {
|
|
806
|
+
const style = getComputedStyle(slide);
|
|
807
|
+
return {
|
|
808
|
+
duration: style.getPropertyValue("--hono-decks-slide-transition-duration").trim(),
|
|
809
|
+
easing: style.getPropertyValue("--hono-decks-slide-transition-easing").trim(),
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function applyActiveTransitionTiming(slide, timing) {
|
|
814
|
+
if (timing?.duration) {
|
|
815
|
+
slide.style.setProperty("--hono-decks-active-transition-duration", timing.duration);
|
|
816
|
+
} else {
|
|
817
|
+
slide.style.removeProperty("--hono-decks-active-transition-duration");
|
|
818
|
+
}
|
|
819
|
+
if (timing?.easing) {
|
|
820
|
+
slide.style.setProperty("--hono-decks-active-transition-easing", timing.easing);
|
|
821
|
+
} else {
|
|
822
|
+
slide.style.removeProperty("--hono-decks-active-transition-easing");
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function setSlideState(slide, state, direction, transition, timing) {
|
|
827
|
+
if (!slide) return;
|
|
828
|
+
slide.setAttribute("data-slide-state", state);
|
|
829
|
+
if (transition && transition !== "none" && state !== "inactive") {
|
|
830
|
+
slide.setAttribute("data-active-transition", transition);
|
|
831
|
+
applyActiveTransitionTiming(slide, timing);
|
|
832
|
+
} else {
|
|
833
|
+
slide.removeAttribute("data-active-transition");
|
|
834
|
+
applyActiveTransitionTiming(slide);
|
|
835
|
+
}
|
|
836
|
+
if (direction) {
|
|
837
|
+
slide.setAttribute("data-slide-direction", direction);
|
|
838
|
+
} else {
|
|
839
|
+
slide.removeAttribute("data-slide-direction");
|
|
840
|
+
}
|
|
841
|
+
slide.hidden = state === "inactive";
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function transitionForSlide(slide) {
|
|
845
|
+
return slide?.getAttribute("data-transition") || "none";
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
function parseCssTime(value) {
|
|
849
|
+
const time = value.trim();
|
|
850
|
+
if (!time) return Number.NaN;
|
|
851
|
+
if (time.endsWith("ms")) return Number.parseFloat(time);
|
|
852
|
+
if (time.endsWith("s")) return Number.parseFloat(time) * 1000;
|
|
853
|
+
return Number.NaN;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function parseCssTimeList(value) {
|
|
857
|
+
return value.split(",").map(parseCssTime).filter((time) => Number.isFinite(time));
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function listValue(values, index) {
|
|
861
|
+
return values.length ? values[index % values.length] : 0;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
function transitionDurationMs(slide) {
|
|
865
|
+
if (!slide) return 240;
|
|
866
|
+
const style = getComputedStyle(slide);
|
|
867
|
+
const durations = parseCssTimeList(style.transitionDuration);
|
|
868
|
+
const delays = parseCssTimeList(style.transitionDelay);
|
|
869
|
+
const count = Math.max(durations.length, delays.length);
|
|
870
|
+
if (count === 0) return 240;
|
|
871
|
+
let max = 0;
|
|
872
|
+
for (let index = 0; index < count; index += 1) {
|
|
873
|
+
max = Math.max(max, listValue(durations, index) + listValue(delays, index));
|
|
874
|
+
}
|
|
875
|
+
return max;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function prefersReducedMotion() {
|
|
879
|
+
return window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches === true;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
function queueNavigation(targetIndex, nextStepIndex = 0) {
|
|
883
|
+
pendingNavigation = {
|
|
884
|
+
targetIndex: Math.max(0, Math.min(slides.length - 1, targetIndex)),
|
|
885
|
+
nextStepIndex,
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function drainPendingNavigation() {
|
|
890
|
+
const pending = pendingNavigation;
|
|
891
|
+
pendingNavigation = null;
|
|
892
|
+
if (pending) show(pending.targetIndex, pending.nextStepIndex);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function applyInstantSlideChange(targetIndex, nextStepIndex = 0, direction) {
|
|
896
|
+
previousIndex = index;
|
|
897
|
+
index = Math.max(0, Math.min(slides.length - 1, targetIndex));
|
|
898
|
+
slides.forEach((slide, slideIndex) => {
|
|
899
|
+
setSlideState(slide, slideIndex === index ? "active" : "inactive", slideIndex === index ? direction : undefined);
|
|
900
|
+
});
|
|
901
|
+
updateFires(nextStepIndex);
|
|
902
|
+
publishState();
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
function finishSlideTransition(outgoing, incoming, direction) {
|
|
906
|
+
setSlideState(outgoing, "inactive");
|
|
907
|
+
setSlideState(incoming, "active", direction);
|
|
908
|
+
isTransitioning = false;
|
|
909
|
+
drainPendingNavigation();
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
function isTransitionEndEvent(event, slide) {
|
|
913
|
+
return event.target === slide && (event.propertyName === "opacity" || event.propertyName === "transform" || event.propertyName === "all");
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function waitForSlideTransition(outgoing, incoming, direction, token) {
|
|
917
|
+
let finished = false;
|
|
918
|
+
const watchedSlides = [outgoing, incoming].filter(Boolean);
|
|
919
|
+
const expectedDuration = Math.max(transitionDurationMs(outgoing), transitionDurationMs(incoming));
|
|
920
|
+
const startedAt = performance.now();
|
|
921
|
+
function cleanup() {
|
|
922
|
+
watchedSlides.forEach((slide) => {
|
|
923
|
+
slide.removeEventListener("transitionend", onTransitionEnd);
|
|
924
|
+
slide.removeEventListener("transitioncancel", onTransitionEnd);
|
|
925
|
+
});
|
|
926
|
+
window.clearTimeout(timeout);
|
|
927
|
+
}
|
|
928
|
+
function finish() {
|
|
929
|
+
if (finished || token !== transitionToken) return;
|
|
930
|
+
finished = true;
|
|
931
|
+
cleanup();
|
|
932
|
+
finishSlideTransition(outgoing, incoming, direction);
|
|
933
|
+
}
|
|
934
|
+
function onTransitionEnd(event) {
|
|
935
|
+
if (!watchedSlides.some((slide) => isTransitionEndEvent(event, slide))) return;
|
|
936
|
+
const elapsed = performance.now() - startedAt;
|
|
937
|
+
if (elapsed >= Math.max(expectedDuration - 20, 0)) finish();
|
|
938
|
+
}
|
|
939
|
+
watchedSlides.forEach((slide) => {
|
|
940
|
+
slide.addEventListener("transitionend", onTransitionEnd);
|
|
941
|
+
slide.addEventListener("transitioncancel", onTransitionEnd);
|
|
942
|
+
});
|
|
943
|
+
const timeout = window.setTimeout(finish, expectedDuration + 80);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
function show(nextIndex, nextStepIndex = 0) {
|
|
947
|
+
const targetIndex = Math.max(0, Math.min(slides.length - 1, nextIndex));
|
|
948
|
+
const direction = targetIndex >= index ? "forward" : "backward";
|
|
949
|
+
if (document.body.hasAttribute("data-overview-mode") || targetIndex === index) {
|
|
950
|
+
applyInstantSlideChange(targetIndex, nextStepIndex, direction);
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
if (isTransitioning) {
|
|
954
|
+
queueNavigation(targetIndex, nextStepIndex);
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
const outgoing = slides[index];
|
|
959
|
+
const incoming = slides[targetIndex];
|
|
960
|
+
const transition = transitionForSlide(incoming);
|
|
961
|
+
const timing = activeTransitionTiming(incoming);
|
|
962
|
+
if (transition === "none" || prefersReducedMotion()) {
|
|
963
|
+
applyInstantSlideChange(targetIndex, nextStepIndex, direction);
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
if (transition === "view-transition" && typeof document.startViewTransition === "function") {
|
|
968
|
+
isTransitioning = true;
|
|
969
|
+
const viewTransition = document.startViewTransition(() => {
|
|
970
|
+
applyInstantSlideChange(targetIndex, nextStepIndex, direction);
|
|
971
|
+
});
|
|
972
|
+
Promise.resolve(viewTransition.finished).finally(() => {
|
|
973
|
+
isTransitioning = false;
|
|
974
|
+
drainPendingNavigation();
|
|
975
|
+
});
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
isTransitioning = true;
|
|
980
|
+
const token = ++transitionToken;
|
|
981
|
+
previousIndex = index;
|
|
982
|
+
index = targetIndex;
|
|
983
|
+
slides.forEach((slide) => {
|
|
984
|
+
if (slide !== outgoing && slide !== incoming) setSlideState(slide, "inactive");
|
|
985
|
+
});
|
|
986
|
+
setSlideState(outgoing, "active", direction, transition, timing);
|
|
987
|
+
setSlideState(incoming, "entering", direction, transition, timing);
|
|
988
|
+
updateFires(nextStepIndex);
|
|
989
|
+
publishState();
|
|
990
|
+
requestAnimationFrame(() => {
|
|
991
|
+
setSlideState(outgoing, "leaving", direction, transition, timing);
|
|
992
|
+
setSlideState(incoming, "active", direction, transition, timing);
|
|
993
|
+
waitForSlideTransition(outgoing, incoming, direction, token);
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
function next() {
|
|
998
|
+
const isAtDeckEnd = index >= slides.length - 1 && stepIndex >= stepCount;
|
|
999
|
+
if (isAtDeckEnd) return;
|
|
1000
|
+
if (isTransitioning) {
|
|
1001
|
+
queueNavigation(index + 1, 0);
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
if (stepIndex < stepCount) {
|
|
1005
|
+
updateFires(stepIndex + 1);
|
|
1006
|
+
publishState();
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
show(index + 1, 0);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
function previous() {
|
|
1013
|
+
if (isTransitioning) {
|
|
1014
|
+
const targetIndex = Math.max(0, index - 1);
|
|
1015
|
+
queueNavigation(targetIndex, fireCountForSlide(targetIndex));
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
if (stepIndex > 0) {
|
|
1019
|
+
updateFires(stepIndex - 1);
|
|
1020
|
+
publishState();
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
if (index > 0) {
|
|
1024
|
+
const previousIndex = index - 1;
|
|
1025
|
+
show(previousIndex, fireCountForSlide(previousIndex));
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
show(0, 0);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
function toggleOverview() {
|
|
1032
|
+
const enabled = document.body.toggleAttribute("data-overview-mode");
|
|
1033
|
+
transitionToken += 1;
|
|
1034
|
+
isTransitioning = false;
|
|
1035
|
+
pendingNavigation = null;
|
|
1036
|
+
slides.forEach((slide) => {
|
|
1037
|
+
slide.hidden = false;
|
|
1038
|
+
slide.setAttribute("data-slide-state", "active");
|
|
1039
|
+
slide.removeAttribute("data-slide-direction");
|
|
1040
|
+
slide.removeAttribute("data-active-transition");
|
|
1041
|
+
});
|
|
1042
|
+
setFiresVisible(Array.from(document.querySelectorAll("[data-hono-decks-fire]")), enabled);
|
|
1043
|
+
if (!enabled) show(index);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
function togglePresenter() {
|
|
1047
|
+
const enabled = document.body.toggleAttribute("data-presenter-mode");
|
|
1048
|
+
document.querySelectorAll(".speaker-notes").forEach((note) => { note.hidden = !enabled; });
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
function handleCommand(action, commandIndex, commandStepIndex) {
|
|
1052
|
+
if (action === "previous") previous();
|
|
1053
|
+
if (action === "next") next();
|
|
1054
|
+
if (action === "goTo" && Number.isInteger(commandIndex)) {
|
|
1055
|
+
show(commandIndex, Number.isInteger(commandStepIndex) ? commandStepIndex : 0);
|
|
1056
|
+
}
|
|
1057
|
+
if (action === "fullscreen") void toggleFullscreen();
|
|
1058
|
+
if (action === "presenter") togglePresenter();
|
|
1059
|
+
if (action === "overview") toggleOverview();
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
async function toggleFullscreen() {
|
|
1063
|
+
if (document.fullscreenElement) {
|
|
1064
|
+
await document.exitFullscreen?.();
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
await document.documentElement.requestFullscreen?.();
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
slides.forEach((slide, slideIndex) => {
|
|
1071
|
+
slide.addEventListener("click", () => {
|
|
1072
|
+
if (!document.body.hasAttribute("data-overview-mode")) return;
|
|
1073
|
+
document.body.removeAttribute("data-overview-mode");
|
|
1074
|
+
show(slideIndex);
|
|
1075
|
+
});
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
document.addEventListener("keydown", (event) => {
|
|
1079
|
+
if (event.key === "ArrowRight" || event.key === " ") handleCommand("next");
|
|
1080
|
+
if (event.key === "ArrowLeft") handleCommand("previous");
|
|
1081
|
+
if (event.key === "f") handleCommand("fullscreen");
|
|
1082
|
+
if (event.key === "p") handleCommand("presenter");
|
|
1083
|
+
if (event.key === "o") handleCommand("overview");
|
|
1084
|
+
});
|
|
1085
|
+
|
|
1086
|
+
window.addEventListener("message", (event) => {
|
|
1087
|
+
const message = event.data;
|
|
1088
|
+
if (!message || message.type !== "hono-decks:command") return;
|
|
1089
|
+
handleCommand(message.action, message.index, message.stepIndex);
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
window.__honoDecksPresentationRuntime = { command: handleCommand };
|
|
1093
|
+
|
|
1094
|
+
window.addEventListener("resize", fitDeck);
|
|
1095
|
+
fitDeck();
|
|
1096
|
+
const initialState = readInitialState();
|
|
1097
|
+
show(initialState.index, initialState.stepIndex);
|
|
1098
|
+
})();
|
|
1099
|
+
</script>`;
|
|
1100
|
+
}
|
|
1101
|
+
function escapeHtml3(value) {
|
|
1102
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// src/renderer/presentation-style.ts
|
|
1106
|
+
var PRESENTATION_DESIGN_WIDTH = 1920;
|
|
1107
|
+
var PRESENTATION_DESIGN_HEIGHT = 1080;
|
|
1108
|
+
function presentationDesignCssVariables() {
|
|
1109
|
+
return `--hono-decks-width:${PRESENTATION_DESIGN_WIDTH}px;--hono-decks-height:${PRESENTATION_DESIGN_HEIGHT}px`;
|
|
1110
|
+
}
|
|
1111
|
+
function basePresentationStyle() {
|
|
1112
|
+
return `
|
|
1113
|
+
:root{color-scheme:dark;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:32px;${presentationDesignCssVariables()};--hono-decks-transition-duration:.24s;--hono-decks-transition-easing:ease;--hono-decks-color:#eef2ff;--hono-decks-muted-color:#cbd5e1;--hono-decks-accent-color:#8bd3ff;--hono-decks-border-color:rgba(148,163,184,.24);--hono-decks-inline-code-background:rgba(15,23,42,.72);--hono-decks-code-background:rgba(15,23,42,.78);--hono-decks-card-background:rgba(15,23,42,.78);--hono-decks-card-image-background:rgba(255,255,255,.08);--hono-decks-warning-background:rgba(255,193,7,.12);--hono-decks-warning-color:#ffe59b;color:var(--hono-decks-color)}
|
|
1114
|
+
html,body{margin:0;width:100%;height:100%;overflow:hidden}
|
|
1115
|
+
.hono-decks-stage{width:100vw;height:100vh;overflow:hidden;position:relative;display:grid;place-items:center}
|
|
1116
|
+
.hono-decks-deck{display:grid;gap:1rem;width:var(--hono-decks-width);height:var(--hono-decks-height);box-sizing:border-box;transform-origin:left top}
|
|
1117
|
+
.slide{box-sizing:border-box;aspect-ratio:16/9;padding:clamp(1.2rem,3vw,3rem);overflow:hidden}
|
|
1118
|
+
.hono-decks-slide-content{width:100%;height:100%}
|
|
1119
|
+
.slide.layout-cover,.slide.layout-statement{display:flex;flex-direction:column;justify-content:center}
|
|
1120
|
+
.slide.layout-cover>.hono-decks-slide-content,.slide.layout-statement>.hono-decks-slide-content{display:flex;flex-direction:column;justify-content:center}
|
|
1121
|
+
.slide code{font-family:"SFMono-Regular","Cascadia Code","Liberation Mono",Menlo,Consolas,monospace;font-size:.9em;line-height:1.45}
|
|
1122
|
+
.slide :not(pre)>code{border-radius:6px;background:var(--hono-decks-inline-code-background);padding:.12em .34em}
|
|
1123
|
+
.slide pre{max-width:100%;overflow:auto;box-sizing:border-box;border:1px solid var(--hono-decks-border-color);border-radius:8px;background:var(--hono-decks-code-background);padding:1rem;tab-size:2;white-space:pre}
|
|
1124
|
+
.slide pre code{display:block;min-width:max-content;background:transparent;padding:0}
|
|
1125
|
+
.slide table{width:100%;margin:1rem 0;border-collapse:collapse}
|
|
1126
|
+
.slide th,.slide td{border:1px solid var(--hono-decks-border-color);padding:.4em .75em;text-align:left}
|
|
1127
|
+
.slide th{background:var(--hono-decks-card-background);font-weight:700}
|
|
1128
|
+
.slide del{color:var(--hono-decks-muted-color)}
|
|
1129
|
+
.slide li.task-list-item{list-style:none;margin-left:-1.4em}
|
|
1130
|
+
.slide li.task-list-item input[type=checkbox]{margin-right:.5em;vertical-align:middle}
|
|
1131
|
+
.hono-decks-code-block{margin:1rem 0;max-width:100%}
|
|
1132
|
+
.hono-decks-code-caption{display:inline-flex;margin:0 0 .4rem;border:1px solid var(--hono-decks-border-color);border-radius:6px;padding:.2rem .5rem;background:var(--hono-decks-inline-code-background);color:var(--hono-decks-muted-color);font-size:.82rem}
|
|
1133
|
+
.hono-decks-embed-frame{margin:1rem 0;max-width:100%}
|
|
1134
|
+
.hono-decks-embed-viewport{width:min(100%,72rem);overflow:hidden}
|
|
1135
|
+
.hono-decks-embed-viewport iframe{display:block;width:100%;height:100%;border:0}
|
|
1136
|
+
.hono-decks-embed-print-fallback{display:none}
|
|
1137
|
+
.hono-decks-embed-print-poster{display:block;width:100%;height:100%;object-fit:cover}
|
|
1138
|
+
.hono-decks-embed-fallback{margin:.45rem 0 0;color:var(--hono-decks-muted-color);font-size:.84rem}
|
|
1139
|
+
.hono-decks-embed-fallback a{color:inherit}
|
|
1140
|
+
.hono-decks-social-embed{margin:1rem 0;max-width:min(100%,42rem)}
|
|
1141
|
+
.hono-decks-social-card{margin:0;border:1px solid var(--hono-decks-border-color);border-radius:8px;background:var(--hono-decks-card-background);padding:1rem}
|
|
1142
|
+
.hono-decks-social-card p{margin:0 0 .75rem;line-height:1.55}
|
|
1143
|
+
.hono-decks-social-card footer{display:flex;flex-wrap:wrap;gap:.65rem;align-items:center;color:var(--hono-decks-muted-color);font-size:.9rem}
|
|
1144
|
+
.hono-decks-social-card a{color:inherit}
|
|
1145
|
+
.hono-decks-tweet-embed{margin:1rem 0;max-width:min(100%,42rem)}
|
|
1146
|
+
.hono-decks-tweet-embed .twitter-tweet{margin:0;border:1px solid var(--hono-decks-border-color);border-radius:8px;background:var(--hono-decks-card-background);padding:1rem}
|
|
1147
|
+
.hono-decks-tweet-embed .twitter-tweet a{color:inherit}
|
|
1148
|
+
.hono-decks-tweet-print-fallback{display:none}
|
|
1149
|
+
.hono-decks-link-card{margin:1rem 0;max-width:min(100%,42rem)}
|
|
1150
|
+
.hono-decks-link-card-anchor{display:grid;grid-template-columns:minmax(9rem,32%) minmax(0,1fr);gap:.75rem;align-items:stretch;border:1px solid var(--hono-decks-border-color);border-radius:8px;background:var(--hono-decks-card-background);padding:1rem;color:inherit;text-decoration:none}
|
|
1151
|
+
.hono-decks-link-card-body{display:grid;gap:.35rem;min-width:0}
|
|
1152
|
+
.hono-decks-link-card-image{width:100%;height:100%;max-height:10rem;aspect-ratio:16/9;object-fit:cover;border-radius:6px;background:var(--hono-decks-card-image-background)}
|
|
1153
|
+
.hono-decks-link-card-site{color:var(--hono-decks-accent-color);font-size:.8rem;text-transform:uppercase}
|
|
1154
|
+
.hono-decks-link-card-title{font-weight:700}
|
|
1155
|
+
.hono-decks-link-card-description{color:var(--hono-decks-muted-color);line-height:1.45}
|
|
1156
|
+
.hono-decks-link-card-label{color:var(--hono-decks-accent-color);font-size:.88rem}
|
|
1157
|
+
@media (max-width: 640px){.hono-decks-link-card-anchor{grid-template-columns:1fr}.hono-decks-link-card-image{height:auto;max-height:12rem}}
|
|
1158
|
+
.mdx-hero{height:100%;display:grid;grid-template-columns:minmax(0,1fr) minmax(280px,42%);gap:clamp(1rem,3vw,3rem);align-items:center}
|
|
1159
|
+
.mdx-hero:not(.has-image){grid-template-columns:1fr}
|
|
1160
|
+
.mdx-hero-copy{min-width:0}
|
|
1161
|
+
.mdx-hero-eyebrow{margin:0 0 .75rem;color:var(--hono-decks-accent-color);text-transform:uppercase;font-size:.85rem;letter-spacing:0}
|
|
1162
|
+
.mdx-hero h1{margin:0;font-size:clamp(2.2rem,5vw,5rem);line-height:1.02}
|
|
1163
|
+
.mdx-hero-subtitle{margin:1rem 0 0;font-size:clamp(1rem,1.8vw,1.5rem);line-height:1.45;color:var(--hono-decks-muted-color)}
|
|
1164
|
+
.mdx-hero-image{width:100%;height:auto;max-height:70vh;object-fit:contain;border-radius:8px}
|
|
1165
|
+
[data-hono-decks-fire]{--fire-duration:.18s;--fire-easing:ease;--fire-opacity:0;--fire-transform:translateY(.35rem);--fire-filter:none;visibility:visible;opacity:1;transform:none;filter:none;transition:visibility 0s linear 0s,opacity var(--fire-duration) var(--fire-easing),transform var(--fire-duration) var(--fire-easing),filter var(--fire-duration) var(--fire-easing)}
|
|
1166
|
+
[data-hono-decks-fire][data-fire-hidden]{visibility:hidden;opacity:var(--fire-opacity);transform:var(--fire-transform);filter:var(--fire-filter);transition-delay:var(--fire-duration),0s,0s,0s}
|
|
1167
|
+
[data-fire-effect=none]{--fire-duration:0s;--fire-transform:none}
|
|
1168
|
+
[data-fire-effect=fade]{--fire-transform:none}
|
|
1169
|
+
[data-fire-effect=fade-up]{--fire-transform:translateY(.85rem)}
|
|
1170
|
+
[data-fire-effect=scale]{--fire-transform:scale(.96)}
|
|
1171
|
+
body:not([data-overview-mode]) .hono-decks-deck{position:relative}
|
|
1172
|
+
body:not([data-overview-mode]) .slide{position:absolute;inset:0;width:100%;height:100%}
|
|
1173
|
+
.slide[data-active-transition]{transition:opacity var(--hono-decks-active-transition-duration,var(--hono-decks-slide-transition-duration,var(--hono-decks-transition-duration))) var(--hono-decks-active-transition-easing,var(--hono-decks-slide-transition-easing,var(--hono-decks-transition-easing))),transform var(--hono-decks-active-transition-duration,var(--hono-decks-slide-transition-duration,var(--hono-decks-transition-duration))) var(--hono-decks-active-transition-easing,var(--hono-decks-slide-transition-easing,var(--hono-decks-transition-easing)));will-change:opacity,transform}
|
|
1174
|
+
.slide[data-slide-state="inactive"]{visibility:hidden;pointer-events:none}
|
|
1175
|
+
.slide[data-slide-state="active"]{visibility:visible;opacity:1;transform:translate3d(0,0,0) scale(1)}
|
|
1176
|
+
.slide[data-active-transition="fade"][data-slide-state="entering"],.slide[data-active-transition="fade"][data-slide-state="leaving"],.slide[data-active-transition="view-transition"][data-slide-state="entering"],.slide[data-active-transition="view-transition"][data-slide-state="leaving"]{opacity:0}
|
|
1177
|
+
.slide[data-active-transition="fade-out"][data-slide-state="entering"]{opacity:1}
|
|
1178
|
+
.slide[data-active-transition="fade-out"][data-slide-state="leaving"]{opacity:0}
|
|
1179
|
+
.slide[data-active-transition="slide-left"][data-slide-direction="forward"][data-slide-state="entering"],.slide[data-active-transition="slide-right"][data-slide-direction="backward"][data-slide-state="leaving"]{transform:translate3d(100%,0,0)}
|
|
1180
|
+
.slide[data-active-transition="slide-left"][data-slide-direction="forward"][data-slide-state="leaving"],.slide[data-active-transition="slide-right"][data-slide-direction="backward"][data-slide-state="entering"]{transform:translate3d(-100%,0,0)}
|
|
1181
|
+
.slide[data-active-transition="slide-left"][data-slide-direction="backward"][data-slide-state="entering"],.slide[data-active-transition="slide-right"][data-slide-direction="forward"][data-slide-state="leaving"]{transform:translate3d(-100%,0,0)}
|
|
1182
|
+
.slide[data-active-transition="slide-left"][data-slide-direction="backward"][data-slide-state="leaving"],.slide[data-active-transition="slide-right"][data-slide-direction="forward"][data-slide-state="entering"]{transform:translate3d(100%,0,0)}
|
|
1183
|
+
.slide[data-active-transition="slide-up"][data-slide-direction="forward"][data-slide-state="entering"],.slide[data-active-transition="slide-down"][data-slide-direction="backward"][data-slide-state="leaving"]{transform:translate3d(0,100%,0)}
|
|
1184
|
+
.slide[data-active-transition="slide-up"][data-slide-direction="forward"][data-slide-state="leaving"],.slide[data-active-transition="slide-down"][data-slide-direction="backward"][data-slide-state="entering"]{transform:translate3d(0,-100%,0)}
|
|
1185
|
+
.slide[data-active-transition="slide-up"][data-slide-direction="backward"][data-slide-state="entering"],.slide[data-active-transition="slide-down"][data-slide-direction="forward"][data-slide-state="leaving"]{transform:translate3d(0,-100%,0)}
|
|
1186
|
+
.slide[data-active-transition="slide-up"][data-slide-direction="backward"][data-slide-state="leaving"],.slide[data-active-transition="slide-down"][data-slide-direction="forward"][data-slide-state="entering"]{transform:translate3d(0,100%,0)}
|
|
1187
|
+
body:not([data-overview-mode]) .slide[hidden]{display:none}
|
|
1188
|
+
body[data-overview-mode] .hono-decks-deck{grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}
|
|
1189
|
+
body[data-overview-mode] .slide{cursor:pointer}
|
|
1190
|
+
body[data-presenter-mode] .speaker-notes{display:block;margin-top:1rem;padding:.75rem;border-radius:8px;background:var(--hono-decks-card-image-background)}
|
|
1191
|
+
.hono-decks-warnings{margin:1rem;padding:.75rem;border-radius:14px;background:var(--hono-decks-warning-background);color:var(--hono-decks-warning-color)}
|
|
1192
|
+
@media screen{html[data-hono-decks-print-preview]{width:auto;height:auto;min-height:100%;overflow:visible}
|
|
1193
|
+
body[data-hono-decks-print-preview]{min-height:100vh;overflow:auto;color-scheme:light;color:#000;--hono-decks-print-gap:6mm;--hono-decks-print-slot-height:80mm;--hono-decks-print-scale:.28}
|
|
1194
|
+
body[data-hono-decks-print-preview] .hono-decks-stage{display:block;width:auto;height:auto;min-height:100vh;overflow:visible;padding:12mm 0;box-sizing:border-box}
|
|
1195
|
+
body[data-hono-decks-print-preview] .hono-decks-deck{display:grid;grid-template-columns:1fr;grid-auto-rows:var(--hono-decks-print-slot-height);gap:var(--hono-decks-print-gap);width:calc(var(--hono-decks-print-slot-height) * 16 / 9);max-width:calc(100vw - 24px);height:auto;margin:0 auto;transform:none!important}
|
|
1196
|
+
body[data-hono-decks-print-preview] .slide{position:static;width:100%;max-width:100%;height:var(--hono-decks-print-slot-height);aspect-ratio:16/9;justify-self:center;align-self:center;padding:0;box-shadow:0 2px 10px rgba(15,23,42,.16);transition:none!important;transform:none!important}
|
|
1197
|
+
body[data-hono-decks-print-preview] .slide.layout-cover,body[data-hono-decks-print-preview] .slide.layout-statement{display:block}
|
|
1198
|
+
body[data-hono-decks-print-preview] .hono-decks-slide-content{width:var(--hono-decks-width);height:var(--hono-decks-height);box-sizing:border-box;padding:clamp(1.2rem,3vw,3rem);transform:scale(var(--hono-decks-print-scale));transform-origin:left top;overflow:hidden}
|
|
1199
|
+
body[data-hono-decks-print-preview]:not([data-overview-mode]) .slide[hidden]{display:block!important}
|
|
1200
|
+
body[data-hono-decks-print-preview] .slide[data-slide-state]{visibility:visible!important;opacity:1!important;transform:none!important}
|
|
1201
|
+
body[data-hono-decks-print-preview] [data-hono-decks-fire]{visibility:visible!important;opacity:1!important;transform:none!important;filter:none!important}}
|
|
1202
|
+
@page{size:A4 portrait;margin:12mm}
|
|
1203
|
+
@media print{:root{color-scheme:light;color:#000;--hono-decks-print-gap:6mm;--hono-decks-print-slot-height:80mm;--hono-decks-print-scale:.28}html,body{width:auto;height:auto;overflow:visible}.hono-decks-stage{display:block;width:auto;height:auto;overflow:visible}.hono-decks-deck{display:grid;grid-template-columns:1fr;grid-auto-rows:var(--hono-decks-print-slot-height);gap:var(--hono-decks-print-gap);width:calc(var(--hono-decks-print-slot-height) * 16 / 9);height:auto;margin:0 auto;transform:none!important}body:not([data-overview-mode]) .slide{position:relative}.slide{position:relative;width:100%;max-width:100%;height:var(--hono-decks-print-slot-height);aspect-ratio:16/9;justify-self:center;align-self:center;padding:0;page-break-after:auto;break-after:auto;break-inside:avoid;box-shadow:none;transition:none!important;transform:none!important}.slide.layout-cover,.slide.layout-statement{display:block}.hono-decks-slide-content{position:absolute;inset:0 auto auto 0;width:var(--hono-decks-width);height:var(--hono-decks-height);box-sizing:border-box;padding:clamp(1.2rem,3vw,3rem);zoom:var(--hono-decks-print-scale);overflow:hidden}.slide:nth-of-type(3n):not(:last-child){page-break-after:always;break-after:page}body:not([data-overview-mode]) .slide[hidden]{display:block!important}.slide[data-slide-state]{visibility:visible!important;opacity:1!important;transform:none!important}[data-hono-decks-fire]{visibility:visible!important;opacity:1!important;transform:none!important;filter:none!important}}
|
|
1204
|
+
@media print{.hono-decks-embed-viewport iframe,.hono-decks-tweet-embed iframe,.hono-decks-tweet-embed .twitter-tweet{display:none!important}.hono-decks-embed-print-fallback{display:grid;width:100%;height:100%;place-items:center;overflow:hidden;background:var(--hono-decks-card-background);color:var(--hono-decks-accent-color);text-decoration:none}.hono-decks-embed-print-fallback:has(.hono-decks-embed-print-poster){display:block}.hono-decks-embed-print-poster+span{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}.hono-decks-tweet-print-fallback{display:flex;min-height:12rem;align-items:center;justify-content:center;border:1px solid var(--hono-decks-border-color);border-radius:8px;background:var(--hono-decks-card-background);color:var(--hono-decks-accent-color);text-decoration:none}}
|
|
1205
|
+
@media (prefers-reduced-motion: reduce){*,*::before,*::after{scroll-behavior:auto!important;animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important;transition-delay:0s!important}.slide[data-active-transition]{transform:none!important}}`;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// src/renderer/presentation-page.ts
|
|
1209
|
+
function presentationPageTitle(deck) {
|
|
1210
|
+
return deck.meta.title ?? deck.slug;
|
|
1211
|
+
}
|
|
1212
|
+
function renderCompiledDeckPage(input) {
|
|
1213
|
+
const { deck } = input;
|
|
1214
|
+
const document = input.document ?? { lang: "ja", head: "" };
|
|
1215
|
+
const nonceAttribute = documentNonceAttribute(document.nonce);
|
|
1216
|
+
const warnings = renderWarnings(deck);
|
|
1217
|
+
const htmlAttrs = input.printPreview ? ' data-hono-decks-print-preview="true"' : "";
|
|
1218
|
+
const bodyAttrs = [
|
|
1219
|
+
input.printPreview ? 'data-hono-decks-print-preview="true"' : "",
|
|
1220
|
+
input.speakerNotes === false ? 'data-hono-decks-projection="true"' : ""
|
|
1221
|
+
].filter(Boolean);
|
|
1222
|
+
return `<!doctype html>
|
|
1223
|
+
<html lang="${escapeHtml4(document.lang)}"${htmlAttrs}>
|
|
1224
|
+
<head>
|
|
1225
|
+
<meta charset="utf-8" />
|
|
1226
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1227
|
+
<title>${escapeHtml4(presentationPageTitle(deck))}</title>
|
|
1228
|
+
<style${nonceAttribute}>${basePresentationStyle()}${deck.themeStyle ?? ""}${input.style ?? ""}</style>
|
|
1229
|
+
${document.head}
|
|
1230
|
+
</head>
|
|
1231
|
+
<body${bodyAttrs.length ? ` ${bodyAttrs.join(" ")}` : ""}>
|
|
1232
|
+
${warnings}
|
|
1233
|
+
${renderCompiledDeck(deck, {
|
|
1234
|
+
components: mergeComponentInputs(deck.componentRegistry, input.components),
|
|
1235
|
+
speakerNotes: input.speakerNotes
|
|
1236
|
+
})}
|
|
1237
|
+
${input.printPreview ? renderPrintPreviewScript(document.nonce) : renderPresentationScript(document.nonce)}
|
|
1238
|
+
${!input.printPreview && input.liveReloadPath ? renderLiveReloadScript(input.liveReloadPath, document.nonce) : ""}
|
|
1239
|
+
${!input.printPreview && input.clientEntry ? renderClientEntryScript(input.clientEntry, document.nonce) : ""}
|
|
1240
|
+
</body>
|
|
1241
|
+
</html>`;
|
|
1242
|
+
}
|
|
1243
|
+
async function renderCompiledDeckPageAsync(input) {
|
|
1244
|
+
const { deck } = input;
|
|
1245
|
+
const document = input.document ?? { lang: "ja", head: "" };
|
|
1246
|
+
const nonceAttribute = documentNonceAttribute(document.nonce);
|
|
1247
|
+
const warnings = renderWarnings(deck);
|
|
1248
|
+
const htmlAttrs = input.printPreview ? ' data-hono-decks-print-preview="true"' : "";
|
|
1249
|
+
const bodyAttrs = [
|
|
1250
|
+
input.printPreview ? 'data-hono-decks-print-preview="true"' : "",
|
|
1251
|
+
input.speakerNotes === false ? 'data-hono-decks-projection="true"' : ""
|
|
1252
|
+
].filter(Boolean);
|
|
1253
|
+
return `<!doctype html>
|
|
1254
|
+
<html lang="${escapeHtml4(document.lang)}"${htmlAttrs}>
|
|
1255
|
+
<head>
|
|
1256
|
+
<meta charset="utf-8" />
|
|
1257
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1258
|
+
<title>${escapeHtml4(presentationPageTitle(deck))}</title>
|
|
1259
|
+
<style${nonceAttribute}>${basePresentationStyle()}${deck.themeStyle ?? ""}${input.style ?? ""}</style>
|
|
1260
|
+
${document.head}
|
|
1261
|
+
</head>
|
|
1262
|
+
<body${bodyAttrs.length ? ` ${bodyAttrs.join(" ")}` : ""}>
|
|
1263
|
+
${warnings}
|
|
1264
|
+
${await renderCompiledDeckAsync(deck, {
|
|
1265
|
+
components: mergeComponentInputs(deck.componentRegistry, input.components),
|
|
1266
|
+
speakerNotes: input.speakerNotes
|
|
1267
|
+
})}
|
|
1268
|
+
${input.printPreview ? renderPrintPreviewScript(document.nonce) : renderPresentationScript(document.nonce)}
|
|
1269
|
+
${!input.printPreview && input.liveReloadPath ? renderLiveReloadScript(input.liveReloadPath, document.nonce) : ""}
|
|
1270
|
+
${!input.printPreview && input.clientEntry ? renderClientEntryScript(input.clientEntry, document.nonce) : ""}
|
|
1271
|
+
</body>
|
|
1272
|
+
</html>`;
|
|
1273
|
+
}
|
|
1274
|
+
async function renderPresenterPageAsync(input) {
|
|
1275
|
+
const { deck } = input;
|
|
1276
|
+
const document = input.document ?? { lang: "ja", head: "" };
|
|
1277
|
+
const nonceAttribute = documentNonceAttribute(document.nonce);
|
|
1278
|
+
const components = mergeComponentInputs(deck.componentRegistry, input.components);
|
|
1279
|
+
const mountPath = input.mountPath.replace(/\/$/, "") || "/";
|
|
1280
|
+
const deckPath = `${mountPath === "/" ? "" : mountPath}/${encodeURIComponent(deck.slug)}`;
|
|
1281
|
+
const stateQuery = input.presenterStateQuery ?? "";
|
|
1282
|
+
const deckHref = `${deckPath}${stateQuery}`;
|
|
1283
|
+
const projectionPath = `${deckPath}/presentation${stateQuery}`;
|
|
1284
|
+
const previews = await Promise.all(
|
|
1285
|
+
deck.slides.map(
|
|
1286
|
+
(slide) => renderCompiledSlideAsync(slide, deck.assets, { components, deck, speakerNotes: false, slideState: "active" })
|
|
1287
|
+
)
|
|
1288
|
+
);
|
|
1289
|
+
const notes = deck.slides.map((slide) => slide.notes ?? slide.meta.notes);
|
|
1290
|
+
return `<!doctype html>
|
|
1291
|
+
<html lang="${escapeHtml4(document.lang)}">
|
|
1292
|
+
<head>
|
|
1293
|
+
<meta charset="utf-8" />
|
|
1294
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1295
|
+
<title>${escapeHtml4(presentationPageTitle(deck))} - Presenter</title>
|
|
1296
|
+
<style${nonceAttribute}>${basePresenterStyle()}${deck.themeStyle ?? ""}${input.style ?? ""}</style>
|
|
1297
|
+
${document.head}
|
|
1298
|
+
</head>
|
|
1299
|
+
<body>
|
|
1300
|
+
<main class="hono-decks-presenter" data-hono-decks-presenter data-slide-index="0">
|
|
1301
|
+
<section class="hono-decks-presenter-current" data-hono-decks-presenter-current aria-label="Current slide">
|
|
1302
|
+
<nav class="hono-decks-presenter-controls" data-hono-decks-presenter-controls aria-label="Presenter controls">
|
|
1303
|
+
<a href="${escapeHtml4(mountPath)}" title="Deck list" aria-label="Deck list">${renderControlIconHtml("home")}</a>
|
|
1304
|
+
<a href="${escapeHtml4(deckHref)}" title="Viewer" aria-label="Viewer">${renderControlIconHtml("viewer")}</a>
|
|
1305
|
+
<button type="button" data-action="openProjection" data-projection-url="${escapeHtml4(projectionPath)}" title="Projection" aria-label="Projection">${renderControlIconHtml("projection")}</button>
|
|
1306
|
+
<button type="button" data-action="previous" title="Previous slide" aria-label="Previous slide">${renderControlIconHtml("previous")}</button>
|
|
1307
|
+
<span data-hono-decks-presenter-position>1 / ${deck.slides.length}</span>
|
|
1308
|
+
<button type="button" data-action="next" title="Next slide" aria-label="Next slide">${renderControlIconHtml("next")}</button>
|
|
1309
|
+
<span data-hono-decks-presenter-connection>Connected</span>
|
|
1310
|
+
</nav>
|
|
1311
|
+
<iframe title="${escapeHtml4(presentationPageTitle(deck))}" src="${escapeHtml4(projectionPath)}"></iframe>
|
|
1312
|
+
</section>
|
|
1313
|
+
<aside class="hono-decks-presenter-panel" aria-label="Presenter panel">
|
|
1314
|
+
<section class="hono-decks-presenter-next" data-hono-decks-presenter-next aria-label="Next slide preview">
|
|
1315
|
+
<h2>Next slide</h2>
|
|
1316
|
+
<div class="hono-decks-presenter-preview-list">
|
|
1317
|
+
${previews.map(
|
|
1318
|
+
(preview, index) => `<div class="hono-decks-presenter-preview" data-hono-decks-presenter-preview data-slide-index="${index}"${index === 1 ? "" : " hidden"}>${preview}</div>`
|
|
1319
|
+
).join("\n")}
|
|
1320
|
+
<p class="hono-decks-presenter-no-next" data-hono-decks-presenter-no-next${deck.slides.length > 1 ? " hidden" : ""}>No next slide</p>
|
|
1321
|
+
</div>
|
|
1322
|
+
</section>
|
|
1323
|
+
<section class="hono-decks-presenter-notes" data-hono-decks-presenter-notes aria-label="Speaker notes">
|
|
1324
|
+
<h2>Speaker notes</h2>
|
|
1325
|
+
${notes.map(
|
|
1326
|
+
(note, index) => `<article data-hono-decks-presenter-note data-slide-index="${index}"${index === 0 ? "" : " hidden"}>${note ? escapeHtml4(note) : "<p>No speaker notes.</p>"}</article>`
|
|
1327
|
+
).join("\n")}
|
|
1328
|
+
</section>
|
|
1329
|
+
</aside>
|
|
1330
|
+
</main>
|
|
1331
|
+
${renderPresenterScript(deck.slides.length, document.nonce)}
|
|
1332
|
+
</body>
|
|
1333
|
+
</html>`;
|
|
1334
|
+
}
|
|
1335
|
+
function renderWarnings(deck) {
|
|
1336
|
+
return deck.warnings.length ? `<aside class="hono-decks-warnings">${deck.warnings.map((warning) => `<p>${escapeHtml4(warning.message)}</p>`).join("")}</aside>` : "";
|
|
1337
|
+
}
|
|
1338
|
+
function renderPrintPreviewScript(nonce) {
|
|
1339
|
+
return `<script${documentNonceAttribute(nonce)}>
|
|
1340
|
+
(() => {
|
|
1341
|
+
const params = new URLSearchParams(window.location.search);
|
|
1342
|
+
if (params.get("autoprint") !== "1") return;
|
|
1343
|
+
window.addEventListener("load", () => {
|
|
1344
|
+
window.requestAnimationFrame(() => window.print());
|
|
1345
|
+
}, { once: true });
|
|
1346
|
+
})();
|
|
1347
|
+
</script>`;
|
|
1348
|
+
}
|
|
1349
|
+
function mergeComponentInputs(base, overrides) {
|
|
1350
|
+
if (!base) return overrides;
|
|
1351
|
+
if (!overrides) return base;
|
|
1352
|
+
return { ...base, ...overrides };
|
|
1353
|
+
}
|
|
1354
|
+
function basePresenterStyle() {
|
|
1355
|
+
return `${basePresentationStyle()}
|
|
1356
|
+
body{margin:0;min-height:100vh;background:#050816;color:#eef2ff;font-family:Inter,ui-sans-serif,system-ui,sans-serif}
|
|
1357
|
+
.hono-decks-presenter{box-sizing:border-box;display:grid;grid-template-columns:minmax(0,2fr) minmax(320px,1fr);gap:16px;min-height:100vh;padding:16px}
|
|
1358
|
+
.hono-decks-presenter-current,.hono-decks-presenter-panel{min-width:0}
|
|
1359
|
+
.hono-decks-presenter-current{display:grid;grid-template-rows:auto 1fr;gap:12px;align-items:center}
|
|
1360
|
+
.hono-decks-presenter-controls{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
|
|
1361
|
+
.hono-decks-presenter-controls a,.hono-decks-presenter-controls button,.hono-decks-presenter-controls span{border:1px solid rgba(148,163,184,.32);border-radius:8px;background:rgba(15,23,42,.78);color:inherit;padding:8px 10px;font:inherit;font-size:14px}
|
|
1362
|
+
.hono-decks-presenter-controls a,.hono-decks-presenter-controls button{display:inline-flex;align-items:center;justify-content:center;width:38px;min-height:38px;box-sizing:border-box;text-decoration:none;cursor:pointer}
|
|
1363
|
+
.hono-decks-presenter-controls a *,.hono-decks-presenter-controls button *{pointer-events:none;cursor:pointer}
|
|
1364
|
+
.hono-decks-control-icon{width:16px;height:16px;flex:0 0 auto;stroke:currentColor;pointer-events:none}
|
|
1365
|
+
.hono-decks-presenter-current iframe{width:100%;aspect-ratio:16/9;border:0;border-radius:8px;background:#000}
|
|
1366
|
+
.hono-decks-presenter-panel{display:grid;grid-template-rows:auto 1fr;gap:16px}
|
|
1367
|
+
.hono-decks-presenter-next,.hono-decks-presenter-notes{min-width:0;border:1px solid rgba(148,163,184,.28);border-radius:8px;background:rgba(15,23,42,.78);padding:12px}
|
|
1368
|
+
.hono-decks-presenter-next h2,.hono-decks-presenter-notes h2{margin:0 0 8px;font-size:16px;color:#93c5fd}
|
|
1369
|
+
.hono-decks-presenter-preview{position:relative;aspect-ratio:16/9;overflow:hidden;border-radius:6px;background:#020617}
|
|
1370
|
+
body:not([data-overview-mode]) .hono-decks-presenter-preview .slide{position:absolute;inset:0 auto auto 0;width:var(--hono-decks-width);height:var(--hono-decks-height);aspect-ratio:16/9;max-width:none;transform-origin:left top;transition:none!important}
|
|
1371
|
+
.hono-decks-presenter-preview .hono-decks-slide-content{transform-origin:top left}
|
|
1372
|
+
.hono-decks-presenter-no-next:not([hidden]){display:grid;place-items:center;aspect-ratio:16/9;margin:0;border-radius:6px;background:#020617;color:#93a4bd;font-size:14px}
|
|
1373
|
+
.hono-decks-presenter-notes article{white-space:pre-wrap;font-size:18px;line-height:1.6}
|
|
1374
|
+
@media (max-width:900px){.hono-decks-presenter{grid-template-columns:1fr}.hono-decks-presenter-panel{grid-template-rows:auto auto}}`;
|
|
1375
|
+
}
|
|
1376
|
+
function renderPresenterScript(slideCount, nonce) {
|
|
1377
|
+
return `<script${documentNonceAttribute(nonce)}>
|
|
1378
|
+
(() => {
|
|
1379
|
+
const root = document.querySelector("[data-hono-decks-presenter]");
|
|
1380
|
+
const frame = document.querySelector("[data-hono-decks-presenter-current] iframe");
|
|
1381
|
+
const previews = Array.from(document.querySelectorAll("[data-hono-decks-presenter-preview]"));
|
|
1382
|
+
const notes = Array.from(document.querySelectorAll("[data-hono-decks-presenter-note]"));
|
|
1383
|
+
const noNext = document.querySelector("[data-hono-decks-presenter-no-next]");
|
|
1384
|
+
const position = document.querySelector("[data-hono-decks-presenter-position]");
|
|
1385
|
+
const DESIGN_WIDTH = 1920;
|
|
1386
|
+
const DESIGN_HEIGHT = 1080;
|
|
1387
|
+
let projectionWindow = null;
|
|
1388
|
+
let currentIndex = 0;
|
|
1389
|
+
let currentStepIndex = 0;
|
|
1390
|
+
|
|
1391
|
+
function fitPresenterPreview(preview) {
|
|
1392
|
+
const slide = preview?.querySelector(".slide");
|
|
1393
|
+
if (!(preview instanceof HTMLElement) || !(slide instanceof HTMLElement)) return;
|
|
1394
|
+
const bounds = preview.getBoundingClientRect();
|
|
1395
|
+
if (bounds.width <= 0 || bounds.height <= 0) return;
|
|
1396
|
+
const scale = Math.min(bounds.width / DESIGN_WIDTH, bounds.height / DESIGN_HEIGHT);
|
|
1397
|
+
slide.style.transform = "scale(" + scale + ")";
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
function fitVisiblePresenterPreviews() {
|
|
1401
|
+
previews.forEach((preview) => {
|
|
1402
|
+
if (!preview.hidden) fitPresenterPreview(preview);
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
function show(index, stepIndex = 0) {
|
|
1407
|
+
const nextIndex = index + 1;
|
|
1408
|
+
currentIndex = index;
|
|
1409
|
+
currentStepIndex = stepIndex;
|
|
1410
|
+
writePresenterPaginationState();
|
|
1411
|
+
root?.setAttribute("data-slide-index", String(index));
|
|
1412
|
+
if (position) position.textContent = String(index + 1) + " / ${slideCount}";
|
|
1413
|
+
previews.forEach((preview) => {
|
|
1414
|
+
preview.hidden = Number(preview.getAttribute("data-slide-index")) !== nextIndex;
|
|
1415
|
+
});
|
|
1416
|
+
if (noNext) noNext.hidden = previews.some((preview) => Number(preview.getAttribute("data-slide-index")) === nextIndex);
|
|
1417
|
+
notes.forEach((note) => {
|
|
1418
|
+
note.hidden = Number(note.getAttribute("data-slide-index")) !== index;
|
|
1419
|
+
});
|
|
1420
|
+
fitVisiblePresenterPreviews();
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
function writePresenterPaginationState() {
|
|
1424
|
+
const url = new URL(window.location.href);
|
|
1425
|
+
const params = url.searchParams;
|
|
1426
|
+
params.set("slide", String(currentIndex + 1));
|
|
1427
|
+
params.set("step", String(currentStepIndex));
|
|
1428
|
+
window.history.replaceState(null, "", url);
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
function sendCommand(action) {
|
|
1432
|
+
const message = { type: "hono-decks:command", action };
|
|
1433
|
+
frame?.contentWindow?.postMessage(message, window.location.origin);
|
|
1434
|
+
if (projectionWindow?.closed) projectionWindow = null;
|
|
1435
|
+
projectionWindow?.postMessage(message, window.location.origin);
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
function syncProjectionState(source, index, stepIndex) {
|
|
1439
|
+
const message = { type: "hono-decks:command", action: "goTo", index, stepIndex };
|
|
1440
|
+
if (source !== frame?.contentWindow) frame?.contentWindow?.postMessage(message, window.location.origin);
|
|
1441
|
+
if (projectionWindow?.closed) projectionWindow = null;
|
|
1442
|
+
if (projectionWindow && source !== projectionWindow) projectionWindow.postMessage(message, window.location.origin);
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
function openProjection(button) {
|
|
1446
|
+
const projectionUrl = button.getAttribute("data-projection-url");
|
|
1447
|
+
if (!projectionUrl) return;
|
|
1448
|
+
const url = new URL(projectionUrl, window.location.href);
|
|
1449
|
+
url.searchParams.set("slide", String(currentIndex + 1));
|
|
1450
|
+
url.searchParams.set("step", String(currentStepIndex));
|
|
1451
|
+
const features = "popup=yes,width=1920,height=1080,left=0,top=0,menubar=no,toolbar=no,location=no,status=no";
|
|
1452
|
+
projectionWindow = window.open(url.href, "hono-decks-projection", features);
|
|
1453
|
+
projectionWindow?.focus?.();
|
|
1454
|
+
window.setTimeout(() => {
|
|
1455
|
+
projectionWindow?.postMessage(
|
|
1456
|
+
{ type: "hono-decks:command", action: "goTo", index: currentIndex, stepIndex: currentStepIndex },
|
|
1457
|
+
window.location.origin,
|
|
1458
|
+
);
|
|
1459
|
+
}, 250);
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
fitVisiblePresenterPreviews();
|
|
1463
|
+
window.addEventListener("resize", fitVisiblePresenterPreviews);
|
|
1464
|
+
document.querySelectorAll("[data-action='previous']").forEach((button) => {
|
|
1465
|
+
button.addEventListener("click", () => sendCommand("previous"));
|
|
1466
|
+
});
|
|
1467
|
+
document.querySelectorAll("[data-action='next']").forEach((button) => {
|
|
1468
|
+
button.addEventListener("click", () => sendCommand("next"));
|
|
1469
|
+
});
|
|
1470
|
+
document.querySelectorAll("[data-action='openProjection']").forEach((button) => {
|
|
1471
|
+
button.addEventListener("click", () => openProjection(button));
|
|
1472
|
+
});
|
|
1473
|
+
|
|
1474
|
+
window.addEventListener("message", (event) => {
|
|
1475
|
+
if (event.origin !== window.location.origin) return;
|
|
1476
|
+
const message = event.data;
|
|
1477
|
+
if (!message || message.type !== "hono-decks:state") return;
|
|
1478
|
+
if (event.source !== frame?.contentWindow && event.source !== projectionWindow && !window.opener) return;
|
|
1479
|
+
if (event.source !== frame?.contentWindow) projectionWindow = event.source;
|
|
1480
|
+
if (Number.isInteger(message.index)) {
|
|
1481
|
+
const stepIndex = Number.isInteger(message.stepIndex) ? message.stepIndex : 0;
|
|
1482
|
+
const alreadyCurrent = message.index === currentIndex && stepIndex === currentStepIndex;
|
|
1483
|
+
show(message.index, stepIndex);
|
|
1484
|
+
if (!alreadyCurrent) syncProjectionState(event.source, message.index, stepIndex);
|
|
1485
|
+
}
|
|
1486
|
+
});
|
|
1487
|
+
})();
|
|
1488
|
+
</script>`;
|
|
1489
|
+
}
|
|
1490
|
+
function escapeHtml4(value) {
|
|
1491
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
// src/renderer/compiled-render.ts
|
|
1495
|
+
function renderCompiledDeck(deck, input = {}) {
|
|
1496
|
+
const components = normalizeComponents(input.components);
|
|
1497
|
+
return `<main class="hono-decks-stage" data-hono-decks-stage data-deck-slug="${escapeHtml5(deck.slug)}"><div class="hono-decks-deck" data-hono-decks-deck>${deck.slides.map((slide) => renderCompiledSlide(slide, deck.assets, { components, deck, speakerNotes: input.speakerNotes })).join("\n")}</div></main>`;
|
|
1498
|
+
}
|
|
1499
|
+
async function renderCompiledDeckAsync(deck, input = {}) {
|
|
1500
|
+
const components = normalizeComponents(input.components);
|
|
1501
|
+
const slides = await Promise.all(
|
|
1502
|
+
deck.slides.map(
|
|
1503
|
+
(slide) => renderCompiledSlideAsync(slide, deck.assets, { components, deck, speakerNotes: input.speakerNotes })
|
|
1504
|
+
)
|
|
1505
|
+
);
|
|
1506
|
+
return `<main class="hono-decks-stage" data-hono-decks-stage data-deck-slug="${escapeHtml5(deck.slug)}"><div class="hono-decks-deck" data-hono-decks-deck>${slides.join(
|
|
1507
|
+
"\n"
|
|
1508
|
+
)}</div></main>`;
|
|
1509
|
+
}
|
|
1510
|
+
function renderCompiledSlide(slide, assets = [], input = {}) {
|
|
1511
|
+
const components = normalizeComponents(input.components);
|
|
1512
|
+
const slideState = input.slideState ?? "inactive";
|
|
1513
|
+
const layout = slide.meta.layout ?? "default";
|
|
1514
|
+
const classes = ["slide", `layout-${safeClass(layout)}`, slide.meta.className ? safeClass(slide.meta.className) : ""].filter(Boolean).join(" ");
|
|
1515
|
+
const notes = slide.notes ?? slide.meta.notes;
|
|
1516
|
+
const notesHtml = input.speakerNotes === false || !notes ? "" : `<aside class="speaker-notes" hidden>${escapeHtml5(notes)}</aside>`;
|
|
1517
|
+
const html = slide.nodes?.length ? renderSlideNodes(slide.nodes, { components, assets }) : rewriteLocalAssetUrls(slide.html, assets);
|
|
1518
|
+
const style = slideStyleAttribute(slide, assets);
|
|
1519
|
+
const transition = slide.meta.transition ? ` data-transition="${escapeHtml5(safeClass(slide.meta.transition))}"` : "";
|
|
1520
|
+
return `<section class="${classes}" data-slide-index="${slide.index}" data-slide-state="${slideState}"${slide.meta.title ? ` aria-label="${escapeHtml5(slide.meta.title)}"` : ""}${transition}${style}><div class="hono-decks-slide-content">${html}</div>${notesHtml}</section>`;
|
|
1521
|
+
}
|
|
1522
|
+
async function renderCompiledSlideAsync(slide, assets = [], input = {}) {
|
|
1523
|
+
const components = normalizeComponents(input.components);
|
|
1524
|
+
const slideState = input.slideState ?? "inactive";
|
|
1525
|
+
const layout = slide.meta.layout ?? "default";
|
|
1526
|
+
const classes = ["slide", `layout-${safeClass(layout)}`, slide.meta.className ? safeClass(slide.meta.className) : ""].filter(Boolean).join(" ");
|
|
1527
|
+
const notes = slide.notes ?? slide.meta.notes;
|
|
1528
|
+
const notesHtml = input.speakerNotes === false || !notes ? "" : `<aside class="speaker-notes" hidden>${escapeHtml5(notes)}</aside>`;
|
|
1529
|
+
const html = await renderSlideBodyAsync(slide, assets, { ...input, components });
|
|
1530
|
+
const style = slideStyleAttribute(slide, assets);
|
|
1531
|
+
const transition = slide.meta.transition ? ` data-transition="${escapeHtml5(safeClass(slide.meta.transition))}"` : "";
|
|
1532
|
+
return `<section class="${classes}" data-slide-index="${slide.index}" data-slide-state="${slideState}"${slide.meta.title ? ` aria-label="${escapeHtml5(slide.meta.title)}"` : ""}${transition}${style}><div class="hono-decks-slide-content">${html}</div>${notesHtml}</section>`;
|
|
1533
|
+
}
|
|
1534
|
+
function slideStyleAttribute(slide, assets) {
|
|
1535
|
+
const declarations = [];
|
|
1536
|
+
if (slide.meta.background) declarations.push(backgroundStyle(slide.meta.background, assets));
|
|
1537
|
+
if (slide.meta.transitionDuration) {
|
|
1538
|
+
declarations.push(`--hono-decks-slide-transition-duration:${slide.meta.transitionDuration}`);
|
|
1539
|
+
}
|
|
1540
|
+
if (slide.meta.transitionEasing) {
|
|
1541
|
+
declarations.push(`--hono-decks-slide-transition-easing:${slide.meta.transitionEasing}`);
|
|
1542
|
+
}
|
|
1543
|
+
return declarations.length ? ` style="${escapeHtml5(declarations.join(";"))}"` : "";
|
|
1544
|
+
}
|
|
1545
|
+
async function renderSlideBodyAsync(slide, assets, input) {
|
|
1546
|
+
try {
|
|
1547
|
+
return slide.render ? await renderJsxValue(
|
|
1548
|
+
slide.render({ components: createMdxComponents(input.components ?? builtInSlideComponents, { assets }) })
|
|
1549
|
+
) : slide.nodes?.length ? await renderSlideNodesAsync(slide.nodes, { components: input.components, assets }) : rewriteLocalAssetUrls(slide.html, assets);
|
|
1550
|
+
} catch (error) {
|
|
1551
|
+
if (error instanceof RenderError) throw error;
|
|
1552
|
+
throw new RenderError(
|
|
1553
|
+
`Render failed in ${input.deck?.sourcePath ?? "unknown deck"} slide ${slide.index + 1}: ${formatErrorMessage(error)}`,
|
|
1554
|
+
"slide-render-error",
|
|
1555
|
+
error
|
|
1556
|
+
);
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
function normalizeComponents(components) {
|
|
1560
|
+
return {
|
|
1561
|
+
...builtInSlideComponents,
|
|
1562
|
+
...components ? defineSlideComponents(components) : {}
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
function safeClass(value) {
|
|
1566
|
+
return value.toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
|
|
1567
|
+
}
|
|
1568
|
+
function escapeHtml5(value) {
|
|
1569
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1570
|
+
}
|
|
1571
|
+
function formatErrorMessage(error) {
|
|
1572
|
+
if (error instanceof Error) return error.message;
|
|
1573
|
+
return String(error);
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
// src/server/client-entry.ts
|
|
1577
|
+
function serveDecksClientEntry(clientEntry, options = {}) {
|
|
1578
|
+
return (c) => {
|
|
1579
|
+
const headers = {
|
|
1580
|
+
"content-type": options.contentType ?? "text/javascript; charset=utf-8"
|
|
1581
|
+
};
|
|
1582
|
+
const cacheControl = options.cacheControl ?? "public, max-age=300";
|
|
1583
|
+
if (cacheControl !== false) headers["cache-control"] = cacheControl;
|
|
1584
|
+
return c.body(clientEntry, 200, headers);
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
// src/server/path-utils.ts
|
|
1589
|
+
function extractAssetPath(path, slug) {
|
|
1590
|
+
const marker = `/${slug}/assets/`;
|
|
1591
|
+
const markerIndex = path.indexOf(marker);
|
|
1592
|
+
if (markerIndex === -1) return "";
|
|
1593
|
+
return path.slice(markerIndex + marker.length);
|
|
1594
|
+
}
|
|
1595
|
+
function stripPathSuffix(path, suffix) {
|
|
1596
|
+
return path.endsWith(suffix) ? path.slice(0, -suffix.length) : path;
|
|
1597
|
+
}
|
|
1598
|
+
function inferMountPath(path, slug) {
|
|
1599
|
+
const marker = `/${slug}`;
|
|
1600
|
+
const markerIndex = path.indexOf(marker);
|
|
1601
|
+
if (markerIndex === -1) return "";
|
|
1602
|
+
return path.slice(0, markerIndex) || "/";
|
|
1603
|
+
}
|
|
1604
|
+
function resolveGeneratedClientEntryUrl(options, mountPath) {
|
|
1605
|
+
if (!options.clientEntryAsset) return void 0;
|
|
1606
|
+
const basePath = mountPath === "/" ? "" : mountPath.replace(/\/$/, "");
|
|
1607
|
+
return `${basePath}${normalizeClientEntryAssetPath(options.clientEntryAssetPath)}`;
|
|
1608
|
+
}
|
|
1609
|
+
function normalizeClientEntryAssetPath(path = "/_assets/client.js") {
|
|
1610
|
+
const normalized = path.startsWith("/") ? path : `/${path}`;
|
|
1611
|
+
return normalized.replace(/\/{2,}/g, "/");
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
// src/server/paths.ts
|
|
1615
|
+
function createDeckPaths(mountPath, slug) {
|
|
1616
|
+
const mount = normalizeMountPath(mountPath);
|
|
1617
|
+
const viewer = `${mount === "/" ? "" : mount}/${encodeURIComponent(slug)}`;
|
|
1618
|
+
return {
|
|
1619
|
+
viewer,
|
|
1620
|
+
render: `${viewer}/render`,
|
|
1621
|
+
print: `${viewer}/print`,
|
|
1622
|
+
presentation: `${viewer}/presentation`,
|
|
1623
|
+
presenter: `${viewer}/presenter`,
|
|
1624
|
+
embed: `${viewer}/embed`,
|
|
1625
|
+
exportPdf: `${viewer}/export.pdf`,
|
|
1626
|
+
exportPng: `${viewer}/export.png`,
|
|
1627
|
+
ogImage: `${viewer}/og.png`,
|
|
1628
|
+
assets: `${viewer}/assets`
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
function normalizeMountPath(value) {
|
|
1632
|
+
const trimmed = value.trim();
|
|
1633
|
+
if (!trimmed || trimmed === "/") return "/";
|
|
1634
|
+
return `/${trimmed.replace(/^\/+|\/+$/g, "")}`;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
// src/server/browser-export.ts
|
|
1638
|
+
async function renderDeckBrowserExport(c, options, format) {
|
|
1639
|
+
const slug = c.req.param("slug");
|
|
1640
|
+
if (!slug) return c.json({ error: "Deck not found", slug: "" }, 404);
|
|
1641
|
+
const deck = await options.source.getCompiledDeck(c, slug);
|
|
1642
|
+
if (!deck || options.dev !== true && deck.meta.draft) return c.json({ error: "Deck not found", slug }, 404);
|
|
1643
|
+
const exportOptions = options.export;
|
|
1644
|
+
if (!exportOptions) return c.json({ error: "Browser export not configured", slug, format }, 503);
|
|
1645
|
+
const mountPath = stripPathSuffix(c.req.path, `/${slug}/export.${format}`);
|
|
1646
|
+
const resolverInput = {
|
|
1647
|
+
c,
|
|
1648
|
+
deck,
|
|
1649
|
+
slug,
|
|
1650
|
+
mountPath,
|
|
1651
|
+
paths: createDeckPaths(mountPath, slug),
|
|
1652
|
+
format
|
|
1653
|
+
};
|
|
1654
|
+
if (!await isBrowserExportAuthorized(exportOptions, resolverInput)) {
|
|
1655
|
+
return c.json({ error: "Browser export not authorized", slug, format }, 403);
|
|
1656
|
+
}
|
|
1657
|
+
const browser = await exportOptions.browser(resolverInput);
|
|
1658
|
+
if (!browser) return c.json({ error: "Browser export not configured", slug, format }, 503);
|
|
1659
|
+
const printUrl = new URL(resolverInput.paths.print, c.req.url).toString();
|
|
1660
|
+
const action = format === "pdf" ? "pdf" : "screenshot";
|
|
1661
|
+
const response = await browser.quickAction(action, createBrowserRunExportRequest(exportOptions, printUrl, format));
|
|
1662
|
+
const headers = new Headers(response.headers);
|
|
1663
|
+
if (!headers.has("content-type")) headers.set("content-type", format === "pdf" ? "application/pdf" : "image/png");
|
|
1664
|
+
headers.set("content-disposition", `attachment; filename="${exportFilename(exportOptions, deck, format)}"`);
|
|
1665
|
+
return new Response(response.body, {
|
|
1666
|
+
status: response.status,
|
|
1667
|
+
statusText: response.statusText,
|
|
1668
|
+
headers
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
function isPdfExportEnabled(options) {
|
|
1672
|
+
return Boolean(options && options.pdf !== void 0 && options.pdf !== false);
|
|
1673
|
+
}
|
|
1674
|
+
function isPngExportEnabled(options) {
|
|
1675
|
+
return Boolean(options && options.png !== void 0 && options.png !== false);
|
|
1676
|
+
}
|
|
1677
|
+
async function resolveAuthorizedExportPaths(c, deck, mountPath, options) {
|
|
1678
|
+
if (!options) return {};
|
|
1679
|
+
const slug = deck.slug;
|
|
1680
|
+
const paths = createDeckPaths(mountPath, slug);
|
|
1681
|
+
const pdf = isPdfExportEnabled(options) && await isBrowserExportAuthorized(options, { c, deck, slug, mountPath, paths, format: "pdf" });
|
|
1682
|
+
const png = isPngExportEnabled(options) && await isBrowserExportAuthorized(options, { c, deck, slug, mountPath, paths, format: "png" });
|
|
1683
|
+
return { pdf, png };
|
|
1684
|
+
}
|
|
1685
|
+
async function isBrowserExportAuthorized(options, input) {
|
|
1686
|
+
return options.authorize ? options.authorize(input) : true;
|
|
1687
|
+
}
|
|
1688
|
+
function createBrowserRunExportRequest(options, printUrl, format) {
|
|
1689
|
+
if (format === "pdf") {
|
|
1690
|
+
const request2 = exportOptionObject(options.pdf).request ?? {};
|
|
1691
|
+
return {
|
|
1692
|
+
...request2,
|
|
1693
|
+
url: printUrl,
|
|
1694
|
+
gotoOptions: {
|
|
1695
|
+
waitUntil: "networkidle2",
|
|
1696
|
+
timeout: 45e3,
|
|
1697
|
+
...recordValue(request2.gotoOptions)
|
|
1698
|
+
},
|
|
1699
|
+
pdfOptions: {
|
|
1700
|
+
format: "a4",
|
|
1701
|
+
printBackground: true,
|
|
1702
|
+
preferCSSPageSize: true,
|
|
1703
|
+
...recordValue(request2.pdfOptions)
|
|
1704
|
+
}
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
const request = exportOptionObject(options.png).request ?? {};
|
|
1708
|
+
return {
|
|
1709
|
+
...request,
|
|
1710
|
+
url: printUrl,
|
|
1711
|
+
gotoOptions: {
|
|
1712
|
+
waitUntil: "networkidle2",
|
|
1713
|
+
timeout: 45e3,
|
|
1714
|
+
...recordValue(request.gotoOptions)
|
|
1715
|
+
},
|
|
1716
|
+
viewport: {
|
|
1717
|
+
width: 794,
|
|
1718
|
+
height: 1123,
|
|
1719
|
+
deviceScaleFactor: 2,
|
|
1720
|
+
...recordValue(request.viewport)
|
|
1721
|
+
},
|
|
1722
|
+
screenshotOptions: {
|
|
1723
|
+
type: "png",
|
|
1724
|
+
fullPage: true,
|
|
1725
|
+
...recordValue(request.screenshotOptions)
|
|
1726
|
+
}
|
|
1727
|
+
};
|
|
1728
|
+
}
|
|
1729
|
+
function exportFilename(options, deck, format) {
|
|
1730
|
+
const option = exportOptionObject(format === "pdf" ? options.pdf : options.png);
|
|
1731
|
+
const name = typeof option.filename === "function" ? option.filename(deck) : option.filename;
|
|
1732
|
+
return `${safeFilename(name ?? deck.meta.title ?? deck.slug)}.${format}`;
|
|
1733
|
+
}
|
|
1734
|
+
function exportOptionObject(option) {
|
|
1735
|
+
return typeof option === "object" && option ? option : {};
|
|
1736
|
+
}
|
|
1737
|
+
function recordValue(value) {
|
|
1738
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
1739
|
+
}
|
|
1740
|
+
function safeFilename(value) {
|
|
1741
|
+
const normalized = value.trim().replaceAll(/[^a-zA-Z0-9._-]+/g, "-").replaceAll(/^-+|-+$/g, "");
|
|
1742
|
+
return normalized || "deck";
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
// src/server/viewer.ts
|
|
1746
|
+
import { raw } from "hono/html";
|
|
1747
|
+
import { jsx as jsx3 } from "hono/jsx/jsx-runtime";
|
|
1748
|
+
|
|
1749
|
+
// src/server/viewer-script.ts
|
|
1750
|
+
var VIEWER_STATE_MESSAGE_TYPE = "hono-decks:state";
|
|
1751
|
+
var VIEWER_COMMAND_MESSAGE_TYPE = "hono-decks:command";
|
|
1752
|
+
function renderViewerScript(nonce) {
|
|
1753
|
+
const nonceAttribute = nonce ? ` nonce="${escapeHtml6(nonce)}"` : "";
|
|
1754
|
+
return `<script data-hono-decks-viewer-runtime${nonceAttribute}>
|
|
1755
|
+
(() => {
|
|
1756
|
+
const roots = Array.from(document.querySelectorAll("[data-hono-decks-viewer]"));
|
|
1757
|
+
const runtime = window.__honoDecksViewerRuntime ??= { controllers: new Map(), globalInitialized: false };
|
|
1758
|
+
const controllers = runtime.controllers;
|
|
1759
|
+
|
|
1760
|
+
for (const root of roots) {
|
|
1761
|
+
if (root.hasAttribute("data-hono-decks-initialized")) continue;
|
|
1762
|
+
root.setAttribute("data-hono-decks-initialized", "true");
|
|
1763
|
+
|
|
1764
|
+
const viewport = root.querySelector("[data-viewer-viewport]");
|
|
1765
|
+
const iframe = root.querySelector("iframe");
|
|
1766
|
+
const frameOrigin = iframe?.src ? new URL(iframe.src, window.location.href).origin : window.location.origin;
|
|
1767
|
+
const position = root.querySelector("[data-slide-position]");
|
|
1768
|
+
const navigationLayers = root.querySelectorAll("[data-viewer-navigation]");
|
|
1769
|
+
const printPath = root.getAttribute("data-hono-decks-print-path") || "";
|
|
1770
|
+
let pointerStartX = null;
|
|
1771
|
+
let pointerStartY = null;
|
|
1772
|
+
let suppressNavigationClick = false;
|
|
1773
|
+
let suppressNavigationClickTimer = null;
|
|
1774
|
+
|
|
1775
|
+
if (position && viewport) viewport.append(position);
|
|
1776
|
+
|
|
1777
|
+
function sendCommand(action, index) {
|
|
1778
|
+
const target = iframe?.contentWindow;
|
|
1779
|
+
try {
|
|
1780
|
+
const command = target?.__honoDecksPresentationRuntime?.command;
|
|
1781
|
+
if (typeof command === "function") {
|
|
1782
|
+
command(action, index);
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
} catch {
|
|
1786
|
+
// Cross-origin frames cannot expose their runtime. Use postMessage below.
|
|
1787
|
+
}
|
|
1788
|
+
target?.postMessage({ type: ${JSON.stringify(VIEWER_COMMAND_MESSAGE_TYPE)}, action, index }, frameOrigin);
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
function navigationClick(event) {
|
|
1792
|
+
if (suppressNavigationClick) {
|
|
1793
|
+
clearNavigationClickSuppression();
|
|
1794
|
+
event.preventDefault();
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
const action = event.currentTarget?.getAttribute("data-viewer-navigation");
|
|
1798
|
+
if (action === "previous" || action === "next") {
|
|
1799
|
+
sendCommand(action);
|
|
1800
|
+
viewport?.focus({ preventScroll: true });
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
function clearNavigationClickSuppression() {
|
|
1805
|
+
suppressNavigationClick = false;
|
|
1806
|
+
if (suppressNavigationClickTimer !== null) window.clearTimeout(suppressNavigationClickTimer);
|
|
1807
|
+
suppressNavigationClickTimer = null;
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
function suppressCurrentNavigationClick() {
|
|
1811
|
+
clearNavigationClickSuppression();
|
|
1812
|
+
suppressNavigationClick = true;
|
|
1813
|
+
suppressNavigationClickTimer = window.setTimeout(clearNavigationClickSuppression, 0);
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
function viewerPointerDown(event) {
|
|
1817
|
+
pointerStartX = event.clientX;
|
|
1818
|
+
pointerStartY = event.clientY;
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
function viewerPointerUp(event) {
|
|
1822
|
+
if (pointerStartX === null || pointerStartY === null) return;
|
|
1823
|
+
const deltaX = event.clientX - pointerStartX;
|
|
1824
|
+
const deltaY = event.clientY - pointerStartY;
|
|
1825
|
+
pointerStartX = null;
|
|
1826
|
+
pointerStartY = null;
|
|
1827
|
+
if (Math.abs(deltaX) < 48 || Math.abs(deltaX) < Math.abs(deltaY)) return;
|
|
1828
|
+
suppressCurrentNavigationClick();
|
|
1829
|
+
sendCommand(deltaX < 0 ? "next" : "previous");
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
function viewerPointerCancel() {
|
|
1833
|
+
pointerStartX = null;
|
|
1834
|
+
pointerStartY = null;
|
|
1835
|
+
clearNavigationClickSuppression();
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
function isPortraitMobile() {
|
|
1839
|
+
return window.matchMedia("(orientation: portrait) and (pointer: coarse)").matches;
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
async function lockViewerLandscape() {
|
|
1843
|
+
const orientation = window.screen?.orientation;
|
|
1844
|
+
if (!orientation || typeof orientation.lock !== "function") return;
|
|
1845
|
+
try {
|
|
1846
|
+
await orientation.lock("landscape");
|
|
1847
|
+
} catch {
|
|
1848
|
+
// Orientation locking is optional; fullscreen remains available when unsupported.
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
function unlockViewerOrientation() {
|
|
1853
|
+
const orientation = window.screen?.orientation;
|
|
1854
|
+
if (!orientation || typeof orientation.unlock !== "function") return;
|
|
1855
|
+
orientation.unlock();
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
async function toggleViewerFullscreen() {
|
|
1859
|
+
if (document.fullscreenElement) {
|
|
1860
|
+
unlockViewerOrientation();
|
|
1861
|
+
await document.exitFullscreen?.();
|
|
1862
|
+
return;
|
|
1863
|
+
}
|
|
1864
|
+
await root.requestFullscreen?.();
|
|
1865
|
+
if (document.fullscreenElement && isPortraitMobile()) await lockViewerLandscape();
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
function writeViewerPaginationState(message) {
|
|
1869
|
+
if (!Number.isInteger(message.index)) return;
|
|
1870
|
+
const url = new URL(window.location.href);
|
|
1871
|
+
const params = url.searchParams;
|
|
1872
|
+
params.set("slide", String(message.index + 1));
|
|
1873
|
+
params.set("step", String(Number.isInteger(message.stepIndex) ? message.stepIndex : 0));
|
|
1874
|
+
window.history.replaceState(null, "", url);
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
function handleMessage(event) {
|
|
1878
|
+
if (event.source !== iframe?.contentWindow) return;
|
|
1879
|
+
if (event.origin !== frameOrigin) return;
|
|
1880
|
+
const message = event.data;
|
|
1881
|
+
if (!message || message.type !== ${JSON.stringify(VIEWER_STATE_MESSAGE_TYPE)}) return;
|
|
1882
|
+
writeViewerPaginationState(message);
|
|
1883
|
+
root.setAttribute("data-step-index", String(message.stepIndex ?? 0));
|
|
1884
|
+
root.setAttribute("data-step-count", String(message.stepCount ?? 0));
|
|
1885
|
+
if (position) {
|
|
1886
|
+
const slideText = String(message.index + 1) + " / " + String(message.slideCount ?? "?");
|
|
1887
|
+
position.textContent = slideText;
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
function handleKeydown(event) {
|
|
1892
|
+
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "p" && printPath) {
|
|
1893
|
+
event.preventDefault();
|
|
1894
|
+
const printUrl = new URL(printPath, window.location.href);
|
|
1895
|
+
printUrl.searchParams.set("autoprint", "1");
|
|
1896
|
+
window.location.assign(printUrl);
|
|
1897
|
+
return;
|
|
1898
|
+
}
|
|
1899
|
+
if (event.key === "ArrowRight" || event.key === " ") sendCommand("next");
|
|
1900
|
+
if (event.key === "ArrowLeft") sendCommand("previous");
|
|
1901
|
+
if (event.key === "f") void toggleViewerFullscreen();
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
root.querySelectorAll("[data-action='previous']").forEach((control) => {
|
|
1905
|
+
control.addEventListener("click", () => sendCommand("previous"));
|
|
1906
|
+
});
|
|
1907
|
+
root.querySelectorAll("[data-action='next']").forEach((control) => {
|
|
1908
|
+
control.addEventListener("click", () => sendCommand("next"));
|
|
1909
|
+
});
|
|
1910
|
+
root.querySelectorAll("[data-action='fullscreen']").forEach((control) => {
|
|
1911
|
+
control.addEventListener("click", () => { void toggleViewerFullscreen(); });
|
|
1912
|
+
});
|
|
1913
|
+
root.querySelectorAll("[data-action='goTo']").forEach((control) => {
|
|
1914
|
+
control.addEventListener("click", () => {
|
|
1915
|
+
const index = Number(control.getAttribute("data-slide-index"));
|
|
1916
|
+
if (Number.isFinite(index)) sendCommand("goTo", index);
|
|
1917
|
+
});
|
|
1918
|
+
});
|
|
1919
|
+
navigationLayers.forEach((layer) => layer.addEventListener("click", navigationClick));
|
|
1920
|
+
viewport?.addEventListener("pointerdown", viewerPointerDown);
|
|
1921
|
+
viewport?.addEventListener("pointerup", viewerPointerUp);
|
|
1922
|
+
viewport?.addEventListener("pointercancel", viewerPointerCancel);
|
|
1923
|
+
window.addEventListener("message", handleMessage);
|
|
1924
|
+
controllers.set(root, { handleKeydown, unlockViewerOrientation });
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
if (!runtime.globalInitialized) {
|
|
1928
|
+
runtime.globalInitialized = true;
|
|
1929
|
+
document.addEventListener("fullscreenchange", () => {
|
|
1930
|
+
if (document.fullscreenElement) return;
|
|
1931
|
+
controllers.forEach((controller) => controller.unlockViewerOrientation());
|
|
1932
|
+
});
|
|
1933
|
+
|
|
1934
|
+
document.addEventListener("keydown", (event) => {
|
|
1935
|
+
const activeRoot = document.activeElement?.closest?.("[data-hono-decks-viewer]");
|
|
1936
|
+
const onlyRoot = controllers.size === 1 ? controllers.keys().next().value : null;
|
|
1937
|
+
controllers.get(activeRoot || onlyRoot)?.handleKeydown(event);
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
})();
|
|
1941
|
+
</script>`;
|
|
1942
|
+
}
|
|
1943
|
+
function escapeHtml6(value) {
|
|
1944
|
+
return value.replaceAll("&", "&").replaceAll('"', """).replaceAll("<", "<").replaceAll(">", ">");
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
// src/server/viewer-style.ts
|
|
1948
|
+
var VIEWER_ASPECT_RATIO = "16/9";
|
|
1949
|
+
function viewerViewportRule() {
|
|
1950
|
+
return `.hono-decks-viewport{width:min(100%,calc((100vh - 58px) * 16 / 9));aspect-ratio:${VIEWER_ASPECT_RATIO};position:relative;overflow:hidden;touch-action:pan-y}
|
|
1951
|
+
@supports (height:100dvh){.hono-decks-viewport{width:min(100%,calc((100dvh - 58px) * 16 / 9))}}
|
|
1952
|
+
@supports (width:1cqw){.hono-decks-viewport{width:min(100cqw,calc(100cqh * 16 / 9));height:min(100cqh,calc(100cqw * 9 / 16))}}`;
|
|
1953
|
+
}
|
|
1954
|
+
function baseViewerStyle() {
|
|
1955
|
+
return `
|
|
1956
|
+
:root{color-scheme:dark;background:#050816;color:#eef2ff;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
|
1957
|
+
html,body{margin:0;width:100%;height:100%;min-height:100vh}
|
|
1958
|
+
@supports (height:100dvh){html,body{height:100dvh;min-height:100dvh}}
|
|
1959
|
+
body{overflow:hidden}
|
|
1960
|
+
*:focus-visible{outline:none}
|
|
1961
|
+
[data-hono-decks-viewer]{width:100%;height:100vh;min-height:0;display:grid;place-items:center;box-sizing:border-box;overflow:hidden}
|
|
1962
|
+
@supports (height:100dvh){[data-hono-decks-viewer]{height:100dvh}}
|
|
1963
|
+
.hono-decks-viewer-header{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
|
|
1964
|
+
.hono-decks-viewer-title{margin:0;font-size:1rem;line-height:1.25}
|
|
1965
|
+
.hono-decks-viewer-meta{margin:.2rem 0 0;color:#cbd5e1;font-size:.82rem}
|
|
1966
|
+
.hono-decks-viewer-shell{display:grid;grid-template-rows:minmax(0,1fr) auto;place-items:center;width:100%;height:100%;min-width:0;min-height:0;box-sizing:border-box;padding:env(safe-area-inset-top,0) env(safe-area-inset-right,0) env(safe-area-inset-bottom,0) env(safe-area-inset-left,0)}
|
|
1967
|
+
.hono-decks-viewer-stage{display:grid;place-items:center;justify-content:center;width:100%;height:100%;min-width:0;min-height:0;container-type:size}
|
|
1968
|
+
${viewerViewportRule()}
|
|
1969
|
+
.hono-decks-viewport:focus-visible{outline:2px solid currentColor;outline-offset:4px}
|
|
1970
|
+
.hono-decks-frame-stage{width:100%;height:100%}
|
|
1971
|
+
.hono-decks-frame-stage iframe{width:100%;height:100%;border:0;display:block}
|
|
1972
|
+
.hono-decks-viewer-navigation-layer{position:absolute;top:0;bottom:0;width:50%;z-index:2;margin:0;border:0;padding:0;appearance:none;background:transparent;color:transparent;cursor:pointer;touch-action:pan-y}
|
|
1973
|
+
.hono-decks-viewer-navigation-previous{left:0}
|
|
1974
|
+
.hono-decks-viewer-navigation-next{right:0}
|
|
1975
|
+
.hono-decks-viewport>[data-hono-decks-position]{position:absolute;left:50%;bottom:max(8px,env(safe-area-inset-bottom,0));z-index:3;transform:translateX(-50%);border:0;background:transparent;color:inherit;padding:0;font:inherit;font-size:12px;line-height:1;opacity:.38;pointer-events:none;white-space:nowrap}
|
|
1976
|
+
.hono-decks-viewer-controls{display:flex;gap:8px;align-items:center;justify-content:center;max-width:100%;min-width:0;z-index:1}
|
|
1977
|
+
.hono-decks-viewer-controls [data-hono-decks-navigation-control="previous"],.hono-decks-viewer-controls [data-hono-decks-navigation-control="next"],.hono-decks-viewer-controls [data-hono-decks-position]{position:absolute;visibility:hidden;pointer-events:none}
|
|
1978
|
+
.hono-decks-viewer-controls button,.hono-decks-viewer-controls a,.hono-decks-viewer-controls span{border:1px solid rgba(148,163,184,.32);border-radius:8px;background:rgba(15,23,42,.78);color:inherit;padding:8px 10px;font:inherit;font-size:14px}
|
|
1979
|
+
.hono-decks-viewer-controls span{flex:0 0 auto;white-space:nowrap}
|
|
1980
|
+
.hono-decks-viewer-toc button,.hono-decks-viewer-controls button,.hono-decks-viewer-controls a{font:inherit}
|
|
1981
|
+
.hono-decks-viewer-controls button,.hono-decks-viewer-controls a{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;box-sizing:border-box;text-decoration:none;cursor:pointer}
|
|
1982
|
+
.hono-decks-viewer-controls button *,.hono-decks-viewer-controls a *{pointer-events:none;cursor:pointer}
|
|
1983
|
+
.hono-decks-control-icon{width:16px;height:16px;flex:0 0 auto;stroke:currentColor;pointer-events:none}
|
|
1984
|
+
@media (max-width:480px){.hono-decks-viewer-controls{gap:4px}.hono-decks-viewer-controls button,.hono-decks-viewer-controls a{width:36px;height:36px}.hono-decks-viewer-controls button,.hono-decks-viewer-controls a,.hono-decks-viewer-controls span{padding:7px 8px}}
|
|
1985
|
+
@media (pointer:coarse){.hono-decks-viewer-controls [data-hono-decks-navigation-control="fullscreen"],.hono-decks-viewer-controls [data-hono-decks-print]{display:none}}
|
|
1986
|
+
@media (orientation:landscape) and (max-height:600px){.hono-decks-viewer-shell{grid-template-columns:minmax(0,1fr) auto;grid-template-rows:minmax(0,1fr)}.hono-decks-viewer-controls{flex-direction:column}.hono-decks-viewport{width:min(100%,calc(100dvh * 16 / 9))}}
|
|
1987
|
+
@media (prefers-reduced-motion: reduce){*,*::before,*::after{scroll-behavior:auto!important;animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important}}`;
|
|
1988
|
+
}
|
|
1989
|
+
function embeddedViewerStyle() {
|
|
1990
|
+
const root = "[data-hono-decks-viewer][data-hono-decks-embed]";
|
|
1991
|
+
return `${root}{color-scheme:dark;background:#050816;color:#eef2ff;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;display:grid;gap:12px;width:100%;min-width:0;box-sizing:border-box}
|
|
1992
|
+
${root} *:focus-visible{outline:none}
|
|
1993
|
+
${root} .hono-decks-viewer-shell{display:grid;grid-template-rows:minmax(0,1fr) auto;place-items:center;width:100%;min-width:0;min-height:0;box-sizing:border-box}
|
|
1994
|
+
${root} .hono-decks-viewer-stage{display:grid;place-items:center;width:100%;min-width:0;min-height:0;container-type:inline-size}
|
|
1995
|
+
${root} .hono-decks-viewport{width:100%;aspect-ratio:${VIEWER_ASPECT_RATIO};position:relative;overflow:hidden;touch-action:pan-y}
|
|
1996
|
+
${root} .hono-decks-viewport:focus-visible{outline:2px solid currentColor;outline-offset:4px}
|
|
1997
|
+
${root} .hono-decks-frame-stage,${root} .hono-decks-frame-stage iframe{width:100%;height:100%}
|
|
1998
|
+
${root} .hono-decks-frame-stage iframe{border:0;display:block}
|
|
1999
|
+
${root} .hono-decks-viewer-navigation-layer{position:absolute;top:0;bottom:0;width:50%;z-index:2;margin:0;border:0;padding:0;appearance:none;background:transparent;color:transparent;cursor:pointer;touch-action:pan-y}
|
|
2000
|
+
${root} .hono-decks-viewer-navigation-previous{left:0}
|
|
2001
|
+
${root} .hono-decks-viewer-navigation-next{right:0}
|
|
2002
|
+
${root} .hono-decks-viewport>[data-hono-decks-position]{position:absolute;left:50%;bottom:8px;z-index:3;transform:translateX(-50%);border:0;background:transparent;color:inherit;padding:0;font:inherit;font-size:12px;line-height:1;opacity:.5;pointer-events:none;white-space:nowrap}
|
|
2003
|
+
${root} .hono-decks-viewer-controls{display:flex;gap:8px;align-items:center;justify-content:center;max-width:100%;min-width:0;z-index:4}
|
|
2004
|
+
${root} .hono-decks-viewer-controls [data-hono-decks-navigation-control="previous"],${root} .hono-decks-viewer-controls [data-hono-decks-navigation-control="next"],${root} .hono-decks-viewer-controls [data-hono-decks-position]{position:absolute;visibility:hidden;pointer-events:none}
|
|
2005
|
+
${root} .hono-decks-viewer-controls button,${root} .hono-decks-viewer-controls a,${root} .hono-decks-viewer-controls span{border:1px solid rgba(148,163,184,.32);border-radius:8px;background:rgba(15,23,42,.78);color:inherit;padding:8px 10px;font:inherit;font-size:14px}
|
|
2006
|
+
${root} .hono-decks-viewer-controls button,${root} .hono-decks-viewer-controls a{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;box-sizing:border-box;text-decoration:none;cursor:pointer}
|
|
2007
|
+
${root} .hono-decks-viewer-controls button *,${root} .hono-decks-viewer-controls a *{pointer-events:none;cursor:pointer}
|
|
2008
|
+
${root} .hono-decks-control-icon{width:16px;height:16px;flex:0 0 auto;stroke:currentColor;pointer-events:none}
|
|
2009
|
+
${root} .hono-decks-viewer-toc button{font:inherit}
|
|
2010
|
+
@media (prefers-reduced-motion: reduce){${root},${root} *{scroll-behavior:auto!important;animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important}}`;
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
// src/server/viewer.ts
|
|
2014
|
+
async function createDeckViewerParts(input) {
|
|
2015
|
+
const slug = input.deck.slug;
|
|
2016
|
+
const title = input.deck.meta.title ?? slug;
|
|
2017
|
+
const basePath = input.mountPath.replace(/\/$/, "");
|
|
2018
|
+
const paths = createDeckPaths(basePath, slug);
|
|
2019
|
+
const imagePath = await resolveOpenGraphImagePath(input.openGraph, { deck: input.deck, paths });
|
|
2020
|
+
const renderUrl = `${paths.render}${input.viewerStateQuery ?? ""}`;
|
|
2021
|
+
const slides = createDeckToc(input.deck);
|
|
2022
|
+
const meta = {
|
|
2023
|
+
title,
|
|
2024
|
+
description: input.deck.meta.description,
|
|
2025
|
+
paths,
|
|
2026
|
+
availablePages: { print: input.availablePages?.print !== false },
|
|
2027
|
+
availableExports: input.exportPaths ?? {},
|
|
2028
|
+
...imagePath ? { imagePath } : {}
|
|
2029
|
+
};
|
|
2030
|
+
const controlsInput = input.controls === false ? false : input.controls ?? {};
|
|
2031
|
+
const controlsContext = { slug, title, mountPath: basePath, meta, slides };
|
|
2032
|
+
const controls = controlsInput === false ? null : renderViewerControls(controlsInput, controlsContext);
|
|
2033
|
+
return {
|
|
2034
|
+
slug,
|
|
2035
|
+
title,
|
|
2036
|
+
renderUrl,
|
|
2037
|
+
frame: {
|
|
2038
|
+
content: renderViewerFrame({ title, renderUrl }),
|
|
2039
|
+
html: renderViewerFrameHtml({ title, renderUrl })
|
|
2040
|
+
},
|
|
2041
|
+
controls: controls ? { content: controls, html: await renderJsxValue(controls) } : null,
|
|
2042
|
+
toc: { content: renderViewerToc(slides), html: renderViewerTocHtml(slides) },
|
|
2043
|
+
slides,
|
|
2044
|
+
meta
|
|
2045
|
+
};
|
|
2046
|
+
}
|
|
2047
|
+
async function createDeckViewerEmbed(input) {
|
|
2048
|
+
const parts = await createDeckViewerParts(input);
|
|
2049
|
+
const root = jsx3("section", {
|
|
2050
|
+
class: ["hono-decks-embedded-viewer", input.className].filter(Boolean).join(" "),
|
|
2051
|
+
"data-hono-decks-viewer": true,
|
|
2052
|
+
"data-hono-decks-embed": true,
|
|
2053
|
+
"data-deck-slug": parts.slug,
|
|
2054
|
+
...parts.meta.availablePages.print ? { "data-hono-decks-print-path": parts.meta.paths.print } : {},
|
|
2055
|
+
"aria-label": parts.title,
|
|
2056
|
+
children: [
|
|
2057
|
+
jsx3("div", {
|
|
2058
|
+
class: "hono-decks-viewer-shell",
|
|
2059
|
+
children: [parts.frame.content, parts.controls?.content]
|
|
2060
|
+
}),
|
|
2061
|
+
input.toc ? parts.toc.content : null
|
|
2062
|
+
]
|
|
2063
|
+
});
|
|
2064
|
+
const rootHtml = await renderJsxValue(root);
|
|
2065
|
+
const nonceAttribute = input.nonce ? ` nonce="${escapeHtml7(input.nonce)}"` : "";
|
|
2066
|
+
const embedHtml = `<style data-hono-decks-embed-style${nonceAttribute}>${embeddedViewerStyle()}${input.style ?? ""}</style>${rootHtml}${renderViewerScript(input.nonce)}`;
|
|
2067
|
+
return {
|
|
2068
|
+
...parts,
|
|
2069
|
+
embed: raw(embedHtml),
|
|
2070
|
+
embedHtml
|
|
2071
|
+
};
|
|
2072
|
+
}
|
|
2073
|
+
async function renderDeckViewerPage(input) {
|
|
2074
|
+
const parts = await createDeckViewerParts({
|
|
2075
|
+
deck: input.deck,
|
|
2076
|
+
mountPath: input.mountPath,
|
|
2077
|
+
viewerStateQuery: input.viewerStateQuery,
|
|
2078
|
+
controls: input.viewer?.controls,
|
|
2079
|
+
openGraph: input.viewer?.openGraph,
|
|
2080
|
+
availablePages: input.availablePages,
|
|
2081
|
+
exportPaths: await resolveAuthorizedExportPaths(input.c, input.deck, input.mountPath, input.exportOptions)
|
|
2082
|
+
});
|
|
2083
|
+
const renderInput = {
|
|
2084
|
+
...parts,
|
|
2085
|
+
c: input.c,
|
|
2086
|
+
deck: input.deck,
|
|
2087
|
+
mountPath: input.mountPath
|
|
2088
|
+
};
|
|
2089
|
+
const customContent = input.viewer?.render ? await input.viewer.render(renderInput) : void 0;
|
|
2090
|
+
const content = jsx3("main", {
|
|
2091
|
+
"data-hono-decks-viewer": true,
|
|
2092
|
+
"data-deck-slug": parts.slug,
|
|
2093
|
+
...parts.meta.availablePages.print ? { "data-hono-decks-print-path": parts.meta.paths.print } : {},
|
|
2094
|
+
...customContent ? { "aria-label": parts.title } : { "aria-labelledby": "hono-decks-viewer-title" },
|
|
2095
|
+
children: customContent ?? [
|
|
2096
|
+
jsx3("header", {
|
|
2097
|
+
class: "hono-decks-viewer-header",
|
|
2098
|
+
children: [
|
|
2099
|
+
jsx3("h1", { id: "hono-decks-viewer-title", class: "hono-decks-viewer-title", children: parts.title }),
|
|
2100
|
+
jsx3("p", {
|
|
2101
|
+
class: "hono-decks-viewer-meta",
|
|
2102
|
+
children: `${parts.slides.length} ${parts.slides.length === 1 ? "slide" : "slides"}`
|
|
2103
|
+
})
|
|
2104
|
+
]
|
|
2105
|
+
}),
|
|
2106
|
+
jsx3("div", {
|
|
2107
|
+
class: "hono-decks-viewer-shell",
|
|
2108
|
+
children: [parts.frame.content, parts.controls?.content]
|
|
2109
|
+
})
|
|
2110
|
+
]
|
|
2111
|
+
});
|
|
2112
|
+
const viewerHead = typeof input.viewer?.head === "function" ? await input.viewer.head(renderInput) : await input.viewer?.head;
|
|
2113
|
+
const viewerLang = typeof input.viewer?.lang === "function" ? await input.viewer.lang(renderInput) : input.viewer?.lang;
|
|
2114
|
+
const viewerNonce = typeof input.viewer?.nonce === "function" ? await input.viewer.nonce(renderInput) : input.viewer?.nonce;
|
|
2115
|
+
const document = await resolveDeckDocument(
|
|
2116
|
+
{
|
|
2117
|
+
c: input.c,
|
|
2118
|
+
surface: "viewer",
|
|
2119
|
+
deck: input.deck,
|
|
2120
|
+
slug: input.deck.slug,
|
|
2121
|
+
mountPath: input.mountPath,
|
|
2122
|
+
title: parts.title
|
|
2123
|
+
},
|
|
2124
|
+
input.document,
|
|
2125
|
+
{ head: viewerHead, lang: viewerLang, nonce: viewerNonce }
|
|
2126
|
+
);
|
|
2127
|
+
const nonceAttribute = documentNonceAttribute(document.nonce);
|
|
2128
|
+
const socialHead = renderViewerSocialHead(input.c, parts.meta);
|
|
2129
|
+
return `<!doctype html>
|
|
2130
|
+
<html lang="${escapeHtml7(document.lang)}">
|
|
2131
|
+
<head>
|
|
2132
|
+
<meta charset="utf-8" />
|
|
2133
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
2134
|
+
<title>${escapeHtml7(parts.title)}</title>
|
|
2135
|
+
${socialHead}
|
|
2136
|
+
<style${nonceAttribute}>${baseViewerStyle()}${input.viewer?.style ?? ""}</style>
|
|
2137
|
+
${document.head}
|
|
2138
|
+
</head>
|
|
2139
|
+
<body>
|
|
2140
|
+
${await renderJsxValue(await content)}
|
|
2141
|
+
${renderViewerScript(document.nonce)}
|
|
2142
|
+
</body>
|
|
2143
|
+
</html>`;
|
|
2144
|
+
}
|
|
2145
|
+
async function resolveOpenGraphImagePath(options, input) {
|
|
2146
|
+
if (!options) return void 0;
|
|
2147
|
+
if (options === true) return input.paths.ogImage;
|
|
2148
|
+
if (typeof options.imagePath === "function") return options.imagePath(input);
|
|
2149
|
+
return options.imagePath ?? input.paths.ogImage;
|
|
2150
|
+
}
|
|
2151
|
+
function renderViewerSocialHead(c, meta) {
|
|
2152
|
+
if (!meta.imagePath) return "";
|
|
2153
|
+
const pageUrl = new URL(meta.paths.viewer, c.req.url).href;
|
|
2154
|
+
const imageUrl = new URL(meta.imagePath, c.req.url).href;
|
|
2155
|
+
const description = meta.description ? `
|
|
2156
|
+
<meta name="description" content="${escapeHtml7(meta.description)}" />
|
|
2157
|
+
<meta property="og:description" content="${escapeHtml7(meta.description)}" />
|
|
2158
|
+
<meta name="twitter:description" content="${escapeHtml7(meta.description)}" />` : "";
|
|
2159
|
+
return `<meta property="og:type" content="website" />
|
|
2160
|
+
<meta property="og:title" content="${escapeHtml7(meta.title)}" />${description}
|
|
2161
|
+
<meta property="og:url" content="${escapeHtml7(pageUrl)}" />
|
|
2162
|
+
<meta property="og:image" content="${escapeHtml7(imageUrl)}" />
|
|
2163
|
+
<meta property="og:image:width" content="1200" />
|
|
2164
|
+
<meta property="og:image:height" content="630" />
|
|
2165
|
+
<meta property="og:image:alt" content="${escapeHtml7(meta.title)}" />
|
|
2166
|
+
<meta name="twitter:card" content="summary_large_image" />
|
|
2167
|
+
<meta name="twitter:title" content="${escapeHtml7(meta.title)}" />
|
|
2168
|
+
<meta name="twitter:image" content="${escapeHtml7(imageUrl)}" />`;
|
|
2169
|
+
}
|
|
2170
|
+
function createDeckToc(deck) {
|
|
2171
|
+
return deck.slides.map((slide) => ({
|
|
2172
|
+
index: slide.index,
|
|
2173
|
+
title: slide.meta.title,
|
|
2174
|
+
label: slide.meta.title ?? `Slide ${slide.index + 1}`
|
|
2175
|
+
}));
|
|
2176
|
+
}
|
|
2177
|
+
function renderViewerFrame(input) {
|
|
2178
|
+
return jsx3("div", {
|
|
2179
|
+
class: "hono-decks-viewer-stage",
|
|
2180
|
+
"data-hono-decks-frame": true,
|
|
2181
|
+
children: jsx3("div", {
|
|
2182
|
+
class: "hono-decks-viewport",
|
|
2183
|
+
"data-viewer-viewport": true,
|
|
2184
|
+
tabindex: "0",
|
|
2185
|
+
children: [
|
|
2186
|
+
jsx3("div", {
|
|
2187
|
+
class: "hono-decks-frame-stage",
|
|
2188
|
+
"data-viewer-stage": true,
|
|
2189
|
+
children: jsx3("iframe", {
|
|
2190
|
+
title: input.title,
|
|
2191
|
+
src: input.renderUrl
|
|
2192
|
+
})
|
|
2193
|
+
}),
|
|
2194
|
+
jsx3("button", {
|
|
2195
|
+
class: "hono-decks-viewer-navigation-layer hono-decks-viewer-navigation-previous",
|
|
2196
|
+
type: "button",
|
|
2197
|
+
"data-viewer-navigation": "previous",
|
|
2198
|
+
"aria-label": "Previous slide"
|
|
2199
|
+
}),
|
|
2200
|
+
jsx3("button", {
|
|
2201
|
+
class: "hono-decks-viewer-navigation-layer hono-decks-viewer-navigation-next",
|
|
2202
|
+
type: "button",
|
|
2203
|
+
"data-viewer-navigation": "next",
|
|
2204
|
+
"aria-label": "Next slide"
|
|
2205
|
+
})
|
|
2206
|
+
]
|
|
2207
|
+
})
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
function renderViewerFrameHtml(input) {
|
|
2211
|
+
return `<div class="hono-decks-viewer-stage" data-hono-decks-frame><div class="hono-decks-viewport" data-viewer-viewport tabindex="0"><div class="hono-decks-frame-stage" data-viewer-stage><iframe title="${escapeHtml7(input.title)}" src="${escapeHtml7(input.renderUrl)}"></iframe></div><button class="hono-decks-viewer-navigation-layer hono-decks-viewer-navigation-previous" type="button" data-viewer-navigation="previous" aria-label="Previous slide"></button><button class="hono-decks-viewer-navigation-layer hono-decks-viewer-navigation-next" type="button" data-viewer-navigation="next" aria-label="Next slide"></button></div></div>`;
|
|
2212
|
+
}
|
|
2213
|
+
function renderViewerControls(options, context) {
|
|
2214
|
+
return jsx3("nav", {
|
|
2215
|
+
...controlAttributes(options.attributes),
|
|
2216
|
+
class: controlsClassName(options),
|
|
2217
|
+
"data-hono-decks-viewer-controls": true,
|
|
2218
|
+
"aria-label": options.ariaLabel ?? "Viewer controls",
|
|
2219
|
+
children: resolveViewerControlItems(options, context).map((item) => renderViewerControlItem(item, options, context))
|
|
2220
|
+
});
|
|
2221
|
+
}
|
|
2222
|
+
function renderViewerToc(slides) {
|
|
2223
|
+
return jsx3("nav", {
|
|
2224
|
+
class: "hono-decks-viewer-toc",
|
|
2225
|
+
"data-hono-decks-toc": true,
|
|
2226
|
+
"aria-label": "Slide navigation",
|
|
2227
|
+
children: jsx3("ol", {
|
|
2228
|
+
children: slides.map(
|
|
2229
|
+
(slide) => jsx3("li", {
|
|
2230
|
+
children: jsx3("button", {
|
|
2231
|
+
type: "button",
|
|
2232
|
+
"data-action": "goTo",
|
|
2233
|
+
"data-slide-index": String(slide.index),
|
|
2234
|
+
children: slide.label
|
|
2235
|
+
})
|
|
2236
|
+
})
|
|
2237
|
+
)
|
|
2238
|
+
})
|
|
2239
|
+
});
|
|
2240
|
+
}
|
|
2241
|
+
function renderViewerTocHtml(slides) {
|
|
2242
|
+
const items = slides.map(
|
|
2243
|
+
(slide) => `<li><button type="button" data-action="goTo" data-slide-index="${slide.index}">${escapeHtml7(slide.label)}</button></li>`
|
|
2244
|
+
).join("");
|
|
2245
|
+
return `<nav class="hono-decks-viewer-toc" data-hono-decks-toc aria-label="Slide navigation"><ol>${items}</ol></nav>`;
|
|
2246
|
+
}
|
|
2247
|
+
function escapeHtml7(value) {
|
|
2248
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
2249
|
+
}
|
|
2250
|
+
function safeFilename2(value) {
|
|
2251
|
+
const normalized = value.trim().replaceAll(/[^a-zA-Z0-9._-]+/g, "-").replaceAll(/^-+|-+$/g, "");
|
|
2252
|
+
return normalized || "deck";
|
|
2253
|
+
}
|
|
2254
|
+
function buildViewerControlDefaults(context) {
|
|
2255
|
+
return {
|
|
2256
|
+
back: { type: "default", key: "back" },
|
|
2257
|
+
previous: { type: "default", key: "previous" },
|
|
2258
|
+
position: { type: "default", key: "position" },
|
|
2259
|
+
next: { type: "default", key: "next" },
|
|
2260
|
+
fullscreen: { type: "default", key: "fullscreen" },
|
|
2261
|
+
print: context.meta.availablePages.print ? { type: "default", key: "print" } : null,
|
|
2262
|
+
exportPdf: context.meta.availableExports.pdf ? { type: "default", key: "exportPdf" } : null,
|
|
2263
|
+
exportPng: context.meta.availableExports.png ? { type: "default", key: "exportPng" } : null
|
|
2264
|
+
};
|
|
2265
|
+
}
|
|
2266
|
+
function resolveViewerControlItems(options, context) {
|
|
2267
|
+
const defaults = buildViewerControlDefaults(context);
|
|
2268
|
+
const baseItems = [
|
|
2269
|
+
defaults.back,
|
|
2270
|
+
defaults.previous,
|
|
2271
|
+
defaults.position,
|
|
2272
|
+
defaults.next,
|
|
2273
|
+
defaults.fullscreen,
|
|
2274
|
+
defaults.print,
|
|
2275
|
+
defaults.exportPdf,
|
|
2276
|
+
defaults.exportPng
|
|
2277
|
+
];
|
|
2278
|
+
const items = typeof options.items === "function" ? options.items(defaults, context) : options.items ?? [
|
|
2279
|
+
...resolveViewerControlSlotItems(options.before, context),
|
|
2280
|
+
...filterHiddenDefaultItems(baseItems, options.hidden),
|
|
2281
|
+
...resolveViewerControlSlotItems(options.after, context)
|
|
2282
|
+
];
|
|
2283
|
+
return items.filter((item) => Boolean(item)).map((item) => applyViewerControlLabels(item, options.labels));
|
|
2284
|
+
}
|
|
2285
|
+
function renderViewerControlItem(item, options, context) {
|
|
2286
|
+
const renderDefault = () => renderBaseViewerControlItem(item, options, context);
|
|
2287
|
+
return options.renderItem?.(item, context, renderDefault) ?? renderDefault();
|
|
2288
|
+
}
|
|
2289
|
+
function renderBaseViewerControlItem(item, options, context) {
|
|
2290
|
+
if (item.type === "link") {
|
|
2291
|
+
const ariaLabel = item.icon ? String(item.attributes?.["aria-label"] ?? item.label) : void 0;
|
|
2292
|
+
return jsx3("a", {
|
|
2293
|
+
...linkAttributes(item.attributes),
|
|
2294
|
+
...safeHref(item.href) ? { href: safeHref(item.href) } : {},
|
|
2295
|
+
...item.download ? { download: item.download } : {},
|
|
2296
|
+
...classAttribute(options.itemClassName, item.className),
|
|
2297
|
+
...item.icon ? { "aria-label": ariaLabel, title: ariaLabel } : {},
|
|
2298
|
+
children: item.icon ? renderControlIcon(item.icon) : item.label
|
|
2299
|
+
});
|
|
2300
|
+
}
|
|
2301
|
+
if (item.type === "render") {
|
|
2302
|
+
return item.render({ context });
|
|
2303
|
+
}
|
|
2304
|
+
return renderDefaultViewerControlItem(item, options, context);
|
|
2305
|
+
}
|
|
2306
|
+
function renderDefaultViewerControlItem(item, options, context) {
|
|
2307
|
+
const itemProps = {
|
|
2308
|
+
...linkAttributes(item.attributes),
|
|
2309
|
+
...classAttribute(options.itemClassName, item.className)
|
|
2310
|
+
};
|
|
2311
|
+
switch (item.key) {
|
|
2312
|
+
case "back":
|
|
2313
|
+
return jsx3("a", {
|
|
2314
|
+
...itemProps,
|
|
2315
|
+
href: context.mountPath,
|
|
2316
|
+
"data-hono-decks-back-link": true,
|
|
2317
|
+
...item.label === void 0 ? { "aria-label": controlIconLabel("deck-list"), title: controlIconLabel("deck-list") } : {},
|
|
2318
|
+
children: item.label ?? renderControlIcon("deck-list")
|
|
2319
|
+
});
|
|
2320
|
+
case "previous":
|
|
2321
|
+
return renderIconButton("previous", item, itemProps);
|
|
2322
|
+
case "position":
|
|
2323
|
+
return jsx3("span", {
|
|
2324
|
+
...itemProps,
|
|
2325
|
+
"data-slide-position": true,
|
|
2326
|
+
"data-hono-decks-position": true,
|
|
2327
|
+
children: item.label ?? "1 / ?"
|
|
2328
|
+
});
|
|
2329
|
+
case "next":
|
|
2330
|
+
return renderIconButton("next", item, itemProps);
|
|
2331
|
+
case "fullscreen":
|
|
2332
|
+
return renderIconButton("fullscreen", item, itemProps);
|
|
2333
|
+
case "print":
|
|
2334
|
+
return jsx3("a", {
|
|
2335
|
+
...itemProps,
|
|
2336
|
+
href: context.meta.paths.print,
|
|
2337
|
+
"data-hono-decks-print": true,
|
|
2338
|
+
...item.label === void 0 ? { "aria-label": controlIconLabel("print"), title: controlIconLabel("print") } : {},
|
|
2339
|
+
children: item.label ?? renderControlIcon("print")
|
|
2340
|
+
});
|
|
2341
|
+
case "exportPdf":
|
|
2342
|
+
return jsx3("a", {
|
|
2343
|
+
...itemProps,
|
|
2344
|
+
href: context.meta.paths.exportPdf,
|
|
2345
|
+
download: `${safeFilename2(context.meta.title)}.pdf`,
|
|
2346
|
+
"data-hono-decks-export": "pdf",
|
|
2347
|
+
...item.label === void 0 ? { "aria-label": controlIconLabel("export-pdf"), title: controlIconLabel("export-pdf") } : {},
|
|
2348
|
+
children: item.label ?? renderControlIcon("export-pdf")
|
|
2349
|
+
});
|
|
2350
|
+
case "exportPng":
|
|
2351
|
+
return jsx3("a", {
|
|
2352
|
+
...itemProps,
|
|
2353
|
+
href: context.meta.paths.exportPng,
|
|
2354
|
+
download: `${safeFilename2(context.meta.title)}.png`,
|
|
2355
|
+
"data-hono-decks-export": "png",
|
|
2356
|
+
...item.label === void 0 ? { "aria-label": controlIconLabel("export-png"), title: controlIconLabel("export-png") } : {},
|
|
2357
|
+
children: item.label ?? renderControlIcon("export-png")
|
|
2358
|
+
});
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
function renderIconButton(action, item, itemProps) {
|
|
2362
|
+
const iconName = controlIconNameForKey(action);
|
|
2363
|
+
const label = controlIconLabel(iconName);
|
|
2364
|
+
return jsx3("button", {
|
|
2365
|
+
...itemProps,
|
|
2366
|
+
type: "button",
|
|
2367
|
+
"data-action": action,
|
|
2368
|
+
"data-hono-decks-navigation-control": action,
|
|
2369
|
+
...item.label === void 0 ? { "aria-label": label, title: label } : {},
|
|
2370
|
+
children: item.label ?? renderControlIcon(iconName)
|
|
2371
|
+
});
|
|
2372
|
+
}
|
|
2373
|
+
function controlIconNameForKey(key) {
|
|
2374
|
+
switch (key) {
|
|
2375
|
+
case "back":
|
|
2376
|
+
return "deck-list";
|
|
2377
|
+
case "previous":
|
|
2378
|
+
return "previous";
|
|
2379
|
+
case "next":
|
|
2380
|
+
return "next";
|
|
2381
|
+
case "fullscreen":
|
|
2382
|
+
return "fullscreen";
|
|
2383
|
+
case "print":
|
|
2384
|
+
return "print";
|
|
2385
|
+
case "exportPdf":
|
|
2386
|
+
return "export-pdf";
|
|
2387
|
+
case "exportPng":
|
|
2388
|
+
return "export-png";
|
|
2389
|
+
case "position":
|
|
2390
|
+
return "viewer";
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
function controlsClassName(options) {
|
|
2394
|
+
return ["hono-decks-viewer-controls", options.className].filter(Boolean).join(" ");
|
|
2395
|
+
}
|
|
2396
|
+
function resolveViewerControlSlotItems(items, context) {
|
|
2397
|
+
return typeof items === "function" ? items(context) : items ?? [];
|
|
2398
|
+
}
|
|
2399
|
+
function filterHiddenDefaultItems(items, hidden) {
|
|
2400
|
+
if (!hidden?.length) return items;
|
|
2401
|
+
const hiddenKeys = new Set(hidden);
|
|
2402
|
+
return items.filter((item) => !item || item.type !== "default" || !hiddenKeys.has(item.key));
|
|
2403
|
+
}
|
|
2404
|
+
function applyViewerControlLabels(item, labels) {
|
|
2405
|
+
if (item.type !== "default" || item.label !== void 0) return item;
|
|
2406
|
+
const label = labels?.[item.key];
|
|
2407
|
+
return label === void 0 ? item : { ...item, label };
|
|
2408
|
+
}
|
|
2409
|
+
function linkAttributes(attributes) {
|
|
2410
|
+
const props = {};
|
|
2411
|
+
for (const [name, value] of Object.entries(attributes ?? {})) {
|
|
2412
|
+
if (!isSafeAttributeName(name) || value === false || value === void 0) continue;
|
|
2413
|
+
props[name] = value;
|
|
2414
|
+
}
|
|
2415
|
+
return props;
|
|
2416
|
+
}
|
|
2417
|
+
function controlAttributes(attributes) {
|
|
2418
|
+
const props = linkAttributes(attributes);
|
|
2419
|
+
delete props.class;
|
|
2420
|
+
delete props["aria-label"];
|
|
2421
|
+
delete props["data-hono-decks-viewer-controls"];
|
|
2422
|
+
return props;
|
|
2423
|
+
}
|
|
2424
|
+
function classAttribute(...classNames) {
|
|
2425
|
+
const className = classNames.filter(Boolean).join(" ");
|
|
2426
|
+
return className ? { class: className } : {};
|
|
2427
|
+
}
|
|
2428
|
+
function isSafeAttributeName(value) {
|
|
2429
|
+
return /^[A-Za-z_:][A-Za-z0-9:_.-]*$/.test(value) && !/^on/i.test(value);
|
|
2430
|
+
}
|
|
2431
|
+
function safeHref(value) {
|
|
2432
|
+
const trimmed = value.trim();
|
|
2433
|
+
if (trimmed.length === 0) return void 0;
|
|
2434
|
+
if (/^(https?:|mailto:|tel:)/i.test(trimmed)) return value;
|
|
2435
|
+
if (/^(javascript:|data:|vbscript:)/i.test(trimmed)) return void 0;
|
|
2436
|
+
if (/^[^/?#.]+:/i.test(trimmed)) return void 0;
|
|
2437
|
+
return value;
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
// src/server/external-embed.ts
|
|
2441
|
+
async function renderDeckExternalEmbedResponse(input) {
|
|
2442
|
+
const { c, deck, slug, mountPath } = input.context;
|
|
2443
|
+
const enabled = input.options.enabled;
|
|
2444
|
+
if (enabled === false || typeof enabled === "function" && !await enabled(input.context)) {
|
|
2445
|
+
return c.json({ error: "Embed route not found", slug }, 404);
|
|
2446
|
+
}
|
|
2447
|
+
const document = await resolveDeckDocument(
|
|
2448
|
+
{
|
|
2449
|
+
c,
|
|
2450
|
+
surface: "embed",
|
|
2451
|
+
deck,
|
|
2452
|
+
slug,
|
|
2453
|
+
mountPath,
|
|
2454
|
+
title: deck.meta.title ?? slug
|
|
2455
|
+
},
|
|
2456
|
+
input.document,
|
|
2457
|
+
input.options.document
|
|
2458
|
+
);
|
|
2459
|
+
const viewerOptions = typeof input.options.viewer === "function" ? await input.options.viewer(input.context) : input.options.viewer;
|
|
2460
|
+
const viewer = await createDeckViewerEmbed({
|
|
2461
|
+
deck,
|
|
2462
|
+
mountPath,
|
|
2463
|
+
...viewerOptions,
|
|
2464
|
+
availablePages: input.availablePages,
|
|
2465
|
+
nonce: document.nonce
|
|
2466
|
+
});
|
|
2467
|
+
const renderInput = { ...input.context, viewer, document };
|
|
2468
|
+
const body = input.options.render ? await input.options.render(renderInput) : viewer.embed;
|
|
2469
|
+
const html = await renderExternalEmbedDocument({
|
|
2470
|
+
title: deck.meta.title ?? slug,
|
|
2471
|
+
body: await renderJsxValue(body),
|
|
2472
|
+
document,
|
|
2473
|
+
pageStyle: input.options.pageStyle,
|
|
2474
|
+
robots: input.options.robots
|
|
2475
|
+
});
|
|
2476
|
+
const frameAncestorsValue = typeof input.options.frameAncestors === "function" ? await input.options.frameAncestors(input.context) : input.options.frameAncestors;
|
|
2477
|
+
const response = c.html(html);
|
|
2478
|
+
const headers = new Headers(response.headers);
|
|
2479
|
+
headers.delete("x-frame-options");
|
|
2480
|
+
headers.set(
|
|
2481
|
+
"content-security-policy",
|
|
2482
|
+
withFrameAncestors(headers.get("content-security-policy"), normalizeFrameAncestors(frameAncestorsValue))
|
|
2483
|
+
);
|
|
2484
|
+
return new Response(response.body, {
|
|
2485
|
+
status: response.status,
|
|
2486
|
+
statusText: response.statusText,
|
|
2487
|
+
headers
|
|
2488
|
+
});
|
|
2489
|
+
}
|
|
2490
|
+
async function renderExternalEmbedDocument(input) {
|
|
2491
|
+
const nonceAttribute = documentNonceAttribute(input.document.nonce);
|
|
2492
|
+
const robots = input.robots === false ? "" : `<meta name="robots" content="${escapeHtml8(input.robots ?? "noindex")}"/>`;
|
|
2493
|
+
return `<!doctype html>
|
|
2494
|
+
<html lang="${escapeHtml8(input.document.lang)}" data-hono-decks-external-embed-document>
|
|
2495
|
+
<head>
|
|
2496
|
+
<meta charset="utf-8" />
|
|
2497
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
2498
|
+
${robots}
|
|
2499
|
+
<title>${escapeHtml8(input.title)}</title>
|
|
2500
|
+
<style id="hono-decks-external-embed-css"${nonceAttribute}>${externalEmbedPageStyle()}${input.pageStyle ?? ""}</style>
|
|
2501
|
+
${input.document.head}
|
|
2502
|
+
</head>
|
|
2503
|
+
<body>${input.body}</body>
|
|
2504
|
+
</html>`;
|
|
2505
|
+
}
|
|
2506
|
+
function normalizeFrameAncestors(value) {
|
|
2507
|
+
const values = Array.isArray(value) ? value : value?.split(/[\s,]+/) ?? [];
|
|
2508
|
+
const ancestors = /* @__PURE__ */ new Set(["'self'"]);
|
|
2509
|
+
for (const candidate of values) {
|
|
2510
|
+
const token = candidate.trim();
|
|
2511
|
+
if (!token || token === "'self'") continue;
|
|
2512
|
+
if (token === "*") return ["*"];
|
|
2513
|
+
if (token === "'none'") return ["'none'"];
|
|
2514
|
+
try {
|
|
2515
|
+
const url = new URL(token);
|
|
2516
|
+
if ((url.protocol === "https:" || url.protocol === "http:") && !url.username && !url.password) {
|
|
2517
|
+
ancestors.add(url.origin);
|
|
2518
|
+
}
|
|
2519
|
+
} catch {
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
return [...ancestors];
|
|
2523
|
+
}
|
|
2524
|
+
function withFrameAncestors(currentPolicy, ancestors) {
|
|
2525
|
+
const directives = (currentPolicy ?? "").split(";").map((directive) => directive.trim()).filter((directive) => directive && !/^frame-ancestors(?:\s|$)/i.test(directive));
|
|
2526
|
+
directives.push(`frame-ancestors ${ancestors.join(" ")}`);
|
|
2527
|
+
return directives.join("; ");
|
|
2528
|
+
}
|
|
2529
|
+
function externalEmbedPageStyle() {
|
|
2530
|
+
return `:root{color-scheme:dark;background:#050816}html,body{width:100%;height:100%;margin:0;overflow:hidden}body{min-height:100vh}@supports (height:100dvh){body{min-height:100dvh}}.hono-decks-embedded-viewer{width:100%;height:100%;min-height:0}.hono-decks-embedded-viewer .hono-decks-viewer-shell{position:relative;height:100%;grid-template-rows:minmax(0,1fr)}.hono-decks-embedded-viewer .hono-decks-viewport{width:min(100%,calc(100vh * 16 / 9));max-height:100%}@supports (height:100dvh){.hono-decks-embedded-viewer .hono-decks-viewport{width:min(100%,calc(100dvh * 16 / 9))}}.hono-decks-embedded-viewer .hono-decks-viewer-controls{position:absolute;right:max(10px,env(safe-area-inset-right,0px));bottom:max(10px,env(safe-area-inset-bottom,0px));z-index:4}`;
|
|
2531
|
+
}
|
|
2532
|
+
function escapeHtml8(value) {
|
|
2533
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
2534
|
+
}
|
|
2535
|
+
|
|
2536
|
+
// src/server/router.ts
|
|
2537
|
+
function decksRouter(options) {
|
|
2538
|
+
const router = new Hono();
|
|
2539
|
+
if (options.clientEntryAsset) {
|
|
2540
|
+
router.get(normalizeClientEntryAssetPath(options.clientEntryAssetPath), serveDecksClientEntry(options.clientEntryAsset));
|
|
2541
|
+
}
|
|
2542
|
+
for (const extension of options.extensions ?? []) {
|
|
2543
|
+
router.route(extension.path, extension.router);
|
|
2544
|
+
}
|
|
2545
|
+
router.get("/", async (c) => {
|
|
2546
|
+
const dev = await isDevEnabled(c, options);
|
|
2547
|
+
const decks = (await options.source.listDecks(c)).filter((deck) => dev || !deck.draft);
|
|
2548
|
+
const mountPath = c.req.path.replace(/\/$/, "");
|
|
2549
|
+
const pageInput = { c, surface: "index", mountPath, dev, decks };
|
|
2550
|
+
if (!await isPageSurfaceEnabled(options, pageInput)) {
|
|
2551
|
+
return c.json({ error: "Deck index not found" }, 404);
|
|
2552
|
+
}
|
|
2553
|
+
const indexOptions = options.pages?.index === false ? void 0 : options.pages?.index;
|
|
2554
|
+
const title = await resolveIndexTitle(indexOptions?.title, pageInput);
|
|
2555
|
+
const document = await resolveRouterDocument(c, options, "index", mountPath, void 0, title);
|
|
2556
|
+
const defaultContentHtml = renderDeckIndexContent(decks, mountPath);
|
|
2557
|
+
const defaultContent = raw2(defaultContentHtml);
|
|
2558
|
+
const content = indexOptions?.render ? await renderJsxValue(indexOptions.render({ ...pageInput, title, document, defaultContent })) : defaultContentHtml;
|
|
2559
|
+
return c.html(renderDeckIndex(content, title, document));
|
|
2560
|
+
});
|
|
2561
|
+
router.get("/:slug/assets/*", async (c) => {
|
|
2562
|
+
const slug = c.req.param("slug");
|
|
2563
|
+
const assetPath = extractAssetPath(c.req.path, slug);
|
|
2564
|
+
const deck = await options.source.getCompiledDeck(c, slug);
|
|
2565
|
+
if (!deck || !await isDevEnabled(c, options) && deck.meta.draft) {
|
|
2566
|
+
return c.json({ error: "Asset not found", slug, assetPath }, 404);
|
|
2567
|
+
}
|
|
2568
|
+
const response = await options.source.getAsset?.(c, slug, assetPath);
|
|
2569
|
+
if (!response) return c.json({ error: "Asset not found", slug, assetPath }, 404);
|
|
2570
|
+
return response;
|
|
2571
|
+
});
|
|
2572
|
+
router.get("/:slug/render", async (c) => {
|
|
2573
|
+
const slug = c.req.param("slug");
|
|
2574
|
+
const deck = await options.source.getCompiledDeck(c, slug);
|
|
2575
|
+
const dev = await isDevEnabled(c, options);
|
|
2576
|
+
if (!deck || !dev && deck.meta.draft) return c.json({ error: "Deck not found", slug }, 404);
|
|
2577
|
+
const mountPath = stripPathSuffix(c.req.path, `/${slug}/render`);
|
|
2578
|
+
if (!await isPageSurfaceEnabled(options, { c, surface: "render", mountPath, dev, deck, slug })) {
|
|
2579
|
+
return c.json({ error: "Deck route not found", slug, surface: "render" }, 404);
|
|
2580
|
+
}
|
|
2581
|
+
const clientEntry = options.clientEntry ?? resolveGeneratedClientEntryUrl(options, mountPath);
|
|
2582
|
+
try {
|
|
2583
|
+
const document = await resolveRouterDocument(c, options, "render", mountPath, deck);
|
|
2584
|
+
return c.html(
|
|
2585
|
+
await renderCompiledDeckPageAsync({
|
|
2586
|
+
deck,
|
|
2587
|
+
mountPath,
|
|
2588
|
+
style: options.style,
|
|
2589
|
+
components: options.components,
|
|
2590
|
+
clientEntry,
|
|
2591
|
+
liveReloadPath: dev ? options.liveReloadPath?.(slug, mountPath) : void 0,
|
|
2592
|
+
document
|
|
2593
|
+
})
|
|
2594
|
+
);
|
|
2595
|
+
} catch (error) {
|
|
2596
|
+
const message = error instanceof RenderError ? error.message : `Render failed in ${deck.sourcePath}: ${formatErrorMessage2(error)}`;
|
|
2597
|
+
return c.text(message, 500);
|
|
2598
|
+
}
|
|
2599
|
+
});
|
|
2600
|
+
router.get("/:slug/print", async (c) => {
|
|
2601
|
+
const slug = c.req.param("slug");
|
|
2602
|
+
const deck = await options.source.getCompiledDeck(c, slug);
|
|
2603
|
+
const dev = await isDevEnabled(c, options);
|
|
2604
|
+
if (!deck || !dev && deck.meta.draft) return c.json({ error: "Deck not found", slug }, 404);
|
|
2605
|
+
const mountPath = stripPathSuffix(c.req.path, `/${slug}/print`);
|
|
2606
|
+
if (!await isPageSurfaceEnabled(options, { c, surface: "print", mountPath, dev, deck, slug })) {
|
|
2607
|
+
return c.json({ error: "Deck route not found", slug, surface: "print" }, 404);
|
|
2608
|
+
}
|
|
2609
|
+
try {
|
|
2610
|
+
const document = await resolveRouterDocument(c, options, "print", mountPath, deck);
|
|
2611
|
+
return c.html(
|
|
2612
|
+
await renderCompiledDeckPageAsync({
|
|
2613
|
+
deck,
|
|
2614
|
+
mountPath,
|
|
2615
|
+
style: options.style,
|
|
2616
|
+
components: options.components,
|
|
2617
|
+
printPreview: true,
|
|
2618
|
+
document
|
|
2619
|
+
})
|
|
2620
|
+
);
|
|
2621
|
+
} catch (error) {
|
|
2622
|
+
const message = error instanceof RenderError ? error.message : `Render failed in ${deck.sourcePath}: ${formatErrorMessage2(error)}`;
|
|
2623
|
+
return c.text(message, 500);
|
|
2624
|
+
}
|
|
2625
|
+
});
|
|
2626
|
+
if (isPdfExportEnabled(options.export)) {
|
|
2627
|
+
router.get(
|
|
2628
|
+
"/:slug/export.pdf",
|
|
2629
|
+
async (c) => renderDeckBrowserExport(c, { ...options, dev: await isDevEnabled(c, options) }, "pdf")
|
|
2630
|
+
);
|
|
2631
|
+
}
|
|
2632
|
+
if (isPngExportEnabled(options.export)) {
|
|
2633
|
+
router.get(
|
|
2634
|
+
"/:slug/export.png",
|
|
2635
|
+
async (c) => renderDeckBrowserExport(c, { ...options, dev: await isDevEnabled(c, options) }, "png")
|
|
2636
|
+
);
|
|
2637
|
+
}
|
|
2638
|
+
const embedOptions = options.embed;
|
|
2639
|
+
if (embedOptions) {
|
|
2640
|
+
router.get("/:slug/embed", async (c) => {
|
|
2641
|
+
const slug = c.req.param("slug");
|
|
2642
|
+
const deck = await options.source.getCompiledDeck(c, slug);
|
|
2643
|
+
const dev = await isDevEnabled(c, options);
|
|
2644
|
+
if (!deck || !dev && deck.meta.draft) {
|
|
2645
|
+
return c.json({ error: "Deck not found", slug }, 404);
|
|
2646
|
+
}
|
|
2647
|
+
const mountPath = stripPathSuffix(c.req.path, `/${slug}/embed`);
|
|
2648
|
+
const availablePages = await resolveViewerAvailablePages(c, options, deck, slug, mountPath, dev);
|
|
2649
|
+
return renderDeckExternalEmbedResponse({
|
|
2650
|
+
context: { c, deck, slug, mountPath },
|
|
2651
|
+
options: embedOptions,
|
|
2652
|
+
document: options.document,
|
|
2653
|
+
availablePages
|
|
2654
|
+
});
|
|
2655
|
+
});
|
|
2656
|
+
}
|
|
2657
|
+
router.get("/:slug/presentation", async (c) => {
|
|
2658
|
+
const slug = c.req.param("slug");
|
|
2659
|
+
const deck = await options.source.getCompiledDeck(c, slug);
|
|
2660
|
+
const dev = await isDevEnabled(c, options);
|
|
2661
|
+
if (!deck || !dev && deck.meta.draft) return c.json({ error: "Deck not found", slug }, 404);
|
|
2662
|
+
const mountPath = stripPathSuffix(c.req.path, `/${slug}/presentation`);
|
|
2663
|
+
if (!await isPageSurfaceEnabled(options, { c, surface: "presentation", mountPath, dev, deck, slug })) {
|
|
2664
|
+
return c.json({ error: "Deck route not found", slug, surface: "presentation" }, 404);
|
|
2665
|
+
}
|
|
2666
|
+
const clientEntry = options.clientEntry ?? resolveGeneratedClientEntryUrl(options, mountPath);
|
|
2667
|
+
try {
|
|
2668
|
+
const document = await resolveRouterDocument(c, options, "presentation", mountPath, deck);
|
|
2669
|
+
return c.html(
|
|
2670
|
+
await renderCompiledDeckPageAsync({
|
|
2671
|
+
deck,
|
|
2672
|
+
mountPath,
|
|
2673
|
+
style: options.style,
|
|
2674
|
+
components: options.components,
|
|
2675
|
+
clientEntry,
|
|
2676
|
+
speakerNotes: false,
|
|
2677
|
+
liveReloadPath: dev ? options.liveReloadPath?.(slug, mountPath) : void 0,
|
|
2678
|
+
document
|
|
2679
|
+
})
|
|
2680
|
+
);
|
|
2681
|
+
} catch (error) {
|
|
2682
|
+
const message = error instanceof RenderError ? error.message : `Render failed in ${deck.sourcePath}: ${formatErrorMessage2(error)}`;
|
|
2683
|
+
return c.text(message, 500);
|
|
2684
|
+
}
|
|
2685
|
+
});
|
|
2686
|
+
router.get("/:slug/presenter", async (c) => {
|
|
2687
|
+
const slug = c.req.param("slug");
|
|
2688
|
+
const deck = await options.source.getCompiledDeck(c, slug);
|
|
2689
|
+
const dev = await isDevEnabled(c, options);
|
|
2690
|
+
if (!deck || !dev && deck.meta.draft) return c.json({ error: "Deck not found", slug }, 404);
|
|
2691
|
+
const mountPath = stripPathSuffix(c.req.path, `/${slug}/presenter`);
|
|
2692
|
+
if (!await isPageSurfaceEnabled(options, { c, surface: "presenter", mountPath, dev, deck, slug })) {
|
|
2693
|
+
return c.json({ error: "Presenter route not found", slug }, 404);
|
|
2694
|
+
}
|
|
2695
|
+
if (!await isPresenterEnabled(c, options, deck, slug, mountPath)) {
|
|
2696
|
+
return c.json({ error: "Presenter route not found", slug }, 404);
|
|
2697
|
+
}
|
|
2698
|
+
try {
|
|
2699
|
+
const document = await resolveRouterDocument(c, options, "presenter", mountPath, deck);
|
|
2700
|
+
return c.html(
|
|
2701
|
+
await renderPresenterPageAsync({
|
|
2702
|
+
deck,
|
|
2703
|
+
mountPath,
|
|
2704
|
+
presenterStateQuery: paginationQueryFromRequest(c),
|
|
2705
|
+
style: options.style,
|
|
2706
|
+
components: options.components,
|
|
2707
|
+
document
|
|
2708
|
+
})
|
|
2709
|
+
);
|
|
2710
|
+
} catch (error) {
|
|
2711
|
+
const message = error instanceof RenderError ? error.message : `Render failed in ${deck.sourcePath}: ${formatErrorMessage2(error)}`;
|
|
2712
|
+
return c.text(message, 500);
|
|
2713
|
+
}
|
|
2714
|
+
});
|
|
2715
|
+
router.get("/:slug", async (c) => {
|
|
2716
|
+
const slug = c.req.param("slug");
|
|
2717
|
+
const deck = await options.source.getCompiledDeck(c, slug);
|
|
2718
|
+
const dev = await isDevEnabled(c, options);
|
|
2719
|
+
if (!deck || !dev && deck.meta.draft) return c.json({ error: "Deck not found", slug }, 404);
|
|
2720
|
+
const mountPath = stripPathSuffix(c.req.path, `/${slug}`);
|
|
2721
|
+
if (!await isPageSurfaceEnabled(options, { c, surface: "viewer", mountPath, dev, deck, slug })) {
|
|
2722
|
+
return c.json({ error: "Deck route not found", slug, surface: "viewer" }, 404);
|
|
2723
|
+
}
|
|
2724
|
+
const availablePages = await resolveViewerAvailablePages(c, options, deck, slug, mountPath, dev);
|
|
2725
|
+
return c.html(
|
|
2726
|
+
await renderDeckViewerPage({
|
|
2727
|
+
c,
|
|
2728
|
+
deck,
|
|
2729
|
+
mountPath,
|
|
2730
|
+
viewerStateQuery: paginationQueryFromRequest(c),
|
|
2731
|
+
viewer: {
|
|
2732
|
+
...options.viewer,
|
|
2733
|
+
controls: await resolveViewerControls(c, options, deck, slug, mountPath)
|
|
2734
|
+
},
|
|
2735
|
+
availablePages,
|
|
2736
|
+
exportOptions: options.export,
|
|
2737
|
+
document: options.document
|
|
2738
|
+
})
|
|
2739
|
+
);
|
|
2740
|
+
});
|
|
2741
|
+
return router;
|
|
2742
|
+
}
|
|
2743
|
+
function paginationQueryFromRequest(c) {
|
|
2744
|
+
const source = new URL(c.req.url);
|
|
2745
|
+
const params = new URLSearchParams();
|
|
2746
|
+
const slide = positiveIntegerParam(source.searchParams.get("slide"));
|
|
2747
|
+
const step = nonNegativeIntegerParam(source.searchParams.get("step"));
|
|
2748
|
+
if (slide !== void 0) params.set("slide", String(slide));
|
|
2749
|
+
if (step !== void 0) params.set("step", String(step));
|
|
2750
|
+
const query = params.toString();
|
|
2751
|
+
return query ? `?${query}` : "";
|
|
2752
|
+
}
|
|
2753
|
+
function positiveIntegerParam(value) {
|
|
2754
|
+
if (value === null) return void 0;
|
|
2755
|
+
const parsed = Number(value);
|
|
2756
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
|
|
2757
|
+
}
|
|
2758
|
+
function nonNegativeIntegerParam(value) {
|
|
2759
|
+
if (value === null) return void 0;
|
|
2760
|
+
const parsed = Number(value);
|
|
2761
|
+
return Number.isInteger(parsed) && parsed >= 0 ? parsed : void 0;
|
|
2762
|
+
}
|
|
2763
|
+
function deckContext(options) {
|
|
2764
|
+
return async (c, next) => {
|
|
2765
|
+
const slug = c.req.param("slug");
|
|
2766
|
+
if (!slug) return c.json({ error: "Deck not found", slug: "" }, 404);
|
|
2767
|
+
const sourceContext = c;
|
|
2768
|
+
const deck = await options.source.getCompiledDeck(sourceContext, slug);
|
|
2769
|
+
const dev = await isDevEnabled(sourceContext, options);
|
|
2770
|
+
if (!deck || !dev && deck.meta.draft) return c.json({ error: "Deck not found", slug }, 404);
|
|
2771
|
+
const mountPath = options.mountPath ?? inferMountPath(c.req.path, slug);
|
|
2772
|
+
const availablePages = await resolveViewerAvailablePages(sourceContext, options, deck, slug, mountPath, dev);
|
|
2773
|
+
const viewer = await createDeckViewerParts({
|
|
2774
|
+
deck,
|
|
2775
|
+
mountPath,
|
|
2776
|
+
controls: options.viewer?.controls,
|
|
2777
|
+
openGraph: options.viewer?.openGraph,
|
|
2778
|
+
availablePages
|
|
2779
|
+
});
|
|
2780
|
+
c.set("deck", deck);
|
|
2781
|
+
c.set("deckViewer", viewer);
|
|
2782
|
+
c.set("deckToc", viewer.slides);
|
|
2783
|
+
c.set("deckMeta", viewer.meta);
|
|
2784
|
+
await next();
|
|
2785
|
+
};
|
|
2786
|
+
}
|
|
2787
|
+
async function resolveViewerAvailablePages(c, options, deck, slug, mountPath, dev) {
|
|
2788
|
+
return {
|
|
2789
|
+
print: await isPageSurfaceEnabled(options, { c, surface: "print", mountPath, dev, deck, slug })
|
|
2790
|
+
};
|
|
2791
|
+
}
|
|
2792
|
+
async function isDevEnabled(c, options) {
|
|
2793
|
+
if (typeof options.dev === "function") return Boolean(await options.dev({ c }));
|
|
2794
|
+
if (typeof options.dev === "boolean") return options.dev;
|
|
2795
|
+
return inferToolchainDevMode();
|
|
2796
|
+
}
|
|
2797
|
+
function inferToolchainDevMode() {
|
|
2798
|
+
try {
|
|
2799
|
+
return process.env.NODE_ENV === "development";
|
|
2800
|
+
} catch {
|
|
2801
|
+
return false;
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
async function resolveViewerControls(c, options, deck, slug, mountPath) {
|
|
2805
|
+
const controls = options.viewer?.controls;
|
|
2806
|
+
if (controls === false) return false;
|
|
2807
|
+
const presenterControl = await resolvePresenterViewerControl(c, options, deck, slug, mountPath);
|
|
2808
|
+
if (!presenterControl) return controls;
|
|
2809
|
+
const nextControls = controls ? { ...controls } : {};
|
|
2810
|
+
if (nextControls.items) return nextControls;
|
|
2811
|
+
const placement = presenterControl.placement ?? "after";
|
|
2812
|
+
const item = presenterControl.item;
|
|
2813
|
+
if (placement === "before") {
|
|
2814
|
+
nextControls.before = mergeControlSlotItems(nextControls.before, item, "before");
|
|
2815
|
+
} else {
|
|
2816
|
+
nextControls.after = mergeControlSlotItems(nextControls.after, item, "after");
|
|
2817
|
+
}
|
|
2818
|
+
return nextControls;
|
|
2819
|
+
}
|
|
2820
|
+
async function resolvePresenterViewerControl(c, options, deck, slug, mountPath) {
|
|
2821
|
+
const presenter = options.presenter;
|
|
2822
|
+
if (!presenter || !presenter.viewerControl) return null;
|
|
2823
|
+
if (!await isPresenterEnabled(c, options, deck, slug, mountPath)) return null;
|
|
2824
|
+
const control = presenter.viewerControl === true ? {} : presenter.viewerControl;
|
|
2825
|
+
const presenterPath = createDeckPaths(mountPath, slug).presenter;
|
|
2826
|
+
return {
|
|
2827
|
+
item: {
|
|
2828
|
+
type: "link",
|
|
2829
|
+
key: control.key ?? "presenter",
|
|
2830
|
+
href: presenterPath,
|
|
2831
|
+
label: control.label ?? "Presenter",
|
|
2832
|
+
icon: control.icon ?? "presenter",
|
|
2833
|
+
className: control.className,
|
|
2834
|
+
attributes: control.attributes
|
|
2835
|
+
},
|
|
2836
|
+
placement: control.placement ?? "after"
|
|
2837
|
+
};
|
|
2838
|
+
}
|
|
2839
|
+
function mergeControlSlotItems(slot, item, placement) {
|
|
2840
|
+
if (!slot) return [item];
|
|
2841
|
+
if (Array.isArray(slot)) return placement === "before" ? [item, ...slot] : [...slot, item];
|
|
2842
|
+
return (context) => {
|
|
2843
|
+
const items = slot(context);
|
|
2844
|
+
return placement === "before" ? [item, ...items] : [...items, item];
|
|
2845
|
+
};
|
|
2846
|
+
}
|
|
2847
|
+
async function isPresenterEnabled(c, options, deck, slug, mountPath) {
|
|
2848
|
+
const presenter = options.presenter;
|
|
2849
|
+
if (presenter === false) return false;
|
|
2850
|
+
const enabled = presenter?.enabled;
|
|
2851
|
+
if (enabled === void 0) return true;
|
|
2852
|
+
if (typeof enabled === "boolean") return enabled;
|
|
2853
|
+
return Boolean(
|
|
2854
|
+
await enabled({
|
|
2855
|
+
c,
|
|
2856
|
+
deck,
|
|
2857
|
+
slug,
|
|
2858
|
+
mountPath: mountPath.replace(/\/$/, ""),
|
|
2859
|
+
dev: await isDevEnabled(c, options),
|
|
2860
|
+
presenterPath: createDeckPaths(mountPath, slug).presenter,
|
|
2861
|
+
presentationPath: createDeckPaths(mountPath, slug).presentation
|
|
2862
|
+
})
|
|
2863
|
+
);
|
|
2864
|
+
}
|
|
2865
|
+
function renderDeckIndexContent(decks, mountPath) {
|
|
2866
|
+
const basePath = mountPath.replace(/\/$/, "");
|
|
2867
|
+
const items = decks.map((deck) => {
|
|
2868
|
+
const href = `${basePath}/${encodeURIComponent(deck.slug)}`;
|
|
2869
|
+
const title = escapeHtml9(deck.title ?? deck.slug);
|
|
2870
|
+
const description = deck.description ? `<p>${escapeHtml9(deck.description)}</p>` : "";
|
|
2871
|
+
return `<li><a href="${href}">${title}</a>${description}</li>`;
|
|
2872
|
+
}).join("");
|
|
2873
|
+
return `<main>
|
|
2874
|
+
<h1>Hono Decks</h1>
|
|
2875
|
+
<ul>${items}</ul>
|
|
2876
|
+
</main>`;
|
|
2877
|
+
}
|
|
2878
|
+
function renderDeckIndex(content, title, document) {
|
|
2879
|
+
return `<!doctype html>
|
|
2880
|
+
<html lang="${escapeHtml9(document.lang)}">
|
|
2881
|
+
<head>
|
|
2882
|
+
<meta charset="utf-8" />
|
|
2883
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
2884
|
+
<title>${escapeHtml9(title)}</title>
|
|
2885
|
+
${document.head}
|
|
2886
|
+
</head>
|
|
2887
|
+
<body>
|
|
2888
|
+
${content}
|
|
2889
|
+
</body>
|
|
2890
|
+
</html>`;
|
|
2891
|
+
}
|
|
2892
|
+
async function resolveIndexTitle(title, input) {
|
|
2893
|
+
const resolved = typeof title === "function" ? await title(input) : title;
|
|
2894
|
+
return resolved?.trim() || "Hono Decks";
|
|
2895
|
+
}
|
|
2896
|
+
async function isPageSurfaceEnabled(options, input) {
|
|
2897
|
+
const configured = input.surface === "index" ? options.pages?.index === false ? false : options.pages?.index?.enabled : options.pages?.[input.surface];
|
|
2898
|
+
if (typeof configured === "function") return Boolean(await configured(input));
|
|
2899
|
+
return configured !== false;
|
|
2900
|
+
}
|
|
2901
|
+
async function resolveRouterDocument(c, options, surface, mountPath, deck, titleOverride) {
|
|
2902
|
+
const title = titleOverride ?? deck?.meta.title ?? deck?.slug ?? "Hono Decks";
|
|
2903
|
+
return resolveDeckDocument(
|
|
2904
|
+
{
|
|
2905
|
+
c,
|
|
2906
|
+
surface,
|
|
2907
|
+
deck,
|
|
2908
|
+
slug: deck?.slug,
|
|
2909
|
+
mountPath,
|
|
2910
|
+
title: surface === "presenter" ? `${title} - Presenter` : title
|
|
2911
|
+
},
|
|
2912
|
+
options.document
|
|
2913
|
+
);
|
|
2914
|
+
}
|
|
2915
|
+
function escapeHtml9(value) {
|
|
2916
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
2917
|
+
}
|
|
2918
|
+
function formatErrorMessage2(error) {
|
|
2919
|
+
if (error instanceof Error) return error.message;
|
|
2920
|
+
return String(error);
|
|
2921
|
+
}
|
|
2922
|
+
|
|
2923
|
+
// src/server/define-decks.ts
|
|
2924
|
+
function defineDecks(options) {
|
|
2925
|
+
const manifest = Array.isArray(options.decks) ? { decks: options.decks } : options.manifest;
|
|
2926
|
+
if (!manifest) throw new Error("defineDecks requires either decks or manifest.");
|
|
2927
|
+
const source = manifestDeckSource(manifest);
|
|
2928
|
+
const baseOptions = {
|
|
2929
|
+
...options,
|
|
2930
|
+
source
|
|
2931
|
+
};
|
|
2932
|
+
return {
|
|
2933
|
+
source,
|
|
2934
|
+
router(overrides = {}) {
|
|
2935
|
+
const nextSource = overrides.source ?? source;
|
|
2936
|
+
return decksRouter(mergeDecksRouterOptions(baseOptions, { ...overrides, source: nextSource }));
|
|
2937
|
+
}
|
|
2938
|
+
};
|
|
2939
|
+
}
|
|
2940
|
+
function defineDecksConfig(config) {
|
|
2941
|
+
return config;
|
|
2942
|
+
}
|
|
2943
|
+
function configureDecks(defined, config) {
|
|
2944
|
+
const mountPath = normalizeMountPath(config.mountPath);
|
|
2945
|
+
const source = config.source?.(defined.source) ?? defined.source;
|
|
2946
|
+
const configuredRouter = mergeDecksRouterOptions(
|
|
2947
|
+
{ source, ...config.router },
|
|
2948
|
+
{ source }
|
|
2949
|
+
);
|
|
2950
|
+
return {
|
|
2951
|
+
mountPath,
|
|
2952
|
+
source,
|
|
2953
|
+
router(overrides = {}) {
|
|
2954
|
+
return defined.router(mergeDecksRouterOptions(configuredRouter, overrides));
|
|
2955
|
+
},
|
|
2956
|
+
context(overrides = {}) {
|
|
2957
|
+
const merged = mergeDecksRouterOptions(configuredRouter, overrides);
|
|
2958
|
+
return deckContext({
|
|
2959
|
+
source,
|
|
2960
|
+
mountPath,
|
|
2961
|
+
dev: merged.dev,
|
|
2962
|
+
pages: merged.pages,
|
|
2963
|
+
viewer: merged.viewer
|
|
2964
|
+
});
|
|
2965
|
+
},
|
|
2966
|
+
paths(slug) {
|
|
2967
|
+
return createDeckPaths(mountPath, slug);
|
|
2968
|
+
}
|
|
2969
|
+
};
|
|
2970
|
+
}
|
|
2971
|
+
function mergeDecksRouterOptions(base, overrides) {
|
|
2972
|
+
const viewer = mergeViewerOptions(base.viewer, overrides.viewer);
|
|
2973
|
+
const presenter = mergePresenterOptions(base.presenter, overrides.presenter);
|
|
2974
|
+
const document = mergeDocumentOptions(base.document, overrides.document);
|
|
2975
|
+
const pages = mergePagesOptions(base.pages, overrides.pages);
|
|
2976
|
+
const embed = mergeExternalEmbedOptions(base.embed, overrides.embed);
|
|
2977
|
+
const exportOptions = overrides.export === false ? false : base.export === false ? overrides.export : base.export || overrides.export ? { ...base.export, ...overrides.export } : void 0;
|
|
2978
|
+
const components = base.components || overrides.components ? { ...base.components, ...overrides.components } : void 0;
|
|
2979
|
+
return {
|
|
2980
|
+
...base,
|
|
2981
|
+
...overrides,
|
|
2982
|
+
...viewer === void 0 ? {} : { viewer },
|
|
2983
|
+
...presenter === void 0 ? {} : { presenter },
|
|
2984
|
+
...document === void 0 ? {} : { document },
|
|
2985
|
+
...pages === void 0 ? {} : { pages },
|
|
2986
|
+
...embed === void 0 ? {} : { embed },
|
|
2987
|
+
...exportOptions === void 0 ? {} : { export: exportOptions },
|
|
2988
|
+
...components === void 0 ? {} : { components },
|
|
2989
|
+
source: overrides.source ?? base.source
|
|
2990
|
+
};
|
|
2991
|
+
}
|
|
2992
|
+
function mergePagesOptions(base, override) {
|
|
2993
|
+
if (!base) return override;
|
|
2994
|
+
if (!override) return base;
|
|
2995
|
+
const index = override.index === false ? false : override.index === void 0 ? base.index : base.index === false || base.index === void 0 ? override.index : { ...base.index, ...override.index };
|
|
2996
|
+
return {
|
|
2997
|
+
...base,
|
|
2998
|
+
...override,
|
|
2999
|
+
...index === void 0 ? {} : { index }
|
|
3000
|
+
};
|
|
3001
|
+
}
|
|
3002
|
+
function mergeExternalEmbedOptions(base, override) {
|
|
3003
|
+
if (override === false) return false;
|
|
3004
|
+
if (override === void 0) return base;
|
|
3005
|
+
if (base === false || base === void 0) return override;
|
|
3006
|
+
const viewer = typeof base.viewer === "object" && typeof override.viewer === "object" ? { ...base.viewer, ...override.viewer } : override.viewer ?? base.viewer;
|
|
3007
|
+
const document = base.document || override.document ? { ...base.document, ...override.document } : void 0;
|
|
3008
|
+
return {
|
|
3009
|
+
...base,
|
|
3010
|
+
...override,
|
|
3011
|
+
...viewer === void 0 ? {} : { viewer },
|
|
3012
|
+
...document === void 0 ? {} : { document }
|
|
3013
|
+
};
|
|
3014
|
+
}
|
|
3015
|
+
function mergeDocumentOptions(base, override) {
|
|
3016
|
+
if (!base) return override;
|
|
3017
|
+
if (!override) return base;
|
|
3018
|
+
return {
|
|
3019
|
+
...base,
|
|
3020
|
+
...override,
|
|
3021
|
+
surfaces: base.surfaces || override.surfaces ? { ...base.surfaces, ...override.surfaces } : void 0
|
|
3022
|
+
};
|
|
3023
|
+
}
|
|
3024
|
+
function mergeViewerOptions(base, override) {
|
|
3025
|
+
if (!base) return override;
|
|
3026
|
+
if (!override) return base;
|
|
3027
|
+
const controls = mergeControls(base.controls, override.controls);
|
|
3028
|
+
const openGraph = mergeOpenGraph(base.openGraph, override.openGraph);
|
|
3029
|
+
return {
|
|
3030
|
+
...base,
|
|
3031
|
+
...override,
|
|
3032
|
+
...controls === void 0 ? {} : { controls },
|
|
3033
|
+
...openGraph === void 0 ? {} : { openGraph }
|
|
3034
|
+
};
|
|
3035
|
+
}
|
|
3036
|
+
function mergeOpenGraph(base, override) {
|
|
3037
|
+
if (override === void 0) return base;
|
|
3038
|
+
if (base === void 0 || typeof base === "boolean" || typeof override === "boolean") return override;
|
|
3039
|
+
return { ...base, ...override };
|
|
3040
|
+
}
|
|
3041
|
+
function mergeControls(base, override) {
|
|
3042
|
+
if (override === void 0) return base;
|
|
3043
|
+
if (base === void 0 || base === false || override === false) return override;
|
|
3044
|
+
return {
|
|
3045
|
+
...base,
|
|
3046
|
+
...override,
|
|
3047
|
+
attributes: base.attributes || override.attributes ? { ...base.attributes, ...override.attributes } : void 0,
|
|
3048
|
+
labels: base.labels || override.labels ? { ...base.labels, ...override.labels } : void 0,
|
|
3049
|
+
hidden: base.hidden || override.hidden ? [.../* @__PURE__ */ new Set([...base.hidden ?? [], ...override.hidden ?? []])] : void 0,
|
|
3050
|
+
before: mergeControlSlots(base.before, override.before),
|
|
3051
|
+
after: mergeControlSlots(base.after, override.after)
|
|
3052
|
+
};
|
|
3053
|
+
}
|
|
3054
|
+
function mergeControlSlots(base, override) {
|
|
3055
|
+
if (override === void 0) return base;
|
|
3056
|
+
if (base === void 0) return override;
|
|
3057
|
+
if (Array.isArray(base) && Array.isArray(override)) return [...base, ...override];
|
|
3058
|
+
return override;
|
|
3059
|
+
}
|
|
3060
|
+
function mergePresenterOptions(base, override) {
|
|
3061
|
+
if (override === void 0) return base;
|
|
3062
|
+
if (override === false || base === false || base === void 0) return override;
|
|
3063
|
+
const viewerControl = typeof base.viewerControl === "object" && typeof override.viewerControl === "object" ? { ...base.viewerControl, ...override.viewerControl } : override.viewerControl ?? base.viewerControl;
|
|
3064
|
+
return { ...base, ...override, viewerControl };
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3067
|
+
// src/source/r2-assets.ts
|
|
3068
|
+
function withR2Assets(source, options) {
|
|
3069
|
+
return {
|
|
3070
|
+
async listDecks(c) {
|
|
3071
|
+
return source.listDecks(c);
|
|
3072
|
+
},
|
|
3073
|
+
async getCompiledDeck(c, slug) {
|
|
3074
|
+
return source.getCompiledDeck(c, slug);
|
|
3075
|
+
},
|
|
3076
|
+
async getAsset(c, slug, assetPath) {
|
|
3077
|
+
const deck = await source.getCompiledDeck(c, slug);
|
|
3078
|
+
const asset = deck ? findRoutableAsset(deck.assets, slug, assetPath) : void 0;
|
|
3079
|
+
const bucket = asset ? await resolveBucket(options.bucket, c) : void 0;
|
|
3080
|
+
if (deck && asset && bucket) {
|
|
3081
|
+
const object = await bucket.get(resolveR2Key({ deck, asset, slug, assetPath }, options));
|
|
3082
|
+
if (object) {
|
|
3083
|
+
const response = await responseFromR2Object(object, { deck, asset, slug, assetPath }, options);
|
|
3084
|
+
if (response) return response;
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
return source.getAsset?.(c, slug, assetPath) ?? null;
|
|
3088
|
+
}
|
|
3089
|
+
};
|
|
3090
|
+
}
|
|
3091
|
+
async function resolveBucket(bucket, c) {
|
|
3092
|
+
return typeof bucket === "function" ? bucket(c) : bucket;
|
|
3093
|
+
}
|
|
3094
|
+
function findRoutableAsset(assets, slug, assetPath) {
|
|
3095
|
+
const normalized = assetPath.replace(/^\/+/, "");
|
|
3096
|
+
return assets.find((asset) => {
|
|
3097
|
+
if (asset.type !== "local" && asset.type !== "r2") return false;
|
|
3098
|
+
const suffix = `/${slug}/assets/${normalized}`;
|
|
3099
|
+
return asset.publicPath.endsWith(suffix);
|
|
3100
|
+
});
|
|
3101
|
+
}
|
|
3102
|
+
function resolveR2Key(input, options) {
|
|
3103
|
+
if (options.key) return options.key(input);
|
|
3104
|
+
if (input.asset.r2Key) return input.asset.r2Key;
|
|
3105
|
+
const key = input.asset.sourcePath || `${input.slug}/assets/${input.assetPath}`;
|
|
3106
|
+
const prefix = options.keyPrefix?.replace(/\/+$/, "");
|
|
3107
|
+
return prefix ? `${prefix}/${key.replace(/^\/+/, "")}` : key.replace(/^\/+/, "");
|
|
3108
|
+
}
|
|
3109
|
+
async function responseFromR2Object(object, input, options) {
|
|
3110
|
+
const body = object.body ?? (object.arrayBuffer ? await object.arrayBuffer() : void 0);
|
|
3111
|
+
if (body == null) return null;
|
|
3112
|
+
const headers = new Headers();
|
|
3113
|
+
object.writeHttpMetadata?.(headers);
|
|
3114
|
+
applyHttpMetadata(headers, object.httpMetadata);
|
|
3115
|
+
if (!headers.has("content-type") && input.asset.contentType) headers.set("content-type", input.asset.contentType);
|
|
3116
|
+
const cacheControl = resolveCacheControl(input, options);
|
|
3117
|
+
if (cacheControl) {
|
|
3118
|
+
headers.set("cache-control", cacheControl);
|
|
3119
|
+
} else if (cacheControl !== false && input.asset.cacheControl && !headers.has("cache-control")) {
|
|
3120
|
+
headers.set("cache-control", input.asset.cacheControl);
|
|
3121
|
+
}
|
|
3122
|
+
return new Response(body, { headers });
|
|
3123
|
+
}
|
|
3124
|
+
function applyHttpMetadata(headers, metadata) {
|
|
3125
|
+
if (!metadata) return;
|
|
3126
|
+
if (metadata.contentType && !headers.has("content-type")) headers.set("content-type", metadata.contentType);
|
|
3127
|
+
if (metadata.cacheControl && !headers.has("cache-control")) headers.set("cache-control", metadata.cacheControl);
|
|
3128
|
+
if (metadata.contentDisposition && !headers.has("content-disposition")) {
|
|
3129
|
+
headers.set("content-disposition", metadata.contentDisposition);
|
|
3130
|
+
}
|
|
3131
|
+
if (metadata.contentEncoding && !headers.has("content-encoding")) headers.set("content-encoding", metadata.contentEncoding);
|
|
3132
|
+
if (metadata.contentLanguage && !headers.has("content-language")) headers.set("content-language", metadata.contentLanguage);
|
|
3133
|
+
}
|
|
3134
|
+
function resolveCacheControl(input, options) {
|
|
3135
|
+
return typeof options.cacheControl === "function" ? options.cacheControl(input) : options.cacheControl;
|
|
3136
|
+
}
|
|
3137
|
+
export {
|
|
3138
|
+
SLIDE_TRANSITIONS,
|
|
3139
|
+
builtInSlideComponents,
|
|
3140
|
+
configureDecks,
|
|
3141
|
+
controlIconLabel,
|
|
3142
|
+
createDeckPaths,
|
|
3143
|
+
createDeckViewerEmbed,
|
|
3144
|
+
createDeckViewerParts,
|
|
3145
|
+
deckContext,
|
|
3146
|
+
decksRouter,
|
|
3147
|
+
defineDecks,
|
|
3148
|
+
defineDecksConfig,
|
|
3149
|
+
defineSlideComponents,
|
|
3150
|
+
manifestDeckSource,
|
|
3151
|
+
mergeDecksRouterOptions,
|
|
3152
|
+
renderCompiledDeck,
|
|
3153
|
+
renderCompiledDeckAsync,
|
|
3154
|
+
renderCompiledDeckPage,
|
|
3155
|
+
renderCompiledDeckPageAsync,
|
|
3156
|
+
renderCompiledSlide,
|
|
3157
|
+
renderCompiledSlideAsync,
|
|
3158
|
+
renderControlIcon,
|
|
3159
|
+
renderControlIconHtml,
|
|
3160
|
+
serveDecksClientEntry,
|
|
3161
|
+
withR2Assets
|
|
3162
|
+
};
|