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