@ox-content/vite-plugin 2.80.0 → 2.82.0
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/incremental-dom.cjs +3 -2
- package/dist/incremental-dom.cjs.map +1 -1
- package/dist/incremental-dom.d.cts.map +1 -1
- package/dist/incremental-dom.d.mts.map +1 -1
- package/dist/incremental-dom.mjs +2 -1
- package/dist/incremental-dom.mjs.map +1 -1
- package/dist/index.cjs +1486 -99
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -13
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +8 -13
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1429 -69
- package/dist/index.mjs.map +1 -1
- package/dist/interop.cjs.map +1 -1
- package/dist/interop.mjs.map +1 -1
- package/dist/tabs.cjs +3 -4
- package/dist/tabs.cjs.map +1 -1
- package/dist/tabs.mjs +99 -2
- package/dist/tabs.mjs.map +1 -0
- package/dist/vitepress-cli.cjs +5 -4
- package/dist/vitepress-cli.cjs.map +1 -1
- package/dist/vitepress-cli.d.cts +1 -1
- package/dist/vitepress-cli.d.mts +1 -1
- package/dist/vitepress-cli.mjs +4 -2
- package/dist/vitepress-cli.mjs.map +1 -1
- package/dist/vitepress.cjs +106 -2
- package/dist/vitepress.cjs.map +1 -1
- package/dist/vitepress.mjs +32 -2
- package/dist/vitepress.mjs.map +1 -1
- package/package.json +19 -19
- package/dist/chunk.cjs +0 -57
- package/dist/github.cjs +0 -690
- package/dist/github.cjs.map +0 -1
- package/dist/github.mjs +0 -625
- package/dist/github.mjs.map +0 -1
- package/dist/github2.mjs +0 -2
- package/dist/media.cjs +0 -310
- package/dist/media.cjs.map +0 -1
- package/dist/media.mjs +0 -302
- package/dist/media.mjs.map +0 -1
- package/dist/media2.mjs +0 -2
- package/dist/mermaid.cjs +0 -114
- package/dist/mermaid.cjs.map +0 -1
- package/dist/mermaid.mjs +0 -2
- package/dist/mermaid2.mjs +0 -92
- package/dist/mermaid2.mjs.map +0 -1
- package/dist/napi.cjs +0 -45
- package/dist/napi.cjs.map +0 -1
- package/dist/napi.mjs +0 -34
- package/dist/napi.mjs.map +0 -1
- package/dist/ogp.cjs +0 -341
- package/dist/ogp.cjs.map +0 -1
- package/dist/ogp.mjs +0 -2
- package/dist/ogp2.mjs +0 -302
- package/dist/ogp2.mjs.map +0 -1
- package/dist/pm.cjs +0 -45
- package/dist/pm.cjs.map +0 -1
- package/dist/pm.mjs +0 -2
- package/dist/pm2.mjs +0 -38
- package/dist/pm2.mjs.map +0 -1
- package/dist/tabs2.mjs +0 -56
- package/dist/tabs2.mjs.map +0 -1
- package/dist/youtube.cjs +0 -57
- package/dist/youtube.cjs.map +0 -1
- package/dist/youtube.mjs +0 -2
- package/dist/youtube2.mjs +0 -35
- package/dist/youtube2.mjs.map +0 -1
package/dist/media.mjs
DELETED
|
@@ -1,302 +0,0 @@
|
|
|
1
|
-
import { t as importNapiModule } from "./napi.mjs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
-
//#region src/plugins/twitter/url.ts
|
|
5
|
-
const STATUS_PATH = /^\/(?:[^/]+|i\/web)\/status\/(\d+)(?:\/.*)?$/;
|
|
6
|
-
function createSyndicationToken(id) {
|
|
7
|
-
return (Number(id) / 0x38d7ea4c68000 * Math.PI).toString(36).replaceAll(/(0+|\.)/g, "");
|
|
8
|
-
}
|
|
9
|
-
function parseTweetReference(value) {
|
|
10
|
-
const trimmed = value.trim();
|
|
11
|
-
if (/^\d+$/.test(trimmed)) return {
|
|
12
|
-
id: trimmed,
|
|
13
|
-
url: `https://x.com/i/web/status/${trimmed}`
|
|
14
|
-
};
|
|
15
|
-
try {
|
|
16
|
-
const url = new URL(trimmed);
|
|
17
|
-
const hostname = url.hostname.toLowerCase().replace(/^(?:www\.|mobile\.)/, "");
|
|
18
|
-
if (url.protocol !== "https:" || hostname !== "x.com" && hostname !== "twitter.com") return null;
|
|
19
|
-
const match = url.pathname.match(STATUS_PATH);
|
|
20
|
-
if (!match) return null;
|
|
21
|
-
const screenName = url.pathname.startsWith("/i/web/status/") ? "i/web" : url.pathname.split("/")[1];
|
|
22
|
-
return {
|
|
23
|
-
id: match[1],
|
|
24
|
-
url: `https://x.com/${screenName}/status/${match[1]}`
|
|
25
|
-
};
|
|
26
|
-
} catch {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
function referenceFromAttributes(attributes) {
|
|
31
|
-
const values = /* @__PURE__ */ new Map();
|
|
32
|
-
for (const match of attributes.matchAll(/\b(url|href|id)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi)) values.set(match[1].toLowerCase(), match[2] ?? match[3] ?? match[4] ?? "");
|
|
33
|
-
return parseTweetReference(values.get("url") ?? values.get("href") ?? values.get("id") ?? "");
|
|
34
|
-
}
|
|
35
|
-
//#endregion
|
|
36
|
-
//#region src/plugins/twitter/fetch.ts
|
|
37
|
-
const tweetCache = /* @__PURE__ */ new Map();
|
|
38
|
-
async function fetchTweetData(id, options) {
|
|
39
|
-
const key = `${id}-${sanitizeSegment(options.lang)}`;
|
|
40
|
-
if (options.cache) {
|
|
41
|
-
const memory = tweetCache.get(key);
|
|
42
|
-
if (memory) return memory;
|
|
43
|
-
const disk = await readCachedTweet(key, options.cacheDir);
|
|
44
|
-
if (disk) {
|
|
45
|
-
tweetCache.set(key, disk);
|
|
46
|
-
return disk;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
const controller = new AbortController();
|
|
50
|
-
const timeout = setTimeout(() => controller.abort(), options.timeout);
|
|
51
|
-
const endpoint = new URL("https://cdn.syndication.twimg.com/tweet-result");
|
|
52
|
-
endpoint.searchParams.set("id", id);
|
|
53
|
-
endpoint.searchParams.set("lang", options.lang);
|
|
54
|
-
endpoint.searchParams.set("token", createSyndicationToken(id));
|
|
55
|
-
try {
|
|
56
|
-
const response = await fetch(endpoint, {
|
|
57
|
-
headers: { Accept: "application/json" },
|
|
58
|
-
signal: controller.signal
|
|
59
|
-
});
|
|
60
|
-
if (!response.ok) return null;
|
|
61
|
-
const data = await response.json();
|
|
62
|
-
if (!isTweetData(data)) return null;
|
|
63
|
-
if (options.cache) {
|
|
64
|
-
tweetCache.set(key, data);
|
|
65
|
-
await writeCachedTweet(key, data, options.cacheDir);
|
|
66
|
-
}
|
|
67
|
-
return data;
|
|
68
|
-
} catch {
|
|
69
|
-
return null;
|
|
70
|
-
} finally {
|
|
71
|
-
clearTimeout(timeout);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
async function materializeTweetAssets(id, data, options) {
|
|
75
|
-
const assets = { media: [] };
|
|
76
|
-
const avatarUrl = data.user.profile_image_url_https?.replace(/_normal(?=\.[^.]+$)/, "_bigger");
|
|
77
|
-
if (avatarUrl) assets.avatar = await downloadAsset(avatarUrl, `${id}-avatar`, options);
|
|
78
|
-
const media = data.mediaDetails ?? data.entities?.media ?? [];
|
|
79
|
-
for (const [index, item] of media.entries()) {
|
|
80
|
-
if (item.type && item.type !== "photo") continue;
|
|
81
|
-
if (!item.media_url_https) continue;
|
|
82
|
-
const src = await downloadAsset(item.media_url_https, `${id}-media-${index + 1}`, options);
|
|
83
|
-
if (src) assets.media.push(assetRecord(src, item));
|
|
84
|
-
}
|
|
85
|
-
return assets;
|
|
86
|
-
}
|
|
87
|
-
async function downloadAsset(source, basename, options) {
|
|
88
|
-
let url;
|
|
89
|
-
try {
|
|
90
|
-
url = new URL(source);
|
|
91
|
-
} catch {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
if (url.protocol !== "https:" || url.hostname.toLowerCase() !== "pbs.twimg.com") return;
|
|
95
|
-
const filename = `${basename}${extensionFromUrl(url)}`;
|
|
96
|
-
const output = path.join(options.mediaOutputDir, filename);
|
|
97
|
-
try {
|
|
98
|
-
await access(output);
|
|
99
|
-
return joinPublicPath(options.mediaPublicPath, filename);
|
|
100
|
-
} catch {}
|
|
101
|
-
try {
|
|
102
|
-
const response = await fetch(url, { headers: { Accept: "image/*" } });
|
|
103
|
-
if (!response.ok) return void 0;
|
|
104
|
-
await mkdir(options.mediaOutputDir, { recursive: true });
|
|
105
|
-
await writeFile(output, new Uint8Array(await response.arrayBuffer()));
|
|
106
|
-
return joinPublicPath(options.mediaPublicPath, filename);
|
|
107
|
-
} catch {
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
async function readCachedTweet(key, directory) {
|
|
112
|
-
try {
|
|
113
|
-
const data = JSON.parse(await readFile(path.join(directory, `${key}.json`), "utf8"));
|
|
114
|
-
return isTweetData(data) ? data : null;
|
|
115
|
-
} catch {
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
async function writeCachedTweet(key, data, directory) {
|
|
120
|
-
try {
|
|
121
|
-
await mkdir(directory, { recursive: true });
|
|
122
|
-
await writeFile(path.join(directory, `${key}.json`), `${JSON.stringify(data)}\n`);
|
|
123
|
-
} catch {}
|
|
124
|
-
}
|
|
125
|
-
function isTweetData(data) {
|
|
126
|
-
if (!data || typeof data !== "object") return false;
|
|
127
|
-
const value = data;
|
|
128
|
-
return typeof value.text === "string" && Boolean(value.user) && typeof value.user?.name === "string" && typeof value.user.screen_name === "string";
|
|
129
|
-
}
|
|
130
|
-
function extensionFromUrl(url) {
|
|
131
|
-
const match = url.pathname.match(/\.(jpe?g|png|webp|gif)$/i);
|
|
132
|
-
return match ? `.${match[1].toLowerCase().replace("jpeg", "jpg")}` : ".jpg";
|
|
133
|
-
}
|
|
134
|
-
function joinPublicPath(prefix, filename) {
|
|
135
|
-
return `${prefix.replace(/\/$/, "")}/${filename}`;
|
|
136
|
-
}
|
|
137
|
-
function sanitizeSegment(value) {
|
|
138
|
-
return value.replaceAll(/[^a-zA-Z0-9_-]/g, "-");
|
|
139
|
-
}
|
|
140
|
-
function assetRecord(src, media) {
|
|
141
|
-
return {
|
|
142
|
-
src,
|
|
143
|
-
alt: media.ext_alt_text,
|
|
144
|
-
width: media.original_info?.width,
|
|
145
|
-
height: media.original_info?.height
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
//#endregion
|
|
149
|
-
//#region src/plugins/twitter/render.ts
|
|
150
|
-
function renderFetchedTweet(permalink, data, assets, options) {
|
|
151
|
-
const profile = `https://x.com/${encodeURIComponent(data.user.screen_name)}`;
|
|
152
|
-
const author = escapeHtml(data.user.name);
|
|
153
|
-
const handle = escapeHtml(data.user.screen_name);
|
|
154
|
-
const avatar = assets.avatar ? `<img class="ox-tweet__avatar" src="${escapeAttribute(assets.avatar)}" alt="" width="48" height="48" loading="lazy" decoding="async">` : "";
|
|
155
|
-
const media = renderMedia(assets);
|
|
156
|
-
const footer = renderFooter(permalink, data.created_at, options.lang);
|
|
157
|
-
return [
|
|
158
|
-
"<figure class=\"ox-tweet ox-tweet--fetched\">",
|
|
159
|
-
"<header class=\"ox-tweet__header\">",
|
|
160
|
-
`<a class="ox-tweet__profile" href="${escapeAttribute(profile)}" target="_blank" rel="noopener noreferrer">`,
|
|
161
|
-
avatar,
|
|
162
|
-
`<span class="ox-tweet__author-name">${author}</span>`,
|
|
163
|
-
`<span class="ox-tweet__author-handle">@${handle}</span>`,
|
|
164
|
-
"</a></header>",
|
|
165
|
-
`<div class="ox-tweet__body">${renderTweetText(data)}</div>`,
|
|
166
|
-
media,
|
|
167
|
-
footer,
|
|
168
|
-
"</figure>"
|
|
169
|
-
].join("");
|
|
170
|
-
}
|
|
171
|
-
function renderTweetText(data) {
|
|
172
|
-
const [start, end] = data.display_text_range ?? [0, data.text.length];
|
|
173
|
-
const entities = collectEntities(data).filter((entity) => validRange(entity.indices, start, end)).sort((left, right) => left.indices[0] - right.indices[0]);
|
|
174
|
-
let cursor = start;
|
|
175
|
-
let output = "";
|
|
176
|
-
for (const entity of entities) {
|
|
177
|
-
const [entityStart, entityEnd] = entity.indices;
|
|
178
|
-
if (entityStart < cursor) continue;
|
|
179
|
-
output += escapeText(data.text.slice(cursor, entityStart));
|
|
180
|
-
if (entity.kind === "url") {
|
|
181
|
-
const href = entity.expanded_url ?? entity.url;
|
|
182
|
-
const label = entity.display_url ?? href;
|
|
183
|
-
output += `<a href="${escapeAttribute(href)}" target="_blank" rel="noopener noreferrer">${escapeHtml(label)}</a>`;
|
|
184
|
-
}
|
|
185
|
-
cursor = entityEnd;
|
|
186
|
-
}
|
|
187
|
-
output += escapeText(data.text.slice(cursor, end));
|
|
188
|
-
return output.trim();
|
|
189
|
-
}
|
|
190
|
-
function collectEntities(data) {
|
|
191
|
-
return [...(data.entities?.urls ?? []).map((entity) => ({
|
|
192
|
-
...entity,
|
|
193
|
-
kind: "url"
|
|
194
|
-
})), ...(data.entities?.media ?? []).map((entity) => ({
|
|
195
|
-
...entity,
|
|
196
|
-
kind: "media"
|
|
197
|
-
}))];
|
|
198
|
-
}
|
|
199
|
-
function validRange(indices, start, end) {
|
|
200
|
-
return Boolean(indices && indices[0] >= start && indices[1] <= end && indices[0] < indices[1]);
|
|
201
|
-
}
|
|
202
|
-
function renderMedia(assets) {
|
|
203
|
-
if (assets.media.length === 0) return "";
|
|
204
|
-
const images = assets.media.map((item) => {
|
|
205
|
-
const size = [item.width ? ` width="${item.width}"` : "", item.height ? ` height="${item.height}"` : ""].join("");
|
|
206
|
-
return `<img class="ox-tweet__media-item" src="${escapeAttribute(item.src)}" alt="${escapeAttribute(item.alt ?? "")}"${size} loading="lazy" decoding="async">`;
|
|
207
|
-
}).join("");
|
|
208
|
-
return `<div class="ox-tweet__media" data-count="${assets.media.length}">${images}</div>`;
|
|
209
|
-
}
|
|
210
|
-
function renderFooter(permalink, createdAt, lang) {
|
|
211
|
-
if (!createdAt) return `<footer class="ox-tweet__footer"><a class="ox-tweet__permalink" href="${escapeAttribute(permalink)}" target="_blank" rel="noopener noreferrer">View on X</a></footer>`;
|
|
212
|
-
const date = new Date(createdAt);
|
|
213
|
-
if (Number.isNaN(date.valueOf())) return renderFooter(permalink, void 0, lang);
|
|
214
|
-
const iso = date.toISOString();
|
|
215
|
-
let label;
|
|
216
|
-
try {
|
|
217
|
-
label = new Intl.DateTimeFormat(lang, {
|
|
218
|
-
dateStyle: "medium",
|
|
219
|
-
timeZone: "UTC"
|
|
220
|
-
}).format(date);
|
|
221
|
-
} catch {
|
|
222
|
-
label = new Intl.DateTimeFormat("en", {
|
|
223
|
-
dateStyle: "medium",
|
|
224
|
-
timeZone: "UTC"
|
|
225
|
-
}).format(date);
|
|
226
|
-
}
|
|
227
|
-
return `<footer class="ox-tweet__footer"><a class="ox-tweet__permalink" href="${escapeAttribute(permalink)}" target="_blank" rel="noopener noreferrer"><time datetime="${iso}">${escapeHtml(label)}</time></a></footer>`;
|
|
228
|
-
}
|
|
229
|
-
function escapeText(value) {
|
|
230
|
-
return escapeHtml(value).replaceAll("\n", "<br>");
|
|
231
|
-
}
|
|
232
|
-
function escapeAttribute(value) {
|
|
233
|
-
return escapeHtml(value).replaceAll("`", "`");
|
|
234
|
-
}
|
|
235
|
-
function escapeHtml(value) {
|
|
236
|
-
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
237
|
-
}
|
|
238
|
-
//#endregion
|
|
239
|
-
//#region src/plugins/twitter/transform.ts
|
|
240
|
-
const TWEET_ELEMENT = /<(tweet|xpost)\b([^>]*?)(?:\/\s*>|>[\s\S]*?<\/\1\s*>)/gi;
|
|
241
|
-
function resolveTwitterEmbedOptions(options) {
|
|
242
|
-
return {
|
|
243
|
-
fetch: options.fetch ?? false,
|
|
244
|
-
lang: options.lang ?? "en",
|
|
245
|
-
timeout: options.timeout ?? 1e4,
|
|
246
|
-
cache: options.cache ?? true,
|
|
247
|
-
cacheDir: path.resolve(options.cacheDir ?? ".cache/ox-content/twitter"),
|
|
248
|
-
mediaOutputDir: path.resolve(options.mediaOutputDir ?? "public/ox-content/twitter"),
|
|
249
|
-
mediaPublicPath: options.mediaPublicPath ?? "/ox-content/twitter"
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
async function transformFetchedTweets(html, options) {
|
|
253
|
-
const resolved = resolveTwitterEmbedOptions(options);
|
|
254
|
-
if (!resolved.fetch) return html;
|
|
255
|
-
let output = "";
|
|
256
|
-
let cursor = 0;
|
|
257
|
-
for (const match of html.matchAll(TWEET_ELEMENT)) {
|
|
258
|
-
const index = match.index ?? 0;
|
|
259
|
-
output += html.slice(cursor, index);
|
|
260
|
-
const reference = referenceFromAttributes(match[2]);
|
|
261
|
-
if (!reference) {
|
|
262
|
-
output += match[0];
|
|
263
|
-
cursor = index + match[0].length;
|
|
264
|
-
continue;
|
|
265
|
-
}
|
|
266
|
-
const data = await fetchTweetData(reference.id, resolved);
|
|
267
|
-
if (!data) {
|
|
268
|
-
output += match[0];
|
|
269
|
-
cursor = index + match[0].length;
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
const assets = await materializeTweetAssets(reference.id, data, resolved);
|
|
273
|
-
output += renderFetchedTweet(reference.url, data, assets, resolved);
|
|
274
|
-
cursor = index + match[0].length;
|
|
275
|
-
}
|
|
276
|
-
return output + html.slice(cursor);
|
|
277
|
-
}
|
|
278
|
-
//#endregion
|
|
279
|
-
//#region src/plugins/media.ts
|
|
280
|
-
async function transformMediaEmbeds(html, options) {
|
|
281
|
-
if (!hasEnabledMediaEmbed(options) || !hasMediaMarker(html)) return html;
|
|
282
|
-
let result = html;
|
|
283
|
-
if (typeof options.twitter === "object") result = await transformFetchedTweets(result, options.twitter);
|
|
284
|
-
if (!hasMediaMarker(result)) return result;
|
|
285
|
-
return (await importNapiModule()).transformMediaEmbeds(result, {
|
|
286
|
-
spotify: options.spotify,
|
|
287
|
-
stackBlitz: options.stackBlitz,
|
|
288
|
-
twitter: Boolean(options.twitter),
|
|
289
|
-
bluesky: options.bluesky,
|
|
290
|
-
webContainer: options.webContainer
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
function hasEnabledMediaEmbed(options) {
|
|
294
|
-
return Boolean(options.spotify || options.stackBlitz || options.twitter || options.bluesky || options.webContainer);
|
|
295
|
-
}
|
|
296
|
-
function hasMediaMarker(html) {
|
|
297
|
-
return /<(spotify|stackblitz|tweet|xpost|bluesky|webcontainer)[\s/>]/i.test(html);
|
|
298
|
-
}
|
|
299
|
-
//#endregion
|
|
300
|
-
export { transformMediaEmbeds as t };
|
|
301
|
-
|
|
302
|
-
//# sourceMappingURL=media.mjs.map
|
package/dist/media.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"media.mjs","names":[],"sources":["../src/plugins/twitter/url.ts","../src/plugins/twitter/fetch.ts","../src/plugins/twitter/render.ts","../src/plugins/twitter/transform.ts","../src/plugins/media.ts"],"sourcesContent":["import type { TweetReference } from \"./types\";\n\nconst STATUS_PATH = /^\\/(?:[^/]+|i\\/web)\\/status\\/(\\d+)(?:\\/.*)?$/;\n\nexport function createSyndicationToken(id: string): string {\n return ((Number(id) / 1e15) * Math.PI).toString(36).replaceAll(/(0+|\\.)/g, \"\");\n}\n\nexport function parseTweetReference(value: string): TweetReference | null {\n const trimmed = value.trim();\n if (/^\\d+$/.test(trimmed)) {\n return { id: trimmed, url: `https://x.com/i/web/status/${trimmed}` };\n }\n\n try {\n const url = new URL(trimmed);\n const hostname = url.hostname.toLowerCase().replace(/^(?:www\\.|mobile\\.)/, \"\");\n if (url.protocol !== \"https:\" || (hostname !== \"x.com\" && hostname !== \"twitter.com\")) {\n return null;\n }\n\n const match = url.pathname.match(STATUS_PATH);\n if (!match) return null;\n const screenName = url.pathname.startsWith(\"/i/web/status/\")\n ? \"i/web\"\n : url.pathname.split(\"/\")[1];\n return {\n id: match[1],\n url: `https://x.com/${screenName}/status/${match[1]}`,\n };\n } catch {\n return null;\n }\n}\n\nexport function referenceFromAttributes(attributes: string): TweetReference | null {\n const values = new Map<string, string>();\n const pattern = /\\b(url|href|id)\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s>]+))/gi;\n for (const match of attributes.matchAll(pattern)) {\n values.set(match[1].toLowerCase(), match[2] ?? match[3] ?? match[4] ?? \"\");\n }\n return parseTweetReference(values.get(\"url\") ?? values.get(\"href\") ?? values.get(\"id\") ?? \"\");\n}\n","import { access, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { createSyndicationToken } from \"./url\";\nimport type { ResolvedTwitterEmbedOptions, TweetAssets, TweetData, TweetMedia } from \"./types\";\n\nconst tweetCache = new Map<string, TweetData>();\n\nexport function clearTweetCache(): void {\n tweetCache.clear();\n}\n\nexport async function fetchTweetData(\n id: string,\n options: ResolvedTwitterEmbedOptions,\n): Promise<TweetData | null> {\n const key = `${id}-${sanitizeSegment(options.lang)}`;\n if (options.cache) {\n const memory = tweetCache.get(key);\n if (memory) return memory;\n const disk = await readCachedTweet(key, options.cacheDir);\n if (disk) {\n tweetCache.set(key, disk);\n return disk;\n }\n }\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), options.timeout);\n const endpoint = new URL(\"https://cdn.syndication.twimg.com/tweet-result\");\n endpoint.searchParams.set(\"id\", id);\n endpoint.searchParams.set(\"lang\", options.lang);\n endpoint.searchParams.set(\"token\", createSyndicationToken(id));\n\n try {\n const response = await fetch(endpoint, {\n headers: { Accept: \"application/json\" },\n signal: controller.signal,\n });\n if (!response.ok) return null;\n const data: unknown = await response.json();\n if (!isTweetData(data)) return null;\n if (options.cache) {\n tweetCache.set(key, data);\n await writeCachedTweet(key, data, options.cacheDir);\n }\n return data;\n } catch {\n return null;\n } finally {\n clearTimeout(timeout);\n }\n}\n\nexport async function materializeTweetAssets(\n id: string,\n data: TweetData,\n options: ResolvedTwitterEmbedOptions,\n): Promise<TweetAssets> {\n const assets: TweetAssets = { media: [] };\n const avatarUrl = data.user.profile_image_url_https?.replace(/_normal(?=\\.[^.]+$)/, \"_bigger\");\n if (avatarUrl) {\n assets.avatar = await downloadAsset(avatarUrl, `${id}-avatar`, options);\n }\n\n const media = data.mediaDetails ?? data.entities?.media ?? [];\n for (const [index, item] of media.entries()) {\n if (item.type && item.type !== \"photo\") continue;\n if (!item.media_url_https) continue;\n const src = await downloadAsset(item.media_url_https, `${id}-media-${index + 1}`, options);\n if (src) assets.media.push(assetRecord(src, item));\n }\n return assets;\n}\n\nasync function downloadAsset(\n source: string,\n basename: string,\n options: ResolvedTwitterEmbedOptions,\n): Promise<string | undefined> {\n let url: URL;\n try {\n url = new URL(source);\n } catch {\n return undefined;\n }\n if (url.protocol !== \"https:\" || url.hostname.toLowerCase() !== \"pbs.twimg.com\") {\n return undefined;\n }\n\n const extension = extensionFromUrl(url);\n const filename = `${basename}${extension}`;\n const output = path.join(options.mediaOutputDir, filename);\n try {\n await access(output);\n return joinPublicPath(options.mediaPublicPath, filename);\n } catch {\n // Download the missing asset below.\n }\n\n try {\n const response = await fetch(url, { headers: { Accept: \"image/*\" } });\n if (!response.ok) return undefined;\n await mkdir(options.mediaOutputDir, { recursive: true });\n await writeFile(output, new Uint8Array(await response.arrayBuffer()));\n return joinPublicPath(options.mediaPublicPath, filename);\n } catch {\n return undefined;\n }\n}\n\nasync function readCachedTweet(key: string, directory: string): Promise<TweetData | null> {\n try {\n const data: unknown = JSON.parse(await readFile(path.join(directory, `${key}.json`), \"utf8\"));\n return isTweetData(data) ? data : null;\n } catch {\n return null;\n }\n}\n\nasync function writeCachedTweet(key: string, data: TweetData, directory: string): Promise<void> {\n try {\n await mkdir(directory, { recursive: true });\n await writeFile(path.join(directory, `${key}.json`), `${JSON.stringify(data)}\\n`);\n } catch {\n // A read-only cache directory must not fail the build.\n }\n}\n\nfunction isTweetData(data: unknown): data is TweetData {\n if (!data || typeof data !== \"object\") return false;\n const value = data as Partial<TweetData>;\n return (\n typeof value.text === \"string\" &&\n Boolean(value.user) &&\n typeof value.user?.name === \"string\" &&\n typeof value.user.screen_name === \"string\"\n );\n}\n\nfunction extensionFromUrl(url: URL): string {\n const match = url.pathname.match(/\\.(jpe?g|png|webp|gif)$/i);\n return match ? `.${match[1].toLowerCase().replace(\"jpeg\", \"jpg\")}` : \".jpg\";\n}\n\nfunction joinPublicPath(prefix: string, filename: string): string {\n return `${prefix.replace(/\\/$/, \"\")}/${filename}`;\n}\n\nfunction sanitizeSegment(value: string): string {\n return value.replaceAll(/[^a-zA-Z0-9_-]/g, \"-\");\n}\n\nfunction assetRecord(src: string, media: TweetMedia): TweetAssets[\"media\"][number] {\n return {\n src,\n alt: media.ext_alt_text,\n width: media.original_info?.width,\n height: media.original_info?.height,\n };\n}\n","import type { ResolvedTwitterEmbedOptions, TweetAssets, TweetData, TweetEntity } from \"./types\";\n\nexport function renderFetchedTweet(\n permalink: string,\n data: TweetData,\n assets: TweetAssets,\n options: ResolvedTwitterEmbedOptions,\n): string {\n const profile = `https://x.com/${encodeURIComponent(data.user.screen_name)}`;\n const author = escapeHtml(data.user.name);\n const handle = escapeHtml(data.user.screen_name);\n const avatar = assets.avatar\n ? `<img class=\"ox-tweet__avatar\" src=\"${escapeAttribute(assets.avatar)}\" alt=\"\" width=\"48\" height=\"48\" loading=\"lazy\" decoding=\"async\">`\n : \"\";\n const media = renderMedia(assets);\n const footer = renderFooter(permalink, data.created_at, options.lang);\n\n return [\n '<figure class=\"ox-tweet ox-tweet--fetched\">',\n '<header class=\"ox-tweet__header\">',\n `<a class=\"ox-tweet__profile\" href=\"${escapeAttribute(profile)}\" target=\"_blank\" rel=\"noopener noreferrer\">`,\n avatar,\n `<span class=\"ox-tweet__author-name\">${author}</span>`,\n `<span class=\"ox-tweet__author-handle\">@${handle}</span>`,\n \"</a></header>\",\n `<div class=\"ox-tweet__body\">${renderTweetText(data)}</div>`,\n media,\n footer,\n \"</figure>\",\n ].join(\"\");\n}\n\nexport function renderTweetText(data: TweetData): string {\n const [start, end] = data.display_text_range ?? [0, data.text.length];\n const entities = collectEntities(data)\n .filter((entity) => validRange(entity.indices, start, end))\n .sort((left, right) => left.indices![0] - right.indices![0]);\n\n let cursor = start;\n let output = \"\";\n for (const entity of entities) {\n const [entityStart, entityEnd] = entity.indices!;\n if (entityStart < cursor) continue;\n output += escapeText(data.text.slice(cursor, entityStart));\n if (entity.kind === \"url\") {\n const href = entity.expanded_url ?? entity.url;\n const label = entity.display_url ?? href;\n output += `<a href=\"${escapeAttribute(href)}\" target=\"_blank\" rel=\"noopener noreferrer\">${escapeHtml(label)}</a>`;\n }\n cursor = entityEnd;\n }\n output += escapeText(data.text.slice(cursor, end));\n return output.trim();\n}\n\nfunction collectEntities(data: TweetData): Array<TweetEntity & { kind: \"url\" | \"media\" }> {\n return [\n ...(data.entities?.urls ?? []).map((entity) => ({ ...entity, kind: \"url\" as const })),\n ...(data.entities?.media ?? []).map((entity) => ({ ...entity, kind: \"media\" as const })),\n ];\n}\n\nfunction validRange(\n indices: [number, number] | undefined,\n start: number,\n end: number,\n): indices is [number, number] {\n return Boolean(indices && indices[0] >= start && indices[1] <= end && indices[0] < indices[1]);\n}\n\nfunction renderMedia(assets: TweetAssets): string {\n if (assets.media.length === 0) return \"\";\n const images = assets.media\n .map((item) => {\n const size = [\n item.width ? ` width=\"${item.width}\"` : \"\",\n item.height ? ` height=\"${item.height}\"` : \"\",\n ].join(\"\");\n return `<img class=\"ox-tweet__media-item\" src=\"${escapeAttribute(item.src)}\" alt=\"${escapeAttribute(item.alt ?? \"\")}\"${size} loading=\"lazy\" decoding=\"async\">`;\n })\n .join(\"\");\n return `<div class=\"ox-tweet__media\" data-count=\"${assets.media.length}\">${images}</div>`;\n}\n\nfunction renderFooter(permalink: string, createdAt: string | undefined, lang: string): string {\n if (!createdAt) {\n return `<footer class=\"ox-tweet__footer\"><a class=\"ox-tweet__permalink\" href=\"${escapeAttribute(permalink)}\" target=\"_blank\" rel=\"noopener noreferrer\">View on X</a></footer>`;\n }\n const date = new Date(createdAt);\n if (Number.isNaN(date.valueOf())) return renderFooter(permalink, undefined, lang);\n const iso = date.toISOString();\n let label: string;\n try {\n label = new Intl.DateTimeFormat(lang, { dateStyle: \"medium\", timeZone: \"UTC\" }).format(date);\n } catch {\n label = new Intl.DateTimeFormat(\"en\", { dateStyle: \"medium\", timeZone: \"UTC\" }).format(date);\n }\n return `<footer class=\"ox-tweet__footer\"><a class=\"ox-tweet__permalink\" href=\"${escapeAttribute(permalink)}\" target=\"_blank\" rel=\"noopener noreferrer\"><time datetime=\"${iso}\">${escapeHtml(label)}</time></a></footer>`;\n}\n\nfunction escapeText(value: string): string {\n return escapeHtml(value).replaceAll(\"\\n\", \"<br>\");\n}\n\nfunction escapeAttribute(value: string): string {\n return escapeHtml(value).replaceAll(\"`\", \"`\");\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\")\n .replaceAll('\"', \""\")\n .replaceAll(\"'\", \"'\");\n}\n","import path from \"node:path\";\nimport { fetchTweetData, materializeTweetAssets } from \"./fetch\";\nimport { renderFetchedTweet } from \"./render\";\nimport type { ResolvedTwitterEmbedOptions, TwitterEmbedOptions } from \"./types\";\nimport { referenceFromAttributes } from \"./url\";\n\nconst TWEET_ELEMENT = /<(tweet|xpost)\\b([^>]*?)(?:\\/\\s*>|>[\\s\\S]*?<\\/\\1\\s*>)/gi;\n\nexport function resolveTwitterEmbedOptions(\n options: TwitterEmbedOptions,\n): ResolvedTwitterEmbedOptions {\n return {\n fetch: options.fetch ?? false,\n lang: options.lang ?? \"en\",\n timeout: options.timeout ?? 10000,\n cache: options.cache ?? true,\n cacheDir: path.resolve(options.cacheDir ?? \".cache/ox-content/twitter\"),\n mediaOutputDir: path.resolve(options.mediaOutputDir ?? \"public/ox-content/twitter\"),\n mediaPublicPath: options.mediaPublicPath ?? \"/ox-content/twitter\",\n };\n}\n\nexport async function transformFetchedTweets(\n html: string,\n options: TwitterEmbedOptions,\n): Promise<string> {\n const resolved = resolveTwitterEmbedOptions(options);\n if (!resolved.fetch) return html;\n\n let output = \"\";\n let cursor = 0;\n for (const match of html.matchAll(TWEET_ELEMENT)) {\n const index = match.index ?? 0;\n output += html.slice(cursor, index);\n const reference = referenceFromAttributes(match[2]);\n if (!reference) {\n output += match[0];\n cursor = index + match[0].length;\n continue;\n }\n\n const data = await fetchTweetData(reference.id, resolved);\n if (!data) {\n output += match[0];\n cursor = index + match[0].length;\n continue;\n }\n\n const assets = await materializeTweetAssets(reference.id, data, resolved);\n output += renderFetchedTweet(reference.url, data, assets, resolved);\n cursor = index + match[0].length;\n }\n return output + html.slice(cursor);\n}\n","import { importNapiModule } from \"../napi\";\nimport { transformFetchedTweets } from \"./twitter\";\nimport type { TwitterEmbedOptions } from \"./twitter\";\n\nexport interface MediaEmbedOptions {\n /**\n * Render `<Spotify>` embeds.\n * @default false\n */\n spotify?: boolean;\n\n /**\n * Render `<StackBlitz>` embeds.\n * @default false\n */\n stackBlitz?: boolean;\n\n /**\n * Render `<Tweet>` / `<XPost>` static cards. Pass `{ fetch: true }` to\n * resolve the post content and self-host its media at build time.\n * @default false\n */\n twitter?: boolean | TwitterEmbedOptions;\n\n /**\n * Render `<Bluesky>` static cards.\n * @default false\n */\n bluesky?: boolean;\n\n /**\n * Render `<WebContainer>` lazy placeholder blocks.\n * @default false\n */\n webContainer?: boolean;\n}\n\nexport async function transformMediaEmbeds(\n html: string,\n options: MediaEmbedOptions,\n): Promise<string> {\n if (!hasEnabledMediaEmbed(options) || !hasMediaMarker(html)) {\n return html;\n }\n\n let result = html;\n if (typeof options.twitter === \"object\") {\n result = await transformFetchedTweets(result, options.twitter);\n }\n if (!hasMediaMarker(result)) return result;\n\n const mod = await importNapiModule();\n return mod.transformMediaEmbeds(result, {\n spotify: options.spotify,\n stackBlitz: options.stackBlitz,\n twitter: Boolean(options.twitter),\n bluesky: options.bluesky,\n webContainer: options.webContainer,\n });\n}\n\nfunction hasEnabledMediaEmbed(options: MediaEmbedOptions): boolean {\n return Boolean(\n options.spotify ||\n options.stackBlitz ||\n options.twitter ||\n options.bluesky ||\n options.webContainer,\n );\n}\n\nfunction hasMediaMarker(html: string): boolean {\n return /<(spotify|stackblitz|tweet|xpost|bluesky|webcontainer)[\\s/>]/i.test(html);\n}\n"],"mappings":"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"}
|
package/dist/media2.mjs
DELETED
package/dist/mermaid.cjs
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
const require_chunk = require("./chunk.cjs");
|
|
2
|
-
const require_napi = require("./napi.cjs");
|
|
3
|
-
let node_module = require("node:module");
|
|
4
|
-
let node_fs = require("node:fs");
|
|
5
|
-
let node_path = require("node:path");
|
|
6
|
-
//#region src/plugins/mermaid.ts
|
|
7
|
-
/**
|
|
8
|
-
* Mermaid Plugin - Native Rust renderer via NAPI
|
|
9
|
-
*
|
|
10
|
-
* Renders mermaid code blocks to SVG using the native Rust renderer
|
|
11
|
-
* via NAPI. Delegates to the NAPI `transformMermaid` function which
|
|
12
|
-
* extracts mermaid code blocks from HTML and renders them using mmdc.
|
|
13
|
-
*/
|
|
14
|
-
var mermaid_exports = /* @__PURE__ */ require_chunk.__exportAll({
|
|
15
|
-
mermaidClientScript: () => "",
|
|
16
|
-
transformMermaidStatic: () => transformMermaidStatic
|
|
17
|
-
});
|
|
18
|
-
/** Cached NAPI bindings */
|
|
19
|
-
let napiBindings = null;
|
|
20
|
-
let napiLoadAttempted = false;
|
|
21
|
-
async function loadNapi() {
|
|
22
|
-
if (napiLoadAttempted) return napiBindings;
|
|
23
|
-
napiLoadAttempted = true;
|
|
24
|
-
try {
|
|
25
|
-
const binding = await require_napi.importNapiModule();
|
|
26
|
-
if (typeof binding.transformMermaid !== "function") {
|
|
27
|
-
napiBindings = null;
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
napiBindings = binding;
|
|
31
|
-
return binding;
|
|
32
|
-
} catch {
|
|
33
|
-
napiBindings = null;
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
let cachedMmdcPath;
|
|
38
|
-
let missingMmdcWarned = false;
|
|
39
|
-
function resolveMmdcPath() {
|
|
40
|
-
if (cachedMmdcPath !== void 0) return cachedMmdcPath;
|
|
41
|
-
for (const resolver of createMmdcResolvers()) try {
|
|
42
|
-
const cliPath = (0, node_path.join)((0, node_path.dirname)(resolver.resolve("@mermaid-js/mermaid-cli")), "cli.js");
|
|
43
|
-
if ((0, node_fs.existsSync)(cliPath)) {
|
|
44
|
-
cachedMmdcPath = cliPath;
|
|
45
|
-
return cachedMmdcPath;
|
|
46
|
-
}
|
|
47
|
-
} catch {}
|
|
48
|
-
const binPath = (0, node_path.join)(process.cwd(), "node_modules", ".bin", "mmdc");
|
|
49
|
-
if ((0, node_fs.existsSync)(binPath)) {
|
|
50
|
-
cachedMmdcPath = binPath;
|
|
51
|
-
return cachedMmdcPath;
|
|
52
|
-
}
|
|
53
|
-
cachedMmdcPath = null;
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
function createMmdcResolvers() {
|
|
57
|
-
const consumerRequire = (0, node_module.createRequire)((0, node_path.join)(process.cwd(), "noop.js"));
|
|
58
|
-
const resolvers = [consumerRequire];
|
|
59
|
-
try {
|
|
60
|
-
resolvers.push((0, node_module.createRequire)(consumerRequire.resolve("@ox-content/vite-plugin")));
|
|
61
|
-
} catch {}
|
|
62
|
-
return resolvers;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Transforms mermaid code blocks in HTML to rendered SVG diagrams.
|
|
66
|
-
* Uses the native Rust NAPI transformMermaid function.
|
|
67
|
-
*/
|
|
68
|
-
async function transformMermaidStatic(html, _options) {
|
|
69
|
-
const napi = await loadNapi();
|
|
70
|
-
if (!napi) return html;
|
|
71
|
-
const mmdcPath = resolveMmdcPath();
|
|
72
|
-
if (!mmdcPath) {
|
|
73
|
-
warnMissingMmdcOnce();
|
|
74
|
-
return html;
|
|
75
|
-
}
|
|
76
|
-
try {
|
|
77
|
-
const result = napi.transformMermaid(html, mmdcPath);
|
|
78
|
-
for (const error of result.errors) console.warn("[ox-content] Mermaid render error:", error);
|
|
79
|
-
return result.html;
|
|
80
|
-
} catch (err) {
|
|
81
|
-
console.warn("[ox-content] Mermaid transform error:", err);
|
|
82
|
-
return html;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
function warnMissingMmdcOnce() {
|
|
86
|
-
if (missingMmdcWarned) return;
|
|
87
|
-
missingMmdcWarned = true;
|
|
88
|
-
console.warn("[ox-content] mmdc not found; skipping Mermaid rendering.");
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* @deprecated No longer used. Mermaid rendering is now done at build time via NAPI.
|
|
92
|
-
*/
|
|
93
|
-
const mermaidClientScript = "";
|
|
94
|
-
//#endregion
|
|
95
|
-
Object.defineProperty(exports, "mermaidClientScript", {
|
|
96
|
-
enumerable: true,
|
|
97
|
-
get: function() {
|
|
98
|
-
return mermaidClientScript;
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
Object.defineProperty(exports, "mermaid_exports", {
|
|
102
|
-
enumerable: true,
|
|
103
|
-
get: function() {
|
|
104
|
-
return mermaid_exports;
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
Object.defineProperty(exports, "transformMermaidStatic", {
|
|
108
|
-
enumerable: true,
|
|
109
|
-
get: function() {
|
|
110
|
-
return transformMermaidStatic;
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
//# sourceMappingURL=mermaid.cjs.map
|
package/dist/mermaid.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mermaid.cjs","names":["importNapiModule"],"sources":["../src/plugins/mermaid.ts"],"sourcesContent":["/**\n * Mermaid Plugin - Native Rust renderer via NAPI\n *\n * Renders mermaid code blocks to SVG using the native Rust renderer\n * via NAPI. Delegates to the NAPI `transformMermaid` function which\n * extracts mermaid code blocks from HTML and renders them using mmdc.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { dirname, join } from \"node:path\";\nimport { importNapiModule } from \"../napi\";\n\nexport interface MermaidOptions {\n /**\n * Mermaid theme used by the CLI renderer.\n * @default 'neutral'\n */\n theme?: \"default\" | \"dark\" | \"forest\" | \"neutral\" | \"base\";\n}\n\n/** Cached NAPI bindings */\nlet napiBindings: {\n transformMermaid: (html: string, mmdcPath: string) => { html: string; errors: string[] };\n} | null = null;\n\nlet napiLoadAttempted = false;\n\nasync function loadNapi() {\n if (napiLoadAttempted) return napiBindings;\n napiLoadAttempted = true;\n try {\n const binding = (await importNapiModule()) as unknown as NonNullable<typeof napiBindings>;\n if (typeof binding.transformMermaid !== \"function\") {\n napiBindings = null;\n return null;\n }\n napiBindings = binding;\n return binding;\n } catch {\n napiBindings = null;\n return null;\n }\n}\n\nlet cachedMmdcPath: string | null | undefined;\nlet missingMmdcWarned = false;\n\nfunction resolveMmdcPath(): string | null {\n if (cachedMmdcPath !== undefined) return cachedMmdcPath;\n\n for (const resolver of createMmdcResolvers()) {\n try {\n const entry = resolver.resolve(\"@mermaid-js/mermaid-cli\");\n const cliPath = join(dirname(entry), \"cli.js\");\n if (existsSync(cliPath)) {\n cachedMmdcPath = cliPath;\n return cachedMmdcPath;\n }\n } catch {\n // Try the next resolver.\n }\n }\n\n // Fallback: node_modules/.bin/mmdc relative to cwd\n const binPath = join(process.cwd(), \"node_modules\", \".bin\", \"mmdc\");\n if (existsSync(binPath)) {\n cachedMmdcPath = binPath;\n return cachedMmdcPath;\n }\n\n cachedMmdcPath = null;\n return null;\n}\n\nfunction createMmdcResolvers(): NodeJS.Require[] {\n // Resolve from the consumer first, then from this package. The second lookup\n // matters under pnpm strict linking: docs apps can depend on the plugin\n // without directly depending on mermaid-cli.\n const consumerRequire = createRequire(join(process.cwd(), \"noop.js\"));\n const resolvers = [consumerRequire];\n\n try {\n resolvers.push(createRequire(consumerRequire.resolve(\"@ox-content/vite-plugin\")));\n } catch {\n // If the package is used from source without its package name resolvable,\n // the consumer resolver and bin fallback still cover direct installs.\n }\n\n return resolvers;\n}\n\n/**\n * Transforms mermaid code blocks in HTML to rendered SVG diagrams.\n * Uses the native Rust NAPI transformMermaid function.\n */\nexport async function transformMermaidStatic(\n html: string,\n _options?: MermaidOptions,\n): Promise<string> {\n const napi = await loadNapi();\n if (!napi) {\n return html;\n }\n\n const mmdcPath = resolveMmdcPath();\n if (!mmdcPath) {\n warnMissingMmdcOnce();\n return html;\n }\n\n try {\n const result = napi.transformMermaid(html, mmdcPath);\n for (const error of result.errors) {\n console.warn(\"[ox-content] Mermaid render error:\", error);\n }\n return result.html;\n } catch (err) {\n console.warn(\"[ox-content] Mermaid transform error:\", err);\n return html;\n }\n}\n\nfunction warnMissingMmdcOnce(): void {\n if (missingMmdcWarned) {\n return;\n }\n\n missingMmdcWarned = true;\n console.warn(\"[ox-content] mmdc not found; skipping Mermaid rendering.\");\n}\n\n/**\n * @deprecated No longer used. Mermaid rendering is now done at build time via NAPI.\n */\nexport const mermaidClientScript = \"\";\n"],"mappings":";;;;;;;;;;;;;;;;;;AAsBA,IAAI,eAEO;AAEX,IAAI,oBAAoB;AAExB,eAAe,WAAW;CACxB,IAAI,mBAAmB,OAAO;CAC9B,oBAAoB;CACpB,IAAI;EACF,MAAM,UAAW,MAAMA,aAAAA,kBAAkB;EACzC,IAAI,OAAO,QAAQ,qBAAqB,YAAY;GAClD,eAAe;GACf,OAAO;;EAET,eAAe;EACf,OAAO;SACD;EACN,eAAe;EACf,OAAO;;;AAIX,IAAI;AACJ,IAAI,oBAAoB;AAExB,SAAS,kBAAiC;CACxC,IAAI,mBAAmB,KAAA,GAAW,OAAO;CAEzC,KAAK,MAAM,YAAY,qBAAqB,EAC1C,IAAI;EAEF,MAAM,WAAA,GAAA,UAAA,OAAA,GAAA,UAAA,SADQ,SAAS,QAAQ,0BACG,CAAC,EAAE,SAAS;EAC9C,KAAA,GAAA,QAAA,YAAe,QAAQ,EAAE;GACvB,iBAAiB;GACjB,OAAO;;SAEH;CAMV,MAAM,WAAA,GAAA,UAAA,MAAe,QAAQ,KAAK,EAAE,gBAAgB,QAAQ,OAAO;CACnE,KAAA,GAAA,QAAA,YAAe,QAAQ,EAAE;EACvB,iBAAiB;EACjB,OAAO;;CAGT,iBAAiB;CACjB,OAAO;;AAGT,SAAS,sBAAwC;CAI/C,MAAM,mBAAA,GAAA,YAAA,gBAAA,GAAA,UAAA,MAAqC,QAAQ,KAAK,EAAE,UAAU,CAAC;CACrE,MAAM,YAAY,CAAC,gBAAgB;CAEnC,IAAI;EACF,UAAU,MAAA,GAAA,YAAA,eAAmB,gBAAgB,QAAQ,0BAA0B,CAAC,CAAC;SAC3E;CAKR,OAAO;;;;;;AAOT,eAAsB,uBACpB,MACA,UACiB;CACjB,MAAM,OAAO,MAAM,UAAU;CAC7B,IAAI,CAAC,MACH,OAAO;CAGT,MAAM,WAAW,iBAAiB;CAClC,IAAI,CAAC,UAAU;EACb,qBAAqB;EACrB,OAAO;;CAGT,IAAI;EACF,MAAM,SAAS,KAAK,iBAAiB,MAAM,SAAS;EACpD,KAAK,MAAM,SAAS,OAAO,QACzB,QAAQ,KAAK,sCAAsC,MAAM;EAE3D,OAAO,OAAO;UACP,KAAK;EACZ,QAAQ,KAAK,yCAAyC,IAAI;EAC1D,OAAO;;;AAIX,SAAS,sBAA4B;CACnC,IAAI,mBACF;CAGF,oBAAoB;CACpB,QAAQ,KAAK,2DAA2D;;;;;AAM1E,MAAa,sBAAsB"}
|
package/dist/mermaid.mjs
DELETED
package/dist/mermaid2.mjs
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { t as importNapiModule } from "./napi.mjs";
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
|
-
import { existsSync } from "node:fs";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
5
|
-
//#region src/plugins/mermaid.ts
|
|
6
|
-
/**
|
|
7
|
-
* Mermaid Plugin - Native Rust renderer via NAPI
|
|
8
|
-
*
|
|
9
|
-
* Renders mermaid code blocks to SVG using the native Rust renderer
|
|
10
|
-
* via NAPI. Delegates to the NAPI `transformMermaid` function which
|
|
11
|
-
* extracts mermaid code blocks from HTML and renders them using mmdc.
|
|
12
|
-
*/
|
|
13
|
-
/** Cached NAPI bindings */
|
|
14
|
-
let napiBindings = null;
|
|
15
|
-
let napiLoadAttempted = false;
|
|
16
|
-
async function loadNapi() {
|
|
17
|
-
if (napiLoadAttempted) return napiBindings;
|
|
18
|
-
napiLoadAttempted = true;
|
|
19
|
-
try {
|
|
20
|
-
const binding = await importNapiModule();
|
|
21
|
-
if (typeof binding.transformMermaid !== "function") {
|
|
22
|
-
napiBindings = null;
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
napiBindings = binding;
|
|
26
|
-
return binding;
|
|
27
|
-
} catch {
|
|
28
|
-
napiBindings = null;
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
let cachedMmdcPath;
|
|
33
|
-
let missingMmdcWarned = false;
|
|
34
|
-
function resolveMmdcPath() {
|
|
35
|
-
if (cachedMmdcPath !== void 0) return cachedMmdcPath;
|
|
36
|
-
for (const resolver of createMmdcResolvers()) try {
|
|
37
|
-
const cliPath = join(dirname(resolver.resolve("@mermaid-js/mermaid-cli")), "cli.js");
|
|
38
|
-
if (existsSync(cliPath)) {
|
|
39
|
-
cachedMmdcPath = cliPath;
|
|
40
|
-
return cachedMmdcPath;
|
|
41
|
-
}
|
|
42
|
-
} catch {}
|
|
43
|
-
const binPath = join(process.cwd(), "node_modules", ".bin", "mmdc");
|
|
44
|
-
if (existsSync(binPath)) {
|
|
45
|
-
cachedMmdcPath = binPath;
|
|
46
|
-
return cachedMmdcPath;
|
|
47
|
-
}
|
|
48
|
-
cachedMmdcPath = null;
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
function createMmdcResolvers() {
|
|
52
|
-
const consumerRequire = createRequire(join(process.cwd(), "noop.js"));
|
|
53
|
-
const resolvers = [consumerRequire];
|
|
54
|
-
try {
|
|
55
|
-
resolvers.push(createRequire(consumerRequire.resolve("@ox-content/vite-plugin")));
|
|
56
|
-
} catch {}
|
|
57
|
-
return resolvers;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Transforms mermaid code blocks in HTML to rendered SVG diagrams.
|
|
61
|
-
* Uses the native Rust NAPI transformMermaid function.
|
|
62
|
-
*/
|
|
63
|
-
async function transformMermaidStatic(html, _options) {
|
|
64
|
-
const napi = await loadNapi();
|
|
65
|
-
if (!napi) return html;
|
|
66
|
-
const mmdcPath = resolveMmdcPath();
|
|
67
|
-
if (!mmdcPath) {
|
|
68
|
-
warnMissingMmdcOnce();
|
|
69
|
-
return html;
|
|
70
|
-
}
|
|
71
|
-
try {
|
|
72
|
-
const result = napi.transformMermaid(html, mmdcPath);
|
|
73
|
-
for (const error of result.errors) console.warn("[ox-content] Mermaid render error:", error);
|
|
74
|
-
return result.html;
|
|
75
|
-
} catch (err) {
|
|
76
|
-
console.warn("[ox-content] Mermaid transform error:", err);
|
|
77
|
-
return html;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
function warnMissingMmdcOnce() {
|
|
81
|
-
if (missingMmdcWarned) return;
|
|
82
|
-
missingMmdcWarned = true;
|
|
83
|
-
console.warn("[ox-content] mmdc not found; skipping Mermaid rendering.");
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* @deprecated No longer used. Mermaid rendering is now done at build time via NAPI.
|
|
87
|
-
*/
|
|
88
|
-
const mermaidClientScript = "";
|
|
89
|
-
//#endregion
|
|
90
|
-
export { transformMermaidStatic as n, mermaidClientScript as t };
|
|
91
|
-
|
|
92
|
-
//# sourceMappingURL=mermaid2.mjs.map
|
package/dist/mermaid2.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mermaid2.mjs","names":[],"sources":["../src/plugins/mermaid.ts"],"sourcesContent":["/**\n * Mermaid Plugin - Native Rust renderer via NAPI\n *\n * Renders mermaid code blocks to SVG using the native Rust renderer\n * via NAPI. Delegates to the NAPI `transformMermaid` function which\n * extracts mermaid code blocks from HTML and renders them using mmdc.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { dirname, join } from \"node:path\";\nimport { importNapiModule } from \"../napi\";\n\nexport interface MermaidOptions {\n /**\n * Mermaid theme used by the CLI renderer.\n * @default 'neutral'\n */\n theme?: \"default\" | \"dark\" | \"forest\" | \"neutral\" | \"base\";\n}\n\n/** Cached NAPI bindings */\nlet napiBindings: {\n transformMermaid: (html: string, mmdcPath: string) => { html: string; errors: string[] };\n} | null = null;\n\nlet napiLoadAttempted = false;\n\nasync function loadNapi() {\n if (napiLoadAttempted) return napiBindings;\n napiLoadAttempted = true;\n try {\n const binding = (await importNapiModule()) as unknown as NonNullable<typeof napiBindings>;\n if (typeof binding.transformMermaid !== \"function\") {\n napiBindings = null;\n return null;\n }\n napiBindings = binding;\n return binding;\n } catch {\n napiBindings = null;\n return null;\n }\n}\n\nlet cachedMmdcPath: string | null | undefined;\nlet missingMmdcWarned = false;\n\nfunction resolveMmdcPath(): string | null {\n if (cachedMmdcPath !== undefined) return cachedMmdcPath;\n\n for (const resolver of createMmdcResolvers()) {\n try {\n const entry = resolver.resolve(\"@mermaid-js/mermaid-cli\");\n const cliPath = join(dirname(entry), \"cli.js\");\n if (existsSync(cliPath)) {\n cachedMmdcPath = cliPath;\n return cachedMmdcPath;\n }\n } catch {\n // Try the next resolver.\n }\n }\n\n // Fallback: node_modules/.bin/mmdc relative to cwd\n const binPath = join(process.cwd(), \"node_modules\", \".bin\", \"mmdc\");\n if (existsSync(binPath)) {\n cachedMmdcPath = binPath;\n return cachedMmdcPath;\n }\n\n cachedMmdcPath = null;\n return null;\n}\n\nfunction createMmdcResolvers(): NodeJS.Require[] {\n // Resolve from the consumer first, then from this package. The second lookup\n // matters under pnpm strict linking: docs apps can depend on the plugin\n // without directly depending on mermaid-cli.\n const consumerRequire = createRequire(join(process.cwd(), \"noop.js\"));\n const resolvers = [consumerRequire];\n\n try {\n resolvers.push(createRequire(consumerRequire.resolve(\"@ox-content/vite-plugin\")));\n } catch {\n // If the package is used from source without its package name resolvable,\n // the consumer resolver and bin fallback still cover direct installs.\n }\n\n return resolvers;\n}\n\n/**\n * Transforms mermaid code blocks in HTML to rendered SVG diagrams.\n * Uses the native Rust NAPI transformMermaid function.\n */\nexport async function transformMermaidStatic(\n html: string,\n _options?: MermaidOptions,\n): Promise<string> {\n const napi = await loadNapi();\n if (!napi) {\n return html;\n }\n\n const mmdcPath = resolveMmdcPath();\n if (!mmdcPath) {\n warnMissingMmdcOnce();\n return html;\n }\n\n try {\n const result = napi.transformMermaid(html, mmdcPath);\n for (const error of result.errors) {\n console.warn(\"[ox-content] Mermaid render error:\", error);\n }\n return result.html;\n } catch (err) {\n console.warn(\"[ox-content] Mermaid transform error:\", err);\n return html;\n }\n}\n\nfunction warnMissingMmdcOnce(): void {\n if (missingMmdcWarned) {\n return;\n }\n\n missingMmdcWarned = true;\n console.warn(\"[ox-content] mmdc not found; skipping Mermaid rendering.\");\n}\n\n/**\n * @deprecated No longer used. Mermaid rendering is now done at build time via NAPI.\n */\nexport const mermaidClientScript = \"\";\n"],"mappings":";;;;;;;;;;;;;AAsBA,IAAI,eAEO;AAEX,IAAI,oBAAoB;AAExB,eAAe,WAAW;CACxB,IAAI,mBAAmB,OAAO;CAC9B,oBAAoB;CACpB,IAAI;EACF,MAAM,UAAW,MAAM,kBAAkB;EACzC,IAAI,OAAO,QAAQ,qBAAqB,YAAY;GAClD,eAAe;GACf,OAAO;;EAET,eAAe;EACf,OAAO;SACD;EACN,eAAe;EACf,OAAO;;;AAIX,IAAI;AACJ,IAAI,oBAAoB;AAExB,SAAS,kBAAiC;CACxC,IAAI,mBAAmB,KAAA,GAAW,OAAO;CAEzC,KAAK,MAAM,YAAY,qBAAqB,EAC1C,IAAI;EAEF,MAAM,UAAU,KAAK,QADP,SAAS,QAAQ,0BACG,CAAC,EAAE,SAAS;EAC9C,IAAI,WAAW,QAAQ,EAAE;GACvB,iBAAiB;GACjB,OAAO;;SAEH;CAMV,MAAM,UAAU,KAAK,QAAQ,KAAK,EAAE,gBAAgB,QAAQ,OAAO;CACnE,IAAI,WAAW,QAAQ,EAAE;EACvB,iBAAiB;EACjB,OAAO;;CAGT,iBAAiB;CACjB,OAAO;;AAGT,SAAS,sBAAwC;CAI/C,MAAM,kBAAkB,cAAc,KAAK,QAAQ,KAAK,EAAE,UAAU,CAAC;CACrE,MAAM,YAAY,CAAC,gBAAgB;CAEnC,IAAI;EACF,UAAU,KAAK,cAAc,gBAAgB,QAAQ,0BAA0B,CAAC,CAAC;SAC3E;CAKR,OAAO;;;;;;AAOT,eAAsB,uBACpB,MACA,UACiB;CACjB,MAAM,OAAO,MAAM,UAAU;CAC7B,IAAI,CAAC,MACH,OAAO;CAGT,MAAM,WAAW,iBAAiB;CAClC,IAAI,CAAC,UAAU;EACb,qBAAqB;EACrB,OAAO;;CAGT,IAAI;EACF,MAAM,SAAS,KAAK,iBAAiB,MAAM,SAAS;EACpD,KAAK,MAAM,SAAS,OAAO,QACzB,QAAQ,KAAK,sCAAsC,MAAM;EAE3D,OAAO,OAAO;UACP,KAAK;EACZ,QAAQ,KAAK,yCAAyC,IAAI;EAC1D,OAAO;;;AAIX,SAAS,sBAA4B;CACnC,IAAI,mBACF;CAGF,oBAAoB;CACpB,QAAQ,KAAK,2DAA2D;;;;;AAM1E,MAAa,sBAAsB"}
|
package/dist/napi.cjs
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
require("./chunk.cjs");
|
|
2
|
-
//#region src/napi.ts
|
|
3
|
-
const requireNapi = (0, require("node:module").createRequire)(require("url").pathToFileURL(__filename).href);
|
|
4
|
-
function getDefaultExport(value) {
|
|
5
|
-
if (!value || typeof value !== "object" || !("default" in value)) return;
|
|
6
|
-
const defaultExport = value.default;
|
|
7
|
-
return defaultExport && typeof defaultExport === "object" ? defaultExport : void 0;
|
|
8
|
-
}
|
|
9
|
-
function normalizeNapiModule(mod) {
|
|
10
|
-
const defaultExport = getDefaultExport(mod);
|
|
11
|
-
return defaultExport ? {
|
|
12
|
-
...defaultExport,
|
|
13
|
-
...mod
|
|
14
|
-
} : mod;
|
|
15
|
-
}
|
|
16
|
-
async function importNapiModule() {
|
|
17
|
-
return normalizeNapiModule(await import("@ox-content/napi"));
|
|
18
|
-
}
|
|
19
|
-
let syncNapiModule;
|
|
20
|
-
function importNapiModuleSync() {
|
|
21
|
-
if (syncNapiModule) return syncNapiModule;
|
|
22
|
-
if (syncNapiModule === null) throw new Error("[ox-content] @ox-content/napi is required. Please ensure the NAPI module is built.");
|
|
23
|
-
try {
|
|
24
|
-
syncNapiModule = normalizeNapiModule(requireNapi("@ox-content/napi"));
|
|
25
|
-
return syncNapiModule;
|
|
26
|
-
} catch {
|
|
27
|
-
syncNapiModule = null;
|
|
28
|
-
throw new Error("[ox-content] @ox-content/napi is required. Please ensure the NAPI module is built.");
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
//#endregion
|
|
32
|
-
Object.defineProperty(exports, "importNapiModule", {
|
|
33
|
-
enumerable: true,
|
|
34
|
-
get: function() {
|
|
35
|
-
return importNapiModule;
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
Object.defineProperty(exports, "importNapiModuleSync", {
|
|
39
|
-
enumerable: true,
|
|
40
|
-
get: function() {
|
|
41
|
-
return importNapiModuleSync;
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
//# sourceMappingURL=napi.cjs.map
|
package/dist/napi.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"napi.cjs","names":[],"sources":["../src/napi.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\n\ntype NapiModule = typeof import(\"@ox-content/napi\");\nconst requireNapi = createRequire(import.meta.url);\n\nfunction getDefaultExport(value: unknown): object | undefined {\n if (!value || typeof value !== \"object\" || !(\"default\" in value)) {\n return undefined;\n }\n\n const defaultExport = value.default;\n return defaultExport && typeof defaultExport === \"object\" ? defaultExport : undefined;\n}\n\nfunction normalizeNapiModule(mod: NapiModule): NapiModule {\n const defaultExport = getDefaultExport(mod);\n return defaultExport\n ? ({\n ...defaultExport,\n ...mod,\n } as NapiModule)\n : mod;\n}\n\nexport async function importNapiModule(): Promise<NapiModule> {\n return normalizeNapiModule((await import(\"@ox-content/napi\")) as NapiModule);\n}\n\nlet syncNapiModule: NapiModule | null | undefined;\n\nexport function importNapiModuleSync(): NapiModule {\n if (syncNapiModule) {\n return syncNapiModule;\n }\n\n if (syncNapiModule === null) {\n throw new Error(\n \"[ox-content] @ox-content/napi is required. Please ensure the NAPI module is built.\",\n );\n }\n\n try {\n const mod = requireNapi(\"@ox-content/napi\") as NapiModule;\n syncNapiModule = normalizeNapiModule(mod);\n return syncNapiModule;\n } catch {\n syncNapiModule = null;\n throw new Error(\n \"[ox-content] @ox-content/napi is required. Please ensure the NAPI module is built.\",\n );\n }\n}\n"],"mappings":";;AAGA,MAAM,eAAA,yBAAA,CAAA,eAAA,QAAA,MAAA,CAAA,cAAA,WAAA,CAAA,KAA4C;AAElD,SAAS,iBAAiB,OAAoC;CAC5D,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,EAAE,aAAa,QACxD;CAGF,MAAM,gBAAgB,MAAM;CAC5B,OAAO,iBAAiB,OAAO,kBAAkB,WAAW,gBAAgB,KAAA;;AAG9E,SAAS,oBAAoB,KAA6B;CACxD,MAAM,gBAAgB,iBAAiB,IAAI;CAC3C,OAAO,gBACF;EACC,GAAG;EACH,GAAG;EACJ,GACD;;AAGN,eAAsB,mBAAwC;CAC5D,OAAO,oBAAqB,MAAM,OAAO,oBAAmC;;AAG9E,IAAI;AAEJ,SAAgB,uBAAmC;CACjD,IAAI,gBACF,OAAO;CAGT,IAAI,mBAAmB,MACrB,MAAM,IAAI,MACR,qFACD;CAGH,IAAI;EAEF,iBAAiB,oBADL,YAAY,mBACgB,CAAC;EACzC,OAAO;SACD;EACN,iBAAiB;EACjB,MAAM,IAAI,MACR,qFACD"}
|