@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/index.cjs
CHANGED
|
@@ -1,37 +1,31 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
2
|
+
const require_vitepress = require("./vitepress.cjs");
|
|
3
3
|
const require_interop = require("./interop.cjs");
|
|
4
|
-
const require_napi = require("./napi.cjs");
|
|
5
|
-
const require_mermaid = require("./mermaid.cjs");
|
|
6
4
|
const require_tabs = require("./tabs.cjs");
|
|
7
|
-
require("./pm.cjs");
|
|
8
|
-
const require_youtube = require("./youtube.cjs");
|
|
9
|
-
require("./media.cjs");
|
|
10
|
-
const require_github = require("./github.cjs");
|
|
11
|
-
const require_ogp = require("./ogp.cjs");
|
|
12
|
-
const require_vitepress = require("./vitepress.cjs");
|
|
13
5
|
let path = require("path");
|
|
14
|
-
path =
|
|
6
|
+
path = require_vitepress.__toESM(path, 1);
|
|
15
7
|
let unified = require("unified");
|
|
16
8
|
let rehype_parse = require("rehype-parse");
|
|
17
|
-
rehype_parse =
|
|
9
|
+
rehype_parse = require_vitepress.__toESM(rehype_parse, 1);
|
|
18
10
|
let rehype_stringify = require("rehype-stringify");
|
|
19
|
-
rehype_stringify =
|
|
11
|
+
rehype_stringify = require_vitepress.__toESM(rehype_stringify, 1);
|
|
20
12
|
let shiki = require("shiki");
|
|
21
13
|
let node_module = require("node:module");
|
|
14
|
+
let node_fs = require("node:fs");
|
|
22
15
|
let node_path = require("node:path");
|
|
23
|
-
node_path =
|
|
16
|
+
node_path = require_vitepress.__toESM(node_path, 1);
|
|
24
17
|
let node_fs_promises = require("node:fs/promises");
|
|
25
|
-
node_fs_promises =
|
|
18
|
+
node_fs_promises = require_vitepress.__toESM(node_fs_promises, 1);
|
|
19
|
+
let node_buffer = require("node:buffer");
|
|
26
20
|
let node_os = require("node:os");
|
|
27
21
|
let node_util = require("node:util");
|
|
28
22
|
let node_child_process = require("node:child_process");
|
|
29
23
|
let fs_promises = require("fs/promises");
|
|
30
|
-
fs_promises =
|
|
24
|
+
fs_promises = require_vitepress.__toESM(fs_promises, 1);
|
|
31
25
|
let crypto = require("crypto");
|
|
32
|
-
crypto =
|
|
26
|
+
crypto = require_vitepress.__toESM(crypto, 1);
|
|
33
27
|
let fs = require("fs");
|
|
34
|
-
fs =
|
|
28
|
+
fs = require_vitepress.__toESM(fs, 1);
|
|
35
29
|
let glob = require("glob");
|
|
36
30
|
//#region src/markdown.ts
|
|
37
31
|
const DEFAULT_MARKDOWN_EXTENSIONS = [
|
|
@@ -118,8 +112,8 @@ function createMarkdownEnvironment(options) {
|
|
|
118
112
|
/**
|
|
119
113
|
* Syntax highlighting with Shiki via rehype.
|
|
120
114
|
*/
|
|
121
|
-
const rehypeParse$
|
|
122
|
-
const rehypeStringify$
|
|
115
|
+
const rehypeParse$3 = require_interop.interopDefault(rehype_parse.default);
|
|
116
|
+
const rehypeStringify$3 = require_interop.interopDefault(rehype_stringify.default);
|
|
123
117
|
const BUILTIN_LANGS = [
|
|
124
118
|
"javascript",
|
|
125
119
|
"typescript",
|
|
@@ -198,7 +192,7 @@ function rehypeShikiHighlight(options) {
|
|
|
198
192
|
lang,
|
|
199
193
|
theme: themeName
|
|
200
194
|
});
|
|
201
|
-
const parsed = (0, unified.unified)().use(rehypeParse$
|
|
195
|
+
const parsed = (0, unified.unified)().use(rehypeParse$3, { fragment: true }).parse(highlighted);
|
|
202
196
|
if (parsed.children[0]?.type === "element") {
|
|
203
197
|
const highlightedPre = parsed.children[0];
|
|
204
198
|
highlightedPre.properties ??= {};
|
|
@@ -220,13 +214,13 @@ function rehypeShikiHighlight(options) {
|
|
|
220
214
|
lang,
|
|
221
215
|
theme: themeName
|
|
222
216
|
});
|
|
223
|
-
const parsed = (0, unified.unified)().use(rehypeParse$
|
|
217
|
+
const parsed = (0, unified.unified)().use(rehypeParse$3, { fragment: true }).parse(highlighted);
|
|
224
218
|
if (parsed.children[0]?.type === "element") {
|
|
225
219
|
const highlightedCode = parsed.children[0].children.find((child) => child.type === "element" && child.tagName === "code");
|
|
226
220
|
if (highlightedCode) {
|
|
227
221
|
highlightedCode.properties ??= {};
|
|
228
222
|
const highlightedClasses = normalizeClassName(highlightedCode.properties.className);
|
|
229
|
-
highlightedCode.properties.className = [
|
|
223
|
+
highlightedCode.properties.className = [.../* @__PURE__ */ new Set([
|
|
230
224
|
...originalCodeClasses,
|
|
231
225
|
...highlightedClasses,
|
|
232
226
|
"shiki-inline"
|
|
@@ -276,42 +270,1433 @@ function normalizeClassName(className) {
|
|
|
276
270
|
* Apply syntax highlighting to HTML using Shiki.
|
|
277
271
|
*/
|
|
278
272
|
async function highlightCode(html, theme = "github-dark", langs = []) {
|
|
279
|
-
const result = await (0, unified.unified)().use(rehypeParse$
|
|
273
|
+
const result = await (0, unified.unified)().use(rehypeParse$3, { fragment: true }).use(rehypeShikiHighlight, {
|
|
280
274
|
theme,
|
|
281
275
|
langs
|
|
282
|
-
}).use(rehypeStringify$
|
|
276
|
+
}).use(rehypeStringify$3).process(html);
|
|
277
|
+
return String(result);
|
|
278
|
+
}
|
|
279
|
+
//#endregion
|
|
280
|
+
//#region src/plugins/mermaid.ts
|
|
281
|
+
/**
|
|
282
|
+
* Mermaid Plugin - Native Rust renderer via NAPI
|
|
283
|
+
*
|
|
284
|
+
* Renders mermaid code blocks to SVG using the native Rust renderer
|
|
285
|
+
* via NAPI. Delegates to the NAPI `transformMermaid` function which
|
|
286
|
+
* extracts mermaid code blocks from HTML and renders them using mmdc.
|
|
287
|
+
*/
|
|
288
|
+
var mermaid_exports = /* @__PURE__ */ require_vitepress.__exportAll({
|
|
289
|
+
mermaidClientScript: () => "",
|
|
290
|
+
transformMermaidStatic: () => transformMermaidStatic
|
|
291
|
+
});
|
|
292
|
+
/** Cached NAPI bindings */
|
|
293
|
+
let napiBindings$1 = null;
|
|
294
|
+
let napiLoadAttempted$1 = false;
|
|
295
|
+
async function loadNapi() {
|
|
296
|
+
if (napiLoadAttempted$1) return napiBindings$1;
|
|
297
|
+
napiLoadAttempted$1 = true;
|
|
298
|
+
try {
|
|
299
|
+
const binding = await require_vitepress.importNapiModule();
|
|
300
|
+
if (typeof binding.transformMermaid !== "function") {
|
|
301
|
+
napiBindings$1 = null;
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
napiBindings$1 = binding;
|
|
305
|
+
return binding;
|
|
306
|
+
} catch {
|
|
307
|
+
napiBindings$1 = null;
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
let cachedMmdcPath;
|
|
312
|
+
let missingMmdcWarned = false;
|
|
313
|
+
function resolveMmdcPath() {
|
|
314
|
+
if (cachedMmdcPath !== void 0) return cachedMmdcPath;
|
|
315
|
+
for (const resolver of createMmdcResolvers()) try {
|
|
316
|
+
const entry = resolver.resolve("@mermaid-js/mermaid-cli");
|
|
317
|
+
const cliPath = (0, node_path.join)((0, node_path.dirname)(entry), "cli.js");
|
|
318
|
+
if ((0, node_fs.existsSync)(cliPath)) {
|
|
319
|
+
cachedMmdcPath = cliPath;
|
|
320
|
+
return cachedMmdcPath;
|
|
321
|
+
}
|
|
322
|
+
} catch {}
|
|
323
|
+
const binPath = (0, node_path.join)(process.cwd(), "node_modules", ".bin", "mmdc");
|
|
324
|
+
if ((0, node_fs.existsSync)(binPath)) {
|
|
325
|
+
cachedMmdcPath = binPath;
|
|
326
|
+
return cachedMmdcPath;
|
|
327
|
+
}
|
|
328
|
+
cachedMmdcPath = null;
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
function createMmdcResolvers() {
|
|
332
|
+
const consumerRequire = (0, node_module.createRequire)((0, node_path.join)(process.cwd(), "noop.js"));
|
|
333
|
+
const resolvers = [consumerRequire];
|
|
334
|
+
try {
|
|
335
|
+
resolvers.push((0, node_module.createRequire)(consumerRequire.resolve("@ox-content/vite-plugin")));
|
|
336
|
+
} catch {}
|
|
337
|
+
return resolvers;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Transforms mermaid code blocks in HTML to rendered SVG diagrams.
|
|
341
|
+
* Uses the native Rust NAPI transformMermaid function.
|
|
342
|
+
*/
|
|
343
|
+
async function transformMermaidStatic(html, _options) {
|
|
344
|
+
const napi = await loadNapi();
|
|
345
|
+
if (!napi) return html;
|
|
346
|
+
const mmdcPath = resolveMmdcPath();
|
|
347
|
+
if (!mmdcPath) {
|
|
348
|
+
warnMissingMmdcOnce();
|
|
349
|
+
return html;
|
|
350
|
+
}
|
|
351
|
+
try {
|
|
352
|
+
const result = napi.transformMermaid(html, mmdcPath);
|
|
353
|
+
for (const error of result.errors) console.warn("[ox-content] Mermaid render error:", error);
|
|
354
|
+
return result.html;
|
|
355
|
+
} catch (err) {
|
|
356
|
+
console.warn("[ox-content] Mermaid transform error:", err);
|
|
357
|
+
return html;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function warnMissingMmdcOnce() {
|
|
361
|
+
if (missingMmdcWarned) return;
|
|
362
|
+
missingMmdcWarned = true;
|
|
363
|
+
console.warn("[ox-content] mmdc not found; skipping Mermaid rendering.");
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* @deprecated No longer used. Mermaid rendering is now done at build time via NAPI.
|
|
367
|
+
*/
|
|
368
|
+
const mermaidClientScript = "";
|
|
369
|
+
//#endregion
|
|
370
|
+
//#region src/plugins/pm.ts
|
|
371
|
+
/**
|
|
372
|
+
* Package Manager Tabs Plugin
|
|
373
|
+
*
|
|
374
|
+
* Transforms <pm>npm install …</pm> blocks into a tab group with one tab per
|
|
375
|
+
* package manager (npm/pnpm/yarn/bun). The single npm-style command is converted
|
|
376
|
+
* to each package manager's equivalent natively in Rust (`transformPmEmbeds` in
|
|
377
|
+
* @ox-content/napi), and the result reuses the same `ox-tabs` widget markup as
|
|
378
|
+
* the generic `<tabs>` plugin so styling and keyboard navigation are consistent.
|
|
379
|
+
*
|
|
380
|
+
* Syncing is opt-in (off by default): when enabled, the rendered group carries a
|
|
381
|
+
* `data-ox-tab-group="pkg-manager"` attribute so the client runtime can keep
|
|
382
|
+
* every package-manager group on the page in sync via localStorage.
|
|
383
|
+
*
|
|
384
|
+
* Package-manager groups share the tab-group counter with the `<tabs>` plugin so
|
|
385
|
+
* `data-group` ids (and the CSS produced by `generateTabsCSS`) stay unique.
|
|
386
|
+
*/
|
|
387
|
+
var pm_exports = /* @__PURE__ */ require_vitepress.__exportAll({ transformPm: () => transformPm });
|
|
388
|
+
/**
|
|
389
|
+
* Transform `<pm>` package-manager blocks in HTML into install tabs.
|
|
390
|
+
*
|
|
391
|
+
* @param html - Rendered HTML potentially containing `<pm>` blocks.
|
|
392
|
+
* @param options - Package-manager tab options (syncing is opt-in).
|
|
393
|
+
* @returns The rewritten HTML.
|
|
394
|
+
*/
|
|
395
|
+
async function transformPm(html, options) {
|
|
396
|
+
if (!/<pm[\s/>]/i.test(html)) return html;
|
|
397
|
+
const mod = await require_vitepress.importNapiModule();
|
|
398
|
+
const startGroup = require_tabs.getTabGroupCounter();
|
|
399
|
+
const result = mod.transformPmEmbeds(html, startGroup, { sync: options?.sync ?? false });
|
|
400
|
+
require_tabs.setTabGroupCounter(startGroup + result.groupCount);
|
|
401
|
+
return result.html;
|
|
402
|
+
}
|
|
403
|
+
//#endregion
|
|
404
|
+
//#region src/plugins/youtube.ts
|
|
405
|
+
/**
|
|
406
|
+
* YouTube Plugin - Privacy-enhanced iframe embedding
|
|
407
|
+
*
|
|
408
|
+
* Transforms <YouTube> components into responsive iframe embeds using
|
|
409
|
+
* youtube-nocookie.com for enhanced privacy.
|
|
410
|
+
*
|
|
411
|
+
* The HTML rewrite is performed in Rust (`transformYoutubeEmbeds` in
|
|
412
|
+
* @ox-content/napi), replacing the previous rehype parse/stringify
|
|
413
|
+
* round-trip. This module keeps the public TS surface and a cheap marker
|
|
414
|
+
* check so pages without a `<youtube>` element never cross the NAPI boundary.
|
|
415
|
+
*/
|
|
416
|
+
var youtube_exports = /* @__PURE__ */ require_vitepress.__exportAll({
|
|
417
|
+
extractVideoId: () => extractVideoId,
|
|
418
|
+
transformYouTube: () => transformYouTube
|
|
419
|
+
});
|
|
420
|
+
/**
|
|
421
|
+
* Extract YouTube video ID from various URL formats.
|
|
422
|
+
*/
|
|
423
|
+
function extractVideoId(input) {
|
|
424
|
+
if (/^[a-zA-Z0-9_-]{11}$/.test(input)) return input;
|
|
425
|
+
for (const pattern of [/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/|youtube\.com\/v\/)([a-zA-Z0-9_-]{11})/, /youtube\.com\/shorts\/([a-zA-Z0-9_-]{11})/]) {
|
|
426
|
+
const match = input.match(pattern);
|
|
427
|
+
if (match) return match[1];
|
|
428
|
+
}
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Transform YouTube components in HTML.
|
|
433
|
+
*/
|
|
434
|
+
async function transformYouTube(html, options) {
|
|
435
|
+
if (!/<youtube/i.test(html)) return html;
|
|
436
|
+
return (await require_vitepress.importNapiModule()).transformYoutubeEmbeds(html, options);
|
|
437
|
+
}
|
|
438
|
+
//#endregion
|
|
439
|
+
//#region src/plugins/twitter/url.ts
|
|
440
|
+
const STATUS_PATH = /^\/(?:[^/]+|i\/web)\/status\/(\d+)(?:\/.*)?$/;
|
|
441
|
+
function createSyndicationToken(id) {
|
|
442
|
+
return (Number(id) / 0x38d7ea4c68000 * Math.PI).toString(36).replaceAll(/(0+|\.)/g, "");
|
|
443
|
+
}
|
|
444
|
+
function parseTweetReference(value) {
|
|
445
|
+
const trimmed = value.trim();
|
|
446
|
+
if (/^\d+$/.test(trimmed)) return {
|
|
447
|
+
id: trimmed,
|
|
448
|
+
url: `https://x.com/i/web/status/${trimmed}`
|
|
449
|
+
};
|
|
450
|
+
try {
|
|
451
|
+
const url = new URL(trimmed);
|
|
452
|
+
const hostname = url.hostname.toLowerCase().replace(/^(?:www\.|mobile\.)/, "");
|
|
453
|
+
if (url.protocol !== "https:" || hostname !== "x.com" && hostname !== "twitter.com") return null;
|
|
454
|
+
const match = url.pathname.match(STATUS_PATH);
|
|
455
|
+
if (!match) return null;
|
|
456
|
+
const screenName = url.pathname.startsWith("/i/web/status/") ? "i/web" : url.pathname.split("/")[1];
|
|
457
|
+
return {
|
|
458
|
+
id: match[1],
|
|
459
|
+
url: `https://x.com/${screenName}/status/${match[1]}`
|
|
460
|
+
};
|
|
461
|
+
} catch {
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
function referenceFromAttributes(attributes) {
|
|
466
|
+
const values = /* @__PURE__ */ new Map();
|
|
467
|
+
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] ?? "");
|
|
468
|
+
return parseTweetReference(values.get("url") ?? values.get("href") ?? values.get("id") ?? "");
|
|
469
|
+
}
|
|
470
|
+
//#endregion
|
|
471
|
+
//#region src/plugins/twitter/fetch.ts
|
|
472
|
+
const tweetCache = /* @__PURE__ */ new Map();
|
|
473
|
+
async function fetchTweetData(id, options) {
|
|
474
|
+
const key = `${id}-${sanitizeSegment(options.lang)}`;
|
|
475
|
+
if (options.cache) {
|
|
476
|
+
const memory = tweetCache.get(key);
|
|
477
|
+
if (memory) return memory;
|
|
478
|
+
const disk = await readCachedTweet(key, options.cacheDir);
|
|
479
|
+
if (disk) {
|
|
480
|
+
tweetCache.set(key, disk);
|
|
481
|
+
return disk;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
const controller = new AbortController();
|
|
485
|
+
const timeout = setTimeout(() => controller.abort(), options.timeout);
|
|
486
|
+
const endpoint = new URL("https://cdn.syndication.twimg.com/tweet-result");
|
|
487
|
+
endpoint.searchParams.set("id", id);
|
|
488
|
+
endpoint.searchParams.set("lang", options.lang);
|
|
489
|
+
endpoint.searchParams.set("token", createSyndicationToken(id));
|
|
490
|
+
try {
|
|
491
|
+
const response = await fetch(endpoint, {
|
|
492
|
+
headers: { Accept: "application/json" },
|
|
493
|
+
signal: controller.signal
|
|
494
|
+
});
|
|
495
|
+
if (!response.ok) return null;
|
|
496
|
+
const data = await response.json();
|
|
497
|
+
if (!isTweetData(data)) return null;
|
|
498
|
+
if (options.cache) {
|
|
499
|
+
tweetCache.set(key, data);
|
|
500
|
+
await writeCachedTweet(key, data, options.cacheDir);
|
|
501
|
+
}
|
|
502
|
+
return data;
|
|
503
|
+
} catch {
|
|
504
|
+
return null;
|
|
505
|
+
} finally {
|
|
506
|
+
clearTimeout(timeout);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
async function materializeTweetAssets(id, data, options) {
|
|
510
|
+
const assets = { media: [] };
|
|
511
|
+
const avatarUrl = data.user.profile_image_url_https?.replace(/_normal(?=\.[^.]+$)/, "_bigger");
|
|
512
|
+
if (avatarUrl) assets.avatar = await downloadAsset(avatarUrl, `${id}-avatar`, options);
|
|
513
|
+
const media = data.mediaDetails ?? data.entities?.media ?? [];
|
|
514
|
+
for (const [index, item] of media.entries()) {
|
|
515
|
+
if (item.type && item.type !== "photo") continue;
|
|
516
|
+
if (!item.media_url_https) continue;
|
|
517
|
+
const src = await downloadAsset(item.media_url_https, `${id}-media-${index + 1}`, options);
|
|
518
|
+
if (src) assets.media.push(assetRecord(src, item));
|
|
519
|
+
}
|
|
520
|
+
return assets;
|
|
521
|
+
}
|
|
522
|
+
async function downloadAsset(source, basename, options) {
|
|
523
|
+
let url;
|
|
524
|
+
try {
|
|
525
|
+
url = new URL(source);
|
|
526
|
+
} catch {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
if (url.protocol !== "https:" || url.hostname.toLowerCase() !== "pbs.twimg.com") return;
|
|
530
|
+
const filename = `${basename}${extensionFromUrl(url)}`;
|
|
531
|
+
const output = node_path.default.join(options.mediaOutputDir, filename);
|
|
532
|
+
try {
|
|
533
|
+
await (0, node_fs_promises.access)(output);
|
|
534
|
+
return joinPublicPath(options.mediaPublicPath, filename);
|
|
535
|
+
} catch {}
|
|
536
|
+
try {
|
|
537
|
+
const response = await fetch(url, { headers: { Accept: "image/*" } });
|
|
538
|
+
if (!response.ok) return void 0;
|
|
539
|
+
await (0, node_fs_promises.mkdir)(options.mediaOutputDir, { recursive: true });
|
|
540
|
+
await (0, node_fs_promises.writeFile)(output, new Uint8Array(await response.arrayBuffer()));
|
|
541
|
+
return joinPublicPath(options.mediaPublicPath, filename);
|
|
542
|
+
} catch {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
async function readCachedTweet(key, directory) {
|
|
547
|
+
try {
|
|
548
|
+
const data = JSON.parse(await (0, node_fs_promises.readFile)(node_path.default.join(directory, `${key}.json`), "utf8"));
|
|
549
|
+
return isTweetData(data) ? data : null;
|
|
550
|
+
} catch {
|
|
551
|
+
return null;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
async function writeCachedTweet(key, data, directory) {
|
|
555
|
+
try {
|
|
556
|
+
await (0, node_fs_promises.mkdir)(directory, { recursive: true });
|
|
557
|
+
await (0, node_fs_promises.writeFile)(node_path.default.join(directory, `${key}.json`), `${JSON.stringify(data)}\n`);
|
|
558
|
+
} catch {}
|
|
559
|
+
}
|
|
560
|
+
function isTweetData(data) {
|
|
561
|
+
if (!data || typeof data !== "object") return false;
|
|
562
|
+
const value = data;
|
|
563
|
+
return typeof value.text === "string" && Boolean(value.user) && typeof value.user?.name === "string" && typeof value.user.screen_name === "string";
|
|
564
|
+
}
|
|
565
|
+
function extensionFromUrl(url) {
|
|
566
|
+
const match = url.pathname.match(/\.(jpe?g|png|webp|gif)$/i);
|
|
567
|
+
return match ? `.${match[1].toLowerCase().replace("jpeg", "jpg")}` : ".jpg";
|
|
568
|
+
}
|
|
569
|
+
function joinPublicPath(prefix, filename) {
|
|
570
|
+
return `${prefix.replace(/\/$/, "")}/${filename}`;
|
|
571
|
+
}
|
|
572
|
+
function sanitizeSegment(value) {
|
|
573
|
+
return value.replaceAll(/[^a-zA-Z0-9_-]/g, "-");
|
|
574
|
+
}
|
|
575
|
+
function assetRecord(src, media) {
|
|
576
|
+
return {
|
|
577
|
+
src,
|
|
578
|
+
alt: media.ext_alt_text,
|
|
579
|
+
width: media.original_info?.width,
|
|
580
|
+
height: media.original_info?.height
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
//#endregion
|
|
584
|
+
//#region src/plugins/twitter/render.ts
|
|
585
|
+
function renderFetchedTweet(permalink, data, assets, options) {
|
|
586
|
+
const profile = `https://x.com/${encodeURIComponent(data.user.screen_name)}`;
|
|
587
|
+
const author = escapeHtml$3(data.user.name);
|
|
588
|
+
const handle = escapeHtml$3(data.user.screen_name);
|
|
589
|
+
const avatar = assets.avatar ? `<img class="ox-tweet__avatar" src="${escapeAttribute(assets.avatar)}" alt="" width="48" height="48" loading="lazy" decoding="async">` : "";
|
|
590
|
+
const media = renderMedia(assets);
|
|
591
|
+
const footer = renderFooter(permalink, data.created_at, options.lang);
|
|
592
|
+
return [
|
|
593
|
+
"<figure class=\"ox-tweet ox-tweet--fetched\">",
|
|
594
|
+
"<header class=\"ox-tweet__header\">",
|
|
595
|
+
`<a class="ox-tweet__profile" href="${escapeAttribute(profile)}" target="_blank" rel="noopener noreferrer">`,
|
|
596
|
+
avatar,
|
|
597
|
+
`<span class="ox-tweet__author-name">${author}</span>`,
|
|
598
|
+
`<span class="ox-tweet__author-handle">@${handle}</span>`,
|
|
599
|
+
"</a></header>",
|
|
600
|
+
`<div class="ox-tweet__body">${renderTweetText(data)}</div>`,
|
|
601
|
+
media,
|
|
602
|
+
footer,
|
|
603
|
+
"</figure>"
|
|
604
|
+
].join("");
|
|
605
|
+
}
|
|
606
|
+
function renderTweetText(data) {
|
|
607
|
+
const [start, end] = data.display_text_range ?? [0, data.text.length];
|
|
608
|
+
const entities = collectEntities(data).filter((entity) => validRange(entity.indices, start, end)).sort((left, right) => left.indices[0] - right.indices[0]);
|
|
609
|
+
let cursor = start;
|
|
610
|
+
let output = "";
|
|
611
|
+
for (const entity of entities) {
|
|
612
|
+
const [entityStart, entityEnd] = entity.indices;
|
|
613
|
+
if (entityStart < cursor) continue;
|
|
614
|
+
output += escapeText(data.text.slice(cursor, entityStart));
|
|
615
|
+
if (entity.kind === "url") {
|
|
616
|
+
const href = entity.expanded_url ?? entity.url;
|
|
617
|
+
const label = entity.display_url ?? href;
|
|
618
|
+
output += `<a href="${escapeAttribute(href)}" target="_blank" rel="noopener noreferrer">${escapeHtml$3(label)}</a>`;
|
|
619
|
+
}
|
|
620
|
+
cursor = entityEnd;
|
|
621
|
+
}
|
|
622
|
+
output += escapeText(data.text.slice(cursor, end));
|
|
623
|
+
return output.trim();
|
|
624
|
+
}
|
|
625
|
+
function collectEntities(data) {
|
|
626
|
+
return [...(data.entities?.urls ?? []).map((entity) => ({
|
|
627
|
+
...entity,
|
|
628
|
+
kind: "url"
|
|
629
|
+
})), ...(data.entities?.media ?? []).map((entity) => ({
|
|
630
|
+
...entity,
|
|
631
|
+
kind: "media"
|
|
632
|
+
}))];
|
|
633
|
+
}
|
|
634
|
+
function validRange(indices, start, end) {
|
|
635
|
+
return Boolean(indices && indices[0] >= start && indices[1] <= end && indices[0] < indices[1]);
|
|
636
|
+
}
|
|
637
|
+
function renderMedia(assets) {
|
|
638
|
+
if (assets.media.length === 0) return "";
|
|
639
|
+
const images = assets.media.map((item) => {
|
|
640
|
+
const size = [item.width ? ` width="${item.width}"` : "", item.height ? ` height="${item.height}"` : ""].join("");
|
|
641
|
+
return `<img class="ox-tweet__media-item" src="${escapeAttribute(item.src)}" alt="${escapeAttribute(item.alt ?? "")}"${size} loading="lazy" decoding="async">`;
|
|
642
|
+
}).join("");
|
|
643
|
+
return `<div class="ox-tweet__media" data-count="${assets.media.length}">${images}</div>`;
|
|
644
|
+
}
|
|
645
|
+
function renderFooter(permalink, createdAt, lang) {
|
|
646
|
+
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>`;
|
|
647
|
+
const date = new Date(createdAt);
|
|
648
|
+
if (Number.isNaN(date.valueOf())) return renderFooter(permalink, void 0, lang);
|
|
649
|
+
const iso = date.toISOString();
|
|
650
|
+
let label;
|
|
651
|
+
try {
|
|
652
|
+
label = new Intl.DateTimeFormat(lang, {
|
|
653
|
+
dateStyle: "medium",
|
|
654
|
+
timeZone: "UTC"
|
|
655
|
+
}).format(date);
|
|
656
|
+
} catch {
|
|
657
|
+
label = new Intl.DateTimeFormat("en", {
|
|
658
|
+
dateStyle: "medium",
|
|
659
|
+
timeZone: "UTC"
|
|
660
|
+
}).format(date);
|
|
661
|
+
}
|
|
662
|
+
return `<footer class="ox-tweet__footer"><a class="ox-tweet__permalink" href="${escapeAttribute(permalink)}" target="_blank" rel="noopener noreferrer"><time datetime="${iso}">${escapeHtml$3(label)}</time></a></footer>`;
|
|
663
|
+
}
|
|
664
|
+
function escapeText(value) {
|
|
665
|
+
return escapeHtml$3(value).replaceAll("\n", "<br>");
|
|
666
|
+
}
|
|
667
|
+
function escapeAttribute(value) {
|
|
668
|
+
return escapeHtml$3(value).replaceAll("`", "`");
|
|
669
|
+
}
|
|
670
|
+
function escapeHtml$3(value) {
|
|
671
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
672
|
+
}
|
|
673
|
+
//#endregion
|
|
674
|
+
//#region src/plugins/twitter/transform.ts
|
|
675
|
+
const TWEET_ELEMENT = /<(tweet|xpost)\b([^>]*?)(?:\/\s*>|>[\s\S]*?<\/\1\s*>)/gi;
|
|
676
|
+
function resolveTwitterEmbedOptions$1(options) {
|
|
677
|
+
return {
|
|
678
|
+
fetch: options.fetch ?? false,
|
|
679
|
+
lang: options.lang ?? "en",
|
|
680
|
+
timeout: options.timeout ?? 1e4,
|
|
681
|
+
cache: options.cache ?? true,
|
|
682
|
+
cacheDir: node_path.default.resolve(options.cacheDir ?? ".cache/ox-content/twitter"),
|
|
683
|
+
mediaOutputDir: node_path.default.resolve(options.mediaOutputDir ?? "public/ox-content/twitter"),
|
|
684
|
+
mediaPublicPath: options.mediaPublicPath ?? "/ox-content/twitter"
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
async function transformFetchedTweets(html, options) {
|
|
688
|
+
const resolved = resolveTwitterEmbedOptions$1(options);
|
|
689
|
+
if (!resolved.fetch) return html;
|
|
690
|
+
let output = "";
|
|
691
|
+
let cursor = 0;
|
|
692
|
+
for (const match of html.matchAll(TWEET_ELEMENT)) {
|
|
693
|
+
const index = match.index ?? 0;
|
|
694
|
+
output += html.slice(cursor, index);
|
|
695
|
+
const reference = referenceFromAttributes(match[2]);
|
|
696
|
+
if (!reference) {
|
|
697
|
+
output += match[0];
|
|
698
|
+
cursor = index + match[0].length;
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
const data = await fetchTweetData(reference.id, resolved);
|
|
702
|
+
if (!data) {
|
|
703
|
+
output += match[0];
|
|
704
|
+
cursor = index + match[0].length;
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
const assets = await materializeTweetAssets(reference.id, data, resolved);
|
|
708
|
+
output += renderFetchedTweet(reference.url, data, assets, resolved);
|
|
709
|
+
cursor = index + match[0].length;
|
|
710
|
+
}
|
|
711
|
+
return output + html.slice(cursor);
|
|
712
|
+
}
|
|
713
|
+
//#endregion
|
|
714
|
+
//#region src/plugins/media.ts
|
|
715
|
+
var media_exports = /* @__PURE__ */ require_vitepress.__exportAll({ transformMediaEmbeds: () => transformMediaEmbeds });
|
|
716
|
+
async function transformMediaEmbeds(html, options) {
|
|
717
|
+
if (!hasEnabledMediaEmbed(options) || !hasMediaMarker(html)) return html;
|
|
718
|
+
let result = html;
|
|
719
|
+
if (typeof options.twitter === "object") result = await transformFetchedTweets(result, options.twitter);
|
|
720
|
+
if (!hasMediaMarker(result)) return result;
|
|
721
|
+
return (await require_vitepress.importNapiModule()).transformMediaEmbeds(result, {
|
|
722
|
+
spotify: options.spotify,
|
|
723
|
+
stackBlitz: options.stackBlitz,
|
|
724
|
+
twitter: Boolean(options.twitter),
|
|
725
|
+
bluesky: options.bluesky,
|
|
726
|
+
webContainer: options.webContainer
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
function hasEnabledMediaEmbed(options) {
|
|
730
|
+
return Boolean(options.spotify || options.stackBlitz || options.twitter || options.bluesky || options.webContainer);
|
|
731
|
+
}
|
|
732
|
+
function hasMediaMarker(html) {
|
|
733
|
+
return /<(spotify|stackblitz|tweet|xpost|bluesky|webcontainer)[\s/>]/i.test(html);
|
|
734
|
+
}
|
|
735
|
+
//#endregion
|
|
736
|
+
//#region src/plugins/github/validation.ts
|
|
737
|
+
const GITHUB_REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
738
|
+
function hasControlChar(value) {
|
|
739
|
+
for (let index = 0; index < value.length; index++) {
|
|
740
|
+
const code = value.charCodeAt(index);
|
|
741
|
+
if (code <= 31 || code === 127) return true;
|
|
742
|
+
}
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
745
|
+
function isSafeGitHubRepo(repo) {
|
|
746
|
+
return GITHUB_REPO_RE.test(repo) && !repo.split("/").some((part) => part === "." || part === "..");
|
|
747
|
+
}
|
|
748
|
+
function isSafeGitHubRef(ref) {
|
|
749
|
+
return Boolean(ref) && !hasControlChar(ref) && !hasUnsafePathSegment(ref);
|
|
750
|
+
}
|
|
751
|
+
function isSafeGitHubPath(path) {
|
|
752
|
+
return Boolean(path) && !hasControlChar(path) && !hasUnsafePathSegment(path);
|
|
753
|
+
}
|
|
754
|
+
function hasUnsafePathSegment(value) {
|
|
755
|
+
return value.split("/").some((part) => !part || part === "." || part === ".." || part.includes("\\"));
|
|
756
|
+
}
|
|
757
|
+
function encodePath(path) {
|
|
758
|
+
return path.split("/").map(encodeURIComponent).join("/");
|
|
759
|
+
}
|
|
760
|
+
//#endregion
|
|
761
|
+
//#region src/plugins/github/source.ts
|
|
762
|
+
const EXTENSION_LANGUAGE_MAP = /* @__PURE__ */ new Map([
|
|
763
|
+
["cjs", "javascript"],
|
|
764
|
+
["css", "css"],
|
|
765
|
+
["go", "go"],
|
|
766
|
+
["html", "html"],
|
|
767
|
+
["js", "javascript"],
|
|
768
|
+
["json", "json"],
|
|
769
|
+
["jsx", "jsx"],
|
|
770
|
+
["md", "markdown"],
|
|
771
|
+
["mdx", "mdx"],
|
|
772
|
+
["mjs", "javascript"],
|
|
773
|
+
["py", "python"],
|
|
774
|
+
["rb", "ruby"],
|
|
775
|
+
["rs", "rust"],
|
|
776
|
+
["sh", "shell"],
|
|
777
|
+
["svelte", "svelte"],
|
|
778
|
+
["toml", "toml"],
|
|
779
|
+
["ts", "typescript"],
|
|
780
|
+
["tsx", "tsx"],
|
|
781
|
+
["vue", "vue"],
|
|
782
|
+
["yaml", "yaml"],
|
|
783
|
+
["yml", "yaml"]
|
|
784
|
+
]);
|
|
785
|
+
function sourceKey(source) {
|
|
786
|
+
return `${source.repo}@${source.ref}:${source.path}`;
|
|
787
|
+
}
|
|
788
|
+
function formatLineRange(lines) {
|
|
789
|
+
return lines.start === lines.end ? `L${lines.start}` : `L${lines.start}-L${lines.end}`;
|
|
790
|
+
}
|
|
791
|
+
function parseGitHubLineRange(value) {
|
|
792
|
+
if (!value) return void 0;
|
|
793
|
+
const match = value.trim().match(/^#?L?(\d+)(?:-L?(\d+))?$/i);
|
|
794
|
+
if (!match) return void 0;
|
|
795
|
+
const start = Number.parseInt(match[1], 10);
|
|
796
|
+
const end = match[2] ? Number.parseInt(match[2], 10) : start;
|
|
797
|
+
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 1 || end < start) return;
|
|
798
|
+
return {
|
|
799
|
+
start,
|
|
800
|
+
end
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
function createGitHubPermalink(source) {
|
|
804
|
+
const fragment = source.lines ? `#${formatLineRange(source.lines)}` : "";
|
|
805
|
+
return `https://github.com/${source.repo}/blob/${encodeURIComponent(source.ref)}/${encodePath(source.path)}${fragment}`;
|
|
806
|
+
}
|
|
807
|
+
function parseGitHubPermalink(value) {
|
|
808
|
+
let url;
|
|
809
|
+
try {
|
|
810
|
+
url = new URL(value);
|
|
811
|
+
} catch {
|
|
812
|
+
return null;
|
|
813
|
+
}
|
|
814
|
+
if (url.protocol !== "https:" || url.hostname !== "github.com") return null;
|
|
815
|
+
let parts;
|
|
816
|
+
try {
|
|
817
|
+
parts = url.pathname.split("/").filter(Boolean).map((part) => decodeURIComponent(part));
|
|
818
|
+
} catch {
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
if (parts.length < 5 || parts[2] !== "blob") return null;
|
|
822
|
+
const repo = `${parts[0]}/${parts[1]}`;
|
|
823
|
+
const ref = parts[3];
|
|
824
|
+
const path = parts.slice(4).join("/");
|
|
825
|
+
if (!isSafeGitHubRepo(repo) || !isSafeGitHubRef(ref) || !isSafeGitHubPath(path)) return null;
|
|
826
|
+
const source = {
|
|
827
|
+
repo,
|
|
828
|
+
ref,
|
|
829
|
+
path,
|
|
830
|
+
lines: parseGitHubLineRange(url.hash)
|
|
831
|
+
};
|
|
832
|
+
return {
|
|
833
|
+
...source,
|
|
834
|
+
permalink: createGitHubPermalink(source)
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
function inferLanguage(path) {
|
|
838
|
+
const fileName = path.split("/").at(-1)?.toLowerCase() ?? "";
|
|
839
|
+
if (fileName === "dockerfile") return "dockerfile";
|
|
840
|
+
if (fileName === "makefile") return "makefile";
|
|
841
|
+
const extension = fileName.includes(".") ? fileName.split(".").at(-1) : void 0;
|
|
842
|
+
return extension ? EXTENSION_LANGUAGE_MAP.get(extension) ?? extension : null;
|
|
843
|
+
}
|
|
844
|
+
//#endregion
|
|
845
|
+
//#region src/plugins/github/types.ts
|
|
846
|
+
const defaultOptions$1 = {
|
|
847
|
+
token: "",
|
|
848
|
+
cache: true,
|
|
849
|
+
cacheTTL: 36e5,
|
|
850
|
+
maxSourceBytes: 2e5,
|
|
851
|
+
maxSourceLines: 120
|
|
852
|
+
};
|
|
853
|
+
//#endregion
|
|
854
|
+
//#region src/plugins/github/api.ts
|
|
855
|
+
const repoCache = /* @__PURE__ */ new Map();
|
|
856
|
+
const sourceCache = /* @__PURE__ */ new Map();
|
|
857
|
+
function githubHeaders(options) {
|
|
858
|
+
const headers = {
|
|
859
|
+
Accept: "application/vnd.github.v3+json",
|
|
860
|
+
"User-Agent": "ox-content-github-plugin"
|
|
861
|
+
};
|
|
862
|
+
if (options.token) headers.Authorization = `Bearer ${options.token}`;
|
|
863
|
+
return headers;
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Fetch repository data from GitHub API.
|
|
867
|
+
*/
|
|
868
|
+
async function fetchRepoData(repo, options) {
|
|
869
|
+
if (!isSafeGitHubRepo(repo)) return null;
|
|
870
|
+
if (options.cache) {
|
|
871
|
+
const cached = repoCache.get(repo);
|
|
872
|
+
if (cached && Date.now() - cached.timestamp < options.cacheTTL) return cached.data;
|
|
873
|
+
}
|
|
874
|
+
try {
|
|
875
|
+
const response = await fetch(`https://api.github.com/repos/${repo}`, { headers: githubHeaders(options) });
|
|
876
|
+
if (!response.ok) {
|
|
877
|
+
console.warn(`Failed to fetch GitHub repo ${repo}: ${response.status}`);
|
|
878
|
+
return null;
|
|
879
|
+
}
|
|
880
|
+
const data = await response.json();
|
|
881
|
+
if (options.cache) repoCache.set(repo, {
|
|
882
|
+
data,
|
|
883
|
+
timestamp: Date.now()
|
|
884
|
+
});
|
|
885
|
+
return data;
|
|
886
|
+
} catch (error) {
|
|
887
|
+
console.warn(`Error fetching GitHub repo ${repo}:`, error);
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Fetch source file data from GitHub API.
|
|
893
|
+
*/
|
|
894
|
+
async function fetchGitHubSource(source, options) {
|
|
895
|
+
if (!isSafeGitHubRepo(source.repo) || !isSafeGitHubRef(source.ref) || !isSafeGitHubPath(source.path)) return null;
|
|
896
|
+
const key = sourceKey(source);
|
|
897
|
+
if (options.cache) {
|
|
898
|
+
const cached = sourceCache.get(key);
|
|
899
|
+
if (cached && Date.now() - cached.timestamp < options.cacheTTL) return cached.data;
|
|
900
|
+
}
|
|
901
|
+
try {
|
|
902
|
+
const apiUrl = `https://api.github.com/repos/${source.repo}/contents/${encodePath(source.path)}?ref=${encodeURIComponent(source.ref)}`;
|
|
903
|
+
const response = await fetch(apiUrl, { headers: githubHeaders(options) });
|
|
904
|
+
if (!response.ok) {
|
|
905
|
+
console.warn(`Failed to fetch GitHub source ${source.permalink}: ${response.status}`);
|
|
906
|
+
return null;
|
|
907
|
+
}
|
|
908
|
+
const data = await response.json();
|
|
909
|
+
if (data.type !== "file" || data.encoding !== "base64" || !data.content || (data.size ?? 0) > options.maxSourceBytes) return null;
|
|
910
|
+
const content = node_buffer.Buffer.from(data.content.replace(/\s/g, ""), "base64").toString("utf8");
|
|
911
|
+
if (node_buffer.Buffer.byteLength(content) > options.maxSourceBytes) return null;
|
|
912
|
+
const sourceData = {
|
|
913
|
+
repo: source.repo,
|
|
914
|
+
ref: source.ref,
|
|
915
|
+
path: source.path,
|
|
916
|
+
permalink: source.permalink,
|
|
917
|
+
content,
|
|
918
|
+
size: data.size ?? node_buffer.Buffer.byteLength(content),
|
|
919
|
+
html_url: data.html_url ?? source.permalink,
|
|
920
|
+
language: inferLanguage(source.path)
|
|
921
|
+
};
|
|
922
|
+
if (options.cache) sourceCache.set(key, {
|
|
923
|
+
data: sourceData,
|
|
924
|
+
timestamp: Date.now()
|
|
925
|
+
});
|
|
926
|
+
return sourceData;
|
|
927
|
+
} catch (error) {
|
|
928
|
+
console.warn(`Error fetching GitHub source ${source.permalink}:`, error);
|
|
929
|
+
return null;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* Pre-fetch all GitHub repos data.
|
|
934
|
+
*/
|
|
935
|
+
async function prefetchGitHubRepos(repos, options) {
|
|
936
|
+
const mergedOptions = {
|
|
937
|
+
...defaultOptions$1,
|
|
938
|
+
...options
|
|
939
|
+
};
|
|
940
|
+
const results = /* @__PURE__ */ new Map();
|
|
941
|
+
await Promise.all(Array.from(new Set(repos)).map(async (repo) => {
|
|
942
|
+
const data = await fetchRepoData(repo, mergedOptions);
|
|
943
|
+
results.set(repo, data);
|
|
944
|
+
}));
|
|
945
|
+
return results;
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Pre-fetch all GitHub source files.
|
|
949
|
+
*/
|
|
950
|
+
async function prefetchGitHubSources(sources, options) {
|
|
951
|
+
const mergedOptions = {
|
|
952
|
+
...defaultOptions$1,
|
|
953
|
+
...options
|
|
954
|
+
};
|
|
955
|
+
const results = /* @__PURE__ */ new Map();
|
|
956
|
+
const uniqueSources = Array.from(new Map(sources.map((source) => [sourceKey(source), source])).values());
|
|
957
|
+
await Promise.all(uniqueSources.map(async (source) => {
|
|
958
|
+
const data = await fetchGitHubSource(source, mergedOptions);
|
|
959
|
+
results.set(sourceKey(source), data);
|
|
960
|
+
}));
|
|
961
|
+
return results;
|
|
962
|
+
}
|
|
963
|
+
//#endregion
|
|
964
|
+
//#region src/plugins/github/attributes.ts
|
|
965
|
+
const GITHUB_COMPONENT_RE = /<github\b([^>]*)>/gi;
|
|
966
|
+
const ATTRIBUTE_RE = /([:\w-]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>/]+)))?/g;
|
|
967
|
+
/**
|
|
968
|
+
* Collect all GitHub repos from HTML for pre-fetching.
|
|
969
|
+
*/
|
|
970
|
+
async function collectGitHubRepos(html) {
|
|
971
|
+
const repos = [];
|
|
972
|
+
GITHUB_COMPONENT_RE.lastIndex = 0;
|
|
973
|
+
let match;
|
|
974
|
+
while ((match = GITHUB_COMPONENT_RE.exec(html)) !== null) {
|
|
975
|
+
const attrs = parseAttributes(match[1]);
|
|
976
|
+
if (attrs.path || attrs.file || attrs.permalink || attrs.url || attrs.href) continue;
|
|
977
|
+
const repo = attrs.repo;
|
|
978
|
+
if (repo && isSafeGitHubRepo(repo)) repos.push(repo);
|
|
979
|
+
}
|
|
980
|
+
return repos;
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Collect all GitHub source references from HTML for pre-fetching.
|
|
984
|
+
*/
|
|
985
|
+
async function collectGitHubSources(html) {
|
|
986
|
+
const sources = [];
|
|
987
|
+
GITHUB_COMPONENT_RE.lastIndex = 0;
|
|
988
|
+
let match;
|
|
989
|
+
while ((match = GITHUB_COMPONENT_RE.exec(html)) !== null) {
|
|
990
|
+
const source = sourceRefFromAttributes(parseAttributes(match[1]));
|
|
991
|
+
if (source) sources.push(source);
|
|
992
|
+
}
|
|
993
|
+
return sources;
|
|
994
|
+
}
|
|
995
|
+
function parseAttributes(raw) {
|
|
996
|
+
const attrs = {};
|
|
997
|
+
ATTRIBUTE_RE.lastIndex = 0;
|
|
998
|
+
let match;
|
|
999
|
+
while ((match = ATTRIBUTE_RE.exec(raw)) !== null) attrs[match[1].toLowerCase()] = match[2] ?? match[3] ?? match[4] ?? "";
|
|
1000
|
+
return attrs;
|
|
1001
|
+
}
|
|
1002
|
+
function attributesFromElement(el) {
|
|
1003
|
+
const attrs = {};
|
|
1004
|
+
for (const name of [
|
|
1005
|
+
"permalink",
|
|
1006
|
+
"url",
|
|
1007
|
+
"href",
|
|
1008
|
+
"repo",
|
|
1009
|
+
"path",
|
|
1010
|
+
"file",
|
|
1011
|
+
"ref",
|
|
1012
|
+
"sha",
|
|
1013
|
+
"branch",
|
|
1014
|
+
"loc",
|
|
1015
|
+
"lines",
|
|
1016
|
+
"line"
|
|
1017
|
+
]) {
|
|
1018
|
+
const value = getAttribute$2(el, name);
|
|
1019
|
+
if (value !== void 0) attrs[name] = value;
|
|
1020
|
+
}
|
|
1021
|
+
return attrs;
|
|
1022
|
+
}
|
|
1023
|
+
function getAttribute$2(el, name) {
|
|
1024
|
+
const value = el.properties?.[name];
|
|
1025
|
+
if (typeof value === "string") return value;
|
|
1026
|
+
if (Array.isArray(value)) return value.join(" ");
|
|
1027
|
+
}
|
|
1028
|
+
function sourceRefFromAttributes(attrs) {
|
|
1029
|
+
const permalink = attrs.permalink ?? attrs.url ?? attrs.href;
|
|
1030
|
+
if (permalink) return parseGitHubPermalink(permalink);
|
|
1031
|
+
const repo = attrs.repo;
|
|
1032
|
+
const path = attrs.path ?? attrs.file;
|
|
1033
|
+
if (!repo || !path || !isSafeGitHubRepo(repo) || !isSafeGitHubPath(path)) return null;
|
|
1034
|
+
const ref = attrs.ref ?? attrs.sha ?? attrs.branch ?? "main";
|
|
1035
|
+
if (!isSafeGitHubRef(ref)) return null;
|
|
1036
|
+
const source = {
|
|
1037
|
+
repo,
|
|
1038
|
+
ref,
|
|
1039
|
+
path,
|
|
1040
|
+
lines: parseGitHubLineRange(attrs.loc ?? attrs.lines ?? attrs.line)
|
|
1041
|
+
};
|
|
1042
|
+
return {
|
|
1043
|
+
...source,
|
|
1044
|
+
permalink: createGitHubPermalink(source)
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
//#endregion
|
|
1048
|
+
//#region src/plugins/github/fallback-card.ts
|
|
1049
|
+
/**
|
|
1050
|
+
* Create fallback element when repo data is unavailable.
|
|
1051
|
+
*/
|
|
1052
|
+
function createFallbackCard$1(repo) {
|
|
1053
|
+
return {
|
|
1054
|
+
type: "element",
|
|
1055
|
+
tagName: "a",
|
|
1056
|
+
properties: {
|
|
1057
|
+
className: ["ox-github-card", "error"],
|
|
1058
|
+
href: isSafeGitHubRepo(repo) ? `https://github.com/${repo}` : "#",
|
|
1059
|
+
target: "_blank",
|
|
1060
|
+
rel: "noopener noreferrer"
|
|
1061
|
+
},
|
|
1062
|
+
children: [{
|
|
1063
|
+
type: "element",
|
|
1064
|
+
tagName: "div",
|
|
1065
|
+
properties: { className: ["ox-github-header"] },
|
|
1066
|
+
children: [{
|
|
1067
|
+
type: "element",
|
|
1068
|
+
tagName: "svg",
|
|
1069
|
+
properties: {
|
|
1070
|
+
className: ["ox-github-icon"],
|
|
1071
|
+
viewBox: "0 0 16 16",
|
|
1072
|
+
fill: "currentColor"
|
|
1073
|
+
},
|
|
1074
|
+
children: [{
|
|
1075
|
+
type: "element",
|
|
1076
|
+
tagName: "path",
|
|
1077
|
+
properties: { d: "M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z" },
|
|
1078
|
+
children: []
|
|
1079
|
+
}]
|
|
1080
|
+
}, {
|
|
1081
|
+
type: "element",
|
|
1082
|
+
tagName: "span",
|
|
1083
|
+
properties: { className: ["ox-github-repo"] },
|
|
1084
|
+
children: [{
|
|
1085
|
+
type: "text",
|
|
1086
|
+
value: repo
|
|
1087
|
+
}]
|
|
1088
|
+
}]
|
|
1089
|
+
}]
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
//#endregion
|
|
1093
|
+
//#region src/plugins/github/repo-card.ts
|
|
1094
|
+
function formatNumber(num) {
|
|
1095
|
+
if (num >= 1e6) return `${(num / 1e6).toFixed(1)}M`;
|
|
1096
|
+
if (num >= 1e3) return `${(num / 1e3).toFixed(1)}k`;
|
|
1097
|
+
return String(num);
|
|
1098
|
+
}
|
|
1099
|
+
function iconPath(d) {
|
|
1100
|
+
return {
|
|
1101
|
+
type: "element",
|
|
1102
|
+
tagName: "svg",
|
|
1103
|
+
properties: {
|
|
1104
|
+
viewBox: "0 0 16 16",
|
|
1105
|
+
fill: "currentColor"
|
|
1106
|
+
},
|
|
1107
|
+
children: [{
|
|
1108
|
+
type: "element",
|
|
1109
|
+
tagName: "path",
|
|
1110
|
+
properties: { d },
|
|
1111
|
+
children: []
|
|
1112
|
+
}]
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
function createStatsChildren(repoData) {
|
|
1116
|
+
const statsChildren = [];
|
|
1117
|
+
if (repoData.language) statsChildren.push({
|
|
1118
|
+
type: "element",
|
|
1119
|
+
tagName: "span",
|
|
1120
|
+
properties: { className: ["ox-github-language"] },
|
|
1121
|
+
children: [{
|
|
1122
|
+
type: "element",
|
|
1123
|
+
tagName: "span",
|
|
1124
|
+
properties: {
|
|
1125
|
+
className: ["ox-github-language-color"],
|
|
1126
|
+
"data-lang": repoData.language.toLowerCase()
|
|
1127
|
+
},
|
|
1128
|
+
children: []
|
|
1129
|
+
}, {
|
|
1130
|
+
type: "text",
|
|
1131
|
+
value: repoData.language
|
|
1132
|
+
}]
|
|
1133
|
+
});
|
|
1134
|
+
statsChildren.push({
|
|
1135
|
+
type: "element",
|
|
1136
|
+
tagName: "span",
|
|
1137
|
+
properties: { className: ["ox-github-stat"] },
|
|
1138
|
+
children: [iconPath("M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Z"), {
|
|
1139
|
+
type: "text",
|
|
1140
|
+
value: formatNumber(repoData.stargazers_count)
|
|
1141
|
+
}]
|
|
1142
|
+
});
|
|
1143
|
+
statsChildren.push({
|
|
1144
|
+
type: "element",
|
|
1145
|
+
tagName: "span",
|
|
1146
|
+
properties: { className: ["ox-github-stat"] },
|
|
1147
|
+
children: [iconPath("M5 5.372v.878c0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75v-.878a2.25 2.25 0 1 1 1.5 0v.878a2.25 2.25 0 0 1-2.25 2.25h-1.5v2.128a2.251 2.251 0 1 1-1.5 0V8.5h-1.5A2.25 2.25 0 0 1 3.5 6.25v-.878a2.25 2.25 0 1 1 1.5 0ZM5 3.25a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Zm6.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm-3 8.75a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Z"), {
|
|
1148
|
+
type: "text",
|
|
1149
|
+
value: formatNumber(repoData.forks_count)
|
|
1150
|
+
}]
|
|
1151
|
+
});
|
|
1152
|
+
return statsChildren;
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Create GitHub card element from repo data.
|
|
1156
|
+
*/
|
|
1157
|
+
function createGitHubCard(repoData) {
|
|
1158
|
+
return {
|
|
1159
|
+
type: "element",
|
|
1160
|
+
tagName: "a",
|
|
1161
|
+
properties: {
|
|
1162
|
+
className: ["ox-github-card"],
|
|
1163
|
+
href: repoData.html_url,
|
|
1164
|
+
target: "_blank",
|
|
1165
|
+
rel: "noopener noreferrer"
|
|
1166
|
+
},
|
|
1167
|
+
children: [
|
|
1168
|
+
{
|
|
1169
|
+
type: "element",
|
|
1170
|
+
tagName: "div",
|
|
1171
|
+
properties: { className: ["ox-github-header"] },
|
|
1172
|
+
children: [{
|
|
1173
|
+
...iconPath("M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7.75.75 0 1 1-1.072 1.05A2.495 2.495 0 0 1 2 11.5Zm10.5-1h-8a1 1 0 0 0-1 1v6.708A2.486 2.486 0 0 1 4.5 9h8ZM5 12.25a.25.25 0 0 1 .25-.25h3.5a.25.25 0 0 1 .25.25v3.25a.25.25 0 0 1-.4.2l-1.45-1.087a.249.249 0 0 0-.3 0L5.4 15.7a.25.25 0 0 1-.4-.2Z"),
|
|
1174
|
+
properties: {
|
|
1175
|
+
className: ["ox-github-icon"],
|
|
1176
|
+
viewBox: "0 0 16 16",
|
|
1177
|
+
fill: "currentColor"
|
|
1178
|
+
}
|
|
1179
|
+
}, {
|
|
1180
|
+
type: "element",
|
|
1181
|
+
tagName: "span",
|
|
1182
|
+
properties: { className: ["ox-github-repo"] },
|
|
1183
|
+
children: [{
|
|
1184
|
+
type: "text",
|
|
1185
|
+
value: repoData.full_name
|
|
1186
|
+
}]
|
|
1187
|
+
}]
|
|
1188
|
+
},
|
|
1189
|
+
...repoData.description ? [{
|
|
1190
|
+
type: "element",
|
|
1191
|
+
tagName: "p",
|
|
1192
|
+
properties: { className: ["ox-github-description"] },
|
|
1193
|
+
children: [{
|
|
1194
|
+
type: "text",
|
|
1195
|
+
value: repoData.description
|
|
1196
|
+
}]
|
|
1197
|
+
}] : [],
|
|
1198
|
+
{
|
|
1199
|
+
type: "element",
|
|
1200
|
+
tagName: "div",
|
|
1201
|
+
properties: { className: ["ox-github-stats"] },
|
|
1202
|
+
children: createStatsChildren(repoData)
|
|
1203
|
+
}
|
|
1204
|
+
]
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
//#endregion
|
|
1208
|
+
//#region src/plugins/github/source-card.ts
|
|
1209
|
+
function normalizeSourceLines(content) {
|
|
1210
|
+
const lines = content.replace(/\r\n?/g, "\n").split("\n");
|
|
1211
|
+
if (lines.length > 1 && lines.at(-1) === "") lines.pop();
|
|
1212
|
+
return lines.length > 0 ? lines : [""];
|
|
1213
|
+
}
|
|
1214
|
+
function createGitHubSourceCard(source, lines, options) {
|
|
1215
|
+
const allLines = normalizeSourceLines(source.content);
|
|
1216
|
+
const start = Math.min(lines?.start ?? 1, allLines.length);
|
|
1217
|
+
const end = lines ? Math.min(lines.end, allLines.length) : Math.min(allLines.length, options.maxSourceLines);
|
|
1218
|
+
const selectedLines = allLines.slice(start - 1, end);
|
|
1219
|
+
const lineRange = {
|
|
1220
|
+
start,
|
|
1221
|
+
end
|
|
1222
|
+
};
|
|
1223
|
+
const loc = selectedLines.length;
|
|
1224
|
+
const rangeLabel = formatLineRange(lineRange);
|
|
1225
|
+
const locLabel = !lines && end < allLines.length ? `${rangeLabel} of ${allLines.length} LOC` : `${rangeLabel} - ${loc} LOC`;
|
|
1226
|
+
const languageClass = source.language ? [`language-${source.language}`] : [];
|
|
1227
|
+
return {
|
|
1228
|
+
type: "element",
|
|
1229
|
+
tagName: "figure",
|
|
1230
|
+
properties: {
|
|
1231
|
+
className: ["ox-github-code"],
|
|
1232
|
+
"data-loc": String(loc),
|
|
1233
|
+
"data-source": source.permalink
|
|
1234
|
+
},
|
|
1235
|
+
children: [{
|
|
1236
|
+
type: "element",
|
|
1237
|
+
tagName: "figcaption",
|
|
1238
|
+
properties: { className: ["ox-github-code-header"] },
|
|
1239
|
+
children: [{
|
|
1240
|
+
type: "element",
|
|
1241
|
+
tagName: "a",
|
|
1242
|
+
properties: {
|
|
1243
|
+
className: ["ox-github-code-title"],
|
|
1244
|
+
href: source.permalink,
|
|
1245
|
+
target: "_blank",
|
|
1246
|
+
rel: "noopener noreferrer"
|
|
1247
|
+
},
|
|
1248
|
+
children: [{
|
|
1249
|
+
type: "text",
|
|
1250
|
+
value: `${source.repo}/${source.path}`
|
|
1251
|
+
}]
|
|
1252
|
+
}, {
|
|
1253
|
+
type: "element",
|
|
1254
|
+
tagName: "span",
|
|
1255
|
+
properties: { className: ["ox-github-code-loc"] },
|
|
1256
|
+
children: [{
|
|
1257
|
+
type: "text",
|
|
1258
|
+
value: locLabel
|
|
1259
|
+
}]
|
|
1260
|
+
}]
|
|
1261
|
+
}, {
|
|
1262
|
+
type: "element",
|
|
1263
|
+
tagName: "pre",
|
|
1264
|
+
properties: {
|
|
1265
|
+
className: ["ox-github-code-block", ...languageClass],
|
|
1266
|
+
...source.language ? { "data-language": source.language } : {}
|
|
1267
|
+
},
|
|
1268
|
+
children: [{
|
|
1269
|
+
type: "element",
|
|
1270
|
+
tagName: "code",
|
|
1271
|
+
properties: { className: languageClass },
|
|
1272
|
+
children: selectedLines.map((line, index) => {
|
|
1273
|
+
const lineNumber = start + index;
|
|
1274
|
+
return {
|
|
1275
|
+
type: "element",
|
|
1276
|
+
tagName: "span",
|
|
1277
|
+
properties: {
|
|
1278
|
+
className: ["line", "ox-github-code-line"],
|
|
1279
|
+
"data-line": String(lineNumber)
|
|
1280
|
+
},
|
|
1281
|
+
children: [{
|
|
1282
|
+
type: "element",
|
|
1283
|
+
tagName: "span",
|
|
1284
|
+
properties: { className: ["ox-github-code-line-number"] },
|
|
1285
|
+
children: [{
|
|
1286
|
+
type: "text",
|
|
1287
|
+
value: String(lineNumber)
|
|
1288
|
+
}]
|
|
1289
|
+
}, {
|
|
1290
|
+
type: "element",
|
|
1291
|
+
tagName: "span",
|
|
1292
|
+
properties: { className: ["ox-github-code-line-content"] },
|
|
1293
|
+
children: [{
|
|
1294
|
+
type: "text",
|
|
1295
|
+
value: line || " "
|
|
1296
|
+
}]
|
|
1297
|
+
}]
|
|
1298
|
+
};
|
|
1299
|
+
})
|
|
1300
|
+
}]
|
|
1301
|
+
}]
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
//#endregion
|
|
1305
|
+
//#region src/plugins/github/transform.ts
|
|
1306
|
+
const rehypeParse$2 = require_interop.interopDefault(rehype_parse.default);
|
|
1307
|
+
const rehypeStringify$2 = require_interop.interopDefault(rehype_stringify.default);
|
|
1308
|
+
/**
|
|
1309
|
+
* Rehype plugin to transform GitHub components.
|
|
1310
|
+
*/
|
|
1311
|
+
function rehypeGitHub(repoDataMap, sourceDataMap, options) {
|
|
1312
|
+
return (tree) => {
|
|
1313
|
+
const visit = (node) => {
|
|
1314
|
+
if ("children" in node) for (let i = 0; i < node.children.length; i++) {
|
|
1315
|
+
const child = node.children[i];
|
|
1316
|
+
if (child.type !== "element") continue;
|
|
1317
|
+
if (child.tagName.toLowerCase() !== "github") {
|
|
1318
|
+
visit(child);
|
|
1319
|
+
continue;
|
|
1320
|
+
}
|
|
1321
|
+
const attrs = attributesFromElement(child);
|
|
1322
|
+
const source = sourceRefFromAttributes(attrs);
|
|
1323
|
+
if (source) {
|
|
1324
|
+
const sourceData = sourceDataMap.get(sourceKey(source));
|
|
1325
|
+
node.children[i] = sourceData ? createGitHubSourceCard(sourceData, source.lines, options) : createFallbackCard$1(source.permalink);
|
|
1326
|
+
continue;
|
|
1327
|
+
}
|
|
1328
|
+
const repo = attrs.repo;
|
|
1329
|
+
if (repo) {
|
|
1330
|
+
const repoData = repoDataMap.get(repo);
|
|
1331
|
+
node.children[i] = repoData ? createGitHubCard(repoData) : createFallbackCard$1(repo);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
};
|
|
1335
|
+
visit(tree);
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
/**
|
|
1339
|
+
* Transform GitHub components in HTML.
|
|
1340
|
+
*/
|
|
1341
|
+
async function transformGitHub(html, repoDataMap, options) {
|
|
1342
|
+
const mergedOptions = {
|
|
1343
|
+
...defaultOptions$1,
|
|
1344
|
+
...options
|
|
1345
|
+
};
|
|
1346
|
+
let dataMap = repoDataMap;
|
|
1347
|
+
if (!dataMap) dataMap = await prefetchGitHubRepos(await collectGitHubRepos(html), mergedOptions);
|
|
1348
|
+
const sourceDataMap = await prefetchGitHubSources(await collectGitHubSources(html), mergedOptions);
|
|
1349
|
+
const result = await (0, unified.unified)().use(rehypeParse$2, { fragment: true }).use(rehypeGitHub, dataMap, sourceDataMap, mergedOptions).use(rehypeStringify$2).process(html);
|
|
1350
|
+
return String(result);
|
|
1351
|
+
}
|
|
1352
|
+
//#endregion
|
|
1353
|
+
//#region src/plugins/github.ts
|
|
1354
|
+
var github_exports = /* @__PURE__ */ require_vitepress.__exportAll({ transformGitHub: () => transformGitHub });
|
|
1355
|
+
//#endregion
|
|
1356
|
+
//#region src/plugins/ogp.ts
|
|
1357
|
+
/**
|
|
1358
|
+
* OGP Card Plugin - Link card embedding
|
|
1359
|
+
*
|
|
1360
|
+
* Transforms <OgCard> components into static link preview cards
|
|
1361
|
+
* by fetching OGP metadata at build time.
|
|
1362
|
+
*/
|
|
1363
|
+
var ogp_exports = /* @__PURE__ */ require_vitepress.__exportAll({
|
|
1364
|
+
collectOgpUrls: () => collectOgpUrls,
|
|
1365
|
+
fetchOgpData: () => fetchOgpData,
|
|
1366
|
+
isSafeOgpUrl: () => isSafeOgpUrl,
|
|
1367
|
+
prefetchOgpData: () => prefetchOgpData,
|
|
1368
|
+
transformOgp: () => transformOgp
|
|
1369
|
+
});
|
|
1370
|
+
const rehypeParse$1 = require_interop.interopDefault(rehype_parse.default);
|
|
1371
|
+
const rehypeStringify$1 = require_interop.interopDefault(rehype_stringify.default);
|
|
1372
|
+
const defaultOptions = {
|
|
1373
|
+
timeout: 1e4,
|
|
1374
|
+
cache: true,
|
|
1375
|
+
cacheTTL: 36e5,
|
|
1376
|
+
userAgent: "ox-content-ogp-bot/1.0 (compatible; +https://github.com/ubugeeei-prod/ox-content)"
|
|
1377
|
+
};
|
|
1378
|
+
const ogpCache = /* @__PURE__ */ new Map();
|
|
1379
|
+
function isPrivateIPv4(hostname) {
|
|
1380
|
+
const parts = hostname.split(".").map(Number);
|
|
1381
|
+
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return false;
|
|
1382
|
+
const [a, b] = parts;
|
|
1383
|
+
return a === 10 || a === 127 || a === 0 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254;
|
|
1384
|
+
}
|
|
1385
|
+
function isSafeOgpUrl(value) {
|
|
1386
|
+
try {
|
|
1387
|
+
const url = new URL(value);
|
|
1388
|
+
const host = url.hostname.toLowerCase();
|
|
1389
|
+
const ipv6 = host.replace(/^\[|\]$/g, "");
|
|
1390
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
1391
|
+
if (host === "localhost" || host.endsWith(".localhost")) return false;
|
|
1392
|
+
if (ipv6.includes(":") && (ipv6 === "::1" || ipv6.startsWith("fc") || ipv6.startsWith("fd") || ipv6.startsWith("fe80"))) return false;
|
|
1393
|
+
return !isPrivateIPv4(host);
|
|
1394
|
+
} catch {
|
|
1395
|
+
return false;
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
/**
|
|
1399
|
+
* Get element attribute value.
|
|
1400
|
+
*/
|
|
1401
|
+
function getAttribute$1(el, name) {
|
|
1402
|
+
const value = el.properties?.[name];
|
|
1403
|
+
if (typeof value === "string") return value;
|
|
1404
|
+
if (Array.isArray(value)) return value.join(" ");
|
|
1405
|
+
}
|
|
1406
|
+
/**
|
|
1407
|
+
* Extract domain from URL.
|
|
1408
|
+
*/
|
|
1409
|
+
function extractDomain(url) {
|
|
1410
|
+
try {
|
|
1411
|
+
return new URL(url).hostname;
|
|
1412
|
+
} catch {
|
|
1413
|
+
return url;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Get favicon URL for a domain.
|
|
1418
|
+
*/
|
|
1419
|
+
function getFaviconUrl(url) {
|
|
1420
|
+
try {
|
|
1421
|
+
return `https://www.google.com/s2/favicons?domain=${new URL(url).hostname}&sz=32`;
|
|
1422
|
+
} catch {
|
|
1423
|
+
return "";
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
/**
|
|
1427
|
+
* Parse OGP metadata from HTML.
|
|
1428
|
+
*/
|
|
1429
|
+
function parseOgpFromHtml(html, url) {
|
|
1430
|
+
const result = {
|
|
1431
|
+
url,
|
|
1432
|
+
title: ""
|
|
1433
|
+
};
|
|
1434
|
+
const titleMatch = html.match(/<title[^>]*>([^<]+)<\/title>/i);
|
|
1435
|
+
result.title = (html.match(/<meta[^>]*property=["']og:title["'][^>]*content=["']([^"']+)["']/i) || html.match(/<meta[^>]*content=["']([^"']+)["'][^>]*property=["']og:title["']/i))?.[1] || titleMatch?.[1] || extractDomain(url);
|
|
1436
|
+
const descMatch = html.match(/<meta[^>]*property=["']og:description["'][^>]*content=["']([^"']+)["']/i) || html.match(/<meta[^>]*content=["']([^"']+)["'][^>]*property=["']og:description["']/i) || html.match(/<meta[^>]*name=["']description["'][^>]*content=["']([^"']+)["']/i) || html.match(/<meta[^>]*content=["']([^"']+)["'][^>]*name=["']description["']/i);
|
|
1437
|
+
if (descMatch) result.description = descMatch[1];
|
|
1438
|
+
const imageMatch = html.match(/<meta[^>]*property=["']og:image["'][^>]*content=["']([^"']+)["']/i) || html.match(/<meta[^>]*content=["']([^"']+)["'][^>]*property=["']og:image["']/i);
|
|
1439
|
+
if (imageMatch) {
|
|
1440
|
+
let imageUrl = imageMatch[1];
|
|
1441
|
+
if (imageUrl.startsWith("/")) try {
|
|
1442
|
+
const urlObj = new URL(url);
|
|
1443
|
+
imageUrl = `${urlObj.protocol}//${urlObj.host}${imageUrl}`;
|
|
1444
|
+
} catch {}
|
|
1445
|
+
result.image = imageUrl;
|
|
1446
|
+
}
|
|
1447
|
+
const siteNameMatch = html.match(/<meta[^>]*property=["']og:site_name["'][^>]*content=["']([^"']+)["']/i) || html.match(/<meta[^>]*content=["']([^"']+)["'][^>]*property=["']og:site_name["']/i);
|
|
1448
|
+
if (siteNameMatch) result.siteName = siteNameMatch[1];
|
|
1449
|
+
result.favicon = getFaviconUrl(url);
|
|
1450
|
+
return result;
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Fetch OGP data for a URL.
|
|
1454
|
+
*/
|
|
1455
|
+
async function fetchOgpData(url, options) {
|
|
1456
|
+
if (!isSafeOgpUrl(url)) return null;
|
|
1457
|
+
if (options.cache) {
|
|
1458
|
+
const cached = ogpCache.get(url);
|
|
1459
|
+
if (cached && Date.now() - cached.timestamp < options.cacheTTL) return cached.data;
|
|
1460
|
+
}
|
|
1461
|
+
try {
|
|
1462
|
+
const controller = new AbortController();
|
|
1463
|
+
const timeoutId = setTimeout(() => controller.abort(), options.timeout);
|
|
1464
|
+
const response = await fetch(url, {
|
|
1465
|
+
headers: {
|
|
1466
|
+
"User-Agent": options.userAgent,
|
|
1467
|
+
Accept: "text/html,application/xhtml+xml"
|
|
1468
|
+
},
|
|
1469
|
+
signal: controller.signal
|
|
1470
|
+
});
|
|
1471
|
+
clearTimeout(timeoutId);
|
|
1472
|
+
if (!response.ok) {
|
|
1473
|
+
console.warn(`Failed to fetch OGP for ${url}: ${response.status}`);
|
|
1474
|
+
return null;
|
|
1475
|
+
}
|
|
1476
|
+
const data = parseOgpFromHtml(await response.text(), url);
|
|
1477
|
+
if (options.cache) ogpCache.set(url, {
|
|
1478
|
+
data,
|
|
1479
|
+
timestamp: Date.now()
|
|
1480
|
+
});
|
|
1481
|
+
return data;
|
|
1482
|
+
} catch (error) {
|
|
1483
|
+
if (error instanceof Error && error.name === "AbortError") console.warn(`Timeout fetching OGP for ${url}`);
|
|
1484
|
+
else console.warn(`Error fetching OGP for ${url}:`, error);
|
|
1485
|
+
return null;
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
/**
|
|
1489
|
+
* Create OGP card element.
|
|
1490
|
+
*/
|
|
1491
|
+
function createOgpCard(data) {
|
|
1492
|
+
const children = [];
|
|
1493
|
+
const contentChildren = [];
|
|
1494
|
+
contentChildren.push({
|
|
1495
|
+
type: "element",
|
|
1496
|
+
tagName: "div",
|
|
1497
|
+
properties: { className: ["ox-ogp-title"] },
|
|
1498
|
+
children: [{
|
|
1499
|
+
type: "text",
|
|
1500
|
+
value: data.title
|
|
1501
|
+
}]
|
|
1502
|
+
});
|
|
1503
|
+
if (data.description) contentChildren.push({
|
|
1504
|
+
type: "element",
|
|
1505
|
+
tagName: "div",
|
|
1506
|
+
properties: { className: ["ox-ogp-description"] },
|
|
1507
|
+
children: [{
|
|
1508
|
+
type: "text",
|
|
1509
|
+
value: data.description
|
|
1510
|
+
}]
|
|
1511
|
+
});
|
|
1512
|
+
const metaChildren = [];
|
|
1513
|
+
if (data.favicon) metaChildren.push({
|
|
1514
|
+
type: "element",
|
|
1515
|
+
tagName: "img",
|
|
1516
|
+
properties: {
|
|
1517
|
+
className: ["ox-ogp-favicon"],
|
|
1518
|
+
src: data.favicon,
|
|
1519
|
+
alt: "",
|
|
1520
|
+
loading: "lazy"
|
|
1521
|
+
},
|
|
1522
|
+
children: []
|
|
1523
|
+
});
|
|
1524
|
+
metaChildren.push({
|
|
1525
|
+
type: "element",
|
|
1526
|
+
tagName: "span",
|
|
1527
|
+
properties: { className: ["ox-ogp-domain"] },
|
|
1528
|
+
children: [{
|
|
1529
|
+
type: "text",
|
|
1530
|
+
value: data.siteName || extractDomain(data.url)
|
|
1531
|
+
}]
|
|
1532
|
+
});
|
|
1533
|
+
contentChildren.push({
|
|
1534
|
+
type: "element",
|
|
1535
|
+
tagName: "div",
|
|
1536
|
+
properties: { className: ["ox-ogp-meta"] },
|
|
1537
|
+
children: metaChildren
|
|
1538
|
+
});
|
|
1539
|
+
children.push({
|
|
1540
|
+
type: "element",
|
|
1541
|
+
tagName: "div",
|
|
1542
|
+
properties: { className: ["ox-ogp-content"] },
|
|
1543
|
+
children: contentChildren
|
|
1544
|
+
});
|
|
1545
|
+
if (data.image) children.push({
|
|
1546
|
+
type: "element",
|
|
1547
|
+
tagName: "img",
|
|
1548
|
+
properties: {
|
|
1549
|
+
className: ["ox-ogp-image"],
|
|
1550
|
+
src: data.image,
|
|
1551
|
+
alt: "",
|
|
1552
|
+
loading: "lazy"
|
|
1553
|
+
},
|
|
1554
|
+
children: []
|
|
1555
|
+
});
|
|
1556
|
+
return {
|
|
1557
|
+
type: "element",
|
|
1558
|
+
tagName: "a",
|
|
1559
|
+
properties: {
|
|
1560
|
+
className: ["ox-ogp-card"],
|
|
1561
|
+
href: isSafeOgpUrl(data.url) ? data.url : "#",
|
|
1562
|
+
target: "_blank",
|
|
1563
|
+
rel: "noopener noreferrer"
|
|
1564
|
+
},
|
|
1565
|
+
children
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
/**
|
|
1569
|
+
* Create fallback element when OGP data is unavailable.
|
|
1570
|
+
*/
|
|
1571
|
+
function createFallbackCard(url) {
|
|
1572
|
+
return {
|
|
1573
|
+
type: "element",
|
|
1574
|
+
tagName: "a",
|
|
1575
|
+
properties: {
|
|
1576
|
+
className: ["ox-ogp-simple"],
|
|
1577
|
+
href: isSafeOgpUrl(url) ? url : "#",
|
|
1578
|
+
target: "_blank",
|
|
1579
|
+
rel: "noopener noreferrer"
|
|
1580
|
+
},
|
|
1581
|
+
children: [{
|
|
1582
|
+
type: "element",
|
|
1583
|
+
tagName: "svg",
|
|
1584
|
+
properties: {
|
|
1585
|
+
viewBox: "0 0 24 24",
|
|
1586
|
+
fill: "none",
|
|
1587
|
+
stroke: "currentColor",
|
|
1588
|
+
"stroke-width": "2"
|
|
1589
|
+
},
|
|
1590
|
+
children: [{
|
|
1591
|
+
type: "element",
|
|
1592
|
+
tagName: "path",
|
|
1593
|
+
properties: { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3" },
|
|
1594
|
+
children: []
|
|
1595
|
+
}]
|
|
1596
|
+
}, {
|
|
1597
|
+
type: "text",
|
|
1598
|
+
value: extractDomain(url)
|
|
1599
|
+
}]
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
/**
|
|
1603
|
+
* Collect all OGP URLs from HTML for pre-fetching.
|
|
1604
|
+
*/
|
|
1605
|
+
async function collectOgpUrls(html) {
|
|
1606
|
+
const urls = [];
|
|
1607
|
+
const urlPattern = /<ogcard[^>]*\s+url=["']([^"']+)["']/gi;
|
|
1608
|
+
let match;
|
|
1609
|
+
while ((match = urlPattern.exec(html)) !== null) if (isSafeOgpUrl(match[1])) urls.push(match[1]);
|
|
1610
|
+
return urls;
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Pre-fetch all OGP data.
|
|
1614
|
+
*/
|
|
1615
|
+
async function prefetchOgpData(urls, options) {
|
|
1616
|
+
const mergedOptions = {
|
|
1617
|
+
...defaultOptions,
|
|
1618
|
+
...options
|
|
1619
|
+
};
|
|
1620
|
+
const results = /* @__PURE__ */ new Map();
|
|
1621
|
+
await Promise.all(urls.map(async (url) => {
|
|
1622
|
+
const data = await fetchOgpData(url, mergedOptions);
|
|
1623
|
+
results.set(url, data);
|
|
1624
|
+
}));
|
|
1625
|
+
return results;
|
|
1626
|
+
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Rehype plugin to transform OgCard components.
|
|
1629
|
+
*/
|
|
1630
|
+
function rehypeOgp(ogpDataMap) {
|
|
1631
|
+
return (tree) => {
|
|
1632
|
+
const visit = (node) => {
|
|
1633
|
+
if ("children" in node) for (let i = 0; i < node.children.length; i++) {
|
|
1634
|
+
const child = node.children[i];
|
|
1635
|
+
if (child.type === "element") if (child.tagName.toLowerCase() === "ogcard") {
|
|
1636
|
+
const url = getAttribute$1(child, "url");
|
|
1637
|
+
if (url) {
|
|
1638
|
+
const ogpData = ogpDataMap.get(url);
|
|
1639
|
+
const cardElement = ogpData ? createOgpCard(ogpData) : createFallbackCard(url);
|
|
1640
|
+
node.children[i] = cardElement;
|
|
1641
|
+
}
|
|
1642
|
+
} else visit(child);
|
|
1643
|
+
}
|
|
1644
|
+
};
|
|
1645
|
+
visit(tree);
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* Transform OgCard components in HTML.
|
|
1650
|
+
*/
|
|
1651
|
+
async function transformOgp(html, ogpDataMap, options) {
|
|
1652
|
+
let dataMap = ogpDataMap;
|
|
1653
|
+
if (!dataMap) dataMap = await prefetchOgpData(await collectOgpUrls(html), options);
|
|
1654
|
+
const result = await (0, unified.unified)().use(rehypeParse$1, { fragment: true }).use(rehypeOgp, dataMap).use(rehypeStringify$1).process(html);
|
|
283
1655
|
return String(result);
|
|
284
1656
|
}
|
|
285
1657
|
//#endregion
|
|
286
1658
|
//#region src/plugins/index.ts
|
|
1659
|
+
const SELF_CLOSING_EMBED_TAG = /<(GitHub|OgCard|Tweet|XPost|Bluesky|Spotify|StackBlitz|WebContainer|YouTube)((?:[^>"']|"[^"]*"|'[^']*')*?)\s*\/>/gi;
|
|
1660
|
+
/**
|
|
1661
|
+
* Custom embed tags are not HTML void elements, so a self-closing authoring
|
|
1662
|
+
* form like `<GitHub ... />` reaches the HTML re-parsers (Shiki highlighting,
|
|
1663
|
+
* embed transforms) as an unclosed element that swallows the rest of the
|
|
1664
|
+
* document. Normalize to an explicit open/close pair before any rehype pass
|
|
1665
|
+
* runs.
|
|
1666
|
+
*/
|
|
1667
|
+
function normalizeSelfClosingEmbeds(html) {
|
|
1668
|
+
return html.replace(SELF_CLOSING_EMBED_TAG, (_match, tag, attrs) => {
|
|
1669
|
+
return `<${tag}${attrs}></${tag}>`;
|
|
1670
|
+
});
|
|
1671
|
+
}
|
|
287
1672
|
/**
|
|
288
1673
|
* Transform all enabled plugins in HTML content.
|
|
289
1674
|
*/
|
|
290
1675
|
async function transformAllPlugins(html, options = {}) {
|
|
291
1676
|
const { tabs = true, pm = false, youtube = true, github = true, ogp, openGraph, mermaid = true, githubToken, spotify = false, stackBlitz = false, twitter = false, bluesky = false, webContainer = false } = options;
|
|
292
|
-
let result = html;
|
|
1677
|
+
let result = normalizeSelfClosingEmbeds(html);
|
|
293
1678
|
const ogpOptions = openGraph ?? ogp ?? true;
|
|
294
1679
|
if (tabs) {
|
|
295
1680
|
const { transformTabs } = await Promise.resolve().then(() => require("./tabs.cjs")).then((n) => n.tabs_exports);
|
|
296
1681
|
result = await transformTabs(result);
|
|
297
1682
|
}
|
|
298
1683
|
if (pm) {
|
|
299
|
-
const { transformPm } = await Promise.resolve().then(() =>
|
|
1684
|
+
const { transformPm } = await Promise.resolve().then(() => pm_exports);
|
|
300
1685
|
result = await transformPm(result, typeof pm === "object" ? pm : {});
|
|
301
1686
|
}
|
|
302
1687
|
if (youtube) {
|
|
303
|
-
const { transformYouTube } = await Promise.resolve().then(() =>
|
|
1688
|
+
const { transformYouTube } = await Promise.resolve().then(() => youtube_exports);
|
|
304
1689
|
result = await transformYouTube(result);
|
|
305
1690
|
}
|
|
306
1691
|
if (github !== false) {
|
|
307
|
-
const { transformGitHub } = await Promise.resolve().then(() =>
|
|
1692
|
+
const { transformGitHub } = await Promise.resolve().then(() => github_exports);
|
|
308
1693
|
result = await transformGitHub(result, void 0, {
|
|
309
1694
|
token: githubToken,
|
|
310
1695
|
...typeof github === "object" ? github : {}
|
|
311
1696
|
});
|
|
312
1697
|
}
|
|
313
1698
|
if (ogpOptions !== false) {
|
|
314
|
-
const { transformOgp } = await Promise.resolve().then(() =>
|
|
1699
|
+
const { transformOgp } = await Promise.resolve().then(() => ogp_exports);
|
|
315
1700
|
result = await transformOgp(result, void 0, typeof ogpOptions === "object" ? ogpOptions : {});
|
|
316
1701
|
}
|
|
317
1702
|
const mediaOptions = {
|
|
@@ -322,11 +1707,11 @@ async function transformAllPlugins(html, options = {}) {
|
|
|
322
1707
|
webContainer
|
|
323
1708
|
};
|
|
324
1709
|
if (Object.values(mediaOptions).some(Boolean)) {
|
|
325
|
-
const { transformMediaEmbeds } = await Promise.resolve().then(() =>
|
|
1710
|
+
const { transformMediaEmbeds } = await Promise.resolve().then(() => media_exports);
|
|
326
1711
|
result = await transformMediaEmbeds(result, mediaOptions);
|
|
327
1712
|
}
|
|
328
1713
|
if (mermaid) {
|
|
329
|
-
const { transformMermaidStatic } = await Promise.resolve().then(() =>
|
|
1714
|
+
const { transformMermaidStatic } = await Promise.resolve().then(() => mermaid_exports);
|
|
330
1715
|
result = await transformMermaidStatic(result);
|
|
331
1716
|
}
|
|
332
1717
|
return result;
|
|
@@ -335,20 +1720,20 @@ async function transformAllPlugins(html, options = {}) {
|
|
|
335
1720
|
* Transform built-in embed components in HTML content.
|
|
336
1721
|
*/
|
|
337
1722
|
async function transformBuiltinEmbeds(html, options) {
|
|
338
|
-
let result = html;
|
|
1723
|
+
let result = normalizeSelfClosingEmbeds(html);
|
|
339
1724
|
if (options.github) {
|
|
340
|
-
const { transformGitHub } = await Promise.resolve().then(() =>
|
|
1725
|
+
const { transformGitHub } = await Promise.resolve().then(() => github_exports);
|
|
341
1726
|
result = await transformGitHub(result, void 0, {
|
|
342
1727
|
token: process.env.GITHUB_TOKEN,
|
|
343
1728
|
...options.github
|
|
344
1729
|
});
|
|
345
1730
|
}
|
|
346
1731
|
if (options.openGraph) {
|
|
347
|
-
const { transformOgp } = await Promise.resolve().then(() =>
|
|
1732
|
+
const { transformOgp } = await Promise.resolve().then(() => ogp_exports);
|
|
348
1733
|
result = await transformOgp(result, void 0, options.openGraph);
|
|
349
1734
|
}
|
|
350
1735
|
if (options.pm) {
|
|
351
|
-
const { transformPm } = await Promise.resolve().then(() =>
|
|
1736
|
+
const { transformPm } = await Promise.resolve().then(() => pm_exports);
|
|
352
1737
|
result = await transformPm(result, typeof options.pm === "object" ? options.pm : {});
|
|
353
1738
|
}
|
|
354
1739
|
const mediaOptions = {
|
|
@@ -359,7 +1744,7 @@ async function transformBuiltinEmbeds(html, options) {
|
|
|
359
1744
|
webContainer: options.webContainer
|
|
360
1745
|
};
|
|
361
1746
|
if (Object.values(mediaOptions).some(Boolean)) {
|
|
362
|
-
const { transformMediaEmbeds } = await Promise.resolve().then(() =>
|
|
1747
|
+
const { transformMediaEmbeds } = await Promise.resolve().then(() => media_exports);
|
|
363
1748
|
result = await transformMediaEmbeds(result, mediaOptions);
|
|
364
1749
|
}
|
|
365
1750
|
return result;
|
|
@@ -422,10 +1807,10 @@ function restoreMermaidSvgs(html, svgs) {
|
|
|
422
1807
|
//#region src/code-blocks.ts
|
|
423
1808
|
const execFileAsync = (0, node_util.promisify)(node_child_process.execFile);
|
|
424
1809
|
async function extractCodeBlocks(source) {
|
|
425
|
-
return (await
|
|
1810
|
+
return (await require_vitepress.importNapiModule()).extractCodeBlocks(source).map(normalizeBlock);
|
|
426
1811
|
}
|
|
427
1812
|
async function lintCodeBlocks(source, options = {}) {
|
|
428
|
-
return (await
|
|
1813
|
+
return (await require_vitepress.importNapiModule()).lintCodeBlocks(source, {
|
|
429
1814
|
enabled: true,
|
|
430
1815
|
languages: options.languages,
|
|
431
1816
|
requireLanguage: options.requireLanguage,
|
|
@@ -433,7 +1818,7 @@ async function lintCodeBlocks(source, options = {}) {
|
|
|
433
1818
|
}).map(normalizeDiagnostic);
|
|
434
1819
|
}
|
|
435
1820
|
async function extractDocsTests(source, options = {}) {
|
|
436
|
-
return (await
|
|
1821
|
+
return (await require_vitepress.importNapiModule()).extractDocsTests(source, {
|
|
437
1822
|
enabled: true,
|
|
438
1823
|
languages: options.languages,
|
|
439
1824
|
requireMeta: options.requireMeta
|
|
@@ -451,7 +1836,8 @@ async function typecheckCodeBlocks(source, options = {}) {
|
|
|
451
1836
|
try {
|
|
452
1837
|
const files = [];
|
|
453
1838
|
await Promise.all(blocks.map(async (block, index) => {
|
|
454
|
-
const
|
|
1839
|
+
const extension = block.language.toLowerCase() === "tsx" ? "tsx" : "ts";
|
|
1840
|
+
const file = (0, node_path.join)(temp, `snippet-${index}.${extension}`);
|
|
455
1841
|
files.push(file);
|
|
456
1842
|
await (0, node_fs_promises.writeFile)(file, block.code);
|
|
457
1843
|
}));
|
|
@@ -463,7 +1849,7 @@ async function typecheckCodeBlocks(source, options = {}) {
|
|
|
463
1849
|
...files
|
|
464
1850
|
], {
|
|
465
1851
|
cwd: process.cwd(),
|
|
466
|
-
maxBuffer:
|
|
1852
|
+
maxBuffer: 4194304
|
|
467
1853
|
});
|
|
468
1854
|
return [];
|
|
469
1855
|
} catch (error) {
|
|
@@ -561,7 +1947,7 @@ async function loadNapiBindings() {
|
|
|
561
1947
|
if (napiLoadAttempted) return napiBindings ?? null;
|
|
562
1948
|
napiLoadAttempted = true;
|
|
563
1949
|
try {
|
|
564
|
-
const mod = await
|
|
1950
|
+
const mod = await require_vitepress.importNapiModule();
|
|
565
1951
|
napiBindings = mod;
|
|
566
1952
|
return mod;
|
|
567
1953
|
} catch (error) {
|
|
@@ -616,10 +2002,10 @@ async function transformMarkdown(source, filePath, options, ssgOptions) {
|
|
|
616
2002
|
} : void 0
|
|
617
2003
|
});
|
|
618
2004
|
if (result.errors.length > 0) console.warn("[ox-content] Transform warnings:", result.errors);
|
|
619
|
-
let html = result.html;
|
|
2005
|
+
let html = normalizeSelfClosingEmbeds(result.html);
|
|
620
2006
|
const frontmatter = parseFrontmatterJson(result.frontmatter);
|
|
621
2007
|
const toc = options.toc ? result.toc.map(normalizeTocEntry) : [];
|
|
622
|
-
if (options.mermaid) html = await
|
|
2008
|
+
if (options.mermaid) html = await transformMermaidStatic(html);
|
|
623
2009
|
const { html: protectedHtml, svgs } = protectMermaidSvgs(html);
|
|
624
2010
|
html = protectedHtml;
|
|
625
2011
|
if (options.highlight) {
|
|
@@ -815,7 +2201,7 @@ const DEFAULT_DOCS_INCLUDE = [
|
|
|
815
2201
|
* ```
|
|
816
2202
|
*/
|
|
817
2203
|
async function extractDocs(srcDirs, options) {
|
|
818
|
-
const napi = await
|
|
2204
|
+
const napi = await require_vitepress.importNapiModule();
|
|
819
2205
|
if (options.entryPoints?.length) {
|
|
820
2206
|
const extractDocsFromEntryPoints = napi.extractDocsFromEntryPoints;
|
|
821
2207
|
if (!extractDocsFromEntryPoints) throw new Error("[ox-content] extractDocsFromEntryPoints is not available from @ox-content/napi.");
|
|
@@ -844,7 +2230,7 @@ async function extractDocs(srcDirs, options) {
|
|
|
844
2230
|
* Generates Markdown documentation from extracted docs.
|
|
845
2231
|
*/
|
|
846
2232
|
function generateMarkdown(docs, options) {
|
|
847
|
-
const napi =
|
|
2233
|
+
const napi = require_vitepress.importNapiModuleSync();
|
|
848
2234
|
if (typeof napi.generateDocsMarkdown !== "function") throw new Error("[ox-content] generateDocsMarkdown is not available from @ox-content/napi. Please rebuild the NAPI package.");
|
|
849
2235
|
return napi.generateDocsMarkdown(toRustDocsModules(docs), {
|
|
850
2236
|
groupBy: options.groupBy,
|
|
@@ -874,7 +2260,7 @@ function generateMarkdown(docs, options) {
|
|
|
874
2260
|
* Writes generated documentation to the output directory.
|
|
875
2261
|
*/
|
|
876
2262
|
async function writeDocs(docs, outDir, extractedDocs, options) {
|
|
877
|
-
const napi =
|
|
2263
|
+
const napi = require_vitepress.importNapiModuleSync();
|
|
878
2264
|
if (typeof napi.writeGeneratedDocs !== "function") throw new Error("[ox-content] writeGeneratedDocs is not available from @ox-content/napi. Please rebuild the NAPI package.");
|
|
879
2265
|
napi.writeGeneratedDocs(docs, outDir, extractedDocs ? toRustDocsModules(extractedDocs) : void 0, {
|
|
880
2266
|
generateNav: options?.generateNav ?? false,
|
|
@@ -1171,7 +2557,7 @@ function getDefaultTemplate() {
|
|
|
1171
2557
|
const { title, description, siteName } = props;
|
|
1172
2558
|
const rawBrand = siteName?.trim() ? siteName : "Ox Content";
|
|
1173
2559
|
const isBrandCard = normalizeBrandValue(title) === normalizeBrandValue(rawBrand);
|
|
1174
|
-
const heroTitle = isBrandCard ? "
|
|
2560
|
+
const heroTitle = isBrandCard ? "High-performance Markdown toolkit" : title;
|
|
1175
2561
|
const heroDescription = isBrandCard ? "Rust-powered docs and high-performance Markdown tooling." : description && description.trim().length > 0 ? description : "Rust-powered docs and Markdown tooling.";
|
|
1176
2562
|
const descriptionHtml = heroDescription.trim().length > 0 ? `<p style="max-width:760px;font-size:28px;color:#93a4c3;line-height:1.45;margin:0;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;">${escapeHtml$2(heroDescription)}</p>` : "";
|
|
1177
2563
|
return `<div style="width:100%;height:100%;position:relative;overflow:hidden;box-sizing:border-box;padding:56px 64px 52px;background:#0b1220;font-family:'IBM Plex Sans','Avenir Next','Segoe UI',system-ui,sans-serif;color:#eff6ff;border:1px solid #223252;border-top:4px solid #4f6fae;">
|
|
@@ -1226,7 +2612,7 @@ async function writeCache(cacheDir, key, png) {
|
|
|
1226
2612
|
await fs_promises.writeFile(filePath, png);
|
|
1227
2613
|
}
|
|
1228
2614
|
//#endregion
|
|
1229
|
-
//#region \0@oxc-project+runtime@0.
|
|
2615
|
+
//#region \0@oxc-project+runtime@0.142.0/helpers/esm/usingCtx.js
|
|
1230
2616
|
function _usingCtx() {
|
|
1231
2617
|
var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
|
|
1232
2618
|
var n = Error();
|
|
@@ -1393,7 +2779,8 @@ async function resolveVueTemplate(templatePath, options, root) {
|
|
|
1393
2779
|
const { createSSRApp } = await import("vue");
|
|
1394
2780
|
const { renderToString } = await import("vue/server-renderer");
|
|
1395
2781
|
return async (props) => {
|
|
1396
|
-
const
|
|
2782
|
+
const app = createSSRApp(Component, props);
|
|
2783
|
+
const html = await renderToString(app);
|
|
1397
2784
|
if (extractedCss) return `<style>${extractedCss}</style>${html}`;
|
|
1398
2785
|
return html;
|
|
1399
2786
|
};
|
|
@@ -1908,13 +3295,13 @@ function resolveSsgOptions(ssg) {
|
|
|
1908
3295
|
* Extracts title from content or frontmatter.
|
|
1909
3296
|
*/
|
|
1910
3297
|
function extractTitle$1(content, frontmatter) {
|
|
1911
|
-
return
|
|
3298
|
+
return require_vitepress.importNapiModuleSync().extractSsgTitle(content, typeof frontmatter.title === "string" ? frontmatter.title : void 0);
|
|
1912
3299
|
}
|
|
1913
3300
|
/**
|
|
1914
3301
|
* Generates bare HTML page (no navigation, no styles).
|
|
1915
3302
|
*/
|
|
1916
3303
|
function generateBareHtmlPage(content, title) {
|
|
1917
|
-
return
|
|
3304
|
+
return require_vitepress.importNapiModuleSync().generateSsgBareHtml(content, title);
|
|
1918
3305
|
}
|
|
1919
3306
|
/**
|
|
1920
3307
|
* Per-build cache for the Rust-facing nav conversion. `navGroups` is the same
|
|
@@ -1991,7 +3378,7 @@ function localeCodesFor(locales) {
|
|
|
1991
3378
|
* Generates HTML page with navigation using Rust NAPI bindings.
|
|
1992
3379
|
*/
|
|
1993
3380
|
async function generateHtmlPage(pageData, navGroups, siteName, base, ogImage, theme, locale, availableLocales) {
|
|
1994
|
-
const mod = await
|
|
3381
|
+
const mod = await require_vitepress.importNapiModule();
|
|
1995
3382
|
const tocForRust = pageData.toc.map(toRustTocEntry);
|
|
1996
3383
|
const navGroupsForRust = convertNavGroupsForRust(navGroups);
|
|
1997
3384
|
const themeForRust = theme ? require_vitepress.themeToNapi(theme) : void 0;
|
|
@@ -2044,7 +3431,7 @@ async function generateHtmlPage(pageData, navGroups, siteName, base, ogImage, th
|
|
|
2044
3431
|
});
|
|
2045
3432
|
}
|
|
2046
3433
|
async function externalizeSharedPageAssets(pages, outDir, base) {
|
|
2047
|
-
const optimized = (await
|
|
3434
|
+
const optimized = (await require_vitepress.importNapiModule()).externalizeSsgAssets(pages, outDir, base);
|
|
2048
3435
|
await Promise.all(optimized.assets.map(async (asset) => {
|
|
2049
3436
|
await fs_promises.mkdir(path.dirname(asset.outputPath), { recursive: true });
|
|
2050
3437
|
await fs_promises.writeFile(asset.outputPath, asset.content, "utf-8");
|
|
@@ -2058,45 +3445,45 @@ async function externalizeSharedPageAssets(pages, outDir, base) {
|
|
|
2058
3445
|
* Converts a markdown file path to a relative URL path.
|
|
2059
3446
|
*/
|
|
2060
3447
|
function getUrlPath$1(inputPath, srcDir) {
|
|
2061
|
-
return
|
|
3448
|
+
return require_vitepress.importNapiModuleSync().getSsgUrlPath(inputPath, srcDir);
|
|
2062
3449
|
}
|
|
2063
3450
|
/**
|
|
2064
3451
|
* Resolves manual navigation config to the format used by the built-in SSG renderer.
|
|
2065
3452
|
*/
|
|
2066
3453
|
function resolveNavigationGroups(navigation, base, extension) {
|
|
2067
3454
|
if (!navigation) return;
|
|
2068
|
-
return
|
|
3455
|
+
return require_vitepress.importNapiModuleSync().resolveSsgNavigationGroups(navigation, base, extension);
|
|
2069
3456
|
}
|
|
2070
3457
|
function getPageLocale(urlPath, i18n) {
|
|
2071
3458
|
if (!i18n) return void 0;
|
|
2072
|
-
return
|
|
3459
|
+
return require_vitepress.importNapiModuleSync().getSsgPageLocale(urlPath, i18n.defaultLocale, localeCodesFor(i18n.locales)) ?? void 0;
|
|
2073
3460
|
}
|
|
2074
3461
|
function getRoutePaths(inputPath, srcDir, outDir, base, extension, siteUrl) {
|
|
2075
|
-
return
|
|
3462
|
+
return require_vitepress.importNapiModuleSync().resolveSsgRoutePaths(inputPath, srcDir, outDir, base, extension, siteUrl);
|
|
2076
3463
|
}
|
|
2077
3464
|
/**
|
|
2078
3465
|
* Formats a file/dir name as a title.
|
|
2079
3466
|
*/
|
|
2080
3467
|
function formatTitle(name) {
|
|
2081
|
-
return
|
|
3468
|
+
return require_vitepress.importNapiModuleSync().formatSsgTitle(name);
|
|
2082
3469
|
}
|
|
2083
3470
|
/**
|
|
2084
3471
|
* Collects all markdown files from the source directory.
|
|
2085
3472
|
*/
|
|
2086
3473
|
async function collectMarkdownFiles(srcDir, extensions = DEFAULT_MARKDOWN_EXTENSIONS) {
|
|
2087
|
-
return
|
|
3474
|
+
return require_vitepress.importNapiModuleSync().collectSsgMarkdownFiles(srcDir, [...extensions]);
|
|
2088
3475
|
}
|
|
2089
3476
|
/**
|
|
2090
3477
|
* Builds navigation items from markdown files, grouped by directory.
|
|
2091
3478
|
*/
|
|
2092
3479
|
function buildNavItems(markdownFiles, srcDir, base, extension) {
|
|
2093
|
-
return
|
|
3480
|
+
return require_vitepress.importNapiModuleSync().buildSsgNavItems(markdownFiles, srcDir, base, extension);
|
|
2094
3481
|
}
|
|
2095
3482
|
/**
|
|
2096
3483
|
* Builds navigation items from an explicit theme sidebar tree.
|
|
2097
3484
|
*/
|
|
2098
3485
|
function buildThemeNavItems(sidebar, base, extension) {
|
|
2099
|
-
return
|
|
3486
|
+
return require_vitepress.importNapiModuleSync().buildSsgThemeNavItems(sidebar, base, extension);
|
|
2100
3487
|
}
|
|
2101
3488
|
/**
|
|
2102
3489
|
* Builds all markdown files to static HTML.
|
|
@@ -2145,7 +3532,7 @@ async function createBuildSsgContext(options, root, srcDir, outDir, markdownFile
|
|
|
2145
3532
|
navItems: resolveNavigationGroups(ssgOptions.navigation, base, ssgOptions.extension) ?? (ssgOptions.theme?.sidebar.length ? buildThemeNavItems(ssgOptions.theme.sidebar, base, ssgOptions.extension) : buildNavItems(markdownFiles, srcDir, base, ssgOptions.extension)),
|
|
2146
3533
|
siteName: await resolveSiteName$1(root, ssgOptions),
|
|
2147
3534
|
shouldGenerateOgImages: (options.ogImage || ssgOptions.generateOgImage) && !ssgOptions.bare,
|
|
2148
|
-
napi: ssgOptions.lastUpdated ? await
|
|
3535
|
+
napi: ssgOptions.lastUpdated ? await require_vitepress.importNapiModule() : void 0
|
|
2149
3536
|
};
|
|
2150
3537
|
}
|
|
2151
3538
|
async function resolveSiteName$1(root, ssgOptions) {
|
|
@@ -2321,7 +3708,7 @@ async function writeGeneratedPages(generatedPages, context, generatedFiles) {
|
|
|
2321
3708
|
let oxContent$1 = null;
|
|
2322
3709
|
async function getOxContent() {
|
|
2323
3710
|
if (!oxContent$1) try {
|
|
2324
|
-
oxContent$1 = await
|
|
3711
|
+
oxContent$1 = await require_vitepress.importNapiModule();
|
|
2325
3712
|
} catch {
|
|
2326
3713
|
console.warn("[ox-content] Native bindings not available, search disabled");
|
|
2327
3714
|
return null;
|
|
@@ -2375,7 +3762,7 @@ async function writeSearchIndex(indexJson, outDir) {
|
|
|
2375
3762
|
* This is injected into the bundle as a virtual module.
|
|
2376
3763
|
*/
|
|
2377
3764
|
function generateSearchModule(options, indexPath) {
|
|
2378
|
-
return
|
|
3765
|
+
return require_vitepress.importNapiModuleSync().generateSearchModuleFromOptions(options, indexPath);
|
|
2379
3766
|
}
|
|
2380
3767
|
//#endregion
|
|
2381
3768
|
//#region src/dev-server.ts
|
|
@@ -2386,7 +3773,7 @@ function generateSearchModule(options, indexPath) {
|
|
|
2386
3773
|
* during `vite dev`, matching the SSG build output.
|
|
2387
3774
|
*/
|
|
2388
3775
|
/** File extensions to skip in the middleware. */
|
|
2389
|
-
const SKIP_EXTENSIONS = new Set([
|
|
3776
|
+
const SKIP_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
2390
3777
|
".js",
|
|
2391
3778
|
".ts",
|
|
2392
3779
|
".css",
|
|
@@ -2995,7 +4382,7 @@ function createI18nPlugin(resolvedOptions) {
|
|
|
2995
4382
|
return;
|
|
2996
4383
|
}
|
|
2997
4384
|
try {
|
|
2998
|
-
const { checkI18nProject } = await
|
|
4385
|
+
const { checkI18nProject } = await require_vitepress.importNapiModule();
|
|
2999
4386
|
const checkResult = checkI18nProject(dictDir, [path.resolve(root, "src"), path.resolve(root, "content")], i18nOptions.functionNames, i18nOptions.defaultLocale);
|
|
3000
4387
|
if (checkResult.errorCount > 0 || checkResult.warningCount > 0) {
|
|
3001
4388
|
for (const diag of checkResult.diagnostics) if (diag.severity === "error") console.error(`[ox-content:i18n] ${diag.message}`);
|
|
@@ -3288,7 +4675,7 @@ function resolveCollectionsOptions(options) {
|
|
|
3288
4675
|
}
|
|
3289
4676
|
async function buildCollectionManifest(root, options) {
|
|
3290
4677
|
if (!options.collections.enabled) return { collections: {} };
|
|
3291
|
-
return parseCollectionManifest((await
|
|
4678
|
+
return parseCollectionManifest((await require_vitepress.importNapiModule()).buildCollectionManifest({
|
|
3292
4679
|
srcDir: node_path.resolve(root, options.srcDir),
|
|
3293
4680
|
extensions: [...options.extensions],
|
|
3294
4681
|
frontmatter: options.frontmatter,
|
|
@@ -3386,7 +4773,7 @@ var IncrementalMarkdownParser = class {
|
|
|
3386
4773
|
#includePendingAst;
|
|
3387
4774
|
#completeInline;
|
|
3388
4775
|
constructor(options = {}) {
|
|
3389
|
-
const napi =
|
|
4776
|
+
const napi = require_vitepress.importNapiModuleSync();
|
|
3390
4777
|
this.#native = new napi.IncrementalMarkdownParser(toNativeParserOptions(options));
|
|
3391
4778
|
this.#includePendingAst = options.includePendingAst ?? false;
|
|
3392
4779
|
this.#completeInline = options.completeInline ?? true;
|
|
@@ -3422,7 +4809,7 @@ var IncrementalMarkdownRenderer = class {
|
|
|
3422
4809
|
#renderPending;
|
|
3423
4810
|
#completeInline;
|
|
3424
4811
|
constructor(options = {}) {
|
|
3425
|
-
const napi =
|
|
4812
|
+
const napi = require_vitepress.importNapiModuleSync();
|
|
3426
4813
|
this.#native = new napi.IncrementalMarkdownRenderer(toNativeParserOptions(options));
|
|
3427
4814
|
this.#renderPending = options.renderPending ?? true;
|
|
3428
4815
|
this.#completeInline = options.completeInline ?? true;
|
|
@@ -3575,7 +4962,7 @@ function renderHtmlToVueH(html, islands = []) {
|
|
|
3575
4962
|
return renderHtmlToFrameworkCode(html, "vue", "expression", islands);
|
|
3576
4963
|
}
|
|
3577
4964
|
function renderHtmlToFrameworkCode(html, target, mode, islands = []) {
|
|
3578
|
-
return
|
|
4965
|
+
return require_vitepress.importNapiModuleSync().renderFrameworkComponentCode(html, target, toNapiIslands(islands), mode);
|
|
3579
4966
|
}
|
|
3580
4967
|
function renderHtmlToReactComponent(html, islands = []) {
|
|
3581
4968
|
return renderHtmlToFrameworkCode(html, "react", "component", islands);
|
|
@@ -3587,7 +4974,7 @@ function renderHtmlToSvelteComponent(html) {
|
|
|
3587
4974
|
return renderHtmlToFrameworkCode(html, "svelte", "component");
|
|
3588
4975
|
}
|
|
3589
4976
|
function escapeSvelteMarkup(html) {
|
|
3590
|
-
return
|
|
4977
|
+
return require_vitepress.importNapiModuleSync().escapeSvelteMarkup(html);
|
|
3591
4978
|
}
|
|
3592
4979
|
function toNapiIslands(islands) {
|
|
3593
4980
|
return islands.map((island) => ({
|
|
@@ -4237,7 +5624,7 @@ async function lintMarkdownFiles(options = {}) {
|
|
|
4237
5624
|
function resolveMarkdownLintFileOptions(options) {
|
|
4238
5625
|
return {
|
|
4239
5626
|
cwd: node_path.resolve(options.cwd ?? process.cwd()),
|
|
4240
|
-
exclude: [
|
|
5627
|
+
exclude: [.../* @__PURE__ */ new Set([...options.exclude ?? DEFAULT_LINT_FILE_EXCLUDE, ...options.ignore ?? []])],
|
|
4241
5628
|
include: [...new Set(options.include ?? DEFAULT_LINT_FILE_INCLUDE)],
|
|
4242
5629
|
lintOptions: {
|
|
4243
5630
|
dictionary: options.dictionary,
|
|
@@ -4329,7 +5716,7 @@ function createEmptyLintResult() {
|
|
|
4329
5716
|
* }
|
|
4330
5717
|
* ```
|
|
4331
5718
|
*/
|
|
4332
|
-
const VOID_ELEMENTS = new Set([
|
|
5719
|
+
const VOID_ELEMENTS = /* @__PURE__ */ new Set([
|
|
4333
5720
|
"area",
|
|
4334
5721
|
"base",
|
|
4335
5722
|
"br",
|
|
@@ -4345,7 +5732,7 @@ const VOID_ELEMENTS = new Set([
|
|
|
4345
5732
|
"track",
|
|
4346
5733
|
"wbr"
|
|
4347
5734
|
]);
|
|
4348
|
-
const BOOLEAN_ATTRS = new Set([
|
|
5735
|
+
const BOOLEAN_ATTRS = /* @__PURE__ */ new Set([
|
|
4349
5736
|
"allowfullscreen",
|
|
4350
5737
|
"async",
|
|
4351
5738
|
"autofocus",
|
|
@@ -4492,7 +5879,7 @@ function each(items, render) {
|
|
|
4492
5879
|
}
|
|
4493
5880
|
//#endregion
|
|
4494
5881
|
//#region src/page-context.ts
|
|
4495
|
-
var page_context_exports = /* @__PURE__ */
|
|
5882
|
+
var page_context_exports = /* @__PURE__ */ require_vitepress.__exportAll({
|
|
4496
5883
|
clearRenderContext: () => clearRenderContext,
|
|
4497
5884
|
generateFrontmatterTypes: () => generateFrontmatterTypes,
|
|
4498
5885
|
inferType: () => inferType,
|
|
@@ -4677,7 +6064,7 @@ function generateFrontmatterTypes(samples, interfaceName = "PageFrontmatter") {
|
|
|
4677
6064
|
return lines.join("\n");
|
|
4678
6065
|
}
|
|
4679
6066
|
var currentContext;
|
|
4680
|
-
var init_page_context =
|
|
6067
|
+
var init_page_context = require_vitepress.__esmMin((() => {
|
|
4681
6068
|
currentContext = null;
|
|
4682
6069
|
}));
|
|
4683
6070
|
//#endregion
|
|
@@ -4771,7 +6158,7 @@ async function generateTypes(pages, outDir) {
|
|
|
4771
6158
|
* A minimal theme that renders page content with basic styling.
|
|
4772
6159
|
*/
|
|
4773
6160
|
function DefaultTheme({ children }) {
|
|
4774
|
-
const { usePageProps, useSiteConfig } = (init_page_context(),
|
|
6161
|
+
const { usePageProps, useSiteConfig } = (init_page_context(), require_vitepress.__toCommonJS(page_context_exports));
|
|
4775
6162
|
const page = usePageProps();
|
|
4776
6163
|
const site = useSiteConfig();
|
|
4777
6164
|
return { __html: `<!DOCTYPE html>
|
|
@@ -4835,7 +6222,7 @@ function escapeHtml(str) {
|
|
|
4835
6222
|
function createTheme(config) {
|
|
4836
6223
|
const { layouts, defaultLayout = "default" } = config;
|
|
4837
6224
|
return function ThemeWithLayouts({ children }) {
|
|
4838
|
-
const { usePageProps } = (init_page_context(),
|
|
6225
|
+
const { usePageProps } = (init_page_context(), require_vitepress.__toCommonJS(page_context_exports));
|
|
4839
6226
|
const layoutName = usePageProps().layout ?? defaultLayout;
|
|
4840
6227
|
const Layout = layouts[layoutName] ?? layouts[defaultLayout];
|
|
4841
6228
|
if (!Layout) throw new Error(`[ox-content] Layout "${layoutName}" not found. Available layouts: ${Object.keys(layouts).join(", ")}`);
|
|
@@ -5359,9 +6746,9 @@ function resolveCodeAnnotationsOptions(options) {
|
|
|
5359
6746
|
/**
|
|
5360
6747
|
* Generates virtual module content.
|
|
5361
6748
|
*/
|
|
5362
|
-
function generateVirtualModule(path$
|
|
5363
|
-
if (path$
|
|
5364
|
-
if (path$
|
|
6749
|
+
function generateVirtualModule(path$3, options) {
|
|
6750
|
+
if (path$3 === "config") return `export default ${JSON.stringify(options)};`;
|
|
6751
|
+
if (path$3 === "runtime") {
|
|
5365
6752
|
const base = normalizeRuntimeBase(options.base);
|
|
5366
6753
|
return `
|
|
5367
6754
|
export const base = ${JSON.stringify(base)};
|
|
@@ -5420,9 +6807,9 @@ exports.buildSearchIndex = buildSearchIndex;
|
|
|
5420
6807
|
exports.buildSsg = buildSsg;
|
|
5421
6808
|
exports.clearRenderContext = clearRenderContext;
|
|
5422
6809
|
exports.collectDocsTests = collectDocsTests;
|
|
5423
|
-
exports.collectGitHubRepos =
|
|
5424
|
-
exports.collectGitHubSources =
|
|
5425
|
-
exports.collectOgpUrls =
|
|
6810
|
+
exports.collectGitHubRepos = collectGitHubRepos;
|
|
6811
|
+
exports.collectGitHubSources = collectGitHubSources;
|
|
6812
|
+
exports.collectOgpUrls = collectOgpUrls;
|
|
5426
6813
|
exports.convertVitePressNav = require_vitepress.convertVitePressNav;
|
|
5427
6814
|
exports.convertVitePressSidebar = require_vitepress.convertVitePressSidebar;
|
|
5428
6815
|
exports.createFrameworkMarkdownOptions = createFrameworkMarkdownOptions;
|
|
@@ -5441,10 +6828,10 @@ exports.extractCodeBlocks = extractCodeBlocks;
|
|
|
5441
6828
|
exports.extractDocs = extractDocs;
|
|
5442
6829
|
exports.extractDocsTests = extractDocsTests;
|
|
5443
6830
|
exports.extractIslandInfo = extractIslandInfo;
|
|
5444
|
-
exports.extractVideoId =
|
|
5445
|
-
exports.fetchGitHubSource =
|
|
5446
|
-
exports.fetchOgpData =
|
|
5447
|
-
exports.fetchRepoData =
|
|
6831
|
+
exports.extractVideoId = extractVideoId;
|
|
6832
|
+
exports.fetchGitHubSource = fetchGitHubSource;
|
|
6833
|
+
exports.fetchOgpData = fetchOgpData;
|
|
6834
|
+
exports.fetchRepoData = fetchRepoData;
|
|
5448
6835
|
exports.fromVitePressConfig = require_vitepress.fromVitePressConfig;
|
|
5449
6836
|
exports.generateCollectionsVirtualModule = generateCollectionsVirtualModule;
|
|
5450
6837
|
exports.generateFrontmatterTypes = generateFrontmatterTypes;
|
|
@@ -5466,15 +6853,15 @@ exports.lintMarkdownAsync = lintMarkdownAsync;
|
|
|
5466
6853
|
exports.lintMarkdownFile = lintMarkdownFile;
|
|
5467
6854
|
exports.lintMarkdownFiles = lintMarkdownFiles;
|
|
5468
6855
|
exports.mergeThemes = require_vitepress.mergeThemes;
|
|
5469
|
-
exports.mermaidClientScript =
|
|
6856
|
+
exports.mermaidClientScript = mermaidClientScript;
|
|
5470
6857
|
exports.normalizeMarkdownExtensions = normalizeMarkdownExtensions;
|
|
5471
6858
|
exports.normalizeVitePressFrontmatter = require_vitepress.normalizeVitePressFrontmatter;
|
|
5472
6859
|
exports.oxContent = oxContent;
|
|
5473
|
-
exports.parseGitHubLineRange =
|
|
5474
|
-
exports.parseGitHubPermalink =
|
|
5475
|
-
exports.prefetchGitHubRepos =
|
|
5476
|
-
exports.prefetchGitHubSources =
|
|
5477
|
-
exports.prefetchOgpData =
|
|
6860
|
+
exports.parseGitHubLineRange = parseGitHubLineRange;
|
|
6861
|
+
exports.parseGitHubPermalink = parseGitHubPermalink;
|
|
6862
|
+
exports.prefetchGitHubRepos = prefetchGitHubRepos;
|
|
6863
|
+
exports.prefetchGitHubSources = prefetchGitHubSources;
|
|
6864
|
+
exports.prefetchOgpData = prefetchOgpData;
|
|
5478
6865
|
exports.raw = raw;
|
|
5479
6866
|
exports.renderAllPages = renderAllPages;
|
|
5480
6867
|
exports.renderHtmlToFrameworkCode = renderHtmlToFrameworkCode;
|
|
@@ -5499,13 +6886,13 @@ exports.setRenderContext = setRenderContext;
|
|
|
5499
6886
|
exports.shouldLintMarkdownFile = shouldLintMarkdownFile;
|
|
5500
6887
|
exports.stripMarkdownExtension = stripMarkdownExtension;
|
|
5501
6888
|
exports.transformAllPlugins = transformAllPlugins;
|
|
5502
|
-
exports.transformGitHub =
|
|
6889
|
+
exports.transformGitHub = transformGitHub;
|
|
5503
6890
|
exports.transformIslands = transformIslands;
|
|
5504
6891
|
exports.transformMarkdown = transformMarkdown;
|
|
5505
|
-
exports.transformMermaidStatic =
|
|
5506
|
-
exports.transformOgp =
|
|
6892
|
+
exports.transformMermaidStatic = transformMermaidStatic;
|
|
6893
|
+
exports.transformOgp = transformOgp;
|
|
5507
6894
|
exports.transformTabs = require_tabs.transformTabs;
|
|
5508
|
-
exports.transformYouTube =
|
|
6895
|
+
exports.transformYouTube = transformYouTube;
|
|
5509
6896
|
exports.typecheckCodeBlocks = typecheckCodeBlocks;
|
|
5510
6897
|
exports.useIsActive = useIsActive;
|
|
5511
6898
|
exports.useNav = useNav;
|