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