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