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/vite.js
CHANGED
|
@@ -1,970 +1,919 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { join, relative, resolve } from "node:path";
|
|
2
|
+
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { unified } from "unified";
|
|
4
|
+
import remarkGfm from "remark-gfm";
|
|
5
|
+
import remarkMdx from "remark-mdx";
|
|
6
|
+
import remarkParse from "remark-parse";
|
|
7
|
+
import { compile } from "@mdx-js/mdx";
|
|
8
|
+
import remarkDirective from "remark-directive";
|
|
9
|
+
import { codeToHtml } from "shiki";
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { build } from "esbuild";
|
|
13
|
+
import { lookup } from "node:dns/promises";
|
|
14
|
+
import { isIP } from "node:net";
|
|
15
|
+
//#region src/routing/file-routing.ts
|
|
9
16
|
function resolveDeckFiles(paths, root = "decks") {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return [...decks.values()].map((deck) => ({
|
|
50
|
-
...deck,
|
|
51
|
-
assetPaths: [...deck.assetPaths].sort()
|
|
52
|
-
}));
|
|
17
|
+
const normalizedRoot = normalizePath$3(root).replace(/\/$/, "");
|
|
18
|
+
const decks = /* @__PURE__ */ new Map();
|
|
19
|
+
const assetsBySlug = /* @__PURE__ */ new Map();
|
|
20
|
+
for (const inputPath of paths.map(normalizePath$3).sort()) {
|
|
21
|
+
assertInsideRoot(inputPath, normalizedRoot);
|
|
22
|
+
const segments = inputPath.slice(normalizedRoot.length + 1).split("/");
|
|
23
|
+
if (segments.length === 1 && segments[0].endsWith(".mdx")) {
|
|
24
|
+
addDeck(decks, {
|
|
25
|
+
slug: segments[0].replace(/\.mdx$/, ""),
|
|
26
|
+
sourcePath: inputPath,
|
|
27
|
+
kind: "single-file",
|
|
28
|
+
assetPaths: []
|
|
29
|
+
});
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (segments.length === 2 && segments[1] === "deck.mdx") {
|
|
33
|
+
addDeck(decks, {
|
|
34
|
+
slug: segments[0],
|
|
35
|
+
sourcePath: inputPath,
|
|
36
|
+
kind: "directory",
|
|
37
|
+
assetPaths: assetsBySlug.get(segments[0]) ?? []
|
|
38
|
+
});
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (segments.length >= 3 && segments[1] === "assets") {
|
|
42
|
+
const slug = segments[0];
|
|
43
|
+
const assetPaths = assetsBySlug.get(slug) ?? [];
|
|
44
|
+
assetPaths.push(inputPath);
|
|
45
|
+
assetsBySlug.set(slug, assetPaths);
|
|
46
|
+
const deck = decks.get(slug);
|
|
47
|
+
if (deck?.kind === "directory") deck.assetPaths = assetPaths;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (inputPath.endsWith(".mdx")) throw new Error(`Nested deck slugs are not supported in this slice: ${inputPath}`);
|
|
51
|
+
}
|
|
52
|
+
return [...decks.values()].map((deck) => ({
|
|
53
|
+
...deck,
|
|
54
|
+
assetPaths: [...deck.assetPaths].sort()
|
|
55
|
+
}));
|
|
53
56
|
}
|
|
54
57
|
function addDeck(decks, next) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
decks.set(next.slug, next);
|
|
58
|
+
const current = decks.get(next.slug);
|
|
59
|
+
if (current) throw new Error(`Deck slug conflict for "${next.slug}": ${current.sourcePath} and ${next.sourcePath}`);
|
|
60
|
+
decks.set(next.slug, next);
|
|
60
61
|
}
|
|
61
62
|
function assertInsideRoot(path, root) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
"slide-right",
|
|
80
|
-
"slide-up",
|
|
81
|
-
"slide-down",
|
|
82
|
-
"view-transition"
|
|
63
|
+
if (path.includes("/../") || path.endsWith("/..") || path.startsWith("../")) throw new Error(`Deck path escapes the root: ${path}`);
|
|
64
|
+
if (path !== root && !path.startsWith(`${root}/`)) throw new Error(`Deck path is outside ${root}: ${path}`);
|
|
65
|
+
}
|
|
66
|
+
function normalizePath$3(path) {
|
|
67
|
+
return path.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+/g, "/");
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/deck/model.ts
|
|
71
|
+
const SLIDE_TRANSITIONS = [
|
|
72
|
+
"none",
|
|
73
|
+
"fade",
|
|
74
|
+
"fade-out",
|
|
75
|
+
"slide-left",
|
|
76
|
+
"slide-right",
|
|
77
|
+
"slide-up",
|
|
78
|
+
"slide-down",
|
|
79
|
+
"view-transition"
|
|
83
80
|
];
|
|
84
81
|
var CompileError = class extends Error {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
82
|
+
code;
|
|
83
|
+
constructor(message, code) {
|
|
84
|
+
super(message);
|
|
85
|
+
this.code = code;
|
|
86
|
+
this.name = "CompileError";
|
|
87
|
+
}
|
|
90
88
|
};
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/deck/frontmatter.ts
|
|
93
91
|
function readFrontmatter(source) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
92
|
+
const normalized = source.replace(/\r\n/g, "\n").trimStart();
|
|
93
|
+
if (!normalized.startsWith("---\n")) return {
|
|
94
|
+
attrs: {},
|
|
95
|
+
body: source.trim()
|
|
96
|
+
};
|
|
97
|
+
const end = normalized.indexOf("\n---", 4);
|
|
98
|
+
if (end === -1) throw new CompileError("Frontmatter block is not closed.", "frontmatter-unclosed");
|
|
99
|
+
const rawAttrs = normalized.slice(4, end).trim();
|
|
100
|
+
const body = normalized.slice(end + 4).replace(/^\n/, "").trim();
|
|
101
|
+
return {
|
|
102
|
+
attrs: parseFrontmatterAttrs(rawAttrs),
|
|
103
|
+
body
|
|
104
|
+
};
|
|
101
105
|
}
|
|
102
106
|
function toDeckFrontmatter(attrs, warnings) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
107
|
+
const meta = { ...attrs };
|
|
108
|
+
const deck = { meta };
|
|
109
|
+
deck.title = takeString(meta, "title");
|
|
110
|
+
deck.description = takeString(meta, "description");
|
|
111
|
+
deck.author = takeString(meta, "author");
|
|
112
|
+
deck.date = takeString(meta, "date");
|
|
113
|
+
deck.theme = takeString(meta, "theme");
|
|
114
|
+
deck.transition = takeKnownStringWithWarning(meta, "transition", SLIDE_TRANSITIONS, "none", warnings, "unknown-transition");
|
|
115
|
+
deck.transitionDuration = takeTransitionDuration(meta, warnings);
|
|
116
|
+
deck.transitionEasing = takeTransitionEasing(meta, warnings);
|
|
117
|
+
deck.assets = takeStringOrStringArray(meta, "assets");
|
|
118
|
+
deck.draft = takeBoolean(meta, "draft");
|
|
119
|
+
deck.presenter = takeBoolean(meta, "presenter");
|
|
120
|
+
const tags = meta.tags;
|
|
121
|
+
if (Array.isArray(tags)) {
|
|
122
|
+
deck.tags = tags.map(String);
|
|
123
|
+
delete meta.tags;
|
|
124
|
+
}
|
|
125
|
+
return deck;
|
|
122
126
|
}
|
|
123
127
|
function toSlideFrontmatter(attrs, warnings, input) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
warnings,
|
|
137
|
-
"unknown-transition",
|
|
138
|
-
input.slideIndex
|
|
139
|
-
) ?? input.fallbackTransition,
|
|
140
|
-
transitionDuration: takeTransitionDuration(meta, warnings, input.slideIndex) ?? input.fallbackTransitionDuration,
|
|
141
|
-
transitionEasing: takeTransitionEasing(meta, warnings, input.slideIndex) ?? input.fallbackTransitionEasing,
|
|
142
|
-
meta
|
|
143
|
-
};
|
|
144
|
-
return slide;
|
|
128
|
+
const meta = { ...attrs };
|
|
129
|
+
return {
|
|
130
|
+
title: takeString(meta, "title") ?? input.fallbackTitle,
|
|
131
|
+
layout: takeString(meta, "layout") ?? input.fallbackLayout,
|
|
132
|
+
className: takeString(meta, "class") ?? input.fallbackClassName,
|
|
133
|
+
notes: takeString(meta, "notes"),
|
|
134
|
+
background: takeString(meta, "background"),
|
|
135
|
+
transition: takeKnownStringWithWarning(meta, "transition", SLIDE_TRANSITIONS, "none", warnings, "unknown-transition", input.slideIndex) ?? input.fallbackTransition,
|
|
136
|
+
transitionDuration: takeTransitionDuration(meta, warnings, input.slideIndex) ?? input.fallbackTransitionDuration,
|
|
137
|
+
transitionEasing: takeTransitionEasing(meta, warnings, input.slideIndex) ?? input.fallbackTransitionEasing,
|
|
138
|
+
meta
|
|
139
|
+
};
|
|
145
140
|
}
|
|
146
141
|
function addUnknownFrontmatterWarnings(warnings, meta, scope, slideIndex) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
});
|
|
153
|
-
}
|
|
142
|
+
for (const key of Object.keys(meta)) warnings.push({
|
|
143
|
+
code: "unknown-frontmatter-key",
|
|
144
|
+
message: `Unknown ${scope} frontmatter key "${key}" is preserved in meta.`,
|
|
145
|
+
...slideIndex !== void 0 ? { slideIndex } : {}
|
|
146
|
+
});
|
|
154
147
|
}
|
|
155
148
|
function splitSlideSources(source) {
|
|
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
|
-
cursor += 1;
|
|
184
|
-
}
|
|
185
|
-
if (hasMeaningfulLines(current)) {
|
|
186
|
-
slides.push(current.join("\n").trim());
|
|
187
|
-
}
|
|
188
|
-
return slides;
|
|
149
|
+
const lines = source.replace(/\r\n/g, "\n").trim().split("\n");
|
|
150
|
+
const slides = [];
|
|
151
|
+
let current = [];
|
|
152
|
+
let cursor = 0;
|
|
153
|
+
while (cursor < lines.length) {
|
|
154
|
+
if (isFence(lines[cursor])) {
|
|
155
|
+
if (looksLikeFrontmatterFence(lines, cursor) && findFrontmatterEnd(lines, cursor) === -1) throw new CompileError("Frontmatter block is not closed.", "frontmatter-unclosed");
|
|
156
|
+
if (isFrontmatterStart(lines, cursor)) {
|
|
157
|
+
if (hasMeaningfulLines(current)) slides.push(current.join("\n").trim());
|
|
158
|
+
current = [];
|
|
159
|
+
const frontmatterEnd = findFrontmatterEnd(lines, cursor);
|
|
160
|
+
current.push(...lines.slice(cursor, frontmatterEnd + 1));
|
|
161
|
+
cursor = frontmatterEnd + 1;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (hasMeaningfulLines(current)) {
|
|
165
|
+
slides.push(current.join("\n").trim());
|
|
166
|
+
current = [];
|
|
167
|
+
}
|
|
168
|
+
cursor += 1;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
current.push(lines[cursor]);
|
|
172
|
+
cursor += 1;
|
|
173
|
+
}
|
|
174
|
+
if (hasMeaningfulLines(current)) slides.push(current.join("\n").trim());
|
|
175
|
+
return slides;
|
|
189
176
|
}
|
|
190
177
|
function parseFrontmatterAttrs(source) {
|
|
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
|
-
return attrs;
|
|
178
|
+
const attrs = {};
|
|
179
|
+
const lines = source.split("\n");
|
|
180
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
181
|
+
const line = lines[index];
|
|
182
|
+
const match = /^([A-Za-z_][A-Za-z0-9_-]*):\s*(.*)$/.exec(line);
|
|
183
|
+
if (!match) {
|
|
184
|
+
if (line.trim()) throw new CompileError(`Invalid frontmatter line ${index + 1}: "${line.trim()}"`, "frontmatter-invalid-line");
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const key = match[1];
|
|
188
|
+
const value = match[2];
|
|
189
|
+
if (value === "|") {
|
|
190
|
+
const block = [];
|
|
191
|
+
index += 1;
|
|
192
|
+
while (index < lines.length && /^\s+/.test(lines[index])) {
|
|
193
|
+
block.push(lines[index].trim());
|
|
194
|
+
index += 1;
|
|
195
|
+
}
|
|
196
|
+
index -= 1;
|
|
197
|
+
attrs[key] = block.join("\n").trim();
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (value.trim() === "") {
|
|
201
|
+
const nested = [];
|
|
202
|
+
index += 1;
|
|
203
|
+
while (index < lines.length && /^\s+/.test(lines[index])) {
|
|
204
|
+
nested.push(lines[index]);
|
|
205
|
+
index += 1;
|
|
206
|
+
}
|
|
207
|
+
index -= 1;
|
|
208
|
+
attrs[key] = parseNestedFrontmatterValue(nested);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
attrs[key] = parseScalar(value);
|
|
212
|
+
}
|
|
213
|
+
return attrs;
|
|
229
214
|
}
|
|
230
215
|
function isFrontmatterStart(lines, index) {
|
|
231
|
-
|
|
216
|
+
return looksLikeFrontmatterFence(lines, index) && findFrontmatterEnd(lines, index) > index;
|
|
232
217
|
}
|
|
233
218
|
function looksLikeFrontmatterFence(lines, index) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
219
|
+
if (!isFence(lines[index])) return false;
|
|
220
|
+
const next = lines[index + 1];
|
|
221
|
+
return next != null && /^([A-Za-z_][A-Za-z0-9_-]*):\s*/.test(next);
|
|
237
222
|
}
|
|
238
223
|
function findFrontmatterEnd(lines, start) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
return -1;
|
|
224
|
+
for (let index = start + 1; index < lines.length; index += 1) if (isFence(lines[index])) return index;
|
|
225
|
+
return -1;
|
|
243
226
|
}
|
|
244
227
|
function isFence(line) {
|
|
245
|
-
|
|
228
|
+
return /^---\s*$/.test(line);
|
|
246
229
|
}
|
|
247
230
|
function hasMeaningfulLines(lines) {
|
|
248
|
-
|
|
231
|
+
return lines.some((line) => line.trim() !== "");
|
|
249
232
|
}
|
|
250
233
|
function parseNestedFrontmatterValue(lines) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
object[match[1]] = parseScalar(match[2]);
|
|
262
|
-
}
|
|
263
|
-
return object;
|
|
234
|
+
const meaningful = lines.map((line) => line.trim()).filter(Boolean);
|
|
235
|
+
if (meaningful.every((line) => line.startsWith("- "))) return meaningful.map((line) => parseScalar(line.slice(2)));
|
|
236
|
+
const object = {};
|
|
237
|
+
for (const line of meaningful) {
|
|
238
|
+
const match = /^([A-Za-z_][A-Za-z0-9_-]*):\s*(.*)$/.exec(line);
|
|
239
|
+
if (!match) throw new CompileError(`Invalid nested frontmatter line: "${line}"`, "frontmatter-invalid-line");
|
|
240
|
+
object[match[1]] = parseScalar(match[2]);
|
|
241
|
+
}
|
|
242
|
+
return object;
|
|
264
243
|
}
|
|
265
244
|
function parseScalar(value) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
return trimmed;
|
|
245
|
+
const trimmed = value.trim().replace(/^['"]|['"]$/g, "");
|
|
246
|
+
if (trimmed === "true") return true;
|
|
247
|
+
if (trimmed === "false") return false;
|
|
248
|
+
if (/^\[.*\]$/.test(trimmed)) return trimmed.slice(1, -1).split(",").map((item) => item.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
|
|
249
|
+
return trimmed;
|
|
273
250
|
}
|
|
274
251
|
function takeString(attrs, key) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
252
|
+
const value = attrs[key];
|
|
253
|
+
if (typeof value !== "string") return void 0;
|
|
254
|
+
delete attrs[key];
|
|
255
|
+
return value;
|
|
279
256
|
}
|
|
280
257
|
function takeKnownStringWithWarning(attrs, key, values, fallback, warnings, code, slideIndex) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
258
|
+
const value = attrs[key];
|
|
259
|
+
delete attrs[key];
|
|
260
|
+
if (value === void 0) return void 0;
|
|
261
|
+
if (typeof value === "string" && values.includes(value)) return value;
|
|
262
|
+
warnings.push({
|
|
263
|
+
code,
|
|
264
|
+
message: `Unknown ${key} value "${String(value)}"; using ${fallback}.`,
|
|
265
|
+
...slideIndex !== void 0 ? { slideIndex } : {}
|
|
266
|
+
});
|
|
267
|
+
return fallback;
|
|
291
268
|
}
|
|
292
269
|
function takeTransitionDuration(attrs, warnings, slideIndex) {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
return void 0;
|
|
270
|
+
const value = attrs.transitionDuration;
|
|
271
|
+
delete attrs.transitionDuration;
|
|
272
|
+
if (value === void 0) return void 0;
|
|
273
|
+
if (typeof value === "string" && isValidTransitionDuration(value)) return value;
|
|
274
|
+
warnings.push({
|
|
275
|
+
code: "invalid-transition-duration",
|
|
276
|
+
message: `Invalid transitionDuration value "${String(value)}"; ignoring it.`,
|
|
277
|
+
...slideIndex !== void 0 ? { slideIndex } : {}
|
|
278
|
+
});
|
|
303
279
|
}
|
|
304
280
|
function takeTransitionEasing(attrs, warnings, slideIndex) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
return void 0;
|
|
281
|
+
const value = attrs.transitionEasing;
|
|
282
|
+
delete attrs.transitionEasing;
|
|
283
|
+
if (value === void 0) return void 0;
|
|
284
|
+
if (typeof value === "string" && isValidTransitionEasing(value)) return value;
|
|
285
|
+
warnings.push({
|
|
286
|
+
code: "invalid-transition-easing",
|
|
287
|
+
message: `Invalid transitionEasing value "${String(value)}"; ignoring it.`,
|
|
288
|
+
...slideIndex !== void 0 ? { slideIndex } : {}
|
|
289
|
+
});
|
|
315
290
|
}
|
|
316
291
|
function isValidTransitionDuration(value) {
|
|
317
|
-
|
|
292
|
+
return value.split(",").map((item) => item.trim()).every((item) => /^(?:\d+|\d*\.\d+)(?:ms|s)$/.test(item));
|
|
318
293
|
}
|
|
319
294
|
function isValidTransitionEasing(value) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
295
|
+
const easing = value.trim();
|
|
296
|
+
return [
|
|
297
|
+
"linear",
|
|
298
|
+
"ease",
|
|
299
|
+
"ease-in",
|
|
300
|
+
"ease-out",
|
|
301
|
+
"ease-in-out",
|
|
302
|
+
"step-start",
|
|
303
|
+
"step-end"
|
|
304
|
+
].includes(easing) || /^cubic-bezier\(\s*-?(?:\d+|\d*\.\d+)\s*,\s*-?(?:\d+|\d*\.\d+)\s*,\s*-?(?:\d+|\d*\.\d+)\s*,\s*-?(?:\d+|\d*\.\d+)\s*\)$/.test(easing) || /^steps\(\s*\d+\s*(?:,\s*(?:jump-start|jump-end|jump-none|jump-both|start|end))?\s*\)$/.test(easing) || /^linear\([^)]+\)$/.test(easing);
|
|
324
305
|
}
|
|
325
306
|
function takeStringOrStringArray(attrs, key) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
return void 0;
|
|
307
|
+
const value = attrs[key];
|
|
308
|
+
if (typeof value === "string") {
|
|
309
|
+
delete attrs[key];
|
|
310
|
+
return value;
|
|
311
|
+
}
|
|
312
|
+
if (Array.isArray(value)) {
|
|
313
|
+
delete attrs[key];
|
|
314
|
+
return value.map(String);
|
|
315
|
+
}
|
|
336
316
|
}
|
|
337
317
|
function takeBoolean(attrs, key) {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
318
|
+
const value = attrs[key];
|
|
319
|
+
if (typeof value !== "boolean") return void 0;
|
|
320
|
+
delete attrs[key];
|
|
321
|
+
return value;
|
|
342
322
|
}
|
|
343
|
-
|
|
344
|
-
|
|
323
|
+
//#endregion
|
|
324
|
+
//#region src/deck/assets.ts
|
|
345
325
|
function buildExternalAssetRefs(candidates) {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
326
|
+
const refs = /* @__PURE__ */ new Map();
|
|
327
|
+
for (const candidate of candidates) {
|
|
328
|
+
const type = assetRefType(candidate);
|
|
329
|
+
if (!type || refs.has(candidate)) continue;
|
|
330
|
+
const contentType = contentTypeForPath(candidate);
|
|
331
|
+
refs.set(candidate, {
|
|
332
|
+
sourcePath: candidate,
|
|
333
|
+
publicPath: candidate,
|
|
334
|
+
type,
|
|
335
|
+
...contentType ? { contentType } : {}
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
return [...refs.values()];
|
|
359
339
|
}
|
|
360
340
|
function addExternalAssetWarnings(warnings, assets) {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
341
|
+
for (const asset of assets) {
|
|
342
|
+
if (asset.type !== "remote" && asset.type !== "r2") continue;
|
|
343
|
+
const label = asset.type === "r2" ? "R2" : "Remote";
|
|
344
|
+
warnings.push({
|
|
345
|
+
code: "external-asset-unverified",
|
|
346
|
+
message: `${label} asset existence cannot be verified at compile time: ${asset.sourcePath}`
|
|
347
|
+
});
|
|
348
|
+
}
|
|
369
349
|
}
|
|
370
350
|
function collectMarkdownAssetCandidates(markdown) {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
candidates.push(match[1].trim());
|
|
377
|
-
}
|
|
378
|
-
for (const match of markdown.matchAll(/\b(?:src|href|image|background)=["']([^"']+)["']/g)) {
|
|
379
|
-
candidates.push(match[1].trim());
|
|
380
|
-
}
|
|
381
|
-
return candidates;
|
|
351
|
+
const candidates = [];
|
|
352
|
+
for (const match of markdown.matchAll(/^\s*(?:background|image|src|asset):\s*['"]?([^'"\n]+)['"]?\s*$/gim)) candidates.push(match[1].trim());
|
|
353
|
+
for (const match of markdown.matchAll(/!\[[^\]]*\]\(([^)\s]+)(?:\s+["'][^"']*["'])?\)/g)) candidates.push(match[1].trim());
|
|
354
|
+
for (const match of markdown.matchAll(/\b(?:src|href|image|background)=["']([^"']+)["']/g)) candidates.push(match[1].trim());
|
|
355
|
+
return candidates;
|
|
382
356
|
}
|
|
383
357
|
function collectFrontmatterAssetCandidates(value) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
358
|
+
if (Array.isArray(value)) return value.map(String).map((item) => item.trim()).filter(Boolean);
|
|
359
|
+
if (typeof value === "string") return [value.trim()].filter(Boolean);
|
|
360
|
+
return [];
|
|
387
361
|
}
|
|
388
362
|
function contentTypeForPath(path) {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
return void 0;
|
|
363
|
+
const pathname = path.replace(/\?.*$/, "").toLowerCase();
|
|
364
|
+
if (/\.png(?:#.*)?$/.test(pathname)) return "image/png";
|
|
365
|
+
if (/\.jpe?g(?:#.*)?$/.test(pathname)) return "image/jpeg";
|
|
366
|
+
if (/\.gif(?:#.*)?$/.test(pathname)) return "image/gif";
|
|
367
|
+
if (/\.svg(?:#.*)?$/.test(pathname)) return "image/svg+xml";
|
|
368
|
+
if (/\.webp(?:#.*)?$/.test(pathname)) return "image/webp";
|
|
396
369
|
}
|
|
397
370
|
function assetRefType(value) {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
return void 0;
|
|
371
|
+
if (/^https?:\/\//i.test(value)) return "remote";
|
|
372
|
+
if (/^r2:\/\//i.test(value)) return "r2";
|
|
373
|
+
if (value.startsWith("/")) return "public";
|
|
402
374
|
}
|
|
403
|
-
|
|
404
|
-
|
|
375
|
+
//#endregion
|
|
376
|
+
//#region src/deck/speaker-notes.ts
|
|
405
377
|
function extractMdxCommentSpeakerNotes(source) {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
};
|
|
378
|
+
const notes = [];
|
|
379
|
+
return {
|
|
380
|
+
body: source.replace(/\{\/\*([\s\S]*?)\*\/\}/g, (_match, rawNote) => {
|
|
381
|
+
const note = normalizeSpeakerNote(rawNote);
|
|
382
|
+
if (note) notes.push(note);
|
|
383
|
+
return "";
|
|
384
|
+
}),
|
|
385
|
+
...notes.length ? { notes: notes.join("\n\n") } : {}
|
|
386
|
+
};
|
|
416
387
|
}
|
|
417
388
|
function combineSpeakerNotes(...notes) {
|
|
418
|
-
|
|
419
|
-
|
|
389
|
+
const combined = notes.map((note) => note?.trim()).filter((note) => Boolean(note));
|
|
390
|
+
return combined.length ? combined.join("\n\n") : void 0;
|
|
420
391
|
}
|
|
421
392
|
function normalizeSpeakerNote(value) {
|
|
422
|
-
|
|
393
|
+
return value.replace(/\r\n/g, "\n").split("\n").map((line) => line.trim()).join("\n").trim();
|
|
423
394
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
import remarkGfm from "remark-gfm";
|
|
428
|
-
import remarkMdx from "remark-mdx";
|
|
429
|
-
import remarkParse from "remark-parse";
|
|
430
|
-
var slideSeparator = /^---\s*$/m;
|
|
395
|
+
//#endregion
|
|
396
|
+
//#region src/parser/parser.ts
|
|
397
|
+
const slideSeparator = /^---\s*$/m;
|
|
431
398
|
function parseDeckWithWarnings(markdown) {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
};
|
|
399
|
+
const normalized = markdown.replace(/\r\n/g, "\n").trim();
|
|
400
|
+
const parsedSlides = (normalized.length > 0 ? normalized.split(slideSeparator) : [""]).map((chunk, index) => parseSlide(chunk.trim(), index)).filter(({ slide }) => slide.raw.length > 0 || slide.blocks.length > 0 || slide.nodes.length > 0);
|
|
401
|
+
return {
|
|
402
|
+
slides: parsedSlides.map(({ slide }) => slide),
|
|
403
|
+
warnings: parsedSlides.flatMap(({ warnings }) => warnings)
|
|
404
|
+
};
|
|
439
405
|
}
|
|
440
406
|
function parseSlide(source, index) {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
407
|
+
const { attrs, body } = readSlideAttributes(source);
|
|
408
|
+
const parsed = parseContent(body, index);
|
|
409
|
+
const firstHeading = parsed.blocks.find((block) => block.type === "heading");
|
|
410
|
+
return {
|
|
411
|
+
slide: {
|
|
412
|
+
index,
|
|
413
|
+
title: stringAttr(attrs.title) ?? firstHeading?.text,
|
|
414
|
+
layout: stringAttr(attrs.layout) ?? (index === 0 ? "cover" : "default"),
|
|
415
|
+
className: stringAttr(attrs.class),
|
|
416
|
+
blocks: parsed.blocks,
|
|
417
|
+
nodes: parsed.nodes,
|
|
418
|
+
raw: source
|
|
419
|
+
},
|
|
420
|
+
warnings: parsed.warnings
|
|
421
|
+
};
|
|
456
422
|
}
|
|
457
423
|
function readSlideAttributes(source) {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
424
|
+
const lines = source.split("\n");
|
|
425
|
+
const attrs = {};
|
|
426
|
+
let cursor = 0;
|
|
427
|
+
while (cursor < lines.length) {
|
|
428
|
+
const line = lines[cursor];
|
|
429
|
+
const match = /^(title|layout|class):\s*(.+)$/.exec(line);
|
|
430
|
+
if (!match) break;
|
|
431
|
+
attrs[match[1]] = match[2].trim().replace(/^['"]|['"]$/g, "");
|
|
432
|
+
cursor += 1;
|
|
433
|
+
}
|
|
434
|
+
return {
|
|
435
|
+
attrs,
|
|
436
|
+
body: lines.slice(cursor).join("\n").trim()
|
|
437
|
+
};
|
|
469
438
|
}
|
|
470
439
|
function parseContent(body, slideIndex) {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
440
|
+
const { root, warnings: parseWarnings } = parseMarkdownTree(body, slideIndex);
|
|
441
|
+
const blockResults = (root.children ?? []).map((child) => toBlocks(child, slideIndex, body));
|
|
442
|
+
const nodeResults = (root.children ?? []).map((child) => toNodes(child, slideIndex, body));
|
|
443
|
+
return {
|
|
444
|
+
blocks: blockResults.flatMap(({ blocks }) => blocks),
|
|
445
|
+
nodes: nodeResults.flatMap(({ nodes }) => nodes),
|
|
446
|
+
warnings: [
|
|
447
|
+
...fenceWarningsFor(body, slideIndex),
|
|
448
|
+
...parseWarnings,
|
|
449
|
+
...blockResults.flatMap(({ warnings }) => warnings),
|
|
450
|
+
...nodeResults.flatMap(({ warnings }) => warnings)
|
|
451
|
+
]
|
|
452
|
+
};
|
|
484
453
|
}
|
|
485
454
|
function parseMarkdownTree(body, slideIndex) {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
455
|
+
try {
|
|
456
|
+
return {
|
|
457
|
+
root: unified().use(remarkParse).use(remarkGfm).use(remarkMdx).parse(body),
|
|
458
|
+
warnings: []
|
|
459
|
+
};
|
|
460
|
+
} catch (error) {
|
|
461
|
+
const message = error instanceof Error ? error.message : "Unknown MDX parse error";
|
|
462
|
+
return {
|
|
463
|
+
root: unified().use(remarkParse).use(remarkGfm).parse(body),
|
|
464
|
+
warnings: [parserWarning("mdx-parse-error", `Slide ${slideIndex + 1}: ${message}`, slideIndex)]
|
|
465
|
+
};
|
|
466
|
+
}
|
|
498
467
|
}
|
|
499
468
|
function fenceWarningsFor(body, slideIndex) {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
469
|
+
let openFence;
|
|
470
|
+
for (const line of body.split("\n")) {
|
|
471
|
+
const match = /^(```|~~~)/.exec(line);
|
|
472
|
+
if (!match) continue;
|
|
473
|
+
if (!openFence) {
|
|
474
|
+
openFence = match[1];
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
if (line.startsWith(openFence)) openFence = void 0;
|
|
478
|
+
}
|
|
479
|
+
if (!openFence) return [];
|
|
480
|
+
return [parserWarning("code-fence-unclosed", `Slide ${slideIndex + 1}: code fence is not closed.`, slideIndex)];
|
|
512
481
|
}
|
|
513
482
|
function toBlocks(node, slideIndex, source) {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
483
|
+
switch (node.type) {
|
|
484
|
+
case "heading": return blockResult([{
|
|
485
|
+
type: "heading",
|
|
486
|
+
depth: headingDepth(node.depth),
|
|
487
|
+
text: textContent(node)
|
|
488
|
+
}]);
|
|
489
|
+
case "paragraph": {
|
|
490
|
+
const image = soleImage(node);
|
|
491
|
+
if (image) return blockResult([{
|
|
492
|
+
type: "image",
|
|
493
|
+
alt: image.alt ?? "",
|
|
494
|
+
src: image.url ?? "",
|
|
495
|
+
...image.title ? { title: image.title } : {}
|
|
496
|
+
}]);
|
|
497
|
+
return blockResult([{
|
|
498
|
+
type: "paragraph",
|
|
499
|
+
text: textContent(node).replace(/\s+/g, " ").trim()
|
|
500
|
+
}]);
|
|
501
|
+
}
|
|
502
|
+
case "list": return blockResult([{
|
|
503
|
+
type: "list",
|
|
504
|
+
ordered: node.ordered === true,
|
|
505
|
+
items: (node.children ?? []).map((item) => taskListItemText(item)).filter(Boolean)
|
|
506
|
+
}]);
|
|
507
|
+
case "code": return blockResult([{
|
|
508
|
+
type: "code",
|
|
509
|
+
lang: node.lang ?? void 0,
|
|
510
|
+
code: node.value ?? ""
|
|
511
|
+
}]);
|
|
512
|
+
case "blockquote": return blockResult([{
|
|
513
|
+
type: "blockquote",
|
|
514
|
+
text: textContent(node).trim()
|
|
515
|
+
}]);
|
|
516
|
+
case "table": return blockResult([tableBlock(node)]);
|
|
517
|
+
case "mdxJsxFlowElement":
|
|
518
|
+
case "mdxJsxTextElement": return jsxElementBlock(node, slideIndex, source);
|
|
519
|
+
case "mdxjsEsm":
|
|
520
|
+
case "mdxFlowExpression":
|
|
521
|
+
case "mdxTextExpression":
|
|
522
|
+
case "thematicBreak": return blockResult([]);
|
|
523
|
+
default: return blockResult(textContent(node).trim() ? [{
|
|
524
|
+
type: "paragraph",
|
|
525
|
+
text: textContent(node).trim()
|
|
526
|
+
}] : []);
|
|
527
|
+
}
|
|
556
528
|
}
|
|
557
529
|
function jsxElementBlock(node, slideIndex, source) {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
}
|
|
574
|
-
return blockResult([{ type: "paragraph", text: textContent(node).trim() }]);
|
|
530
|
+
const name = node.name ?? "";
|
|
531
|
+
if (!name) return blockResult([]);
|
|
532
|
+
if (isComponentName(name)) {
|
|
533
|
+
const parsedProps = parseProps(node.attributes ?? [], slideIndex, name);
|
|
534
|
+
return blockResult([{
|
|
535
|
+
type: "component",
|
|
536
|
+
name,
|
|
537
|
+
props: parsedProps.props,
|
|
538
|
+
raw: sourceForNode(node, source) ?? `<${name} />`
|
|
539
|
+
}], parsedProps.warnings);
|
|
540
|
+
}
|
|
541
|
+
return blockResult([{
|
|
542
|
+
type: "paragraph",
|
|
543
|
+
text: textContent(node).trim()
|
|
544
|
+
}]);
|
|
575
545
|
}
|
|
576
546
|
function toNodes(node, slideIndex, source) {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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
|
-
return nodeResult(
|
|
649
|
-
[],
|
|
650
|
-
[
|
|
651
|
-
parserWarning(
|
|
652
|
-
"mdx-expression-ignored",
|
|
653
|
-
`Slide ${slideIndex + 1}: MDX JavaScript expressions are ignored.`,
|
|
654
|
-
slideIndex
|
|
655
|
-
)
|
|
656
|
-
]
|
|
657
|
-
);
|
|
658
|
-
case "thematicBreak":
|
|
659
|
-
return nodeResult([{ type: "element", tag: "hr", props: {}, children: [] }]);
|
|
660
|
-
default:
|
|
661
|
-
return childrenToNodes(node, slideIndex, source);
|
|
662
|
-
}
|
|
547
|
+
switch (node.type) {
|
|
548
|
+
case "text": return nodeResult(node.value ? [{
|
|
549
|
+
type: "text",
|
|
550
|
+
value: node.value
|
|
551
|
+
}] : []);
|
|
552
|
+
case "emphasis": return elementNodeResult(node, "em", slideIndex, source);
|
|
553
|
+
case "strong": return elementNodeResult(node, "strong", slideIndex, source);
|
|
554
|
+
case "delete": return elementNodeResult(node, "del", slideIndex, source);
|
|
555
|
+
case "inlineCode": return nodeResult([{
|
|
556
|
+
type: "element",
|
|
557
|
+
tag: "code",
|
|
558
|
+
props: {},
|
|
559
|
+
children: [{
|
|
560
|
+
type: "text",
|
|
561
|
+
value: node.value ?? ""
|
|
562
|
+
}]
|
|
563
|
+
}]);
|
|
564
|
+
case "break": return nodeResult([{
|
|
565
|
+
type: "element",
|
|
566
|
+
tag: "br",
|
|
567
|
+
props: {},
|
|
568
|
+
children: []
|
|
569
|
+
}]);
|
|
570
|
+
case "heading": return elementNodeResult(node, `h${headingDepth(node.depth)}`, slideIndex, source);
|
|
571
|
+
case "paragraph":
|
|
572
|
+
if (soleImage(node)) return childrenToNodes(node, slideIndex, source);
|
|
573
|
+
if (isJsxOnlyParagraph(node)) return childrenToNodes(node, slideIndex, source);
|
|
574
|
+
return elementNodeResult(node, "p", slideIndex, source);
|
|
575
|
+
case "list": {
|
|
576
|
+
const tag = node.ordered === true ? "ol" : "ul";
|
|
577
|
+
const items = node.children ?? [];
|
|
578
|
+
const childResults = items.map((child) => childrenToNodes(child, slideIndex, source));
|
|
579
|
+
return nodeResult([{
|
|
580
|
+
type: "element",
|
|
581
|
+
tag,
|
|
582
|
+
props: {},
|
|
583
|
+
children: childResults.map(({ nodes }, itemIndex) => listItemNode(nodes, items[itemIndex]?.checked))
|
|
584
|
+
}], childResults.flatMap(({ warnings }) => warnings));
|
|
585
|
+
}
|
|
586
|
+
case "listItem": return childrenToNodes(node, slideIndex, source);
|
|
587
|
+
case "code": return nodeResult([{
|
|
588
|
+
type: "code",
|
|
589
|
+
lang: node.lang ?? void 0,
|
|
590
|
+
value: node.value ?? ""
|
|
591
|
+
}]);
|
|
592
|
+
case "blockquote": return elementNodeResult(node, "blockquote", slideIndex, source);
|
|
593
|
+
case "image": return nodeResult([{
|
|
594
|
+
type: "element",
|
|
595
|
+
tag: "img",
|
|
596
|
+
props: {
|
|
597
|
+
src: node.url ?? "",
|
|
598
|
+
alt: node.alt ?? "",
|
|
599
|
+
...node.title ? { title: node.title } : {}
|
|
600
|
+
},
|
|
601
|
+
children: []
|
|
602
|
+
}]);
|
|
603
|
+
case "link": return elementNodeResult(node, "a", slideIndex, source, { href: node.url ?? "" });
|
|
604
|
+
case "table": return tableNodes(node, slideIndex, source);
|
|
605
|
+
case "mdxJsxFlowElement":
|
|
606
|
+
case "mdxJsxTextElement": return jsxElementNodes(node, slideIndex, source);
|
|
607
|
+
case "mdxjsEsm": return nodeResult([], [parserWarning("mdx-import-export-ignored", `Slide ${slideIndex + 1}: MDX import/export syntax is ignored.`, slideIndex)]);
|
|
608
|
+
case "mdxFlowExpression":
|
|
609
|
+
case "mdxTextExpression": return nodeResult([], [parserWarning("mdx-expression-ignored", `Slide ${slideIndex + 1}: MDX JavaScript expressions are ignored.`, slideIndex)]);
|
|
610
|
+
case "thematicBreak": return nodeResult([{
|
|
611
|
+
type: "element",
|
|
612
|
+
tag: "hr",
|
|
613
|
+
props: {},
|
|
614
|
+
children: []
|
|
615
|
+
}]);
|
|
616
|
+
default: return childrenToNodes(node, slideIndex, source);
|
|
617
|
+
}
|
|
663
618
|
}
|
|
664
619
|
function elementNodeResult(node, tag, slideIndex, source, props = {}) {
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
children: children.nodes
|
|
673
|
-
}
|
|
674
|
-
],
|
|
675
|
-
children.warnings
|
|
676
|
-
);
|
|
620
|
+
const children = childrenToNodes(node, slideIndex, source);
|
|
621
|
+
return nodeResult([{
|
|
622
|
+
type: "element",
|
|
623
|
+
tag,
|
|
624
|
+
props,
|
|
625
|
+
children: children.nodes
|
|
626
|
+
}], children.warnings);
|
|
677
627
|
}
|
|
678
628
|
function listItemNode(children, checked) {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
629
|
+
if (typeof checked !== "boolean") return {
|
|
630
|
+
type: "element",
|
|
631
|
+
tag: "li",
|
|
632
|
+
props: {},
|
|
633
|
+
children
|
|
634
|
+
};
|
|
635
|
+
return {
|
|
636
|
+
type: "element",
|
|
637
|
+
tag: "li",
|
|
638
|
+
props: { class: "task-list-item" },
|
|
639
|
+
children: [
|
|
640
|
+
{
|
|
641
|
+
type: "element",
|
|
642
|
+
tag: "input",
|
|
643
|
+
props: {
|
|
644
|
+
type: "checkbox",
|
|
645
|
+
disabled: true,
|
|
646
|
+
...checked ? { checked: true } : {}
|
|
647
|
+
},
|
|
648
|
+
children: []
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
type: "text",
|
|
652
|
+
value: " "
|
|
653
|
+
},
|
|
654
|
+
...children
|
|
655
|
+
]
|
|
656
|
+
};
|
|
695
657
|
}
|
|
696
658
|
function tableNodes(node, slideIndex, source) {
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
659
|
+
const align = node.align ?? [];
|
|
660
|
+
const [headerRow, ...bodyRows] = node.children ?? [];
|
|
661
|
+
const headerResult = headerRow ? tableRowCells(headerRow, "th", align, slideIndex, source) : cellsResult([]);
|
|
662
|
+
const bodyResults = bodyRows.map((row) => tableRowCells(row, "td", align, slideIndex, source));
|
|
663
|
+
return nodeResult([{
|
|
664
|
+
type: "element",
|
|
665
|
+
tag: "table",
|
|
666
|
+
props: {},
|
|
667
|
+
children: [{
|
|
668
|
+
type: "element",
|
|
669
|
+
tag: "thead",
|
|
670
|
+
props: {},
|
|
671
|
+
children: [{
|
|
672
|
+
type: "element",
|
|
673
|
+
tag: "tr",
|
|
674
|
+
props: {},
|
|
675
|
+
children: headerResult.cells
|
|
676
|
+
}]
|
|
677
|
+
}, {
|
|
678
|
+
type: "element",
|
|
679
|
+
tag: "tbody",
|
|
680
|
+
props: {},
|
|
681
|
+
children: bodyResults.map(({ cells }) => ({
|
|
682
|
+
type: "element",
|
|
683
|
+
tag: "tr",
|
|
684
|
+
props: {},
|
|
685
|
+
children: cells
|
|
686
|
+
}))
|
|
687
|
+
}]
|
|
688
|
+
}], [...headerResult.warnings, ...bodyResults.flatMap(({ warnings }) => warnings)]);
|
|
725
689
|
}
|
|
726
690
|
function cellsResult(cells, emittedWarnings = []) {
|
|
727
|
-
|
|
691
|
+
return {
|
|
692
|
+
cells,
|
|
693
|
+
warnings: emittedWarnings
|
|
694
|
+
};
|
|
728
695
|
}
|
|
729
696
|
function tableRowCells(row, tag, align, slideIndex, source) {
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
results.map(({ cell }) => cell),
|
|
745
|
-
results.flatMap(({ warnings }) => warnings)
|
|
746
|
-
);
|
|
697
|
+
const results = (row.children ?? []).map((cell, cellIndex) => {
|
|
698
|
+
const children = childrenToNodes(cell, slideIndex, source);
|
|
699
|
+
const cellAlign = toTableAlign(align[cellIndex]);
|
|
700
|
+
return {
|
|
701
|
+
cell: {
|
|
702
|
+
type: "element",
|
|
703
|
+
tag,
|
|
704
|
+
props: cellAlign ? { style: `text-align:${cellAlign}` } : {},
|
|
705
|
+
children: children.nodes
|
|
706
|
+
},
|
|
707
|
+
warnings: children.warnings
|
|
708
|
+
};
|
|
709
|
+
});
|
|
710
|
+
return cellsResult(results.map(({ cell }) => cell), results.flatMap(({ warnings }) => warnings));
|
|
747
711
|
}
|
|
748
712
|
function jsxElementNodes(node, slideIndex, source) {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
713
|
+
const name = node.name ?? "";
|
|
714
|
+
if (!name) return childrenToNodes(node, slideIndex, source);
|
|
715
|
+
if (isUnsafeHtmlElement(name)) return nodeResult([{
|
|
716
|
+
type: "text",
|
|
717
|
+
value: sourceForNode(node, source) ?? textContent(node)
|
|
718
|
+
}]);
|
|
719
|
+
const parsedProps = parseProps(node.attributes ?? [], slideIndex, name);
|
|
720
|
+
const parsedChildren = childrenToNodes(node, slideIndex, source);
|
|
721
|
+
const children = parsedChildren.nodes.filter((child) => child.type !== "text" || child.value.trim() !== "");
|
|
722
|
+
const warnings = [...parsedProps.warnings, ...parsedChildren.warnings];
|
|
723
|
+
if (isComponentName(name)) return nodeResult([{
|
|
724
|
+
type: "component",
|
|
725
|
+
name,
|
|
726
|
+
props: parsedProps.props,
|
|
727
|
+
children
|
|
728
|
+
}], warnings);
|
|
729
|
+
return nodeResult([{
|
|
730
|
+
type: "element",
|
|
731
|
+
tag: name,
|
|
732
|
+
props: parsedProps.props,
|
|
733
|
+
children
|
|
734
|
+
}], warnings);
|
|
760
735
|
}
|
|
761
736
|
function childrenToNodes(node, slideIndex, source) {
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
results.flatMap(({ nodes }) => nodes),
|
|
765
|
-
results.flatMap(({ warnings }) => warnings)
|
|
766
|
-
);
|
|
737
|
+
const results = (node.children ?? []).map((child) => toNodes(child, slideIndex, source));
|
|
738
|
+
return nodeResult(results.flatMap(({ nodes }) => nodes), results.flatMap(({ warnings }) => warnings));
|
|
767
739
|
}
|
|
768
740
|
function parseProps(attributes, slideIndex, componentName) {
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
)
|
|
786
|
-
];
|
|
787
|
-
});
|
|
788
|
-
return { props, warnings };
|
|
741
|
+
const props = {};
|
|
742
|
+
return {
|
|
743
|
+
props,
|
|
744
|
+
warnings: attributes.flatMap((attr) => {
|
|
745
|
+
if (attr.type !== "mdxJsxAttribute" || !attr.name) return [];
|
|
746
|
+
if (attr.value === null || attr.value === void 0) {
|
|
747
|
+
props[attr.name] = true;
|
|
748
|
+
return [];
|
|
749
|
+
}
|
|
750
|
+
if (typeof attr.value === "string" || typeof attr.value === "number" || typeof attr.value === "boolean") {
|
|
751
|
+
props[attr.name] = attr.value;
|
|
752
|
+
return [];
|
|
753
|
+
}
|
|
754
|
+
return [parserWarning("mdx-expression-prop-ignored", `Slide ${slideIndex + 1}: MDX JavaScript expression props are ignored on ${ignoredPropTarget(componentName, attr.name)}.`, slideIndex)];
|
|
755
|
+
})
|
|
756
|
+
};
|
|
789
757
|
}
|
|
790
758
|
function blockResult(blocks, emittedWarnings = []) {
|
|
791
|
-
|
|
759
|
+
return {
|
|
760
|
+
blocks,
|
|
761
|
+
warnings: emittedWarnings
|
|
762
|
+
};
|
|
792
763
|
}
|
|
793
764
|
function nodeResult(nodes, emittedWarnings = []) {
|
|
794
|
-
|
|
765
|
+
return {
|
|
766
|
+
nodes,
|
|
767
|
+
warnings: emittedWarnings
|
|
768
|
+
};
|
|
795
769
|
}
|
|
796
770
|
function parserWarning(code, message, slideIndex) {
|
|
797
|
-
|
|
771
|
+
return {
|
|
772
|
+
code,
|
|
773
|
+
message,
|
|
774
|
+
slideIndex
|
|
775
|
+
};
|
|
798
776
|
}
|
|
799
777
|
function ignoredPropTarget(componentName, propName) {
|
|
800
|
-
|
|
801
|
-
|
|
778
|
+
if (propName.startsWith("$")) return `${componentName} dynamic prop`;
|
|
779
|
+
return `${componentName}.${propName}`;
|
|
802
780
|
}
|
|
803
781
|
function textContent(node) {
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
}
|
|
812
|
-
return (node.children ?? []).map(textContent).join("");
|
|
782
|
+
if (typeof node.value === "string") return node.value;
|
|
783
|
+
if (node.type === "strong") return `**${(node.children ?? []).map(textContent).join("")}**`;
|
|
784
|
+
if (node.type === "emphasis") return `*${(node.children ?? []).map(textContent).join("")}*`;
|
|
785
|
+
if (node.type === "delete") return `~~${(node.children ?? []).map(textContent).join("")}~~`;
|
|
786
|
+
if (node.type === "inlineCode") return `\`${node.value ?? ""}\``;
|
|
787
|
+
if ((node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement") && node.name && !isComponentName(node.name)) return `<${node.name}>${(node.children ?? []).map(textContent).join("")}</${node.name}>`;
|
|
788
|
+
return (node.children ?? []).map(textContent).join("");
|
|
813
789
|
}
|
|
814
790
|
function taskListItemText(item) {
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
791
|
+
const text = textContent(item).replace(/\s+/g, " ").trim();
|
|
792
|
+
if (typeof item.checked === "boolean") return `${item.checked ? "[x]" : "[ ]"} ${text}`.trim();
|
|
793
|
+
return text;
|
|
818
794
|
}
|
|
819
795
|
function tableBlock(node) {
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
header,
|
|
828
|
-
rows: bodyRows
|
|
829
|
-
};
|
|
796
|
+
const [header = [], ...bodyRows] = (node.children ?? []).map((row) => (row.children ?? []).map((cell) => textContent(cell).replace(/\s+/g, " ").trim()));
|
|
797
|
+
return {
|
|
798
|
+
type: "table",
|
|
799
|
+
align: (node.align ?? []).map(toTableAlign),
|
|
800
|
+
header,
|
|
801
|
+
rows: bodyRows
|
|
802
|
+
};
|
|
830
803
|
}
|
|
831
804
|
function toTableAlign(value) {
|
|
832
|
-
|
|
805
|
+
return value === "left" || value === "center" || value === "right" ? value : void 0;
|
|
833
806
|
}
|
|
834
807
|
function soleImage(node) {
|
|
835
|
-
|
|
836
|
-
|
|
808
|
+
const meaningful = (node.children ?? []).filter((child) => child.type !== "text" || child.value?.trim());
|
|
809
|
+
return meaningful.length === 1 && meaningful[0].type === "image" ? meaningful[0] : void 0;
|
|
837
810
|
}
|
|
838
811
|
function isJsxOnlyParagraph(node) {
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
(child) => child.type === "mdxJsxTextElement" || child.type === "mdxJsxFlowElement" || child.type === "text" && child.value?.trim() === ""
|
|
842
|
-
);
|
|
812
|
+
const children = node.children ?? [];
|
|
813
|
+
return children.length > 0 && children.every((child) => child.type === "mdxJsxTextElement" || child.type === "mdxJsxFlowElement" || child.type === "text" && child.value?.trim() === "");
|
|
843
814
|
}
|
|
844
815
|
function sourceForNode(node, source) {
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
816
|
+
const start = node.position?.start?.offset;
|
|
817
|
+
const end = node.position?.end?.offset;
|
|
818
|
+
return typeof start === "number" && typeof end === "number" ? source.slice(start, end) : void 0;
|
|
848
819
|
}
|
|
849
820
|
function isComponentName(name) {
|
|
850
|
-
|
|
821
|
+
return /^[A-Z]/.test(name);
|
|
851
822
|
}
|
|
852
823
|
function isUnsafeHtmlElement(name) {
|
|
853
|
-
|
|
824
|
+
return /^(script|style|iframe|object|embed)$/i.test(name);
|
|
854
825
|
}
|
|
855
826
|
function headingDepth(value) {
|
|
856
|
-
|
|
857
|
-
|
|
827
|
+
if (value === 1 || value === 2 || value === 3) return value;
|
|
828
|
+
return 3;
|
|
858
829
|
}
|
|
859
830
|
function stringAttr(value) {
|
|
860
|
-
|
|
831
|
+
return value && value.length > 0 ? value : void 0;
|
|
861
832
|
}
|
|
862
|
-
|
|
863
|
-
|
|
833
|
+
//#endregion
|
|
834
|
+
//#region src/generator/mdx/assets.ts
|
|
864
835
|
function rewriteAssetUrls(source, assets) {
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
836
|
+
let result = source;
|
|
837
|
+
for (const asset of assets) {
|
|
838
|
+
const assetPath = localAssetRelativePath(asset.sourcePath);
|
|
839
|
+
result = result.replaceAll(`./assets/${assetPath}`, asset.publicPath);
|
|
840
|
+
result = result.replace(new RegExp(`(?<!/)assets/${escapeRegExp(assetPath)}`, "g"), asset.publicPath);
|
|
841
|
+
}
|
|
842
|
+
return result;
|
|
872
843
|
}
|
|
873
844
|
function rewriteRelativeMdxImports(source, sourceDir, generatedDir) {
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
});
|
|
845
|
+
return source.replace(/(from\s+|import\s+)(["'])(\.[^"']+)\2/g, (_match, prefix, quote, specifier) => {
|
|
846
|
+
return `${prefix}${quote}${toRelativeImportPath(generatedDir, normalizePath$2(`${sourceDir}/${specifier}`))}${quote}`;
|
|
847
|
+
});
|
|
878
848
|
}
|
|
879
849
|
function componentImportPath(outDir, sourcePath) {
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
return toRelativeImportPath(outDir, source);
|
|
850
|
+
if (!sourcePath) return void 0;
|
|
851
|
+
return toRelativeImportPath(outDir, sourcePath.replace(/\/index\.(tsx|ts|jsx|js)$/, ""));
|
|
883
852
|
}
|
|
884
|
-
function dirname(path) {
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
853
|
+
function dirname$1(path) {
|
|
854
|
+
const normalized = normalizePath$2(path);
|
|
855
|
+
const index = normalized.lastIndexOf("/");
|
|
856
|
+
return index === -1 ? "." : normalized.slice(0, index);
|
|
888
857
|
}
|
|
889
858
|
function localAssetRelativePath(sourcePath) {
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
859
|
+
const marker = "/assets/";
|
|
860
|
+
const normalized = normalizePath$2(sourcePath);
|
|
861
|
+
const markerIndex = normalized.indexOf(marker);
|
|
862
|
+
return markerIndex === -1 ? normalized.split("/").at(-1) ?? normalized : normalized.slice(markerIndex + 8);
|
|
894
863
|
}
|
|
895
|
-
function
|
|
896
|
-
|
|
864
|
+
function normalizePath$2(path) {
|
|
865
|
+
return path.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+/g, "/");
|
|
897
866
|
}
|
|
898
867
|
function toRelativeImportPath(fromDir, target) {
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
868
|
+
const fromParts = normalizePath$2(fromDir).split("/").filter(Boolean);
|
|
869
|
+
const targetParts = normalizePath$2(target).split("/").filter(Boolean);
|
|
870
|
+
while (fromParts.length > 0 && targetParts.length > 0 && fromParts[0] === targetParts[0]) {
|
|
871
|
+
fromParts.shift();
|
|
872
|
+
targetParts.shift();
|
|
873
|
+
}
|
|
874
|
+
const relative = [...fromParts.map(() => ".."), ...targetParts].join("/");
|
|
875
|
+
return relative.startsWith(".") ? relative : `./${relative || "."}`;
|
|
907
876
|
}
|
|
908
877
|
function escapeRegExp(value) {
|
|
909
|
-
|
|
878
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
910
879
|
}
|
|
911
|
-
|
|
912
|
-
|
|
880
|
+
//#endregion
|
|
881
|
+
//#region src/generator/assets.ts
|
|
913
882
|
async function buildAssetRefs(slug, assetPaths, input) {
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
)}`,
|
|
922
|
-
type: "local",
|
|
923
|
-
contentType: contentTypeForPath(sourcePath),
|
|
924
|
-
body: input.readBinary ? await input.readBinary(sourcePath) : void 0
|
|
925
|
-
}))
|
|
926
|
-
);
|
|
883
|
+
return Promise.all(assetPaths.map(async (sourcePath) => ({
|
|
884
|
+
sourcePath,
|
|
885
|
+
publicPath: `${normalizeMountPath(input.mountPath ?? `/${input.root}`)}/${encodeURIComponent(slug)}/assets/${assetName(sourcePath, input.root, slug)}`,
|
|
886
|
+
type: "local",
|
|
887
|
+
contentType: contentTypeForPath(sourcePath),
|
|
888
|
+
body: input.readBinary ? await input.readBinary(sourcePath) : void 0
|
|
889
|
+
})));
|
|
927
890
|
}
|
|
928
891
|
function assetName(sourcePath, root, slug) {
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
return relative4.split("/").map(encodeURIComponent).join("/");
|
|
892
|
+
const normalizedPath = normalizePath$1(sourcePath);
|
|
893
|
+
const prefix = `${normalizePath$1(root).replace(/\/$/, "")}/${slug}/assets/`;
|
|
894
|
+
return (normalizedPath.startsWith(prefix) ? normalizedPath.slice(prefix.length) : normalizedPath.split("/").at(-1) ?? normalizedPath).split("/").map(encodeURIComponent).join("/");
|
|
933
895
|
}
|
|
934
896
|
function normalizeMountPath(value) {
|
|
935
|
-
|
|
936
|
-
return withLeadingSlash.replace(/\/$/, "");
|
|
897
|
+
return (value.startsWith("/") ? value : `/${value}`).replace(/\/$/, "");
|
|
937
898
|
}
|
|
938
|
-
function
|
|
939
|
-
|
|
899
|
+
function normalizePath$1(path) {
|
|
900
|
+
return path.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+/g, "/");
|
|
940
901
|
}
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
import { compile } from "@mdx-js/mdx";
|
|
948
|
-
import remarkDirective from "remark-directive";
|
|
949
|
-
import remarkGfm2 from "remark-gfm";
|
|
950
|
-
|
|
951
|
-
// src/generator/mdx/emit.ts
|
|
902
|
+
//#endregion
|
|
903
|
+
//#region src/generator/package-entry.ts
|
|
904
|
+
const DECKS_RUNTIME_ENTRY = "hono-decks";
|
|
905
|
+
const DECKS_ADVANCED_ENTRY = "hono-decks/advanced";
|
|
906
|
+
//#endregion
|
|
907
|
+
//#region src/generator/mdx/emit.ts
|
|
952
908
|
function emitModuleDecksRouter(input) {
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
(deck) => deck.slideModules.map(
|
|
957
|
-
(slide, index) => `import ${slideImportName(deck.deck.slug, index)} from ${JSON.stringify(slide.importPath)};`
|
|
958
|
-
)
|
|
959
|
-
).join("\n");
|
|
960
|
-
const componentImports = input.decks.filter((deck) => deck.componentModulePath).map((deck) => `import * as ${componentImportName(deck.deck.slug)} from ${JSON.stringify(deck.componentModulePath)};`).join("\n");
|
|
961
|
-
return `// @ts-nocheck
|
|
962
|
-
import { configureDecks, defineDecks } from ${advancedEntry};
|
|
909
|
+
const runtimeEntry = JSON.stringify(DECKS_RUNTIME_ENTRY);
|
|
910
|
+
return `// @ts-nocheck
|
|
911
|
+
import { configureDecks, defineDecks } from ${JSON.stringify(DECKS_ADVANCED_ENTRY)};
|
|
963
912
|
import type { ConfiguredDecks, DecksConfig } from ${runtimeEntry};
|
|
964
913
|
import type { Env } from "hono";
|
|
965
914
|
import { decksClientEntry } from "./client-entry";
|
|
966
|
-
${
|
|
967
|
-
${
|
|
915
|
+
${input.decks.flatMap((deck) => deck.slideModules.map((slide, index) => `import ${slideImportName(deck.deck.slug, index)} from ${JSON.stringify(slide.importPath)};`)).join("\n")}
|
|
916
|
+
${input.decks.filter((deck) => deck.componentModulePath).map((deck) => `import * as ${componentImportName(deck.deck.slug)} from ${JSON.stringify(deck.componentModulePath)};`).join("\n")}
|
|
968
917
|
|
|
969
918
|
function withClientComponentIds(module, clientIds) {
|
|
970
919
|
const registry = {};
|
|
@@ -998,589 +947,510 @@ function definedDecksFor<E extends Env>() {
|
|
|
998
947
|
`;
|
|
999
948
|
}
|
|
1000
949
|
function emitDeckObject(deck) {
|
|
1001
|
-
|
|
950
|
+
return ` {
|
|
1002
951
|
slug: ${JSON.stringify(deck.deck.slug)},
|
|
1003
952
|
sourcePath: ${JSON.stringify(deck.deck.sourcePath)},
|
|
1004
953
|
kind: ${JSON.stringify(deck.deck.kind)},
|
|
1005
954
|
meta: ${serializeValue(deck.deck.meta, 3)},
|
|
1006
|
-
${deck.deck.themeStyle ? ` "themeStyle": ${serializeValue(deck.deck.themeStyle, 3)}
|
|
1007
|
-
` : ""}${deck.deck.themeSourcePath ? ` "themeSourcePath": ${JSON.stringify(deck.deck.themeSourcePath)},
|
|
1008
|
-
` : ""}
|
|
955
|
+
${deck.deck.themeStyle ? ` "themeStyle": ${serializeValue(deck.deck.themeStyle, 3)},\n` : ""}${deck.deck.themeSourcePath ? ` "themeSourcePath": ${JSON.stringify(deck.deck.themeSourcePath)},\n` : ""}
|
|
1009
956
|
assets: ${serializeValue(deck.deck.assets, 3)},
|
|
1010
957
|
componentRegistry: ${deck.componentModulePath ? `withClientComponentIds(${componentImportName(deck.deck.slug)}, ${serializeValue(deck.clientComponentIds ?? {}, 3)})` : "{}"},
|
|
1011
958
|
warnings: ${serializeValue(deck.deck.warnings, 3)},
|
|
1012
959
|
slides: [
|
|
1013
|
-
${deck.deck.slides.map(
|
|
1014
|
-
(slide) => ` {
|
|
960
|
+
${deck.deck.slides.map((slide) => ` {
|
|
1015
961
|
index: ${slide.index},
|
|
1016
962
|
meta: ${serializeValue(slide.meta, 5)},
|
|
1017
963
|
html: "",
|
|
1018
964
|
components: [],
|
|
1019
965
|
notes: ${serializeValue(slide.notes, 5)},
|
|
1020
966
|
render: ${slideImportName(deck.deck.slug, slide.index)}
|
|
1021
|
-
}`
|
|
1022
|
-
).join(",\n")}
|
|
967
|
+
}`).join(",\n")}
|
|
1023
968
|
]
|
|
1024
969
|
}`;
|
|
1025
970
|
}
|
|
1026
971
|
function slideImportName(slug, index) {
|
|
1027
|
-
|
|
972
|
+
return `Slide_${safeIdentifier$1(slug)}_${index}`;
|
|
1028
973
|
}
|
|
1029
974
|
function componentImportName(slug) {
|
|
1030
|
-
|
|
975
|
+
return `Components_${safeIdentifier$1(slug)}`;
|
|
1031
976
|
}
|
|
1032
|
-
function safeIdentifier(value) {
|
|
1033
|
-
|
|
977
|
+
function safeIdentifier$1(value) {
|
|
978
|
+
return value.replace(/[^A-Za-z0-9_$]+/g, "_").replace(/^[^A-Za-z_$]+/, "_") || "_";
|
|
1034
979
|
}
|
|
1035
980
|
function serializeValue(value, depth) {
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
return JSON.stringify(value);
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
// src/generator/mdx/ogp.ts
|
|
981
|
+
const indent = " ".repeat(depth);
|
|
982
|
+
const nextIndent = " ".repeat(depth + 1);
|
|
983
|
+
if (value === void 0) return "undefined";
|
|
984
|
+
if (value instanceof Uint8Array) return `new Uint8Array([${[...value].join(", ")}])`;
|
|
985
|
+
if (Array.isArray(value)) {
|
|
986
|
+
if (value.length === 0) return "[]";
|
|
987
|
+
return `[\n${value.map((item) => `${nextIndent}${serializeValue(item, depth + 1)}`).join(",\n")}\n${indent}]`;
|
|
988
|
+
}
|
|
989
|
+
if (typeof value === "object" && value !== null) {
|
|
990
|
+
const entries = Object.entries(value).filter(([, item]) => item !== void 0);
|
|
991
|
+
if (entries.length === 0) return "{}";
|
|
992
|
+
return `{\n${entries.map(([key, item]) => `${nextIndent}${JSON.stringify(key)}: ${serializeValue(item, depth + 1)}`).join(",\n")}\n${indent}}`;
|
|
993
|
+
}
|
|
994
|
+
return JSON.stringify(value);
|
|
995
|
+
}
|
|
996
|
+
//#endregion
|
|
997
|
+
//#region src/generator/mdx/ogp.ts
|
|
1057
998
|
async function resolveLinkCardMetadataByUrl(source, resolveOgp) {
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
return result;
|
|
999
|
+
const result = /* @__PURE__ */ new Map();
|
|
1000
|
+
if (!resolveOgp) return result;
|
|
1001
|
+
for (const url of collectLinkCardUrls(source)) try {
|
|
1002
|
+
const metadata = await resolveOgp(url);
|
|
1003
|
+
if (metadata) result.set(url, metadata);
|
|
1004
|
+
} catch {}
|
|
1005
|
+
return result;
|
|
1068
1006
|
}
|
|
1069
1007
|
function collectLinkCardUrls(source) {
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
urls.add(match[1]);
|
|
1074
|
-
}
|
|
1075
|
-
return [...urls];
|
|
1008
|
+
const urls = /* @__PURE__ */ new Set();
|
|
1009
|
+
for (const match of source.matchAll(/@\[card\]\(([^)\s]+)\)/g)) urls.add(match[1]);
|
|
1010
|
+
return [...urls];
|
|
1076
1011
|
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
import { codeToHtml } from "shiki";
|
|
1012
|
+
//#endregion
|
|
1013
|
+
//#region src/generator/mdx/syntax.ts
|
|
1080
1014
|
function remarkDeckSyntax(input = {}) {
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1015
|
+
return () => (tree) => {
|
|
1016
|
+
transformDeckSyntaxChildren(tree, input);
|
|
1017
|
+
};
|
|
1084
1018
|
}
|
|
1085
1019
|
function remarkCodeHighlight() {
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1020
|
+
return async (tree) => {
|
|
1021
|
+
await highlightMarkdownNode(tree);
|
|
1022
|
+
};
|
|
1089
1023
|
}
|
|
1090
1024
|
function transformDeckSyntaxChildren(node, input) {
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
}
|
|
1100
|
-
node.children = children;
|
|
1025
|
+
if (!Array.isArray(node.children)) return;
|
|
1026
|
+
const children = [];
|
|
1027
|
+
for (const child of node.children) {
|
|
1028
|
+
transformDeckSyntaxChildren(child, input);
|
|
1029
|
+
rejectRemovedFireAuthoring(child);
|
|
1030
|
+
children.push(fireAttributeNode(child) ?? zennEmbedNode(child, input) ?? plainUrlLinkNode(child) ?? fireDirectiveNode(child) ?? unknownDirectiveFallback(child) ?? child);
|
|
1031
|
+
}
|
|
1032
|
+
node.children = children;
|
|
1101
1033
|
}
|
|
1102
1034
|
function rejectRemovedFireAuthoring(node) {
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
if (node.name === "Fire") {
|
|
1112
|
-
const atAttribute = node.attributes.find((attribute) => attribute.name === "at");
|
|
1113
|
-
if (atAttribute) fireAtAttributeValue(atAttribute.value);
|
|
1114
|
-
}
|
|
1035
|
+
if (node.type !== "mdxJsxFlowElement" && node.type !== "mdxJsxTextElement") return;
|
|
1036
|
+
if (!Array.isArray(node.attributes)) return;
|
|
1037
|
+
if (node.attributes.some((attribute) => attribute.name === "$fire")) throw new Error("The \"$fire\" prop is not supported. Use fire or fire=\"effect\" on a block-level custom component.");
|
|
1038
|
+
if (node.name === "Fire" && node.attributes.some((attribute) => attribute.name === "order")) throw new Error("The Fire \"order\" prop is not supported. Fires reveal in source order.");
|
|
1039
|
+
if (node.name === "Fire") {
|
|
1040
|
+
const atAttribute = node.attributes.find((attribute) => attribute.name === "at");
|
|
1041
|
+
if (atAttribute) fireAtAttributeValue(atAttribute.value);
|
|
1042
|
+
}
|
|
1115
1043
|
}
|
|
1116
1044
|
function fireAttributeNode(node) {
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
throw new Error('The "fire" attribute is only supported on custom components. Use :::fire for Markdown content.');
|
|
1131
|
-
}
|
|
1132
|
-
if (node.type !== "mdxJsxFlowElement") {
|
|
1133
|
-
throw new Error('The "fire" attribute is only supported on block-level custom components. Move the component to its own line.');
|
|
1134
|
-
}
|
|
1135
|
-
if (fireAttribute.value !== null && fireAttribute.value !== void 0 && typeof fireAttribute.value !== "string") {
|
|
1136
|
-
throw new Error('The "fire" attribute accepts no value or a static effect name such as fire="fade-up".');
|
|
1137
|
-
}
|
|
1138
|
-
const effect = typeof fireAttribute.value === "string" ? fireAttribute.value.trim() : "";
|
|
1139
|
-
const at = atAttribute ? fireAtAttributeValue(atAttribute.value) : void 0;
|
|
1140
|
-
node.attributes = node.attributes.filter((attribute) => attribute !== fireAttribute && attribute !== atAttribute);
|
|
1141
|
-
return mdxElement(
|
|
1142
|
-
"Fire",
|
|
1143
|
-
[
|
|
1144
|
-
...effect ? [mdxAttribute("effect", effect)] : [],
|
|
1145
|
-
...at ? [mdxAttribute("at", at)] : []
|
|
1146
|
-
],
|
|
1147
|
-
[node]
|
|
1148
|
-
);
|
|
1045
|
+
if (node.type !== "mdxJsxFlowElement" && node.type !== "mdxJsxTextElement") return void 0;
|
|
1046
|
+
if (!Array.isArray(node.attributes)) return void 0;
|
|
1047
|
+
const fireAttribute = node.attributes.find((attribute) => attribute.type === "mdxJsxAttribute" && attribute.name === "fire");
|
|
1048
|
+
if (!fireAttribute) return void 0;
|
|
1049
|
+
const atAttribute = node.attributes.find((attribute) => attribute.type === "mdxJsxAttribute" && attribute.name === "at");
|
|
1050
|
+
if (node.name === "Fire") throw new Error("The \"fire\" attribute is not supported on <Fire>. Use the effect prop instead.");
|
|
1051
|
+
if (!node.name || !/^[A-Z]/.test(node.name)) throw new Error("The \"fire\" attribute is only supported on custom components. Use :::fire for Markdown content.");
|
|
1052
|
+
if (node.type !== "mdxJsxFlowElement") throw new Error("The \"fire\" attribute is only supported on block-level custom components. Move the component to its own line.");
|
|
1053
|
+
if (fireAttribute.value !== null && fireAttribute.value !== void 0 && typeof fireAttribute.value !== "string") throw new Error("The \"fire\" attribute accepts no value or a static effect name such as fire=\"fade-up\".");
|
|
1054
|
+
const effect = typeof fireAttribute.value === "string" ? fireAttribute.value.trim() : "";
|
|
1055
|
+
const at = atAttribute ? fireAtAttributeValue(atAttribute.value) : void 0;
|
|
1056
|
+
node.attributes = node.attributes.filter((attribute) => attribute !== fireAttribute && attribute !== atAttribute);
|
|
1057
|
+
return mdxElement("Fire", [...effect ? [mdxAttribute("effect", effect)] : [], ...at ? [mdxAttribute("at", at)] : []], [node]);
|
|
1149
1058
|
}
|
|
1150
1059
|
function fireAtAttributeValue(value) {
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1060
|
+
if (typeof value === "string") {
|
|
1061
|
+
const at = value.trim();
|
|
1062
|
+
if (/^(?:\d+|[+-]\d+)$/.test(at)) return at;
|
|
1063
|
+
}
|
|
1064
|
+
throw new Error("The fire \"at\" attribute accepts a non-negative integer or a relative value such as \"+1\".");
|
|
1156
1065
|
}
|
|
1157
1066
|
function zennEmbedNode(node, input) {
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
"LinkCard",
|
|
1182
|
-
[
|
|
1183
|
-
mdxAttribute("href", link.url),
|
|
1184
|
-
...metadataAttributes(metadata)
|
|
1185
|
-
],
|
|
1186
|
-
[]
|
|
1187
|
-
);
|
|
1188
|
-
}
|
|
1189
|
-
if (name === "embed" || name === "iframe") {
|
|
1190
|
-
return mdxElement(
|
|
1191
|
-
"EmbedFrame",
|
|
1192
|
-
[mdxAttribute("src", link.url), mdxAttribute("title", "Embedded content")],
|
|
1193
|
-
[{ type: "text", value: "Open embed" }]
|
|
1194
|
-
);
|
|
1195
|
-
}
|
|
1196
|
-
return void 0;
|
|
1067
|
+
if (node.type !== "paragraph" || !Array.isArray(node.children) || node.children.length !== 2) return void 0;
|
|
1068
|
+
const [prefix, link] = node.children;
|
|
1069
|
+
if (prefix?.type !== "text" || String(prefix.value ?? "").trim() !== "@") return void 0;
|
|
1070
|
+
if (link?.type !== "link" || typeof link.url !== "string") return void 0;
|
|
1071
|
+
const name = collectMarkdownText(link).trim().toLowerCase();
|
|
1072
|
+
if (name === "youtube") return mdxElement("EmbedFrame", [
|
|
1073
|
+
mdxAttribute("provider", "youtube"),
|
|
1074
|
+
mdxAttribute("src", toYoutubeEmbedUrl(link.url)),
|
|
1075
|
+
mdxAttribute("fallbackHref", link.url),
|
|
1076
|
+
mdxAttribute("title", "YouTube embed example")
|
|
1077
|
+
], [{
|
|
1078
|
+
type: "text",
|
|
1079
|
+
value: "Open YouTube embed"
|
|
1080
|
+
}]);
|
|
1081
|
+
if (name === "x") return mdxElement("TweetEmbed", [mdxAttribute("href", link.url), mdxAttribute("label", "Open post on X")], []);
|
|
1082
|
+
if (name === "card") {
|
|
1083
|
+
const metadata = input.linkCardMetadata?.get(link.url);
|
|
1084
|
+
return mdxElement("LinkCard", [mdxAttribute("href", link.url), ...metadataAttributes(metadata)], []);
|
|
1085
|
+
}
|
|
1086
|
+
if (name === "embed" || name === "iframe") return mdxElement("EmbedFrame", [mdxAttribute("src", link.url), mdxAttribute("title", "Embedded content")], [{
|
|
1087
|
+
type: "text",
|
|
1088
|
+
value: "Open embed"
|
|
1089
|
+
}]);
|
|
1197
1090
|
}
|
|
1198
1091
|
function metadataAttributes(metadata) {
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1092
|
+
if (!metadata) return [];
|
|
1093
|
+
return [
|
|
1094
|
+
...metadata.title ? [mdxAttribute("title", metadata.title)] : [],
|
|
1095
|
+
...metadata.description ? [mdxAttribute("description", metadata.description)] : [],
|
|
1096
|
+
...metadata.image ? [mdxAttribute("image", metadata.image)] : [],
|
|
1097
|
+
...metadata.siteName ? [mdxAttribute("siteName", metadata.siteName)] : []
|
|
1098
|
+
];
|
|
1206
1099
|
}
|
|
1207
1100
|
function plainUrlLinkNode(node) {
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1101
|
+
if (node.type !== "paragraph" || !Array.isArray(node.children) || node.children.length !== 1) return void 0;
|
|
1102
|
+
const [child] = node.children;
|
|
1103
|
+
if (child?.type !== "text" || typeof child.value !== "string") return void 0;
|
|
1104
|
+
const value = child.value.trim();
|
|
1105
|
+
if (!isHttpUrl$1(value)) return void 0;
|
|
1106
|
+
return {
|
|
1107
|
+
type: "paragraph",
|
|
1108
|
+
children: [{
|
|
1109
|
+
type: "link",
|
|
1110
|
+
url: value,
|
|
1111
|
+
children: [{
|
|
1112
|
+
type: "text",
|
|
1113
|
+
value
|
|
1114
|
+
}]
|
|
1115
|
+
}]
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
function isHttpUrl$1(value) {
|
|
1119
|
+
try {
|
|
1120
|
+
const url = new URL(value);
|
|
1121
|
+
return url.protocol === "https:" || url.protocol === "http:";
|
|
1122
|
+
} catch {
|
|
1123
|
+
return false;
|
|
1124
|
+
}
|
|
1231
1125
|
}
|
|
1232
1126
|
function fireDirectiveNode(node) {
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
}
|
|
1241
|
-
if (attributes.each !== void 0) return fireEachItemNode(node, attributes);
|
|
1242
|
-
const at = attributes.at !== void 0 ? fireAtAttributeValue(attributes.at) : void 0;
|
|
1243
|
-
const fireAttributes = [
|
|
1244
|
-
...typeof attributes.effect === "string" ? [mdxAttribute("effect", attributes.effect)] : [],
|
|
1245
|
-
...at ? [mdxAttribute("at", at)] : []
|
|
1246
|
-
];
|
|
1247
|
-
return mdxElement("Fire", fireAttributes, node.children ?? []);
|
|
1127
|
+
if (node.type !== "containerDirective" || node.name !== "fire") return void 0;
|
|
1128
|
+
const attributes = directiveAttributes(node);
|
|
1129
|
+
if (attributes.order !== void 0) throw new Error("The fire \"order\" attribute is not supported. Fires reveal in source order.");
|
|
1130
|
+
if (attributes.each === void 0 && (attributes.depth !== void 0 || attributes.every !== void 0)) throw new Error("The fire \"depth\" and \"every\" attributes require each=\"item\".");
|
|
1131
|
+
if (attributes.each !== void 0) return fireEachItemNode(node, attributes);
|
|
1132
|
+
const at = attributes.at !== void 0 ? fireAtAttributeValue(attributes.at) : void 0;
|
|
1133
|
+
return mdxElement("Fire", [...typeof attributes.effect === "string" ? [mdxAttribute("effect", attributes.effect)] : [], ...at ? [mdxAttribute("at", at)] : []], node.children ?? []);
|
|
1248
1134
|
}
|
|
1249
1135
|
function fireEachItemNode(node, attributes) {
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
}
|
|
1273
|
-
};
|
|
1274
|
-
}
|
|
1275
|
-
return list;
|
|
1136
|
+
if (attributes.each !== "item") throw new Error("The fire \"each\" attribute only accepts \"item\".");
|
|
1137
|
+
const children = node.children ?? [];
|
|
1138
|
+
const list = children.length === 1 && children[0]?.type === "list" ? children[0] : void 0;
|
|
1139
|
+
if (!list) throw new Error("fire each=\"item\" must contain exactly one Markdown list.");
|
|
1140
|
+
const depth = positiveFireInteger(attributes.depth, "depth", 1);
|
|
1141
|
+
const every = positiveFireInteger(attributes.every, "every", 1);
|
|
1142
|
+
const at = attributes.at !== void 0 ? fireAtAttributeValue(attributes.at) : void 0;
|
|
1143
|
+
const effect = attributes.effect ? fireEffectToken(attributes.effect) : void 0;
|
|
1144
|
+
const items = fireListItems(list, depth);
|
|
1145
|
+
for (const [itemIndex, item] of items.entries()) {
|
|
1146
|
+
const itemAt = fireListItemAt(at, every, itemIndex);
|
|
1147
|
+
item.data = {
|
|
1148
|
+
...item.data,
|
|
1149
|
+
hProperties: {
|
|
1150
|
+
...item.data?.hProperties,
|
|
1151
|
+
"data-hono-decks-fire": "true",
|
|
1152
|
+
...itemAt ? { "data-fire-at": itemAt } : {},
|
|
1153
|
+
...effect ? { "data-fire-effect": effect } : {}
|
|
1154
|
+
}
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
return list;
|
|
1276
1158
|
}
|
|
1277
1159
|
function positiveFireInteger(value, name, fallback) {
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1160
|
+
if (value === void 0) return fallback;
|
|
1161
|
+
const parsed = Number(value);
|
|
1162
|
+
if (Number.isInteger(parsed) && parsed > 0) return parsed;
|
|
1163
|
+
throw new Error(`The fire "${name}" attribute accepts a positive integer.`);
|
|
1282
1164
|
}
|
|
1283
1165
|
function fireListItems(list, maxDepth, depth = 1) {
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
}
|
|
1293
|
-
return items;
|
|
1166
|
+
const items = [];
|
|
1167
|
+
for (const item of list.children ?? []) {
|
|
1168
|
+
if (item.type !== "listItem") continue;
|
|
1169
|
+
items.push(item);
|
|
1170
|
+
if (depth >= maxDepth) continue;
|
|
1171
|
+
for (const child of item.children ?? []) if (child.type === "list") items.push(...fireListItems(child, maxDepth, depth + 1));
|
|
1172
|
+
}
|
|
1173
|
+
return items;
|
|
1294
1174
|
}
|
|
1295
1175
|
function fireListItemAt(at, every, itemIndex) {
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1176
|
+
if (at && /^\d+$/.test(at)) return String(Number(at) + Math.floor(itemIndex / every));
|
|
1177
|
+
if (!at && every === 1) return void 0;
|
|
1178
|
+
if (itemIndex === 0) return at;
|
|
1179
|
+
return itemIndex % every === 0 ? "+1" : "+0";
|
|
1300
1180
|
}
|
|
1301
1181
|
function fireEffectToken(value) {
|
|
1302
|
-
|
|
1182
|
+
return value.trim().replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "fade";
|
|
1303
1183
|
}
|
|
1304
1184
|
function unknownDirectiveFallback(node) {
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1185
|
+
if (node.type === "textDirective") return {
|
|
1186
|
+
type: "text",
|
|
1187
|
+
value: `:${node.name ?? ""}`
|
|
1188
|
+
};
|
|
1189
|
+
if (node.type === "leafDirective") return {
|
|
1190
|
+
type: "text",
|
|
1191
|
+
value: `::${node.name ?? ""}`
|
|
1192
|
+
};
|
|
1193
|
+
if (node.type === "containerDirective") return {
|
|
1194
|
+
type: "paragraph",
|
|
1195
|
+
children: [{
|
|
1196
|
+
type: "text",
|
|
1197
|
+
value: `:::${node.name ?? ""}`
|
|
1198
|
+
}, ...node.children ?? []]
|
|
1199
|
+
};
|
|
1314
1200
|
}
|
|
1315
1201
|
function directiveAttributes(node) {
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
result[key] = String(value);
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
return result;
|
|
1202
|
+
if (!node.attributes || Array.isArray(node.attributes)) return {};
|
|
1203
|
+
const result = {};
|
|
1204
|
+
for (const [key, value] of Object.entries(node.attributes)) if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") result[key] = String(value);
|
|
1205
|
+
return result;
|
|
1324
1206
|
}
|
|
1325
1207
|
function mdxElement(name, attributes, children) {
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1208
|
+
return {
|
|
1209
|
+
type: "mdxJsxFlowElement",
|
|
1210
|
+
name,
|
|
1211
|
+
attributes,
|
|
1212
|
+
children
|
|
1213
|
+
};
|
|
1332
1214
|
}
|
|
1333
1215
|
function toYoutubeEmbedUrl(value) {
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1216
|
+
try {
|
|
1217
|
+
const url = new URL(value);
|
|
1218
|
+
const host = url.hostname.replace(/^www\./, "");
|
|
1219
|
+
if (host === "youtu.be") {
|
|
1220
|
+
const id = url.pathname.split("/").filter(Boolean)[0];
|
|
1221
|
+
return id ? `https://www.youtube.com/embed/${id}` : value;
|
|
1222
|
+
}
|
|
1223
|
+
if (host === "youtube.com" || host === "m.youtube.com") {
|
|
1224
|
+
if (url.pathname.startsWith("/embed/")) return value;
|
|
1225
|
+
const id = url.searchParams.get("v");
|
|
1226
|
+
return id ? `https://www.youtube.com/embed/${id}` : value;
|
|
1227
|
+
}
|
|
1228
|
+
return value;
|
|
1229
|
+
} catch {
|
|
1230
|
+
return value;
|
|
1231
|
+
}
|
|
1350
1232
|
}
|
|
1351
1233
|
async function highlightMarkdownNode(node) {
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
await Promise.all(node.children.map((child) => highlightMarkdownNode(child)));
|
|
1234
|
+
if (node.type === "code") {
|
|
1235
|
+
const code = typeof node.value === "string" ? node.value : "";
|
|
1236
|
+
const lang = typeof node.lang === "string" && node.lang ? node.lang : void 0;
|
|
1237
|
+
const highlightedHtml = await highlightCodeBlock(code, lang);
|
|
1238
|
+
node.type = "mdxJsxFlowElement";
|
|
1239
|
+
node.name = "CodeBlock";
|
|
1240
|
+
node.attributes = [...lang ? [mdxAttribute("lang", lang)] : [], mdxAttribute("highlightedHtml", highlightedHtml)];
|
|
1241
|
+
node.children = [{
|
|
1242
|
+
type: "text",
|
|
1243
|
+
value: code
|
|
1244
|
+
}];
|
|
1245
|
+
delete node.value;
|
|
1246
|
+
delete node.lang;
|
|
1247
|
+
delete node.meta;
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
if (node.type === "mdxJsxFlowElement" && node.name === "CodeBlock") {
|
|
1251
|
+
const code = collectMarkdownText(node);
|
|
1252
|
+
if (code.trim()) {
|
|
1253
|
+
const highlightedHtml = await highlightCodeBlock(code, getMdxStringAttribute(node, "lang"));
|
|
1254
|
+
node.attributes = upsertMdxStringAttribute(node.attributes, "highlightedHtml", highlightedHtml);
|
|
1255
|
+
}
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
if (!Array.isArray(node.children)) return;
|
|
1259
|
+
await Promise.all(node.children.map((child) => highlightMarkdownNode(child)));
|
|
1379
1260
|
}
|
|
1380
1261
|
async function highlightCodeBlock(code, lang) {
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1262
|
+
const language = lang && /^[A-Za-z0-9_#+.-]+$/.test(lang) ? lang : "text";
|
|
1263
|
+
try {
|
|
1264
|
+
return await codeToHtml(code, {
|
|
1265
|
+
lang: language,
|
|
1266
|
+
theme: "github-dark"
|
|
1267
|
+
});
|
|
1268
|
+
} catch (error) {
|
|
1269
|
+
if (language === "text") throw error;
|
|
1270
|
+
return codeToHtml(code, {
|
|
1271
|
+
lang: "text",
|
|
1272
|
+
theme: "github-dark"
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1388
1275
|
}
|
|
1389
1276
|
function mdxAttribute(name, value) {
|
|
1390
|
-
|
|
1277
|
+
return {
|
|
1278
|
+
type: "mdxJsxAttribute",
|
|
1279
|
+
name,
|
|
1280
|
+
value
|
|
1281
|
+
};
|
|
1391
1282
|
}
|
|
1392
1283
|
function getMdxStringAttribute(node, name) {
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
return typeof attribute?.value === "string" ? attribute.value : void 0;
|
|
1284
|
+
const attribute = (Array.isArray(node.attributes) ? node.attributes : []).find((item) => item.type === "mdxJsxAttribute" && item.name === name);
|
|
1285
|
+
return typeof attribute?.value === "string" ? attribute.value : void 0;
|
|
1396
1286
|
}
|
|
1397
1287
|
function upsertMdxStringAttribute(attributes, name, value) {
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1288
|
+
const next = Array.isArray(attributes) ? [...attributes] : [];
|
|
1289
|
+
const index = next.findIndex((item) => item.type === "mdxJsxAttribute" && item.name === name);
|
|
1290
|
+
const attribute = mdxAttribute(name, value);
|
|
1291
|
+
if (index === -1) return [...next, attribute];
|
|
1292
|
+
next[index] = attribute;
|
|
1293
|
+
return next;
|
|
1404
1294
|
}
|
|
1405
1295
|
function collectMarkdownText(node) {
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1296
|
+
if (typeof node.value === "string") return node.value;
|
|
1297
|
+
if (!Array.isArray(node.children)) return "";
|
|
1298
|
+
return node.children.map((child) => collectMarkdownText(child)).join(node.type === "paragraph" ? "\n" : "");
|
|
1409
1299
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1300
|
+
//#endregion
|
|
1301
|
+
//#region src/generator/mdx-module-generator.ts
|
|
1412
1302
|
async function compileMdxModuleDecks(input) {
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1303
|
+
const decks = await Promise.all(input.decks.map((entry) => compileMdxModuleDeck(input, entry)));
|
|
1304
|
+
return {
|
|
1305
|
+
decks,
|
|
1306
|
+
routerModule: emitModuleDecksRouter({ decks })
|
|
1307
|
+
};
|
|
1418
1308
|
}
|
|
1419
1309
|
async function compileMdxModuleDeck(input, entry) {
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
slides,
|
|
1480
|
-
assets,
|
|
1481
|
-
warnings
|
|
1482
|
-
},
|
|
1483
|
-
slideModules
|
|
1484
|
-
};
|
|
1310
|
+
const { attrs, body } = readFrontmatter(await input.readText(entry.sourcePath));
|
|
1311
|
+
const { prelude, body: contentBody } = extractDeckPrelude(body);
|
|
1312
|
+
const slideSources = splitSlideSources(contentBody);
|
|
1313
|
+
let warnings = [];
|
|
1314
|
+
const deckMeta = toDeckFrontmatter(attrs, warnings);
|
|
1315
|
+
addUnknownFrontmatterWarnings(warnings, deckMeta.meta, "deck");
|
|
1316
|
+
const assets = [...await buildAssetRefs(entry.slug, entry.assetPaths, input), ...collectGeneratedExternalAssetRefs(contentBody, deckMeta)];
|
|
1317
|
+
const componentModulePath = componentImportPath(input.outDir, input.componentModulePaths?.[entry.slug]);
|
|
1318
|
+
const themeStyle = entry.kind === "directory" ? input.themeStyles?.[entry.slug] : void 0;
|
|
1319
|
+
const slideModules = [];
|
|
1320
|
+
const slides = [];
|
|
1321
|
+
for (let index = 0; index < slideSources.length; index += 1) {
|
|
1322
|
+
const { attrs: slideAttrs, body: slideBody } = readFrontmatter(slideSources[index]);
|
|
1323
|
+
const speakerNotes = extractMdxCommentSpeakerNotes(slideBody);
|
|
1324
|
+
const parsed = parseDeckWithWarnings(speakerNotes.body);
|
|
1325
|
+
warnings = warnings.concat(toGeneratedParserCompileWarnings(parsed.warnings, index));
|
|
1326
|
+
const firstParsedSlide = parsed.slides[0];
|
|
1327
|
+
const slideModulePath = `${input.outDir}/decks/${entry.slug}/slide-${index}.ts`;
|
|
1328
|
+
const slideMeta = toSlideFrontmatter(slideAttrs, warnings, {
|
|
1329
|
+
slideIndex: index,
|
|
1330
|
+
fallbackTransition: deckMeta.transition,
|
|
1331
|
+
fallbackTransitionDuration: deckMeta.transitionDuration,
|
|
1332
|
+
fallbackTransitionEasing: deckMeta.transitionEasing,
|
|
1333
|
+
fallbackTitle: firstParsedSlide?.title,
|
|
1334
|
+
fallbackLayout: firstParsedSlide?.layout,
|
|
1335
|
+
fallbackClassName: firstParsedSlide?.className
|
|
1336
|
+
});
|
|
1337
|
+
addUnknownFrontmatterWarnings(warnings, slideMeta.meta, "slide", index);
|
|
1338
|
+
const code = await compileMdxModule(rewriteRelativeMdxImports([prelude, rewriteAssetUrls(speakerNotes.body, assets)].filter(Boolean).join("\n\n"), dirname$1(entry.sourcePath), dirname$1(slideModulePath)), entry.sourcePath, index, input.resolveOgp);
|
|
1339
|
+
slideModules.push({
|
|
1340
|
+
path: slideModulePath,
|
|
1341
|
+
importPath: `./decks/${entry.slug}/slide-${index}`,
|
|
1342
|
+
code
|
|
1343
|
+
});
|
|
1344
|
+
slides.push({
|
|
1345
|
+
index,
|
|
1346
|
+
meta: slideMeta,
|
|
1347
|
+
html: "",
|
|
1348
|
+
components: [],
|
|
1349
|
+
notes: combineSpeakerNotes(slideMeta.notes, speakerNotes.notes)
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
addExternalAssetWarnings(warnings, assets);
|
|
1353
|
+
return {
|
|
1354
|
+
componentModulePath,
|
|
1355
|
+
clientComponentIds: input.clientComponentIds?.[entry.slug],
|
|
1356
|
+
deck: {
|
|
1357
|
+
slug: entry.slug,
|
|
1358
|
+
sourcePath: entry.sourcePath,
|
|
1359
|
+
kind: entry.kind,
|
|
1360
|
+
meta: deckMeta,
|
|
1361
|
+
themeStyle: themeStyle?.style,
|
|
1362
|
+
themeSourcePath: themeStyle?.sourcePath,
|
|
1363
|
+
slides,
|
|
1364
|
+
assets,
|
|
1365
|
+
warnings
|
|
1366
|
+
},
|
|
1367
|
+
slideModules
|
|
1368
|
+
};
|
|
1485
1369
|
}
|
|
1486
1370
|
function collectGeneratedExternalAssetRefs(contentBody, deckMeta) {
|
|
1487
|
-
|
|
1488
|
-
...collectMarkdownAssetCandidates(contentBody),
|
|
1489
|
-
...collectFrontmatterAssetCandidates(deckMeta.assets)
|
|
1490
|
-
]);
|
|
1371
|
+
return buildExternalAssetRefs([...collectMarkdownAssetCandidates(contentBody), ...collectFrontmatterAssetCandidates(deckMeta.assets)]);
|
|
1491
1372
|
}
|
|
1492
1373
|
function generatedMdxParserWarnings(warnings) {
|
|
1493
|
-
|
|
1374
|
+
return warnings.filter((warning) => warning.code === "code-fence-unclosed");
|
|
1494
1375
|
}
|
|
1495
1376
|
function toGeneratedParserCompileWarnings(warnings, slideIndex) {
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1377
|
+
return generatedMdxParserWarnings(warnings).map((warning) => ({
|
|
1378
|
+
code: "parse-warning",
|
|
1379
|
+
message: warning.message,
|
|
1380
|
+
slideIndex
|
|
1381
|
+
}));
|
|
1501
1382
|
}
|
|
1502
1383
|
async function compileMdxModule(source, sourcePath, slideIndex, resolveOgp) {
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
} catch (error) {
|
|
1525
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1526
|
-
throw new CompileError(
|
|
1527
|
-
`MDX compile failed in ${sourcePath} slide ${slideIndex + 1}: ${message}`,
|
|
1528
|
-
"mdx-compile-error"
|
|
1529
|
-
);
|
|
1530
|
-
}
|
|
1384
|
+
try {
|
|
1385
|
+
const linkCardMetadata = await resolveLinkCardMetadataByUrl(source, resolveOgp);
|
|
1386
|
+
return `// @ts-nocheck\n${String(await compile({
|
|
1387
|
+
path: `${sourcePath}#slide-${slideIndex + 1}`,
|
|
1388
|
+
value: source
|
|
1389
|
+
}, {
|
|
1390
|
+
jsxRuntime: "automatic",
|
|
1391
|
+
jsxImportSource: "hono/jsx",
|
|
1392
|
+
format: "mdx",
|
|
1393
|
+
elementAttributeNameCase: "html",
|
|
1394
|
+
remarkPlugins: [
|
|
1395
|
+
remarkGfm,
|
|
1396
|
+
remarkDirective,
|
|
1397
|
+
remarkDeckSyntax({ linkCardMetadata }),
|
|
1398
|
+
remarkCodeHighlight
|
|
1399
|
+
]
|
|
1400
|
+
}))}`;
|
|
1401
|
+
} catch (error) {
|
|
1402
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1403
|
+
throw new CompileError(`MDX compile failed in ${sourcePath} slide ${slideIndex + 1}: ${message}`, "mdx-compile-error");
|
|
1404
|
+
}
|
|
1531
1405
|
}
|
|
1532
1406
|
function extractDeckPrelude(source) {
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
import { existsSync } from "fs";
|
|
1558
|
-
import { readFile } from "fs/promises";
|
|
1559
|
-
import { join } from "path";
|
|
1560
|
-
import { fileURLToPath } from "url";
|
|
1561
|
-
import { build as buildBrowserBundle } from "esbuild";
|
|
1407
|
+
const lines = source.replace(/\r\n/g, "\n").split("\n");
|
|
1408
|
+
const prelude = [];
|
|
1409
|
+
let cursor = 0;
|
|
1410
|
+
while (cursor < lines.length) {
|
|
1411
|
+
const line = lines[cursor];
|
|
1412
|
+
if (line.trim() === "") {
|
|
1413
|
+
prelude.push(line);
|
|
1414
|
+
cursor += 1;
|
|
1415
|
+
continue;
|
|
1416
|
+
}
|
|
1417
|
+
if (/^\s*(import|export)\s/.test(line)) {
|
|
1418
|
+
prelude.push(line);
|
|
1419
|
+
cursor += 1;
|
|
1420
|
+
continue;
|
|
1421
|
+
}
|
|
1422
|
+
break;
|
|
1423
|
+
}
|
|
1424
|
+
return {
|
|
1425
|
+
prelude: prelude.join("\n").trim(),
|
|
1426
|
+
body: lines.slice(cursor).join("\n").trim()
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1429
|
+
//#endregion
|
|
1430
|
+
//#region src/node/client-entry.ts
|
|
1562
1431
|
async function discoverClientComponentIds(input) {
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
return result;
|
|
1432
|
+
const result = {};
|
|
1433
|
+
for (const entry of input.clientEntries) {
|
|
1434
|
+
const exports = extractComponentExportNames(await readFile(join(input.cwd, entry.sourcePath), "utf8"));
|
|
1435
|
+
result[entry.slug] = Object.fromEntries(exports.map((name) => [name, clientComponentId(entry.slug, name)]));
|
|
1436
|
+
}
|
|
1437
|
+
return result;
|
|
1570
1438
|
}
|
|
1571
1439
|
async function emitClientEntryModule(input) {
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1440
|
+
const imports = [];
|
|
1441
|
+
const registrations = [];
|
|
1442
|
+
for (const entry of input.clientEntries) {
|
|
1443
|
+
const ids = input.clientComponentIds[entry.slug] ?? {};
|
|
1444
|
+
for (const [exportName, clientId] of Object.entries(ids)) {
|
|
1445
|
+
const localName = clientImportName(entry.slug, exportName);
|
|
1446
|
+
imports.push(`import { ${exportName} as ${localName} } from ${JSON.stringify(join(input.cwd, entry.sourcePath))};`);
|
|
1447
|
+
registrations.push(`${JSON.stringify(clientId)}: ${localName}`);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
if (registrations.length === 0) return "export const decksClientEntry = \"\";\n";
|
|
1451
|
+
const output = (await build({
|
|
1452
|
+
stdin: {
|
|
1453
|
+
contents: `import { hydrateSlideIslands } from "hono-decks/client";
|
|
1584
1454
|
${imports.join("\n")}
|
|
1585
1455
|
|
|
1586
1456
|
hydrateSlideIslands({
|
|
@@ -1588,588 +1458,559 @@ hydrateSlideIslands({
|
|
|
1588
1458
|
${registrations.join(",\n ")}
|
|
1589
1459
|
}
|
|
1590
1460
|
});
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
});
|
|
1611
|
-
const output = result.outputFiles[0];
|
|
1612
|
-
if (!output) throw new Error("Client entry did not produce output.");
|
|
1613
|
-
return `export const decksClientEntry = ${JSON.stringify(output.text)};
|
|
1614
|
-
`;
|
|
1461
|
+
`,
|
|
1462
|
+
resolveDir: input.cwd,
|
|
1463
|
+
sourcefile: "hono-decks-client-entry.tsx",
|
|
1464
|
+
loader: "tsx"
|
|
1465
|
+
},
|
|
1466
|
+
bundle: true,
|
|
1467
|
+
write: false,
|
|
1468
|
+
format: "esm",
|
|
1469
|
+
platform: "browser",
|
|
1470
|
+
target: "es2022",
|
|
1471
|
+
jsx: "automatic",
|
|
1472
|
+
jsxImportSource: "hono/jsx/dom",
|
|
1473
|
+
nodePaths: nodeModuleFallbackPaths(input.cwd),
|
|
1474
|
+
alias: { "hono-decks/client": resolveClientRuntimeEntry() },
|
|
1475
|
+
sourcemap: false,
|
|
1476
|
+
minify: false
|
|
1477
|
+
})).outputFiles[0];
|
|
1478
|
+
if (!output) throw new Error("Client entry did not produce output.");
|
|
1479
|
+
return `export const decksClientEntry = ${JSON.stringify(output.text)};\n`;
|
|
1615
1480
|
}
|
|
1616
1481
|
function resolveClientRuntimeEntry() {
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1482
|
+
const built = fileURLToPath(new URL("./client.js", import.meta.url));
|
|
1483
|
+
if (existsSync(built)) return built;
|
|
1484
|
+
return fileURLToPath(new URL("../client.ts", import.meta.url));
|
|
1620
1485
|
}
|
|
1621
1486
|
function extractComponentExportNames(source) {
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
for (const match of source.matchAll(/\bexport\s+const\s+([A-Z][A-Za-z0-9_]*)\b/g)) {
|
|
1627
|
-
names.add(match[1]);
|
|
1628
|
-
}
|
|
1629
|
-
return [...names].sort();
|
|
1487
|
+
const names = /* @__PURE__ */ new Set();
|
|
1488
|
+
for (const match of source.matchAll(/\bexport\s+function\s+([A-Z][A-Za-z0-9_]*)\b/g)) names.add(match[1]);
|
|
1489
|
+
for (const match of source.matchAll(/\bexport\s+const\s+([A-Z][A-Za-z0-9_]*)\b/g)) names.add(match[1]);
|
|
1490
|
+
return [...names].sort();
|
|
1630
1491
|
}
|
|
1631
1492
|
function nodeModuleFallbackPaths(cwd) {
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1493
|
+
const current = process.cwd();
|
|
1494
|
+
return [
|
|
1495
|
+
join(cwd, "node_modules"),
|
|
1496
|
+
join(cwd, "..", "node_modules"),
|
|
1497
|
+
join(cwd, "..", "..", "node_modules"),
|
|
1498
|
+
join(current, "node_modules"),
|
|
1499
|
+
join(current, "..", "node_modules"),
|
|
1500
|
+
join(current, "..", "..", "node_modules")
|
|
1501
|
+
];
|
|
1641
1502
|
}
|
|
1642
1503
|
function clientComponentId(slug, exportName) {
|
|
1643
|
-
|
|
1644
|
-
return `${base}_${hashString(`${slug}:${exportName}`).slice(0, 8)}`;
|
|
1504
|
+
return `${`${exportName}__${safeIdentifier(slug)}`}_${hashString(`${slug}:${exportName}`).slice(0, 8)}`;
|
|
1645
1505
|
}
|
|
1646
1506
|
function clientImportName(slug, exportName) {
|
|
1647
|
-
|
|
1507
|
+
return `${safeIdentifier(exportName)}__${safeIdentifier(slug)}_${hashString(`${slug}:${exportName}`).slice(0, 8)}`;
|
|
1648
1508
|
}
|
|
1649
1509
|
function hashString(value) {
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
}
|
|
1654
|
-
return (hash >>> 0).toString(36);
|
|
1510
|
+
let hash = 5381;
|
|
1511
|
+
for (let index = 0; index < value.length; index += 1) hash = hash * 33 ^ value.charCodeAt(index);
|
|
1512
|
+
return (hash >>> 0).toString(36);
|
|
1655
1513
|
}
|
|
1656
|
-
function
|
|
1657
|
-
|
|
1514
|
+
function safeIdentifier(value) {
|
|
1515
|
+
return value.replace(/[^A-Za-z0-9_$]+/g, "_").replace(/^[^A-Za-z_$]+/, "_") || "_";
|
|
1658
1516
|
}
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
// src/node/path-utils.ts
|
|
1666
|
-
import { relative } from "path";
|
|
1667
|
-
function dirname2(path) {
|
|
1668
|
-
const normalized = normalizePath4(path);
|
|
1669
|
-
return normalized.includes("/") ? normalized.slice(0, normalized.lastIndexOf("/")) : ".";
|
|
1517
|
+
//#endregion
|
|
1518
|
+
//#region src/node/path-utils.ts
|
|
1519
|
+
function dirname(path) {
|
|
1520
|
+
const normalized = normalizePath(path);
|
|
1521
|
+
return normalized.includes("/") ? normalized.slice(0, normalized.lastIndexOf("/")) : ".";
|
|
1670
1522
|
}
|
|
1671
|
-
function
|
|
1672
|
-
|
|
1523
|
+
function normalizePath(path) {
|
|
1524
|
+
return path.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+/g, "/");
|
|
1673
1525
|
}
|
|
1674
1526
|
function normalizeDeckRoot(root) {
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
}
|
|
1679
|
-
return normalized;
|
|
1527
|
+
const normalized = normalizeRelativePath(root, "Deck root").replace(/\/$/, "");
|
|
1528
|
+
if (normalized === ".") throw new Error("Deck root must be a relative path inside the current working directory");
|
|
1529
|
+
return normalized;
|
|
1680
1530
|
}
|
|
1681
1531
|
function normalizeRelativePath(path, label) {
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
}
|
|
1687
|
-
return normalized;
|
|
1532
|
+
const normalized = normalizePath(path).replace(/\/$/, "");
|
|
1533
|
+
const segments = normalized.split("/");
|
|
1534
|
+
if (normalized === "" || normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized) || segments.includes("..")) throw new Error(`${label} must be a relative path inside the current working directory`);
|
|
1535
|
+
return normalized;
|
|
1688
1536
|
}
|
|
1689
|
-
|
|
1690
|
-
|
|
1537
|
+
//#endregion
|
|
1538
|
+
//#region src/node/local-deck-io.ts
|
|
1691
1539
|
async function listFiles(cwd, dir) {
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
// src/node/ogp.ts
|
|
1705
|
-
import { lookup } from "dns/promises";
|
|
1706
|
-
import { isIP } from "net";
|
|
1707
|
-
var MAX_OGP_BYTES = 256 * 1024;
|
|
1708
|
-
var MAX_OGP_REDIRECTS = 3;
|
|
1540
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
1541
|
+
return (await Promise.all(entries.map(async (entry) => {
|
|
1542
|
+
const fullPath = join(dir, entry.name);
|
|
1543
|
+
if (entry.isDirectory()) return listFiles(cwd, fullPath);
|
|
1544
|
+
if (entry.isFile()) return [normalizePath(relative(cwd, fullPath))];
|
|
1545
|
+
return [];
|
|
1546
|
+
}))).flat().sort();
|
|
1547
|
+
}
|
|
1548
|
+
//#endregion
|
|
1549
|
+
//#region src/node/ogp.ts
|
|
1550
|
+
const MAX_OGP_BYTES = 256 * 1024;
|
|
1551
|
+
const MAX_OGP_REDIRECTS = 3;
|
|
1709
1552
|
async function resolveOgpMetadata(url) {
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1553
|
+
if (!isHttpUrl(url) || typeof fetch === "undefined") return void 0;
|
|
1554
|
+
try {
|
|
1555
|
+
const response = await fetchPublicHttpUrl(url);
|
|
1556
|
+
if (!response) return void 0;
|
|
1557
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
1558
|
+
if (!response.ok || !contentType.toLowerCase().includes("text/html") || isTooLarge(response)) return void 0;
|
|
1559
|
+
const html = await readTextWithLimit(response, MAX_OGP_BYTES);
|
|
1560
|
+
if (!html) return void 0;
|
|
1561
|
+
return parseOgpHtml(html, response.url || url);
|
|
1562
|
+
} catch {
|
|
1563
|
+
return;
|
|
1564
|
+
}
|
|
1722
1565
|
}
|
|
1723
1566
|
async function fetchPublicHttpUrl(url) {
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
return void 0;
|
|
1567
|
+
let current = url;
|
|
1568
|
+
for (let redirect = 0; redirect <= MAX_OGP_REDIRECTS; redirect += 1) {
|
|
1569
|
+
if (!await isPublicHttpUrl(current)) return void 0;
|
|
1570
|
+
const response = await fetch(current, {
|
|
1571
|
+
headers: { accept: "text/html,application/xhtml+xml" },
|
|
1572
|
+
redirect: "manual",
|
|
1573
|
+
signal: AbortSignal.timeout(1500)
|
|
1574
|
+
});
|
|
1575
|
+
if (!isRedirect(response.status)) return response;
|
|
1576
|
+
await response.body?.cancel();
|
|
1577
|
+
const location = response.headers.get("location");
|
|
1578
|
+
if (!location) return void 0;
|
|
1579
|
+
current = new URL(location, current).toString();
|
|
1580
|
+
}
|
|
1739
1581
|
}
|
|
1740
1582
|
async function isPublicHttpUrl(value) {
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1583
|
+
let url;
|
|
1584
|
+
try {
|
|
1585
|
+
url = new URL(value);
|
|
1586
|
+
} catch {
|
|
1587
|
+
return false;
|
|
1588
|
+
}
|
|
1589
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
1590
|
+
if (isLocalHostname(url.hostname)) return false;
|
|
1591
|
+
if (isPrivateIpAddress(url.hostname)) return false;
|
|
1592
|
+
const addresses = await lookup(url.hostname, {
|
|
1593
|
+
all: true,
|
|
1594
|
+
verbatim: true
|
|
1595
|
+
});
|
|
1596
|
+
return addresses.length > 0 && addresses.every(({ address }) => !isPrivateIpAddress(address));
|
|
1752
1597
|
}
|
|
1753
1598
|
function isLocalHostname(hostname) {
|
|
1754
|
-
|
|
1755
|
-
|
|
1599
|
+
const normalized = hostname.toLowerCase().replace(/\.$/, "");
|
|
1600
|
+
return normalized === "localhost" || normalized.endsWith(".localhost");
|
|
1756
1601
|
}
|
|
1757
1602
|
function isPrivateIpAddress(value) {
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1603
|
+
const normalized = value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
|
|
1604
|
+
const version = isIP(normalized);
|
|
1605
|
+
if (version === 4) return isPrivateIpv4(normalized);
|
|
1606
|
+
if (version === 6) return isPrivateIpv6(normalized);
|
|
1607
|
+
return false;
|
|
1763
1608
|
}
|
|
1764
1609
|
function isPrivateIpv4(address) {
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1610
|
+
const parts = address.split(".").map((part) => Number.parseInt(part, 10));
|
|
1611
|
+
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return true;
|
|
1612
|
+
const [a, b] = parts;
|
|
1613
|
+
return a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 198 && (b === 18 || b === 19) || a >= 224;
|
|
1769
1614
|
}
|
|
1770
1615
|
function isPrivateIpv6(address) {
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1616
|
+
const normalized = address.toLowerCase();
|
|
1617
|
+
if (normalized === "::" || normalized === "::1" || normalized.startsWith("fe80:") || normalized.startsWith("fec0:") || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("ff")) return true;
|
|
1618
|
+
if (normalized.startsWith("::ffff:")) {
|
|
1619
|
+
const tail = normalized.slice(7);
|
|
1620
|
+
const dotted = tail.match(/^(?:0:)?(\d+\.\d+\.\d+\.\d+)$/);
|
|
1621
|
+
if (dotted) return isPrivateIpv4(dotted[1]);
|
|
1622
|
+
const hex = tail.match(/^(?:0:)?([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
|
|
1623
|
+
if (hex) {
|
|
1624
|
+
const hi = Number.parseInt(hex[1], 16);
|
|
1625
|
+
const lo = Number.parseInt(hex[2], 16);
|
|
1626
|
+
const value32 = hi << 16 | lo;
|
|
1627
|
+
return isPrivateIpv4([
|
|
1628
|
+
value32 >>> 24 & 255,
|
|
1629
|
+
value32 >>> 16 & 255,
|
|
1630
|
+
value32 >>> 8 & 255,
|
|
1631
|
+
value32 & 255
|
|
1632
|
+
].join("."));
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
return false;
|
|
1789
1636
|
}
|
|
1790
1637
|
function isRedirect(status) {
|
|
1791
|
-
|
|
1638
|
+
return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
|
|
1792
1639
|
}
|
|
1793
1640
|
function isTooLarge(response) {
|
|
1794
|
-
|
|
1795
|
-
|
|
1641
|
+
const contentLength = response.headers.get("content-length");
|
|
1642
|
+
return contentLength != null && Number(contentLength) > MAX_OGP_BYTES;
|
|
1796
1643
|
}
|
|
1797
1644
|
async function readTextWithLimit(response, maxBytes) {
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1645
|
+
const reader = response.body?.getReader();
|
|
1646
|
+
if (!reader) {
|
|
1647
|
+
const text = await response.text();
|
|
1648
|
+
return new TextEncoder().encode(text).byteLength > maxBytes ? void 0 : text;
|
|
1649
|
+
}
|
|
1650
|
+
const chunks = [];
|
|
1651
|
+
let bytes = 0;
|
|
1652
|
+
while (true) {
|
|
1653
|
+
const { done, value } = await reader.read();
|
|
1654
|
+
if (done) break;
|
|
1655
|
+
bytes += value.byteLength;
|
|
1656
|
+
if (bytes > maxBytes) {
|
|
1657
|
+
await reader.cancel();
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
chunks.push(value);
|
|
1661
|
+
}
|
|
1662
|
+
const buffer = new Uint8Array(bytes);
|
|
1663
|
+
let offset = 0;
|
|
1664
|
+
for (const chunk of chunks) {
|
|
1665
|
+
buffer.set(chunk, offset);
|
|
1666
|
+
offset += chunk.byteLength;
|
|
1667
|
+
}
|
|
1668
|
+
return new TextDecoder().decode(buffer);
|
|
1822
1669
|
}
|
|
1823
1670
|
function parseOgpHtml(html, pageUrl) {
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1671
|
+
const meta = collectHtmlMeta(html);
|
|
1672
|
+
const result = {
|
|
1673
|
+
title: firstValue(meta, ["og:title", "twitter:title"]) ?? htmlTitle(html),
|
|
1674
|
+
description: firstValue(meta, [
|
|
1675
|
+
"og:description",
|
|
1676
|
+
"twitter:description",
|
|
1677
|
+
"description"
|
|
1678
|
+
]),
|
|
1679
|
+
image: absoluteUrl(firstValue(meta, [
|
|
1680
|
+
"og:image",
|
|
1681
|
+
"og:image:url",
|
|
1682
|
+
"twitter:image"
|
|
1683
|
+
]), pageUrl),
|
|
1684
|
+
siteName: firstValue(meta, ["og:site_name", "application-name"])
|
|
1685
|
+
};
|
|
1686
|
+
return Object.values(result).some(Boolean) ? result : void 0;
|
|
1831
1687
|
}
|
|
1832
1688
|
function collectHtmlMeta(html) {
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
}
|
|
1842
|
-
}
|
|
1843
|
-
return meta;
|
|
1689
|
+
const meta = /* @__PURE__ */ new Map();
|
|
1690
|
+
for (const match of html.matchAll(/<meta\b[^>]*>/gi)) {
|
|
1691
|
+
const attributes = parseHtmlAttributes(match[0]);
|
|
1692
|
+
const key = attributes.property ?? attributes.name;
|
|
1693
|
+
const content = attributes.content;
|
|
1694
|
+
if (key && content && !meta.has(key.toLowerCase())) meta.set(key.toLowerCase(), decodeHtmlEntities(content.trim()));
|
|
1695
|
+
}
|
|
1696
|
+
return meta;
|
|
1844
1697
|
}
|
|
1845
1698
|
function parseHtmlAttributes(tag) {
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
attributes[match[1].toLowerCase()] = match[2] ?? match[3] ?? match[4] ?? "";
|
|
1850
|
-
}
|
|
1851
|
-
return attributes;
|
|
1699
|
+
const attributes = {};
|
|
1700
|
+
for (const match of tag.matchAll(/([^\s"'<>/=]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/g)) attributes[match[1].toLowerCase()] = match[2] ?? match[3] ?? match[4] ?? "";
|
|
1701
|
+
return attributes;
|
|
1852
1702
|
}
|
|
1853
1703
|
function firstValue(meta, keys) {
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
return void 0;
|
|
1704
|
+
for (const key of keys) {
|
|
1705
|
+
const value = meta.get(key);
|
|
1706
|
+
if (value) return value;
|
|
1707
|
+
}
|
|
1859
1708
|
}
|
|
1860
1709
|
function htmlTitle(html) {
|
|
1861
|
-
|
|
1862
|
-
|
|
1710
|
+
const match = html.match(/<title\b[^>]*>([\s\S]*?)<\/title>/i);
|
|
1711
|
+
return match ? decodeHtmlEntities(match[1].replace(/\s+/g, " ").trim()) : void 0;
|
|
1863
1712
|
}
|
|
1864
1713
|
function absoluteUrl(value, base) {
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1714
|
+
if (!value) return void 0;
|
|
1715
|
+
try {
|
|
1716
|
+
return new URL(value, base).toString();
|
|
1717
|
+
} catch {
|
|
1718
|
+
return value;
|
|
1719
|
+
}
|
|
1871
1720
|
}
|
|
1872
1721
|
function decodeHtmlEntities(value) {
|
|
1873
|
-
|
|
1874
|
-
}
|
|
1875
|
-
function isHttpUrl2(value) {
|
|
1876
|
-
try {
|
|
1877
|
-
const url = new URL(value);
|
|
1878
|
-
return url.protocol === "http:" || url.protocol === "https:";
|
|
1879
|
-
} catch {
|
|
1880
|
-
return false;
|
|
1881
|
-
}
|
|
1722
|
+
return value.replaceAll("&", "&").replaceAll(""", "\"").replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
1882
1723
|
}
|
|
1883
|
-
|
|
1884
|
-
|
|
1724
|
+
function isHttpUrl(value) {
|
|
1725
|
+
try {
|
|
1726
|
+
const url = new URL(value);
|
|
1727
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
1728
|
+
} catch {
|
|
1729
|
+
return false;
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
//#endregion
|
|
1733
|
+
//#region src/node/compile-decks.ts
|
|
1885
1734
|
async function compileDecks(input) {
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1735
|
+
const out = normalizeRelativePath(input.out, "Output directory");
|
|
1736
|
+
const root = normalizeDeckRoot(input.root);
|
|
1737
|
+
const paths = await listFiles(input.cwd, join(input.cwd, root));
|
|
1738
|
+
const resolved = resolveDeckFiles(paths, root);
|
|
1739
|
+
const componentModulePaths = Object.fromEntries(resolved.flatMap((deck) => {
|
|
1740
|
+
const path = deck.kind === "directory" ? findComponentModule(paths, root, deck.slug) : void 0;
|
|
1741
|
+
return path ? [[deck.slug, path]] : [];
|
|
1742
|
+
}));
|
|
1743
|
+
const clientEntryPaths = resolved.flatMap((deck) => {
|
|
1744
|
+
const path = deck.kind === "directory" ? findClientEntryModule(paths, root, deck.slug) : void 0;
|
|
1745
|
+
return path ? [{
|
|
1746
|
+
slug: deck.slug,
|
|
1747
|
+
sourcePath: path
|
|
1748
|
+
}] : [];
|
|
1749
|
+
});
|
|
1750
|
+
const themeStyles = await discoverDeckThemeStyles({
|
|
1751
|
+
cwd: input.cwd,
|
|
1752
|
+
root,
|
|
1753
|
+
paths,
|
|
1754
|
+
decks: resolved
|
|
1755
|
+
});
|
|
1756
|
+
const clientComponentIds = await discoverClientComponentIds({
|
|
1757
|
+
cwd: input.cwd,
|
|
1758
|
+
clientEntries: clientEntryPaths
|
|
1759
|
+
});
|
|
1760
|
+
const ogpCache = await createOgpCacheResolver({
|
|
1761
|
+
cwd: input.cwd,
|
|
1762
|
+
cacheFile: input.ogpCacheFile,
|
|
1763
|
+
refresh: input.refreshOgp,
|
|
1764
|
+
resolveOgp: input.resolveOgp ?? resolveOgpMetadata
|
|
1765
|
+
});
|
|
1766
|
+
const generated = await compileMdxModuleDecks({
|
|
1767
|
+
root,
|
|
1768
|
+
outDir: out,
|
|
1769
|
+
mountPath: input.mountPath,
|
|
1770
|
+
decks: resolved,
|
|
1771
|
+
componentModulePaths,
|
|
1772
|
+
clientComponentIds,
|
|
1773
|
+
themeStyles,
|
|
1774
|
+
resolveOgp: ogpCache.resolveOgp,
|
|
1775
|
+
readText: (path) => readFile(join(input.cwd, path), "utf8"),
|
|
1776
|
+
readBinary: (path) => readFile(join(input.cwd, path))
|
|
1777
|
+
});
|
|
1778
|
+
for (const deck of generated.decks) for (const slide of deck.slideModules) await writeTextFile(join(input.cwd, slide.path), slide.code);
|
|
1779
|
+
await writeTextFile(join(input.cwd, out, "decks.ts"), generated.routerModule);
|
|
1780
|
+
await writeTextFile(join(input.cwd, out, "client-entry.ts"), await emitClientEntryModule({
|
|
1781
|
+
cwd: input.cwd,
|
|
1782
|
+
clientEntries: clientEntryPaths,
|
|
1783
|
+
clientComponentIds
|
|
1784
|
+
}));
|
|
1785
|
+
await ogpCache.write();
|
|
1786
|
+
return { decks: generated.decks.map((deck) => deck.deck) };
|
|
1935
1787
|
}
|
|
1936
1788
|
async function createOgpCacheResolver(input) {
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
`);
|
|
1961
|
-
}
|
|
1962
|
-
};
|
|
1789
|
+
if (!input.cacheFile) return {
|
|
1790
|
+
resolveOgp: input.resolveOgp,
|
|
1791
|
+
write: async () => void 0
|
|
1792
|
+
};
|
|
1793
|
+
const cachePath = join(input.cwd, normalizeRelativePath(input.cacheFile, "OGP cache file"));
|
|
1794
|
+
const cache = await readOgpCache(cachePath);
|
|
1795
|
+
let dirty = false;
|
|
1796
|
+
return {
|
|
1797
|
+
async resolveOgp(url) {
|
|
1798
|
+
if (!input.refresh && cache[url]) return cache[url];
|
|
1799
|
+
if (!input.refresh) return void 0;
|
|
1800
|
+
const metadata = await input.resolveOgp(url);
|
|
1801
|
+
if (metadata) {
|
|
1802
|
+
cache[url] = metadata;
|
|
1803
|
+
dirty = true;
|
|
1804
|
+
}
|
|
1805
|
+
return metadata;
|
|
1806
|
+
},
|
|
1807
|
+
async write() {
|
|
1808
|
+
if (!dirty) return;
|
|
1809
|
+
await writeTextFile(cachePath, `${JSON.stringify(cache, null, 2)}\n`);
|
|
1810
|
+
}
|
|
1811
|
+
};
|
|
1963
1812
|
}
|
|
1964
1813
|
async function readOgpCache(path) {
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
}
|
|
1987
|
-
return cache;
|
|
1814
|
+
let raw;
|
|
1815
|
+
try {
|
|
1816
|
+
raw = await readFile(path, "utf8");
|
|
1817
|
+
} catch (error) {
|
|
1818
|
+
if (isNodeError(error) && error.code === "ENOENT") return {};
|
|
1819
|
+
throw error;
|
|
1820
|
+
}
|
|
1821
|
+
const parsed = JSON.parse(raw);
|
|
1822
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("OGP cache file must contain a JSON object");
|
|
1823
|
+
const cache = {};
|
|
1824
|
+
for (const [url, value] of Object.entries(parsed)) {
|
|
1825
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
1826
|
+
const metadata = value;
|
|
1827
|
+
cache[url] = {
|
|
1828
|
+
...typeof metadata.title === "string" ? { title: metadata.title } : {},
|
|
1829
|
+
...typeof metadata.description === "string" ? { description: metadata.description } : {},
|
|
1830
|
+
...typeof metadata.image === "string" ? { image: metadata.image } : {},
|
|
1831
|
+
...typeof metadata.siteName === "string" ? { siteName: metadata.siteName } : {}
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1834
|
+
return cache;
|
|
1988
1835
|
}
|
|
1989
1836
|
function isNodeError(error) {
|
|
1990
|
-
|
|
1837
|
+
return error instanceof Error && "code" in error;
|
|
1991
1838
|
}
|
|
1992
1839
|
async function writeTextFile(path, contents) {
|
|
1993
|
-
|
|
1994
|
-
|
|
1840
|
+
await mkdir(dirname(path), { recursive: true });
|
|
1841
|
+
await writeFile(path, contents, "utf8");
|
|
1995
1842
|
}
|
|
1996
1843
|
function findComponentModule(paths, root, slug) {
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
1844
|
+
const base = `${normalizePath(root).replace(/\/$/, "")}/${slug}/components/index`;
|
|
1845
|
+
return paths.find((path) => {
|
|
1846
|
+
const normalized = normalizePath(path);
|
|
1847
|
+
return normalized === `${base}.tsx` || normalized === `${base}.ts` || normalized === `${base}.jsx` || normalized === `${base}.js`;
|
|
1848
|
+
});
|
|
2002
1849
|
}
|
|
2003
1850
|
function findClientEntryModule(paths, root, slug) {
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
1851
|
+
const base = `${normalizePath(root).replace(/\/$/, "")}/${slug}/components/client/index`;
|
|
1852
|
+
return paths.find((path) => {
|
|
1853
|
+
const normalized = normalizePath(path);
|
|
1854
|
+
return normalized === `${base}.tsx` || normalized === `${base}.ts` || normalized === `${base}.jsx` || normalized === `${base}.js`;
|
|
1855
|
+
});
|
|
2009
1856
|
}
|
|
2010
1857
|
async function discoverDeckThemeStyles(input) {
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
1858
|
+
const result = {};
|
|
1859
|
+
for (const deck of input.decks) {
|
|
1860
|
+
if (deck.kind !== "directory") continue;
|
|
1861
|
+
const sourcePath = findDeckThemeStyle(input.paths, input.root, deck.slug);
|
|
1862
|
+
if (!sourcePath) continue;
|
|
1863
|
+
result[deck.slug] = {
|
|
1864
|
+
sourcePath,
|
|
1865
|
+
style: await readFile(join(input.cwd, sourcePath), "utf8")
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1868
|
+
return result;
|
|
2022
1869
|
}
|
|
2023
1870
|
function findDeckThemeStyle(paths, root, slug) {
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
}
|
|
2032
|
-
return hasTheme ? themePath : hasStylesEntry ? stylesEntryPath : void 0;
|
|
1871
|
+
const base = `${normalizePath(root).replace(/\/$/, "")}/${slug}`;
|
|
1872
|
+
const themePath = `${base}/theme.css`;
|
|
1873
|
+
const stylesEntryPath = `${base}/styles/index.css`;
|
|
1874
|
+
const hasTheme = paths.includes(themePath);
|
|
1875
|
+
const hasStylesEntry = paths.includes(stylesEntryPath);
|
|
1876
|
+
if (hasTheme && hasStylesEntry) throw new Error(`Deck ${slug} has both ${themePath} and ${stylesEntryPath}. Use only one theme CSS entry.`);
|
|
1877
|
+
return hasTheme ? themePath : hasStylesEntry ? stylesEntryPath : void 0;
|
|
2033
1878
|
}
|
|
2034
|
-
|
|
2035
|
-
// src/node/config.ts
|
|
2036
|
-
import { existsSync as existsSync3 } from "fs";
|
|
2037
|
-
import { resolve } from "path";
|
|
2038
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2039
|
-
import { build } from "esbuild";
|
|
2040
|
-
var DEFAULT_DECKS_CONFIG_FILE = "hono-decks.config.ts";
|
|
2041
1879
|
async function loadDecksConfig(input) {
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
path,
|
|
2067
|
-
config,
|
|
2068
|
-
root: config.build?.root ?? "decks",
|
|
2069
|
-
outDir: config.build?.outDir ?? "src/generated",
|
|
2070
|
-
ogpCacheFile: config.build?.ogpCacheFile
|
|
2071
|
-
};
|
|
1880
|
+
const path = resolve(input.cwd, input.configFile ?? "hono-decks.config.ts");
|
|
1881
|
+
if (!existsSync(path)) throw new Error(`Config file not found: ${input.configFile ?? "hono-decks.config.ts"}. Run \`hono-decks init\` first.`);
|
|
1882
|
+
const source = (await build({
|
|
1883
|
+
absWorkingDir: input.cwd,
|
|
1884
|
+
entryPoints: [path],
|
|
1885
|
+
bundle: true,
|
|
1886
|
+
format: "esm",
|
|
1887
|
+
platform: "node",
|
|
1888
|
+
target: "node20",
|
|
1889
|
+
write: false,
|
|
1890
|
+
logLevel: "silent",
|
|
1891
|
+
alias: { "hono-decks": resolveRuntimeEntry() }
|
|
1892
|
+
})).outputFiles[0]?.text;
|
|
1893
|
+
if (!source) throw new Error(`Could not load config file: ${path}`);
|
|
1894
|
+
const config = (await import(`data:text/javascript;charset=utf-8,${encodeURIComponent(source)}#${Date.now()}`)).default;
|
|
1895
|
+
if (!config || typeof config !== "object") throw new Error(`Config must have a default export: ${path}`);
|
|
1896
|
+
if (typeof config.mountPath !== "string") throw new Error(`Config mountPath must be a string: ${path}`);
|
|
1897
|
+
return {
|
|
1898
|
+
path,
|
|
1899
|
+
config,
|
|
1900
|
+
root: config.build?.root ?? "decks",
|
|
1901
|
+
outDir: config.build?.outDir ?? "src/generated",
|
|
1902
|
+
ogpCacheFile: config.build?.ogpCacheFile
|
|
1903
|
+
};
|
|
2072
1904
|
}
|
|
2073
1905
|
function resolveRuntimeEntry() {
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
}
|
|
2078
|
-
|
|
2079
|
-
|
|
1906
|
+
const built = fileURLToPath(new URL("./mod.js", import.meta.url));
|
|
1907
|
+
if (existsSync(built)) return built;
|
|
1908
|
+
return fileURLToPath(new URL("../mod.ts", import.meta.url));
|
|
1909
|
+
}
|
|
1910
|
+
//#endregion
|
|
1911
|
+
//#region src/vite.ts
|
|
1912
|
+
/**
|
|
1913
|
+
* Compiles decks before Vite starts and regenerates them while authoring.
|
|
1914
|
+
*
|
|
1915
|
+
* Add this plugin to the same Vite config that hosts Hono or HonoX. The
|
|
1916
|
+
* project's ordinary `vite` development command is then sufficient; no
|
|
1917
|
+
* separate `hono-decks compile --watch` process is required.
|
|
1918
|
+
*/
|
|
2080
1919
|
function honoDecks(options = {}) {
|
|
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
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
1920
|
+
let projectRoot = process.cwd();
|
|
1921
|
+
let configPath = resolve(projectRoot, options.configFile ?? "hono-decks.config.ts");
|
|
1922
|
+
let deckRoot = resolve(projectRoot, "decks");
|
|
1923
|
+
let resolvedConfig;
|
|
1924
|
+
let server;
|
|
1925
|
+
let watchedPaths = /* @__PURE__ */ new Set();
|
|
1926
|
+
let timer;
|
|
1927
|
+
let compiling = false;
|
|
1928
|
+
let queued = false;
|
|
1929
|
+
const syncWatchedPaths = async () => {
|
|
1930
|
+
if (!server) return;
|
|
1931
|
+
const next = /* @__PURE__ */ new Set([deckRoot, configPath]);
|
|
1932
|
+
const removed = [...watchedPaths].filter((path) => !next.has(path));
|
|
1933
|
+
if (removed.length > 0) await server.watcher.unwatch(removed);
|
|
1934
|
+
server.watcher.add([...next]);
|
|
1935
|
+
watchedPaths = next;
|
|
1936
|
+
};
|
|
1937
|
+
const compileOnce = async () => {
|
|
1938
|
+
const loaded = await loadDecksConfig({
|
|
1939
|
+
cwd: projectRoot,
|
|
1940
|
+
configFile: options.configFile
|
|
1941
|
+
});
|
|
1942
|
+
const manifest = await compileDecks({
|
|
1943
|
+
cwd: projectRoot,
|
|
1944
|
+
root: loaded.root,
|
|
1945
|
+
out: loaded.outDir,
|
|
1946
|
+
mountPath: loaded.config.mountPath,
|
|
1947
|
+
ogpCacheFile: loaded.ogpCacheFile,
|
|
1948
|
+
refreshOgp: options.refreshOgp
|
|
1949
|
+
});
|
|
1950
|
+
configPath = loaded.path;
|
|
1951
|
+
deckRoot = resolve(projectRoot, loaded.root);
|
|
1952
|
+
await syncWatchedPaths();
|
|
1953
|
+
resolvedConfig?.logger.info(`[hono-decks] Compiled ${manifest.decks.length} decks to ${loaded.outDir}`);
|
|
1954
|
+
};
|
|
1955
|
+
const compile = async (fatal) => {
|
|
1956
|
+
if (compiling) {
|
|
1957
|
+
queued = true;
|
|
1958
|
+
return;
|
|
1959
|
+
}
|
|
1960
|
+
compiling = true;
|
|
1961
|
+
let latestCompileSucceeded = false;
|
|
1962
|
+
try {
|
|
1963
|
+
do {
|
|
1964
|
+
queued = false;
|
|
1965
|
+
try {
|
|
1966
|
+
await compileOnce();
|
|
1967
|
+
latestCompileSucceeded = true;
|
|
1968
|
+
} catch (error) {
|
|
1969
|
+
latestCompileSucceeded = false;
|
|
1970
|
+
if (fatal) throw error;
|
|
1971
|
+
resolvedConfig?.logger.error(`[hono-decks] ${error instanceof Error ? error.message : String(error)}`);
|
|
1972
|
+
}
|
|
1973
|
+
} while (queued);
|
|
1974
|
+
if (latestCompileSucceeded && server) server.ws.send({ type: "full-reload" });
|
|
1975
|
+
} finally {
|
|
1976
|
+
compiling = false;
|
|
1977
|
+
}
|
|
1978
|
+
};
|
|
1979
|
+
const scheduleCompile = () => {
|
|
1980
|
+
if (timer) clearTimeout(timer);
|
|
1981
|
+
timer = setTimeout(() => void compile(false), 75);
|
|
1982
|
+
};
|
|
1983
|
+
const onWatcherEvent = (event, path) => {
|
|
1984
|
+
if (event !== "add" && event !== "change" && event !== "unlink") return;
|
|
1985
|
+
const absolutePath = resolve(projectRoot, path);
|
|
1986
|
+
if (absolutePath === configPath || isWithin(deckRoot, absolutePath)) scheduleCompile();
|
|
1987
|
+
};
|
|
1988
|
+
return {
|
|
1989
|
+
name: "hono-decks",
|
|
1990
|
+
enforce: "pre",
|
|
1991
|
+
async configResolved(config) {
|
|
1992
|
+
resolvedConfig = config;
|
|
1993
|
+
projectRoot = config.root;
|
|
1994
|
+
configPath = resolve(projectRoot, options.configFile ?? "hono-decks.config.ts");
|
|
1995
|
+
await compile(true);
|
|
1996
|
+
},
|
|
1997
|
+
configureServer(devServer) {
|
|
1998
|
+
server = devServer;
|
|
1999
|
+
syncWatchedPaths();
|
|
2000
|
+
devServer.watcher.on("all", onWatcherEvent);
|
|
2001
|
+
const cleanup = () => {
|
|
2002
|
+
if (timer) clearTimeout(timer);
|
|
2003
|
+
devServer.watcher.off("all", onWatcherEvent);
|
|
2004
|
+
server = void 0;
|
|
2005
|
+
watchedPaths.clear();
|
|
2006
|
+
};
|
|
2007
|
+
devServer.httpServer?.once("close", cleanup);
|
|
2008
|
+
}
|
|
2009
|
+
};
|
|
2168
2010
|
}
|
|
2169
2011
|
function isWithin(root, path) {
|
|
2170
|
-
|
|
2171
|
-
|
|
2012
|
+
const child = relative(root, path);
|
|
2013
|
+
return child === "" || child !== ".." && !child.startsWith("../") && !child.startsWith("..\\");
|
|
2172
2014
|
}
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
};
|
|
2015
|
+
//#endregion
|
|
2016
|
+
export { honoDecks };
|