hono-decks 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/advanced.d.ts +603 -46
- package/dist/advanced.js +2011 -1907
- package/dist/bin.d.ts +1 -1
- package/dist/bin.js +1763 -1910
- package/dist/cli.d.ts +16 -15
- package/dist/cli.js +1766 -1915
- package/dist/client.d.ts +14 -8
- package/dist/client.js +13 -16
- package/dist/mod.d.ts +512 -27
- package/dist/mod.js +797 -759
- package/dist/node.d.ts +670 -111
- package/dist/node.js +3756 -3937
- package/dist/vite.d.ts +8 -8
- package/dist/vite.js +1646 -1805
- package/package.json +3 -3
- package/dist/define-decks-U4NxIs66.d.ts +0 -587
- package/dist/jsx-renderer-BO-N4tMZ.d.ts +0 -20
package/dist/advanced.js
CHANGED
|
@@ -1,719 +1,692 @@
|
|
|
1
|
-
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { raw } from "hono/html";
|
|
3
|
+
import { jsx } from "hono/jsx/jsx-runtime";
|
|
4
|
+
import { HtmlEscapedCallbackPhase, resolveCallback } from "hono/utils/html";
|
|
5
|
+
//#region src/source/manifest-source.ts
|
|
2
6
|
function manifestDeckSource(manifest) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
};
|
|
7
|
+
const decks = new Map(manifest.decks.map((deck) => [deck.slug, deck]));
|
|
8
|
+
return {
|
|
9
|
+
async listDecks() {
|
|
10
|
+
return manifest.decks.map((deck) => ({
|
|
11
|
+
slug: deck.slug,
|
|
12
|
+
title: deck.meta.title,
|
|
13
|
+
description: deck.meta.description,
|
|
14
|
+
draft: deck.meta.draft,
|
|
15
|
+
sourcePath: deck.sourcePath
|
|
16
|
+
}));
|
|
17
|
+
},
|
|
18
|
+
async getCompiledDeck(_c, slug) {
|
|
19
|
+
return decks.get(slug) ?? null;
|
|
20
|
+
},
|
|
21
|
+
async getAsset(_c, slug, assetPath) {
|
|
22
|
+
const deck = decks.get(slug);
|
|
23
|
+
if (!deck) return null;
|
|
24
|
+
const asset = findLocalAsset(deck.assets, slug, assetPath);
|
|
25
|
+
if (!asset || asset.body == null) return null;
|
|
26
|
+
return new Response(asset.body, { headers: localAssetHeaders(asset) });
|
|
27
|
+
}
|
|
28
|
+
};
|
|
27
29
|
}
|
|
28
30
|
function localAssetHeaders(asset) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
const headers = {};
|
|
32
|
+
if (asset.contentType) headers["content-type"] = asset.contentType;
|
|
33
|
+
if (asset.cacheControl !== false) headers["cache-control"] = asset.cacheControl ?? "public, max-age=300";
|
|
34
|
+
return headers;
|
|
33
35
|
}
|
|
34
36
|
function findLocalAsset(assets, slug, assetPath) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"slide-right",
|
|
54
|
-
"slide-up",
|
|
55
|
-
"slide-down",
|
|
56
|
-
"view-transition"
|
|
37
|
+
const normalized = assetPath.replace(/^\/+/, "");
|
|
38
|
+
return assets.find((asset) => {
|
|
39
|
+
if (asset.type !== "local") return false;
|
|
40
|
+
const suffix = `/${slug}/assets/${normalized}`;
|
|
41
|
+
return asset.publicPath.endsWith(suffix);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/deck/model.ts
|
|
46
|
+
const SLIDE_TRANSITIONS = [
|
|
47
|
+
"none",
|
|
48
|
+
"fade",
|
|
49
|
+
"fade-out",
|
|
50
|
+
"slide-left",
|
|
51
|
+
"slide-right",
|
|
52
|
+
"slide-up",
|
|
53
|
+
"slide-down",
|
|
54
|
+
"view-transition"
|
|
57
55
|
];
|
|
58
56
|
var RenderError = class extends Error {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
code;
|
|
58
|
+
cause;
|
|
59
|
+
constructor(message, code, cause) {
|
|
60
|
+
super(message);
|
|
61
|
+
this.code = code;
|
|
62
|
+
this.cause = cause;
|
|
63
|
+
this.name = "RenderError";
|
|
64
|
+
}
|
|
65
65
|
};
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/renderer/asset-rewrite.ts
|
|
68
68
|
function rewriteLocalAssetUrls(html, assets) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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$1(localAssets, value);
|
|
73
|
+
return asset ? `${attr}="${escapeHtml$8(asset.publicPath)}"` : match;
|
|
74
|
+
}).replace(/<dd>([^<]+)<\/dd>/g, (match, value) => {
|
|
75
|
+
const asset = findAssetForHtmlUrl$1(localAssets, decodeHtml(value));
|
|
76
|
+
return asset ? `<dd>${escapeHtml$8(asset.publicPath)}</dd>` : match;
|
|
77
|
+
});
|
|
78
78
|
}
|
|
79
79
|
function backgroundStyle(value, assets) {
|
|
80
|
-
|
|
81
|
-
const url = asset?.publicPath ?? value;
|
|
82
|
-
return `background-image:url("${escapeCssUrl(url)}")`;
|
|
80
|
+
return `background-image:url("${escapeCssUrl(findAssetForHtmlUrl$1(assets.filter((candidate) => candidate.type === "local"), value)?.publicPath ?? value)}")`;
|
|
83
81
|
}
|
|
84
|
-
function findAssetForHtmlUrl(assets, value) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
82
|
+
function findAssetForHtmlUrl$1(assets, value) {
|
|
83
|
+
const normalized = decodeURIComponent(value).replace(/^\.?\//, "");
|
|
84
|
+
return assets.find((asset) => {
|
|
85
|
+
const assetPath = localAssetRelativePath$1(asset);
|
|
86
|
+
return normalized === assetPath || normalized === `assets/${assetPath}`;
|
|
87
|
+
});
|
|
90
88
|
}
|
|
91
|
-
function localAssetRelativePath(asset) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
89
|
+
function localAssetRelativePath$1(asset) {
|
|
90
|
+
const marker = "/assets/";
|
|
91
|
+
const normalized = asset.sourcePath.replaceAll("\\", "/");
|
|
92
|
+
const markerIndex = normalized.indexOf(marker);
|
|
93
|
+
return markerIndex === -1 ? normalized.split("/").at(-1) ?? normalized : normalized.slice(markerIndex + 8);
|
|
96
94
|
}
|
|
97
95
|
function escapeCssUrl(value) {
|
|
98
|
-
|
|
96
|
+
return value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"");
|
|
99
97
|
}
|
|
100
|
-
function escapeHtml(value) {
|
|
101
|
-
|
|
98
|
+
function escapeHtml$8(value) {
|
|
99
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
102
100
|
}
|
|
103
101
|
function decodeHtml(value) {
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
102
|
+
return value.replaceAll(""", "\"").replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&");
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region src/renderer/jsx-renderer.ts
|
|
106
|
+
const builtInSlideComponents = defineSlideComponents({
|
|
107
|
+
Fire: (props) => {
|
|
108
|
+
if (props.order !== void 0) throw new Error("The Fire \"order\" prop is not supported. Fires reveal in source order.");
|
|
109
|
+
const effect = stringProp(props.effect);
|
|
110
|
+
const at = fireAtProp(props.at);
|
|
111
|
+
return jsx("div", {
|
|
112
|
+
"data-hono-decks-fire": true,
|
|
113
|
+
...at ? { "data-fire-at": at } : {},
|
|
114
|
+
...effect ? { "data-fire-effect": safeToken(effect).toLowerCase() } : {},
|
|
115
|
+
children: props.children
|
|
116
|
+
});
|
|
117
|
+
},
|
|
118
|
+
Hero: (props) => jsx("section", {
|
|
119
|
+
class: `mdx-hero${props.featured === true ? " is-featured" : ""}${props.image || props.src ? " has-image" : ""}`,
|
|
120
|
+
"data-component": "Hero",
|
|
121
|
+
children: [heroImage(props), jsx("div", {
|
|
122
|
+
class: "mdx-hero-copy",
|
|
123
|
+
children: [
|
|
124
|
+
typeof props.eyebrow === "string" && props.eyebrow ? jsx("p", {
|
|
125
|
+
class: "mdx-hero-eyebrow",
|
|
126
|
+
children: props.eyebrow
|
|
127
|
+
}) : "",
|
|
128
|
+
typeof props.title === "string" && props.title ? jsx("h1", { children: props.title }) : "",
|
|
129
|
+
typeof props.subtitle === "string" && props.subtitle ? jsx("p", {
|
|
130
|
+
class: "mdx-hero-subtitle",
|
|
131
|
+
children: props.subtitle
|
|
132
|
+
}) : typeof props.description === "string" && props.description ? jsx("p", {
|
|
133
|
+
class: "mdx-hero-subtitle",
|
|
134
|
+
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: [filename ? jsx("figcaption", {
|
|
151
|
+
class: "hono-decks-code-caption",
|
|
152
|
+
children: filename
|
|
153
|
+
}) : "", highlightedHtml ? jsx("div", {
|
|
154
|
+
class: "hono-decks-code-highlight",
|
|
155
|
+
dangerouslySetInnerHTML: { __html: highlightedHtml }
|
|
156
|
+
}) : jsx("pre", { children: jsx("code", {
|
|
157
|
+
...lang ? {
|
|
158
|
+
class: `language-${safeToken(lang)}`,
|
|
159
|
+
"data-lang": lang
|
|
160
|
+
} : {},
|
|
161
|
+
children: code
|
|
162
|
+
}) })]
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
EmbedFrame: (props) => {
|
|
166
|
+
const src = stringProp(props.src);
|
|
167
|
+
const provider = stringProp(props.provider)?.trim().toLowerCase();
|
|
168
|
+
const title = stringProp(props.title) ?? "Embedded content";
|
|
169
|
+
const aspectRatio = stringProp(props.aspectRatio) ?? "16 / 9";
|
|
170
|
+
const loading = stringProp(props.loading) ?? "lazy";
|
|
171
|
+
const allow = stringProp(props.allow) ?? "fullscreen; picture-in-picture";
|
|
172
|
+
const sandbox = props.sandbox === false ? void 0 : stringProp(props.sandbox) ?? "allow-scripts allow-same-origin allow-presentation allow-popups";
|
|
173
|
+
const referrerPolicy = stringProp(props.referrerPolicy ?? props.referrerpolicy) ?? "strict-origin-when-cross-origin";
|
|
174
|
+
const fallbackHref = stringProp(props.fallbackHref ?? props.fallbackUrl ?? props.href) ?? src;
|
|
175
|
+
const fallback = codeBlockText(props.children) || "Open embed";
|
|
176
|
+
const printPoster = provider === "youtube" ? youtubePrintPoster(src) : void 0;
|
|
177
|
+
return jsx("figure", {
|
|
178
|
+
class: "hono-decks-embed-frame",
|
|
179
|
+
"data-component": "EmbedFrame",
|
|
180
|
+
...provider ? { "data-provider": safeToken(provider).toLowerCase() } : {},
|
|
181
|
+
children: [jsx("div", {
|
|
182
|
+
class: "hono-decks-embed-viewport",
|
|
183
|
+
style: `aspect-ratio:${safeAspectRatio(aspectRatio)}`,
|
|
184
|
+
children: [src ? jsx("iframe", {
|
|
185
|
+
src,
|
|
186
|
+
title,
|
|
187
|
+
loading,
|
|
188
|
+
referrerpolicy: referrerPolicy,
|
|
189
|
+
...sandbox ? { sandbox } : {},
|
|
190
|
+
...allow ? { allow } : {},
|
|
191
|
+
allowfullscreen: true
|
|
192
|
+
}) : "", fallbackHref ? jsx("a", {
|
|
193
|
+
class: "hono-decks-embed-print-fallback",
|
|
194
|
+
href: fallbackHref,
|
|
195
|
+
children: [printPoster ? jsx("img", {
|
|
196
|
+
class: "hono-decks-embed-print-poster",
|
|
197
|
+
src: printPoster,
|
|
198
|
+
alt: `${title} thumbnail`
|
|
199
|
+
}) : "", jsx("span", { children: fallback })]
|
|
200
|
+
}) : ""]
|
|
201
|
+
}), src ? jsx("figcaption", {
|
|
202
|
+
class: "hono-decks-embed-fallback",
|
|
203
|
+
children: jsx("a", {
|
|
204
|
+
href: fallbackHref,
|
|
205
|
+
target: "_blank",
|
|
206
|
+
rel: "noreferrer",
|
|
207
|
+
children: fallback
|
|
208
|
+
})
|
|
209
|
+
}) : ""]
|
|
210
|
+
});
|
|
211
|
+
},
|
|
212
|
+
SocialEmbed: (props) => {
|
|
213
|
+
const href = stringProp(props.href ?? props.url);
|
|
214
|
+
const provider = stringProp(props.provider ?? props.service);
|
|
215
|
+
const author = stringProp(props.author);
|
|
216
|
+
const label = stringProp(props.label) ?? (provider ? `Open on ${provider}` : "Open social post");
|
|
217
|
+
const quote = codeBlockText(props.children);
|
|
218
|
+
return jsx("figure", {
|
|
219
|
+
class: "hono-decks-social-embed",
|
|
220
|
+
"data-component": "SocialEmbed",
|
|
221
|
+
...provider ? { "data-provider": safeToken(provider).toLowerCase() } : {},
|
|
222
|
+
children: jsx("blockquote", {
|
|
223
|
+
class: "hono-decks-social-card",
|
|
224
|
+
...href ? { cite: href } : {},
|
|
225
|
+
children: [quote ? jsx("p", { children: quote }) : "", jsx("footer", { children: [author ? jsx("span", {
|
|
226
|
+
class: "hono-decks-social-author",
|
|
227
|
+
children: author
|
|
228
|
+
}) : "", href ? jsx("a", {
|
|
229
|
+
href,
|
|
230
|
+
target: "_blank",
|
|
231
|
+
rel: "noreferrer",
|
|
232
|
+
children: label
|
|
233
|
+
}) : ""] })]
|
|
234
|
+
})
|
|
235
|
+
});
|
|
236
|
+
},
|
|
237
|
+
TweetEmbed: (props) => {
|
|
238
|
+
const href = stringProp(props.href ?? props.url);
|
|
239
|
+
const label = stringProp(props.label) ?? "Open post on X";
|
|
240
|
+
return jsx("figure", {
|
|
241
|
+
class: "hono-decks-tweet-embed",
|
|
242
|
+
"data-component": "TweetEmbed",
|
|
243
|
+
children: [
|
|
244
|
+
jsx("blockquote", {
|
|
245
|
+
class: "twitter-tweet",
|
|
246
|
+
"data-dnt": "true",
|
|
247
|
+
children: href ? jsx("a", {
|
|
248
|
+
href,
|
|
249
|
+
target: "_blank",
|
|
250
|
+
rel: "noreferrer",
|
|
251
|
+
children: label
|
|
252
|
+
}) : label
|
|
253
|
+
}),
|
|
254
|
+
href ? jsx("script", {
|
|
255
|
+
async: true,
|
|
256
|
+
src: "https://platform.twitter.com/widgets.js",
|
|
257
|
+
charset: "utf-8"
|
|
258
|
+
}) : "",
|
|
259
|
+
href ? jsx("a", {
|
|
260
|
+
class: "hono-decks-tweet-print-fallback",
|
|
261
|
+
href,
|
|
262
|
+
children: label
|
|
263
|
+
}) : ""
|
|
264
|
+
]
|
|
265
|
+
});
|
|
266
|
+
},
|
|
267
|
+
LinkCard: (props) => {
|
|
268
|
+
const href = stringProp(props.href ?? props.url);
|
|
269
|
+
const title = stringProp(props.title) ?? href ?? "Link";
|
|
270
|
+
const description = stringProp(props.description);
|
|
271
|
+
const image = stringProp(props.image ?? props.imageUrl);
|
|
272
|
+
const siteName = stringProp(props.siteName ?? props.site);
|
|
273
|
+
const label = codeBlockText(props.children) || "Open link";
|
|
274
|
+
return jsx("figure", {
|
|
275
|
+
class: "hono-decks-link-card",
|
|
276
|
+
"data-component": "LinkCard",
|
|
277
|
+
children: href ? jsx("a", {
|
|
278
|
+
class: "hono-decks-link-card-anchor",
|
|
279
|
+
href,
|
|
280
|
+
target: "_blank",
|
|
281
|
+
rel: "noreferrer",
|
|
282
|
+
children: [image ? jsx("img", {
|
|
283
|
+
class: "hono-decks-link-card-image",
|
|
284
|
+
src: image,
|
|
285
|
+
alt: title
|
|
286
|
+
}) : "", jsx("span", {
|
|
287
|
+
class: "hono-decks-link-card-body",
|
|
288
|
+
children: [
|
|
289
|
+
siteName ? jsx("span", {
|
|
290
|
+
class: "hono-decks-link-card-site",
|
|
291
|
+
children: siteName
|
|
292
|
+
}) : "",
|
|
293
|
+
jsx("span", {
|
|
294
|
+
class: "hono-decks-link-card-title",
|
|
295
|
+
children: title
|
|
296
|
+
}),
|
|
297
|
+
description ? jsx("span", {
|
|
298
|
+
class: "hono-decks-link-card-description",
|
|
299
|
+
children: description
|
|
300
|
+
}) : "",
|
|
301
|
+
jsx("span", {
|
|
302
|
+
class: "hono-decks-link-card-label",
|
|
303
|
+
children: label
|
|
304
|
+
})
|
|
305
|
+
]
|
|
306
|
+
})]
|
|
307
|
+
}) : [
|
|
308
|
+
image ? jsx("img", {
|
|
309
|
+
class: "hono-decks-link-card-image",
|
|
310
|
+
src: image,
|
|
311
|
+
alt: title
|
|
312
|
+
}) : "",
|
|
313
|
+
siteName ? jsx("span", {
|
|
314
|
+
class: "hono-decks-link-card-site",
|
|
315
|
+
children: siteName
|
|
316
|
+
}) : "",
|
|
317
|
+
jsx("span", {
|
|
318
|
+
class: "hono-decks-link-card-title",
|
|
319
|
+
children: title
|
|
320
|
+
}),
|
|
321
|
+
description ? jsx("span", {
|
|
322
|
+
class: "hono-decks-link-card-description",
|
|
323
|
+
children: description
|
|
324
|
+
}) : ""
|
|
325
|
+
]
|
|
326
|
+
});
|
|
327
|
+
}
|
|
310
328
|
});
|
|
311
329
|
function defineSlideComponents(input) {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
316
|
-
return registry;
|
|
330
|
+
const registry = {};
|
|
331
|
+
for (const [name, value] of Object.entries(input)) registry[name] = typeof value === "function" ? { component: value } : value;
|
|
332
|
+
return registry;
|
|
317
333
|
}
|
|
318
334
|
function createMdxComponents(registry, input = {}) {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
}
|
|
326
|
-
return components;
|
|
335
|
+
const components = {
|
|
336
|
+
img: (props) => jsx("img", rewriteAssetProps(props, input.assets)),
|
|
337
|
+
a: (props) => jsx("a", rewriteAssetProps(props, input.assets))
|
|
338
|
+
};
|
|
339
|
+
for (const [name, definition] of Object.entries(registry)) components[name] = (props = {}) => renderRegisteredComponent(name, definition, props, props.children, input.assets);
|
|
340
|
+
return components;
|
|
327
341
|
}
|
|
328
342
|
function heroImage(props) {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
343
|
+
const image = typeof props.image === "string" ? props.image : typeof props.src === "string" ? props.src : void 0;
|
|
344
|
+
if (!image) return "";
|
|
345
|
+
return jsx("img", {
|
|
346
|
+
class: "mdx-hero-image",
|
|
347
|
+
src: image,
|
|
348
|
+
alt: typeof props.alt === "string" && props.alt ? props.alt : typeof props.title === "string" ? props.title : "Hero image"
|
|
349
|
+
});
|
|
333
350
|
}
|
|
334
351
|
function stringProp(value) {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
return void 0;
|
|
352
|
+
if (typeof value === "string") return value;
|
|
353
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
338
354
|
}
|
|
339
355
|
function fireAtProp(value) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
356
|
+
if (value === void 0 || value === null) return void 0;
|
|
357
|
+
if (typeof value === "number") {
|
|
358
|
+
if (Number.isInteger(value) && value >= 0) return String(value);
|
|
359
|
+
throw new Error("The Fire \"at\" prop accepts a non-negative integer or a relative string such as \"+1\".");
|
|
360
|
+
}
|
|
361
|
+
if (typeof value === "string") {
|
|
362
|
+
const at = value.trim();
|
|
363
|
+
if (/^(?:\d+|[+-]\d+)$/.test(at)) return at;
|
|
364
|
+
}
|
|
365
|
+
throw new Error("The Fire \"at\" prop accepts a non-negative integer or a relative string such as \"+1\".");
|
|
350
366
|
}
|
|
351
367
|
function safeToken(value) {
|
|
352
|
-
|
|
368
|
+
return value.trim().replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "text";
|
|
353
369
|
}
|
|
354
370
|
function safeAspectRatio(value) {
|
|
355
|
-
|
|
371
|
+
return /^\s*\d+(\.\d+)?\s*(\/|:)\s*\d+(\.\d+)?\s*$/.test(value) ? value.replace(":", " / ") : "16 / 9";
|
|
356
372
|
}
|
|
357
373
|
function youtubePrintPoster(src) {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
374
|
+
if (!src) return void 0;
|
|
375
|
+
try {
|
|
376
|
+
const url = new URL(src);
|
|
377
|
+
if (![
|
|
378
|
+
"youtube.com",
|
|
379
|
+
"www.youtube.com",
|
|
380
|
+
"youtube-nocookie.com",
|
|
381
|
+
"www.youtube-nocookie.com"
|
|
382
|
+
].includes(url.hostname)) return;
|
|
383
|
+
const match = url.pathname.match(/^\/embed\/([A-Za-z0-9_-]+)$/);
|
|
384
|
+
return match ? `https://i.ytimg.com/vi/${match[1]}/hqdefault.jpg` : void 0;
|
|
385
|
+
} catch {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
369
388
|
}
|
|
370
389
|
function codeBlockText(value) {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
390
|
+
if (Array.isArray(value)) return value.map(codeBlockText).join("");
|
|
391
|
+
if (value === null || value === void 0 || typeof value === "boolean") return "";
|
|
392
|
+
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
393
|
+
if (isJsxValue(value)) return codeBlockText(value.props?.children);
|
|
394
|
+
return String(value);
|
|
376
395
|
}
|
|
377
396
|
function renderSlideNodes(nodes, input = {}) {
|
|
378
|
-
|
|
397
|
+
return normalizeVoidElementSpacing(nodes.map((node) => String(renderSlideNode(node, input))).join(""));
|
|
379
398
|
}
|
|
380
399
|
async function renderSlideNodesAsync(nodes, input = {}) {
|
|
381
|
-
|
|
382
|
-
return normalizeVoidElementSpacing(rendered.join(""));
|
|
400
|
+
return normalizeVoidElementSpacing((await Promise.all(nodes.map((node) => renderJsxValue(renderSlideNode(node, input))))).join(""));
|
|
383
401
|
}
|
|
384
402
|
function renderSlideNode(node, input) {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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
|
+
switch (node.type) {
|
|
404
|
+
case "text": return String(node.value);
|
|
405
|
+
case "code": return jsx("pre", { children: jsx("code", {
|
|
406
|
+
...node.lang ? { "data-lang": node.lang } : {},
|
|
407
|
+
children: node.value
|
|
408
|
+
}) });
|
|
409
|
+
case "element": return jsx(node.tag, {
|
|
410
|
+
...rewriteAssetProps(node.props, input.assets),
|
|
411
|
+
children: node.children.map((child) => renderSlideNode(child, input))
|
|
412
|
+
});
|
|
413
|
+
case "component": return renderComponentNode(node, input);
|
|
414
|
+
}
|
|
403
415
|
}
|
|
404
416
|
function renderComponentNode(node, input) {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
417
|
+
const definition = input.components?.[node.name];
|
|
418
|
+
if (!definition) return renderComponentPlaceholder(node);
|
|
419
|
+
const children = node.children.map((child) => renderSlideNode(child, input));
|
|
420
|
+
return renderRegisteredComponent(node.name, definition, node.props, children, input.assets);
|
|
409
421
|
}
|
|
410
422
|
function renderRegisteredComponent(name, definition, props, children, assets = []) {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
423
|
+
const rewritten = rewriteAssetProps(props, assets);
|
|
424
|
+
const componentProps = stripRuntimeProps(rewritten);
|
|
425
|
+
const element = definition.component({
|
|
426
|
+
...componentProps,
|
|
427
|
+
children
|
|
428
|
+
});
|
|
429
|
+
if (!definition.client && rewritten.client !== true) return element;
|
|
430
|
+
return jsx("div", {
|
|
431
|
+
"data-hono-decks-island": definition.clientId ?? name,
|
|
432
|
+
"data-hono-decks-props": JSON.stringify(serializableProps(name, componentProps)),
|
|
433
|
+
children: element
|
|
434
|
+
});
|
|
420
435
|
}
|
|
421
436
|
async function renderJsxValue(value) {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
437
|
+
const resolved = value instanceof Promise ? await value : value;
|
|
438
|
+
if (typeof resolved === "string") return resolved;
|
|
439
|
+
if (resolved === null || resolved === void 0 || typeof resolved === "boolean") return "";
|
|
440
|
+
if (typeof resolved === "number") return String(resolved);
|
|
441
|
+
return await resolveCallback(resolved, HtmlEscapedCallbackPhase.Stringify, false, {});
|
|
427
442
|
}
|
|
428
443
|
function renderComponentPlaceholder(node) {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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
|
-
});
|
|
444
|
+
return jsx("div", {
|
|
445
|
+
class: "mdx-component",
|
|
446
|
+
"data-component": node.name,
|
|
447
|
+
children: [jsx("strong", { children: `<${node.name} />` }), Object.keys(node.props).length > 0 ? jsx("dl", { children: Object.entries(node.props).map(([key, value]) => jsx("div", { children: [jsx("dt", { children: key }), jsx("dd", { children: String(value) })] })) }) : ""]
|
|
448
|
+
});
|
|
443
449
|
}
|
|
444
450
|
function stripRuntimeProps(props) {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
+
const clean = {};
|
|
452
|
+
for (const [key, value] of Object.entries(props)) {
|
|
453
|
+
if (key === "client" || key === "client:island" || key === "children" || value === void 0) continue;
|
|
454
|
+
clean[key] = value;
|
|
455
|
+
}
|
|
456
|
+
return clean;
|
|
451
457
|
}
|
|
452
458
|
function rewriteAssetProps(props, assets = []) {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
459
|
+
const rewritten = { ...props };
|
|
460
|
+
for (const key of [
|
|
461
|
+
"src",
|
|
462
|
+
"href",
|
|
463
|
+
"image",
|
|
464
|
+
"background"
|
|
465
|
+
]) {
|
|
466
|
+
const value = rewritten[key];
|
|
467
|
+
if (typeof value !== "string") continue;
|
|
468
|
+
const asset = findAssetForHtmlUrl(assets, value);
|
|
469
|
+
if (asset) rewritten[key] = asset.publicPath;
|
|
470
|
+
}
|
|
471
|
+
return rewritten;
|
|
461
472
|
}
|
|
462
473
|
function serializableProps(componentName, props) {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
}
|
|
467
|
-
return result;
|
|
474
|
+
const result = {};
|
|
475
|
+
for (const [key, value] of Object.entries(props)) result[key] = serializeClientIslandProp(value, `${componentName}.${key}`);
|
|
476
|
+
return result;
|
|
468
477
|
}
|
|
469
478
|
function serializeClientIslandProp(value, path) {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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;
|
|
479
|
+
if (value === null) return null;
|
|
480
|
+
if (typeof value === "string" || typeof value === "boolean") return value;
|
|
481
|
+
if (typeof value === "number") {
|
|
482
|
+
if (!Number.isFinite(value)) throw new Error(`Client island prop "${path}" must be JSON-serializable; non-finite numbers are not supported.`);
|
|
483
|
+
return value;
|
|
484
|
+
}
|
|
485
|
+
if (typeof value === "function") throw new Error(`Client island prop "${path}" must be JSON-serializable; functions cannot be passed to the client.`);
|
|
486
|
+
if (value === void 0 || typeof value === "symbol" || typeof value === "bigint") throw new Error(`Client island prop "${path}" must be JSON-serializable; ${typeof value} values are not supported.`);
|
|
487
|
+
if (isJsxValue(value)) throw new Error(`Client island prop "${path}" must be JSON-serializable; JSX values cannot be passed to the client.`);
|
|
488
|
+
if (value instanceof Date) throw new Error(`Client island prop "${path}" must be JSON-serializable; Date values must be converted to strings.`);
|
|
489
|
+
if (Array.isArray(value)) return value.map((item, index) => serializeClientIslandProp(item, `${path}[${index}]`));
|
|
490
|
+
if (!isPlainObject(value)) throw new Error(`Client island prop "${path}" must be JSON-serializable; class instances are not supported.`);
|
|
491
|
+
const result = {};
|
|
492
|
+
for (const [key, item] of Object.entries(value)) result[key] = serializeClientIslandProp(item, `${path}.${key}`);
|
|
493
|
+
return result;
|
|
499
494
|
}
|
|
500
495
|
function isJsxValue(value) {
|
|
501
|
-
|
|
496
|
+
return typeof value === "object" && value !== null && "tag" in value && "props" in value && "type" in value;
|
|
502
497
|
}
|
|
503
498
|
function isPlainObject(value) {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
});
|
|
499
|
+
if (typeof value !== "object" || value === null) return false;
|
|
500
|
+
const prototype = Object.getPrototypeOf(value);
|
|
501
|
+
return prototype === Object.prototype || prototype === null;
|
|
514
502
|
}
|
|
515
|
-
function
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
503
|
+
function findAssetForHtmlUrl(assets, value) {
|
|
504
|
+
const normalized = decodeURIComponent(value).replace(/^\.?\//, "");
|
|
505
|
+
return assets.filter((asset) => asset.type === "local").find((asset) => {
|
|
506
|
+
const assetPath = localAssetRelativePath(asset.sourcePath);
|
|
507
|
+
return normalized === assetPath || normalized === `assets/${assetPath}`;
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
function localAssetRelativePath(sourcePath) {
|
|
511
|
+
const marker = "/assets/";
|
|
512
|
+
const normalized = sourcePath.replaceAll("\\", "/");
|
|
513
|
+
const markerIndex = normalized.indexOf(marker);
|
|
514
|
+
return markerIndex === -1 ? normalized.split("/").at(-1) ?? normalized : normalized.slice(markerIndex + 8);
|
|
520
515
|
}
|
|
521
516
|
function normalizeVoidElementSpacing(html) {
|
|
522
|
-
|
|
517
|
+
return html.replace(/<(img|br|hr)([^>]*)\/>/g, "<$1$2 />");
|
|
523
518
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
import { jsx as jsx2 } from "hono/jsx/jsx-runtime";
|
|
519
|
+
//#endregion
|
|
520
|
+
//#region src/renderer/control-icons.ts
|
|
527
521
|
function renderControlIcon(name, className = "hono-decks-control-icon") {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
522
|
+
return jsx("svg", {
|
|
523
|
+
class: className,
|
|
524
|
+
"data-hono-decks-control-icon": true,
|
|
525
|
+
viewBox: "0 0 24 24",
|
|
526
|
+
"aria-hidden": "true",
|
|
527
|
+
focusable: "false",
|
|
528
|
+
fill: "none",
|
|
529
|
+
"stroke-width": "2",
|
|
530
|
+
"stroke-linecap": "round",
|
|
531
|
+
"stroke-linejoin": "round",
|
|
532
|
+
children: controlIconPaths(name)
|
|
533
|
+
});
|
|
540
534
|
}
|
|
541
535
|
function renderControlIconHtml(name, className = "hono-decks-control-icon") {
|
|
542
|
-
|
|
536
|
+
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
537
|
}
|
|
544
538
|
function controlIconLabel(name) {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
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
|
-
}
|
|
539
|
+
switch (name) {
|
|
540
|
+
case "deck-list": return "Deck list";
|
|
541
|
+
case "home": return "Home";
|
|
542
|
+
case "viewer": return "Viewer";
|
|
543
|
+
case "projection": return "Projection";
|
|
544
|
+
case "presenter": return "Presenter";
|
|
545
|
+
case "previous": return "Previous slide";
|
|
546
|
+
case "next": return "Next slide";
|
|
547
|
+
case "fullscreen": return "Toggle fullscreen";
|
|
548
|
+
case "print": return "Print view";
|
|
549
|
+
case "details": return "Details";
|
|
550
|
+
case "export-pdf": return "Export PDF";
|
|
551
|
+
case "export-png": return "Export PNG";
|
|
552
|
+
}
|
|
571
553
|
}
|
|
572
554
|
function controlIconPathHtml(name) {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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
|
-
}
|
|
555
|
+
switch (name) {
|
|
556
|
+
case "deck-list": return "<path d=\"M4 5h16\" /><path d=\"M4 12h16\" /><path d=\"M4 19h16\" />";
|
|
557
|
+
case "home": return "<path d=\"M3 11l9-8 9 8\" /><path d=\"M5 10v10h14V10\" /><path d=\"M9 20v-6h6v6\" />";
|
|
558
|
+
case "viewer": 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\" />";
|
|
559
|
+
case "projection": return "<path d=\"M4 5h16v10H4z\" /><path d=\"M8 19h8\" /><path d=\"M12 15v4\" />";
|
|
560
|
+
case "presenter": return "<path d=\"M4 5h16v10H4z\" /><path d=\"M8 21l4-6 4 6\" /><path d=\"M9 9h6\" />";
|
|
561
|
+
case "previous": return "<path d=\"M15 6l-6 6 6 6\" />";
|
|
562
|
+
case "next": return "<path d=\"M9 6l6 6-6 6\" />";
|
|
563
|
+
case "fullscreen": 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\" />";
|
|
564
|
+
case "print": 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\" />";
|
|
565
|
+
case "details": return "<circle cx=\"12\" cy=\"12\" r=\"9\" /><path d=\"M12 11v5\" /><path d=\"M12 8h.01\" />";
|
|
566
|
+
case "export-pdf": return "<path d=\"M14 3v4a2 2 0 0 0 2 2h4\" /><path d=\"M5 3h9l6 6v12H5z\" /><path d=\"M8 15h8\" /><path d=\"M8 18h5\" />";
|
|
567
|
+
case "export-png": 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\" />";
|
|
568
|
+
}
|
|
599
569
|
}
|
|
600
570
|
function controlIconPaths(name) {
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
|
|
571
|
+
switch (name) {
|
|
572
|
+
case "deck-list": return [
|
|
573
|
+
jsx("path", { d: "M4 5h16" }),
|
|
574
|
+
jsx("path", { d: "M4 12h16" }),
|
|
575
|
+
jsx("path", { d: "M4 19h16" })
|
|
576
|
+
];
|
|
577
|
+
case "home": return [
|
|
578
|
+
jsx("path", { d: "M3 11l9-8 9 8" }),
|
|
579
|
+
jsx("path", { d: "M5 10v10h14V10" }),
|
|
580
|
+
jsx("path", { d: "M9 20v-6h6v6" })
|
|
581
|
+
];
|
|
582
|
+
case "viewer": return [jsx("path", { d: "M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z" }), jsx("circle", {
|
|
583
|
+
cx: "12",
|
|
584
|
+
cy: "12",
|
|
585
|
+
r: "3"
|
|
586
|
+
})];
|
|
587
|
+
case "projection": return [
|
|
588
|
+
jsx("path", { d: "M4 5h16v10H4z" }),
|
|
589
|
+
jsx("path", { d: "M8 19h8" }),
|
|
590
|
+
jsx("path", { d: "M12 15v4" })
|
|
591
|
+
];
|
|
592
|
+
case "presenter": return [
|
|
593
|
+
jsx("path", { d: "M4 5h16v10H4z" }),
|
|
594
|
+
jsx("path", { d: "M8 21l4-6 4 6" }),
|
|
595
|
+
jsx("path", { d: "M9 9h6" })
|
|
596
|
+
];
|
|
597
|
+
case "previous": return [jsx("path", { d: "M15 6l-6 6 6 6" })];
|
|
598
|
+
case "next": return [jsx("path", { d: "M9 6l6 6-6 6" })];
|
|
599
|
+
case "fullscreen": return [
|
|
600
|
+
jsx("path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }),
|
|
601
|
+
jsx("path", { d: "M16 3h3a2 2 0 0 1 2 2v3" }),
|
|
602
|
+
jsx("path", { d: "M8 21H5a2 2 0 0 1-2-2v-3" }),
|
|
603
|
+
jsx("path", { d: "M16 21h3a2 2 0 0 0 2-2v-3" })
|
|
604
|
+
];
|
|
605
|
+
case "print": return [
|
|
606
|
+
jsx("path", { d: "M6 9V3h12v6" }),
|
|
607
|
+
jsx("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" }),
|
|
608
|
+
jsx("path", { d: "M6 14h12v7H6z" })
|
|
609
|
+
];
|
|
610
|
+
case "details": return [
|
|
611
|
+
jsx("circle", {
|
|
612
|
+
cx: "12",
|
|
613
|
+
cy: "12",
|
|
614
|
+
r: "9"
|
|
615
|
+
}),
|
|
616
|
+
jsx("path", { d: "M12 11v5" }),
|
|
617
|
+
jsx("path", { d: "M12 8h.01" })
|
|
618
|
+
];
|
|
619
|
+
case "export-pdf": return [
|
|
620
|
+
jsx("path", { d: "M14 3v4a2 2 0 0 0 2 2h4" }),
|
|
621
|
+
jsx("path", { d: "M5 3h9l6 6v12H5z" }),
|
|
622
|
+
jsx("path", { d: "M8 15h8" }),
|
|
623
|
+
jsx("path", { d: "M8 18h5" })
|
|
624
|
+
];
|
|
625
|
+
case "export-png": return [
|
|
626
|
+
jsx("rect", {
|
|
627
|
+
x: "4",
|
|
628
|
+
y: "5",
|
|
629
|
+
width: "16",
|
|
630
|
+
height: "14",
|
|
631
|
+
rx: "2"
|
|
632
|
+
}),
|
|
633
|
+
jsx("path", { d: "M8 14l2.5-2.5L14 15l2-2 2 2" }),
|
|
634
|
+
jsx("circle", {
|
|
635
|
+
cx: "9",
|
|
636
|
+
cy: "9",
|
|
637
|
+
r: "1"
|
|
638
|
+
})
|
|
639
|
+
];
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
//#endregion
|
|
643
|
+
//#region src/server/document.ts
|
|
671
644
|
async function resolveDeckDocument(input, options, overrides) {
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
645
|
+
const page = {
|
|
646
|
+
lang: options?.lang,
|
|
647
|
+
nonce: options?.nonce,
|
|
648
|
+
head: options?.head,
|
|
649
|
+
...options?.surfaces?.[input.surface],
|
|
650
|
+
...definedDocumentOptions(overrides)
|
|
651
|
+
};
|
|
652
|
+
const langValue = await resolveDocumentValue(page.lang, input);
|
|
653
|
+
const nonce = await resolveDocumentValue(page.nonce, input);
|
|
654
|
+
const headValue = await resolveDocumentValue(page.head, input);
|
|
655
|
+
return {
|
|
656
|
+
lang: langValue?.trim() || "ja",
|
|
657
|
+
...nonce ? { nonce } : {},
|
|
658
|
+
head: headValue === void 0 || headValue === null || headValue === false ? "" : await renderJsxValue(headValue)
|
|
659
|
+
};
|
|
687
660
|
}
|
|
688
661
|
function documentNonceAttribute(nonce) {
|
|
689
|
-
|
|
662
|
+
return nonce ? ` nonce="${escapeHtml$7(nonce)}"` : "";
|
|
690
663
|
}
|
|
691
664
|
function definedDocumentOptions(options) {
|
|
692
|
-
|
|
693
|
-
|
|
665
|
+
if (!options) return {};
|
|
666
|
+
return Object.fromEntries(Object.entries(options).filter(([, value]) => value !== void 0));
|
|
694
667
|
}
|
|
695
668
|
async function resolveDocumentValue(value, input) {
|
|
696
|
-
|
|
669
|
+
return typeof value === "function" ? await value(input) : await value;
|
|
697
670
|
}
|
|
698
|
-
function
|
|
699
|
-
|
|
671
|
+
function escapeHtml$7(value) {
|
|
672
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
700
673
|
}
|
|
701
|
-
|
|
702
|
-
|
|
674
|
+
//#endregion
|
|
675
|
+
//#region src/renderer/presentation-script.ts
|
|
703
676
|
function renderLiveReloadScript(eventsPath, nonce) {
|
|
704
|
-
|
|
677
|
+
return `<script${documentNonceAttribute(nonce)}>
|
|
705
678
|
(() => {
|
|
706
679
|
const eventsUrl = ${JSON.stringify(eventsPath)};
|
|
707
680
|
const events = new EventSource(eventsUrl);
|
|
708
681
|
events.addEventListener("deck:updated", () => location.reload());
|
|
709
682
|
})();
|
|
710
|
-
|
|
683
|
+
<\/script>`;
|
|
711
684
|
}
|
|
712
685
|
function renderClientEntryScript(clientEntry, nonce) {
|
|
713
|
-
|
|
686
|
+
return `<script type="module" src="${escapeHtml$6(clientEntry)}"${documentNonceAttribute(nonce)}><\/script>`;
|
|
714
687
|
}
|
|
715
688
|
function renderPresentationScript(nonce) {
|
|
716
|
-
|
|
689
|
+
return `<script${documentNonceAttribute(nonce)}>
|
|
717
690
|
(() => {
|
|
718
691
|
const slides = Array.from(document.querySelectorAll(".slide"));
|
|
719
692
|
const stage = document.querySelector("[data-hono-decks-stage]");
|
|
@@ -768,10 +741,10 @@ function renderPresentationScript(nonce) {
|
|
|
768
741
|
const entries = slideFires(slide).map((fire) => {
|
|
769
742
|
const rawAt = fire.getAttribute("data-fire-at");
|
|
770
743
|
let position;
|
|
771
|
-
if (rawAt !== null &&
|
|
744
|
+
if (rawAt !== null && /^\d+$/.test(rawAt)) {
|
|
772
745
|
position = Number(rawAt);
|
|
773
746
|
} else {
|
|
774
|
-
const offset = rawAt !== null && /^[+-]d+$/.test(rawAt) ? Number(rawAt) : 1;
|
|
747
|
+
const offset = rawAt !== null && /^[+-]\d+$/.test(rawAt) ? Number(rawAt) : 1;
|
|
775
748
|
cursor += offset;
|
|
776
749
|
position = cursor;
|
|
777
750
|
}
|
|
@@ -1096,20 +1069,20 @@ function renderPresentationScript(nonce) {
|
|
|
1096
1069
|
const initialState = readInitialState();
|
|
1097
1070
|
show(initialState.index, initialState.stepIndex);
|
|
1098
1071
|
})();
|
|
1099
|
-
|
|
1072
|
+
<\/script>`;
|
|
1100
1073
|
}
|
|
1101
|
-
function
|
|
1102
|
-
|
|
1074
|
+
function escapeHtml$6(value) {
|
|
1075
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
1103
1076
|
}
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1077
|
+
//#endregion
|
|
1078
|
+
//#region src/renderer/presentation-style.ts
|
|
1079
|
+
const PRESENTATION_DESIGN_WIDTH = 1920;
|
|
1080
|
+
const PRESENTATION_DESIGN_HEIGHT = 1080;
|
|
1108
1081
|
function presentationDesignCssVariables() {
|
|
1109
|
-
|
|
1082
|
+
return `--hono-decks-width:${PRESENTATION_DESIGN_WIDTH}px;--hono-decks-height:${PRESENTATION_DESIGN_HEIGHT}px`;
|
|
1110
1083
|
}
|
|
1111
1084
|
function basePresentationStyle() {
|
|
1112
|
-
|
|
1085
|
+
return `
|
|
1113
1086
|
: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
1087
|
html,body{margin:0;width:100%;height:100%;overflow:hidden}
|
|
1115
1088
|
.hono-decks-stage{width:100vw;height:100vh;overflow:hidden;position:relative;display:grid;place-items:center}
|
|
@@ -1204,36 +1177,36 @@ body[data-hono-decks-print-preview] [data-hono-decks-fire]{visibility:visible!im
|
|
|
1204
1177
|
@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
1178
|
@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
1179
|
}
|
|
1207
|
-
|
|
1208
|
-
|
|
1180
|
+
//#endregion
|
|
1181
|
+
//#region src/renderer/presentation-page.ts
|
|
1209
1182
|
function presentationPageTitle(deck) {
|
|
1210
|
-
|
|
1183
|
+
return deck.meta.title ?? deck.slug;
|
|
1211
1184
|
}
|
|
1212
1185
|
function renderCompiledDeckPage(input) {
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
<html lang="${
|
|
1186
|
+
const { deck } = input;
|
|
1187
|
+
const document = input.document ?? {
|
|
1188
|
+
lang: "ja",
|
|
1189
|
+
head: ""
|
|
1190
|
+
};
|
|
1191
|
+
const nonceAttribute = documentNonceAttribute(document.nonce);
|
|
1192
|
+
const warnings = renderWarnings(deck);
|
|
1193
|
+
const htmlAttrs = input.printPreview ? " data-hono-decks-print-preview=\"true\"" : "";
|
|
1194
|
+
const bodyAttrs = [input.printPreview ? "data-hono-decks-print-preview=\"true\"" : "", input.speakerNotes === false ? "data-hono-decks-projection=\"true\"" : ""].filter(Boolean);
|
|
1195
|
+
return `<!doctype html>
|
|
1196
|
+
<html lang="${escapeHtml$5(document.lang)}"${htmlAttrs}>
|
|
1224
1197
|
<head>
|
|
1225
1198
|
<meta charset="utf-8" />
|
|
1226
1199
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1227
|
-
<title>${
|
|
1200
|
+
<title>${escapeHtml$5(presentationPageTitle(deck))}</title>
|
|
1228
1201
|
<style${nonceAttribute}>${basePresentationStyle()}${deck.themeStyle ?? ""}${input.style ?? ""}</style>
|
|
1229
1202
|
${document.head}
|
|
1230
1203
|
</head>
|
|
1231
1204
|
<body${bodyAttrs.length ? ` ${bodyAttrs.join(" ")}` : ""}>
|
|
1232
1205
|
${warnings}
|
|
1233
1206
|
${renderCompiledDeck(deck, {
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1207
|
+
components: mergeComponentInputs(deck.componentRegistry, input.components),
|
|
1208
|
+
speakerNotes: input.speakerNotes
|
|
1209
|
+
})}
|
|
1237
1210
|
${input.printPreview ? renderPrintPreviewScript(document.nonce) : renderPresentationScript(document.nonce)}
|
|
1238
1211
|
${!input.printPreview && input.liveReloadPath ? renderLiveReloadScript(input.liveReloadPath, document.nonce) : ""}
|
|
1239
1212
|
${!input.printPreview && input.clientEntry ? renderClientEntryScript(input.clientEntry, document.nonce) : ""}
|
|
@@ -1241,30 +1214,30 @@ function renderCompiledDeckPage(input) {
|
|
|
1241
1214
|
</html>`;
|
|
1242
1215
|
}
|
|
1243
1216
|
async function renderCompiledDeckPageAsync(input) {
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
<html lang="${
|
|
1217
|
+
const { deck } = input;
|
|
1218
|
+
const document = input.document ?? {
|
|
1219
|
+
lang: "ja",
|
|
1220
|
+
head: ""
|
|
1221
|
+
};
|
|
1222
|
+
const nonceAttribute = documentNonceAttribute(document.nonce);
|
|
1223
|
+
const warnings = renderWarnings(deck);
|
|
1224
|
+
const htmlAttrs = input.printPreview ? " data-hono-decks-print-preview=\"true\"" : "";
|
|
1225
|
+
const bodyAttrs = [input.printPreview ? "data-hono-decks-print-preview=\"true\"" : "", input.speakerNotes === false ? "data-hono-decks-projection=\"true\"" : ""].filter(Boolean);
|
|
1226
|
+
return `<!doctype html>
|
|
1227
|
+
<html lang="${escapeHtml$5(document.lang)}"${htmlAttrs}>
|
|
1255
1228
|
<head>
|
|
1256
1229
|
<meta charset="utf-8" />
|
|
1257
1230
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1258
|
-
<title>${
|
|
1231
|
+
<title>${escapeHtml$5(presentationPageTitle(deck))}</title>
|
|
1259
1232
|
<style${nonceAttribute}>${basePresentationStyle()}${deck.themeStyle ?? ""}${input.style ?? ""}</style>
|
|
1260
1233
|
${document.head}
|
|
1261
1234
|
</head>
|
|
1262
1235
|
<body${bodyAttrs.length ? ` ${bodyAttrs.join(" ")}` : ""}>
|
|
1263
1236
|
${warnings}
|
|
1264
1237
|
${await renderCompiledDeckAsync(deck, {
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1238
|
+
components: mergeComponentInputs(deck.componentRegistry, input.components),
|
|
1239
|
+
speakerNotes: input.speakerNotes
|
|
1240
|
+
})}
|
|
1268
1241
|
${input.printPreview ? renderPrintPreviewScript(document.nonce) : renderPresentationScript(document.nonce)}
|
|
1269
1242
|
${!input.printPreview && input.liveReloadPath ? renderLiveReloadScript(input.liveReloadPath, document.nonce) : ""}
|
|
1270
1243
|
${!input.printPreview && input.clientEntry ? renderClientEntryScript(input.clientEntry, document.nonce) : ""}
|
|
@@ -1272,27 +1245,31 @@ async function renderCompiledDeckPageAsync(input) {
|
|
|
1272
1245
|
</html>`;
|
|
1273
1246
|
}
|
|
1274
1247
|
async function renderPresenterPageAsync(input) {
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1248
|
+
const { deck } = input;
|
|
1249
|
+
const document = input.document ?? {
|
|
1250
|
+
lang: "ja",
|
|
1251
|
+
head: ""
|
|
1252
|
+
};
|
|
1253
|
+
const nonceAttribute = documentNonceAttribute(document.nonce);
|
|
1254
|
+
const components = mergeComponentInputs(deck.componentRegistry, input.components);
|
|
1255
|
+
const mountPath = input.mountPath.replace(/\/$/, "") || "/";
|
|
1256
|
+
const deckPath = `${mountPath === "/" ? "" : mountPath}/${encodeURIComponent(deck.slug)}`;
|
|
1257
|
+
const stateQuery = input.presenterStateQuery ?? "";
|
|
1258
|
+
const deckHref = `${deckPath}${stateQuery}`;
|
|
1259
|
+
const projectionPath = `${deckPath}/presentation${stateQuery}`;
|
|
1260
|
+
const previews = await Promise.all(deck.slides.map((slide) => renderCompiledSlideAsync(slide, deck.assets, {
|
|
1261
|
+
components,
|
|
1262
|
+
deck,
|
|
1263
|
+
speakerNotes: false,
|
|
1264
|
+
slideState: "active"
|
|
1265
|
+
})));
|
|
1266
|
+
const notes = deck.slides.map((slide) => slide.notes ?? slide.meta.notes);
|
|
1267
|
+
return `<!doctype html>
|
|
1268
|
+
<html lang="${escapeHtml$5(document.lang)}">
|
|
1292
1269
|
<head>
|
|
1293
1270
|
<meta charset="utf-8" />
|
|
1294
1271
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1295
|
-
<title>${
|
|
1272
|
+
<title>${escapeHtml$5(presentationPageTitle(deck))} - Presenter</title>
|
|
1296
1273
|
<style${nonceAttribute}>${basePresenterStyle()}${deck.themeStyle ?? ""}${input.style ?? ""}</style>
|
|
1297
1274
|
${document.head}
|
|
1298
1275
|
</head>
|
|
@@ -1300,31 +1277,27 @@ async function renderPresenterPageAsync(input) {
|
|
|
1300
1277
|
<main class="hono-decks-presenter" data-hono-decks-presenter data-slide-index="0">
|
|
1301
1278
|
<section class="hono-decks-presenter-current" data-hono-decks-presenter-current aria-label="Current slide">
|
|
1302
1279
|
<nav class="hono-decks-presenter-controls" data-hono-decks-presenter-controls aria-label="Presenter controls">
|
|
1303
|
-
<a href="${
|
|
1304
|
-
<a href="${
|
|
1305
|
-
<button type="button" data-action="openProjection" data-projection-url="${
|
|
1280
|
+
<a href="${escapeHtml$5(mountPath)}" title="Deck list" aria-label="Deck list">${renderControlIconHtml("home")}</a>
|
|
1281
|
+
<a href="${escapeHtml$5(deckHref)}" title="Viewer" aria-label="Viewer">${renderControlIconHtml("viewer")}</a>
|
|
1282
|
+
<button type="button" data-action="openProjection" data-projection-url="${escapeHtml$5(projectionPath)}" title="Projection" aria-label="Projection">${renderControlIconHtml("projection")}</button>
|
|
1306
1283
|
<button type="button" data-action="previous" title="Previous slide" aria-label="Previous slide">${renderControlIconHtml("previous")}</button>
|
|
1307
1284
|
<span data-hono-decks-presenter-position>1 / ${deck.slides.length}</span>
|
|
1308
1285
|
<button type="button" data-action="next" title="Next slide" aria-label="Next slide">${renderControlIconHtml("next")}</button>
|
|
1309
1286
|
<span data-hono-decks-presenter-connection>Connected</span>
|
|
1310
1287
|
</nav>
|
|
1311
|
-
<iframe title="${
|
|
1288
|
+
<iframe title="${escapeHtml$5(presentationPageTitle(deck))}" src="${escapeHtml$5(projectionPath)}"></iframe>
|
|
1312
1289
|
</section>
|
|
1313
1290
|
<aside class="hono-decks-presenter-panel" aria-label="Presenter panel">
|
|
1314
1291
|
<section class="hono-decks-presenter-next" data-hono-decks-presenter-next aria-label="Next slide preview">
|
|
1315
1292
|
<h2>Next slide</h2>
|
|
1316
1293
|
<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")}
|
|
1294
|
+
${previews.map((preview, index) => `<div class="hono-decks-presenter-preview" data-hono-decks-presenter-preview data-slide-index="${index}"${index === 1 ? "" : " hidden"}>${preview}</div>`).join("\n")}
|
|
1320
1295
|
<p class="hono-decks-presenter-no-next" data-hono-decks-presenter-no-next${deck.slides.length > 1 ? " hidden" : ""}>No next slide</p>
|
|
1321
1296
|
</div>
|
|
1322
1297
|
</section>
|
|
1323
1298
|
<section class="hono-decks-presenter-notes" data-hono-decks-presenter-notes aria-label="Speaker notes">
|
|
1324
1299
|
<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")}
|
|
1300
|
+
${notes.map((note, index) => `<article data-hono-decks-presenter-note data-slide-index="${index}"${index === 0 ? "" : " hidden"}>${note ? escapeHtml$5(note) : "<p>No speaker notes.</p>"}</article>`).join("\n")}
|
|
1328
1301
|
</section>
|
|
1329
1302
|
</aside>
|
|
1330
1303
|
</main>
|
|
@@ -1333,10 +1306,10 @@ async function renderPresenterPageAsync(input) {
|
|
|
1333
1306
|
</html>`;
|
|
1334
1307
|
}
|
|
1335
1308
|
function renderWarnings(deck) {
|
|
1336
|
-
|
|
1309
|
+
return deck.warnings.length ? `<aside class="hono-decks-warnings">${deck.warnings.map((warning) => `<p>${escapeHtml$5(warning.message)}</p>`).join("")}</aside>` : "";
|
|
1337
1310
|
}
|
|
1338
1311
|
function renderPrintPreviewScript(nonce) {
|
|
1339
|
-
|
|
1312
|
+
return `<script${documentNonceAttribute(nonce)}>
|
|
1340
1313
|
(() => {
|
|
1341
1314
|
const params = new URLSearchParams(window.location.search);
|
|
1342
1315
|
if (params.get("autoprint") !== "1") return;
|
|
@@ -1344,15 +1317,18 @@ function renderPrintPreviewScript(nonce) {
|
|
|
1344
1317
|
window.requestAnimationFrame(() => window.print());
|
|
1345
1318
|
}, { once: true });
|
|
1346
1319
|
})();
|
|
1347
|
-
|
|
1320
|
+
<\/script>`;
|
|
1348
1321
|
}
|
|
1349
1322
|
function mergeComponentInputs(base, overrides) {
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1323
|
+
if (!base) return overrides;
|
|
1324
|
+
if (!overrides) return base;
|
|
1325
|
+
return {
|
|
1326
|
+
...base,
|
|
1327
|
+
...overrides
|
|
1328
|
+
};
|
|
1353
1329
|
}
|
|
1354
1330
|
function basePresenterStyle() {
|
|
1355
|
-
|
|
1331
|
+
return `${basePresentationStyle()}
|
|
1356
1332
|
body{margin:0;min-height:100vh;background:#050816;color:#eef2ff;font-family:Inter,ui-sans-serif,system-ui,sans-serif}
|
|
1357
1333
|
.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
1334
|
.hono-decks-presenter-current,.hono-decks-presenter-panel{min-width:0}
|
|
@@ -1374,7 +1350,7 @@ body:not([data-overview-mode]) .hono-decks-presenter-preview .slide{position:abs
|
|
|
1374
1350
|
@media (max-width:900px){.hono-decks-presenter{grid-template-columns:1fr}.hono-decks-presenter-panel{grid-template-rows:auto auto}}`;
|
|
1375
1351
|
}
|
|
1376
1352
|
function renderPresenterScript(slideCount, nonce) {
|
|
1377
|
-
|
|
1353
|
+
return `<script${documentNonceAttribute(nonce)}>
|
|
1378
1354
|
(() => {
|
|
1379
1355
|
const root = document.querySelector("[data-hono-decks-presenter]");
|
|
1380
1356
|
const frame = document.querySelector("[data-hono-decks-presenter-current] iframe");
|
|
@@ -1485,273 +1461,302 @@ function renderPresenterScript(slideCount, nonce) {
|
|
|
1485
1461
|
}
|
|
1486
1462
|
});
|
|
1487
1463
|
})();
|
|
1488
|
-
|
|
1464
|
+
<\/script>`;
|
|
1489
1465
|
}
|
|
1490
|
-
function
|
|
1491
|
-
|
|
1466
|
+
function escapeHtml$5(value) {
|
|
1467
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
1492
1468
|
}
|
|
1493
|
-
|
|
1494
|
-
|
|
1469
|
+
//#endregion
|
|
1470
|
+
//#region src/renderer/compiled-render.ts
|
|
1495
1471
|
function renderCompiledDeck(deck, input = {}) {
|
|
1496
|
-
|
|
1497
|
-
|
|
1472
|
+
const components = normalizeComponents(input.components);
|
|
1473
|
+
return `<main class="hono-decks-stage" data-hono-decks-stage data-deck-slug="${escapeHtml$4(deck.slug)}"><div class="hono-decks-deck" data-hono-decks-deck>${deck.slides.map((slide) => renderCompiledSlide(slide, deck.assets, {
|
|
1474
|
+
components,
|
|
1475
|
+
deck,
|
|
1476
|
+
speakerNotes: input.speakerNotes
|
|
1477
|
+
})).join("\n")}</div></main>`;
|
|
1498
1478
|
}
|
|
1499
1479
|
async function renderCompiledDeckAsync(deck, input = {}) {
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
"\n"
|
|
1508
|
-
)}</div></main>`;
|
|
1480
|
+
const components = normalizeComponents(input.components);
|
|
1481
|
+
const slides = await Promise.all(deck.slides.map((slide) => renderCompiledSlideAsync(slide, deck.assets, {
|
|
1482
|
+
components,
|
|
1483
|
+
deck,
|
|
1484
|
+
speakerNotes: input.speakerNotes
|
|
1485
|
+
})));
|
|
1486
|
+
return `<main class="hono-decks-stage" data-hono-decks-stage data-deck-slug="${escapeHtml$4(deck.slug)}"><div class="hono-decks-deck" data-hono-decks-deck>${slides.join("\n")}</div></main>`;
|
|
1509
1487
|
}
|
|
1510
1488
|
function renderCompiledSlide(slide, assets = [], input = {}) {
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1489
|
+
const components = normalizeComponents(input.components);
|
|
1490
|
+
const slideState = input.slideState ?? "inactive";
|
|
1491
|
+
const classes = [
|
|
1492
|
+
"slide",
|
|
1493
|
+
`layout-${safeClass(slide.meta.layout ?? "default")}`,
|
|
1494
|
+
slide.meta.className ? safeClass(slide.meta.className) : ""
|
|
1495
|
+
].filter(Boolean).join(" ");
|
|
1496
|
+
const notes = slide.notes ?? slide.meta.notes;
|
|
1497
|
+
const notesHtml = input.speakerNotes === false || !notes ? "" : `<aside class="speaker-notes" hidden>${escapeHtml$4(notes)}</aside>`;
|
|
1498
|
+
const html = slide.nodes?.length ? renderSlideNodes(slide.nodes, {
|
|
1499
|
+
components,
|
|
1500
|
+
assets
|
|
1501
|
+
}) : rewriteLocalAssetUrls(slide.html, assets);
|
|
1502
|
+
const style = slideStyleAttribute(slide, assets);
|
|
1503
|
+
const transition = slide.meta.transition ? ` data-transition="${escapeHtml$4(safeClass(slide.meta.transition))}"` : "";
|
|
1504
|
+
return `<section class="${classes}" data-slide-index="${slide.index}" data-slide-state="${slideState}"${slide.meta.title ? ` aria-label="${escapeHtml$4(slide.meta.title)}"` : ""}${transition}${style}><div class="hono-decks-slide-content">${html}</div>${notesHtml}</section>`;
|
|
1521
1505
|
}
|
|
1522
1506
|
async function renderCompiledSlideAsync(slide, assets = [], input = {}) {
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1507
|
+
const components = normalizeComponents(input.components);
|
|
1508
|
+
const slideState = input.slideState ?? "inactive";
|
|
1509
|
+
const classes = [
|
|
1510
|
+
"slide",
|
|
1511
|
+
`layout-${safeClass(slide.meta.layout ?? "default")}`,
|
|
1512
|
+
slide.meta.className ? safeClass(slide.meta.className) : ""
|
|
1513
|
+
].filter(Boolean).join(" ");
|
|
1514
|
+
const notes = slide.notes ?? slide.meta.notes;
|
|
1515
|
+
const notesHtml = input.speakerNotes === false || !notes ? "" : `<aside class="speaker-notes" hidden>${escapeHtml$4(notes)}</aside>`;
|
|
1516
|
+
const html = await renderSlideBodyAsync(slide, assets, {
|
|
1517
|
+
...input,
|
|
1518
|
+
components
|
|
1519
|
+
});
|
|
1520
|
+
const style = slideStyleAttribute(slide, assets);
|
|
1521
|
+
const transition = slide.meta.transition ? ` data-transition="${escapeHtml$4(safeClass(slide.meta.transition))}"` : "";
|
|
1522
|
+
return `<section class="${classes}" data-slide-index="${slide.index}" data-slide-state="${slideState}"${slide.meta.title ? ` aria-label="${escapeHtml$4(slide.meta.title)}"` : ""}${transition}${style}><div class="hono-decks-slide-content">${html}</div>${notesHtml}</section>`;
|
|
1533
1523
|
}
|
|
1534
1524
|
function slideStyleAttribute(slide, assets) {
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
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(";"))}"` : "";
|
|
1525
|
+
const declarations = [];
|
|
1526
|
+
if (slide.meta.background) declarations.push(backgroundStyle(slide.meta.background, assets));
|
|
1527
|
+
if (slide.meta.transitionDuration) declarations.push(`--hono-decks-slide-transition-duration:${slide.meta.transitionDuration}`);
|
|
1528
|
+
if (slide.meta.transitionEasing) declarations.push(`--hono-decks-slide-transition-easing:${slide.meta.transitionEasing}`);
|
|
1529
|
+
return declarations.length ? ` style="${escapeHtml$4(declarations.join(";"))}"` : "";
|
|
1544
1530
|
}
|
|
1545
1531
|
async function renderSlideBodyAsync(slide, assets, input) {
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
error
|
|
1556
|
-
);
|
|
1557
|
-
}
|
|
1532
|
+
try {
|
|
1533
|
+
return slide.render ? await renderJsxValue(slide.render({ components: createMdxComponents(input.components ?? builtInSlideComponents, { assets }) })) : slide.nodes?.length ? await renderSlideNodesAsync(slide.nodes, {
|
|
1534
|
+
components: input.components,
|
|
1535
|
+
assets
|
|
1536
|
+
}) : rewriteLocalAssetUrls(slide.html, assets);
|
|
1537
|
+
} catch (error) {
|
|
1538
|
+
if (error instanceof RenderError) throw error;
|
|
1539
|
+
throw new RenderError(`Render failed in ${input.deck?.sourcePath ?? "unknown deck"} slide ${slide.index + 1}: ${formatErrorMessage$1(error)}`, "slide-render-error", error);
|
|
1540
|
+
}
|
|
1558
1541
|
}
|
|
1559
1542
|
function normalizeComponents(components) {
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1543
|
+
return {
|
|
1544
|
+
...builtInSlideComponents,
|
|
1545
|
+
...components ? defineSlideComponents(components) : {}
|
|
1546
|
+
};
|
|
1564
1547
|
}
|
|
1565
1548
|
function safeClass(value) {
|
|
1566
|
-
|
|
1549
|
+
return value.toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
|
|
1567
1550
|
}
|
|
1568
|
-
function
|
|
1569
|
-
|
|
1551
|
+
function escapeHtml$4(value) {
|
|
1552
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
1570
1553
|
}
|
|
1571
|
-
function formatErrorMessage(error) {
|
|
1572
|
-
|
|
1573
|
-
|
|
1554
|
+
function formatErrorMessage$1(error) {
|
|
1555
|
+
if (error instanceof Error) return error.message;
|
|
1556
|
+
return String(error);
|
|
1574
1557
|
}
|
|
1575
|
-
|
|
1576
|
-
|
|
1558
|
+
//#endregion
|
|
1559
|
+
//#region src/server/client-entry.ts
|
|
1577
1560
|
function serveDecksClientEntry(clientEntry, options = {}) {
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
// src/server/path-utils.ts
|
|
1561
|
+
return (c) => {
|
|
1562
|
+
const headers = { "content-type": options.contentType ?? "text/javascript; charset=utf-8" };
|
|
1563
|
+
const cacheControl = options.cacheControl ?? "public, max-age=300";
|
|
1564
|
+
if (cacheControl !== false) headers["cache-control"] = cacheControl;
|
|
1565
|
+
return c.body(clientEntry, 200, headers);
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
//#endregion
|
|
1569
|
+
//#region src/server/path-utils.ts
|
|
1589
1570
|
function extractAssetPath(path, slug) {
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1571
|
+
const marker = `/${slug}/assets/`;
|
|
1572
|
+
const markerIndex = path.indexOf(marker);
|
|
1573
|
+
if (markerIndex === -1) return "";
|
|
1574
|
+
return path.slice(markerIndex + marker.length);
|
|
1594
1575
|
}
|
|
1595
1576
|
function stripPathSuffix(path, suffix) {
|
|
1596
|
-
|
|
1577
|
+
return path.endsWith(suffix) ? path.slice(0, -suffix.length) : path;
|
|
1597
1578
|
}
|
|
1598
1579
|
function inferMountPath(path, slug) {
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1580
|
+
const marker = `/${slug}`;
|
|
1581
|
+
const markerIndex = path.indexOf(marker);
|
|
1582
|
+
if (markerIndex === -1) return "";
|
|
1583
|
+
return path.slice(0, markerIndex) || "/";
|
|
1603
1584
|
}
|
|
1604
1585
|
function resolveGeneratedClientEntryUrl(options, mountPath) {
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
return `${basePath}${normalizeClientEntryAssetPath(options.clientEntryAssetPath)}`;
|
|
1586
|
+
if (!options.clientEntryAsset) return void 0;
|
|
1587
|
+
return `${mountPath === "/" ? "" : mountPath.replace(/\/$/, "")}${normalizeClientEntryAssetPath(options.clientEntryAssetPath)}`;
|
|
1608
1588
|
}
|
|
1609
1589
|
function normalizeClientEntryAssetPath(path = "/_assets/client.js") {
|
|
1610
|
-
|
|
1611
|
-
return normalized.replace(/\/{2,}/g, "/");
|
|
1590
|
+
return (path.startsWith("/") ? path : `/${path}`).replace(/\/{2,}/g, "/");
|
|
1612
1591
|
}
|
|
1613
|
-
|
|
1614
|
-
|
|
1592
|
+
//#endregion
|
|
1593
|
+
//#region src/server/paths.ts
|
|
1594
|
+
/** Creates the canonical route map used by routers, controls, and app code. */
|
|
1615
1595
|
function createDeckPaths(mountPath, slug) {
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
}
|
|
1596
|
+
const mount = normalizeMountPath(mountPath);
|
|
1597
|
+
const viewer = `${mount === "/" ? "" : mount}/${encodeURIComponent(slug)}`;
|
|
1598
|
+
return {
|
|
1599
|
+
viewer,
|
|
1600
|
+
render: `${viewer}/render`,
|
|
1601
|
+
print: `${viewer}/print`,
|
|
1602
|
+
presentation: `${viewer}/presentation`,
|
|
1603
|
+
presenter: `${viewer}/presenter`,
|
|
1604
|
+
embed: `${viewer}/embed`,
|
|
1605
|
+
exportPdf: `${viewer}/export.pdf`,
|
|
1606
|
+
exportPng: `${viewer}/export.png`,
|
|
1607
|
+
ogImage: `${viewer}/og.png`,
|
|
1608
|
+
assets: `${viewer}/assets`
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
/** Normalizes a configured mount path to one leading slash and no trailing slash. */
|
|
1631
1612
|
function normalizeMountPath(value) {
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1613
|
+
const trimmed = value.trim();
|
|
1614
|
+
if (!trimmed || trimmed === "/") return "/";
|
|
1615
|
+
return `/${trimmed.replace(/^\/+|\/+$/g, "")}`;
|
|
1635
1616
|
}
|
|
1636
|
-
|
|
1637
|
-
|
|
1617
|
+
//#endregion
|
|
1618
|
+
//#region src/server/browser-export.ts
|
|
1638
1619
|
async function renderDeckBrowserExport(c, options, format) {
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1620
|
+
const slug = c.req.param("slug");
|
|
1621
|
+
if (!slug) return c.json({
|
|
1622
|
+
error: "Deck not found",
|
|
1623
|
+
slug: ""
|
|
1624
|
+
}, 404);
|
|
1625
|
+
const deck = await options.source.getCompiledDeck(c, slug);
|
|
1626
|
+
if (!deck || options.dev !== true && deck.meta.draft) return c.json({
|
|
1627
|
+
error: "Deck not found",
|
|
1628
|
+
slug
|
|
1629
|
+
}, 404);
|
|
1630
|
+
const exportOptions = options.export;
|
|
1631
|
+
if (!exportOptions) return c.json({
|
|
1632
|
+
error: "Browser export not configured",
|
|
1633
|
+
slug,
|
|
1634
|
+
format
|
|
1635
|
+
}, 503);
|
|
1636
|
+
const mountPath = stripPathSuffix(c.req.path, `/${slug}/export.${format}`);
|
|
1637
|
+
const resolverInput = {
|
|
1638
|
+
c,
|
|
1639
|
+
deck,
|
|
1640
|
+
slug,
|
|
1641
|
+
mountPath,
|
|
1642
|
+
paths: createDeckPaths(mountPath, slug),
|
|
1643
|
+
format
|
|
1644
|
+
};
|
|
1645
|
+
if (!await isBrowserExportAuthorized(exportOptions, resolverInput)) return c.json({
|
|
1646
|
+
error: "Browser export not authorized",
|
|
1647
|
+
slug,
|
|
1648
|
+
format
|
|
1649
|
+
}, 403);
|
|
1650
|
+
const browser = await exportOptions.browser(resolverInput);
|
|
1651
|
+
if (!browser) return c.json({
|
|
1652
|
+
error: "Browser export not configured",
|
|
1653
|
+
slug,
|
|
1654
|
+
format
|
|
1655
|
+
}, 503);
|
|
1656
|
+
const printUrl = new URL(resolverInput.paths.print, c.req.url).toString();
|
|
1657
|
+
const action = format === "pdf" ? "pdf" : "screenshot";
|
|
1658
|
+
const response = await browser.quickAction(action, createBrowserRunExportRequest(exportOptions, printUrl, format));
|
|
1659
|
+
const headers = new Headers(response.headers);
|
|
1660
|
+
if (!headers.has("content-type")) headers.set("content-type", format === "pdf" ? "application/pdf" : "image/png");
|
|
1661
|
+
headers.set("content-disposition", `attachment; filename="${exportFilename(exportOptions, deck, format)}"`);
|
|
1662
|
+
return new Response(response.body, {
|
|
1663
|
+
status: response.status,
|
|
1664
|
+
statusText: response.statusText,
|
|
1665
|
+
headers
|
|
1666
|
+
});
|
|
1670
1667
|
}
|
|
1671
1668
|
function isPdfExportEnabled(options) {
|
|
1672
|
-
|
|
1669
|
+
return Boolean(options && options.pdf !== void 0 && options.pdf !== false);
|
|
1673
1670
|
}
|
|
1674
1671
|
function isPngExportEnabled(options) {
|
|
1675
|
-
|
|
1672
|
+
return Boolean(options && options.png !== void 0 && options.png !== false);
|
|
1676
1673
|
}
|
|
1677
1674
|
async function resolveAuthorizedExportPaths(c, deck, mountPath, options) {
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1675
|
+
if (!options) return {};
|
|
1676
|
+
const slug = deck.slug;
|
|
1677
|
+
const paths = createDeckPaths(mountPath, slug);
|
|
1678
|
+
return {
|
|
1679
|
+
pdf: isPdfExportEnabled(options) && await isBrowserExportAuthorized(options, {
|
|
1680
|
+
c,
|
|
1681
|
+
deck,
|
|
1682
|
+
slug,
|
|
1683
|
+
mountPath,
|
|
1684
|
+
paths,
|
|
1685
|
+
format: "pdf"
|
|
1686
|
+
}),
|
|
1687
|
+
png: isPngExportEnabled(options) && await isBrowserExportAuthorized(options, {
|
|
1688
|
+
c,
|
|
1689
|
+
deck,
|
|
1690
|
+
slug,
|
|
1691
|
+
mountPath,
|
|
1692
|
+
paths,
|
|
1693
|
+
format: "png"
|
|
1694
|
+
})
|
|
1695
|
+
};
|
|
1684
1696
|
}
|
|
1685
1697
|
async function isBrowserExportAuthorized(options, input) {
|
|
1686
|
-
|
|
1698
|
+
return options.authorize ? options.authorize(input) : true;
|
|
1687
1699
|
}
|
|
1688
1700
|
function createBrowserRunExportRequest(options, printUrl, format) {
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1701
|
+
if (format === "pdf") {
|
|
1702
|
+
const request = exportOptionObject(options.pdf).request ?? {};
|
|
1703
|
+
return {
|
|
1704
|
+
...request,
|
|
1705
|
+
url: printUrl,
|
|
1706
|
+
gotoOptions: {
|
|
1707
|
+
waitUntil: "networkidle2",
|
|
1708
|
+
timeout: 45e3,
|
|
1709
|
+
...recordValue(request.gotoOptions)
|
|
1710
|
+
},
|
|
1711
|
+
pdfOptions: {
|
|
1712
|
+
format: "a4",
|
|
1713
|
+
printBackground: true,
|
|
1714
|
+
preferCSSPageSize: true,
|
|
1715
|
+
...recordValue(request.pdfOptions)
|
|
1716
|
+
}
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
const request = exportOptionObject(options.png).request ?? {};
|
|
1720
|
+
return {
|
|
1721
|
+
...request,
|
|
1722
|
+
url: printUrl,
|
|
1723
|
+
gotoOptions: {
|
|
1724
|
+
waitUntil: "networkidle2",
|
|
1725
|
+
timeout: 45e3,
|
|
1726
|
+
...recordValue(request.gotoOptions)
|
|
1727
|
+
},
|
|
1728
|
+
viewport: {
|
|
1729
|
+
width: 794,
|
|
1730
|
+
height: 1123,
|
|
1731
|
+
deviceScaleFactor: 2,
|
|
1732
|
+
...recordValue(request.viewport)
|
|
1733
|
+
},
|
|
1734
|
+
screenshotOptions: {
|
|
1735
|
+
type: "png",
|
|
1736
|
+
fullPage: true,
|
|
1737
|
+
...recordValue(request.screenshotOptions)
|
|
1738
|
+
}
|
|
1739
|
+
};
|
|
1728
1740
|
}
|
|
1729
1741
|
function exportFilename(options, deck, format) {
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
return `${safeFilename(name ?? deck.meta.title ?? deck.slug)}.${format}`;
|
|
1742
|
+
const option = exportOptionObject(format === "pdf" ? options.pdf : options.png);
|
|
1743
|
+
return `${safeFilename$1((typeof option.filename === "function" ? option.filename(deck) : option.filename) ?? deck.meta.title ?? deck.slug)}.${format}`;
|
|
1733
1744
|
}
|
|
1734
1745
|
function exportOptionObject(option) {
|
|
1735
|
-
|
|
1746
|
+
return typeof option === "object" && option ? option : {};
|
|
1736
1747
|
}
|
|
1737
1748
|
function recordValue(value) {
|
|
1738
|
-
|
|
1749
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
1739
1750
|
}
|
|
1740
|
-
function safeFilename(value) {
|
|
1741
|
-
|
|
1742
|
-
return normalized || "deck";
|
|
1751
|
+
function safeFilename$1(value) {
|
|
1752
|
+
return value.trim().replaceAll(/[^a-zA-Z0-9._-]+/g, "-").replaceAll(/^-+|-+$/g, "") || "deck";
|
|
1743
1753
|
}
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
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";
|
|
1754
|
+
//#endregion
|
|
1755
|
+
//#region src/server/viewer-script.ts
|
|
1756
|
+
const VIEWER_STATE_MESSAGE_TYPE = "hono-decks:state";
|
|
1757
|
+
const VIEWER_COMMAND_MESSAGE_TYPE = "hono-decks:command";
|
|
1752
1758
|
function renderViewerScript(nonce) {
|
|
1753
|
-
|
|
1754
|
-
return `<script data-hono-decks-viewer-runtime${nonceAttribute}>
|
|
1759
|
+
return `<script data-hono-decks-viewer-runtime${nonce ? ` nonce="${escapeHtml$3(nonce)}"` : ""}>
|
|
1755
1760
|
(() => {
|
|
1756
1761
|
const roots = Array.from(document.querySelectorAll("[data-hono-decks-viewer]"));
|
|
1757
1762
|
const runtime = window.__honoDecksViewerRuntime ??= { controllers: new Map(), globalInitialized: false };
|
|
@@ -1938,21 +1943,21 @@ function renderViewerScript(nonce) {
|
|
|
1938
1943
|
});
|
|
1939
1944
|
}
|
|
1940
1945
|
})();
|
|
1941
|
-
|
|
1946
|
+
<\/script>`;
|
|
1942
1947
|
}
|
|
1943
|
-
function
|
|
1944
|
-
|
|
1948
|
+
function escapeHtml$3(value) {
|
|
1949
|
+
return value.replaceAll("&", "&").replaceAll("\"", """).replaceAll("<", "<").replaceAll(">", ">");
|
|
1945
1950
|
}
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1951
|
+
//#endregion
|
|
1952
|
+
//#region src/server/viewer-style.ts
|
|
1953
|
+
const VIEWER_ASPECT_RATIO = "16/9";
|
|
1949
1954
|
function viewerViewportRule() {
|
|
1950
|
-
|
|
1955
|
+
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
1956
|
@supports (height:100dvh){.hono-decks-viewport{width:min(100%,calc((100dvh - 58px) * 16 / 9))}}
|
|
1952
1957
|
@supports (width:1cqw){.hono-decks-viewport{width:min(100cqw,calc(100cqh * 16 / 9));height:min(100cqh,calc(100cqw * 9 / 16))}}`;
|
|
1953
1958
|
}
|
|
1954
1959
|
function baseViewerStyle() {
|
|
1955
|
-
|
|
1960
|
+
return `
|
|
1956
1961
|
:root{color-scheme:dark;background:#050816;color:#eef2ff;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
|
1957
1962
|
html,body{margin:0;width:100%;height:100%;min-height:100vh}
|
|
1958
1963
|
@supports (height:100dvh){html,body{height:100dvh;min-height:100dvh}}
|
|
@@ -1987,8 +1992,8 @@ ${viewerViewportRule()}
|
|
|
1987
1992
|
@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
1993
|
}
|
|
1989
1994
|
function embeddedViewerStyle() {
|
|
1990
|
-
|
|
1991
|
-
|
|
1995
|
+
const root = "[data-hono-decks-viewer][data-hono-decks-embed]";
|
|
1996
|
+
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
1997
|
${root} *:focus-visible{outline:none}
|
|
1993
1998
|
${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
1999
|
${root} .hono-decks-viewer-stage{display:grid;place-items:center;width:100%;min-width:0;min-height:0;container-type:inline-size}
|
|
@@ -2009,129 +2014,142 @@ ${root} .hono-decks-control-icon{width:16px;height:16px;flex:0 0 auto;stroke:cur
|
|
|
2009
2014
|
${root} .hono-decks-viewer-toc button{font:inherit}
|
|
2010
2015
|
@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
2016
|
}
|
|
2012
|
-
|
|
2013
|
-
|
|
2017
|
+
//#endregion
|
|
2018
|
+
//#region src/server/viewer.ts
|
|
2014
2019
|
async function createDeckViewerParts(input) {
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2020
|
+
const slug = input.deck.slug;
|
|
2021
|
+
const title = input.deck.meta.title ?? slug;
|
|
2022
|
+
const basePath = input.mountPath.replace(/\/$/, "");
|
|
2023
|
+
const paths = createDeckPaths(basePath, slug);
|
|
2024
|
+
const imagePath = await resolveOpenGraphImagePath(input.openGraph, {
|
|
2025
|
+
deck: input.deck,
|
|
2026
|
+
paths
|
|
2027
|
+
});
|
|
2028
|
+
const renderUrl = `${paths.render}${input.viewerStateQuery ?? ""}`;
|
|
2029
|
+
const slides = createDeckToc(input.deck);
|
|
2030
|
+
const meta = {
|
|
2031
|
+
title,
|
|
2032
|
+
description: input.deck.meta.description,
|
|
2033
|
+
paths,
|
|
2034
|
+
availablePages: { print: input.availablePages?.print !== false },
|
|
2035
|
+
availableExports: input.exportPaths ?? {},
|
|
2036
|
+
...imagePath ? { imagePath } : {}
|
|
2037
|
+
};
|
|
2038
|
+
const controlsInput = input.controls === false ? false : input.controls ?? {};
|
|
2039
|
+
const controls = controlsInput === false ? null : renderViewerControls(controlsInput, {
|
|
2040
|
+
slug,
|
|
2041
|
+
title,
|
|
2042
|
+
mountPath: basePath,
|
|
2043
|
+
meta,
|
|
2044
|
+
slides
|
|
2045
|
+
});
|
|
2046
|
+
return {
|
|
2047
|
+
slug,
|
|
2048
|
+
title,
|
|
2049
|
+
renderUrl,
|
|
2050
|
+
frame: {
|
|
2051
|
+
content: renderViewerFrame({
|
|
2052
|
+
title,
|
|
2053
|
+
renderUrl
|
|
2054
|
+
}),
|
|
2055
|
+
html: renderViewerFrameHtml({
|
|
2056
|
+
title,
|
|
2057
|
+
renderUrl
|
|
2058
|
+
})
|
|
2059
|
+
},
|
|
2060
|
+
controls: controls ? {
|
|
2061
|
+
content: controls,
|
|
2062
|
+
html: await renderJsxValue(controls)
|
|
2063
|
+
} : null,
|
|
2064
|
+
toc: {
|
|
2065
|
+
content: renderViewerToc(slides),
|
|
2066
|
+
html: renderViewerTocHtml(slides)
|
|
2067
|
+
},
|
|
2068
|
+
slides,
|
|
2069
|
+
meta
|
|
2070
|
+
};
|
|
2046
2071
|
}
|
|
2047
2072
|
async function createDeckViewerEmbed(input) {
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
return {
|
|
2068
|
-
...parts,
|
|
2069
|
-
embed: raw(embedHtml),
|
|
2070
|
-
embedHtml
|
|
2071
|
-
};
|
|
2073
|
+
const parts = await createDeckViewerParts(input);
|
|
2074
|
+
const rootHtml = await renderJsxValue(jsx("section", {
|
|
2075
|
+
class: ["hono-decks-embedded-viewer", input.className].filter(Boolean).join(" "),
|
|
2076
|
+
"data-hono-decks-viewer": true,
|
|
2077
|
+
"data-hono-decks-embed": true,
|
|
2078
|
+
"data-deck-slug": parts.slug,
|
|
2079
|
+
...parts.meta.availablePages.print ? { "data-hono-decks-print-path": parts.meta.paths.print } : {},
|
|
2080
|
+
"aria-label": parts.title,
|
|
2081
|
+
children: [jsx("div", {
|
|
2082
|
+
class: "hono-decks-viewer-shell",
|
|
2083
|
+
children: [parts.frame.content, parts.controls?.content]
|
|
2084
|
+
}), input.toc ? parts.toc.content : null]
|
|
2085
|
+
}));
|
|
2086
|
+
const embedHtml = `<style data-hono-decks-embed-style${input.nonce ? ` nonce="${escapeHtml$2(input.nonce)}"` : ""}>${embeddedViewerStyle()}${input.style ?? ""}</style>${rootHtml}${renderViewerScript(input.nonce)}`;
|
|
2087
|
+
return {
|
|
2088
|
+
...parts,
|
|
2089
|
+
embed: raw(embedHtml),
|
|
2090
|
+
embedHtml
|
|
2091
|
+
};
|
|
2072
2092
|
}
|
|
2073
2093
|
async function renderDeckViewerPage(input) {
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
return `<!doctype html>
|
|
2130
|
-
<html lang="${escapeHtml7(document.lang)}">
|
|
2094
|
+
const parts = await createDeckViewerParts({
|
|
2095
|
+
deck: input.deck,
|
|
2096
|
+
mountPath: input.mountPath,
|
|
2097
|
+
viewerStateQuery: input.viewerStateQuery,
|
|
2098
|
+
controls: input.viewer?.controls,
|
|
2099
|
+
openGraph: input.viewer?.openGraph,
|
|
2100
|
+
availablePages: input.availablePages,
|
|
2101
|
+
exportPaths: await resolveAuthorizedExportPaths(input.c, input.deck, input.mountPath, input.exportOptions)
|
|
2102
|
+
});
|
|
2103
|
+
const renderInput = {
|
|
2104
|
+
...parts,
|
|
2105
|
+
c: input.c,
|
|
2106
|
+
deck: input.deck,
|
|
2107
|
+
mountPath: input.mountPath
|
|
2108
|
+
};
|
|
2109
|
+
const customContent = input.viewer?.render ? await input.viewer.render(renderInput) : void 0;
|
|
2110
|
+
const content = jsx("main", {
|
|
2111
|
+
"data-hono-decks-viewer": true,
|
|
2112
|
+
"data-deck-slug": parts.slug,
|
|
2113
|
+
...parts.meta.availablePages.print ? { "data-hono-decks-print-path": parts.meta.paths.print } : {},
|
|
2114
|
+
...customContent ? { "aria-label": parts.title } : { "aria-labelledby": "hono-decks-viewer-title" },
|
|
2115
|
+
children: customContent ?? [jsx("header", {
|
|
2116
|
+
class: "hono-decks-viewer-header",
|
|
2117
|
+
children: [jsx("h1", {
|
|
2118
|
+
id: "hono-decks-viewer-title",
|
|
2119
|
+
class: "hono-decks-viewer-title",
|
|
2120
|
+
children: parts.title
|
|
2121
|
+
}), jsx("p", {
|
|
2122
|
+
class: "hono-decks-viewer-meta",
|
|
2123
|
+
children: `${parts.slides.length} ${parts.slides.length === 1 ? "slide" : "slides"}`
|
|
2124
|
+
})]
|
|
2125
|
+
}), jsx("div", {
|
|
2126
|
+
class: "hono-decks-viewer-shell",
|
|
2127
|
+
children: [parts.frame.content, parts.controls?.content]
|
|
2128
|
+
})]
|
|
2129
|
+
});
|
|
2130
|
+
const viewerHead = typeof input.viewer?.head === "function" ? await input.viewer.head(renderInput) : await input.viewer?.head;
|
|
2131
|
+
const viewerLang = typeof input.viewer?.lang === "function" ? await input.viewer.lang(renderInput) : input.viewer?.lang;
|
|
2132
|
+
const viewerNonce = typeof input.viewer?.nonce === "function" ? await input.viewer.nonce(renderInput) : input.viewer?.nonce;
|
|
2133
|
+
const document = await resolveDeckDocument({
|
|
2134
|
+
c: input.c,
|
|
2135
|
+
surface: "viewer",
|
|
2136
|
+
deck: input.deck,
|
|
2137
|
+
slug: input.deck.slug,
|
|
2138
|
+
mountPath: input.mountPath,
|
|
2139
|
+
title: parts.title
|
|
2140
|
+
}, input.document, {
|
|
2141
|
+
head: viewerHead,
|
|
2142
|
+
lang: viewerLang,
|
|
2143
|
+
nonce: viewerNonce
|
|
2144
|
+
});
|
|
2145
|
+
const nonceAttribute = documentNonceAttribute(document.nonce);
|
|
2146
|
+
const socialHead = renderViewerSocialHead(input.c, parts.meta);
|
|
2147
|
+
return `<!doctype html>
|
|
2148
|
+
<html lang="${escapeHtml$2(document.lang)}">
|
|
2131
2149
|
<head>
|
|
2132
2150
|
<meta charset="utf-8" />
|
|
2133
2151
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
2134
|
-
<title>${
|
|
2152
|
+
<title>${escapeHtml$2(parts.title)}</title>
|
|
2135
2153
|
${socialHead}
|
|
2136
2154
|
<style${nonceAttribute}>${baseViewerStyle()}${input.viewer?.style ?? ""}</style>
|
|
2137
2155
|
${document.head}
|
|
@@ -2143,360 +2161,370 @@ async function renderDeckViewerPage(input) {
|
|
|
2143
2161
|
</html>`;
|
|
2144
2162
|
}
|
|
2145
2163
|
async function resolveOpenGraphImagePath(options, input) {
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2164
|
+
if (!options) return void 0;
|
|
2165
|
+
if (options === true) return input.paths.ogImage;
|
|
2166
|
+
if (typeof options.imagePath === "function") return options.imagePath(input);
|
|
2167
|
+
return options.imagePath ?? input.paths.ogImage;
|
|
2150
2168
|
}
|
|
2151
2169
|
function renderViewerSocialHead(c, meta) {
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
<meta property="og:
|
|
2158
|
-
<meta
|
|
2159
|
-
|
|
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)}" />
|
|
2170
|
+
if (!meta.imagePath) return "";
|
|
2171
|
+
const pageUrl = new URL(meta.paths.viewer, c.req.url).href;
|
|
2172
|
+
const imageUrl = new URL(meta.imagePath, c.req.url).href;
|
|
2173
|
+
const description = meta.description ? `\n <meta name="description" content="${escapeHtml$2(meta.description)}" />\n <meta property="og:description" content="${escapeHtml$2(meta.description)}" />\n <meta name="twitter:description" content="${escapeHtml$2(meta.description)}" />` : "";
|
|
2174
|
+
return `<meta property="og:type" content="website" />
|
|
2175
|
+
<meta property="og:title" content="${escapeHtml$2(meta.title)}" />${description}
|
|
2176
|
+
<meta property="og:url" content="${escapeHtml$2(pageUrl)}" />
|
|
2177
|
+
<meta property="og:image" content="${escapeHtml$2(imageUrl)}" />
|
|
2163
2178
|
<meta property="og:image:width" content="1200" />
|
|
2164
2179
|
<meta property="og:image:height" content="630" />
|
|
2165
|
-
<meta property="og:image:alt" content="${
|
|
2180
|
+
<meta property="og:image:alt" content="${escapeHtml$2(meta.title)}" />
|
|
2166
2181
|
<meta name="twitter:card" content="summary_large_image" />
|
|
2167
|
-
<meta name="twitter:title" content="${
|
|
2168
|
-
<meta name="twitter:image" content="${
|
|
2182
|
+
<meta name="twitter:title" content="${escapeHtml$2(meta.title)}" />
|
|
2183
|
+
<meta name="twitter:image" content="${escapeHtml$2(imageUrl)}" />`;
|
|
2169
2184
|
}
|
|
2170
2185
|
function createDeckToc(deck) {
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2186
|
+
return deck.slides.map((slide) => ({
|
|
2187
|
+
index: slide.index,
|
|
2188
|
+
title: slide.meta.title,
|
|
2189
|
+
label: slide.meta.title ?? `Slide ${slide.index + 1}`
|
|
2190
|
+
}));
|
|
2176
2191
|
}
|
|
2177
2192
|
function renderViewerFrame(input) {
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2193
|
+
return jsx("div", {
|
|
2194
|
+
class: "hono-decks-viewer-stage",
|
|
2195
|
+
"data-hono-decks-frame": true,
|
|
2196
|
+
children: jsx("div", {
|
|
2197
|
+
class: "hono-decks-viewport",
|
|
2198
|
+
"data-viewer-viewport": true,
|
|
2199
|
+
tabindex: "0",
|
|
2200
|
+
children: [
|
|
2201
|
+
jsx("div", {
|
|
2202
|
+
class: "hono-decks-frame-stage",
|
|
2203
|
+
"data-viewer-stage": true,
|
|
2204
|
+
children: jsx("iframe", {
|
|
2205
|
+
title: input.title,
|
|
2206
|
+
src: input.renderUrl
|
|
2207
|
+
})
|
|
2208
|
+
}),
|
|
2209
|
+
jsx("button", {
|
|
2210
|
+
class: "hono-decks-viewer-navigation-layer hono-decks-viewer-navigation-previous",
|
|
2211
|
+
type: "button",
|
|
2212
|
+
"data-viewer-navigation": "previous",
|
|
2213
|
+
"aria-label": "Previous slide"
|
|
2214
|
+
}),
|
|
2215
|
+
jsx("button", {
|
|
2216
|
+
class: "hono-decks-viewer-navigation-layer hono-decks-viewer-navigation-next",
|
|
2217
|
+
type: "button",
|
|
2218
|
+
"data-viewer-navigation": "next",
|
|
2219
|
+
"aria-label": "Next slide"
|
|
2220
|
+
})
|
|
2221
|
+
]
|
|
2222
|
+
})
|
|
2223
|
+
});
|
|
2209
2224
|
}
|
|
2210
2225
|
function renderViewerFrameHtml(input) {
|
|
2211
|
-
|
|
2226
|
+
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="${escapeHtml$2(input.title)}" src="${escapeHtml$2(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
2227
|
}
|
|
2213
2228
|
function renderViewerControls(options, context) {
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2229
|
+
return jsx("nav", {
|
|
2230
|
+
...controlAttributes(options.attributes),
|
|
2231
|
+
class: controlsClassName(options),
|
|
2232
|
+
"data-hono-decks-viewer-controls": true,
|
|
2233
|
+
"aria-label": options.ariaLabel ?? "Viewer controls",
|
|
2234
|
+
children: resolveViewerControlItems(options, context).map((item) => renderViewerControlItem(item, options, context))
|
|
2235
|
+
});
|
|
2221
2236
|
}
|
|
2222
2237
|
function renderViewerToc(slides) {
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
children: slide.label
|
|
2235
|
-
})
|
|
2236
|
-
})
|
|
2237
|
-
)
|
|
2238
|
-
})
|
|
2239
|
-
});
|
|
2238
|
+
return jsx("nav", {
|
|
2239
|
+
class: "hono-decks-viewer-toc",
|
|
2240
|
+
"data-hono-decks-toc": true,
|
|
2241
|
+
"aria-label": "Slide navigation",
|
|
2242
|
+
children: jsx("ol", { children: slides.map((slide) => jsx("li", { children: jsx("button", {
|
|
2243
|
+
type: "button",
|
|
2244
|
+
"data-action": "goTo",
|
|
2245
|
+
"data-slide-index": String(slide.index),
|
|
2246
|
+
children: slide.label
|
|
2247
|
+
}) })) })
|
|
2248
|
+
});
|
|
2240
2249
|
}
|
|
2241
2250
|
function renderViewerTocHtml(slides) {
|
|
2242
|
-
|
|
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>`;
|
|
2251
|
+
return `<nav class="hono-decks-viewer-toc" data-hono-decks-toc aria-label="Slide navigation"><ol>${slides.map((slide) => `<li><button type="button" data-action="goTo" data-slide-index="${slide.index}">${escapeHtml$2(slide.label)}</button></li>`).join("")}</ol></nav>`;
|
|
2246
2252
|
}
|
|
2247
|
-
function
|
|
2248
|
-
|
|
2253
|
+
function escapeHtml$2(value) {
|
|
2254
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
|
2249
2255
|
}
|
|
2250
|
-
function
|
|
2251
|
-
|
|
2252
|
-
return normalized || "deck";
|
|
2256
|
+
function safeFilename(value) {
|
|
2257
|
+
return value.trim().replaceAll(/[^a-zA-Z0-9._-]+/g, "-").replaceAll(/^-+|-+$/g, "") || "deck";
|
|
2253
2258
|
}
|
|
2254
2259
|
function buildViewerControlDefaults(context) {
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2260
|
+
return {
|
|
2261
|
+
back: {
|
|
2262
|
+
type: "default",
|
|
2263
|
+
key: "back"
|
|
2264
|
+
},
|
|
2265
|
+
previous: {
|
|
2266
|
+
type: "default",
|
|
2267
|
+
key: "previous"
|
|
2268
|
+
},
|
|
2269
|
+
position: {
|
|
2270
|
+
type: "default",
|
|
2271
|
+
key: "position"
|
|
2272
|
+
},
|
|
2273
|
+
next: {
|
|
2274
|
+
type: "default",
|
|
2275
|
+
key: "next"
|
|
2276
|
+
},
|
|
2277
|
+
fullscreen: {
|
|
2278
|
+
type: "default",
|
|
2279
|
+
key: "fullscreen"
|
|
2280
|
+
},
|
|
2281
|
+
print: context.meta.availablePages.print ? {
|
|
2282
|
+
type: "default",
|
|
2283
|
+
key: "print"
|
|
2284
|
+
} : null,
|
|
2285
|
+
exportPdf: context.meta.availableExports.pdf ? {
|
|
2286
|
+
type: "default",
|
|
2287
|
+
key: "exportPdf"
|
|
2288
|
+
} : null,
|
|
2289
|
+
exportPng: context.meta.availableExports.png ? {
|
|
2290
|
+
type: "default",
|
|
2291
|
+
key: "exportPng"
|
|
2292
|
+
} : null
|
|
2293
|
+
};
|
|
2265
2294
|
}
|
|
2266
2295
|
function resolveViewerControlItems(options, context) {
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
return items.filter((item) => Boolean(item)).map((item) => applyViewerControlLabels(item, options.labels));
|
|
2296
|
+
const defaults = buildViewerControlDefaults(context);
|
|
2297
|
+
const baseItems = [
|
|
2298
|
+
defaults.back,
|
|
2299
|
+
defaults.previous,
|
|
2300
|
+
defaults.position,
|
|
2301
|
+
defaults.next,
|
|
2302
|
+
defaults.fullscreen,
|
|
2303
|
+
defaults.print,
|
|
2304
|
+
defaults.exportPdf,
|
|
2305
|
+
defaults.exportPng
|
|
2306
|
+
];
|
|
2307
|
+
return (typeof options.items === "function" ? options.items(defaults, context) : options.items ?? [
|
|
2308
|
+
...resolveViewerControlSlotItems(options.before, context),
|
|
2309
|
+
...filterHiddenDefaultItems(baseItems, options.hidden),
|
|
2310
|
+
...resolveViewerControlSlotItems(options.after, context)
|
|
2311
|
+
]).filter((item) => Boolean(item)).map((item) => applyViewerControlLabels(item, options.labels));
|
|
2284
2312
|
}
|
|
2285
2313
|
function renderViewerControlItem(item, options, context) {
|
|
2286
|
-
|
|
2287
|
-
|
|
2314
|
+
const renderDefault = () => renderBaseViewerControlItem(item, options, context);
|
|
2315
|
+
return options.renderItem?.(item, context, renderDefault) ?? renderDefault();
|
|
2288
2316
|
}
|
|
2289
2317
|
function renderBaseViewerControlItem(item, options, context) {
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2318
|
+
if (item.type === "link") {
|
|
2319
|
+
const ariaLabel = item.icon ? String(item.attributes?.["aria-label"] ?? item.label) : void 0;
|
|
2320
|
+
return jsx("a", {
|
|
2321
|
+
...linkAttributes(item.attributes),
|
|
2322
|
+
...safeHref(item.href) ? { href: safeHref(item.href) } : {},
|
|
2323
|
+
...item.download ? { download: item.download } : {},
|
|
2324
|
+
...classAttribute(options.itemClassName, item.className),
|
|
2325
|
+
...item.icon ? {
|
|
2326
|
+
"aria-label": ariaLabel,
|
|
2327
|
+
title: ariaLabel
|
|
2328
|
+
} : {},
|
|
2329
|
+
children: item.icon ? renderControlIcon(item.icon) : item.label
|
|
2330
|
+
});
|
|
2331
|
+
}
|
|
2332
|
+
if (item.type === "render") return item.render({ context });
|
|
2333
|
+
return renderDefaultViewerControlItem(item, options, context);
|
|
2305
2334
|
}
|
|
2306
2335
|
function renderDefaultViewerControlItem(item, options, context) {
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2336
|
+
const itemProps = {
|
|
2337
|
+
...linkAttributes(item.attributes),
|
|
2338
|
+
...classAttribute(options.itemClassName, item.className)
|
|
2339
|
+
};
|
|
2340
|
+
switch (item.key) {
|
|
2341
|
+
case "back": return jsx("a", {
|
|
2342
|
+
...itemProps,
|
|
2343
|
+
href: context.mountPath,
|
|
2344
|
+
"data-hono-decks-back-link": true,
|
|
2345
|
+
...item.label === void 0 ? {
|
|
2346
|
+
"aria-label": controlIconLabel("deck-list"),
|
|
2347
|
+
title: controlIconLabel("deck-list")
|
|
2348
|
+
} : {},
|
|
2349
|
+
children: item.label ?? renderControlIcon("deck-list")
|
|
2350
|
+
});
|
|
2351
|
+
case "previous": return renderIconButton("previous", item, itemProps);
|
|
2352
|
+
case "position": return jsx("span", {
|
|
2353
|
+
...itemProps,
|
|
2354
|
+
"data-slide-position": true,
|
|
2355
|
+
"data-hono-decks-position": true,
|
|
2356
|
+
children: item.label ?? "1 / ?"
|
|
2357
|
+
});
|
|
2358
|
+
case "next": return renderIconButton("next", item, itemProps);
|
|
2359
|
+
case "fullscreen": return renderIconButton("fullscreen", item, itemProps);
|
|
2360
|
+
case "print": return jsx("a", {
|
|
2361
|
+
...itemProps,
|
|
2362
|
+
href: context.meta.paths.print,
|
|
2363
|
+
"data-hono-decks-print": true,
|
|
2364
|
+
...item.label === void 0 ? {
|
|
2365
|
+
"aria-label": controlIconLabel("print"),
|
|
2366
|
+
title: controlIconLabel("print")
|
|
2367
|
+
} : {},
|
|
2368
|
+
children: item.label ?? renderControlIcon("print")
|
|
2369
|
+
});
|
|
2370
|
+
case "exportPdf": return jsx("a", {
|
|
2371
|
+
...itemProps,
|
|
2372
|
+
href: context.meta.paths.exportPdf,
|
|
2373
|
+
download: `${safeFilename(context.meta.title)}.pdf`,
|
|
2374
|
+
"data-hono-decks-export": "pdf",
|
|
2375
|
+
...item.label === void 0 ? {
|
|
2376
|
+
"aria-label": controlIconLabel("export-pdf"),
|
|
2377
|
+
title: controlIconLabel("export-pdf")
|
|
2378
|
+
} : {},
|
|
2379
|
+
children: item.label ?? renderControlIcon("export-pdf")
|
|
2380
|
+
});
|
|
2381
|
+
case "exportPng": return jsx("a", {
|
|
2382
|
+
...itemProps,
|
|
2383
|
+
href: context.meta.paths.exportPng,
|
|
2384
|
+
download: `${safeFilename(context.meta.title)}.png`,
|
|
2385
|
+
"data-hono-decks-export": "png",
|
|
2386
|
+
...item.label === void 0 ? {
|
|
2387
|
+
"aria-label": controlIconLabel("export-png"),
|
|
2388
|
+
title: controlIconLabel("export-png")
|
|
2389
|
+
} : {},
|
|
2390
|
+
children: item.label ?? renderControlIcon("export-png")
|
|
2391
|
+
});
|
|
2392
|
+
}
|
|
2360
2393
|
}
|
|
2361
2394
|
function renderIconButton(action, item, itemProps) {
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2395
|
+
const iconName = controlIconNameForKey(action);
|
|
2396
|
+
const label = controlIconLabel(iconName);
|
|
2397
|
+
return jsx("button", {
|
|
2398
|
+
...itemProps,
|
|
2399
|
+
type: "button",
|
|
2400
|
+
"data-action": action,
|
|
2401
|
+
"data-hono-decks-navigation-control": action,
|
|
2402
|
+
...item.label === void 0 ? {
|
|
2403
|
+
"aria-label": label,
|
|
2404
|
+
title: label
|
|
2405
|
+
} : {},
|
|
2406
|
+
children: item.label ?? renderControlIcon(iconName)
|
|
2407
|
+
});
|
|
2372
2408
|
}
|
|
2373
2409
|
function controlIconNameForKey(key) {
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
return "print";
|
|
2385
|
-
case "exportPdf":
|
|
2386
|
-
return "export-pdf";
|
|
2387
|
-
case "exportPng":
|
|
2388
|
-
return "export-png";
|
|
2389
|
-
case "position":
|
|
2390
|
-
return "viewer";
|
|
2391
|
-
}
|
|
2410
|
+
switch (key) {
|
|
2411
|
+
case "back": return "deck-list";
|
|
2412
|
+
case "previous": return "previous";
|
|
2413
|
+
case "next": return "next";
|
|
2414
|
+
case "fullscreen": return "fullscreen";
|
|
2415
|
+
case "print": return "print";
|
|
2416
|
+
case "exportPdf": return "export-pdf";
|
|
2417
|
+
case "exportPng": return "export-png";
|
|
2418
|
+
case "position": return "viewer";
|
|
2419
|
+
}
|
|
2392
2420
|
}
|
|
2393
2421
|
function controlsClassName(options) {
|
|
2394
|
-
|
|
2422
|
+
return ["hono-decks-viewer-controls", options.className].filter(Boolean).join(" ");
|
|
2395
2423
|
}
|
|
2396
2424
|
function resolveViewerControlSlotItems(items, context) {
|
|
2397
|
-
|
|
2425
|
+
return typeof items === "function" ? items(context) : items ?? [];
|
|
2398
2426
|
}
|
|
2399
2427
|
function filterHiddenDefaultItems(items, hidden) {
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2428
|
+
if (!hidden?.length) return items;
|
|
2429
|
+
const hiddenKeys = new Set(hidden);
|
|
2430
|
+
return items.filter((item) => !item || item.type !== "default" || !hiddenKeys.has(item.key));
|
|
2403
2431
|
}
|
|
2404
2432
|
function applyViewerControlLabels(item, labels) {
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2433
|
+
if (item.type !== "default" || item.label !== void 0) return item;
|
|
2434
|
+
const label = labels?.[item.key];
|
|
2435
|
+
return label === void 0 ? item : {
|
|
2436
|
+
...item,
|
|
2437
|
+
label
|
|
2438
|
+
};
|
|
2408
2439
|
}
|
|
2409
2440
|
function linkAttributes(attributes) {
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2441
|
+
const props = {};
|
|
2442
|
+
for (const [name, value] of Object.entries(attributes ?? {})) {
|
|
2443
|
+
if (!isSafeAttributeName(name) || value === false || value === void 0) continue;
|
|
2444
|
+
props[name] = value;
|
|
2445
|
+
}
|
|
2446
|
+
return props;
|
|
2416
2447
|
}
|
|
2417
2448
|
function controlAttributes(attributes) {
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2449
|
+
const props = linkAttributes(attributes);
|
|
2450
|
+
delete props.class;
|
|
2451
|
+
delete props["aria-label"];
|
|
2452
|
+
delete props["data-hono-decks-viewer-controls"];
|
|
2453
|
+
return props;
|
|
2423
2454
|
}
|
|
2424
2455
|
function classAttribute(...classNames) {
|
|
2425
|
-
|
|
2426
|
-
|
|
2456
|
+
const className = classNames.filter(Boolean).join(" ");
|
|
2457
|
+
return className ? { class: className } : {};
|
|
2427
2458
|
}
|
|
2428
2459
|
function isSafeAttributeName(value) {
|
|
2429
|
-
|
|
2460
|
+
return /^[A-Za-z_:][A-Za-z0-9:_.-]*$/.test(value) && !/^on/i.test(value);
|
|
2430
2461
|
}
|
|
2431
2462
|
function safeHref(value) {
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
}
|
|
2439
|
-
|
|
2440
|
-
|
|
2463
|
+
const trimmed = value.trim();
|
|
2464
|
+
if (trimmed.length === 0) return void 0;
|
|
2465
|
+
if (/^(https?:|mailto:|tel:)/i.test(trimmed)) return value;
|
|
2466
|
+
if (/^(javascript:|data:|vbscript:)/i.test(trimmed)) return void 0;
|
|
2467
|
+
if (/^[^/?#.]+:/i.test(trimmed)) return void 0;
|
|
2468
|
+
return value;
|
|
2469
|
+
}
|
|
2470
|
+
//#endregion
|
|
2471
|
+
//#region src/server/external-embed.ts
|
|
2441
2472
|
async function renderDeckExternalEmbedResponse(input) {
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
statusText: response.statusText,
|
|
2487
|
-
headers
|
|
2488
|
-
});
|
|
2473
|
+
const { c, deck, slug, mountPath } = input.context;
|
|
2474
|
+
const enabled = input.options.enabled;
|
|
2475
|
+
if (enabled === false || typeof enabled === "function" && !await enabled(input.context)) return c.json({
|
|
2476
|
+
error: "Embed route not found",
|
|
2477
|
+
slug
|
|
2478
|
+
}, 404);
|
|
2479
|
+
const document = await resolveDeckDocument({
|
|
2480
|
+
c,
|
|
2481
|
+
surface: "embed",
|
|
2482
|
+
deck,
|
|
2483
|
+
slug,
|
|
2484
|
+
mountPath,
|
|
2485
|
+
title: deck.meta.title ?? slug
|
|
2486
|
+
}, input.document, input.options.document);
|
|
2487
|
+
const viewer = await createDeckViewerEmbed({
|
|
2488
|
+
deck,
|
|
2489
|
+
mountPath,
|
|
2490
|
+
...typeof input.options.viewer === "function" ? await input.options.viewer(input.context) : input.options.viewer,
|
|
2491
|
+
availablePages: input.availablePages,
|
|
2492
|
+
nonce: document.nonce
|
|
2493
|
+
});
|
|
2494
|
+
const renderInput = {
|
|
2495
|
+
...input.context,
|
|
2496
|
+
viewer,
|
|
2497
|
+
document
|
|
2498
|
+
};
|
|
2499
|
+
const body = input.options.render ? await input.options.render(renderInput) : viewer.embed;
|
|
2500
|
+
const html = await renderExternalEmbedDocument({
|
|
2501
|
+
title: deck.meta.title ?? slug,
|
|
2502
|
+
body: await renderJsxValue(body),
|
|
2503
|
+
document,
|
|
2504
|
+
pageStyle: input.options.pageStyle,
|
|
2505
|
+
robots: input.options.robots
|
|
2506
|
+
});
|
|
2507
|
+
const frameAncestorsValue = typeof input.options.frameAncestors === "function" ? await input.options.frameAncestors(input.context) : input.options.frameAncestors;
|
|
2508
|
+
const response = c.html(html);
|
|
2509
|
+
const headers = new Headers(response.headers);
|
|
2510
|
+
headers.delete("x-frame-options");
|
|
2511
|
+
headers.set("content-security-policy", withFrameAncestors(headers.get("content-security-policy"), normalizeFrameAncestors(frameAncestorsValue)));
|
|
2512
|
+
return new Response(response.body, {
|
|
2513
|
+
status: response.status,
|
|
2514
|
+
statusText: response.statusText,
|
|
2515
|
+
headers
|
|
2516
|
+
});
|
|
2489
2517
|
}
|
|
2490
2518
|
async function renderExternalEmbedDocument(input) {
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
<html lang="${
|
|
2519
|
+
const nonceAttribute = documentNonceAttribute(input.document.nonce);
|
|
2520
|
+
const robots = input.robots === false ? "" : `<meta name="robots" content="${escapeHtml$1(input.robots ?? "noindex")}"/>`;
|
|
2521
|
+
return `<!doctype html>
|
|
2522
|
+
<html lang="${escapeHtml$1(input.document.lang)}" data-hono-decks-external-embed-document>
|
|
2495
2523
|
<head>
|
|
2496
2524
|
<meta charset="utf-8" />
|
|
2497
2525
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
2498
2526
|
${robots}
|
|
2499
|
-
<title>${
|
|
2527
|
+
<title>${escapeHtml$1(input.title)}</title>
|
|
2500
2528
|
<style id="hono-decks-external-embed-css"${nonceAttribute}>${externalEmbedPageStyle()}${input.pageStyle ?? ""}</style>
|
|
2501
2529
|
${input.document.head}
|
|
2502
2530
|
</head>
|
|
@@ -2504,384 +2532,444 @@ async function renderExternalEmbedDocument(input) {
|
|
|
2504
2532
|
</html>`;
|
|
2505
2533
|
}
|
|
2506
2534
|
function normalizeFrameAncestors(value) {
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
}
|
|
2521
|
-
}
|
|
2522
|
-
return [...ancestors];
|
|
2535
|
+
const values = Array.isArray(value) ? value : value?.split(/[\s,]+/) ?? [];
|
|
2536
|
+
const ancestors = /* @__PURE__ */ new Set(["'self'"]);
|
|
2537
|
+
for (const candidate of values) {
|
|
2538
|
+
const token = candidate.trim();
|
|
2539
|
+
if (!token || token === "'self'") continue;
|
|
2540
|
+
if (token === "*") return ["*"];
|
|
2541
|
+
if (token === "'none'") return ["'none'"];
|
|
2542
|
+
try {
|
|
2543
|
+
const url = new URL(token);
|
|
2544
|
+
if ((url.protocol === "https:" || url.protocol === "http:") && !url.username && !url.password) ancestors.add(url.origin);
|
|
2545
|
+
} catch {}
|
|
2546
|
+
}
|
|
2547
|
+
return [...ancestors];
|
|
2523
2548
|
}
|
|
2524
2549
|
function withFrameAncestors(currentPolicy, ancestors) {
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2550
|
+
const directives = (currentPolicy ?? "").split(";").map((directive) => directive.trim()).filter((directive) => directive && !/^frame-ancestors(?:\s|$)/i.test(directive));
|
|
2551
|
+
directives.push(`frame-ancestors ${ancestors.join(" ")}`);
|
|
2552
|
+
return directives.join("; ");
|
|
2528
2553
|
}
|
|
2529
2554
|
function externalEmbedPageStyle() {
|
|
2530
|
-
|
|
2555
|
+
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
2556
|
}
|
|
2532
|
-
function
|
|
2533
|
-
|
|
2557
|
+
function escapeHtml$1(value) {
|
|
2558
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
2534
2559
|
}
|
|
2535
|
-
|
|
2536
|
-
|
|
2560
|
+
//#endregion
|
|
2561
|
+
//#region src/server/router.ts
|
|
2537
2562
|
function decksRouter(options) {
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2563
|
+
const router = new Hono();
|
|
2564
|
+
if (options.clientEntryAsset) router.get(normalizeClientEntryAssetPath(options.clientEntryAssetPath), serveDecksClientEntry(options.clientEntryAsset));
|
|
2565
|
+
for (const extension of options.extensions ?? []) router.route(extension.path, extension.router);
|
|
2566
|
+
router.get("/", async (c) => {
|
|
2567
|
+
const dev = await isDevEnabled(c, options);
|
|
2568
|
+
const decks = (await options.source.listDecks(c)).filter((deck) => dev || !deck.draft);
|
|
2569
|
+
const mountPath = c.req.path.replace(/\/$/, "");
|
|
2570
|
+
const pageInput = {
|
|
2571
|
+
c,
|
|
2572
|
+
surface: "index",
|
|
2573
|
+
mountPath,
|
|
2574
|
+
dev,
|
|
2575
|
+
decks
|
|
2576
|
+
};
|
|
2577
|
+
if (!await isPageSurfaceEnabled(options, pageInput)) return c.json({ error: "Deck index not found" }, 404);
|
|
2578
|
+
const indexOptions = options.pages?.index === false ? void 0 : options.pages?.index;
|
|
2579
|
+
const title = await resolveIndexTitle(indexOptions?.title, pageInput);
|
|
2580
|
+
const document = await resolveRouterDocument(c, options, "index", mountPath, void 0, title);
|
|
2581
|
+
const defaultContentHtml = renderDeckIndexContent(decks, mountPath);
|
|
2582
|
+
const defaultContent = raw(defaultContentHtml);
|
|
2583
|
+
const content = indexOptions?.render ? await renderJsxValue(indexOptions.render({
|
|
2584
|
+
...pageInput,
|
|
2585
|
+
title,
|
|
2586
|
+
document,
|
|
2587
|
+
defaultContent
|
|
2588
|
+
})) : defaultContentHtml;
|
|
2589
|
+
return c.html(renderDeckIndex(content, title, document));
|
|
2590
|
+
});
|
|
2591
|
+
router.get("/:slug/assets/*", async (c) => {
|
|
2592
|
+
const slug = c.req.param("slug");
|
|
2593
|
+
const assetPath = extractAssetPath(c.req.path, slug);
|
|
2594
|
+
const deck = await options.source.getCompiledDeck(c, slug);
|
|
2595
|
+
if (!deck || !await isDevEnabled(c, options) && deck.meta.draft) return c.json({
|
|
2596
|
+
error: "Asset not found",
|
|
2597
|
+
slug,
|
|
2598
|
+
assetPath
|
|
2599
|
+
}, 404);
|
|
2600
|
+
const response = await options.source.getAsset?.(c, slug, assetPath);
|
|
2601
|
+
if (!response) return c.json({
|
|
2602
|
+
error: "Asset not found",
|
|
2603
|
+
slug,
|
|
2604
|
+
assetPath
|
|
2605
|
+
}, 404);
|
|
2606
|
+
return response;
|
|
2607
|
+
});
|
|
2608
|
+
router.get("/:slug/render", async (c) => {
|
|
2609
|
+
const slug = c.req.param("slug");
|
|
2610
|
+
const deck = await options.source.getCompiledDeck(c, slug);
|
|
2611
|
+
const dev = await isDevEnabled(c, options);
|
|
2612
|
+
if (!deck || !dev && deck.meta.draft) return c.json({
|
|
2613
|
+
error: "Deck not found",
|
|
2614
|
+
slug
|
|
2615
|
+
}, 404);
|
|
2616
|
+
const mountPath = stripPathSuffix(c.req.path, `/${slug}/render`);
|
|
2617
|
+
if (!await isPageSurfaceEnabled(options, {
|
|
2618
|
+
c,
|
|
2619
|
+
surface: "render",
|
|
2620
|
+
mountPath,
|
|
2621
|
+
dev,
|
|
2622
|
+
deck,
|
|
2623
|
+
slug
|
|
2624
|
+
})) return c.json({
|
|
2625
|
+
error: "Deck route not found",
|
|
2626
|
+
slug,
|
|
2627
|
+
surface: "render"
|
|
2628
|
+
}, 404);
|
|
2629
|
+
const clientEntry = options.clientEntry ?? resolveGeneratedClientEntryUrl(options, mountPath);
|
|
2630
|
+
try {
|
|
2631
|
+
const document = await resolveRouterDocument(c, options, "render", mountPath, deck);
|
|
2632
|
+
return c.html(await renderCompiledDeckPageAsync({
|
|
2633
|
+
deck,
|
|
2634
|
+
mountPath,
|
|
2635
|
+
style: options.style,
|
|
2636
|
+
components: options.components,
|
|
2637
|
+
clientEntry,
|
|
2638
|
+
liveReloadPath: dev ? options.liveReloadPath?.(slug, mountPath) : void 0,
|
|
2639
|
+
document
|
|
2640
|
+
}));
|
|
2641
|
+
} catch (error) {
|
|
2642
|
+
const message = error instanceof RenderError ? error.message : `Render failed in ${deck.sourcePath}: ${formatErrorMessage(error)}`;
|
|
2643
|
+
return c.text(message, 500);
|
|
2644
|
+
}
|
|
2645
|
+
});
|
|
2646
|
+
router.get("/:slug/print", async (c) => {
|
|
2647
|
+
const slug = c.req.param("slug");
|
|
2648
|
+
const deck = await options.source.getCompiledDeck(c, slug);
|
|
2649
|
+
const dev = await isDevEnabled(c, options);
|
|
2650
|
+
if (!deck || !dev && deck.meta.draft) return c.json({
|
|
2651
|
+
error: "Deck not found",
|
|
2652
|
+
slug
|
|
2653
|
+
}, 404);
|
|
2654
|
+
const mountPath = stripPathSuffix(c.req.path, `/${slug}/print`);
|
|
2655
|
+
if (!await isPageSurfaceEnabled(options, {
|
|
2656
|
+
c,
|
|
2657
|
+
surface: "print",
|
|
2658
|
+
mountPath,
|
|
2659
|
+
dev,
|
|
2660
|
+
deck,
|
|
2661
|
+
slug
|
|
2662
|
+
})) return c.json({
|
|
2663
|
+
error: "Deck route not found",
|
|
2664
|
+
slug,
|
|
2665
|
+
surface: "print"
|
|
2666
|
+
}, 404);
|
|
2667
|
+
try {
|
|
2668
|
+
const document = await resolveRouterDocument(c, options, "print", mountPath, deck);
|
|
2669
|
+
return c.html(await renderCompiledDeckPageAsync({
|
|
2670
|
+
deck,
|
|
2671
|
+
mountPath,
|
|
2672
|
+
style: options.style,
|
|
2673
|
+
components: options.components,
|
|
2674
|
+
printPreview: true,
|
|
2675
|
+
document
|
|
2676
|
+
}));
|
|
2677
|
+
} catch (error) {
|
|
2678
|
+
const message = error instanceof RenderError ? error.message : `Render failed in ${deck.sourcePath}: ${formatErrorMessage(error)}`;
|
|
2679
|
+
return c.text(message, 500);
|
|
2680
|
+
}
|
|
2681
|
+
});
|
|
2682
|
+
if (isPdfExportEnabled(options.export)) router.get("/:slug/export.pdf", async (c) => renderDeckBrowserExport(c, {
|
|
2683
|
+
...options,
|
|
2684
|
+
dev: await isDevEnabled(c, options)
|
|
2685
|
+
}, "pdf"));
|
|
2686
|
+
if (isPngExportEnabled(options.export)) router.get("/:slug/export.png", async (c) => renderDeckBrowserExport(c, {
|
|
2687
|
+
...options,
|
|
2688
|
+
dev: await isDevEnabled(c, options)
|
|
2689
|
+
}, "png"));
|
|
2690
|
+
const embedOptions = options.embed;
|
|
2691
|
+
if (embedOptions) router.get("/:slug/embed", async (c) => {
|
|
2692
|
+
const slug = c.req.param("slug");
|
|
2693
|
+
const deck = await options.source.getCompiledDeck(c, slug);
|
|
2694
|
+
const dev = await isDevEnabled(c, options);
|
|
2695
|
+
if (!deck || !dev && deck.meta.draft) return c.json({
|
|
2696
|
+
error: "Deck not found",
|
|
2697
|
+
slug
|
|
2698
|
+
}, 404);
|
|
2699
|
+
const mountPath = stripPathSuffix(c.req.path, `/${slug}/embed`);
|
|
2700
|
+
const availablePages = await resolveViewerAvailablePages(c, options, deck, slug, mountPath, dev);
|
|
2701
|
+
return renderDeckExternalEmbedResponse({
|
|
2702
|
+
context: {
|
|
2703
|
+
c,
|
|
2704
|
+
deck,
|
|
2705
|
+
slug,
|
|
2706
|
+
mountPath
|
|
2707
|
+
},
|
|
2708
|
+
options: embedOptions,
|
|
2709
|
+
document: options.document,
|
|
2710
|
+
availablePages
|
|
2711
|
+
});
|
|
2712
|
+
});
|
|
2713
|
+
router.get("/:slug/presentation", async (c) => {
|
|
2714
|
+
const slug = c.req.param("slug");
|
|
2715
|
+
const deck = await options.source.getCompiledDeck(c, slug);
|
|
2716
|
+
const dev = await isDevEnabled(c, options);
|
|
2717
|
+
if (!deck || !dev && deck.meta.draft) return c.json({
|
|
2718
|
+
error: "Deck not found",
|
|
2719
|
+
slug
|
|
2720
|
+
}, 404);
|
|
2721
|
+
const mountPath = stripPathSuffix(c.req.path, `/${slug}/presentation`);
|
|
2722
|
+
if (!await isPageSurfaceEnabled(options, {
|
|
2723
|
+
c,
|
|
2724
|
+
surface: "presentation",
|
|
2725
|
+
mountPath,
|
|
2726
|
+
dev,
|
|
2727
|
+
deck,
|
|
2728
|
+
slug
|
|
2729
|
+
})) return c.json({
|
|
2730
|
+
error: "Deck route not found",
|
|
2731
|
+
slug,
|
|
2732
|
+
surface: "presentation"
|
|
2733
|
+
}, 404);
|
|
2734
|
+
const clientEntry = options.clientEntry ?? resolveGeneratedClientEntryUrl(options, mountPath);
|
|
2735
|
+
try {
|
|
2736
|
+
const document = await resolveRouterDocument(c, options, "presentation", mountPath, deck);
|
|
2737
|
+
return c.html(await renderCompiledDeckPageAsync({
|
|
2738
|
+
deck,
|
|
2739
|
+
mountPath,
|
|
2740
|
+
style: options.style,
|
|
2741
|
+
components: options.components,
|
|
2742
|
+
clientEntry,
|
|
2743
|
+
speakerNotes: false,
|
|
2744
|
+
liveReloadPath: dev ? options.liveReloadPath?.(slug, mountPath) : void 0,
|
|
2745
|
+
document
|
|
2746
|
+
}));
|
|
2747
|
+
} catch (error) {
|
|
2748
|
+
const message = error instanceof RenderError ? error.message : `Render failed in ${deck.sourcePath}: ${formatErrorMessage(error)}`;
|
|
2749
|
+
return c.text(message, 500);
|
|
2750
|
+
}
|
|
2751
|
+
});
|
|
2752
|
+
router.get("/:slug/presenter", async (c) => {
|
|
2753
|
+
const slug = c.req.param("slug");
|
|
2754
|
+
const deck = await options.source.getCompiledDeck(c, slug);
|
|
2755
|
+
const dev = await isDevEnabled(c, options);
|
|
2756
|
+
if (!deck || !dev && deck.meta.draft) return c.json({
|
|
2757
|
+
error: "Deck not found",
|
|
2758
|
+
slug
|
|
2759
|
+
}, 404);
|
|
2760
|
+
const mountPath = stripPathSuffix(c.req.path, `/${slug}/presenter`);
|
|
2761
|
+
if (!await isPageSurfaceEnabled(options, {
|
|
2762
|
+
c,
|
|
2763
|
+
surface: "presenter",
|
|
2764
|
+
mountPath,
|
|
2765
|
+
dev,
|
|
2766
|
+
deck,
|
|
2767
|
+
slug
|
|
2768
|
+
})) return c.json({
|
|
2769
|
+
error: "Presenter route not found",
|
|
2770
|
+
slug
|
|
2771
|
+
}, 404);
|
|
2772
|
+
if (!await isPresenterEnabled(c, options, deck, slug, mountPath)) return c.json({
|
|
2773
|
+
error: "Presenter route not found",
|
|
2774
|
+
slug
|
|
2775
|
+
}, 404);
|
|
2776
|
+
try {
|
|
2777
|
+
const document = await resolveRouterDocument(c, options, "presenter", mountPath, deck);
|
|
2778
|
+
return c.html(await renderPresenterPageAsync({
|
|
2779
|
+
deck,
|
|
2780
|
+
mountPath,
|
|
2781
|
+
presenterStateQuery: paginationQueryFromRequest(c),
|
|
2782
|
+
style: options.style,
|
|
2783
|
+
components: options.components,
|
|
2784
|
+
document
|
|
2785
|
+
}));
|
|
2786
|
+
} catch (error) {
|
|
2787
|
+
const message = error instanceof RenderError ? error.message : `Render failed in ${deck.sourcePath}: ${formatErrorMessage(error)}`;
|
|
2788
|
+
return c.text(message, 500);
|
|
2789
|
+
}
|
|
2790
|
+
});
|
|
2791
|
+
router.get("/:slug", async (c) => {
|
|
2792
|
+
const slug = c.req.param("slug");
|
|
2793
|
+
const deck = await options.source.getCompiledDeck(c, slug);
|
|
2794
|
+
const dev = await isDevEnabled(c, options);
|
|
2795
|
+
if (!deck || !dev && deck.meta.draft) return c.json({
|
|
2796
|
+
error: "Deck not found",
|
|
2797
|
+
slug
|
|
2798
|
+
}, 404);
|
|
2799
|
+
const mountPath = stripPathSuffix(c.req.path, `/${slug}`);
|
|
2800
|
+
if (!await isPageSurfaceEnabled(options, {
|
|
2801
|
+
c,
|
|
2802
|
+
surface: "viewer",
|
|
2803
|
+
mountPath,
|
|
2804
|
+
dev,
|
|
2805
|
+
deck,
|
|
2806
|
+
slug
|
|
2807
|
+
})) return c.json({
|
|
2808
|
+
error: "Deck route not found",
|
|
2809
|
+
slug,
|
|
2810
|
+
surface: "viewer"
|
|
2811
|
+
}, 404);
|
|
2812
|
+
const availablePages = await resolveViewerAvailablePages(c, options, deck, slug, mountPath, dev);
|
|
2813
|
+
return c.html(await renderDeckViewerPage({
|
|
2814
|
+
c,
|
|
2815
|
+
deck,
|
|
2816
|
+
mountPath,
|
|
2817
|
+
viewerStateQuery: paginationQueryFromRequest(c),
|
|
2818
|
+
viewer: {
|
|
2819
|
+
...options.viewer,
|
|
2820
|
+
controls: await resolveViewerControls(c, options, deck, slug, mountPath)
|
|
2821
|
+
},
|
|
2822
|
+
availablePages,
|
|
2823
|
+
exportOptions: options.export,
|
|
2824
|
+
document: options.document
|
|
2825
|
+
}));
|
|
2826
|
+
});
|
|
2827
|
+
return router;
|
|
2742
2828
|
}
|
|
2743
2829
|
function paginationQueryFromRequest(c) {
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2830
|
+
const source = new URL(c.req.url);
|
|
2831
|
+
const params = new URLSearchParams();
|
|
2832
|
+
const slide = positiveIntegerParam(source.searchParams.get("slide"));
|
|
2833
|
+
const step = nonNegativeIntegerParam(source.searchParams.get("step"));
|
|
2834
|
+
if (slide !== void 0) params.set("slide", String(slide));
|
|
2835
|
+
if (step !== void 0) params.set("step", String(step));
|
|
2836
|
+
const query = params.toString();
|
|
2837
|
+
return query ? `?${query}` : "";
|
|
2752
2838
|
}
|
|
2753
2839
|
function positiveIntegerParam(value) {
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2840
|
+
if (value === null) return void 0;
|
|
2841
|
+
const parsed = Number(value);
|
|
2842
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
|
|
2757
2843
|
}
|
|
2758
2844
|
function nonNegativeIntegerParam(value) {
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2845
|
+
if (value === null) return void 0;
|
|
2846
|
+
const parsed = Number(value);
|
|
2847
|
+
return Number.isInteger(parsed) && parsed >= 0 ? parsed : void 0;
|
|
2762
2848
|
}
|
|
2763
2849
|
function deckContext(options) {
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2850
|
+
return async (c, next) => {
|
|
2851
|
+
const slug = c.req.param("slug");
|
|
2852
|
+
if (!slug) return c.json({
|
|
2853
|
+
error: "Deck not found",
|
|
2854
|
+
slug: ""
|
|
2855
|
+
}, 404);
|
|
2856
|
+
const sourceContext = c;
|
|
2857
|
+
const deck = await options.source.getCompiledDeck(sourceContext, slug);
|
|
2858
|
+
const dev = await isDevEnabled(sourceContext, options);
|
|
2859
|
+
if (!deck || !dev && deck.meta.draft) return c.json({
|
|
2860
|
+
error: "Deck not found",
|
|
2861
|
+
slug
|
|
2862
|
+
}, 404);
|
|
2863
|
+
const mountPath = options.mountPath ?? inferMountPath(c.req.path, slug);
|
|
2864
|
+
const availablePages = await resolveViewerAvailablePages(sourceContext, options, deck, slug, mountPath, dev);
|
|
2865
|
+
const viewer = await createDeckViewerParts({
|
|
2866
|
+
deck,
|
|
2867
|
+
mountPath,
|
|
2868
|
+
controls: options.viewer?.controls,
|
|
2869
|
+
openGraph: options.viewer?.openGraph,
|
|
2870
|
+
availablePages
|
|
2871
|
+
});
|
|
2872
|
+
c.set("deck", deck);
|
|
2873
|
+
c.set("deckViewer", viewer);
|
|
2874
|
+
c.set("deckToc", viewer.slides);
|
|
2875
|
+
c.set("deckMeta", viewer.meta);
|
|
2876
|
+
await next();
|
|
2877
|
+
};
|
|
2786
2878
|
}
|
|
2787
2879
|
async function resolveViewerAvailablePages(c, options, deck, slug, mountPath, dev) {
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2880
|
+
return { print: await isPageSurfaceEnabled(options, {
|
|
2881
|
+
c,
|
|
2882
|
+
surface: "print",
|
|
2883
|
+
mountPath,
|
|
2884
|
+
dev,
|
|
2885
|
+
deck,
|
|
2886
|
+
slug
|
|
2887
|
+
}) };
|
|
2791
2888
|
}
|
|
2792
2889
|
async function isDevEnabled(c, options) {
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2890
|
+
if (typeof options.dev === "function") return Boolean(await options.dev({ c }));
|
|
2891
|
+
if (typeof options.dev === "boolean") return options.dev;
|
|
2892
|
+
return inferToolchainDevMode();
|
|
2796
2893
|
}
|
|
2797
2894
|
function inferToolchainDevMode() {
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2895
|
+
try {
|
|
2896
|
+
return process.env.NODE_ENV === "development";
|
|
2897
|
+
} catch {
|
|
2898
|
+
return false;
|
|
2899
|
+
}
|
|
2803
2900
|
}
|
|
2804
2901
|
async function resolveViewerControls(c, options, deck, slug, mountPath) {
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
nextControls.after = mergeControlSlotItems(nextControls.after, item, "after");
|
|
2817
|
-
}
|
|
2818
|
-
return nextControls;
|
|
2902
|
+
const controls = options.viewer?.controls;
|
|
2903
|
+
if (controls === false) return false;
|
|
2904
|
+
const presenterControl = await resolvePresenterViewerControl(c, options, deck, slug, mountPath);
|
|
2905
|
+
if (!presenterControl) return controls;
|
|
2906
|
+
const nextControls = controls ? { ...controls } : {};
|
|
2907
|
+
if (nextControls.items) return nextControls;
|
|
2908
|
+
const placement = presenterControl.placement ?? "after";
|
|
2909
|
+
const item = presenterControl.item;
|
|
2910
|
+
if (placement === "before") nextControls.before = mergeControlSlotItems(nextControls.before, item, "before");
|
|
2911
|
+
else nextControls.after = mergeControlSlotItems(nextControls.after, item, "after");
|
|
2912
|
+
return nextControls;
|
|
2819
2913
|
}
|
|
2820
2914
|
async function resolvePresenterViewerControl(c, options, deck, slug, mountPath) {
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2915
|
+
const presenter = options.presenter;
|
|
2916
|
+
if (!presenter || !presenter.viewerControl) return null;
|
|
2917
|
+
if (!await isPresenterEnabled(c, options, deck, slug, mountPath)) return null;
|
|
2918
|
+
const control = presenter.viewerControl === true ? {} : presenter.viewerControl;
|
|
2919
|
+
const presenterPath = createDeckPaths(mountPath, slug).presenter;
|
|
2920
|
+
return {
|
|
2921
|
+
item: {
|
|
2922
|
+
type: "link",
|
|
2923
|
+
key: control.key ?? "presenter",
|
|
2924
|
+
href: presenterPath,
|
|
2925
|
+
label: control.label ?? "Presenter",
|
|
2926
|
+
icon: control.icon ?? "presenter",
|
|
2927
|
+
className: control.className,
|
|
2928
|
+
attributes: control.attributes
|
|
2929
|
+
},
|
|
2930
|
+
placement: control.placement ?? "after"
|
|
2931
|
+
};
|
|
2838
2932
|
}
|
|
2839
2933
|
function mergeControlSlotItems(slot, item, placement) {
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2934
|
+
if (!slot) return [item];
|
|
2935
|
+
if (Array.isArray(slot)) return placement === "before" ? [item, ...slot] : [...slot, item];
|
|
2936
|
+
return (context) => {
|
|
2937
|
+
const items = slot(context);
|
|
2938
|
+
return placement === "before" ? [item, ...items] : [...items, item];
|
|
2939
|
+
};
|
|
2846
2940
|
}
|
|
2847
2941
|
async function isPresenterEnabled(c, options, deck, slug, mountPath) {
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
})
|
|
2863
|
-
);
|
|
2942
|
+
const presenter = options.presenter;
|
|
2943
|
+
if (presenter === false) return false;
|
|
2944
|
+
const enabled = presenter?.enabled;
|
|
2945
|
+
if (enabled === void 0) return true;
|
|
2946
|
+
if (typeof enabled === "boolean") return enabled;
|
|
2947
|
+
return Boolean(await enabled({
|
|
2948
|
+
c,
|
|
2949
|
+
deck,
|
|
2950
|
+
slug,
|
|
2951
|
+
mountPath: mountPath.replace(/\/$/, ""),
|
|
2952
|
+
dev: await isDevEnabled(c, options),
|
|
2953
|
+
presenterPath: createDeckPaths(mountPath, slug).presenter,
|
|
2954
|
+
presentationPath: createDeckPaths(mountPath, slug).presentation
|
|
2955
|
+
}));
|
|
2864
2956
|
}
|
|
2865
2957
|
function renderDeckIndexContent(decks, mountPath) {
|
|
2866
|
-
|
|
2867
|
-
|
|
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>
|
|
2958
|
+
const basePath = mountPath.replace(/\/$/, "");
|
|
2959
|
+
return `<main>
|
|
2874
2960
|
<h1>Hono Decks</h1>
|
|
2875
|
-
<ul>${
|
|
2961
|
+
<ul>${decks.map((deck) => {
|
|
2962
|
+
return `<li><a href="${`${basePath}/${encodeURIComponent(deck.slug)}`}">${escapeHtml(deck.title ?? deck.slug)}</a>${deck.description ? `<p>${escapeHtml(deck.description)}</p>` : ""}</li>`;
|
|
2963
|
+
}).join("")}</ul>
|
|
2876
2964
|
</main>`;
|
|
2877
2965
|
}
|
|
2878
2966
|
function renderDeckIndex(content, title, document) {
|
|
2879
|
-
|
|
2880
|
-
<html lang="${
|
|
2967
|
+
return `<!doctype html>
|
|
2968
|
+
<html lang="${escapeHtml(document.lang)}">
|
|
2881
2969
|
<head>
|
|
2882
2970
|
<meta charset="utf-8" />
|
|
2883
2971
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
2884
|
-
<title>${
|
|
2972
|
+
<title>${escapeHtml(title)}</title>
|
|
2885
2973
|
${document.head}
|
|
2886
2974
|
</head>
|
|
2887
2975
|
<body>
|
|
@@ -2890,273 +2978,289 @@ function renderDeckIndex(content, title, document) {
|
|
|
2890
2978
|
</html>`;
|
|
2891
2979
|
}
|
|
2892
2980
|
async function resolveIndexTitle(title, input) {
|
|
2893
|
-
|
|
2894
|
-
return resolved?.trim() || "Hono Decks";
|
|
2981
|
+
return (typeof title === "function" ? await title(input) : title)?.trim() || "Hono Decks";
|
|
2895
2982
|
}
|
|
2896
2983
|
async function isPageSurfaceEnabled(options, input) {
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2984
|
+
const configured = input.surface === "index" ? options.pages?.index === false ? false : options.pages?.index?.enabled : options.pages?.[input.surface];
|
|
2985
|
+
if (typeof configured === "function") return Boolean(await configured(input));
|
|
2986
|
+
return configured !== false;
|
|
2900
2987
|
}
|
|
2901
2988
|
async function resolveRouterDocument(c, options, surface, mountPath, deck, titleOverride) {
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
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
|
-
};
|
|
2989
|
+
const title = titleOverride ?? deck?.meta.title ?? deck?.slug ?? "Hono Decks";
|
|
2990
|
+
return resolveDeckDocument({
|
|
2991
|
+
c,
|
|
2992
|
+
surface,
|
|
2993
|
+
deck,
|
|
2994
|
+
slug: deck?.slug,
|
|
2995
|
+
mountPath,
|
|
2996
|
+
title: surface === "presenter" ? `${title} - Presenter` : title
|
|
2997
|
+
}, options.document);
|
|
2998
|
+
}
|
|
2999
|
+
function escapeHtml(value) {
|
|
3000
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
|
2939
3001
|
}
|
|
3002
|
+
function formatErrorMessage(error) {
|
|
3003
|
+
if (error instanceof Error) return error.message;
|
|
3004
|
+
return String(error);
|
|
3005
|
+
}
|
|
3006
|
+
//#endregion
|
|
3007
|
+
//#region src/server/define-decks.ts
|
|
3008
|
+
function defineDecks(options) {
|
|
3009
|
+
const manifest = Array.isArray(options.decks) ? { decks: options.decks } : options.manifest;
|
|
3010
|
+
if (!manifest) throw new Error("defineDecks requires either decks or manifest.");
|
|
3011
|
+
const source = manifestDeckSource(manifest);
|
|
3012
|
+
const baseOptions = {
|
|
3013
|
+
...options,
|
|
3014
|
+
source
|
|
3015
|
+
};
|
|
3016
|
+
return {
|
|
3017
|
+
source,
|
|
3018
|
+
router(overrides = {}) {
|
|
3019
|
+
const nextSource = overrides.source ?? source;
|
|
3020
|
+
return decksRouter(mergeDecksRouterOptions(baseOptions, {
|
|
3021
|
+
...overrides,
|
|
3022
|
+
source: nextSource
|
|
3023
|
+
}));
|
|
3024
|
+
}
|
|
3025
|
+
};
|
|
3026
|
+
}
|
|
3027
|
+
/** Defines the single configuration consumed by the CLI and runtime. */
|
|
2940
3028
|
function defineDecksConfig(config) {
|
|
2941
|
-
|
|
3029
|
+
return config;
|
|
2942
3030
|
}
|
|
3031
|
+
/** Applies app configuration to generated decks and returns the primary runtime API. */
|
|
2943
3032
|
function configureDecks(defined, config) {
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
3033
|
+
const mountPath = normalizeMountPath(config.mountPath);
|
|
3034
|
+
const source = config.source?.(defined.source) ?? defined.source;
|
|
3035
|
+
const configuredRouter = mergeDecksRouterOptions({
|
|
3036
|
+
source,
|
|
3037
|
+
...config.router
|
|
3038
|
+
}, { source });
|
|
3039
|
+
return {
|
|
3040
|
+
mountPath,
|
|
3041
|
+
source,
|
|
3042
|
+
router(overrides = {}) {
|
|
3043
|
+
return defined.router(mergeDecksRouterOptions(configuredRouter, overrides));
|
|
3044
|
+
},
|
|
3045
|
+
context(overrides = {}) {
|
|
3046
|
+
const merged = mergeDecksRouterOptions(configuredRouter, overrides);
|
|
3047
|
+
return deckContext({
|
|
3048
|
+
source,
|
|
3049
|
+
mountPath,
|
|
3050
|
+
dev: merged.dev,
|
|
3051
|
+
pages: merged.pages,
|
|
3052
|
+
viewer: merged.viewer
|
|
3053
|
+
});
|
|
3054
|
+
},
|
|
3055
|
+
paths(slug) {
|
|
3056
|
+
return createDeckPaths(mountPath, slug);
|
|
3057
|
+
}
|
|
3058
|
+
};
|
|
2970
3059
|
}
|
|
2971
3060
|
function mergeDecksRouterOptions(base, overrides) {
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
3061
|
+
const viewer = mergeViewerOptions(base.viewer, overrides.viewer);
|
|
3062
|
+
const presenter = mergePresenterOptions(base.presenter, overrides.presenter);
|
|
3063
|
+
const document = mergeDocumentOptions(base.document, overrides.document);
|
|
3064
|
+
const pages = mergePagesOptions(base.pages, overrides.pages);
|
|
3065
|
+
const embed = mergeExternalEmbedOptions(base.embed, overrides.embed);
|
|
3066
|
+
const exportOptions = overrides.export === false ? false : base.export === false ? overrides.export : base.export || overrides.export ? {
|
|
3067
|
+
...base.export,
|
|
3068
|
+
...overrides.export
|
|
3069
|
+
} : void 0;
|
|
3070
|
+
const components = base.components || overrides.components ? {
|
|
3071
|
+
...base.components,
|
|
3072
|
+
...overrides.components
|
|
3073
|
+
} : void 0;
|
|
3074
|
+
return {
|
|
3075
|
+
...base,
|
|
3076
|
+
...overrides,
|
|
3077
|
+
...viewer === void 0 ? {} : { viewer },
|
|
3078
|
+
...presenter === void 0 ? {} : { presenter },
|
|
3079
|
+
...document === void 0 ? {} : { document },
|
|
3080
|
+
...pages === void 0 ? {} : { pages },
|
|
3081
|
+
...embed === void 0 ? {} : { embed },
|
|
3082
|
+
...exportOptions === void 0 ? {} : { export: exportOptions },
|
|
3083
|
+
...components === void 0 ? {} : { components },
|
|
3084
|
+
source: overrides.source ?? base.source
|
|
3085
|
+
};
|
|
2991
3086
|
}
|
|
2992
3087
|
function mergePagesOptions(base, override) {
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3088
|
+
if (!base) return override;
|
|
3089
|
+
if (!override) return base;
|
|
3090
|
+
const index = override.index === false ? false : override.index === void 0 ? base.index : base.index === false || base.index === void 0 ? override.index : {
|
|
3091
|
+
...base.index,
|
|
3092
|
+
...override.index
|
|
3093
|
+
};
|
|
3094
|
+
return {
|
|
3095
|
+
...base,
|
|
3096
|
+
...override,
|
|
3097
|
+
...index === void 0 ? {} : { index }
|
|
3098
|
+
};
|
|
3001
3099
|
}
|
|
3002
3100
|
function mergeExternalEmbedOptions(base, override) {
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3101
|
+
if (override === false) return false;
|
|
3102
|
+
if (override === void 0) return base;
|
|
3103
|
+
if (base === false || base === void 0) return override;
|
|
3104
|
+
const viewer = typeof base.viewer === "object" && typeof override.viewer === "object" ? {
|
|
3105
|
+
...base.viewer,
|
|
3106
|
+
...override.viewer
|
|
3107
|
+
} : override.viewer ?? base.viewer;
|
|
3108
|
+
const document = base.document || override.document ? {
|
|
3109
|
+
...base.document,
|
|
3110
|
+
...override.document
|
|
3111
|
+
} : void 0;
|
|
3112
|
+
return {
|
|
3113
|
+
...base,
|
|
3114
|
+
...override,
|
|
3115
|
+
...viewer === void 0 ? {} : { viewer },
|
|
3116
|
+
...document === void 0 ? {} : { document }
|
|
3117
|
+
};
|
|
3014
3118
|
}
|
|
3015
3119
|
function mergeDocumentOptions(base, override) {
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3120
|
+
if (!base) return override;
|
|
3121
|
+
if (!override) return base;
|
|
3122
|
+
return {
|
|
3123
|
+
...base,
|
|
3124
|
+
...override,
|
|
3125
|
+
surfaces: base.surfaces || override.surfaces ? {
|
|
3126
|
+
...base.surfaces,
|
|
3127
|
+
...override.surfaces
|
|
3128
|
+
} : void 0
|
|
3129
|
+
};
|
|
3023
3130
|
}
|
|
3024
3131
|
function mergeViewerOptions(base, override) {
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3132
|
+
if (!base) return override;
|
|
3133
|
+
if (!override) return base;
|
|
3134
|
+
const controls = mergeControls(base.controls, override.controls);
|
|
3135
|
+
const openGraph = mergeOpenGraph(base.openGraph, override.openGraph);
|
|
3136
|
+
return {
|
|
3137
|
+
...base,
|
|
3138
|
+
...override,
|
|
3139
|
+
...controls === void 0 ? {} : { controls },
|
|
3140
|
+
...openGraph === void 0 ? {} : { openGraph }
|
|
3141
|
+
};
|
|
3035
3142
|
}
|
|
3036
3143
|
function mergeOpenGraph(base, override) {
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3144
|
+
if (override === void 0) return base;
|
|
3145
|
+
if (base === void 0 || typeof base === "boolean" || typeof override === "boolean") return override;
|
|
3146
|
+
return {
|
|
3147
|
+
...base,
|
|
3148
|
+
...override
|
|
3149
|
+
};
|
|
3040
3150
|
}
|
|
3041
3151
|
function mergeControls(base, override) {
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3152
|
+
if (override === void 0) return base;
|
|
3153
|
+
if (base === void 0 || base === false || override === false) return override;
|
|
3154
|
+
return {
|
|
3155
|
+
...base,
|
|
3156
|
+
...override,
|
|
3157
|
+
attributes: base.attributes || override.attributes ? {
|
|
3158
|
+
...base.attributes,
|
|
3159
|
+
...override.attributes
|
|
3160
|
+
} : void 0,
|
|
3161
|
+
labels: base.labels || override.labels ? {
|
|
3162
|
+
...base.labels,
|
|
3163
|
+
...override.labels
|
|
3164
|
+
} : void 0,
|
|
3165
|
+
hidden: base.hidden || override.hidden ? [.../* @__PURE__ */ new Set([...base.hidden ?? [], ...override.hidden ?? []])] : void 0,
|
|
3166
|
+
before: mergeControlSlots(base.before, override.before),
|
|
3167
|
+
after: mergeControlSlots(base.after, override.after)
|
|
3168
|
+
};
|
|
3053
3169
|
}
|
|
3054
3170
|
function mergeControlSlots(base, override) {
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3171
|
+
if (override === void 0) return base;
|
|
3172
|
+
if (base === void 0) return override;
|
|
3173
|
+
if (Array.isArray(base) && Array.isArray(override)) return [...base, ...override];
|
|
3174
|
+
return override;
|
|
3059
3175
|
}
|
|
3060
3176
|
function mergePresenterOptions(base, override) {
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3177
|
+
if (override === void 0) return base;
|
|
3178
|
+
if (override === false || base === false || base === void 0) return override;
|
|
3179
|
+
const viewerControl = typeof base.viewerControl === "object" && typeof override.viewerControl === "object" ? {
|
|
3180
|
+
...base.viewerControl,
|
|
3181
|
+
...override.viewerControl
|
|
3182
|
+
} : override.viewerControl ?? base.viewerControl;
|
|
3183
|
+
return {
|
|
3184
|
+
...base,
|
|
3185
|
+
...override,
|
|
3186
|
+
viewerControl
|
|
3187
|
+
};
|
|
3188
|
+
}
|
|
3189
|
+
//#endregion
|
|
3190
|
+
//#region src/source/r2-assets.ts
|
|
3068
3191
|
function withR2Assets(source, options) {
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3192
|
+
return {
|
|
3193
|
+
async listDecks(c) {
|
|
3194
|
+
return source.listDecks(c);
|
|
3195
|
+
},
|
|
3196
|
+
async getCompiledDeck(c, slug) {
|
|
3197
|
+
return source.getCompiledDeck(c, slug);
|
|
3198
|
+
},
|
|
3199
|
+
async getAsset(c, slug, assetPath) {
|
|
3200
|
+
const deck = await source.getCompiledDeck(c, slug);
|
|
3201
|
+
const asset = deck ? findRoutableAsset(deck.assets, slug, assetPath) : void 0;
|
|
3202
|
+
const bucket = asset ? await resolveBucket(options.bucket, c) : void 0;
|
|
3203
|
+
if (deck && asset && bucket) {
|
|
3204
|
+
const object = await bucket.get(resolveR2Key({
|
|
3205
|
+
deck,
|
|
3206
|
+
asset,
|
|
3207
|
+
slug,
|
|
3208
|
+
assetPath
|
|
3209
|
+
}, options));
|
|
3210
|
+
if (object) {
|
|
3211
|
+
const response = await responseFromR2Object(object, {
|
|
3212
|
+
deck,
|
|
3213
|
+
asset,
|
|
3214
|
+
slug,
|
|
3215
|
+
assetPath
|
|
3216
|
+
}, options);
|
|
3217
|
+
if (response) return response;
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
return source.getAsset?.(c, slug, assetPath) ?? null;
|
|
3221
|
+
}
|
|
3222
|
+
};
|
|
3090
3223
|
}
|
|
3091
3224
|
async function resolveBucket(bucket, c) {
|
|
3092
|
-
|
|
3225
|
+
return typeof bucket === "function" ? bucket(c) : bucket;
|
|
3093
3226
|
}
|
|
3094
3227
|
function findRoutableAsset(assets, slug, assetPath) {
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3228
|
+
const normalized = assetPath.replace(/^\/+/, "");
|
|
3229
|
+
return assets.find((asset) => {
|
|
3230
|
+
if (asset.type !== "local" && asset.type !== "r2") return false;
|
|
3231
|
+
const suffix = `/${slug}/assets/${normalized}`;
|
|
3232
|
+
return asset.publicPath.endsWith(suffix);
|
|
3233
|
+
});
|
|
3101
3234
|
}
|
|
3102
3235
|
function resolveR2Key(input, options) {
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3236
|
+
if (options.key) return options.key(input);
|
|
3237
|
+
if (input.asset.r2Key) return input.asset.r2Key;
|
|
3238
|
+
const key = input.asset.sourcePath || `${input.slug}/assets/${input.assetPath}`;
|
|
3239
|
+
const prefix = options.keyPrefix?.replace(/\/+$/, "");
|
|
3240
|
+
return prefix ? `${prefix}/${key.replace(/^\/+/, "")}` : key.replace(/^\/+/, "");
|
|
3108
3241
|
}
|
|
3109
3242
|
async function responseFromR2Object(object, input, options) {
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
headers.set("cache-control", input.asset.cacheControl);
|
|
3121
|
-
}
|
|
3122
|
-
return new Response(body, { headers });
|
|
3243
|
+
const body = object.body ?? (object.arrayBuffer ? await object.arrayBuffer() : void 0);
|
|
3244
|
+
if (body == null) return null;
|
|
3245
|
+
const headers = new Headers();
|
|
3246
|
+
object.writeHttpMetadata?.(headers);
|
|
3247
|
+
applyHttpMetadata(headers, object.httpMetadata);
|
|
3248
|
+
if (!headers.has("content-type") && input.asset.contentType) headers.set("content-type", input.asset.contentType);
|
|
3249
|
+
const cacheControl = resolveCacheControl(input, options);
|
|
3250
|
+
if (cacheControl) headers.set("cache-control", cacheControl);
|
|
3251
|
+
else if (cacheControl !== false && input.asset.cacheControl && !headers.has("cache-control")) headers.set("cache-control", input.asset.cacheControl);
|
|
3252
|
+
return new Response(body, { headers });
|
|
3123
3253
|
}
|
|
3124
3254
|
function applyHttpMetadata(headers, metadata) {
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
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);
|
|
3255
|
+
if (!metadata) return;
|
|
3256
|
+
if (metadata.contentType && !headers.has("content-type")) headers.set("content-type", metadata.contentType);
|
|
3257
|
+
if (metadata.cacheControl && !headers.has("cache-control")) headers.set("cache-control", metadata.cacheControl);
|
|
3258
|
+
if (metadata.contentDisposition && !headers.has("content-disposition")) headers.set("content-disposition", metadata.contentDisposition);
|
|
3259
|
+
if (metadata.contentEncoding && !headers.has("content-encoding")) headers.set("content-encoding", metadata.contentEncoding);
|
|
3260
|
+
if (metadata.contentLanguage && !headers.has("content-language")) headers.set("content-language", metadata.contentLanguage);
|
|
3133
3261
|
}
|
|
3134
3262
|
function resolveCacheControl(input, options) {
|
|
3135
|
-
|
|
3136
|
-
}
|
|
3137
|
-
|
|
3138
|
-
|
|
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
|
-
};
|
|
3263
|
+
return typeof options.cacheControl === "function" ? options.cacheControl(input) : options.cacheControl;
|
|
3264
|
+
}
|
|
3265
|
+
//#endregion
|
|
3266
|
+
export { SLIDE_TRANSITIONS, builtInSlideComponents, configureDecks, controlIconLabel, createDeckPaths, createDeckViewerEmbed, createDeckViewerParts, deckContext, decksRouter, defineDecks, defineDecksConfig, defineSlideComponents, manifestDeckSource, mergeDecksRouterOptions, renderCompiledDeck, renderCompiledDeckAsync, renderCompiledDeckPage, renderCompiledDeckPageAsync, renderCompiledSlide, renderCompiledSlideAsync, renderControlIcon, renderControlIconHtml, serveDecksClientEntry, withR2Assets };
|