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/mod.js
ADDED
|
@@ -0,0 +1,1071 @@
|
|
|
1
|
+
// src/server/router.ts
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { raw as raw2 } from "hono/html";
|
|
4
|
+
|
|
5
|
+
// src/deck/model.ts
|
|
6
|
+
var SLIDE_TRANSITIONS = [
|
|
7
|
+
"none",
|
|
8
|
+
"fade",
|
|
9
|
+
"fade-out",
|
|
10
|
+
"slide-left",
|
|
11
|
+
"slide-right",
|
|
12
|
+
"slide-up",
|
|
13
|
+
"slide-down",
|
|
14
|
+
"view-transition"
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
// src/renderer/jsx-renderer.ts
|
|
18
|
+
import { jsx } from "hono/jsx/jsx-runtime";
|
|
19
|
+
import { HtmlEscapedCallbackPhase, resolveCallback } from "hono/utils/html";
|
|
20
|
+
var builtInSlideComponents = defineSlideComponents({
|
|
21
|
+
Fire: (props) => {
|
|
22
|
+
if (props.order !== void 0) {
|
|
23
|
+
throw new Error('The Fire "order" prop is not supported. Fires reveal in source order.');
|
|
24
|
+
}
|
|
25
|
+
const effect = stringProp(props.effect);
|
|
26
|
+
const at = fireAtProp(props.at);
|
|
27
|
+
return jsx("div", {
|
|
28
|
+
"data-hono-decks-fire": true,
|
|
29
|
+
...at ? { "data-fire-at": at } : {},
|
|
30
|
+
...effect ? { "data-fire-effect": safeToken(effect).toLowerCase() } : {},
|
|
31
|
+
children: props.children
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
Hero: (props) => jsx("section", {
|
|
35
|
+
class: `mdx-hero${props.featured === true ? " is-featured" : ""}${props.image || props.src ? " has-image" : ""}`,
|
|
36
|
+
"data-component": "Hero",
|
|
37
|
+
children: [
|
|
38
|
+
heroImage(props),
|
|
39
|
+
jsx("div", {
|
|
40
|
+
class: "mdx-hero-copy",
|
|
41
|
+
children: [
|
|
42
|
+
typeof props.eyebrow === "string" && props.eyebrow ? jsx("p", { class: "mdx-hero-eyebrow", children: props.eyebrow }) : "",
|
|
43
|
+
typeof props.title === "string" && props.title ? jsx("h1", { children: props.title }) : "",
|
|
44
|
+
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 }) : ""
|
|
45
|
+
]
|
|
46
|
+
})
|
|
47
|
+
]
|
|
48
|
+
}),
|
|
49
|
+
CodeBlock: (props) => {
|
|
50
|
+
const lang = stringProp(props.lang);
|
|
51
|
+
const filename = stringProp(props.filename ?? props.title);
|
|
52
|
+
const highlight = stringProp(props.highlight);
|
|
53
|
+
const code = codeBlockText(props.children);
|
|
54
|
+
const highlightedHtml = typeof props.highlightedHtml === "string" ? props.highlightedHtml : void 0;
|
|
55
|
+
return jsx("figure", {
|
|
56
|
+
class: "hono-decks-code-block",
|
|
57
|
+
...lang ? { "data-lang": lang } : {},
|
|
58
|
+
...filename ? { "data-filename": filename } : {},
|
|
59
|
+
...highlight ? { "data-highlight": highlight } : {},
|
|
60
|
+
children: [
|
|
61
|
+
filename ? jsx("figcaption", { class: "hono-decks-code-caption", children: filename }) : "",
|
|
62
|
+
highlightedHtml ? jsx("div", {
|
|
63
|
+
class: "hono-decks-code-highlight",
|
|
64
|
+
dangerouslySetInnerHTML: { __html: highlightedHtml }
|
|
65
|
+
}) : jsx("pre", {
|
|
66
|
+
children: jsx("code", {
|
|
67
|
+
...lang ? { class: `language-${safeToken(lang)}`, "data-lang": lang } : {},
|
|
68
|
+
children: code
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
]
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
EmbedFrame: (props) => {
|
|
75
|
+
const src = stringProp(props.src);
|
|
76
|
+
const provider = stringProp(props.provider)?.trim().toLowerCase();
|
|
77
|
+
const title = stringProp(props.title) ?? "Embedded content";
|
|
78
|
+
const aspectRatio = stringProp(props.aspectRatio) ?? "16 / 9";
|
|
79
|
+
const loading = stringProp(props.loading) ?? "lazy";
|
|
80
|
+
const allow = stringProp(props.allow) ?? "fullscreen; picture-in-picture";
|
|
81
|
+
const sandbox = props.sandbox === false ? void 0 : stringProp(props.sandbox) ?? "allow-scripts allow-same-origin allow-presentation allow-popups";
|
|
82
|
+
const referrerPolicy = stringProp(props.referrerPolicy ?? props.referrerpolicy) ?? "strict-origin-when-cross-origin";
|
|
83
|
+
const fallbackHref = stringProp(props.fallbackHref ?? props.fallbackUrl ?? props.href) ?? src;
|
|
84
|
+
const fallback = codeBlockText(props.children) || "Open embed";
|
|
85
|
+
const printPoster = provider === "youtube" ? youtubePrintPoster(src) : void 0;
|
|
86
|
+
return jsx("figure", {
|
|
87
|
+
class: "hono-decks-embed-frame",
|
|
88
|
+
"data-component": "EmbedFrame",
|
|
89
|
+
...provider ? { "data-provider": safeToken(provider).toLowerCase() } : {},
|
|
90
|
+
children: [
|
|
91
|
+
jsx("div", {
|
|
92
|
+
class: "hono-decks-embed-viewport",
|
|
93
|
+
style: `aspect-ratio:${safeAspectRatio(aspectRatio)}`,
|
|
94
|
+
children: [
|
|
95
|
+
src ? jsx("iframe", {
|
|
96
|
+
src,
|
|
97
|
+
title,
|
|
98
|
+
loading,
|
|
99
|
+
referrerpolicy: referrerPolicy,
|
|
100
|
+
...sandbox ? { sandbox } : {},
|
|
101
|
+
...allow ? { allow } : {},
|
|
102
|
+
allowfullscreen: true
|
|
103
|
+
}) : "",
|
|
104
|
+
fallbackHref ? jsx("a", {
|
|
105
|
+
class: "hono-decks-embed-print-fallback",
|
|
106
|
+
href: fallbackHref,
|
|
107
|
+
children: [
|
|
108
|
+
printPoster ? jsx("img", {
|
|
109
|
+
class: "hono-decks-embed-print-poster",
|
|
110
|
+
src: printPoster,
|
|
111
|
+
alt: `${title} thumbnail`
|
|
112
|
+
}) : "",
|
|
113
|
+
jsx("span", { children: fallback })
|
|
114
|
+
]
|
|
115
|
+
}) : ""
|
|
116
|
+
]
|
|
117
|
+
}),
|
|
118
|
+
src ? jsx("figcaption", {
|
|
119
|
+
class: "hono-decks-embed-fallback",
|
|
120
|
+
children: jsx("a", { href: fallbackHref, target: "_blank", rel: "noreferrer", children: fallback })
|
|
121
|
+
}) : ""
|
|
122
|
+
]
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
SocialEmbed: (props) => {
|
|
126
|
+
const href = stringProp(props.href ?? props.url);
|
|
127
|
+
const provider = stringProp(props.provider ?? props.service);
|
|
128
|
+
const author = stringProp(props.author);
|
|
129
|
+
const label = stringProp(props.label) ?? (provider ? `Open on ${provider}` : "Open social post");
|
|
130
|
+
const quote = codeBlockText(props.children);
|
|
131
|
+
return jsx("figure", {
|
|
132
|
+
class: "hono-decks-social-embed",
|
|
133
|
+
"data-component": "SocialEmbed",
|
|
134
|
+
...provider ? { "data-provider": safeToken(provider).toLowerCase() } : {},
|
|
135
|
+
children: jsx("blockquote", {
|
|
136
|
+
class: "hono-decks-social-card",
|
|
137
|
+
...href ? { cite: href } : {},
|
|
138
|
+
children: [
|
|
139
|
+
quote ? jsx("p", { children: quote }) : "",
|
|
140
|
+
jsx("footer", {
|
|
141
|
+
children: [
|
|
142
|
+
author ? jsx("span", { class: "hono-decks-social-author", children: author }) : "",
|
|
143
|
+
href ? jsx("a", {
|
|
144
|
+
href,
|
|
145
|
+
target: "_blank",
|
|
146
|
+
rel: "noreferrer",
|
|
147
|
+
children: label
|
|
148
|
+
}) : ""
|
|
149
|
+
]
|
|
150
|
+
})
|
|
151
|
+
]
|
|
152
|
+
})
|
|
153
|
+
});
|
|
154
|
+
},
|
|
155
|
+
TweetEmbed: (props) => {
|
|
156
|
+
const href = stringProp(props.href ?? props.url);
|
|
157
|
+
const label = stringProp(props.label) ?? "Open post on X";
|
|
158
|
+
return jsx("figure", {
|
|
159
|
+
class: "hono-decks-tweet-embed",
|
|
160
|
+
"data-component": "TweetEmbed",
|
|
161
|
+
children: [
|
|
162
|
+
jsx("blockquote", {
|
|
163
|
+
class: "twitter-tweet",
|
|
164
|
+
"data-dnt": "true",
|
|
165
|
+
children: href ? jsx("a", {
|
|
166
|
+
href,
|
|
167
|
+
target: "_blank",
|
|
168
|
+
rel: "noreferrer",
|
|
169
|
+
children: label
|
|
170
|
+
}) : label
|
|
171
|
+
}),
|
|
172
|
+
href ? jsx("script", {
|
|
173
|
+
async: true,
|
|
174
|
+
src: "https://platform.twitter.com/widgets.js",
|
|
175
|
+
charset: "utf-8"
|
|
176
|
+
}) : "",
|
|
177
|
+
href ? jsx("a", {
|
|
178
|
+
class: "hono-decks-tweet-print-fallback",
|
|
179
|
+
href,
|
|
180
|
+
children: label
|
|
181
|
+
}) : ""
|
|
182
|
+
]
|
|
183
|
+
});
|
|
184
|
+
},
|
|
185
|
+
LinkCard: (props) => {
|
|
186
|
+
const href = stringProp(props.href ?? props.url);
|
|
187
|
+
const title = stringProp(props.title) ?? href ?? "Link";
|
|
188
|
+
const description = stringProp(props.description);
|
|
189
|
+
const image = stringProp(props.image ?? props.imageUrl);
|
|
190
|
+
const siteName = stringProp(props.siteName ?? props.site);
|
|
191
|
+
const label = codeBlockText(props.children) || "Open link";
|
|
192
|
+
return jsx("figure", {
|
|
193
|
+
class: "hono-decks-link-card",
|
|
194
|
+
"data-component": "LinkCard",
|
|
195
|
+
children: href ? jsx("a", {
|
|
196
|
+
class: "hono-decks-link-card-anchor",
|
|
197
|
+
href,
|
|
198
|
+
target: "_blank",
|
|
199
|
+
rel: "noreferrer",
|
|
200
|
+
children: [
|
|
201
|
+
image ? jsx("img", { class: "hono-decks-link-card-image", src: image, alt: title }) : "",
|
|
202
|
+
jsx("span", {
|
|
203
|
+
class: "hono-decks-link-card-body",
|
|
204
|
+
children: [
|
|
205
|
+
siteName ? jsx("span", { class: "hono-decks-link-card-site", children: siteName }) : "",
|
|
206
|
+
jsx("span", { class: "hono-decks-link-card-title", children: title }),
|
|
207
|
+
description ? jsx("span", { class: "hono-decks-link-card-description", children: description }) : "",
|
|
208
|
+
jsx("span", { class: "hono-decks-link-card-label", children: label })
|
|
209
|
+
]
|
|
210
|
+
})
|
|
211
|
+
]
|
|
212
|
+
}) : [
|
|
213
|
+
image ? jsx("img", { class: "hono-decks-link-card-image", src: image, alt: title }) : "",
|
|
214
|
+
siteName ? jsx("span", { class: "hono-decks-link-card-site", children: siteName }) : "",
|
|
215
|
+
jsx("span", { class: "hono-decks-link-card-title", children: title }),
|
|
216
|
+
description ? jsx("span", { class: "hono-decks-link-card-description", children: description }) : ""
|
|
217
|
+
]
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
function defineSlideComponents(input) {
|
|
222
|
+
const registry = {};
|
|
223
|
+
for (const [name, value] of Object.entries(input)) {
|
|
224
|
+
registry[name] = typeof value === "function" ? { component: value } : value;
|
|
225
|
+
}
|
|
226
|
+
return registry;
|
|
227
|
+
}
|
|
228
|
+
function heroImage(props) {
|
|
229
|
+
const image = typeof props.image === "string" ? props.image : typeof props.src === "string" ? props.src : void 0;
|
|
230
|
+
if (!image) return "";
|
|
231
|
+
const alt = typeof props.alt === "string" && props.alt ? props.alt : typeof props.title === "string" ? props.title : "Hero image";
|
|
232
|
+
return jsx("img", { class: "mdx-hero-image", src: image, alt });
|
|
233
|
+
}
|
|
234
|
+
function stringProp(value) {
|
|
235
|
+
if (typeof value === "string") return value;
|
|
236
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
237
|
+
return void 0;
|
|
238
|
+
}
|
|
239
|
+
function fireAtProp(value) {
|
|
240
|
+
if (value === void 0 || value === null) return void 0;
|
|
241
|
+
if (typeof value === "number") {
|
|
242
|
+
if (Number.isInteger(value) && value >= 0) return String(value);
|
|
243
|
+
throw new Error('The Fire "at" prop accepts a non-negative integer or a relative string such as "+1".');
|
|
244
|
+
}
|
|
245
|
+
if (typeof value === "string") {
|
|
246
|
+
const at = value.trim();
|
|
247
|
+
if (/^(?:\d+|[+-]\d+)$/.test(at)) return at;
|
|
248
|
+
}
|
|
249
|
+
throw new Error('The Fire "at" prop accepts a non-negative integer or a relative string such as "+1".');
|
|
250
|
+
}
|
|
251
|
+
function safeToken(value) {
|
|
252
|
+
return value.trim().replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "text";
|
|
253
|
+
}
|
|
254
|
+
function safeAspectRatio(value) {
|
|
255
|
+
return /^\s*\d+(\.\d+)?\s*(\/|:)\s*\d+(\.\d+)?\s*$/.test(value) ? value.replace(":", " / ") : "16 / 9";
|
|
256
|
+
}
|
|
257
|
+
function youtubePrintPoster(src) {
|
|
258
|
+
if (!src) return void 0;
|
|
259
|
+
try {
|
|
260
|
+
const url = new URL(src);
|
|
261
|
+
if (!["youtube.com", "www.youtube.com", "youtube-nocookie.com", "www.youtube-nocookie.com"].includes(url.hostname)) {
|
|
262
|
+
return void 0;
|
|
263
|
+
}
|
|
264
|
+
const match = url.pathname.match(/^\/embed\/([A-Za-z0-9_-]+)$/);
|
|
265
|
+
return match ? `https://i.ytimg.com/vi/${match[1]}/hqdefault.jpg` : void 0;
|
|
266
|
+
} catch {
|
|
267
|
+
return void 0;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function codeBlockText(value) {
|
|
271
|
+
if (Array.isArray(value)) return value.map(codeBlockText).join("");
|
|
272
|
+
if (value === null || value === void 0 || typeof value === "boolean") return "";
|
|
273
|
+
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
274
|
+
if (isJsxValue(value)) return codeBlockText(value.props?.children);
|
|
275
|
+
return String(value);
|
|
276
|
+
}
|
|
277
|
+
async function renderJsxValue(value) {
|
|
278
|
+
const resolved = value instanceof Promise ? await value : value;
|
|
279
|
+
if (typeof resolved === "string") return resolved;
|
|
280
|
+
if (resolved === null || resolved === void 0 || typeof resolved === "boolean") return "";
|
|
281
|
+
if (typeof resolved === "number") return String(resolved);
|
|
282
|
+
return await resolveCallback(resolved, HtmlEscapedCallbackPhase.Stringify, false, {});
|
|
283
|
+
}
|
|
284
|
+
function isJsxValue(value) {
|
|
285
|
+
return typeof value === "object" && value !== null && "tag" in value && "props" in value && "type" in value;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// src/renderer/control-icons.ts
|
|
289
|
+
import { jsx as jsx2 } from "hono/jsx/jsx-runtime";
|
|
290
|
+
function renderControlIcon(name, className = "hono-decks-control-icon") {
|
|
291
|
+
return jsx2("svg", {
|
|
292
|
+
class: className,
|
|
293
|
+
"data-hono-decks-control-icon": true,
|
|
294
|
+
viewBox: "0 0 24 24",
|
|
295
|
+
"aria-hidden": "true",
|
|
296
|
+
focusable: "false",
|
|
297
|
+
fill: "none",
|
|
298
|
+
"stroke-width": "2",
|
|
299
|
+
"stroke-linecap": "round",
|
|
300
|
+
"stroke-linejoin": "round",
|
|
301
|
+
children: controlIconPaths(name)
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
function controlIconLabel(name) {
|
|
305
|
+
switch (name) {
|
|
306
|
+
case "deck-list":
|
|
307
|
+
return "Deck list";
|
|
308
|
+
case "home":
|
|
309
|
+
return "Home";
|
|
310
|
+
case "viewer":
|
|
311
|
+
return "Viewer";
|
|
312
|
+
case "projection":
|
|
313
|
+
return "Projection";
|
|
314
|
+
case "presenter":
|
|
315
|
+
return "Presenter";
|
|
316
|
+
case "previous":
|
|
317
|
+
return "Previous slide";
|
|
318
|
+
case "next":
|
|
319
|
+
return "Next slide";
|
|
320
|
+
case "fullscreen":
|
|
321
|
+
return "Toggle fullscreen";
|
|
322
|
+
case "print":
|
|
323
|
+
return "Print view";
|
|
324
|
+
case "details":
|
|
325
|
+
return "Details";
|
|
326
|
+
case "export-pdf":
|
|
327
|
+
return "Export PDF";
|
|
328
|
+
case "export-png":
|
|
329
|
+
return "Export PNG";
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function controlIconPaths(name) {
|
|
333
|
+
switch (name) {
|
|
334
|
+
case "deck-list":
|
|
335
|
+
return [
|
|
336
|
+
jsx2("path", { d: "M4 5h16" }),
|
|
337
|
+
jsx2("path", { d: "M4 12h16" }),
|
|
338
|
+
jsx2("path", { d: "M4 19h16" })
|
|
339
|
+
];
|
|
340
|
+
case "home":
|
|
341
|
+
return [
|
|
342
|
+
jsx2("path", { d: "M3 11l9-8 9 8" }),
|
|
343
|
+
jsx2("path", { d: "M5 10v10h14V10" }),
|
|
344
|
+
jsx2("path", { d: "M9 20v-6h6v6" })
|
|
345
|
+
];
|
|
346
|
+
case "viewer":
|
|
347
|
+
return [
|
|
348
|
+
jsx2("path", { d: "M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z" }),
|
|
349
|
+
jsx2("circle", { cx: "12", cy: "12", r: "3" })
|
|
350
|
+
];
|
|
351
|
+
case "projection":
|
|
352
|
+
return [
|
|
353
|
+
jsx2("path", { d: "M4 5h16v10H4z" }),
|
|
354
|
+
jsx2("path", { d: "M8 19h8" }),
|
|
355
|
+
jsx2("path", { d: "M12 15v4" })
|
|
356
|
+
];
|
|
357
|
+
case "presenter":
|
|
358
|
+
return [
|
|
359
|
+
jsx2("path", { d: "M4 5h16v10H4z" }),
|
|
360
|
+
jsx2("path", { d: "M8 21l4-6 4 6" }),
|
|
361
|
+
jsx2("path", { d: "M9 9h6" })
|
|
362
|
+
];
|
|
363
|
+
case "previous":
|
|
364
|
+
return [jsx2("path", { d: "M15 6l-6 6 6 6" })];
|
|
365
|
+
case "next":
|
|
366
|
+
return [jsx2("path", { d: "M9 6l6 6-6 6" })];
|
|
367
|
+
case "fullscreen":
|
|
368
|
+
return [
|
|
369
|
+
jsx2("path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }),
|
|
370
|
+
jsx2("path", { d: "M16 3h3a2 2 0 0 1 2 2v3" }),
|
|
371
|
+
jsx2("path", { d: "M8 21H5a2 2 0 0 1-2-2v-3" }),
|
|
372
|
+
jsx2("path", { d: "M16 21h3a2 2 0 0 0 2-2v-3" })
|
|
373
|
+
];
|
|
374
|
+
case "print":
|
|
375
|
+
return [
|
|
376
|
+
jsx2("path", { d: "M6 9V3h12v6" }),
|
|
377
|
+
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" }),
|
|
378
|
+
jsx2("path", { d: "M6 14h12v7H6z" })
|
|
379
|
+
];
|
|
380
|
+
case "details":
|
|
381
|
+
return [
|
|
382
|
+
jsx2("circle", { cx: "12", cy: "12", r: "9" }),
|
|
383
|
+
jsx2("path", { d: "M12 11v5" }),
|
|
384
|
+
jsx2("path", { d: "M12 8h.01" })
|
|
385
|
+
];
|
|
386
|
+
case "export-pdf":
|
|
387
|
+
return [
|
|
388
|
+
jsx2("path", { d: "M14 3v4a2 2 0 0 0 2 2h4" }),
|
|
389
|
+
jsx2("path", { d: "M5 3h9l6 6v12H5z" }),
|
|
390
|
+
jsx2("path", { d: "M8 15h8" }),
|
|
391
|
+
jsx2("path", { d: "M8 18h5" })
|
|
392
|
+
];
|
|
393
|
+
case "export-png":
|
|
394
|
+
return [
|
|
395
|
+
jsx2("rect", { x: "4", y: "5", width: "16", height: "14", rx: "2" }),
|
|
396
|
+
jsx2("path", { d: "M8 14l2.5-2.5L14 15l2-2 2 2" }),
|
|
397
|
+
jsx2("circle", { cx: "9", cy: "9", r: "1" })
|
|
398
|
+
];
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// src/server/paths.ts
|
|
403
|
+
function createDeckPaths(mountPath, slug) {
|
|
404
|
+
const mount = normalizeMountPath(mountPath);
|
|
405
|
+
const viewer = `${mount === "/" ? "" : mount}/${encodeURIComponent(slug)}`;
|
|
406
|
+
return {
|
|
407
|
+
viewer,
|
|
408
|
+
render: `${viewer}/render`,
|
|
409
|
+
print: `${viewer}/print`,
|
|
410
|
+
presentation: `${viewer}/presentation`,
|
|
411
|
+
presenter: `${viewer}/presenter`,
|
|
412
|
+
embed: `${viewer}/embed`,
|
|
413
|
+
exportPdf: `${viewer}/export.pdf`,
|
|
414
|
+
exportPng: `${viewer}/export.png`,
|
|
415
|
+
ogImage: `${viewer}/og.png`,
|
|
416
|
+
assets: `${viewer}/assets`
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
function normalizeMountPath(value) {
|
|
420
|
+
const trimmed = value.trim();
|
|
421
|
+
if (!trimmed || trimmed === "/") return "/";
|
|
422
|
+
return `/${trimmed.replace(/^\/+|\/+$/g, "")}`;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// src/server/viewer.ts
|
|
426
|
+
import { raw } from "hono/html";
|
|
427
|
+
import { jsx as jsx3 } from "hono/jsx/jsx-runtime";
|
|
428
|
+
|
|
429
|
+
// src/server/viewer-script.ts
|
|
430
|
+
var VIEWER_STATE_MESSAGE_TYPE = "hono-decks:state";
|
|
431
|
+
var VIEWER_COMMAND_MESSAGE_TYPE = "hono-decks:command";
|
|
432
|
+
function renderViewerScript(nonce) {
|
|
433
|
+
const nonceAttribute = nonce ? ` nonce="${escapeHtml(nonce)}"` : "";
|
|
434
|
+
return `<script data-hono-decks-viewer-runtime${nonceAttribute}>
|
|
435
|
+
(() => {
|
|
436
|
+
const roots = Array.from(document.querySelectorAll("[data-hono-decks-viewer]"));
|
|
437
|
+
const runtime = window.__honoDecksViewerRuntime ??= { controllers: new Map(), globalInitialized: false };
|
|
438
|
+
const controllers = runtime.controllers;
|
|
439
|
+
|
|
440
|
+
for (const root of roots) {
|
|
441
|
+
if (root.hasAttribute("data-hono-decks-initialized")) continue;
|
|
442
|
+
root.setAttribute("data-hono-decks-initialized", "true");
|
|
443
|
+
|
|
444
|
+
const viewport = root.querySelector("[data-viewer-viewport]");
|
|
445
|
+
const iframe = root.querySelector("iframe");
|
|
446
|
+
const frameOrigin = iframe?.src ? new URL(iframe.src, window.location.href).origin : window.location.origin;
|
|
447
|
+
const position = root.querySelector("[data-slide-position]");
|
|
448
|
+
const navigationLayers = root.querySelectorAll("[data-viewer-navigation]");
|
|
449
|
+
const printPath = root.getAttribute("data-hono-decks-print-path") || "";
|
|
450
|
+
let pointerStartX = null;
|
|
451
|
+
let pointerStartY = null;
|
|
452
|
+
let suppressNavigationClick = false;
|
|
453
|
+
let suppressNavigationClickTimer = null;
|
|
454
|
+
|
|
455
|
+
if (position && viewport) viewport.append(position);
|
|
456
|
+
|
|
457
|
+
function sendCommand(action, index) {
|
|
458
|
+
const target = iframe?.contentWindow;
|
|
459
|
+
try {
|
|
460
|
+
const command = target?.__honoDecksPresentationRuntime?.command;
|
|
461
|
+
if (typeof command === "function") {
|
|
462
|
+
command(action, index);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
} catch {
|
|
466
|
+
// Cross-origin frames cannot expose their runtime. Use postMessage below.
|
|
467
|
+
}
|
|
468
|
+
target?.postMessage({ type: ${JSON.stringify(VIEWER_COMMAND_MESSAGE_TYPE)}, action, index }, frameOrigin);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function navigationClick(event) {
|
|
472
|
+
if (suppressNavigationClick) {
|
|
473
|
+
clearNavigationClickSuppression();
|
|
474
|
+
event.preventDefault();
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const action = event.currentTarget?.getAttribute("data-viewer-navigation");
|
|
478
|
+
if (action === "previous" || action === "next") {
|
|
479
|
+
sendCommand(action);
|
|
480
|
+
viewport?.focus({ preventScroll: true });
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function clearNavigationClickSuppression() {
|
|
485
|
+
suppressNavigationClick = false;
|
|
486
|
+
if (suppressNavigationClickTimer !== null) window.clearTimeout(suppressNavigationClickTimer);
|
|
487
|
+
suppressNavigationClickTimer = null;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function suppressCurrentNavigationClick() {
|
|
491
|
+
clearNavigationClickSuppression();
|
|
492
|
+
suppressNavigationClick = true;
|
|
493
|
+
suppressNavigationClickTimer = window.setTimeout(clearNavigationClickSuppression, 0);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function viewerPointerDown(event) {
|
|
497
|
+
pointerStartX = event.clientX;
|
|
498
|
+
pointerStartY = event.clientY;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function viewerPointerUp(event) {
|
|
502
|
+
if (pointerStartX === null || pointerStartY === null) return;
|
|
503
|
+
const deltaX = event.clientX - pointerStartX;
|
|
504
|
+
const deltaY = event.clientY - pointerStartY;
|
|
505
|
+
pointerStartX = null;
|
|
506
|
+
pointerStartY = null;
|
|
507
|
+
if (Math.abs(deltaX) < 48 || Math.abs(deltaX) < Math.abs(deltaY)) return;
|
|
508
|
+
suppressCurrentNavigationClick();
|
|
509
|
+
sendCommand(deltaX < 0 ? "next" : "previous");
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function viewerPointerCancel() {
|
|
513
|
+
pointerStartX = null;
|
|
514
|
+
pointerStartY = null;
|
|
515
|
+
clearNavigationClickSuppression();
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function isPortraitMobile() {
|
|
519
|
+
return window.matchMedia("(orientation: portrait) and (pointer: coarse)").matches;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
async function lockViewerLandscape() {
|
|
523
|
+
const orientation = window.screen?.orientation;
|
|
524
|
+
if (!orientation || typeof orientation.lock !== "function") return;
|
|
525
|
+
try {
|
|
526
|
+
await orientation.lock("landscape");
|
|
527
|
+
} catch {
|
|
528
|
+
// Orientation locking is optional; fullscreen remains available when unsupported.
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function unlockViewerOrientation() {
|
|
533
|
+
const orientation = window.screen?.orientation;
|
|
534
|
+
if (!orientation || typeof orientation.unlock !== "function") return;
|
|
535
|
+
orientation.unlock();
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
async function toggleViewerFullscreen() {
|
|
539
|
+
if (document.fullscreenElement) {
|
|
540
|
+
unlockViewerOrientation();
|
|
541
|
+
await document.exitFullscreen?.();
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
await root.requestFullscreen?.();
|
|
545
|
+
if (document.fullscreenElement && isPortraitMobile()) await lockViewerLandscape();
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function writeViewerPaginationState(message) {
|
|
549
|
+
if (!Number.isInteger(message.index)) return;
|
|
550
|
+
const url = new URL(window.location.href);
|
|
551
|
+
const params = url.searchParams;
|
|
552
|
+
params.set("slide", String(message.index + 1));
|
|
553
|
+
params.set("step", String(Number.isInteger(message.stepIndex) ? message.stepIndex : 0));
|
|
554
|
+
window.history.replaceState(null, "", url);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function handleMessage(event) {
|
|
558
|
+
if (event.source !== iframe?.contentWindow) return;
|
|
559
|
+
if (event.origin !== frameOrigin) return;
|
|
560
|
+
const message = event.data;
|
|
561
|
+
if (!message || message.type !== ${JSON.stringify(VIEWER_STATE_MESSAGE_TYPE)}) return;
|
|
562
|
+
writeViewerPaginationState(message);
|
|
563
|
+
root.setAttribute("data-step-index", String(message.stepIndex ?? 0));
|
|
564
|
+
root.setAttribute("data-step-count", String(message.stepCount ?? 0));
|
|
565
|
+
if (position) {
|
|
566
|
+
const slideText = String(message.index + 1) + " / " + String(message.slideCount ?? "?");
|
|
567
|
+
position.textContent = slideText;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function handleKeydown(event) {
|
|
572
|
+
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "p" && printPath) {
|
|
573
|
+
event.preventDefault();
|
|
574
|
+
const printUrl = new URL(printPath, window.location.href);
|
|
575
|
+
printUrl.searchParams.set("autoprint", "1");
|
|
576
|
+
window.location.assign(printUrl);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
if (event.key === "ArrowRight" || event.key === " ") sendCommand("next");
|
|
580
|
+
if (event.key === "ArrowLeft") sendCommand("previous");
|
|
581
|
+
if (event.key === "f") void toggleViewerFullscreen();
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
root.querySelectorAll("[data-action='previous']").forEach((control) => {
|
|
585
|
+
control.addEventListener("click", () => sendCommand("previous"));
|
|
586
|
+
});
|
|
587
|
+
root.querySelectorAll("[data-action='next']").forEach((control) => {
|
|
588
|
+
control.addEventListener("click", () => sendCommand("next"));
|
|
589
|
+
});
|
|
590
|
+
root.querySelectorAll("[data-action='fullscreen']").forEach((control) => {
|
|
591
|
+
control.addEventListener("click", () => { void toggleViewerFullscreen(); });
|
|
592
|
+
});
|
|
593
|
+
root.querySelectorAll("[data-action='goTo']").forEach((control) => {
|
|
594
|
+
control.addEventListener("click", () => {
|
|
595
|
+
const index = Number(control.getAttribute("data-slide-index"));
|
|
596
|
+
if (Number.isFinite(index)) sendCommand("goTo", index);
|
|
597
|
+
});
|
|
598
|
+
});
|
|
599
|
+
navigationLayers.forEach((layer) => layer.addEventListener("click", navigationClick));
|
|
600
|
+
viewport?.addEventListener("pointerdown", viewerPointerDown);
|
|
601
|
+
viewport?.addEventListener("pointerup", viewerPointerUp);
|
|
602
|
+
viewport?.addEventListener("pointercancel", viewerPointerCancel);
|
|
603
|
+
window.addEventListener("message", handleMessage);
|
|
604
|
+
controllers.set(root, { handleKeydown, unlockViewerOrientation });
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
if (!runtime.globalInitialized) {
|
|
608
|
+
runtime.globalInitialized = true;
|
|
609
|
+
document.addEventListener("fullscreenchange", () => {
|
|
610
|
+
if (document.fullscreenElement) return;
|
|
611
|
+
controllers.forEach((controller) => controller.unlockViewerOrientation());
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
document.addEventListener("keydown", (event) => {
|
|
615
|
+
const activeRoot = document.activeElement?.closest?.("[data-hono-decks-viewer]");
|
|
616
|
+
const onlyRoot = controllers.size === 1 ? controllers.keys().next().value : null;
|
|
617
|
+
controllers.get(activeRoot || onlyRoot)?.handleKeydown(event);
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
})();
|
|
621
|
+
</script>`;
|
|
622
|
+
}
|
|
623
|
+
function escapeHtml(value) {
|
|
624
|
+
return value.replaceAll("&", "&").replaceAll('"', """).replaceAll("<", "<").replaceAll(">", ">");
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// src/server/viewer-style.ts
|
|
628
|
+
var VIEWER_ASPECT_RATIO = "16/9";
|
|
629
|
+
function embeddedViewerStyle() {
|
|
630
|
+
const root = "[data-hono-decks-viewer][data-hono-decks-embed]";
|
|
631
|
+
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}
|
|
632
|
+
${root} *:focus-visible{outline:none}
|
|
633
|
+
${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}
|
|
634
|
+
${root} .hono-decks-viewer-stage{display:grid;place-items:center;width:100%;min-width:0;min-height:0;container-type:inline-size}
|
|
635
|
+
${root} .hono-decks-viewport{width:100%;aspect-ratio:${VIEWER_ASPECT_RATIO};position:relative;overflow:hidden;touch-action:pan-y}
|
|
636
|
+
${root} .hono-decks-viewport:focus-visible{outline:2px solid currentColor;outline-offset:4px}
|
|
637
|
+
${root} .hono-decks-frame-stage,${root} .hono-decks-frame-stage iframe{width:100%;height:100%}
|
|
638
|
+
${root} .hono-decks-frame-stage iframe{border:0;display:block}
|
|
639
|
+
${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}
|
|
640
|
+
${root} .hono-decks-viewer-navigation-previous{left:0}
|
|
641
|
+
${root} .hono-decks-viewer-navigation-next{right:0}
|
|
642
|
+
${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}
|
|
643
|
+
${root} .hono-decks-viewer-controls{display:flex;gap:8px;align-items:center;justify-content:center;max-width:100%;min-width:0;z-index:4}
|
|
644
|
+
${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}
|
|
645
|
+
${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}
|
|
646
|
+
${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}
|
|
647
|
+
${root} .hono-decks-viewer-controls button *,${root} .hono-decks-viewer-controls a *{pointer-events:none;cursor:pointer}
|
|
648
|
+
${root} .hono-decks-control-icon{width:16px;height:16px;flex:0 0 auto;stroke:currentColor;pointer-events:none}
|
|
649
|
+
${root} .hono-decks-viewer-toc button{font:inherit}
|
|
650
|
+
@media (prefers-reduced-motion: reduce){${root},${root} *{scroll-behavior:auto!important;animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important}}`;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// src/server/viewer.ts
|
|
654
|
+
async function createDeckViewerParts(input) {
|
|
655
|
+
const slug = input.deck.slug;
|
|
656
|
+
const title = input.deck.meta.title ?? slug;
|
|
657
|
+
const basePath = input.mountPath.replace(/\/$/, "");
|
|
658
|
+
const paths = createDeckPaths(basePath, slug);
|
|
659
|
+
const imagePath = await resolveOpenGraphImagePath(input.openGraph, { deck: input.deck, paths });
|
|
660
|
+
const renderUrl = `${paths.render}${input.viewerStateQuery ?? ""}`;
|
|
661
|
+
const slides = createDeckToc(input.deck);
|
|
662
|
+
const meta = {
|
|
663
|
+
title,
|
|
664
|
+
description: input.deck.meta.description,
|
|
665
|
+
paths,
|
|
666
|
+
availablePages: { print: input.availablePages?.print !== false },
|
|
667
|
+
availableExports: input.exportPaths ?? {},
|
|
668
|
+
...imagePath ? { imagePath } : {}
|
|
669
|
+
};
|
|
670
|
+
const controlsInput = input.controls === false ? false : input.controls ?? {};
|
|
671
|
+
const controlsContext = { slug, title, mountPath: basePath, meta, slides };
|
|
672
|
+
const controls = controlsInput === false ? null : renderViewerControls(controlsInput, controlsContext);
|
|
673
|
+
return {
|
|
674
|
+
slug,
|
|
675
|
+
title,
|
|
676
|
+
renderUrl,
|
|
677
|
+
frame: {
|
|
678
|
+
content: renderViewerFrame({ title, renderUrl }),
|
|
679
|
+
html: renderViewerFrameHtml({ title, renderUrl })
|
|
680
|
+
},
|
|
681
|
+
controls: controls ? { content: controls, html: await renderJsxValue(controls) } : null,
|
|
682
|
+
toc: { content: renderViewerToc(slides), html: renderViewerTocHtml(slides) },
|
|
683
|
+
slides,
|
|
684
|
+
meta
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
async function createDeckViewerEmbed(input) {
|
|
688
|
+
const parts = await createDeckViewerParts(input);
|
|
689
|
+
const root = jsx3("section", {
|
|
690
|
+
class: ["hono-decks-embedded-viewer", input.className].filter(Boolean).join(" "),
|
|
691
|
+
"data-hono-decks-viewer": true,
|
|
692
|
+
"data-hono-decks-embed": true,
|
|
693
|
+
"data-deck-slug": parts.slug,
|
|
694
|
+
...parts.meta.availablePages.print ? { "data-hono-decks-print-path": parts.meta.paths.print } : {},
|
|
695
|
+
"aria-label": parts.title,
|
|
696
|
+
children: [
|
|
697
|
+
jsx3("div", {
|
|
698
|
+
class: "hono-decks-viewer-shell",
|
|
699
|
+
children: [parts.frame.content, parts.controls?.content]
|
|
700
|
+
}),
|
|
701
|
+
input.toc ? parts.toc.content : null
|
|
702
|
+
]
|
|
703
|
+
});
|
|
704
|
+
const rootHtml = await renderJsxValue(root);
|
|
705
|
+
const nonceAttribute = input.nonce ? ` nonce="${escapeHtml2(input.nonce)}"` : "";
|
|
706
|
+
const embedHtml = `<style data-hono-decks-embed-style${nonceAttribute}>${embeddedViewerStyle()}${input.style ?? ""}</style>${rootHtml}${renderViewerScript(input.nonce)}`;
|
|
707
|
+
return {
|
|
708
|
+
...parts,
|
|
709
|
+
embed: raw(embedHtml),
|
|
710
|
+
embedHtml
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
async function resolveOpenGraphImagePath(options, input) {
|
|
714
|
+
if (!options) return void 0;
|
|
715
|
+
if (options === true) return input.paths.ogImage;
|
|
716
|
+
if (typeof options.imagePath === "function") return options.imagePath(input);
|
|
717
|
+
return options.imagePath ?? input.paths.ogImage;
|
|
718
|
+
}
|
|
719
|
+
function createDeckToc(deck) {
|
|
720
|
+
return deck.slides.map((slide) => ({
|
|
721
|
+
index: slide.index,
|
|
722
|
+
title: slide.meta.title,
|
|
723
|
+
label: slide.meta.title ?? `Slide ${slide.index + 1}`
|
|
724
|
+
}));
|
|
725
|
+
}
|
|
726
|
+
function renderViewerFrame(input) {
|
|
727
|
+
return jsx3("div", {
|
|
728
|
+
class: "hono-decks-viewer-stage",
|
|
729
|
+
"data-hono-decks-frame": true,
|
|
730
|
+
children: jsx3("div", {
|
|
731
|
+
class: "hono-decks-viewport",
|
|
732
|
+
"data-viewer-viewport": true,
|
|
733
|
+
tabindex: "0",
|
|
734
|
+
children: [
|
|
735
|
+
jsx3("div", {
|
|
736
|
+
class: "hono-decks-frame-stage",
|
|
737
|
+
"data-viewer-stage": true,
|
|
738
|
+
children: jsx3("iframe", {
|
|
739
|
+
title: input.title,
|
|
740
|
+
src: input.renderUrl
|
|
741
|
+
})
|
|
742
|
+
}),
|
|
743
|
+
jsx3("button", {
|
|
744
|
+
class: "hono-decks-viewer-navigation-layer hono-decks-viewer-navigation-previous",
|
|
745
|
+
type: "button",
|
|
746
|
+
"data-viewer-navigation": "previous",
|
|
747
|
+
"aria-label": "Previous slide"
|
|
748
|
+
}),
|
|
749
|
+
jsx3("button", {
|
|
750
|
+
class: "hono-decks-viewer-navigation-layer hono-decks-viewer-navigation-next",
|
|
751
|
+
type: "button",
|
|
752
|
+
"data-viewer-navigation": "next",
|
|
753
|
+
"aria-label": "Next slide"
|
|
754
|
+
})
|
|
755
|
+
]
|
|
756
|
+
})
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
function renderViewerFrameHtml(input) {
|
|
760
|
+
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="${escapeHtml2(input.title)}" src="${escapeHtml2(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>`;
|
|
761
|
+
}
|
|
762
|
+
function renderViewerControls(options, context) {
|
|
763
|
+
return jsx3("nav", {
|
|
764
|
+
...controlAttributes(options.attributes),
|
|
765
|
+
class: controlsClassName(options),
|
|
766
|
+
"data-hono-decks-viewer-controls": true,
|
|
767
|
+
"aria-label": options.ariaLabel ?? "Viewer controls",
|
|
768
|
+
children: resolveViewerControlItems(options, context).map((item) => renderViewerControlItem(item, options, context))
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
function renderViewerToc(slides) {
|
|
772
|
+
return jsx3("nav", {
|
|
773
|
+
class: "hono-decks-viewer-toc",
|
|
774
|
+
"data-hono-decks-toc": true,
|
|
775
|
+
"aria-label": "Slide navigation",
|
|
776
|
+
children: jsx3("ol", {
|
|
777
|
+
children: slides.map(
|
|
778
|
+
(slide) => jsx3("li", {
|
|
779
|
+
children: jsx3("button", {
|
|
780
|
+
type: "button",
|
|
781
|
+
"data-action": "goTo",
|
|
782
|
+
"data-slide-index": String(slide.index),
|
|
783
|
+
children: slide.label
|
|
784
|
+
})
|
|
785
|
+
})
|
|
786
|
+
)
|
|
787
|
+
})
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
function renderViewerTocHtml(slides) {
|
|
791
|
+
const items = slides.map(
|
|
792
|
+
(slide) => `<li><button type="button" data-action="goTo" data-slide-index="${slide.index}">${escapeHtml2(slide.label)}</button></li>`
|
|
793
|
+
).join("");
|
|
794
|
+
return `<nav class="hono-decks-viewer-toc" data-hono-decks-toc aria-label="Slide navigation"><ol>${items}</ol></nav>`;
|
|
795
|
+
}
|
|
796
|
+
function escapeHtml2(value) {
|
|
797
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
798
|
+
}
|
|
799
|
+
function safeFilename(value) {
|
|
800
|
+
const normalized = value.trim().replaceAll(/[^a-zA-Z0-9._-]+/g, "-").replaceAll(/^-+|-+$/g, "");
|
|
801
|
+
return normalized || "deck";
|
|
802
|
+
}
|
|
803
|
+
function buildViewerControlDefaults(context) {
|
|
804
|
+
return {
|
|
805
|
+
back: { type: "default", key: "back" },
|
|
806
|
+
previous: { type: "default", key: "previous" },
|
|
807
|
+
position: { type: "default", key: "position" },
|
|
808
|
+
next: { type: "default", key: "next" },
|
|
809
|
+
fullscreen: { type: "default", key: "fullscreen" },
|
|
810
|
+
print: context.meta.availablePages.print ? { type: "default", key: "print" } : null,
|
|
811
|
+
exportPdf: context.meta.availableExports.pdf ? { type: "default", key: "exportPdf" } : null,
|
|
812
|
+
exportPng: context.meta.availableExports.png ? { type: "default", key: "exportPng" } : null
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
function resolveViewerControlItems(options, context) {
|
|
816
|
+
const defaults = buildViewerControlDefaults(context);
|
|
817
|
+
const baseItems = [
|
|
818
|
+
defaults.back,
|
|
819
|
+
defaults.previous,
|
|
820
|
+
defaults.position,
|
|
821
|
+
defaults.next,
|
|
822
|
+
defaults.fullscreen,
|
|
823
|
+
defaults.print,
|
|
824
|
+
defaults.exportPdf,
|
|
825
|
+
defaults.exportPng
|
|
826
|
+
];
|
|
827
|
+
const items = typeof options.items === "function" ? options.items(defaults, context) : options.items ?? [
|
|
828
|
+
...resolveViewerControlSlotItems(options.before, context),
|
|
829
|
+
...filterHiddenDefaultItems(baseItems, options.hidden),
|
|
830
|
+
...resolveViewerControlSlotItems(options.after, context)
|
|
831
|
+
];
|
|
832
|
+
return items.filter((item) => Boolean(item)).map((item) => applyViewerControlLabels(item, options.labels));
|
|
833
|
+
}
|
|
834
|
+
function renderViewerControlItem(item, options, context) {
|
|
835
|
+
const renderDefault = () => renderBaseViewerControlItem(item, options, context);
|
|
836
|
+
return options.renderItem?.(item, context, renderDefault) ?? renderDefault();
|
|
837
|
+
}
|
|
838
|
+
function renderBaseViewerControlItem(item, options, context) {
|
|
839
|
+
if (item.type === "link") {
|
|
840
|
+
const ariaLabel = item.icon ? String(item.attributes?.["aria-label"] ?? item.label) : void 0;
|
|
841
|
+
return jsx3("a", {
|
|
842
|
+
...linkAttributes(item.attributes),
|
|
843
|
+
...safeHref(item.href) ? { href: safeHref(item.href) } : {},
|
|
844
|
+
...item.download ? { download: item.download } : {},
|
|
845
|
+
...classAttribute(options.itemClassName, item.className),
|
|
846
|
+
...item.icon ? { "aria-label": ariaLabel, title: ariaLabel } : {},
|
|
847
|
+
children: item.icon ? renderControlIcon(item.icon) : item.label
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
if (item.type === "render") {
|
|
851
|
+
return item.render({ context });
|
|
852
|
+
}
|
|
853
|
+
return renderDefaultViewerControlItem(item, options, context);
|
|
854
|
+
}
|
|
855
|
+
function renderDefaultViewerControlItem(item, options, context) {
|
|
856
|
+
const itemProps = {
|
|
857
|
+
...linkAttributes(item.attributes),
|
|
858
|
+
...classAttribute(options.itemClassName, item.className)
|
|
859
|
+
};
|
|
860
|
+
switch (item.key) {
|
|
861
|
+
case "back":
|
|
862
|
+
return jsx3("a", {
|
|
863
|
+
...itemProps,
|
|
864
|
+
href: context.mountPath,
|
|
865
|
+
"data-hono-decks-back-link": true,
|
|
866
|
+
...item.label === void 0 ? { "aria-label": controlIconLabel("deck-list"), title: controlIconLabel("deck-list") } : {},
|
|
867
|
+
children: item.label ?? renderControlIcon("deck-list")
|
|
868
|
+
});
|
|
869
|
+
case "previous":
|
|
870
|
+
return renderIconButton("previous", item, itemProps);
|
|
871
|
+
case "position":
|
|
872
|
+
return jsx3("span", {
|
|
873
|
+
...itemProps,
|
|
874
|
+
"data-slide-position": true,
|
|
875
|
+
"data-hono-decks-position": true,
|
|
876
|
+
children: item.label ?? "1 / ?"
|
|
877
|
+
});
|
|
878
|
+
case "next":
|
|
879
|
+
return renderIconButton("next", item, itemProps);
|
|
880
|
+
case "fullscreen":
|
|
881
|
+
return renderIconButton("fullscreen", item, itemProps);
|
|
882
|
+
case "print":
|
|
883
|
+
return jsx3("a", {
|
|
884
|
+
...itemProps,
|
|
885
|
+
href: context.meta.paths.print,
|
|
886
|
+
"data-hono-decks-print": true,
|
|
887
|
+
...item.label === void 0 ? { "aria-label": controlIconLabel("print"), title: controlIconLabel("print") } : {},
|
|
888
|
+
children: item.label ?? renderControlIcon("print")
|
|
889
|
+
});
|
|
890
|
+
case "exportPdf":
|
|
891
|
+
return jsx3("a", {
|
|
892
|
+
...itemProps,
|
|
893
|
+
href: context.meta.paths.exportPdf,
|
|
894
|
+
download: `${safeFilename(context.meta.title)}.pdf`,
|
|
895
|
+
"data-hono-decks-export": "pdf",
|
|
896
|
+
...item.label === void 0 ? { "aria-label": controlIconLabel("export-pdf"), title: controlIconLabel("export-pdf") } : {},
|
|
897
|
+
children: item.label ?? renderControlIcon("export-pdf")
|
|
898
|
+
});
|
|
899
|
+
case "exportPng":
|
|
900
|
+
return jsx3("a", {
|
|
901
|
+
...itemProps,
|
|
902
|
+
href: context.meta.paths.exportPng,
|
|
903
|
+
download: `${safeFilename(context.meta.title)}.png`,
|
|
904
|
+
"data-hono-decks-export": "png",
|
|
905
|
+
...item.label === void 0 ? { "aria-label": controlIconLabel("export-png"), title: controlIconLabel("export-png") } : {},
|
|
906
|
+
children: item.label ?? renderControlIcon("export-png")
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
function renderIconButton(action, item, itemProps) {
|
|
911
|
+
const iconName = controlIconNameForKey(action);
|
|
912
|
+
const label = controlIconLabel(iconName);
|
|
913
|
+
return jsx3("button", {
|
|
914
|
+
...itemProps,
|
|
915
|
+
type: "button",
|
|
916
|
+
"data-action": action,
|
|
917
|
+
"data-hono-decks-navigation-control": action,
|
|
918
|
+
...item.label === void 0 ? { "aria-label": label, title: label } : {},
|
|
919
|
+
children: item.label ?? renderControlIcon(iconName)
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
function controlIconNameForKey(key) {
|
|
923
|
+
switch (key) {
|
|
924
|
+
case "back":
|
|
925
|
+
return "deck-list";
|
|
926
|
+
case "previous":
|
|
927
|
+
return "previous";
|
|
928
|
+
case "next":
|
|
929
|
+
return "next";
|
|
930
|
+
case "fullscreen":
|
|
931
|
+
return "fullscreen";
|
|
932
|
+
case "print":
|
|
933
|
+
return "print";
|
|
934
|
+
case "exportPdf":
|
|
935
|
+
return "export-pdf";
|
|
936
|
+
case "exportPng":
|
|
937
|
+
return "export-png";
|
|
938
|
+
case "position":
|
|
939
|
+
return "viewer";
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
function controlsClassName(options) {
|
|
943
|
+
return ["hono-decks-viewer-controls", options.className].filter(Boolean).join(" ");
|
|
944
|
+
}
|
|
945
|
+
function resolveViewerControlSlotItems(items, context) {
|
|
946
|
+
return typeof items === "function" ? items(context) : items ?? [];
|
|
947
|
+
}
|
|
948
|
+
function filterHiddenDefaultItems(items, hidden) {
|
|
949
|
+
if (!hidden?.length) return items;
|
|
950
|
+
const hiddenKeys = new Set(hidden);
|
|
951
|
+
return items.filter((item) => !item || item.type !== "default" || !hiddenKeys.has(item.key));
|
|
952
|
+
}
|
|
953
|
+
function applyViewerControlLabels(item, labels) {
|
|
954
|
+
if (item.type !== "default" || item.label !== void 0) return item;
|
|
955
|
+
const label = labels?.[item.key];
|
|
956
|
+
return label === void 0 ? item : { ...item, label };
|
|
957
|
+
}
|
|
958
|
+
function linkAttributes(attributes) {
|
|
959
|
+
const props = {};
|
|
960
|
+
for (const [name, value] of Object.entries(attributes ?? {})) {
|
|
961
|
+
if (!isSafeAttributeName(name) || value === false || value === void 0) continue;
|
|
962
|
+
props[name] = value;
|
|
963
|
+
}
|
|
964
|
+
return props;
|
|
965
|
+
}
|
|
966
|
+
function controlAttributes(attributes) {
|
|
967
|
+
const props = linkAttributes(attributes);
|
|
968
|
+
delete props.class;
|
|
969
|
+
delete props["aria-label"];
|
|
970
|
+
delete props["data-hono-decks-viewer-controls"];
|
|
971
|
+
return props;
|
|
972
|
+
}
|
|
973
|
+
function classAttribute(...classNames) {
|
|
974
|
+
const className = classNames.filter(Boolean).join(" ");
|
|
975
|
+
return className ? { class: className } : {};
|
|
976
|
+
}
|
|
977
|
+
function isSafeAttributeName(value) {
|
|
978
|
+
return /^[A-Za-z_:][A-Za-z0-9:_.-]*$/.test(value) && !/^on/i.test(value);
|
|
979
|
+
}
|
|
980
|
+
function safeHref(value) {
|
|
981
|
+
const trimmed = value.trim();
|
|
982
|
+
if (trimmed.length === 0) return void 0;
|
|
983
|
+
if (/^(https?:|mailto:|tel:)/i.test(trimmed)) return value;
|
|
984
|
+
if (/^(javascript:|data:|vbscript:)/i.test(trimmed)) return void 0;
|
|
985
|
+
if (/^[^/?#.]+:/i.test(trimmed)) return void 0;
|
|
986
|
+
return value;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// src/server/define-decks.ts
|
|
990
|
+
function defineDecksConfig(config) {
|
|
991
|
+
return config;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// src/source/r2-assets.ts
|
|
995
|
+
function withR2Assets(source, options) {
|
|
996
|
+
return {
|
|
997
|
+
async listDecks(c) {
|
|
998
|
+
return source.listDecks(c);
|
|
999
|
+
},
|
|
1000
|
+
async getCompiledDeck(c, slug) {
|
|
1001
|
+
return source.getCompiledDeck(c, slug);
|
|
1002
|
+
},
|
|
1003
|
+
async getAsset(c, slug, assetPath) {
|
|
1004
|
+
const deck = await source.getCompiledDeck(c, slug);
|
|
1005
|
+
const asset = deck ? findRoutableAsset(deck.assets, slug, assetPath) : void 0;
|
|
1006
|
+
const bucket = asset ? await resolveBucket(options.bucket, c) : void 0;
|
|
1007
|
+
if (deck && asset && bucket) {
|
|
1008
|
+
const object = await bucket.get(resolveR2Key({ deck, asset, slug, assetPath }, options));
|
|
1009
|
+
if (object) {
|
|
1010
|
+
const response = await responseFromR2Object(object, { deck, asset, slug, assetPath }, options);
|
|
1011
|
+
if (response) return response;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
return source.getAsset?.(c, slug, assetPath) ?? null;
|
|
1015
|
+
}
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
async function resolveBucket(bucket, c) {
|
|
1019
|
+
return typeof bucket === "function" ? bucket(c) : bucket;
|
|
1020
|
+
}
|
|
1021
|
+
function findRoutableAsset(assets, slug, assetPath) {
|
|
1022
|
+
const normalized = assetPath.replace(/^\/+/, "");
|
|
1023
|
+
return assets.find((asset) => {
|
|
1024
|
+
if (asset.type !== "local" && asset.type !== "r2") return false;
|
|
1025
|
+
const suffix = `/${slug}/assets/${normalized}`;
|
|
1026
|
+
return asset.publicPath.endsWith(suffix);
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
function resolveR2Key(input, options) {
|
|
1030
|
+
if (options.key) return options.key(input);
|
|
1031
|
+
if (input.asset.r2Key) return input.asset.r2Key;
|
|
1032
|
+
const key = input.asset.sourcePath || `${input.slug}/assets/${input.assetPath}`;
|
|
1033
|
+
const prefix = options.keyPrefix?.replace(/\/+$/, "");
|
|
1034
|
+
return prefix ? `${prefix}/${key.replace(/^\/+/, "")}` : key.replace(/^\/+/, "");
|
|
1035
|
+
}
|
|
1036
|
+
async function responseFromR2Object(object, input, options) {
|
|
1037
|
+
const body = object.body ?? (object.arrayBuffer ? await object.arrayBuffer() : void 0);
|
|
1038
|
+
if (body == null) return null;
|
|
1039
|
+
const headers = new Headers();
|
|
1040
|
+
object.writeHttpMetadata?.(headers);
|
|
1041
|
+
applyHttpMetadata(headers, object.httpMetadata);
|
|
1042
|
+
if (!headers.has("content-type") && input.asset.contentType) headers.set("content-type", input.asset.contentType);
|
|
1043
|
+
const cacheControl = resolveCacheControl(input, options);
|
|
1044
|
+
if (cacheControl) {
|
|
1045
|
+
headers.set("cache-control", cacheControl);
|
|
1046
|
+
} else if (cacheControl !== false && input.asset.cacheControl && !headers.has("cache-control")) {
|
|
1047
|
+
headers.set("cache-control", input.asset.cacheControl);
|
|
1048
|
+
}
|
|
1049
|
+
return new Response(body, { headers });
|
|
1050
|
+
}
|
|
1051
|
+
function applyHttpMetadata(headers, metadata) {
|
|
1052
|
+
if (!metadata) return;
|
|
1053
|
+
if (metadata.contentType && !headers.has("content-type")) headers.set("content-type", metadata.contentType);
|
|
1054
|
+
if (metadata.cacheControl && !headers.has("cache-control")) headers.set("cache-control", metadata.cacheControl);
|
|
1055
|
+
if (metadata.contentDisposition && !headers.has("content-disposition")) {
|
|
1056
|
+
headers.set("content-disposition", metadata.contentDisposition);
|
|
1057
|
+
}
|
|
1058
|
+
if (metadata.contentEncoding && !headers.has("content-encoding")) headers.set("content-encoding", metadata.contentEncoding);
|
|
1059
|
+
if (metadata.contentLanguage && !headers.has("content-language")) headers.set("content-language", metadata.contentLanguage);
|
|
1060
|
+
}
|
|
1061
|
+
function resolveCacheControl(input, options) {
|
|
1062
|
+
return typeof options.cacheControl === "function" ? options.cacheControl(input) : options.cacheControl;
|
|
1063
|
+
}
|
|
1064
|
+
export {
|
|
1065
|
+
SLIDE_TRANSITIONS,
|
|
1066
|
+
createDeckPaths,
|
|
1067
|
+
createDeckViewerEmbed,
|
|
1068
|
+
defineDecksConfig,
|
|
1069
|
+
defineSlideComponents,
|
|
1070
|
+
withR2Assets
|
|
1071
|
+
};
|