@ox-content/vite-plugin 3.0.0-alpha.1 → 3.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.cjs +6563 -0
- package/dist/api.cjs.map +1 -0
- package/dist/api.mjs +6456 -0
- package/dist/api.mjs.map +1 -0
- package/dist/index.cjs +3650 -240
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +629 -8
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +629 -8
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3694 -307
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -2
package/dist/index.cjs
CHANGED
|
@@ -12,6 +12,7 @@ let rehype_stringify = require("rehype-stringify");
|
|
|
12
12
|
rehype_stringify = require_vitepress.__toESM(rehype_stringify, 1);
|
|
13
13
|
let node_module = require("node:module");
|
|
14
14
|
let node_fs = require("node:fs");
|
|
15
|
+
node_fs = require_vitepress.__toESM(node_fs, 1);
|
|
15
16
|
let node_path = require("node:path");
|
|
16
17
|
node_path = require_vitepress.__toESM(node_path, 1);
|
|
17
18
|
let node_fs_promises = require("node:fs/promises");
|
|
@@ -24,6 +25,8 @@ let fs_promises = require("fs/promises");
|
|
|
24
25
|
fs_promises = require_vitepress.__toESM(fs_promises, 1);
|
|
25
26
|
let crypto = require("crypto");
|
|
26
27
|
crypto = require_vitepress.__toESM(crypto, 1);
|
|
28
|
+
let node_crypto = require("node:crypto");
|
|
29
|
+
let node_zlib = require("node:zlib");
|
|
27
30
|
let fs = require("fs");
|
|
28
31
|
fs = require_vitepress.__toESM(fs, 1);
|
|
29
32
|
let glob = require("glob");
|
|
@@ -252,6 +255,15 @@ async function highlightCode(html) {
|
|
|
252
255
|
const result = await (0, unified.unified)().use(rehypeParse$3, { fragment: true }).use(rehypeNativeHighlight).use(rehypeStringify$3).process(html);
|
|
253
256
|
return String(result);
|
|
254
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* Highlight every code block in a rendered page, preserving original classes
|
|
260
|
+
* and per-line metadata when the native document pass cannot read the markup.
|
|
261
|
+
*/
|
|
262
|
+
async function highlightPageHtml(html, mergeHighlightedCodeBlocks) {
|
|
263
|
+
const native = await highlightDocumentNatively(html);
|
|
264
|
+
if (native && native.skipped.length === 0) return native.html;
|
|
265
|
+
return mergeHighlightedCodeBlocks(html, await highlightCode(html));
|
|
266
|
+
}
|
|
255
267
|
//#endregion
|
|
256
268
|
//#region src/plugins/mermaid.ts
|
|
257
269
|
/**
|
|
@@ -343,6 +355,69 @@ function warnMissingMmdcOnce() {
|
|
|
343
355
|
*/
|
|
344
356
|
const mermaidClientScript = "";
|
|
345
357
|
//#endregion
|
|
358
|
+
//#region src/plugins/math.ts
|
|
359
|
+
/**
|
|
360
|
+
* Build-time KaTeX rendering for opt-in `$…$` / `$$…$$` math.
|
|
361
|
+
*
|
|
362
|
+
* KaTeX is an optional peer. Sites that never enable `math` do not install it,
|
|
363
|
+
* and the published plugin does not bundle or depend on it.
|
|
364
|
+
*/
|
|
365
|
+
const KATEX_ASSET_DIR = "__ox_katex__";
|
|
366
|
+
const MATH_TAG = /<(span|div) class="ox-math ox-math-(inline|block)" data-ox-tex="([^"]*)">[\s\S]*?<\/\1>/g;
|
|
367
|
+
let missingWarned = false;
|
|
368
|
+
/**
|
|
369
|
+
* Replaces rust `ox-math` placeholders with static KaTeX HTML.
|
|
370
|
+
* Leaves the escaped TeX fallback when `katex` is not installed.
|
|
371
|
+
*/
|
|
372
|
+
async function renderKatexMath(html) {
|
|
373
|
+
if (!html.includes("data-ox-tex")) return html;
|
|
374
|
+
const katex = loadKatex();
|
|
375
|
+
if (!katex) {
|
|
376
|
+
warnMissingKatexOnce();
|
|
377
|
+
return html;
|
|
378
|
+
}
|
|
379
|
+
return html.replace(MATH_TAG, (_match, tag, kind, encoded) => {
|
|
380
|
+
return `<${tag} class="ox-math ox-math-${kind}">${katex.renderToString(decodeHtmlAttr$2(encoded), {
|
|
381
|
+
displayMode: kind === "block",
|
|
382
|
+
throwOnError: false,
|
|
383
|
+
trust: false,
|
|
384
|
+
output: "htmlAndMathml"
|
|
385
|
+
})}</${tag}>`;
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
/** Directory that contains `katex.min.css` and `fonts/`, or `null`. */
|
|
389
|
+
function resolveKatexDist() {
|
|
390
|
+
for (const resolver of createKatexResolvers()) try {
|
|
391
|
+
return (0, node_path.join)((0, node_path.dirname)(resolver.resolve("katex/package.json")), "dist");
|
|
392
|
+
} catch {}
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
function loadKatex() {
|
|
396
|
+
for (const resolver of createKatexResolvers()) try {
|
|
397
|
+
const loaded = resolver(resolver.resolve("katex"));
|
|
398
|
+
if (typeof loaded.renderToString === "function") return loaded;
|
|
399
|
+
if (loaded.default && typeof loaded.default.renderToString === "function") return loaded.default;
|
|
400
|
+
} catch {}
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
function createKatexResolvers() {
|
|
404
|
+
const consumerRequire = (0, node_module.createRequire)((0, node_path.join)(process.cwd(), "noop.js"));
|
|
405
|
+
const resolvers = [consumerRequire];
|
|
406
|
+
try {
|
|
407
|
+
resolvers.push((0, node_module.createRequire)(consumerRequire.resolve("@ox-content/vite-plugin")));
|
|
408
|
+
} catch {}
|
|
409
|
+
resolvers.push((0, node_module.createRequire)(require("url").pathToFileURL(__filename).href));
|
|
410
|
+
return resolvers;
|
|
411
|
+
}
|
|
412
|
+
function decodeHtmlAttr$2(value) {
|
|
413
|
+
return value.replaceAll(""", "\"").replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&");
|
|
414
|
+
}
|
|
415
|
+
function warnMissingKatexOnce() {
|
|
416
|
+
if (missingWarned) return;
|
|
417
|
+
missingWarned = true;
|
|
418
|
+
console.warn("[ox-content] math is enabled but `katex` was not found. Install it with `npm i -D katex` to render LaTeX; escaped TeX placeholders are left as-is.");
|
|
419
|
+
}
|
|
420
|
+
//#endregion
|
|
346
421
|
//#region src/plugins/pm.ts
|
|
347
422
|
/**
|
|
348
423
|
* Package Manager Tabs Plugin
|
|
@@ -560,15 +635,15 @@ function assetRecord(src, media) {
|
|
|
560
635
|
//#region src/plugins/twitter/render.ts
|
|
561
636
|
function renderFetchedTweet(permalink, data, assets, options) {
|
|
562
637
|
const profile = `https://x.com/${encodeURIComponent(data.user.screen_name)}`;
|
|
563
|
-
const author = escapeHtml$
|
|
564
|
-
const handle = escapeHtml$
|
|
565
|
-
const avatar = assets.avatar ? `<img class="ox-tweet__avatar" src="${escapeAttribute(assets.avatar)}" alt="" width="48" height="48" loading="lazy" decoding="async">` : "";
|
|
638
|
+
const author = escapeHtml$6(data.user.name);
|
|
639
|
+
const handle = escapeHtml$6(data.user.screen_name);
|
|
640
|
+
const avatar = assets.avatar ? `<img class="ox-tweet__avatar" src="${escapeAttribute$2(assets.avatar)}" alt="" width="48" height="48" loading="lazy" decoding="async">` : "";
|
|
566
641
|
const media = renderMedia(assets);
|
|
567
642
|
const footer = renderFooter(permalink, data.created_at, options.lang);
|
|
568
643
|
return [
|
|
569
644
|
"<figure class=\"ox-tweet ox-tweet--fetched\">",
|
|
570
645
|
"<header class=\"ox-tweet__header\">",
|
|
571
|
-
`<a class="ox-tweet__profile" href="${escapeAttribute(profile)}" target="_blank" rel="noopener noreferrer">`,
|
|
646
|
+
`<a class="ox-tweet__profile" href="${escapeAttribute$2(profile)}" target="_blank" rel="noopener noreferrer">`,
|
|
572
647
|
avatar,
|
|
573
648
|
`<span class="ox-tweet__author-name">${author}</span>`,
|
|
574
649
|
`<span class="ox-tweet__author-handle">@${handle}</span>`,
|
|
@@ -591,7 +666,7 @@ function renderTweetText(data) {
|
|
|
591
666
|
if (entity.kind === "url") {
|
|
592
667
|
const href = entity.expanded_url ?? entity.url;
|
|
593
668
|
const label = entity.display_url ?? href;
|
|
594
|
-
output += `<a href="${escapeAttribute(href)}" target="_blank" rel="noopener noreferrer">${escapeHtml$
|
|
669
|
+
output += `<a href="${escapeAttribute$2(href)}" target="_blank" rel="noopener noreferrer">${escapeHtml$6(label)}</a>`;
|
|
595
670
|
}
|
|
596
671
|
cursor = entityEnd;
|
|
597
672
|
}
|
|
@@ -614,12 +689,12 @@ function renderMedia(assets) {
|
|
|
614
689
|
if (assets.media.length === 0) return "";
|
|
615
690
|
const images = assets.media.map((item) => {
|
|
616
691
|
const size = [item.width ? ` width="${item.width}"` : "", item.height ? ` height="${item.height}"` : ""].join("");
|
|
617
|
-
return `<img class="ox-tweet__media-item" src="${escapeAttribute(item.src)}" alt="${escapeAttribute(item.alt ?? "")}"${size} loading="lazy" decoding="async">`;
|
|
692
|
+
return `<img class="ox-tweet__media-item" src="${escapeAttribute$2(item.src)}" alt="${escapeAttribute$2(item.alt ?? "")}"${size} loading="lazy" decoding="async">`;
|
|
618
693
|
}).join("");
|
|
619
694
|
return `<div class="ox-tweet__media" data-count="${assets.media.length}">${images}</div>`;
|
|
620
695
|
}
|
|
621
696
|
function renderFooter(permalink, createdAt, lang) {
|
|
622
|
-
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>`;
|
|
697
|
+
if (!createdAt) return `<footer class="ox-tweet__footer"><a class="ox-tweet__permalink" href="${escapeAttribute$2(permalink)}" target="_blank" rel="noopener noreferrer">View on X</a></footer>`;
|
|
623
698
|
const date = new Date(createdAt);
|
|
624
699
|
if (Number.isNaN(date.valueOf())) return renderFooter(permalink, void 0, lang);
|
|
625
700
|
const iso = date.toISOString();
|
|
@@ -635,15 +710,15 @@ function renderFooter(permalink, createdAt, lang) {
|
|
|
635
710
|
timeZone: "UTC"
|
|
636
711
|
}).format(date);
|
|
637
712
|
}
|
|
638
|
-
return `<footer class="ox-tweet__footer"><a class="ox-tweet__permalink" href="${escapeAttribute(permalink)}" target="_blank" rel="noopener noreferrer"><time datetime="${iso}">${escapeHtml$
|
|
713
|
+
return `<footer class="ox-tweet__footer"><a class="ox-tweet__permalink" href="${escapeAttribute$2(permalink)}" target="_blank" rel="noopener noreferrer"><time datetime="${iso}">${escapeHtml$6(label)}</time></a></footer>`;
|
|
639
714
|
}
|
|
640
715
|
function escapeText(value) {
|
|
641
|
-
return escapeHtml$
|
|
716
|
+
return escapeHtml$6(value).replaceAll("\n", "<br>");
|
|
642
717
|
}
|
|
643
|
-
function escapeAttribute(value) {
|
|
644
|
-
return escapeHtml$
|
|
718
|
+
function escapeAttribute$2(value) {
|
|
719
|
+
return escapeHtml$6(value).replaceAll("`", "`");
|
|
645
720
|
}
|
|
646
|
-
function escapeHtml$
|
|
721
|
+
function escapeHtml$6(value) {
|
|
647
722
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
648
723
|
}
|
|
649
724
|
//#endregion
|
|
@@ -764,6 +839,11 @@ function sourceKey(source) {
|
|
|
764
839
|
function formatLineRange(lines) {
|
|
765
840
|
return lines.start === lines.end ? `L${lines.start}` : `L${lines.start}-L${lines.end}`;
|
|
766
841
|
}
|
|
842
|
+
function summarizeCommitMessage(message) {
|
|
843
|
+
const firstLine = message.split(/\r?\n/, 1)[0]?.replace(/\s+/g, " ").trim() ?? "";
|
|
844
|
+
if (firstLine.length <= 120) return firstLine;
|
|
845
|
+
return `${firstLine.slice(0, 119)}…`;
|
|
846
|
+
}
|
|
767
847
|
function parseGitHubLineRange(value) {
|
|
768
848
|
if (!value) return void 0;
|
|
769
849
|
const match = value.trim().match(/^#?L?(\d+)(?:-L?(\d+))?$/i);
|
|
@@ -838,6 +918,24 @@ function githubHeaders(options) {
|
|
|
838
918
|
if (options.token) headers.Authorization = `Bearer ${options.token}`;
|
|
839
919
|
return headers;
|
|
840
920
|
}
|
|
921
|
+
async function fetchSourceCommit(source, options) {
|
|
922
|
+
try {
|
|
923
|
+
const apiUrl = `https://api.github.com/repos/${source.repo}/commits?path=${encodeURIComponent(source.path)}&sha=${encodeURIComponent(source.ref)}&per_page=1`;
|
|
924
|
+
const response = await fetch(apiUrl, { headers: githubHeaders(options) });
|
|
925
|
+
if (!response.ok) return;
|
|
926
|
+
const item = (await response.json())[0];
|
|
927
|
+
const sha = item?.sha;
|
|
928
|
+
const message = item?.commit?.message ? summarizeCommitMessage(item.commit.message) : "";
|
|
929
|
+
if (!sha || !message) return;
|
|
930
|
+
return {
|
|
931
|
+
sha,
|
|
932
|
+
message,
|
|
933
|
+
html_url: item.html_url ?? `https://github.com/${source.repo}/commit/${sha}`
|
|
934
|
+
};
|
|
935
|
+
} catch {
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
841
939
|
/**
|
|
842
940
|
* Fetch repository data from GitHub API.
|
|
843
941
|
*/
|
|
@@ -876,7 +974,7 @@ async function fetchGitHubSource(source, options) {
|
|
|
876
974
|
}
|
|
877
975
|
try {
|
|
878
976
|
const apiUrl = `https://api.github.com/repos/${source.repo}/contents/${encodePath(source.path)}?ref=${encodeURIComponent(source.ref)}`;
|
|
879
|
-
const response = await fetch(apiUrl, { headers: githubHeaders(options) });
|
|
977
|
+
const [response, commit] = await Promise.all([fetch(apiUrl, { headers: githubHeaders(options) }), fetchSourceCommit(source, options)]);
|
|
880
978
|
if (!response.ok) {
|
|
881
979
|
console.warn(`Failed to fetch GitHub source ${source.permalink}: ${response.status}`);
|
|
882
980
|
return null;
|
|
@@ -893,7 +991,8 @@ async function fetchGitHubSource(source, options) {
|
|
|
893
991
|
content,
|
|
894
992
|
size: data.size ?? node_buffer.Buffer.byteLength(content),
|
|
895
993
|
html_url: data.html_url ?? source.permalink,
|
|
896
|
-
language: inferLanguage(source.path)
|
|
994
|
+
language: inferLanguage(source.path),
|
|
995
|
+
...commit ? { commit } : {}
|
|
897
996
|
};
|
|
898
997
|
if (options.cache) sourceCache.set(key, {
|
|
899
998
|
data: sourceData,
|
|
@@ -1187,19 +1286,76 @@ function normalizeSourceLines(content) {
|
|
|
1187
1286
|
if (lines.length > 1 && lines.at(-1) === "") lines.pop();
|
|
1188
1287
|
return lines.length > 0 ? lines : [""];
|
|
1189
1288
|
}
|
|
1289
|
+
function text(value) {
|
|
1290
|
+
return {
|
|
1291
|
+
type: "text",
|
|
1292
|
+
value
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
function createSourceLines(lines, start) {
|
|
1296
|
+
return lines.flatMap((line, index) => {
|
|
1297
|
+
const lineNumber = start + index;
|
|
1298
|
+
const span = {
|
|
1299
|
+
type: "element",
|
|
1300
|
+
tagName: "span",
|
|
1301
|
+
properties: {
|
|
1302
|
+
className: ["line"],
|
|
1303
|
+
"data-line": String(lineNumber),
|
|
1304
|
+
"data-line-number": String(lineNumber)
|
|
1305
|
+
},
|
|
1306
|
+
children: [text(line)]
|
|
1307
|
+
};
|
|
1308
|
+
return index === 0 ? [span] : [text("\n"), span];
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
function createCommitMeta(commit) {
|
|
1312
|
+
return {
|
|
1313
|
+
type: "element",
|
|
1314
|
+
tagName: "a",
|
|
1315
|
+
properties: {
|
|
1316
|
+
className: ["ox-github-code-commit"],
|
|
1317
|
+
href: commit.html_url,
|
|
1318
|
+
target: "_blank",
|
|
1319
|
+
rel: "noopener noreferrer",
|
|
1320
|
+
title: commit.message
|
|
1321
|
+
},
|
|
1322
|
+
children: [{
|
|
1323
|
+
type: "element",
|
|
1324
|
+
tagName: "span",
|
|
1325
|
+
properties: { className: ["ox-github-code-sha"] },
|
|
1326
|
+
children: [text(commit.sha.slice(0, 7))]
|
|
1327
|
+
}, {
|
|
1328
|
+
type: "element",
|
|
1329
|
+
tagName: "span",
|
|
1330
|
+
properties: { className: ["ox-github-code-commit-message"] },
|
|
1331
|
+
children: [text(commit.message)]
|
|
1332
|
+
}]
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1190
1335
|
function createGitHubSourceCard(source, lines, options) {
|
|
1191
1336
|
const allLines = normalizeSourceLines(source.content);
|
|
1192
1337
|
const start = Math.min(lines?.start ?? 1, allLines.length);
|
|
1193
1338
|
const end = lines ? Math.min(lines.end, allLines.length) : Math.min(allLines.length, options.maxSourceLines);
|
|
1194
1339
|
const selectedLines = allLines.slice(start - 1, end);
|
|
1195
|
-
const
|
|
1340
|
+
const loc = selectedLines.length;
|
|
1341
|
+
const rangeLabel = formatLineRange({
|
|
1196
1342
|
start,
|
|
1197
1343
|
end
|
|
1198
|
-
};
|
|
1199
|
-
const
|
|
1200
|
-
const rangeLabel = formatLineRange(lineRange);
|
|
1201
|
-
const locLabel = !lines && end < allLines.length ? `${rangeLabel} of ${allLines.length} LOC` : `${rangeLabel} - ${loc} LOC`;
|
|
1344
|
+
});
|
|
1345
|
+
const locLabel = !lines && end < allLines.length ? `${rangeLabel} of ${allLines.length} LOC` : `${rangeLabel} · ${loc} LOC`;
|
|
1202
1346
|
const languageClass = source.language ? [`language-${source.language}`] : [];
|
|
1347
|
+
const heading = [{
|
|
1348
|
+
type: "element",
|
|
1349
|
+
tagName: "a",
|
|
1350
|
+
properties: {
|
|
1351
|
+
className: ["ox-github-code-title"],
|
|
1352
|
+
href: source.permalink,
|
|
1353
|
+
target: "_blank",
|
|
1354
|
+
rel: "noopener noreferrer"
|
|
1355
|
+
},
|
|
1356
|
+
children: [text(`${source.repo}/${source.path}`)]
|
|
1357
|
+
}];
|
|
1358
|
+
if (source.commit) heading.push(createCommitMeta(source.commit));
|
|
1203
1359
|
return {
|
|
1204
1360
|
type: "element",
|
|
1205
1361
|
tagName: "figure",
|
|
@@ -1214,65 +1370,34 @@ function createGitHubSourceCard(source, lines, options) {
|
|
|
1214
1370
|
properties: { className: ["ox-github-code-header"] },
|
|
1215
1371
|
children: [{
|
|
1216
1372
|
type: "element",
|
|
1217
|
-
tagName: "
|
|
1218
|
-
properties: {
|
|
1219
|
-
|
|
1220
|
-
href: source.permalink,
|
|
1221
|
-
target: "_blank",
|
|
1222
|
-
rel: "noopener noreferrer"
|
|
1223
|
-
},
|
|
1224
|
-
children: [{
|
|
1225
|
-
type: "text",
|
|
1226
|
-
value: `${source.repo}/${source.path}`
|
|
1227
|
-
}]
|
|
1373
|
+
tagName: "div",
|
|
1374
|
+
properties: { className: ["ox-github-code-heading"] },
|
|
1375
|
+
children: heading
|
|
1228
1376
|
}, {
|
|
1229
1377
|
type: "element",
|
|
1230
1378
|
tagName: "span",
|
|
1231
1379
|
properties: { className: ["ox-github-code-loc"] },
|
|
1232
|
-
children: [
|
|
1233
|
-
type: "text",
|
|
1234
|
-
value: locLabel
|
|
1235
|
-
}]
|
|
1380
|
+
children: [text(locLabel)]
|
|
1236
1381
|
}]
|
|
1237
1382
|
}, {
|
|
1238
1383
|
type: "element",
|
|
1239
1384
|
tagName: "pre",
|
|
1240
1385
|
properties: {
|
|
1241
|
-
className: [
|
|
1386
|
+
className: [
|
|
1387
|
+
"ox-github-code-block",
|
|
1388
|
+
"ox-code-block",
|
|
1389
|
+
"line-numbers-mode",
|
|
1390
|
+
...languageClass
|
|
1391
|
+
],
|
|
1392
|
+
"data-line-numbers": "true",
|
|
1393
|
+
"data-line-number-start": String(start),
|
|
1242
1394
|
...source.language ? { "data-language": source.language } : {}
|
|
1243
1395
|
},
|
|
1244
1396
|
children: [{
|
|
1245
1397
|
type: "element",
|
|
1246
1398
|
tagName: "code",
|
|
1247
1399
|
properties: { className: languageClass },
|
|
1248
|
-
children: selectedLines
|
|
1249
|
-
const lineNumber = start + index;
|
|
1250
|
-
return {
|
|
1251
|
-
type: "element",
|
|
1252
|
-
tagName: "span",
|
|
1253
|
-
properties: {
|
|
1254
|
-
className: ["line", "ox-github-code-line"],
|
|
1255
|
-
"data-line": String(lineNumber)
|
|
1256
|
-
},
|
|
1257
|
-
children: [{
|
|
1258
|
-
type: "element",
|
|
1259
|
-
tagName: "span",
|
|
1260
|
-
properties: { className: ["ox-github-code-line-number"] },
|
|
1261
|
-
children: [{
|
|
1262
|
-
type: "text",
|
|
1263
|
-
value: String(lineNumber)
|
|
1264
|
-
}]
|
|
1265
|
-
}, {
|
|
1266
|
-
type: "element",
|
|
1267
|
-
tagName: "span",
|
|
1268
|
-
properties: { className: ["ox-github-code-line-content"] },
|
|
1269
|
-
children: [{
|
|
1270
|
-
type: "text",
|
|
1271
|
-
value: line || " "
|
|
1272
|
-
}]
|
|
1273
|
-
}]
|
|
1274
|
-
};
|
|
1275
|
-
})
|
|
1400
|
+
children: createSourceLines(selectedLines, start)
|
|
1276
1401
|
}]
|
|
1277
1402
|
}]
|
|
1278
1403
|
};
|
|
@@ -1871,6 +1996,368 @@ function normalizeDiagnostic(diagnostic) {
|
|
|
1871
1996
|
};
|
|
1872
1997
|
}
|
|
1873
1998
|
//#endregion
|
|
1999
|
+
//#region src/typed-hover-generate.ts
|
|
2000
|
+
async function loadTsgoApi() {
|
|
2001
|
+
try {
|
|
2002
|
+
return await Promise.resolve().then(() => require("./api.cjs"));
|
|
2003
|
+
} catch {
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
async function generateTypedHoverAttachments(fences, tsgoCommand) {
|
|
2008
|
+
if (fences.length === 0) return [];
|
|
2009
|
+
const apiMod = await loadTsgoApi();
|
|
2010
|
+
if (!apiMod) return fences.map((fence) => ({
|
|
2011
|
+
code: fence.code,
|
|
2012
|
+
hovers: []
|
|
2013
|
+
}));
|
|
2014
|
+
const temp = await (0, node_fs_promises.mkdtemp)((0, node_path.join)((0, node_os.tmpdir)(), "ox-content-typed-hover-"));
|
|
2015
|
+
const api = new apiMod.API({
|
|
2016
|
+
cwd: temp,
|
|
2017
|
+
...tsgoCommand ? { tsserverPath: tsgoCommand } : {}
|
|
2018
|
+
});
|
|
2019
|
+
try {
|
|
2020
|
+
const files = await Promise.all(fences.map(async (fence, index) => {
|
|
2021
|
+
const extension = fence.language.toLowerCase() === "tsx" ? "tsx" : "ts";
|
|
2022
|
+
const file = (0, node_path.join)(temp, `snippet-${index}.${extension}`);
|
|
2023
|
+
await (0, node_fs_promises.writeFile)(file, fence.code);
|
|
2024
|
+
return {
|
|
2025
|
+
fence,
|
|
2026
|
+
file
|
|
2027
|
+
};
|
|
2028
|
+
}));
|
|
2029
|
+
const snapshot = api.updateSnapshot({ openFiles: files.map((item) => item.file) });
|
|
2030
|
+
return files.map(({ fence, file }) => {
|
|
2031
|
+
const project = snapshot.getDefaultProjectForFile(file);
|
|
2032
|
+
if (!project) return {
|
|
2033
|
+
code: fence.code,
|
|
2034
|
+
hovers: []
|
|
2035
|
+
};
|
|
2036
|
+
const hovers = [];
|
|
2037
|
+
for (const ident of collectIdentifierRanges(fence.code)) {
|
|
2038
|
+
const type = project.checker.getTypeAtPosition(file, ident.start);
|
|
2039
|
+
if (!type || type.isErrorType?.()) continue;
|
|
2040
|
+
const widened = project.checker.getBaseTypeOfLiteralType(type) ?? type;
|
|
2041
|
+
const text = project.checker.typeToString(widened);
|
|
2042
|
+
if (text) hovers.push({
|
|
2043
|
+
start: ident.start,
|
|
2044
|
+
end: ident.end,
|
|
2045
|
+
type: text
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
return {
|
|
2049
|
+
code: fence.code,
|
|
2050
|
+
hovers
|
|
2051
|
+
};
|
|
2052
|
+
});
|
|
2053
|
+
} finally {
|
|
2054
|
+
api.close();
|
|
2055
|
+
await (0, node_fs_promises.rm)(temp, {
|
|
2056
|
+
recursive: true,
|
|
2057
|
+
force: true
|
|
2058
|
+
});
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
const IDENTIFIER_KEYWORDS = /* @__PURE__ */ new Set([
|
|
2062
|
+
"abstract",
|
|
2063
|
+
"any",
|
|
2064
|
+
"as",
|
|
2065
|
+
"asserts",
|
|
2066
|
+
"async",
|
|
2067
|
+
"await",
|
|
2068
|
+
"bigint",
|
|
2069
|
+
"boolean",
|
|
2070
|
+
"break",
|
|
2071
|
+
"case",
|
|
2072
|
+
"catch",
|
|
2073
|
+
"class",
|
|
2074
|
+
"const",
|
|
2075
|
+
"continue",
|
|
2076
|
+
"debugger",
|
|
2077
|
+
"declare",
|
|
2078
|
+
"default",
|
|
2079
|
+
"delete",
|
|
2080
|
+
"do",
|
|
2081
|
+
"else",
|
|
2082
|
+
"enum",
|
|
2083
|
+
"export",
|
|
2084
|
+
"extends",
|
|
2085
|
+
"false",
|
|
2086
|
+
"finally",
|
|
2087
|
+
"for",
|
|
2088
|
+
"from",
|
|
2089
|
+
"function",
|
|
2090
|
+
"if",
|
|
2091
|
+
"implements",
|
|
2092
|
+
"import",
|
|
2093
|
+
"in",
|
|
2094
|
+
"infer",
|
|
2095
|
+
"instanceof",
|
|
2096
|
+
"interface",
|
|
2097
|
+
"is",
|
|
2098
|
+
"keyof",
|
|
2099
|
+
"let",
|
|
2100
|
+
"never",
|
|
2101
|
+
"new",
|
|
2102
|
+
"null",
|
|
2103
|
+
"number",
|
|
2104
|
+
"object",
|
|
2105
|
+
"of",
|
|
2106
|
+
"package",
|
|
2107
|
+
"private",
|
|
2108
|
+
"protected",
|
|
2109
|
+
"public",
|
|
2110
|
+
"readonly",
|
|
2111
|
+
"return",
|
|
2112
|
+
"satisfies",
|
|
2113
|
+
"static",
|
|
2114
|
+
"string",
|
|
2115
|
+
"super",
|
|
2116
|
+
"switch",
|
|
2117
|
+
"symbol",
|
|
2118
|
+
"this",
|
|
2119
|
+
"throw",
|
|
2120
|
+
"true",
|
|
2121
|
+
"try",
|
|
2122
|
+
"type",
|
|
2123
|
+
"typeof",
|
|
2124
|
+
"undefined",
|
|
2125
|
+
"unique",
|
|
2126
|
+
"unknown",
|
|
2127
|
+
"using",
|
|
2128
|
+
"var",
|
|
2129
|
+
"void",
|
|
2130
|
+
"while",
|
|
2131
|
+
"with",
|
|
2132
|
+
"yield"
|
|
2133
|
+
]);
|
|
2134
|
+
function collectIdentifierRanges(code) {
|
|
2135
|
+
const ranges = [];
|
|
2136
|
+
let index = 0;
|
|
2137
|
+
while (index < code.length) {
|
|
2138
|
+
const char = code[index];
|
|
2139
|
+
if (char === "/" && code[index + 1] === "/") {
|
|
2140
|
+
index = code.indexOf("\n", index);
|
|
2141
|
+
if (index === -1) break;
|
|
2142
|
+
continue;
|
|
2143
|
+
}
|
|
2144
|
+
if (char === "/" && code[index + 1] === "*") {
|
|
2145
|
+
const close = code.indexOf("*/", index + 2);
|
|
2146
|
+
index = close === -1 ? code.length : close + 2;
|
|
2147
|
+
continue;
|
|
2148
|
+
}
|
|
2149
|
+
if (char === "\"" || char === "'" || char === "`") {
|
|
2150
|
+
index = skipQuoted(code, index, char);
|
|
2151
|
+
continue;
|
|
2152
|
+
}
|
|
2153
|
+
if (/[A-Za-z_$]/.test(char)) {
|
|
2154
|
+
const start = index;
|
|
2155
|
+
index += 1;
|
|
2156
|
+
while (index < code.length && /[\w$]/.test(code[index])) index += 1;
|
|
2157
|
+
const name = code.slice(start, index);
|
|
2158
|
+
if (!IDENTIFIER_KEYWORDS.has(name)) ranges.push({
|
|
2159
|
+
start,
|
|
2160
|
+
end: index
|
|
2161
|
+
});
|
|
2162
|
+
continue;
|
|
2163
|
+
}
|
|
2164
|
+
index += 1;
|
|
2165
|
+
}
|
|
2166
|
+
return ranges;
|
|
2167
|
+
}
|
|
2168
|
+
function skipQuoted(code, start, quote) {
|
|
2169
|
+
let index = start + 1;
|
|
2170
|
+
while (index < code.length) {
|
|
2171
|
+
if (code[index] === "\\") {
|
|
2172
|
+
index += 2;
|
|
2173
|
+
continue;
|
|
2174
|
+
}
|
|
2175
|
+
if (code[index] === quote) return index + 1;
|
|
2176
|
+
index += 1;
|
|
2177
|
+
}
|
|
2178
|
+
return code.length;
|
|
2179
|
+
}
|
|
2180
|
+
//#endregion
|
|
2181
|
+
//#region src/typed-hover.ts
|
|
2182
|
+
const DEFAULT_LANGUAGES$1 = ["ts", "tsx"];
|
|
2183
|
+
function resolveTypedHoverOptions(options) {
|
|
2184
|
+
if (!options) return {
|
|
2185
|
+
enabled: false,
|
|
2186
|
+
languages: [...DEFAULT_LANGUAGES$1]
|
|
2187
|
+
};
|
|
2188
|
+
if (options === true) return {
|
|
2189
|
+
enabled: true,
|
|
2190
|
+
languages: [...DEFAULT_LANGUAGES$1]
|
|
2191
|
+
};
|
|
2192
|
+
return {
|
|
2193
|
+
enabled: options.enabled ?? true,
|
|
2194
|
+
languages: options.languages ?? [...DEFAULT_LANGUAGES$1],
|
|
2195
|
+
tsgoCommand: options.tsgoCommand
|
|
2196
|
+
};
|
|
2197
|
+
}
|
|
2198
|
+
function hasTypedHoverMeta(meta) {
|
|
2199
|
+
return meta.split(/\s+/).some((token) => token === "twoslash");
|
|
2200
|
+
}
|
|
2201
|
+
function serializeTypedHoverPayload(payload) {
|
|
2202
|
+
return JSON.stringify(payload).replace(/</g, "\\u003c").replace(/>/g, "\\u003e");
|
|
2203
|
+
}
|
|
2204
|
+
async function applyTypedHover(source, html, options) {
|
|
2205
|
+
if (!options?.enabled || !source.includes("```")) return html;
|
|
2206
|
+
const languages = new Set(options.languages.map((language) => language.toLowerCase()));
|
|
2207
|
+
const fences = (await extractCodeBlocks(source)).filter((block) => {
|
|
2208
|
+
return languages.has(block.language.toLowerCase()) && hasTypedHoverMeta(block.meta);
|
|
2209
|
+
});
|
|
2210
|
+
if (fences.length === 0) return html;
|
|
2211
|
+
try {
|
|
2212
|
+
return attachTypedHoverPayloads(html, await generateTypedHoverAttachments(fences, options.tsgoCommand));
|
|
2213
|
+
} catch {
|
|
2214
|
+
return html;
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
function attachTypedHoverPayloads(html, attachments) {
|
|
2218
|
+
const unused = attachments.filter((item) => item.hovers.length > 0);
|
|
2219
|
+
if (unused.length === 0) return html;
|
|
2220
|
+
let attached = 0;
|
|
2221
|
+
const next = html.replace(/<pre(\b[^>]*)><code(\b[^>]*)>([\s\S]*?)<\/code><\/pre>/g, (full, preAttrs, codeAttrs, inner) => {
|
|
2222
|
+
if (unused.length === 0) return full;
|
|
2223
|
+
if (!isTypeScriptFence(codeAttrs)) return full;
|
|
2224
|
+
const text = decodeHtmlEntities(inner.replace(/<[^>]+>/g, ""));
|
|
2225
|
+
const index = unused.findIndex((item) => normalizeFenceText(item.code) === normalizeFenceText(text));
|
|
2226
|
+
if (index === -1) return full;
|
|
2227
|
+
const item = unused.splice(index, 1)[0];
|
|
2228
|
+
if (!item) return full;
|
|
2229
|
+
attached += 1;
|
|
2230
|
+
const wrapped = wrapHoverRanges(inner, item.hovers);
|
|
2231
|
+
return `${withTypedHoverClass(`<pre${preAttrs}`)}><code${codeAttrs}>${wrapped}</code></pre>\n<script type="application/json" class="ox-typed-hover-data">${serializeTypedHoverPayload({ hovers: item.hovers })}<\/script>`;
|
|
2232
|
+
});
|
|
2233
|
+
if (attached === 0) return html;
|
|
2234
|
+
return `${next}${TYPED_HOVER_STYLE}${TYPED_HOVER_CLIENT}`;
|
|
2235
|
+
}
|
|
2236
|
+
function isTypeScriptFence(codeAttrs) {
|
|
2237
|
+
const match = codeAttrs.match(/class="([^"]*)"/);
|
|
2238
|
+
if (!match?.[1]) return false;
|
|
2239
|
+
return match[1].split(/\s+/).some((token) => {
|
|
2240
|
+
const language = token.replace(/^language-/, "").toLowerCase();
|
|
2241
|
+
return language === "ts" || language === "tsx" || language === "typescript" || language === "typescriptreact";
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
2244
|
+
function withTypedHoverClass(openPre) {
|
|
2245
|
+
if (/\bclass="/.test(openPre)) return openPre.replace(/\bclass="([^"]*)"/, (_, classes) => {
|
|
2246
|
+
return `class="${classes} ox-typed-hover"`;
|
|
2247
|
+
});
|
|
2248
|
+
return `${openPre} class="ox-typed-hover"`;
|
|
2249
|
+
}
|
|
2250
|
+
function wrapHoverRanges(inner, hovers) {
|
|
2251
|
+
const ranges = [...hovers].sort((a, b) => a.start - b.start || b.end - a.end);
|
|
2252
|
+
let output = "";
|
|
2253
|
+
let htmlIndex = 0;
|
|
2254
|
+
let sourceOffset = 0;
|
|
2255
|
+
let rangeIndex = 0;
|
|
2256
|
+
let openUntil = -1;
|
|
2257
|
+
let openHoverIndex = -1;
|
|
2258
|
+
const startRange = () => {
|
|
2259
|
+
while (rangeIndex < ranges.length && ranges[rangeIndex].start < sourceOffset) rangeIndex += 1;
|
|
2260
|
+
const range = ranges[rangeIndex];
|
|
2261
|
+
if (!range || range.start !== sourceOffset || openUntil !== -1) return;
|
|
2262
|
+
output += `<span class="ox-typed-hover-token" tabindex="0" data-ox-typed-hover="${rangeIndex}">`;
|
|
2263
|
+
openUntil = range.end;
|
|
2264
|
+
openHoverIndex = rangeIndex;
|
|
2265
|
+
rangeIndex += 1;
|
|
2266
|
+
};
|
|
2267
|
+
const endRange = () => {
|
|
2268
|
+
if (openUntil === sourceOffset && openHoverIndex !== -1) {
|
|
2269
|
+
output += "</span>";
|
|
2270
|
+
openUntil = -1;
|
|
2271
|
+
openHoverIndex = -1;
|
|
2272
|
+
}
|
|
2273
|
+
};
|
|
2274
|
+
while (htmlIndex < inner.length) {
|
|
2275
|
+
const char = inner[htmlIndex];
|
|
2276
|
+
if (char === "<") {
|
|
2277
|
+
const close = inner.indexOf(">", htmlIndex);
|
|
2278
|
+
const tag = close === -1 ? inner.slice(htmlIndex) : inner.slice(htmlIndex, close + 1);
|
|
2279
|
+
output += tag;
|
|
2280
|
+
htmlIndex += tag.length;
|
|
2281
|
+
continue;
|
|
2282
|
+
}
|
|
2283
|
+
startRange();
|
|
2284
|
+
if (char === "&") {
|
|
2285
|
+
const semi = inner.indexOf(";", htmlIndex);
|
|
2286
|
+
const entity = semi === -1 ? inner.slice(htmlIndex) : inner.slice(htmlIndex, semi + 1);
|
|
2287
|
+
output += entity;
|
|
2288
|
+
htmlIndex += entity.length;
|
|
2289
|
+
sourceOffset += 1;
|
|
2290
|
+
endRange();
|
|
2291
|
+
continue;
|
|
2292
|
+
}
|
|
2293
|
+
output += char;
|
|
2294
|
+
htmlIndex += 1;
|
|
2295
|
+
sourceOffset += 1;
|
|
2296
|
+
endRange();
|
|
2297
|
+
}
|
|
2298
|
+
if (openHoverIndex !== -1) output += "</span>";
|
|
2299
|
+
return output;
|
|
2300
|
+
}
|
|
2301
|
+
function normalizeFenceText(value) {
|
|
2302
|
+
return decodeHtmlEntities(value).replace(/\r\n/g, "\n").trim();
|
|
2303
|
+
}
|
|
2304
|
+
function decodeHtmlEntities(value) {
|
|
2305
|
+
return value.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&").replace(/"/g, "\"").replace(/'/g, "'");
|
|
2306
|
+
}
|
|
2307
|
+
const TYPED_HOVER_STYLE = `<style data-ox-typed-hover-style>.ox-typed-hover-token{cursor:help;text-decoration:underline dotted}.ox-typed-hover-overlay{position:fixed;z-index:50;max-width:36rem;padding:.35rem .55rem;border:1px solid #444;border-radius:4px;background:#1e1e1e;color:#d4d4d4;font:12px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;white-space:pre-wrap;pointer-events:none}</style>`;
|
|
2308
|
+
const TYPED_HOVER_CLIENT = `<script data-ox-typed-hover-runtime>(function(){if(window.__oxTypedHover)return;window.__oxTypedHover=1;var tip=document.createElement("div");tip.className="ox-typed-hover-overlay";tip.setAttribute("role","tooltip");tip.hidden=true;document.body.appendChild(tip);function payload(token){var pre=token.closest(".ox-typed-hover");var data=pre&&pre.nextElementSibling;if(!data||!data.classList.contains("ox-typed-hover-data"))return null;try{return JSON.parse(data.textContent||"")}catch(e){return null}}function show(token){var data=payload(token);var item=data&&data.hovers[Number(token.getAttribute("data-ox-typed-hover"))];if(!item)return;tip.textContent=item.type;tip.hidden=false;var box=token.getBoundingClientRect();tip.style.left=Math.max(8,box.left)+"px";tip.style.top=Math.max(8,box.top-tip.offsetHeight-8)+"px"}function hide(){tip.hidden=true}document.addEventListener("mouseover",function(e){var t=e.target.closest(".ox-typed-hover-token");if(t)show(t)});document.addEventListener("mouseout",function(e){var t=e.target.closest(".ox-typed-hover-token");if(t&&!t.contains(e.relatedTarget))hide()});document.addEventListener("focusin",function(e){var t=e.target.closest(".ox-typed-hover-token");if(t)show(t)});document.addEventListener("focusout",function(e){if(!e.relatedTarget||!e.relatedTarget.closest(".ox-typed-hover-token"))hide()});document.addEventListener("keydown",function(e){if(e.key==="Escape")hide()})})();<\/script>`;
|
|
2309
|
+
//#endregion
|
|
2310
|
+
//#region src/file-tree-options.ts
|
|
2311
|
+
const disabled = {
|
|
2312
|
+
enabled: false,
|
|
2313
|
+
defaultOpen: true,
|
|
2314
|
+
icons: true
|
|
2315
|
+
};
|
|
2316
|
+
function resolveFileTreeOptions(options) {
|
|
2317
|
+
if (!options) return { ...disabled };
|
|
2318
|
+
if (options === true) return enabledDefaults();
|
|
2319
|
+
if (options.enabled === false) return {
|
|
2320
|
+
...enabledDefaults(),
|
|
2321
|
+
enabled: false,
|
|
2322
|
+
...resolveIcons(options.icons)
|
|
2323
|
+
};
|
|
2324
|
+
return {
|
|
2325
|
+
enabled: options.enabled ?? true,
|
|
2326
|
+
defaultOpen: options.defaultOpen ?? true,
|
|
2327
|
+
...resolveIcons(options.icons)
|
|
2328
|
+
};
|
|
2329
|
+
}
|
|
2330
|
+
function toJsFileTreeOptions(options) {
|
|
2331
|
+
if (!options?.enabled) return void 0;
|
|
2332
|
+
return {
|
|
2333
|
+
enabled: true,
|
|
2334
|
+
defaultOpen: options.defaultOpen,
|
|
2335
|
+
icons: options.icons,
|
|
2336
|
+
iconFolder: options.iconFolder,
|
|
2337
|
+
iconFolderOpen: options.iconFolderOpen,
|
|
2338
|
+
iconFile: options.iconFile,
|
|
2339
|
+
iconFiles: options.iconFiles
|
|
2340
|
+
};
|
|
2341
|
+
}
|
|
2342
|
+
function enabledDefaults() {
|
|
2343
|
+
return {
|
|
2344
|
+
enabled: true,
|
|
2345
|
+
defaultOpen: true,
|
|
2346
|
+
icons: true
|
|
2347
|
+
};
|
|
2348
|
+
}
|
|
2349
|
+
function resolveIcons(icons) {
|
|
2350
|
+
if (icons === false) return { icons: false };
|
|
2351
|
+
if (icons === true || icons == null) return { icons: true };
|
|
2352
|
+
return {
|
|
2353
|
+
icons: true,
|
|
2354
|
+
iconFolder: icons.folder,
|
|
2355
|
+
iconFolderOpen: icons.folderOpen,
|
|
2356
|
+
iconFile: icons.file,
|
|
2357
|
+
iconFiles: icons.files
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
2360
|
+
//#endregion
|
|
1874
2361
|
//#region src/transform.ts
|
|
1875
2362
|
/**
|
|
1876
2363
|
* The NAPI load, cached as the promise rather than as its result.
|
|
@@ -1981,7 +2468,7 @@ async function transformMarkdown(source, filePath, options, ssgOptions) {
|
|
|
1981
2468
|
} : void 0,
|
|
1982
2469
|
cards: options.cards?.enabled ? { enabled: true } : void 0,
|
|
1983
2470
|
steps: options.steps?.enabled ? { enabled: true } : void 0,
|
|
1984
|
-
fileTree: options.fileTree
|
|
2471
|
+
fileTree: toJsFileTreeOptions(options.fileTree),
|
|
1985
2472
|
sanitize: void 0,
|
|
1986
2473
|
editThisPage: options.editThisPage?.enabled ? {
|
|
1987
2474
|
enabled: true,
|
|
@@ -1999,26 +2486,27 @@ async function transformMarkdown(source, filePath, options, ssgOptions) {
|
|
|
1999
2486
|
if (options.mermaid) html = await transformMermaidStatic(html);
|
|
2000
2487
|
const { html: protectedHtml, svgs } = protectMermaidSvgs(html);
|
|
2001
2488
|
html = protectedHtml;
|
|
2002
|
-
if (options.highlight)
|
|
2003
|
-
const native = await highlightDocumentNatively(html);
|
|
2004
|
-
if (native && native.skipped.length === 0) html = native.html;
|
|
2005
|
-
else {
|
|
2006
|
-
const originalHtml = html;
|
|
2007
|
-
const highlightedHtml = await highlightCode(html);
|
|
2008
|
-
html = napi.mergeHighlightedCodeBlocks(originalHtml, highlightedHtml);
|
|
2009
|
-
}
|
|
2010
|
-
}
|
|
2489
|
+
if (options.highlight) html = await highlightPageHtml(html, napi.mergeHighlightedCodeBlocks);
|
|
2011
2490
|
html = await transformBuiltinEmbeds(html, options.embeds ?? {
|
|
2012
2491
|
github: {},
|
|
2013
2492
|
openGraph: {}
|
|
2014
2493
|
});
|
|
2494
|
+
if (options.highlight && html.includes("ox-github-code-block")) html = await highlightPageHtml(html, napi.mergeHighlightedCodeBlocks);
|
|
2015
2495
|
html = restoreMermaidSvgs(html, svgs);
|
|
2016
2496
|
if (options.sanitize?.enabled) html = napi.sanitizeHtml(html, toJsSanitizeOptions(options.sanitize));
|
|
2497
|
+
if (isMathEnabled(options.math)) html = await renderKatexMath(html);
|
|
2498
|
+
const imports = result.imports ?? [];
|
|
2499
|
+
const exports = result.exports ?? [];
|
|
2500
|
+
const components = result.components ?? [];
|
|
2501
|
+
html = await applyTypedHover(source, html, options.typedHover);
|
|
2017
2502
|
return {
|
|
2018
|
-
code: generateModuleCode(html, frontmatter, toc,
|
|
2503
|
+
code: generateModuleCode(html, frontmatter, toc, imports, exports, components, filePath),
|
|
2019
2504
|
html,
|
|
2020
2505
|
frontmatter,
|
|
2021
|
-
toc
|
|
2506
|
+
toc,
|
|
2507
|
+
imports,
|
|
2508
|
+
exports,
|
|
2509
|
+
components
|
|
2022
2510
|
};
|
|
2023
2511
|
}
|
|
2024
2512
|
async function runCodeBlockTypecheck(source, options) {
|
|
@@ -2077,8 +2565,11 @@ function normalizeTocEntry(entry) {
|
|
|
2077
2565
|
}
|
|
2078
2566
|
/**
|
|
2079
2567
|
* Generates the JavaScript module code.
|
|
2568
|
+
*
|
|
2569
|
+
* MDX metadata is serialized as JSON. User `import` / `export` source is never
|
|
2570
|
+
* emitted as live JavaScript, so transform does not execute module side effects.
|
|
2080
2571
|
*/
|
|
2081
|
-
function generateModuleCode(html, frontmatter, toc,
|
|
2572
|
+
function generateModuleCode(html, frontmatter, toc, imports, exports, components, filePath) {
|
|
2082
2573
|
return `
|
|
2083
2574
|
// Generated by @ox-content/vite-plugin
|
|
2084
2575
|
// Source: ${filePath}
|
|
@@ -2098,6 +2589,21 @@ export const frontmatter = ${JSON.stringify(frontmatter)};
|
|
|
2098
2589
|
*/
|
|
2099
2590
|
export const toc = ${JSON.stringify(toc)};
|
|
2100
2591
|
|
|
2592
|
+
/**
|
|
2593
|
+
* MDX import statements collected from the AST.
|
|
2594
|
+
*/
|
|
2595
|
+
export const imports = ${JSON.stringify(imports)};
|
|
2596
|
+
|
|
2597
|
+
/**
|
|
2598
|
+
* MDX export names collected from the AST.
|
|
2599
|
+
*/
|
|
2600
|
+
export const exports = ${JSON.stringify(exports)};
|
|
2601
|
+
|
|
2602
|
+
/**
|
|
2603
|
+
* Unique JSX component names collected from the AST.
|
|
2604
|
+
*/
|
|
2605
|
+
export const components = ${JSON.stringify(components)};
|
|
2606
|
+
|
|
2101
2607
|
/**
|
|
2102
2608
|
* Default export with all data.
|
|
2103
2609
|
*/
|
|
@@ -2105,6 +2611,9 @@ export default {
|
|
|
2105
2611
|
html,
|
|
2106
2612
|
frontmatter,
|
|
2107
2613
|
toc,
|
|
2614
|
+
imports,
|
|
2615
|
+
exports,
|
|
2616
|
+
components,
|
|
2108
2617
|
};
|
|
2109
2618
|
|
|
2110
2619
|
// HMR support
|
|
@@ -2555,7 +3064,7 @@ function formatChromiumUnavailableDetail(err) {
|
|
|
2555
3064
|
/**
|
|
2556
3065
|
* Escapes HTML special characters.
|
|
2557
3066
|
*/
|
|
2558
|
-
function escapeHtml$
|
|
3067
|
+
function escapeHtml$5(str) {
|
|
2559
3068
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2560
3069
|
}
|
|
2561
3070
|
function normalizeBrandValue(str) {
|
|
@@ -2618,12 +3127,12 @@ function getDefaultTemplate() {
|
|
|
2618
3127
|
const isBrandCard = normalizeBrandValue(title) === normalizeBrandValue(rawBrand);
|
|
2619
3128
|
const heroTitle = isBrandCard ? "High-performance Markdown toolkit" : title;
|
|
2620
3129
|
const heroDescription = isBrandCard ? "Rust-powered docs and high-performance Markdown tooling." : description && description.trim().length > 0 ? description : "Rust-powered docs and Markdown tooling.";
|
|
2621
|
-
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$
|
|
3130
|
+
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$5(heroDescription)}</p>` : "";
|
|
2622
3131
|
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;">
|
|
2623
3132
|
<div style="position:relative;z-index:1;display:flex;flex-direction:column;height:100%;">
|
|
2624
3133
|
<div style="display:flex;align-items:flex-start;">${renderWordmarkSvg()}</div>
|
|
2625
3134
|
<div style="display:flex;flex-direction:column;justify-content:center;gap:24px;max-width:860px;flex:1;padding:22px 0 0;">
|
|
2626
|
-
<h1 style="font-size:78px;font-weight:700;color:#eff6ff;line-height:1.02;letter-spacing:-0.055em;margin:0;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;">${escapeHtml$
|
|
3135
|
+
<h1 style="font-size:78px;font-weight:700;color:#eff6ff;line-height:1.02;letter-spacing:-0.055em;margin:0;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;">${escapeHtml$5(heroTitle)}</h1>
|
|
2627
3136
|
${descriptionHtml}
|
|
2628
3137
|
</div>
|
|
2629
3138
|
</div>
|
|
@@ -3154,6 +3663,80 @@ async function renderSinglePage(entry, templateFn, templateSource, options, cach
|
|
|
3154
3663
|
}
|
|
3155
3664
|
}
|
|
3156
3665
|
//#endregion
|
|
3666
|
+
//#region src/plugins/math-assets.ts
|
|
3667
|
+
/**
|
|
3668
|
+
* Serve and copy KaTeX CSS/fonts only when the optional `katex` package exists.
|
|
3669
|
+
*/
|
|
3670
|
+
/**
|
|
3671
|
+
* Copies `katex.min.css` and `fonts/` into the SSG output.
|
|
3672
|
+
* Returns an empty list when KaTeX is not installed.
|
|
3673
|
+
*/
|
|
3674
|
+
async function copyKatexAssets(outDir) {
|
|
3675
|
+
const dist = resolveKatexDist();
|
|
3676
|
+
if (!dist) return [];
|
|
3677
|
+
const dest = (0, node_path.join)(outDir, KATEX_ASSET_DIR);
|
|
3678
|
+
await (0, node_fs_promises.mkdir)((0, node_path.join)(dest, "fonts"), { recursive: true });
|
|
3679
|
+
const cssDest = (0, node_path.join)(dest, "katex.min.css");
|
|
3680
|
+
await (0, node_fs_promises.copyFile)((0, node_path.join)(dist, "katex.min.css"), cssDest);
|
|
3681
|
+
await (0, node_fs_promises.cp)((0, node_path.join)(dist, "fonts"), (0, node_path.join)(dest, "fonts"), { recursive: true });
|
|
3682
|
+
return [cssDest];
|
|
3683
|
+
}
|
|
3684
|
+
/** Dev-server middleware that serves `/__ox_katex__/*` from `katex/dist`. */
|
|
3685
|
+
function createKatexAssetsPlugin() {
|
|
3686
|
+
return {
|
|
3687
|
+
name: "ox-content:katex-assets",
|
|
3688
|
+
configureServer(server) {
|
|
3689
|
+
const dist = resolveKatexDist();
|
|
3690
|
+
if (!dist) return;
|
|
3691
|
+
server.middlewares.use((req, res, next) => {
|
|
3692
|
+
const url = req.url ?? "";
|
|
3693
|
+
const marker = `/${KATEX_ASSET_DIR}/`;
|
|
3694
|
+
const index = url.indexOf(marker);
|
|
3695
|
+
if (index === -1) {
|
|
3696
|
+
next();
|
|
3697
|
+
return;
|
|
3698
|
+
}
|
|
3699
|
+
const rel = decodeURIComponent(url.slice(index + marker.length).split("?")[0] ?? "");
|
|
3700
|
+
const file = safeKatexFile(dist, rel);
|
|
3701
|
+
if (!file) {
|
|
3702
|
+
res.statusCode = 404;
|
|
3703
|
+
res.end();
|
|
3704
|
+
return;
|
|
3705
|
+
}
|
|
3706
|
+
(0, node_fs_promises.stat)(file).then((info) => {
|
|
3707
|
+
if (!info.isFile()) {
|
|
3708
|
+
res.statusCode = 404;
|
|
3709
|
+
res.end();
|
|
3710
|
+
return;
|
|
3711
|
+
}
|
|
3712
|
+
res.setHeader("Content-Type", katexContentType(file));
|
|
3713
|
+
(0, node_fs.createReadStream)(file).pipe(res);
|
|
3714
|
+
}).catch(() => {
|
|
3715
|
+
res.statusCode = 404;
|
|
3716
|
+
res.end();
|
|
3717
|
+
});
|
|
3718
|
+
});
|
|
3719
|
+
}
|
|
3720
|
+
};
|
|
3721
|
+
}
|
|
3722
|
+
function safeKatexFile(dist, rel) {
|
|
3723
|
+
if (!rel || rel.includes("\0") || rel.split(/[\\/]/).includes("..")) return null;
|
|
3724
|
+
const full = (0, node_path.resolve)(dist, rel);
|
|
3725
|
+
const root = (0, node_path.resolve)(dist) + node_path.sep;
|
|
3726
|
+
if (full !== (0, node_path.resolve)(dist) && !full.startsWith(root)) return null;
|
|
3727
|
+
const inside = (0, node_path.relative)(dist, full);
|
|
3728
|
+
if (inside.startsWith("..") || inside.includes(`..${node_path.sep}`)) return null;
|
|
3729
|
+
return full;
|
|
3730
|
+
}
|
|
3731
|
+
function katexContentType(file) {
|
|
3732
|
+
const ext = (0, node_path.extname)(file);
|
|
3733
|
+
if (ext === ".css") return "text/css; charset=utf-8";
|
|
3734
|
+
if (ext === ".woff2") return "font/woff2";
|
|
3735
|
+
if (ext === ".woff") return "font/woff";
|
|
3736
|
+
if (ext === ".ttf") return "font/ttf";
|
|
3737
|
+
return "application/octet-stream";
|
|
3738
|
+
}
|
|
3739
|
+
//#endregion
|
|
3157
3740
|
//#region src/island/parse.ts
|
|
3158
3741
|
/**
|
|
3159
3742
|
* Island Parser
|
|
@@ -3762,6 +4345,7 @@ function renderPage(page, options) {
|
|
|
3762
4345
|
html: page.html,
|
|
3763
4346
|
toc: page.toc,
|
|
3764
4347
|
lastUpdated: page.lastUpdated,
|
|
4348
|
+
contributors: page.contributors,
|
|
3765
4349
|
path: page.path,
|
|
3766
4350
|
url: page.url,
|
|
3767
4351
|
frontmatter: page.frontmatter,
|
|
@@ -3777,6 +4361,7 @@ function renderPage(page, options) {
|
|
|
3777
4361
|
html: p.html,
|
|
3778
4362
|
toc: p.toc,
|
|
3779
4363
|
lastUpdated: p.lastUpdated,
|
|
4364
|
+
contributors: p.contributors,
|
|
3780
4365
|
path: p.path,
|
|
3781
4366
|
url: p.url,
|
|
3782
4367
|
frontmatter: p.frontmatter,
|
|
@@ -3837,8 +4422,8 @@ function DefaultTheme({ children }) {
|
|
|
3837
4422
|
<head>
|
|
3838
4423
|
<meta charset="UTF-8">
|
|
3839
4424
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3840
|
-
<title>${escapeHtml$
|
|
3841
|
-
${page.description ? `<meta name="description" content="${escapeHtml$
|
|
4425
|
+
<title>${escapeHtml$4(page.title)} - ${escapeHtml$4(site.name)}</title>
|
|
4426
|
+
${page.description ? `<meta name="description" content="${escapeHtml$4(page.description)}">` : ""}
|
|
3842
4427
|
<style>
|
|
3843
4428
|
:root {
|
|
3844
4429
|
--octc-color-primary: #4f6fae;
|
|
@@ -3862,7 +4447,7 @@ function DefaultTheme({ children }) {
|
|
|
3862
4447
|
</head>
|
|
3863
4448
|
<body>
|
|
3864
4449
|
<header>
|
|
3865
|
-
<h1>${escapeHtml$
|
|
4450
|
+
<h1>${escapeHtml$4(site.name)}</h1>
|
|
3866
4451
|
</header>
|
|
3867
4452
|
<main>
|
|
3868
4453
|
${children.__html}
|
|
@@ -3870,7 +4455,7 @@ function DefaultTheme({ children }) {
|
|
|
3870
4455
|
</body>
|
|
3871
4456
|
</html>` };
|
|
3872
4457
|
}
|
|
3873
|
-
function escapeHtml$
|
|
4458
|
+
function escapeHtml$4(str) {
|
|
3874
4459
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
3875
4460
|
}
|
|
3876
4461
|
/**
|
|
@@ -3907,7 +4492,7 @@ function createTheme(config) {
|
|
|
3907
4492
|
* String bodies follow `ox_content_ssg::generate_site_maps`. The Vite plugin
|
|
3908
4493
|
* writes those files during SSG without adding a NAPI surface.
|
|
3909
4494
|
*/
|
|
3910
|
-
const MISSING_SITE_URL$
|
|
4495
|
+
const MISSING_SITE_URL$2 = "[ox-content] siteMaps is enabled but ssg.siteUrl is not set; sitemap.xml, robots.txt, and llms.txt were not written";
|
|
3911
4496
|
/**
|
|
3912
4497
|
* Resolves `siteMaps` with defaults.
|
|
3913
4498
|
*
|
|
@@ -3934,7 +4519,7 @@ function resolveSiteMapsOptions(value) {
|
|
|
3934
4519
|
/** Builds sitemap / robots / llms bodies without writing files. */
|
|
3935
4520
|
function generateSiteMaps(input) {
|
|
3936
4521
|
if (!input.options?.enabled) return {};
|
|
3937
|
-
if (!hasSiteUrl$
|
|
4522
|
+
if (!hasSiteUrl$2(input.siteUrl)) return { warning: MISSING_SITE_URL$2 };
|
|
3938
4523
|
const published = input.pages.filter((page) => !page.draft && !page.unlisted && page.loc.length > 0).slice().sort((left, right) => left.loc < right.loc ? -1 : left.loc > right.loc ? 1 : 0);
|
|
3939
4524
|
const result = { sitemapXml: generateSitemapXml(published) };
|
|
3940
4525
|
if (input.options.robots) result.robotsTxt = generateRobotsTxt(input.sitemapLoc ?? "");
|
|
@@ -3970,11 +4555,11 @@ async function writeSiteMapFiles(input) {
|
|
|
3970
4555
|
}
|
|
3971
4556
|
return { files };
|
|
3972
4557
|
}
|
|
3973
|
-
function hasSiteUrl$
|
|
4558
|
+
function hasSiteUrl$2(siteUrl) {
|
|
3974
4559
|
return Boolean(siteUrl && siteUrl.trim());
|
|
3975
4560
|
}
|
|
3976
4561
|
function absoluteSitemapUrl(siteUrl, base) {
|
|
3977
|
-
if (!hasSiteUrl$
|
|
4562
|
+
if (!hasSiteUrl$2(siteUrl)) return "";
|
|
3978
4563
|
return `${(siteUrl ?? "").trim().replace(/\/+$/, "")}${!base || base === "/" ? "/" : base.endsWith("/") ? base : `${base}/`}sitemap.xml`;
|
|
3979
4564
|
}
|
|
3980
4565
|
function generateSitemapXml(pages) {
|
|
@@ -4160,7 +4745,7 @@ function resolvePageRoutes(input) {
|
|
|
4160
4745
|
if (!input.permalinks?.enabled) return {
|
|
4161
4746
|
pages: cascaded.map((page) => ({
|
|
4162
4747
|
source: page.source,
|
|
4163
|
-
urlPath: normalizeUrlPath(page.fileUrl),
|
|
4748
|
+
urlPath: normalizeUrlPath$1(page.fileUrl),
|
|
4164
4749
|
frontmatter: page.frontmatter
|
|
4165
4750
|
})),
|
|
4166
4751
|
errors: []
|
|
@@ -4188,7 +4773,7 @@ function resolvePageRoutes(input) {
|
|
|
4188
4773
|
errors
|
|
4189
4774
|
};
|
|
4190
4775
|
}
|
|
4191
|
-
function normalizeUrlPath(value) {
|
|
4776
|
+
function normalizeUrlPath$1(value) {
|
|
4192
4777
|
const segments = pathSegments(value);
|
|
4193
4778
|
return segments.length === 0 ? "/" : segments.join("/");
|
|
4194
4779
|
}
|
|
@@ -4222,10 +4807,10 @@ function applyCascade(pages, options) {
|
|
|
4222
4807
|
});
|
|
4223
4808
|
}
|
|
4224
4809
|
function resolveOne(page) {
|
|
4225
|
-
const fileUrl = normalizeUrlPath(page.fileUrl);
|
|
4810
|
+
const fileUrl = normalizeUrlPath$1(page.fileUrl);
|
|
4226
4811
|
const permalink = readString(page.frontmatter.permalink);
|
|
4227
4812
|
if (permalink !== void 0) {
|
|
4228
|
-
const url = isSafePermalink(permalink) ? normalizeUrlPath(permalink) : void 0;
|
|
4813
|
+
const url = isSafePermalink(permalink) ? normalizeUrlPath$1(permalink) : void 0;
|
|
4229
4814
|
return url ? { urlPath: url } : {
|
|
4230
4815
|
urlPath: fileUrl,
|
|
4231
4816
|
error: `[ox-content] rejected permalink ${JSON.stringify(permalink)} on ${page.source} (path escape); using the file-tree URL`
|
|
@@ -4244,7 +4829,7 @@ function resolveOne(page) {
|
|
|
4244
4829
|
function rewriteSlug(fileUrl, slug) {
|
|
4245
4830
|
const trimmed = slug.trim();
|
|
4246
4831
|
if (trimmed.includes("/") || !isSafePermalink(trimmed)) return;
|
|
4247
|
-
const normalized = normalizeUrlPath(trimmed);
|
|
4832
|
+
const normalized = normalizeUrlPath$1(trimmed);
|
|
4248
4833
|
if (normalized === "/") return;
|
|
4249
4834
|
if (fileUrl === "/") return normalized;
|
|
4250
4835
|
const segments = fileUrl.split("/").filter(Boolean);
|
|
@@ -4361,8 +4946,8 @@ function applyCollectionRoutes(manifest, permalinks, cascade) {
|
|
|
4361
4946
|
}
|
|
4362
4947
|
/** Updates auto-nav hrefs after permalinks change a page URL. */
|
|
4363
4948
|
function remapNavGroups(nav, kept, skippedFileUrls) {
|
|
4364
|
-
const skipped = new Set(skippedFileUrls.map(normalizeUrlPath));
|
|
4365
|
-
const byFile = new Map(kept.map((page) => [normalizeUrlPath(page.fileUrl), page]));
|
|
4949
|
+
const skipped = new Set(skippedFileUrls.map(normalizeUrlPath$1));
|
|
4950
|
+
const byFile = new Map(kept.map((page) => [normalizeUrlPath$1(page.fileUrl), page]));
|
|
4366
4951
|
return nav.map((group) => ({
|
|
4367
4952
|
...group,
|
|
4368
4953
|
items: remapNavItems(group.items, byFile, skipped)
|
|
@@ -4374,7 +4959,7 @@ function routePathsFromUrl(urlPath, srcDir, outDir, base, extension, siteUrl) {
|
|
|
4374
4959
|
}
|
|
4375
4960
|
function remapNavItems(items, byFile, skipped) {
|
|
4376
4961
|
return items.flatMap((item) => {
|
|
4377
|
-
const key = normalizeUrlPath(item.path);
|
|
4962
|
+
const key = normalizeUrlPath$1(item.path);
|
|
4378
4963
|
if (skipped.has(key)) return [];
|
|
4379
4964
|
const hit = byFile.get(key);
|
|
4380
4965
|
const children = item.children ? remapNavItems(item.children, byFile, skipped) : void 0;
|
|
@@ -4518,7 +5103,7 @@ async function writeRedirectFiles(input) {
|
|
|
4518
5103
|
}
|
|
4519
5104
|
/** Static HTML redirect body. `dest` is escaped. */
|
|
4520
5105
|
function generateRedirectHtml(dest) {
|
|
4521
|
-
const escaped = escapeHtml$
|
|
5106
|
+
const escaped = escapeHtml$3(dest);
|
|
4522
5107
|
return `\
|
|
4523
5108
|
<!DOCTYPE html>
|
|
4524
5109
|
<html lang="en">
|
|
@@ -4603,7 +5188,7 @@ function upsert(files, index, occupied, from, to, base) {
|
|
|
4603
5188
|
html
|
|
4604
5189
|
});
|
|
4605
5190
|
}
|
|
4606
|
-
function escapeHtml$
|
|
5191
|
+
function escapeHtml$3(value) {
|
|
4607
5192
|
return value.replace(/[&<>"']/g, (ch) => {
|
|
4608
5193
|
switch (ch) {
|
|
4609
5194
|
case "&": return "&";
|
|
@@ -5001,7 +5586,7 @@ function createNativeTransformOptions(options) {
|
|
|
5001
5586
|
} : void 0,
|
|
5002
5587
|
cards: options.cards?.enabled ? { enabled: true } : void 0,
|
|
5003
5588
|
steps: options.steps?.enabled ? { enabled: true } : void 0,
|
|
5004
|
-
fileTree: options.fileTree
|
|
5589
|
+
fileTree: toJsFileTreeOptions(options.fileTree),
|
|
5005
5590
|
editThisPage: options.editThisPage?.enabled ? {
|
|
5006
5591
|
enabled: true,
|
|
5007
5592
|
repoUrl: options.editThisPage.repoUrl,
|
|
@@ -5279,8 +5864,8 @@ function weekdayUtc(year, month, day) {
|
|
|
5279
5864
|
* String bodies follow `ox_content_ssg::generate_feeds`. The Vite plugin
|
|
5280
5865
|
* writes those files during SSG without adding a NAPI surface.
|
|
5281
5866
|
*/
|
|
5282
|
-
const MISSING_SITE_URL = "[ox-content] feeds is enabled but ssg.siteUrl is not set; RSS, Atom, and JSON feeds were not written";
|
|
5283
|
-
const DEFAULT_FORMATS = [
|
|
5867
|
+
const MISSING_SITE_URL$1 = "[ox-content] feeds is enabled but ssg.siteUrl is not set; RSS, Atom, and JSON feeds were not written";
|
|
5868
|
+
const DEFAULT_FORMATS$1 = [
|
|
5284
5869
|
"rss",
|
|
5285
5870
|
"atom",
|
|
5286
5871
|
"json"
|
|
@@ -5297,19 +5882,19 @@ const DEFAULT_PATH = "/";
|
|
|
5297
5882
|
function resolveFeedsOptions(value) {
|
|
5298
5883
|
if (!value) return {
|
|
5299
5884
|
enabled: false,
|
|
5300
|
-
formats: [...DEFAULT_FORMATS],
|
|
5885
|
+
formats: [...DEFAULT_FORMATS$1],
|
|
5301
5886
|
limit: DEFAULT_LIMIT,
|
|
5302
5887
|
path: DEFAULT_PATH
|
|
5303
5888
|
};
|
|
5304
5889
|
if (value === true) return {
|
|
5305
5890
|
enabled: true,
|
|
5306
|
-
formats: [...DEFAULT_FORMATS],
|
|
5891
|
+
formats: [...DEFAULT_FORMATS$1],
|
|
5307
5892
|
limit: DEFAULT_LIMIT,
|
|
5308
5893
|
path: DEFAULT_PATH
|
|
5309
5894
|
};
|
|
5310
5895
|
return {
|
|
5311
5896
|
enabled: true,
|
|
5312
|
-
formats: normalizeFormats(value.formats),
|
|
5897
|
+
formats: normalizeFormats$1(value.formats),
|
|
5313
5898
|
collection: value.collection,
|
|
5314
5899
|
limit: value.limit ?? DEFAULT_LIMIT,
|
|
5315
5900
|
path: value.path ?? DEFAULT_PATH
|
|
@@ -5324,7 +5909,7 @@ function resolveFeedCollectionName(requested, collectionNames) {
|
|
|
5324
5909
|
/** Builds RSS / Atom / JSON Feed bodies without writing files. */
|
|
5325
5910
|
function generateFeeds(input) {
|
|
5326
5911
|
if (!input.options?.enabled) return {};
|
|
5327
|
-
if (!hasSiteUrl(input.siteUrl)) return { warning: MISSING_SITE_URL };
|
|
5912
|
+
if (!hasSiteUrl$1(input.siteUrl)) return { warning: MISSING_SITE_URL$1 };
|
|
5328
5913
|
const published = publishedItems(input);
|
|
5329
5914
|
const doc = feedDocument(input);
|
|
5330
5915
|
const result = {};
|
|
@@ -5356,8 +5941,8 @@ async function writeFeedFiles(input) {
|
|
|
5356
5941
|
}
|
|
5357
5942
|
return { files };
|
|
5358
5943
|
}
|
|
5359
|
-
function normalizeFormats(formats) {
|
|
5360
|
-
if (!formats) return [...DEFAULT_FORMATS];
|
|
5944
|
+
function normalizeFormats$1(formats) {
|
|
5945
|
+
if (!formats) return [...DEFAULT_FORMATS$1];
|
|
5361
5946
|
const seen = /* @__PURE__ */ new Set();
|
|
5362
5947
|
const resolved = [];
|
|
5363
5948
|
for (const format of formats) if ((format === "rss" || format === "atom" || format === "json") && !seen.has(format)) {
|
|
@@ -5366,7 +5951,7 @@ function normalizeFormats(formats) {
|
|
|
5366
5951
|
}
|
|
5367
5952
|
return resolved;
|
|
5368
5953
|
}
|
|
5369
|
-
function hasSiteUrl(siteUrl) {
|
|
5954
|
+
function hasSiteUrl$1(siteUrl) {
|
|
5370
5955
|
return Boolean(siteUrl && siteUrl.trim());
|
|
5371
5956
|
}
|
|
5372
5957
|
function homePageUrl(siteUrl, base = "/") {
|
|
@@ -5418,7 +6003,7 @@ function normalizeItem(item, input) {
|
|
|
5418
6003
|
title: item.title ?? "",
|
|
5419
6004
|
description: typeof item.description === "string" ? item.description : void 0,
|
|
5420
6005
|
loc: item.loc || itemLoc(input, item),
|
|
5421
|
-
date: parseDate(dateField(item.date ?? item.frontmatter?.date)) ?? parseDate(dateField(item.lastUpdated ?? item.frontmatter?.lastUpdated))
|
|
6006
|
+
date: parseDate(dateField$1(item.date ?? item.frontmatter?.date)) ?? parseDate(dateField$1(item.lastUpdated ?? item.frontmatter?.lastUpdated))
|
|
5422
6007
|
};
|
|
5423
6008
|
}
|
|
5424
6009
|
function itemLoc(input, item) {
|
|
@@ -5426,49 +6011,254 @@ function itemLoc(input, item) {
|
|
|
5426
6011
|
const urlPath = (item.path ?? "").replace(/^\/+|\/+$/g, "");
|
|
5427
6012
|
return urlPath ? `${home}${urlPath}/` : home;
|
|
5428
6013
|
}
|
|
5429
|
-
function dateField(value) {
|
|
6014
|
+
function dateField$1(value) {
|
|
5430
6015
|
if (typeof value === "string" && value.trim()) return value.trim();
|
|
5431
6016
|
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
5432
6017
|
if (value instanceof Date && !Number.isNaN(value.getTime())) return value.toISOString();
|
|
5433
6018
|
}
|
|
5434
6019
|
//#endregion
|
|
5435
|
-
//#region src/
|
|
6020
|
+
//#region src/pwa.ts
|
|
5436
6021
|
/**
|
|
5437
|
-
*
|
|
6022
|
+
* Opt-in web app manifest and conservative service worker.
|
|
6023
|
+
*
|
|
6024
|
+
* The Vite plugin writes those files during SSG without adding a NAPI surface.
|
|
6025
|
+
* Enabling `offline` injects a tiny client script that registers `sw.js`.
|
|
5438
6026
|
*/
|
|
5439
|
-
|
|
5440
|
-
|
|
6027
|
+
const MISSING_SITE_URL = "[ox-content] pwa is enabled but ssg.siteUrl is not set; manifest.webmanifest and sw.js were not written";
|
|
6028
|
+
const DEFAULT_THEME_COLOR = "#000000";
|
|
6029
|
+
const DEFAULT_BACKGROUND_COLOR = "#ffffff";
|
|
6030
|
+
const MANIFEST_NAME = "manifest.webmanifest";
|
|
6031
|
+
const SERVICE_WORKER_NAME = "sw.js";
|
|
6032
|
+
/**
|
|
6033
|
+
* Resolves `pwa` with defaults.
|
|
6034
|
+
*
|
|
6035
|
+
* `false` / omitted stays off. `true` enables the manifest and offline
|
|
6036
|
+
* service worker. An object enables the feature and overrides only the
|
|
6037
|
+
* fields the site set.
|
|
6038
|
+
*/
|
|
6039
|
+
function resolvePwaOptions(value) {
|
|
6040
|
+
if (!value) return {
|
|
6041
|
+
enabled: false,
|
|
6042
|
+
offline: true
|
|
6043
|
+
};
|
|
6044
|
+
if (value === true) return {
|
|
6045
|
+
enabled: true,
|
|
6046
|
+
offline: true
|
|
6047
|
+
};
|
|
6048
|
+
return {
|
|
6049
|
+
enabled: true,
|
|
6050
|
+
offline: value.offline ?? true,
|
|
6051
|
+
name: value.name,
|
|
6052
|
+
shortName: value.shortName,
|
|
6053
|
+
themeColor: value.themeColor,
|
|
6054
|
+
backgroundColor: value.backgroundColor,
|
|
6055
|
+
startUrl: value.startUrl
|
|
6056
|
+
};
|
|
5441
6057
|
}
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
6058
|
+
/** Builds manifest / service-worker bodies without writing files. */
|
|
6059
|
+
function generatePwa(input) {
|
|
6060
|
+
if (!input.options?.enabled) return {};
|
|
6061
|
+
if (!hasSiteUrl(input.siteUrl)) return { warning: MISSING_SITE_URL };
|
|
6062
|
+
const base = normalizeBase(input.base);
|
|
6063
|
+
const name = sanitizeManifestText(input.options.name ?? input.siteName ?? "");
|
|
6064
|
+
const shortName = sanitizeManifestText(input.options.shortName ?? name);
|
|
6065
|
+
const startUrl = sanitizeStartUrl(input.options.startUrl, base);
|
|
6066
|
+
const themeColor = sanitizeColor(input.options.themeColor, DEFAULT_THEME_COLOR);
|
|
6067
|
+
const backgroundColor = sanitizeColor(input.options.backgroundColor, DEFAULT_BACKGROUND_COLOR);
|
|
6068
|
+
const result = { manifest: `${escapeJsonScript(JSON.stringify({
|
|
6069
|
+
name,
|
|
6070
|
+
short_name: shortName,
|
|
6071
|
+
start_url: startUrl,
|
|
6072
|
+
scope: base,
|
|
6073
|
+
display: "standalone",
|
|
6074
|
+
background_color: backgroundColor,
|
|
6075
|
+
theme_color: themeColor
|
|
6076
|
+
}, null, 2))}\n` };
|
|
6077
|
+
if (input.options.offline) result.serviceWorker = generateServiceWorker(base);
|
|
6078
|
+
return result;
|
|
5445
6079
|
}
|
|
5446
|
-
|
|
6080
|
+
/** Writes enabled PWA files into `outDir`. */
|
|
6081
|
+
async function writePwaFiles(input) {
|
|
6082
|
+
const generated = generatePwa(input);
|
|
6083
|
+
if (generated.warning) return {
|
|
6084
|
+
files: [],
|
|
6085
|
+
warning: generated.warning
|
|
6086
|
+
};
|
|
6087
|
+
const outputs = [[generated.manifest, MANIFEST_NAME], [generated.serviceWorker, SERVICE_WORKER_NAME]].filter((entry) => entry[0] != null);
|
|
6088
|
+
if (outputs.length === 0) return { files: [] };
|
|
6089
|
+
await node_fs_promises.mkdir(input.outDir, { recursive: true });
|
|
6090
|
+
const files = [];
|
|
6091
|
+
for (const [body, name] of outputs) {
|
|
6092
|
+
const outputPath = node_path.join(input.outDir, name);
|
|
6093
|
+
await node_fs_promises.writeFile(outputPath, body, "utf8");
|
|
6094
|
+
files.push(outputPath);
|
|
6095
|
+
}
|
|
6096
|
+
return { files };
|
|
6097
|
+
}
|
|
6098
|
+
/**
|
|
6099
|
+
* Injects `rel=manifest` (and the service-worker register script when offline)
|
|
6100
|
+
* into a themed HTML document. Bare / fragment HTML is left unchanged.
|
|
6101
|
+
*/
|
|
6102
|
+
function injectPwaPageTags(html, input) {
|
|
6103
|
+
if (!input.options?.enabled || !isThemedDocument(html)) return html;
|
|
6104
|
+
const base = normalizeBase(input.base);
|
|
6105
|
+
const manifestHref = escapeAttribute$1(`${base}${MANIFEST_NAME}`);
|
|
6106
|
+
const themeColor = sanitizeColor(input.options.themeColor, DEFAULT_THEME_COLOR);
|
|
6107
|
+
let next = insertBeforeTag(html, "</head>", ` ${[`<link rel="manifest" href="${manifestHref}">`, `<meta name="theme-color" content="${escapeAttribute$1(themeColor)}">`].join("\n ")}\n`);
|
|
6108
|
+
if (input.options.offline) {
|
|
6109
|
+
const script = `<script>if("serviceWorker"in navigator)navigator.serviceWorker.register(${JSON.stringify(`${base}${SERVICE_WORKER_NAME}`)})<\/script>`;
|
|
6110
|
+
next = insertBeforeTag(next, "</body>", ` ${script}\n`);
|
|
6111
|
+
}
|
|
6112
|
+
return next;
|
|
6113
|
+
}
|
|
6114
|
+
function generateServiceWorker(base) {
|
|
6115
|
+
return `/* ox-content PWA service worker */
|
|
6116
|
+
const CACHE = "ox-content-pwa-v1";
|
|
6117
|
+
const ASSET_PREFIX = ${JSON.stringify(`${base}assets/`)};
|
|
6118
|
+
|
|
6119
|
+
self.addEventListener("install", (event) => {
|
|
6120
|
+
event.waitUntil(self.skipWaiting());
|
|
6121
|
+
});
|
|
6122
|
+
|
|
6123
|
+
self.addEventListener("activate", (event) => {
|
|
6124
|
+
event.waitUntil(self.clients.claim());
|
|
6125
|
+
});
|
|
6126
|
+
|
|
6127
|
+
self.addEventListener("fetch", (event) => {
|
|
6128
|
+
const request = event.request;
|
|
6129
|
+
if (request.method !== "GET") return;
|
|
6130
|
+
const url = new URL(request.url);
|
|
6131
|
+
if (url.origin !== self.location.origin) return;
|
|
6132
|
+
|
|
6133
|
+
if (isHashedAsset(url.pathname)) {
|
|
6134
|
+
event.respondWith(cacheFirst(request));
|
|
6135
|
+
return;
|
|
6136
|
+
}
|
|
6137
|
+
|
|
6138
|
+
if (isHtmlPage(request)) {
|
|
6139
|
+
event.respondWith(networkFirst(request));
|
|
6140
|
+
}
|
|
6141
|
+
});
|
|
6142
|
+
|
|
6143
|
+
function isHashedAsset(pathname) {
|
|
6144
|
+
if (!pathname.startsWith(ASSET_PREFIX)) return false;
|
|
6145
|
+
return /-[0-9a-f]{8,}\\.[a-z0-9]+$/i.test(pathname);
|
|
6146
|
+
}
|
|
6147
|
+
|
|
6148
|
+
function isHtmlPage(request) {
|
|
6149
|
+
if (request.mode === "navigate") return true;
|
|
6150
|
+
const accept = request.headers.get("accept") || "";
|
|
6151
|
+
return accept.includes("text/html");
|
|
6152
|
+
}
|
|
6153
|
+
|
|
6154
|
+
async function cacheFirst(request) {
|
|
6155
|
+
const cache = await caches.open(CACHE);
|
|
6156
|
+
const cached = await cache.match(request);
|
|
6157
|
+
if (cached) return cached;
|
|
6158
|
+
const response = await fetch(request);
|
|
6159
|
+
if (response.ok) cache.put(request, response.clone());
|
|
6160
|
+
return response;
|
|
6161
|
+
}
|
|
6162
|
+
|
|
6163
|
+
async function networkFirst(request) {
|
|
6164
|
+
const cache = await caches.open(CACHE);
|
|
6165
|
+
try {
|
|
6166
|
+
const response = await fetch(request);
|
|
6167
|
+
if (response.ok) cache.put(request, response.clone());
|
|
6168
|
+
return response;
|
|
6169
|
+
} catch (error) {
|
|
6170
|
+
const cached = await cache.match(request);
|
|
6171
|
+
if (cached) return cached;
|
|
6172
|
+
throw error;
|
|
6173
|
+
}
|
|
6174
|
+
}
|
|
6175
|
+
`;
|
|
6176
|
+
}
|
|
6177
|
+
function hasSiteUrl(siteUrl) {
|
|
6178
|
+
return Boolean(siteUrl && siteUrl.trim());
|
|
6179
|
+
}
|
|
6180
|
+
function normalizeBase(base) {
|
|
6181
|
+
if (!base || base === "/") return "/";
|
|
6182
|
+
return base.endsWith("/") ? base : `${base}/`;
|
|
6183
|
+
}
|
|
6184
|
+
function sanitizeManifestText(value) {
|
|
6185
|
+
return value.split(/\s+/u).filter(Boolean).join(" ");
|
|
6186
|
+
}
|
|
6187
|
+
function sanitizeColor(value, fallback) {
|
|
6188
|
+
if (!value) return fallback;
|
|
6189
|
+
const trimmed = value.trim();
|
|
6190
|
+
if (/^#[0-9A-Fa-f]{3,8}$/.test(trimmed)) return trimmed;
|
|
6191
|
+
if (/^[a-zA-Z][a-zA-Z0-9-]{0,31}$/.test(trimmed)) return trimmed;
|
|
6192
|
+
return fallback;
|
|
6193
|
+
}
|
|
6194
|
+
function sanitizeStartUrl(value, base) {
|
|
6195
|
+
if (!value) return base;
|
|
6196
|
+
const trimmed = value.trim();
|
|
6197
|
+
if (!trimmed.startsWith("/") || trimmed.startsWith("//")) return base;
|
|
6198
|
+
if (/[\n\r\t<>"'`]/.test(trimmed)) return base;
|
|
6199
|
+
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed)) return base;
|
|
6200
|
+
return trimmed;
|
|
6201
|
+
}
|
|
6202
|
+
function isThemedDocument(html) {
|
|
6203
|
+
return /<\/head>/i.test(html) && /<\/body>/i.test(html);
|
|
6204
|
+
}
|
|
6205
|
+
function insertBeforeTag(html, tag, snippet) {
|
|
6206
|
+
const index = html.toLowerCase().lastIndexOf(tag.toLowerCase());
|
|
6207
|
+
if (index === -1) return html;
|
|
6208
|
+
return `${html.slice(0, index)}${snippet}${html.slice(index)}`;
|
|
6209
|
+
}
|
|
6210
|
+
function escapeJsonScript(value) {
|
|
6211
|
+
return value.replace(/[<>]/g, (ch) => ch === "<" ? "\\u003c" : "\\u003e");
|
|
6212
|
+
}
|
|
6213
|
+
function escapeAttribute$1(value) {
|
|
6214
|
+
return value.replace(/[&<>"']/g, (ch) => {
|
|
6215
|
+
switch (ch) {
|
|
6216
|
+
case "&": return "&";
|
|
6217
|
+
case "<": return "<";
|
|
6218
|
+
case ">": return ">";
|
|
6219
|
+
case "\"": return """;
|
|
6220
|
+
default: return "'";
|
|
6221
|
+
}
|
|
6222
|
+
});
|
|
6223
|
+
}
|
|
6224
|
+
//#endregion
|
|
6225
|
+
//#region src/taxonomies-html.ts
|
|
6226
|
+
/**
|
|
6227
|
+
* Escaped taxonomy HTML and confined output paths.
|
|
6228
|
+
*/
|
|
6229
|
+
function relatedMarkup(pages) {
|
|
6230
|
+
return `<nav class="ox-related" aria-label="Related pages"><h2>Related pages</h2><ul>${pages.map((page) => listItem$1(page.routePaths.href, page.title)).join("")}</ul></nav>`;
|
|
6231
|
+
}
|
|
6232
|
+
function listPageContent(terms, base, urlName) {
|
|
6233
|
+
const items = terms.map((term) => listItem$1(siteHref$3(base, urlName, term.slug), term.label)).join("");
|
|
6234
|
+
return `<h1>${escapeHtml$2(displayTaxonomyName(urlName))}</h1><ul class="ox-taxonomy">${items}</ul>`;
|
|
6235
|
+
}
|
|
6236
|
+
function termPageContent(term) {
|
|
5447
6237
|
const items = [...term.pages].sort((left, right) => {
|
|
5448
6238
|
const titleCmp = left.title.localeCompare(right.title);
|
|
5449
6239
|
return titleCmp !== 0 ? titleCmp : left.routePaths.href.localeCompare(right.routePaths.href);
|
|
5450
|
-
}).map((page) => listItem(page.routePaths.href, page.title)).join("");
|
|
5451
|
-
return `<h1>${escapeHtml$
|
|
6240
|
+
}).map((page) => listItem$1(page.routePaths.href, page.title)).join("");
|
|
6241
|
+
return `<h1>${escapeHtml$2(term.label)}</h1><ul class="ox-taxonomy-term">${items}</ul>`;
|
|
5452
6242
|
}
|
|
5453
6243
|
function displayTaxonomyName(name) {
|
|
5454
6244
|
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
5455
6245
|
}
|
|
5456
|
-
function siteHref$
|
|
6246
|
+
function siteHref$3(base, ...segments) {
|
|
5457
6247
|
const prefix = !base || base === "/" ? "/" : base.endsWith("/") ? base : `${base}/`;
|
|
5458
6248
|
const rest = segments.filter(Boolean).join("/");
|
|
5459
6249
|
return rest ? `${prefix}${rest}/` : prefix;
|
|
5460
6250
|
}
|
|
5461
|
-
function containedPath(outDir, ...segments) {
|
|
6251
|
+
function containedPath$2(outDir, ...segments) {
|
|
5462
6252
|
const root = node_path.resolve(outDir);
|
|
5463
6253
|
const resolved = node_path.resolve(root, ...segments);
|
|
5464
6254
|
const prefix = root.endsWith(node_path.sep) ? root : `${root}${node_path.sep}`;
|
|
5465
6255
|
if (resolved === root || !resolved.startsWith(prefix)) return;
|
|
5466
6256
|
return resolved;
|
|
5467
6257
|
}
|
|
5468
|
-
function listItem(href, label) {
|
|
5469
|
-
return `<li><a href="${escapeHtml$
|
|
6258
|
+
function listItem$1(href, label) {
|
|
6259
|
+
return `<li><a href="${escapeHtml$2(href)}">${escapeHtml$2(label)}</a></li>`;
|
|
5470
6260
|
}
|
|
5471
|
-
function escapeHtml$
|
|
6261
|
+
function escapeHtml$2(value) {
|
|
5472
6262
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
5473
6263
|
}
|
|
5474
6264
|
//#endregion
|
|
@@ -5481,7 +6271,7 @@ function escapeHtml$1(value) {
|
|
|
5481
6271
|
*/
|
|
5482
6272
|
const DEFAULT_TAXONOMIES = ["tags", "categories"];
|
|
5483
6273
|
const DEFAULT_RELATED_LIMIT = 5;
|
|
5484
|
-
const HOSTILE_TERM = /^(?:javascript|data):/i;
|
|
6274
|
+
const HOSTILE_TERM$1 = /^(?:javascript|data):/i;
|
|
5485
6275
|
/**
|
|
5486
6276
|
* Resolves `taxonomies` with defaults.
|
|
5487
6277
|
*
|
|
@@ -5512,7 +6302,7 @@ function resolveTaxonomiesOptions(value) {
|
|
|
5512
6302
|
*/
|
|
5513
6303
|
function termSlug(term) {
|
|
5514
6304
|
const trimmed = term.trim();
|
|
5515
|
-
if (!trimmed || HOSTILE_TERM.test(trimmed) || trimmed.includes("..") || trimmed.includes("//")) return;
|
|
6305
|
+
if (!trimmed || HOSTILE_TERM$1.test(trimmed) || trimmed.includes("..") || trimmed.includes("//")) return;
|
|
5516
6306
|
return trimmed.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || void 0;
|
|
5517
6307
|
}
|
|
5518
6308
|
/** Appends related-page HTML to source pages that share a listed term. */
|
|
@@ -5570,8 +6360,8 @@ function taxonomyPageSpecs(listed, options, outDir, base) {
|
|
|
5570
6360
|
for (const taxonomy of options.taxonomies) {
|
|
5571
6361
|
const urlName = taxonomy.toLowerCase();
|
|
5572
6362
|
const terms = collectTerms(listed, taxonomy);
|
|
5573
|
-
const listHref = siteHref$
|
|
5574
|
-
const listOutput = containedPath(outDir, urlName, "index.html");
|
|
6363
|
+
const listHref = siteHref$3(base, urlName);
|
|
6364
|
+
const listOutput = containedPath$2(outDir, urlName, "index.html");
|
|
5575
6365
|
if (listOutput) pages.push({
|
|
5576
6366
|
title: displayTaxonomyName(urlName),
|
|
5577
6367
|
content: listPageContent(terms, base, urlName),
|
|
@@ -5580,14 +6370,14 @@ function taxonomyPageSpecs(listed, options, outDir, base) {
|
|
|
5580
6370
|
href: listHref
|
|
5581
6371
|
});
|
|
5582
6372
|
for (const term of terms) {
|
|
5583
|
-
const outputPath = containedPath(outDir, urlName, term.slug, "index.html");
|
|
6373
|
+
const outputPath = containedPath$2(outDir, urlName, term.slug, "index.html");
|
|
5584
6374
|
if (!outputPath) continue;
|
|
5585
6375
|
pages.push({
|
|
5586
6376
|
title: term.label,
|
|
5587
6377
|
content: termPageContent(term),
|
|
5588
6378
|
outputPath,
|
|
5589
6379
|
urlPath: `${urlName}/${term.slug}`,
|
|
5590
|
-
href: siteHref$
|
|
6380
|
+
href: siteHref$3(base, urlName, term.slug)
|
|
5591
6381
|
});
|
|
5592
6382
|
}
|
|
5593
6383
|
}
|
|
@@ -5595,7 +6385,7 @@ function taxonomyPageSpecs(listed, options, outDir, base) {
|
|
|
5595
6385
|
}
|
|
5596
6386
|
function collectTerms(listed, taxonomy) {
|
|
5597
6387
|
const buckets = /* @__PURE__ */ new Map();
|
|
5598
|
-
for (const page of listed) for (const label of termsFromValue(page.frontmatter[taxonomy])) {
|
|
6388
|
+
for (const page of listed) for (const label of termsFromValue$1(page.frontmatter[taxonomy])) {
|
|
5599
6389
|
const slug = termSlug(label);
|
|
5600
6390
|
if (!slug) continue;
|
|
5601
6391
|
const existing = buckets.get(slug);
|
|
@@ -5610,13 +6400,13 @@ function collectTerms(listed, taxonomy) {
|
|
|
5610
6400
|
}
|
|
5611
6401
|
function pageTermKeys(page, taxonomies) {
|
|
5612
6402
|
const keys = /* @__PURE__ */ new Set();
|
|
5613
|
-
for (const taxonomy of taxonomies) for (const label of termsFromValue(page.frontmatter[taxonomy])) {
|
|
6403
|
+
for (const taxonomy of taxonomies) for (const label of termsFromValue$1(page.frontmatter[taxonomy])) {
|
|
5614
6404
|
const slug = termSlug(label);
|
|
5615
6405
|
if (slug) keys.add(`${taxonomy.toLowerCase()}\0${slug}`);
|
|
5616
6406
|
}
|
|
5617
6407
|
return keys;
|
|
5618
6408
|
}
|
|
5619
|
-
function termsFromValue(value) {
|
|
6409
|
+
function termsFromValue$1(value) {
|
|
5620
6410
|
if (typeof value === "string") return value.trim() ? [value.trim()] : [];
|
|
5621
6411
|
if (!Array.isArray(value)) return [];
|
|
5622
6412
|
return value.flatMap((item) => typeof item === "string" && item.trim() ? [item.trim()] : []);
|
|
@@ -5671,25 +6461,810 @@ function resolveTeamOptions(value) {
|
|
|
5671
6461
|
members: normalizeMembers(value.members)
|
|
5672
6462
|
};
|
|
5673
6463
|
}
|
|
5674
|
-
function normalizeMembers(members) {
|
|
5675
|
-
if (!Array.isArray(members)) return [];
|
|
5676
|
-
return members.flatMap((member) => {
|
|
5677
|
-
if (!member || typeof member.name !== "string") return [];
|
|
5678
|
-
const links = Array.isArray(member.links) ? member.links.flatMap((link) => {
|
|
5679
|
-
if (!link || typeof link.label !== "string" || typeof link.href !== "string") return [];
|
|
5680
|
-
return [{
|
|
5681
|
-
label: link.label,
|
|
5682
|
-
href: link.href
|
|
5683
|
-
}];
|
|
5684
|
-
}) : void 0;
|
|
5685
|
-
return [{
|
|
5686
|
-
name: member.name,
|
|
5687
|
-
role: typeof member.role === "string" ? member.role : void 0,
|
|
5688
|
-
avatar: typeof member.avatar === "string" ? member.avatar : void 0,
|
|
5689
|
-
links
|
|
5690
|
-
}];
|
|
6464
|
+
function normalizeMembers(members) {
|
|
6465
|
+
if (!Array.isArray(members)) return [];
|
|
6466
|
+
return members.flatMap((member) => {
|
|
6467
|
+
if (!member || typeof member.name !== "string") return [];
|
|
6468
|
+
const links = Array.isArray(member.links) ? member.links.flatMap((link) => {
|
|
6469
|
+
if (!link || typeof link.label !== "string" || typeof link.href !== "string") return [];
|
|
6470
|
+
return [{
|
|
6471
|
+
label: link.label,
|
|
6472
|
+
href: link.href
|
|
6473
|
+
}];
|
|
6474
|
+
}) : void 0;
|
|
6475
|
+
return [{
|
|
6476
|
+
name: member.name,
|
|
6477
|
+
role: typeof member.role === "string" ? member.role : void 0,
|
|
6478
|
+
avatar: typeof member.avatar === "string" ? member.avatar : void 0,
|
|
6479
|
+
links
|
|
6480
|
+
}];
|
|
6481
|
+
});
|
|
6482
|
+
}
|
|
6483
|
+
//#endregion
|
|
6484
|
+
//#region src/contributors.ts
|
|
6485
|
+
/**
|
|
6486
|
+
* Opt-in git contributor list helpers.
|
|
6487
|
+
*
|
|
6488
|
+
* Resolution and ignore filtering live here. `git log` is read in Rust
|
|
6489
|
+
* (`getGitContributors`) and names are rendered from `PageData.contributors`.
|
|
6490
|
+
*/
|
|
6491
|
+
/**
|
|
6492
|
+
* Resolves `ssg.contributors` with defaults.
|
|
6493
|
+
*
|
|
6494
|
+
* `false` / omitted stays off. `true` enables names only.
|
|
6495
|
+
* An object enables the feature and keeps `ignore` / `avatars`.
|
|
6496
|
+
*/
|
|
6497
|
+
function resolveContributorsOption(value) {
|
|
6498
|
+
if (!value) return false;
|
|
6499
|
+
if (value === true) return {
|
|
6500
|
+
ignore: [],
|
|
6501
|
+
avatars: false
|
|
6502
|
+
};
|
|
6503
|
+
return {
|
|
6504
|
+
ignore: Array.isArray(value.ignore) ? value.ignore.filter((entry) => typeof entry === "string") : [],
|
|
6505
|
+
avatars: value.avatars === true
|
|
6506
|
+
};
|
|
6507
|
+
}
|
|
6508
|
+
function filterGitContributors(contributors, ignore) {
|
|
6509
|
+
if (ignore.length === 0) return contributors.filter((contributor) => contributor.name.trim());
|
|
6510
|
+
const needles = new Set(ignore.map((entry) => entry.toLowerCase()));
|
|
6511
|
+
return contributors.filter((contributor) => {
|
|
6512
|
+
const name = contributor.name.trim();
|
|
6513
|
+
if (!name) return false;
|
|
6514
|
+
if (needles.has(name.toLowerCase())) return false;
|
|
6515
|
+
const email = contributor.email?.trim().toLowerCase();
|
|
6516
|
+
return !email || !needles.has(email);
|
|
6517
|
+
});
|
|
6518
|
+
}
|
|
6519
|
+
function gravatarAvatar(email) {
|
|
6520
|
+
return `https://www.gravatar.com/avatar/${(0, node_crypto.createHash)("md5").update(email.trim().toLowerCase()).digest("hex")}?d=mp&s=40`;
|
|
6521
|
+
}
|
|
6522
|
+
function applyContributorOptions(raw, option) {
|
|
6523
|
+
return filterGitContributors(raw, option.ignore).map((contributor) => ({
|
|
6524
|
+
name: contributor.name.trim(),
|
|
6525
|
+
avatar: option.avatars && contributor.email?.trim() ? gravatarAvatar(contributor.email) : void 0
|
|
6526
|
+
}));
|
|
6527
|
+
}
|
|
6528
|
+
//#endregion
|
|
6529
|
+
//#region src/blog-options.ts
|
|
6530
|
+
const DEFAULT_PAGE_SIZE = 10;
|
|
6531
|
+
function resolveBlogOptions(value) {
|
|
6532
|
+
if (!value) return {
|
|
6533
|
+
enabled: false,
|
|
6534
|
+
authors: {},
|
|
6535
|
+
pageSize: DEFAULT_PAGE_SIZE
|
|
6536
|
+
};
|
|
6537
|
+
if (value === true) return {
|
|
6538
|
+
enabled: true,
|
|
6539
|
+
authors: {},
|
|
6540
|
+
pageSize: DEFAULT_PAGE_SIZE
|
|
6541
|
+
};
|
|
6542
|
+
return {
|
|
6543
|
+
enabled: true,
|
|
6544
|
+
collection: value.collection,
|
|
6545
|
+
authors: normalizeAuthors(value.authors),
|
|
6546
|
+
pageSize: normalizePageSize(value.pageSize)
|
|
6547
|
+
};
|
|
6548
|
+
}
|
|
6549
|
+
/**
|
|
6550
|
+
* Picks a collection named `blog`, else the only configured collection.
|
|
6551
|
+
*
|
|
6552
|
+
* An explicit name always wins. Several collections and no `blog` name
|
|
6553
|
+
* require `blog.collection`.
|
|
6554
|
+
*/
|
|
6555
|
+
function resolveBlogCollectionName(requested, collectionNames) {
|
|
6556
|
+
if (requested) return requested;
|
|
6557
|
+
if (collectionNames.includes("blog")) return "blog";
|
|
6558
|
+
if (collectionNames.length === 1) return collectionNames[0];
|
|
6559
|
+
}
|
|
6560
|
+
function normalizeAuthors(authors) {
|
|
6561
|
+
if (!authors || typeof authors !== "object") return {};
|
|
6562
|
+
const resolved = {};
|
|
6563
|
+
for (const [key, value] of Object.entries(authors)) {
|
|
6564
|
+
if (!value || typeof value.name !== "string") continue;
|
|
6565
|
+
resolved[key] = {
|
|
6566
|
+
name: value.name,
|
|
6567
|
+
bio: typeof value.bio === "string" ? value.bio : void 0,
|
|
6568
|
+
url: typeof value.url === "string" ? value.url : void 0
|
|
6569
|
+
};
|
|
6570
|
+
}
|
|
6571
|
+
return resolved;
|
|
6572
|
+
}
|
|
6573
|
+
function normalizePageSize(value) {
|
|
6574
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 1) return Math.floor(value);
|
|
6575
|
+
return DEFAULT_PAGE_SIZE;
|
|
6576
|
+
}
|
|
6577
|
+
//#endregion
|
|
6578
|
+
//#region src/blog-reading.ts
|
|
6579
|
+
/**
|
|
6580
|
+
* Deterministic blog reading-time estimates.
|
|
6581
|
+
*/
|
|
6582
|
+
const LATIN_WORDS_PER_MINUTE = 200;
|
|
6583
|
+
const CJK_CHARS_PER_MINUTE = 500;
|
|
6584
|
+
function readingTimeMinutes(markdown) {
|
|
6585
|
+
const body = stripInlineCode(stripFences(stripFrontmatter(markdown)));
|
|
6586
|
+
let latin = 0;
|
|
6587
|
+
let cjk = 0;
|
|
6588
|
+
let latinRun = false;
|
|
6589
|
+
for (const char of body) {
|
|
6590
|
+
const code = char.codePointAt(0) ?? 0;
|
|
6591
|
+
if (isCjkCodePoint(code)) {
|
|
6592
|
+
cjk += 1;
|
|
6593
|
+
latinRun = false;
|
|
6594
|
+
continue;
|
|
6595
|
+
}
|
|
6596
|
+
if (isLatinWordChar(code)) {
|
|
6597
|
+
if (!latinRun) {
|
|
6598
|
+
latin += 1;
|
|
6599
|
+
latinRun = true;
|
|
6600
|
+
}
|
|
6601
|
+
continue;
|
|
6602
|
+
}
|
|
6603
|
+
if (char === "'" || char === "’") continue;
|
|
6604
|
+
latinRun = false;
|
|
6605
|
+
}
|
|
6606
|
+
if (latin === 0 && cjk === 0) return 0;
|
|
6607
|
+
return Math.max(1, Math.ceil(latin / LATIN_WORDS_PER_MINUTE + cjk / CJK_CHARS_PER_MINUTE));
|
|
6608
|
+
}
|
|
6609
|
+
function stripFrontmatter(markdown) {
|
|
6610
|
+
if (!markdown.startsWith("---")) return markdown;
|
|
6611
|
+
const match = markdown.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
|
|
6612
|
+
return match ? markdown.slice(match[0].length) : markdown;
|
|
6613
|
+
}
|
|
6614
|
+
function stripFences(text) {
|
|
6615
|
+
return text.replace(/```[\s\S]*?(?:```|$)/g, " ");
|
|
6616
|
+
}
|
|
6617
|
+
function stripInlineCode(text) {
|
|
6618
|
+
return text.replace(/`[^`\n]*`/g, " ");
|
|
6619
|
+
}
|
|
6620
|
+
function isCjkCodePoint(code) {
|
|
6621
|
+
return code >= 12352 && code <= 12543 || code >= 12784 && code <= 12799 || code >= 13312 && code <= 19903 || code >= 19968 && code <= 40959 || code >= 63744 && code <= 64255 || code >= 44032 && code <= 55215 || code >= 4352 && code <= 4607;
|
|
6622
|
+
}
|
|
6623
|
+
function isLatinWordChar(code) {
|
|
6624
|
+
return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122;
|
|
6625
|
+
}
|
|
6626
|
+
//#endregion
|
|
6627
|
+
//#region src/blog-html.ts
|
|
6628
|
+
/**
|
|
6629
|
+
* Escaped blog index, tag, archive, and post-meta HTML.
|
|
6630
|
+
*/
|
|
6631
|
+
/** `https:` or a same-origin path starting with `/` but not `//`. */
|
|
6632
|
+
function isSafeBlogUrl(value) {
|
|
6633
|
+
const trimmed = value.trim();
|
|
6634
|
+
if (trimmed.length === 0 || trimmed.split("").some((ch) => ch === "\n" || ch === "\r" || ch === "\0" || ch === " ")) return false;
|
|
6635
|
+
if (trimmed.startsWith("//")) return false;
|
|
6636
|
+
if (trimmed.startsWith("/")) return true;
|
|
6637
|
+
return trimmed.toLowerCase().startsWith("https:");
|
|
6638
|
+
}
|
|
6639
|
+
function postMetaMarkup(meta) {
|
|
6640
|
+
const parts = [`<p class="ox-blog-meta__reading-time">${escapeHtml$1(String(meta.minutes))} min read</p>`];
|
|
6641
|
+
if (meta.authors.length > 0) {
|
|
6642
|
+
const items = meta.authors.map((author) => authorMarkup(author)).join("");
|
|
6643
|
+
parts.push(`<ul class="ox-blog-meta__authors">${items}</ul>`);
|
|
6644
|
+
}
|
|
6645
|
+
if (meta.tags.length > 0) {
|
|
6646
|
+
const items = meta.tags.map((tag) => `<li><a href="${escapeHtml$1(tag.href)}">${escapeHtml$1(tag.label)}</a></li>`).join("");
|
|
6647
|
+
parts.push(`<ul class="ox-blog-meta__tags">${items}</ul>`);
|
|
6648
|
+
}
|
|
6649
|
+
return `<aside class="ox-blog-meta">${parts.join("")}</aside>\n`;
|
|
6650
|
+
}
|
|
6651
|
+
function indexPageContent(items, pager) {
|
|
6652
|
+
const list = items.map((item) => listItem(item)).join("");
|
|
6653
|
+
const links = [];
|
|
6654
|
+
if (pager.newerHref) links.push(`<a href="${escapeHtml$1(pager.newerHref)}" rel="prev">Newer</a>`);
|
|
6655
|
+
if (pager.olderHref) links.push(`<a href="${escapeHtml$1(pager.olderHref)}" rel="next">Older</a>`);
|
|
6656
|
+
return `<h1>Blog</h1><ul class="ox-blog">${list}</ul>${links.length > 0 ? `<nav class="ox-blog-pager">${links.join("")}</nav>` : ""}`;
|
|
6657
|
+
}
|
|
6658
|
+
function tagPageContent(label, items) {
|
|
6659
|
+
const list = items.map((item) => listItem(item)).join("");
|
|
6660
|
+
return `<h1>${escapeHtml$1(label)}</h1><ul class="ox-blog-tag">${list}</ul>`;
|
|
6661
|
+
}
|
|
6662
|
+
function archiveIndexContent(years) {
|
|
6663
|
+
return `<h1>Archive</h1><ul class="ox-blog-archive">${years.map((entry) => `<li><a href="${escapeHtml$1(entry.href)}">${escapeHtml$1(entry.year)}</a></li>`).join("")}</ul>`;
|
|
6664
|
+
}
|
|
6665
|
+
function archiveYearContent(year, months, items) {
|
|
6666
|
+
const monthList = months.map((entry) => `<li><a href="${escapeHtml$1(entry.href)}">${escapeHtml$1(entry.month)}</a></li>`).join("");
|
|
6667
|
+
const posts = items.map((item) => listItem(item)).join("");
|
|
6668
|
+
return `<h1>${escapeHtml$1(year)}</h1><ul class="ox-blog-archive-months">${monthList}</ul><ul class="ox-blog">${posts}</ul>`;
|
|
6669
|
+
}
|
|
6670
|
+
function archiveMonthContent(label, items) {
|
|
6671
|
+
const list = items.map((item) => listItem(item)).join("");
|
|
6672
|
+
return `<h1>${escapeHtml$1(label)}</h1><ul class="ox-blog">${list}</ul>`;
|
|
6673
|
+
}
|
|
6674
|
+
function siteHref$2(base, ...segments) {
|
|
6675
|
+
const prefix = !base || base === "/" ? "/" : base.endsWith("/") ? base : `${base}/`;
|
|
6676
|
+
const rest = segments.filter(Boolean).join("/");
|
|
6677
|
+
return rest ? `${prefix}${rest}/` : prefix;
|
|
6678
|
+
}
|
|
6679
|
+
function containedPath$1(outDir, ...segments) {
|
|
6680
|
+
const root = node_path.resolve(outDir);
|
|
6681
|
+
const resolved = node_path.resolve(root, ...segments);
|
|
6682
|
+
const prefix = root.endsWith(node_path.sep) ? root : `${root}${node_path.sep}`;
|
|
6683
|
+
if (resolved === root || !resolved.startsWith(prefix)) return;
|
|
6684
|
+
return resolved;
|
|
6685
|
+
}
|
|
6686
|
+
function escapeHtml$1(value) {
|
|
6687
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
6688
|
+
}
|
|
6689
|
+
function authorMarkup(author) {
|
|
6690
|
+
const name = escapeHtml$1(author.name);
|
|
6691
|
+
const url = author.url?.trim();
|
|
6692
|
+
return `<li>${url && isSafeBlogUrl(url) ? `<a class="ox-blog-meta__name" href="${escapeHtml$1(url)}">${name}</a>` : `<span class="ox-blog-meta__name">${name}</span>`}${author.bio && author.bio.length > 0 ? `<p class="ox-blog-meta__bio">${escapeHtml$1(author.bio)}</p>` : ""}</li>`;
|
|
6693
|
+
}
|
|
6694
|
+
function listItem(item) {
|
|
6695
|
+
const time = item.dateLabel ? ` <time datetime="${escapeHtml$1(item.dateLabel)}">${escapeHtml$1(item.dateLabel)}</time>` : "";
|
|
6696
|
+
return `<li><a href="${escapeHtml$1(item.href)}">${escapeHtml$1(item.title)}</a>${time}</li>`;
|
|
6697
|
+
}
|
|
6698
|
+
//#endregion
|
|
6699
|
+
//#region src/blog-posts.ts
|
|
6700
|
+
/**
|
|
6701
|
+
* Blog post selection, tags, authors, and dates.
|
|
6702
|
+
*/
|
|
6703
|
+
const HOSTILE_TERM = /^(?:javascript|data):/i;
|
|
6704
|
+
function selectBlogPosts(listed, options, srcDir, collections) {
|
|
6705
|
+
if (isAmbiguousCollection(options, collections)) return;
|
|
6706
|
+
const names = collectionNames(collections);
|
|
6707
|
+
const name = resolveBlogCollectionName(options.collection, names);
|
|
6708
|
+
const sources = name && collections?.enabled ? collections.collections[name]?.source : void 0;
|
|
6709
|
+
return listed.filter((page) => {
|
|
6710
|
+
if (isExcludedPost(page.frontmatter)) return false;
|
|
6711
|
+
if (!sources) return true;
|
|
6712
|
+
return pageMatchesSources(page.inputPath, srcDir, sources);
|
|
6713
|
+
});
|
|
6714
|
+
}
|
|
6715
|
+
function isAmbiguousCollection(options, collections) {
|
|
6716
|
+
if (options.collection) return false;
|
|
6717
|
+
const names = collectionNames(collections);
|
|
6718
|
+
return names.length > 1 && !names.includes("blog");
|
|
6719
|
+
}
|
|
6720
|
+
function collectionNames(collections) {
|
|
6721
|
+
if (!collections?.enabled) return [];
|
|
6722
|
+
return Object.keys(collections.collections);
|
|
6723
|
+
}
|
|
6724
|
+
function pageMatchesSources(inputPath, srcDir, sources) {
|
|
6725
|
+
const relative = node_path.relative(srcDir, inputPath).split(node_path.sep).join("/");
|
|
6726
|
+
return sources.some((source) => matchGlob(relative, source));
|
|
6727
|
+
}
|
|
6728
|
+
function matchGlob(relative, pattern) {
|
|
6729
|
+
const normalized = pattern.replace(/^\/+/, "");
|
|
6730
|
+
let out = "^";
|
|
6731
|
+
for (let i = 0; i < normalized.length; i += 1) {
|
|
6732
|
+
if (normalized.startsWith("**/", i)) {
|
|
6733
|
+
out += "(?:.*/)?";
|
|
6734
|
+
i += 2;
|
|
6735
|
+
continue;
|
|
6736
|
+
}
|
|
6737
|
+
const ch = normalized[i] ?? "";
|
|
6738
|
+
if (ch === "*") {
|
|
6739
|
+
out += "[^/]*";
|
|
6740
|
+
continue;
|
|
6741
|
+
}
|
|
6742
|
+
if (ch === "?") {
|
|
6743
|
+
out += "[^/]";
|
|
6744
|
+
continue;
|
|
6745
|
+
}
|
|
6746
|
+
if (/[.+^${}()|[\]\\]/.test(ch)) {
|
|
6747
|
+
out += `\\${ch}`;
|
|
6748
|
+
continue;
|
|
6749
|
+
}
|
|
6750
|
+
out += ch;
|
|
6751
|
+
}
|
|
6752
|
+
out += "$";
|
|
6753
|
+
return new RegExp(out).test(relative);
|
|
6754
|
+
}
|
|
6755
|
+
function sortPosts(posts) {
|
|
6756
|
+
return [...posts].sort((left, right) => {
|
|
6757
|
+
const dateCmp = (pageUnix(right.frontmatter) ?? Number.NEGATIVE_INFINITY) - (pageUnix(left.frontmatter) ?? Number.NEGATIVE_INFINITY);
|
|
6758
|
+
if (dateCmp !== 0) return dateCmp;
|
|
6759
|
+
return left.routePaths.href < right.routePaths.href ? -1 : left.routePaths.href > right.routePaths.href ? 1 : 0;
|
|
6760
|
+
});
|
|
6761
|
+
}
|
|
6762
|
+
function collectTags(posts) {
|
|
6763
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
6764
|
+
for (const page of posts) for (const label of termsFromValue(page.frontmatter.tags)) {
|
|
6765
|
+
const slug = tagSlug(label);
|
|
6766
|
+
if (!slug) continue;
|
|
6767
|
+
const existing = buckets.get(slug);
|
|
6768
|
+
if (existing) existing.pages.push(page);
|
|
6769
|
+
else buckets.set(slug, {
|
|
6770
|
+
label,
|
|
6771
|
+
slug,
|
|
6772
|
+
pages: [page]
|
|
6773
|
+
});
|
|
6774
|
+
}
|
|
6775
|
+
return [...buckets.values()].sort((left, right) => left.label.localeCompare(right.label));
|
|
6776
|
+
}
|
|
6777
|
+
function datedPosts(posts) {
|
|
6778
|
+
const dated = [];
|
|
6779
|
+
for (const page of posts) {
|
|
6780
|
+
const parsed = pageDate(page.frontmatter);
|
|
6781
|
+
if (!parsed) continue;
|
|
6782
|
+
dated.push({
|
|
6783
|
+
page,
|
|
6784
|
+
year: String(parsed.year).padStart(4, "0"),
|
|
6785
|
+
month: String(parsed.month).padStart(2, "0"),
|
|
6786
|
+
label: `${String(parsed.year).padStart(4, "0")}-${String(parsed.month).padStart(2, "0")}-${String(parsed.day).padStart(2, "0")}`
|
|
6787
|
+
});
|
|
6788
|
+
}
|
|
6789
|
+
return dated;
|
|
6790
|
+
}
|
|
6791
|
+
function uniqueYears(dated) {
|
|
6792
|
+
return [...new Set(dated.map((entry) => entry.year))].sort((left, right) => right.localeCompare(left));
|
|
6793
|
+
}
|
|
6794
|
+
function uniqueMonths(dated) {
|
|
6795
|
+
return [...new Set(dated.map((entry) => entry.month))].sort((left, right) => left.localeCompare(right));
|
|
6796
|
+
}
|
|
6797
|
+
function toListItem(page) {
|
|
6798
|
+
const parsed = pageDate(page.frontmatter);
|
|
6799
|
+
return {
|
|
6800
|
+
title: page.title,
|
|
6801
|
+
href: page.routePaths.href,
|
|
6802
|
+
dateLabel: parsed ? `${String(parsed.year).padStart(4, "0")}-${String(parsed.month).padStart(2, "0")}-${String(parsed.day).padStart(2, "0")}` : void 0
|
|
6803
|
+
};
|
|
6804
|
+
}
|
|
6805
|
+
function resolvePostAuthors(frontmatter, map) {
|
|
6806
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6807
|
+
const authors = [];
|
|
6808
|
+
for (const key of authorKeys(frontmatter)) {
|
|
6809
|
+
if (seen.has(key)) continue;
|
|
6810
|
+
seen.add(key);
|
|
6811
|
+
authors.push(map[key] ?? { name: key });
|
|
6812
|
+
}
|
|
6813
|
+
return authors;
|
|
6814
|
+
}
|
|
6815
|
+
function authorKeys(frontmatter) {
|
|
6816
|
+
return [...keysFromValue(frontmatter.author), ...keysFromValue(frontmatter.authors)];
|
|
6817
|
+
}
|
|
6818
|
+
function keysFromValue(value) {
|
|
6819
|
+
if (typeof value === "string") return value.trim() ? [value.trim()] : [];
|
|
6820
|
+
if (!Array.isArray(value)) return [];
|
|
6821
|
+
return value.flatMap((item) => typeof item === "string" && item.trim() ? [item.trim()] : []);
|
|
6822
|
+
}
|
|
6823
|
+
function postTagLinks(frontmatter, base) {
|
|
6824
|
+
const links = [];
|
|
6825
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6826
|
+
for (const label of termsFromValue(frontmatter.tags)) {
|
|
6827
|
+
const slug = tagSlug(label);
|
|
6828
|
+
if (!slug || seen.has(slug)) continue;
|
|
6829
|
+
seen.add(slug);
|
|
6830
|
+
links.push({
|
|
6831
|
+
label,
|
|
6832
|
+
href: siteHref$2(base, "blog", "tags", slug)
|
|
6833
|
+
});
|
|
6834
|
+
}
|
|
6835
|
+
return links;
|
|
6836
|
+
}
|
|
6837
|
+
function tagSlug(term) {
|
|
6838
|
+
const trimmed = term.trim();
|
|
6839
|
+
if (!trimmed || HOSTILE_TERM.test(trimmed) || trimmed.includes("..") || trimmed.includes("//")) return;
|
|
6840
|
+
return trimmed.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || void 0;
|
|
6841
|
+
}
|
|
6842
|
+
function termsFromValue(value) {
|
|
6843
|
+
if (typeof value === "string") return value.trim() ? [value.trim()] : [];
|
|
6844
|
+
if (!Array.isArray(value)) return [];
|
|
6845
|
+
return value.flatMap((item) => typeof item === "string" && item.trim() ? [item.trim()] : []);
|
|
6846
|
+
}
|
|
6847
|
+
function isExcludedPost(frontmatter) {
|
|
6848
|
+
return frontmatter.draft === true || frontmatter.unlisted === true;
|
|
6849
|
+
}
|
|
6850
|
+
function pageDate(frontmatter) {
|
|
6851
|
+
return parseDate(dateField(frontmatter.date));
|
|
6852
|
+
}
|
|
6853
|
+
function pageUnix(frontmatter) {
|
|
6854
|
+
return pageDate(frontmatter)?.unix;
|
|
6855
|
+
}
|
|
6856
|
+
function dateField(value) {
|
|
6857
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
6858
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
6859
|
+
if (value instanceof Date && !Number.isNaN(value.getTime())) return value.toISOString();
|
|
6860
|
+
}
|
|
6861
|
+
//#endregion
|
|
6862
|
+
//#region src/blog-pages.ts
|
|
6863
|
+
/**
|
|
6864
|
+
* Generated blog index, tag, and archive pages.
|
|
6865
|
+
*/
|
|
6866
|
+
const AMBIGUOUS_COLLECTION = "[ox-content] blog is enabled but multiple collections are configured; set blog.collection";
|
|
6867
|
+
async function injectBlogPostMeta(input) {
|
|
6868
|
+
if (!input.options?.enabled) return;
|
|
6869
|
+
const posts = selectBlogPosts(input.listed, input.options, input.srcDir, input.collections);
|
|
6870
|
+
if (posts === void 0) return;
|
|
6871
|
+
const listedPaths = new Set(posts.map((page) => page.inputPath));
|
|
6872
|
+
for (const page of input.pages) {
|
|
6873
|
+
if (!listedPaths.has(page.inputPath)) continue;
|
|
6874
|
+
const markdown = await readMarkdown(page.inputPath);
|
|
6875
|
+
page.transformedHtml = postMetaMarkup({
|
|
6876
|
+
authors: resolvePostAuthors(page.frontmatter, input.options.authors),
|
|
6877
|
+
minutes: readingTimeMinutes(markdown),
|
|
6878
|
+
tags: postTagLinks(page.frontmatter, input.base)
|
|
6879
|
+
}) + page.transformedHtml;
|
|
6880
|
+
}
|
|
6881
|
+
}
|
|
6882
|
+
/** Maps a generated blog page onto the SSG render shape. */
|
|
6883
|
+
function toBlogProcessResult(page) {
|
|
6884
|
+
return {
|
|
6885
|
+
inputPath: page.outputPath,
|
|
6886
|
+
routePaths: {
|
|
6887
|
+
outputPath: page.outputPath,
|
|
6888
|
+
urlPath: page.urlPath,
|
|
6889
|
+
href: page.href,
|
|
6890
|
+
ogImagePath: "",
|
|
6891
|
+
ogImageUrl: ""
|
|
6892
|
+
},
|
|
6893
|
+
transformedHtml: page.content,
|
|
6894
|
+
title: page.title,
|
|
6895
|
+
frontmatter: {},
|
|
6896
|
+
toc: []
|
|
6897
|
+
};
|
|
6898
|
+
}
|
|
6899
|
+
/** Renders index, tag, and archive pages and appends them to the build. */
|
|
6900
|
+
async function appendBlogPages(input) {
|
|
6901
|
+
if (!input.options?.enabled) return;
|
|
6902
|
+
if (isAmbiguousCollection(input.options, input.collections)) {
|
|
6903
|
+
input.errors.push(AMBIGUOUS_COLLECTION);
|
|
6904
|
+
return;
|
|
6905
|
+
}
|
|
6906
|
+
const posts = selectBlogPosts(input.listedPages, input.options, input.srcDir, input.collections);
|
|
6907
|
+
if (posts === void 0) return;
|
|
6908
|
+
for (const spec of blogPageSpecs(posts, input.options, input.outDir, input.base)) try {
|
|
6909
|
+
input.generatedPages.push({
|
|
6910
|
+
inputPath: spec.outputPath,
|
|
6911
|
+
outputPath: spec.outputPath,
|
|
6912
|
+
html: await input.render(spec)
|
|
6913
|
+
});
|
|
6914
|
+
} catch (err) {
|
|
6915
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6916
|
+
input.errors.push(`Failed to generate blog page ${spec.href}: ${message}`);
|
|
6917
|
+
}
|
|
6918
|
+
}
|
|
6919
|
+
function blogPageSpecs(posts, options, outDir, base) {
|
|
6920
|
+
const sorted = sortPosts(posts);
|
|
6921
|
+
const pages = [];
|
|
6922
|
+
const pageSize = options.pageSize;
|
|
6923
|
+
const pageCount = Math.max(1, Math.ceil(sorted.length / pageSize) || 1);
|
|
6924
|
+
const totalPages = sorted.length === 0 ? 1 : pageCount;
|
|
6925
|
+
for (let pageNumber = 1; pageNumber <= totalPages; pageNumber += 1) {
|
|
6926
|
+
const slice = sorted.slice((pageNumber - 1) * pageSize, pageNumber * pageSize);
|
|
6927
|
+
const isFirst = pageNumber === 1;
|
|
6928
|
+
const urlPath = isFirst ? "blog" : `blog/page/${pageNumber}`;
|
|
6929
|
+
const outputPath = isFirst ? containedPath$1(outDir, "blog", "index.html") : containedPath$1(outDir, "blog", "page", String(pageNumber), "index.html");
|
|
6930
|
+
if (!outputPath) continue;
|
|
6931
|
+
pages.push({
|
|
6932
|
+
title: isFirst ? "Blog" : `Blog · page ${pageNumber}`,
|
|
6933
|
+
content: indexPageContent(slice.map(toListItem), {
|
|
6934
|
+
newerHref: isFirst ? void 0 : siteHref$2(base, ...pageNumber === 2 ? ["blog"] : [
|
|
6935
|
+
"blog",
|
|
6936
|
+
"page",
|
|
6937
|
+
String(pageNumber - 1)
|
|
6938
|
+
]),
|
|
6939
|
+
olderHref: pageNumber < totalPages ? siteHref$2(base, "blog", "page", String(pageNumber + 1)) : void 0
|
|
6940
|
+
}),
|
|
6941
|
+
outputPath,
|
|
6942
|
+
urlPath,
|
|
6943
|
+
href: siteHref$2(base, ...urlPath.split("/"))
|
|
6944
|
+
});
|
|
6945
|
+
}
|
|
6946
|
+
const tags = collectTags(sorted);
|
|
6947
|
+
for (const tag of tags) {
|
|
6948
|
+
const outputPath = containedPath$1(outDir, "blog", "tags", tag.slug, "index.html");
|
|
6949
|
+
if (!outputPath) continue;
|
|
6950
|
+
pages.push({
|
|
6951
|
+
title: tag.label,
|
|
6952
|
+
content: tagPageContent(tag.label, tag.pages.map(toListItem)),
|
|
6953
|
+
outputPath,
|
|
6954
|
+
urlPath: `blog/tags/${tag.slug}`,
|
|
6955
|
+
href: siteHref$2(base, "blog", "tags", tag.slug)
|
|
6956
|
+
});
|
|
6957
|
+
}
|
|
6958
|
+
const dated = datedPosts(sorted);
|
|
6959
|
+
if (dated.length > 0) {
|
|
6960
|
+
const years = uniqueYears(dated);
|
|
6961
|
+
const archiveIndex = containedPath$1(outDir, "blog", "archive", "index.html");
|
|
6962
|
+
if (archiveIndex) pages.push({
|
|
6963
|
+
title: "Archive",
|
|
6964
|
+
content: archiveIndexContent(years.map((year) => ({
|
|
6965
|
+
year,
|
|
6966
|
+
href: siteHref$2(base, "blog", "archive", year)
|
|
6967
|
+
}))),
|
|
6968
|
+
outputPath: archiveIndex,
|
|
6969
|
+
urlPath: "blog/archive",
|
|
6970
|
+
href: siteHref$2(base, "blog", "archive")
|
|
6971
|
+
});
|
|
6972
|
+
for (const year of years) {
|
|
6973
|
+
const yearPosts = dated.filter((entry) => entry.year === year);
|
|
6974
|
+
const months = uniqueMonths(yearPosts);
|
|
6975
|
+
const yearPath = containedPath$1(outDir, "blog", "archive", year, "index.html");
|
|
6976
|
+
if (yearPath) pages.push({
|
|
6977
|
+
title: year,
|
|
6978
|
+
content: archiveYearContent(year, months.map((month) => ({
|
|
6979
|
+
month: `${year}-${month}`,
|
|
6980
|
+
href: siteHref$2(base, "blog", "archive", year, month)
|
|
6981
|
+
})), yearPosts.map((entry) => toListItem(entry.page))),
|
|
6982
|
+
outputPath: yearPath,
|
|
6983
|
+
urlPath: `blog/archive/${year}`,
|
|
6984
|
+
href: siteHref$2(base, "blog", "archive", year)
|
|
6985
|
+
});
|
|
6986
|
+
for (const month of months) {
|
|
6987
|
+
const monthPosts = yearPosts.filter((entry) => entry.month === month);
|
|
6988
|
+
const monthPath = containedPath$1(outDir, "blog", "archive", year, month, "index.html");
|
|
6989
|
+
if (!monthPath) continue;
|
|
6990
|
+
pages.push({
|
|
6991
|
+
title: `${year}-${month}`,
|
|
6992
|
+
content: archiveMonthContent(`${year}-${month}`, monthPosts.map((entry) => toListItem(entry.page))),
|
|
6993
|
+
outputPath: monthPath,
|
|
6994
|
+
urlPath: `blog/archive/${year}/${month}`,
|
|
6995
|
+
href: siteHref$2(base, "blog", "archive", year, month)
|
|
6996
|
+
});
|
|
6997
|
+
}
|
|
6998
|
+
}
|
|
6999
|
+
}
|
|
7000
|
+
return pages;
|
|
7001
|
+
}
|
|
7002
|
+
async function readMarkdown(inputPath) {
|
|
7003
|
+
try {
|
|
7004
|
+
return await node_fs_promises.readFile(inputPath, "utf8");
|
|
7005
|
+
} catch {
|
|
7006
|
+
return "";
|
|
7007
|
+
}
|
|
7008
|
+
}
|
|
7009
|
+
//#endregion
|
|
7010
|
+
//#region src/section-index-html.ts
|
|
7011
|
+
/**
|
|
7012
|
+
* Section-index listing HTML and href safety.
|
|
7013
|
+
*
|
|
7014
|
+
* Titles are escaped. `javascript:` / `data:` / `vbscript:` / `file:` hrefs
|
|
7015
|
+
* are dropped. The NAPI helper is preferred when present.
|
|
7016
|
+
*/
|
|
7017
|
+
const HOSTILE_SCHEME = /^(?:javascript|data|vbscript|file):/i;
|
|
7018
|
+
/** `https:`-free, same-origin or relative href. `javascript:` is rejected. */
|
|
7019
|
+
function isSafeSectionHref(value) {
|
|
7020
|
+
const trimmed = value.trim();
|
|
7021
|
+
if (!trimmed || /[\n\r\0\t]/.test(trimmed) || trimmed.startsWith("//")) return false;
|
|
7022
|
+
if (trimmed.startsWith("/")) return true;
|
|
7023
|
+
if (trimmed.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/)) return false;
|
|
7024
|
+
return !HOSTILE_SCHEME.test(trimmed);
|
|
7025
|
+
}
|
|
7026
|
+
/** Escapes text and attribute values in generated listing markup. */
|
|
7027
|
+
function escapeSectionIndexHtml(value) {
|
|
7028
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
7029
|
+
}
|
|
7030
|
+
/** Renders the listing body. Titles are escaped; hostile hrefs are dropped. */
|
|
7031
|
+
function renderSectionIndexHtml(title, items, style) {
|
|
7032
|
+
try {
|
|
7033
|
+
const napi = require_vitepress.importNapiModuleSync();
|
|
7034
|
+
if (typeof napi.renderSsgSectionIndex === "function") return napi.renderSsgSectionIndex(title, items.map((item) => ({
|
|
7035
|
+
title: item.title,
|
|
7036
|
+
href: item.href,
|
|
7037
|
+
description: item.description
|
|
7038
|
+
})), style);
|
|
7039
|
+
} catch {}
|
|
7040
|
+
return renderSectionIndexHtmlLocal(title, items, style);
|
|
7041
|
+
}
|
|
7042
|
+
function renderSectionIndexHtmlLocal(title, items, style) {
|
|
7043
|
+
const safe = items.filter((item) => isSafeSectionHref(item.href));
|
|
7044
|
+
const modifier = style === "list" ? "list" : "cards";
|
|
7045
|
+
const listClass = style === "list" ? "ox-section-index__list" : "ox-section-index__cards";
|
|
7046
|
+
const body = safe.map((item) => renderItem(item, style)).join("");
|
|
7047
|
+
return `<nav class="ox-section-index ox-section-index--${modifier}" aria-label="Section pages"><h1>${escapeSectionIndexHtml(title)}</h1><ul class="${listClass}">${body}</ul></nav>`;
|
|
7048
|
+
}
|
|
7049
|
+
function renderItem(item, style) {
|
|
7050
|
+
const href = escapeSectionIndexHtml(item.href.trim());
|
|
7051
|
+
const label = escapeSectionIndexHtml(item.title);
|
|
7052
|
+
if (style === "list") return `<li><a href="${href}">${label}</a></li>`;
|
|
7053
|
+
return `<li class="ox-section-index__card"><a href="${href}"><span class="ox-section-index__title">${label}</span>${typeof item.description === "string" && item.description.trim() ? `<span class="ox-section-index__desc">${escapeSectionIndexHtml(item.description)}</span>` : ""}</a></li>`;
|
|
7054
|
+
}
|
|
7055
|
+
//#endregion
|
|
7056
|
+
//#region src/section-index-paths.ts
|
|
7057
|
+
/**
|
|
7058
|
+
* Section-index URL, title, and output-path helpers.
|
|
7059
|
+
*/
|
|
7060
|
+
function pageTitle(page) {
|
|
7061
|
+
if (page.title.trim()) return page.title;
|
|
7062
|
+
return formatSectionTitle(node_path.basename(page.inputPath ?? page.routePaths.urlPath).replace(/\.[^.]+$/, "") || page.routePaths.urlPath);
|
|
7063
|
+
}
|
|
7064
|
+
function sectionTitle(dir) {
|
|
7065
|
+
if (!dir) return "Home";
|
|
7066
|
+
return formatSectionTitle(dir.slice(dir.lastIndexOf("/") + 1));
|
|
7067
|
+
}
|
|
7068
|
+
function formatSectionTitle(name) {
|
|
7069
|
+
try {
|
|
7070
|
+
return require_vitepress.importNapiModuleSync().formatSsgTitle(name);
|
|
7071
|
+
} catch {
|
|
7072
|
+
if (!name) return "Untitled";
|
|
7073
|
+
return name.charAt(0).toUpperCase() + name.slice(1).replace(/[-_]+/g, " ");
|
|
7074
|
+
}
|
|
7075
|
+
}
|
|
7076
|
+
function normalizeUrlPath(urlPath) {
|
|
7077
|
+
if (!urlPath || urlPath === "/") return "";
|
|
7078
|
+
return urlPath.replace(/^\/+|\/+$/g, "");
|
|
7079
|
+
}
|
|
7080
|
+
function parentDir(urlPath) {
|
|
7081
|
+
const normalized = normalizeUrlPath(urlPath);
|
|
7082
|
+
if (!normalized) return null;
|
|
7083
|
+
const index = normalized.lastIndexOf("/");
|
|
7084
|
+
return index === -1 ? "" : normalized.slice(0, index);
|
|
7085
|
+
}
|
|
7086
|
+
function firstChildDir(urlPath, parent) {
|
|
7087
|
+
const normalized = normalizeUrlPath(urlPath);
|
|
7088
|
+
if (!normalized) return;
|
|
7089
|
+
if (!parent) {
|
|
7090
|
+
const slash = normalized.indexOf("/");
|
|
7091
|
+
return slash === -1 ? void 0 : normalized.slice(0, slash);
|
|
7092
|
+
}
|
|
7093
|
+
const prefix = `${parent}/`;
|
|
7094
|
+
if (!normalized.startsWith(prefix) || normalized === parent) return;
|
|
7095
|
+
const rest = normalized.slice(prefix.length);
|
|
7096
|
+
const slash = rest.indexOf("/");
|
|
7097
|
+
return slash === -1 ? void 0 : `${parent}/${rest.slice(0, slash)}`;
|
|
7098
|
+
}
|
|
7099
|
+
function sectionHref(base, dir, extension) {
|
|
7100
|
+
const prefix = !base || base === "/" ? "/" : base.endsWith("/") ? base : `${base}/`;
|
|
7101
|
+
const ext = extension.startsWith(".") ? extension : `.${extension}`;
|
|
7102
|
+
return dir ? `${prefix}${dir}/index${ext}` : `${prefix}index${ext}`;
|
|
7103
|
+
}
|
|
7104
|
+
function sectionOutputPath(outDir, dir, extension) {
|
|
7105
|
+
const ext = extension.startsWith(".") ? extension : `.${extension}`;
|
|
7106
|
+
return containedPath(outDir, ...dir ? [...dir.split("/").filter(Boolean), `index${ext}`] : [`index${ext}`]);
|
|
7107
|
+
}
|
|
7108
|
+
function dirFromOutputPath(outputPath, outDir) {
|
|
7109
|
+
return node_path.relative(node_path.resolve(outDir), node_path.resolve(outputPath)).replaceAll(node_path.sep, "/").replace(/\/index\.[^/]+$/u, "").replace(/^index\.[^/]+$/u, "").replace(/^\/+|\/+$/g, "");
|
|
7110
|
+
}
|
|
7111
|
+
function containedPath(outDir, ...segments) {
|
|
7112
|
+
const root = node_path.resolve(outDir);
|
|
7113
|
+
const resolved = node_path.resolve(root, ...segments);
|
|
7114
|
+
const prefix = root.endsWith(node_path.sep) ? root : `${root}${node_path.sep}`;
|
|
7115
|
+
if (resolved !== root && !resolved.startsWith(prefix)) return;
|
|
7116
|
+
if (segments.some((segment) => segment === ".." || segment.includes("\0"))) return;
|
|
7117
|
+
return resolved;
|
|
7118
|
+
}
|
|
7119
|
+
//#endregion
|
|
7120
|
+
//#region src/section-index.ts
|
|
7121
|
+
/**
|
|
7122
|
+
* Opt-in generated section index pages.
|
|
7123
|
+
*
|
|
7124
|
+
* Resolution and directory walking live here. Listing HTML is rendered in
|
|
7125
|
+
* Rust (`ox_content_ssg::render_section_index`) when the NAPI helper is
|
|
7126
|
+
* available; a matching TypeScript renderer covers the same escape / href
|
|
7127
|
+
* rules so the SSG path stays safe either way. The Vite plugin appends
|
|
7128
|
+
* themed HTML during SSG and never overwrites an existing index page.
|
|
7129
|
+
*/
|
|
7130
|
+
/**
|
|
7131
|
+
* Resolves `ssg.sectionIndex` with defaults.
|
|
7132
|
+
*
|
|
7133
|
+
* `false` / omitted stays off. `true` enables card listings. An object
|
|
7134
|
+
* enables the feature and overrides only the fields the site set.
|
|
7135
|
+
*/
|
|
7136
|
+
function resolveSectionIndexOptions(value) {
|
|
7137
|
+
if (!value) return {
|
|
7138
|
+
enabled: false,
|
|
7139
|
+
style: "cards"
|
|
7140
|
+
};
|
|
7141
|
+
if (value === true) return {
|
|
7142
|
+
enabled: true,
|
|
7143
|
+
style: "cards"
|
|
7144
|
+
};
|
|
7145
|
+
return {
|
|
7146
|
+
enabled: true,
|
|
7147
|
+
style: value.style === "list" ? "list" : "cards"
|
|
7148
|
+
};
|
|
7149
|
+
}
|
|
7150
|
+
/** Maps a generated section index onto the SSG render shape. */
|
|
7151
|
+
function toSectionIndexProcessResult(page) {
|
|
7152
|
+
return {
|
|
7153
|
+
inputPath: page.outputPath,
|
|
7154
|
+
routePaths: {
|
|
7155
|
+
outputPath: page.outputPath,
|
|
7156
|
+
urlPath: page.urlPath,
|
|
7157
|
+
href: page.href,
|
|
7158
|
+
ogImagePath: "",
|
|
7159
|
+
ogImageUrl: ""
|
|
7160
|
+
},
|
|
7161
|
+
transformedHtml: page.content,
|
|
7162
|
+
title: page.title,
|
|
7163
|
+
frontmatter: {},
|
|
7164
|
+
toc: []
|
|
7165
|
+
};
|
|
7166
|
+
}
|
|
7167
|
+
/** Appends generated section indexes for directories that have no real index. */
|
|
7168
|
+
async function appendSectionIndexPages(input) {
|
|
7169
|
+
if (!input.options?.enabled) return;
|
|
7170
|
+
const existingOutputs = new Set(input.generatedPages.map((page) => node_path.normalize(page.outputPath)));
|
|
7171
|
+
for (const spec of sectionIndexSpecs(input.collectedPages, input.listedPages, input.options, input.outDir, input.base, input.extension)) {
|
|
7172
|
+
if (existingOutputs.has(node_path.normalize(spec.outputPath))) continue;
|
|
7173
|
+
try {
|
|
7174
|
+
const html = await input.render(spec);
|
|
7175
|
+
input.generatedPages.push({
|
|
7176
|
+
inputPath: spec.outputPath,
|
|
7177
|
+
outputPath: spec.outputPath,
|
|
7178
|
+
html
|
|
7179
|
+
});
|
|
7180
|
+
existingOutputs.add(node_path.normalize(spec.outputPath));
|
|
7181
|
+
} catch (err) {
|
|
7182
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7183
|
+
input.errors.push(`Failed to generate section index ${spec.href}: ${message}`);
|
|
7184
|
+
}
|
|
7185
|
+
}
|
|
7186
|
+
}
|
|
7187
|
+
function sectionIndexSpecs(collected, listed, options, outDir, base, extension) {
|
|
7188
|
+
const occupied = /* @__PURE__ */ new Set();
|
|
7189
|
+
for (const page of collected) occupied.add(normalizeUrlPath(page.routePaths.urlPath));
|
|
7190
|
+
for (const page of collected) {
|
|
7191
|
+
const output = page.routePaths.outputPath;
|
|
7192
|
+
if (output) occupied.add(dirFromOutputPath(output, outDir));
|
|
7193
|
+
}
|
|
7194
|
+
const visible = listed.filter((page) => !isHiddenByFlags(page.frontmatter));
|
|
7195
|
+
const childrenByDir = /* @__PURE__ */ new Map();
|
|
7196
|
+
for (const page of visible) {
|
|
7197
|
+
const urlPath = normalizeUrlPath(page.routePaths.urlPath);
|
|
7198
|
+
const parent = parentDir(urlPath);
|
|
7199
|
+
if (parent === null) continue;
|
|
7200
|
+
pushChild(childrenByDir, parent, {
|
|
7201
|
+
title: pageTitle(page),
|
|
7202
|
+
href: page.routePaths.href,
|
|
7203
|
+
description: page.description
|
|
7204
|
+
});
|
|
7205
|
+
let ancestor = parent;
|
|
7206
|
+
while (ancestor !== "") {
|
|
7207
|
+
const grand = parentDir(ancestor);
|
|
7208
|
+
if (grand === null) break;
|
|
7209
|
+
const nested = firstChildDir(urlPath, grand);
|
|
7210
|
+
if (nested) pushUniqueDir(childrenByDir, grand, nested, visible, base, extension);
|
|
7211
|
+
ancestor = grand;
|
|
7212
|
+
}
|
|
7213
|
+
}
|
|
7214
|
+
const pages = [];
|
|
7215
|
+
const dirs = [...childrenByDir.keys()].sort();
|
|
7216
|
+
for (const dir of dirs) {
|
|
7217
|
+
if (occupied.has(dir)) continue;
|
|
7218
|
+
const children = uniqueItems(childrenByDir.get(dir) ?? []).filter((item) => isSafeSectionHref(item.href));
|
|
7219
|
+
if (children.length === 0) continue;
|
|
7220
|
+
children.sort((left, right) => {
|
|
7221
|
+
const titleCmp = left.title.localeCompare(right.title);
|
|
7222
|
+
return titleCmp !== 0 ? titleCmp : left.href.localeCompare(right.href);
|
|
7223
|
+
});
|
|
7224
|
+
const outputPath = sectionOutputPath(outDir, dir, extension);
|
|
7225
|
+
if (!outputPath) continue;
|
|
7226
|
+
const title = sectionTitle(dir);
|
|
7227
|
+
pages.push({
|
|
7228
|
+
title,
|
|
7229
|
+
content: renderSectionIndexHtml(title, children, options.style),
|
|
7230
|
+
outputPath,
|
|
7231
|
+
urlPath: dir || "/",
|
|
7232
|
+
href: sectionHref(base, dir, extension)
|
|
7233
|
+
});
|
|
7234
|
+
}
|
|
7235
|
+
return pages;
|
|
7236
|
+
}
|
|
7237
|
+
function pushChild(map, dir, item) {
|
|
7238
|
+
const list = map.get(dir);
|
|
7239
|
+
if (list) {
|
|
7240
|
+
list.push(item);
|
|
7241
|
+
return;
|
|
7242
|
+
}
|
|
7243
|
+
map.set(dir, [item]);
|
|
7244
|
+
}
|
|
7245
|
+
function pushUniqueDir(map, parent, childDir, visible, base, extension) {
|
|
7246
|
+
const href = sectionHref(base, childDir, extension);
|
|
7247
|
+
if (map.get(parent)?.some((item) => item.href === href)) return;
|
|
7248
|
+
const indexPage = visible.find((page) => normalizeUrlPath(page.routePaths.urlPath) === childDir);
|
|
7249
|
+
pushChild(map, parent, {
|
|
7250
|
+
title: indexPage ? pageTitle(indexPage) : sectionTitle(childDir),
|
|
7251
|
+
href: indexPage?.routePaths.href ?? href,
|
|
7252
|
+
description: indexPage?.description
|
|
5691
7253
|
});
|
|
5692
7254
|
}
|
|
7255
|
+
function uniqueItems(items) {
|
|
7256
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7257
|
+
const unique = [];
|
|
7258
|
+
for (const item of items) {
|
|
7259
|
+
if (seen.has(item.href)) continue;
|
|
7260
|
+
seen.add(item.href);
|
|
7261
|
+
unique.push(item);
|
|
7262
|
+
}
|
|
7263
|
+
return unique;
|
|
7264
|
+
}
|
|
7265
|
+
function isHiddenByFlags(frontmatter) {
|
|
7266
|
+
return frontmatter.draft === true || frontmatter.unlisted === true;
|
|
7267
|
+
}
|
|
5693
7268
|
//#endregion
|
|
5694
7269
|
//#region src/search-provider.ts
|
|
5695
7270
|
const FORBIDDEN_KEY_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -6084,93 +7659,1419 @@ function versionLocation(outputPath, outDir, options) {
|
|
|
6084
7659
|
};
|
|
6085
7660
|
}
|
|
6086
7661
|
return {
|
|
6087
|
-
id: options.current,
|
|
6088
|
-
sibling: normalized
|
|
7662
|
+
id: options.current,
|
|
7663
|
+
sibling: normalized
|
|
7664
|
+
};
|
|
7665
|
+
}
|
|
7666
|
+
function outputToHref(outputPath, outDir, base) {
|
|
7667
|
+
return siteHref$1(base, "", relativeUrl(outputPath, outDir));
|
|
7668
|
+
}
|
|
7669
|
+
/** Applies switcher / banner / search rewrite after every version tree is generated. */
|
|
7670
|
+
function decorateVersionedPages(pages, options, outDir, base) {
|
|
7671
|
+
if (!options.enabled) return;
|
|
7672
|
+
const existingHrefs = new Set(pages.map((page) => outputToHref(page.outputPath, outDir, base)));
|
|
7673
|
+
for (const page of pages) {
|
|
7674
|
+
const { id, sibling } = versionLocation(page.outputPath, outDir, options);
|
|
7675
|
+
page.html = applyVersionChrome(page.html, options, id, sibling, base, existingHrefs);
|
|
7676
|
+
}
|
|
7677
|
+
}
|
|
7678
|
+
async function writeSnapshotSearchIndex(input) {
|
|
7679
|
+
const prefix = sanitizePrefix(input.prefix);
|
|
7680
|
+
if (!prefix) return;
|
|
7681
|
+
const destDir = node_path.join(input.outDir, prefix);
|
|
7682
|
+
const prefixBase = searchIndexUrl(input.base, prefix).replace(/search-index\.json$/, "");
|
|
7683
|
+
const json = await buildSearchIndex(input.srcDir, prefixBase, input.extensions, input.publishState, [], input.mdx);
|
|
7684
|
+
await node_fs_promises.mkdir(destDir, { recursive: true });
|
|
7685
|
+
await writeSearchIndex(json, destDir);
|
|
7686
|
+
const dest = node_path.join(destDir, "search-index.json");
|
|
7687
|
+
try {
|
|
7688
|
+
await node_fs_promises.access(dest);
|
|
7689
|
+
} catch {
|
|
7690
|
+
await node_fs_promises.writeFile(dest, json, "utf8");
|
|
7691
|
+
}
|
|
7692
|
+
return dest;
|
|
7693
|
+
}
|
|
7694
|
+
function applyVersionChrome(html, options, activeId, siblingPath, base, existingHrefs) {
|
|
7695
|
+
if (!options.enabled) return html;
|
|
7696
|
+
const active = options.entries.find((entry) => entry.id === activeId);
|
|
7697
|
+
return injectVersionChrome(html, options.switcher ? versionSwitcherMarkup(versionLinks(options, activeId, siblingPath, base, existingHrefs), options.badge) : "", versionBannerMarkup(active?.banner), searchIndexUrl(base, currentVersionPrefix(options)), searchIndexUrl(base, active?.prefix ?? ""));
|
|
7698
|
+
}
|
|
7699
|
+
function sanitizePrefix(prefix) {
|
|
7700
|
+
const trimmed = prefix.trim().replace(/^\/+|\/+$/g, "");
|
|
7701
|
+
if (!trimmed) return "";
|
|
7702
|
+
return PREFIX_RE.test(trimmed) && !trimmed.includes("..") ? trimmed : "";
|
|
7703
|
+
}
|
|
7704
|
+
function defaultCurrentEntry() {
|
|
7705
|
+
return {
|
|
7706
|
+
id: DEFAULT_CURRENT_ID,
|
|
7707
|
+
label: "Latest",
|
|
7708
|
+
prefix: "",
|
|
7709
|
+
banner: false
|
|
7710
|
+
};
|
|
7711
|
+
}
|
|
7712
|
+
function normalizeEntries(entries) {
|
|
7713
|
+
if (!entries) return [];
|
|
7714
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7715
|
+
const resolved = [];
|
|
7716
|
+
for (const entry of entries) {
|
|
7717
|
+
if (!entry || typeof entry.id !== "string" || typeof entry.label !== "string") continue;
|
|
7718
|
+
const id = entry.id.trim();
|
|
7719
|
+
const label = entry.label.trim();
|
|
7720
|
+
if (!id || !label || seen.has(id)) continue;
|
|
7721
|
+
const prefix = sanitizePrefix(typeof entry.prefix === "string" ? entry.prefix : "");
|
|
7722
|
+
if (entry.prefix && !prefix) continue;
|
|
7723
|
+
const dir = typeof entry.dir === "string" && entry.dir.trim() ? entry.dir.trim() : void 0;
|
|
7724
|
+
if (dir && (dir.includes("\0") || dir.includes(".."))) continue;
|
|
7725
|
+
seen.add(id);
|
|
7726
|
+
resolved.push({
|
|
7727
|
+
id,
|
|
7728
|
+
label,
|
|
7729
|
+
prefix,
|
|
7730
|
+
dir,
|
|
7731
|
+
banner: normalizeBanner(entry.banner)
|
|
7732
|
+
});
|
|
7733
|
+
}
|
|
7734
|
+
return resolved;
|
|
7735
|
+
}
|
|
7736
|
+
function normalizeBanner(value) {
|
|
7737
|
+
return value === "unreleased" || value === "unmaintained" ? value : false;
|
|
7738
|
+
}
|
|
7739
|
+
function siteHref$1(base, prefix, rest) {
|
|
7740
|
+
const root = !base || base === "/" ? "/" : base.endsWith("/") ? base : `${base}/`;
|
|
7741
|
+
const parts = [prefix, rest].filter((part) => part && part !== "/");
|
|
7742
|
+
return parts.length === 0 ? root : `${root}${parts.join("/")}/`;
|
|
7743
|
+
}
|
|
7744
|
+
function relativeUrl(outputPath, outDir) {
|
|
7745
|
+
const rel = node_path.posix.normalize(node_path.relative(node_path.resolve(outDir), node_path.resolve(outputPath)).replaceAll(node_path.sep, "/"));
|
|
7746
|
+
if (rel.startsWith("..")) return "";
|
|
7747
|
+
const dir = rel.endsWith("/index.html") ? rel.slice(0, -11) : rel.replace(/\.html$/, "");
|
|
7748
|
+
return dir === "." ? "" : dir;
|
|
7749
|
+
}
|
|
7750
|
+
//#endregion
|
|
7751
|
+
//#region src/resources-jpeg.ts
|
|
7752
|
+
function encodeJpeg(image, quality = 80) {
|
|
7753
|
+
const yQuant = scaleQuant(LUM_QUANT, quality);
|
|
7754
|
+
const cQuant = scaleQuant(CHR_QUANT, quality);
|
|
7755
|
+
const width = image.width;
|
|
7756
|
+
const height = image.height;
|
|
7757
|
+
const duY = /* @__PURE__ */ new Int32Array(64);
|
|
7758
|
+
const duCb = /* @__PURE__ */ new Int32Array(64);
|
|
7759
|
+
const duCr = /* @__PURE__ */ new Int32Array(64);
|
|
7760
|
+
const bits = new BitWriter();
|
|
7761
|
+
let dcY = 0;
|
|
7762
|
+
let dcCb = 0;
|
|
7763
|
+
let dcCr = 0;
|
|
7764
|
+
for (let y = 0; y < height; y += 8) for (let x = 0; x < width; x += 8) {
|
|
7765
|
+
sampleBlock(image, x, y, duY, duCb, duCr);
|
|
7766
|
+
dcY = encodeBlock(bits, duY, yQuant, dcY, YDC, YAC);
|
|
7767
|
+
dcCb = encodeBlock(bits, duCb, cQuant, dcCb, CDC, CAC);
|
|
7768
|
+
dcCr = encodeBlock(bits, duCr, cQuant, dcCr, CDC, CAC);
|
|
7769
|
+
}
|
|
7770
|
+
bits.flush();
|
|
7771
|
+
return Buffer.concat([
|
|
7772
|
+
jpegHeader(width, height, yQuant, cQuant),
|
|
7773
|
+
bits.toBuffer(),
|
|
7774
|
+
Buffer.from([255, 217])
|
|
7775
|
+
]);
|
|
7776
|
+
}
|
|
7777
|
+
function sampleBlock(image, left, top, yOut, cbOut, crOut) {
|
|
7778
|
+
for (let j = 0; j < 8; j++) {
|
|
7779
|
+
const y = Math.min(image.height - 1, top + j);
|
|
7780
|
+
for (let i = 0; i < 8; i++) {
|
|
7781
|
+
const x = Math.min(image.width - 1, left + i);
|
|
7782
|
+
const p = (y * image.width + x) * 4;
|
|
7783
|
+
const r = image.data[p] ?? 0;
|
|
7784
|
+
const g = image.data[p + 1] ?? 0;
|
|
7785
|
+
const b = image.data[p + 2] ?? 0;
|
|
7786
|
+
const idx = j * 8 + i;
|
|
7787
|
+
yOut[idx] = (66 * r + 129 * g + 25 * b + 128 >> 8) - 128;
|
|
7788
|
+
cbOut[idx] = -38 * r - 74 * g + 112 * b + 128 >> 8;
|
|
7789
|
+
crOut[idx] = 112 * r - 94 * g - 18 * b + 128 >> 8;
|
|
7790
|
+
}
|
|
7791
|
+
}
|
|
7792
|
+
}
|
|
7793
|
+
function encodeBlock(bits, block, quant, lastDc, dcTable, acTable) {
|
|
7794
|
+
const dct = forwardDct(block);
|
|
7795
|
+
const zz = /* @__PURE__ */ new Int32Array(64);
|
|
7796
|
+
for (let i = 0; i < 64; i++) zz[i] = Math.round(dct[ZIGZAG[i]] / quant[i]);
|
|
7797
|
+
const dc = zz[0] ?? 0;
|
|
7798
|
+
writeCoeff(bits, dc - lastDc, dcTable);
|
|
7799
|
+
let zeroRun = 0;
|
|
7800
|
+
for (let i = 1; i < 64; i++) {
|
|
7801
|
+
const value = zz[i] ?? 0;
|
|
7802
|
+
if (value === 0) {
|
|
7803
|
+
zeroRun++;
|
|
7804
|
+
continue;
|
|
7805
|
+
}
|
|
7806
|
+
while (zeroRun > 15) {
|
|
7807
|
+
writeCode(bits, acTable, 240);
|
|
7808
|
+
zeroRun -= 16;
|
|
7809
|
+
}
|
|
7810
|
+
writeCoeff(bits, value, acTable, zeroRun);
|
|
7811
|
+
zeroRun = 0;
|
|
7812
|
+
}
|
|
7813
|
+
if (zeroRun > 0) writeCode(bits, acTable, 0);
|
|
7814
|
+
return dc;
|
|
7815
|
+
}
|
|
7816
|
+
function writeCoeff(bits, value, table, run = 0) {
|
|
7817
|
+
const category = bitCategory(value);
|
|
7818
|
+
writeCode(bits, table, run << 4 | category);
|
|
7819
|
+
if (category > 0) bits.writeBits(value < 0 ? value + ((1 << category) - 1) : value, category);
|
|
7820
|
+
}
|
|
7821
|
+
function writeCode(bits, table, symbol) {
|
|
7822
|
+
const entry = table.get(symbol);
|
|
7823
|
+
if (!entry) throw new Error("missing Huffman code");
|
|
7824
|
+
bits.writeBits(entry.code, entry.len);
|
|
7825
|
+
}
|
|
7826
|
+
function bitCategory(value) {
|
|
7827
|
+
const abs = Math.abs(value);
|
|
7828
|
+
if (!Number.isFinite(abs) || abs === 0) return 0;
|
|
7829
|
+
return Math.min(11, Math.ceil(Math.log2(abs + 1)));
|
|
7830
|
+
}
|
|
7831
|
+
function forwardDct(block) {
|
|
7832
|
+
const out = /* @__PURE__ */ new Float64Array(64);
|
|
7833
|
+
for (let v = 0; v < 8; v++) for (let u = 0; u < 8; u++) {
|
|
7834
|
+
let sum = 0;
|
|
7835
|
+
for (let y = 0; y < 8; y++) for (let x = 0; x < 8; x++) sum += (block[y * 8 + x] ?? 0) * Math.cos((2 * x + 1) * u * Math.PI / 16) * Math.cos((2 * y + 1) * v * Math.PI / 16);
|
|
7836
|
+
const cu = u === 0 ? Math.SQRT1_2 : 1;
|
|
7837
|
+
const cv = v === 0 ? Math.SQRT1_2 : 1;
|
|
7838
|
+
out[v * 8 + u] = .25 * cu * cv * sum;
|
|
7839
|
+
}
|
|
7840
|
+
return out;
|
|
7841
|
+
}
|
|
7842
|
+
function scaleQuant(base, quality) {
|
|
7843
|
+
const q = Math.max(1, Math.min(100, quality));
|
|
7844
|
+
const scale = q < 50 ? Math.floor(5e3 / q) : Math.floor(200 - q * 2);
|
|
7845
|
+
return base.map((value) => Math.max(1, Math.min(255, Math.floor((value * scale + 50) / 100))));
|
|
7846
|
+
}
|
|
7847
|
+
function jpegHeader(width, height, yQuant, cQuant) {
|
|
7848
|
+
const chunks = [
|
|
7849
|
+
Buffer.from([255, 216]),
|
|
7850
|
+
jfifApp0(),
|
|
7851
|
+
dqt(0, yQuant),
|
|
7852
|
+
dqt(1, cQuant),
|
|
7853
|
+
sof(width, height),
|
|
7854
|
+
dht(0, 0, STD_DC_LUM_NCODES, STD_DC_LUM_VALUES),
|
|
7855
|
+
dht(0, 1, STD_DC_CHR_NCODES, STD_DC_CHR_VALUES),
|
|
7856
|
+
dht(1, 0, STD_AC_LUM_NCODES, STD_AC_LUM_VALUES),
|
|
7857
|
+
dht(1, 1, STD_AC_CHR_NCODES, STD_AC_CHR_VALUES),
|
|
7858
|
+
sos()
|
|
7859
|
+
];
|
|
7860
|
+
return Buffer.concat(chunks);
|
|
7861
|
+
}
|
|
7862
|
+
function jfifApp0() {
|
|
7863
|
+
return Buffer.from([
|
|
7864
|
+
255,
|
|
7865
|
+
224,
|
|
7866
|
+
0,
|
|
7867
|
+
16,
|
|
7868
|
+
74,
|
|
7869
|
+
70,
|
|
7870
|
+
73,
|
|
7871
|
+
70,
|
|
7872
|
+
0,
|
|
7873
|
+
1,
|
|
7874
|
+
1,
|
|
7875
|
+
0,
|
|
7876
|
+
0,
|
|
7877
|
+
1,
|
|
7878
|
+
0,
|
|
7879
|
+
1,
|
|
7880
|
+
0,
|
|
7881
|
+
0
|
|
7882
|
+
]);
|
|
7883
|
+
}
|
|
7884
|
+
function dqt(id, table) {
|
|
7885
|
+
const out = Buffer.alloc(69);
|
|
7886
|
+
out[0] = 255;
|
|
7887
|
+
out[1] = 219;
|
|
7888
|
+
out.writeUInt16BE(67, 2);
|
|
7889
|
+
out[4] = id;
|
|
7890
|
+
for (let i = 0; i < 64; i++) out[5 + i] = table[i] ?? 1;
|
|
7891
|
+
return out;
|
|
7892
|
+
}
|
|
7893
|
+
function sof(width, height) {
|
|
7894
|
+
const out = Buffer.from([
|
|
7895
|
+
255,
|
|
7896
|
+
192,
|
|
7897
|
+
0,
|
|
7898
|
+
17,
|
|
7899
|
+
8,
|
|
7900
|
+
0,
|
|
7901
|
+
0,
|
|
7902
|
+
0,
|
|
7903
|
+
0,
|
|
7904
|
+
3,
|
|
7905
|
+
1,
|
|
7906
|
+
17,
|
|
7907
|
+
0,
|
|
7908
|
+
2,
|
|
7909
|
+
17,
|
|
7910
|
+
1,
|
|
7911
|
+
3,
|
|
7912
|
+
17,
|
|
7913
|
+
1
|
|
7914
|
+
]);
|
|
7915
|
+
out.writeUInt16BE(height, 5);
|
|
7916
|
+
out.writeUInt16BE(width, 7);
|
|
7917
|
+
return out;
|
|
7918
|
+
}
|
|
7919
|
+
function dht(cls, id, ncodes, values) {
|
|
7920
|
+
const out = Buffer.alloc(21 + values.length);
|
|
7921
|
+
out[0] = 255;
|
|
7922
|
+
out[1] = 196;
|
|
7923
|
+
out.writeUInt16BE(19 + values.length, 2);
|
|
7924
|
+
out[4] = cls << 4 | id;
|
|
7925
|
+
Buffer.from(ncodes).copy(out, 5);
|
|
7926
|
+
Buffer.from(values).copy(out, 21);
|
|
7927
|
+
return out;
|
|
7928
|
+
}
|
|
7929
|
+
function sos() {
|
|
7930
|
+
return Buffer.from([
|
|
7931
|
+
255,
|
|
7932
|
+
218,
|
|
7933
|
+
0,
|
|
7934
|
+
12,
|
|
7935
|
+
3,
|
|
7936
|
+
1,
|
|
7937
|
+
0,
|
|
7938
|
+
2,
|
|
7939
|
+
17,
|
|
7940
|
+
3,
|
|
7941
|
+
17,
|
|
7942
|
+
0,
|
|
7943
|
+
63,
|
|
7944
|
+
0
|
|
7945
|
+
]);
|
|
7946
|
+
}
|
|
7947
|
+
var BitWriter = class {
|
|
7948
|
+
bytes = [];
|
|
7949
|
+
bits = 0;
|
|
7950
|
+
length = 0;
|
|
7951
|
+
writeBits(value, count) {
|
|
7952
|
+
for (let i = count - 1; i >= 0; i--) {
|
|
7953
|
+
this.bits = this.bits << 1 | value >> i & 1;
|
|
7954
|
+
this.length++;
|
|
7955
|
+
if (this.length === 8) this.pushByte();
|
|
7956
|
+
}
|
|
7957
|
+
}
|
|
7958
|
+
flush() {
|
|
7959
|
+
if (this.length > 0) {
|
|
7960
|
+
this.bits <<= 8 - this.length;
|
|
7961
|
+
this.pushByte();
|
|
7962
|
+
}
|
|
7963
|
+
}
|
|
7964
|
+
toBuffer() {
|
|
7965
|
+
return Buffer.from(this.bytes);
|
|
7966
|
+
}
|
|
7967
|
+
pushByte() {
|
|
7968
|
+
this.bytes.push(this.bits & 255);
|
|
7969
|
+
if ((this.bits & 255) === 255) this.bytes.push(0);
|
|
7970
|
+
this.bits = 0;
|
|
7971
|
+
this.length = 0;
|
|
7972
|
+
}
|
|
7973
|
+
};
|
|
7974
|
+
function buildHuffman(ncodes, values) {
|
|
7975
|
+
const table = /* @__PURE__ */ new Map();
|
|
7976
|
+
let code = 0;
|
|
7977
|
+
let index = 0;
|
|
7978
|
+
for (let len = 1; len <= 16; len++) {
|
|
7979
|
+
const count = ncodes[len - 1] ?? 0;
|
|
7980
|
+
for (let i = 0; i < count; i++) {
|
|
7981
|
+
table.set(values[index++] ?? 0, {
|
|
7982
|
+
code,
|
|
7983
|
+
len
|
|
7984
|
+
});
|
|
7985
|
+
code++;
|
|
7986
|
+
}
|
|
7987
|
+
code <<= 1;
|
|
7988
|
+
}
|
|
7989
|
+
return table;
|
|
7990
|
+
}
|
|
7991
|
+
const ZIGZAG = [
|
|
7992
|
+
0,
|
|
7993
|
+
1,
|
|
7994
|
+
8,
|
|
7995
|
+
16,
|
|
7996
|
+
9,
|
|
7997
|
+
2,
|
|
7998
|
+
3,
|
|
7999
|
+
10,
|
|
8000
|
+
17,
|
|
8001
|
+
24,
|
|
8002
|
+
32,
|
|
8003
|
+
25,
|
|
8004
|
+
18,
|
|
8005
|
+
11,
|
|
8006
|
+
4,
|
|
8007
|
+
5,
|
|
8008
|
+
12,
|
|
8009
|
+
19,
|
|
8010
|
+
26,
|
|
8011
|
+
33,
|
|
8012
|
+
40,
|
|
8013
|
+
48,
|
|
8014
|
+
41,
|
|
8015
|
+
34,
|
|
8016
|
+
27,
|
|
8017
|
+
20,
|
|
8018
|
+
13,
|
|
8019
|
+
6,
|
|
8020
|
+
7,
|
|
8021
|
+
14,
|
|
8022
|
+
21,
|
|
8023
|
+
28,
|
|
8024
|
+
35,
|
|
8025
|
+
42,
|
|
8026
|
+
49,
|
|
8027
|
+
56,
|
|
8028
|
+
57,
|
|
8029
|
+
50,
|
|
8030
|
+
43,
|
|
8031
|
+
36,
|
|
8032
|
+
29,
|
|
8033
|
+
22,
|
|
8034
|
+
15,
|
|
8035
|
+
23,
|
|
8036
|
+
30,
|
|
8037
|
+
37,
|
|
8038
|
+
44,
|
|
8039
|
+
51,
|
|
8040
|
+
58,
|
|
8041
|
+
59,
|
|
8042
|
+
52,
|
|
8043
|
+
45,
|
|
8044
|
+
38,
|
|
8045
|
+
31,
|
|
8046
|
+
39,
|
|
8047
|
+
46,
|
|
8048
|
+
53,
|
|
8049
|
+
60,
|
|
8050
|
+
61,
|
|
8051
|
+
54,
|
|
8052
|
+
47,
|
|
8053
|
+
55,
|
|
8054
|
+
62,
|
|
8055
|
+
63
|
|
8056
|
+
];
|
|
8057
|
+
const LUM_QUANT = [
|
|
8058
|
+
16,
|
|
8059
|
+
11,
|
|
8060
|
+
10,
|
|
8061
|
+
16,
|
|
8062
|
+
24,
|
|
8063
|
+
40,
|
|
8064
|
+
51,
|
|
8065
|
+
61,
|
|
8066
|
+
12,
|
|
8067
|
+
12,
|
|
8068
|
+
14,
|
|
8069
|
+
19,
|
|
8070
|
+
26,
|
|
8071
|
+
58,
|
|
8072
|
+
60,
|
|
8073
|
+
55,
|
|
8074
|
+
14,
|
|
8075
|
+
13,
|
|
8076
|
+
16,
|
|
8077
|
+
24,
|
|
8078
|
+
40,
|
|
8079
|
+
57,
|
|
8080
|
+
69,
|
|
8081
|
+
56,
|
|
8082
|
+
14,
|
|
8083
|
+
17,
|
|
8084
|
+
22,
|
|
8085
|
+
29,
|
|
8086
|
+
51,
|
|
8087
|
+
87,
|
|
8088
|
+
80,
|
|
8089
|
+
62,
|
|
8090
|
+
18,
|
|
8091
|
+
22,
|
|
8092
|
+
37,
|
|
8093
|
+
56,
|
|
8094
|
+
68,
|
|
8095
|
+
109,
|
|
8096
|
+
103,
|
|
8097
|
+
77,
|
|
8098
|
+
24,
|
|
8099
|
+
35,
|
|
8100
|
+
55,
|
|
8101
|
+
64,
|
|
8102
|
+
81,
|
|
8103
|
+
104,
|
|
8104
|
+
113,
|
|
8105
|
+
92,
|
|
8106
|
+
49,
|
|
8107
|
+
64,
|
|
8108
|
+
78,
|
|
8109
|
+
87,
|
|
8110
|
+
103,
|
|
8111
|
+
121,
|
|
8112
|
+
120,
|
|
8113
|
+
101,
|
|
8114
|
+
72,
|
|
8115
|
+
92,
|
|
8116
|
+
95,
|
|
8117
|
+
98,
|
|
8118
|
+
112,
|
|
8119
|
+
100,
|
|
8120
|
+
103,
|
|
8121
|
+
99
|
|
8122
|
+
];
|
|
8123
|
+
const CHR_QUANT = [
|
|
8124
|
+
17,
|
|
8125
|
+
18,
|
|
8126
|
+
24,
|
|
8127
|
+
47,
|
|
8128
|
+
99,
|
|
8129
|
+
99,
|
|
8130
|
+
99,
|
|
8131
|
+
99,
|
|
8132
|
+
18,
|
|
8133
|
+
21,
|
|
8134
|
+
26,
|
|
8135
|
+
66,
|
|
8136
|
+
99,
|
|
8137
|
+
99,
|
|
8138
|
+
99,
|
|
8139
|
+
99,
|
|
8140
|
+
24,
|
|
8141
|
+
26,
|
|
8142
|
+
56,
|
|
8143
|
+
99,
|
|
8144
|
+
99,
|
|
8145
|
+
99,
|
|
8146
|
+
99,
|
|
8147
|
+
99,
|
|
8148
|
+
47,
|
|
8149
|
+
66,
|
|
8150
|
+
99,
|
|
8151
|
+
99,
|
|
8152
|
+
99,
|
|
8153
|
+
99,
|
|
8154
|
+
99,
|
|
8155
|
+
99,
|
|
8156
|
+
99,
|
|
8157
|
+
99,
|
|
8158
|
+
99,
|
|
8159
|
+
99,
|
|
8160
|
+
99,
|
|
8161
|
+
99,
|
|
8162
|
+
99,
|
|
8163
|
+
99,
|
|
8164
|
+
99,
|
|
8165
|
+
99,
|
|
8166
|
+
99,
|
|
8167
|
+
99,
|
|
8168
|
+
99,
|
|
8169
|
+
99,
|
|
8170
|
+
99,
|
|
8171
|
+
99,
|
|
8172
|
+
99,
|
|
8173
|
+
99,
|
|
8174
|
+
99,
|
|
8175
|
+
99,
|
|
8176
|
+
99,
|
|
8177
|
+
99,
|
|
8178
|
+
99,
|
|
8179
|
+
99,
|
|
8180
|
+
99,
|
|
8181
|
+
99,
|
|
8182
|
+
99,
|
|
8183
|
+
99,
|
|
8184
|
+
99,
|
|
8185
|
+
99,
|
|
8186
|
+
99,
|
|
8187
|
+
99
|
|
8188
|
+
];
|
|
8189
|
+
const STD_DC_LUM_NCODES = [
|
|
8190
|
+
0,
|
|
8191
|
+
1,
|
|
8192
|
+
5,
|
|
8193
|
+
1,
|
|
8194
|
+
1,
|
|
8195
|
+
1,
|
|
8196
|
+
1,
|
|
8197
|
+
1,
|
|
8198
|
+
1,
|
|
8199
|
+
0,
|
|
8200
|
+
0,
|
|
8201
|
+
0,
|
|
8202
|
+
0,
|
|
8203
|
+
0,
|
|
8204
|
+
0,
|
|
8205
|
+
0
|
|
8206
|
+
];
|
|
8207
|
+
const STD_DC_LUM_VALUES = [
|
|
8208
|
+
0,
|
|
8209
|
+
1,
|
|
8210
|
+
2,
|
|
8211
|
+
3,
|
|
8212
|
+
4,
|
|
8213
|
+
5,
|
|
8214
|
+
6,
|
|
8215
|
+
7,
|
|
8216
|
+
8,
|
|
8217
|
+
9,
|
|
8218
|
+
10,
|
|
8219
|
+
11
|
|
8220
|
+
];
|
|
8221
|
+
const STD_DC_CHR_NCODES = [
|
|
8222
|
+
0,
|
|
8223
|
+
3,
|
|
8224
|
+
1,
|
|
8225
|
+
1,
|
|
8226
|
+
1,
|
|
8227
|
+
1,
|
|
8228
|
+
1,
|
|
8229
|
+
1,
|
|
8230
|
+
1,
|
|
8231
|
+
1,
|
|
8232
|
+
1,
|
|
8233
|
+
0,
|
|
8234
|
+
0,
|
|
8235
|
+
0,
|
|
8236
|
+
0,
|
|
8237
|
+
0
|
|
8238
|
+
];
|
|
8239
|
+
const STD_DC_CHR_VALUES = [
|
|
8240
|
+
0,
|
|
8241
|
+
1,
|
|
8242
|
+
2,
|
|
8243
|
+
3,
|
|
8244
|
+
4,
|
|
8245
|
+
5,
|
|
8246
|
+
6,
|
|
8247
|
+
7,
|
|
8248
|
+
8,
|
|
8249
|
+
9,
|
|
8250
|
+
10,
|
|
8251
|
+
11
|
|
8252
|
+
];
|
|
8253
|
+
const STD_AC_LUM_NCODES = [
|
|
8254
|
+
0,
|
|
8255
|
+
2,
|
|
8256
|
+
1,
|
|
8257
|
+
3,
|
|
8258
|
+
3,
|
|
8259
|
+
2,
|
|
8260
|
+
4,
|
|
8261
|
+
3,
|
|
8262
|
+
5,
|
|
8263
|
+
5,
|
|
8264
|
+
4,
|
|
8265
|
+
4,
|
|
8266
|
+
0,
|
|
8267
|
+
0,
|
|
8268
|
+
1,
|
|
8269
|
+
125
|
|
8270
|
+
];
|
|
8271
|
+
const STD_AC_LUM_VALUES = [
|
|
8272
|
+
1,
|
|
8273
|
+
2,
|
|
8274
|
+
3,
|
|
8275
|
+
0,
|
|
8276
|
+
4,
|
|
8277
|
+
17,
|
|
8278
|
+
5,
|
|
8279
|
+
18,
|
|
8280
|
+
33,
|
|
8281
|
+
49,
|
|
8282
|
+
65,
|
|
8283
|
+
6,
|
|
8284
|
+
19,
|
|
8285
|
+
81,
|
|
8286
|
+
97,
|
|
8287
|
+
7,
|
|
8288
|
+
34,
|
|
8289
|
+
113,
|
|
8290
|
+
20,
|
|
8291
|
+
50,
|
|
8292
|
+
129,
|
|
8293
|
+
145,
|
|
8294
|
+
161,
|
|
8295
|
+
8,
|
|
8296
|
+
35,
|
|
8297
|
+
66,
|
|
8298
|
+
177,
|
|
8299
|
+
193,
|
|
8300
|
+
21,
|
|
8301
|
+
82,
|
|
8302
|
+
209,
|
|
8303
|
+
240,
|
|
8304
|
+
36,
|
|
8305
|
+
51,
|
|
8306
|
+
98,
|
|
8307
|
+
114,
|
|
8308
|
+
130,
|
|
8309
|
+
9,
|
|
8310
|
+
10,
|
|
8311
|
+
22,
|
|
8312
|
+
23,
|
|
8313
|
+
24,
|
|
8314
|
+
25,
|
|
8315
|
+
26,
|
|
8316
|
+
37,
|
|
8317
|
+
38,
|
|
8318
|
+
39,
|
|
8319
|
+
40,
|
|
8320
|
+
41,
|
|
8321
|
+
42,
|
|
8322
|
+
52,
|
|
8323
|
+
53,
|
|
8324
|
+
54,
|
|
8325
|
+
55,
|
|
8326
|
+
56,
|
|
8327
|
+
57,
|
|
8328
|
+
58,
|
|
8329
|
+
67,
|
|
8330
|
+
68,
|
|
8331
|
+
69,
|
|
8332
|
+
70,
|
|
8333
|
+
71,
|
|
8334
|
+
72,
|
|
8335
|
+
73,
|
|
8336
|
+
74,
|
|
8337
|
+
83,
|
|
8338
|
+
84,
|
|
8339
|
+
85,
|
|
8340
|
+
86,
|
|
8341
|
+
87,
|
|
8342
|
+
88,
|
|
8343
|
+
89,
|
|
8344
|
+
90,
|
|
8345
|
+
99,
|
|
8346
|
+
100,
|
|
8347
|
+
101,
|
|
8348
|
+
102,
|
|
8349
|
+
103,
|
|
8350
|
+
104,
|
|
8351
|
+
105,
|
|
8352
|
+
106,
|
|
8353
|
+
115,
|
|
8354
|
+
116,
|
|
8355
|
+
117,
|
|
8356
|
+
118,
|
|
8357
|
+
119,
|
|
8358
|
+
120,
|
|
8359
|
+
121,
|
|
8360
|
+
122,
|
|
8361
|
+
131,
|
|
8362
|
+
132,
|
|
8363
|
+
133,
|
|
8364
|
+
134,
|
|
8365
|
+
135,
|
|
8366
|
+
136,
|
|
8367
|
+
137,
|
|
8368
|
+
138,
|
|
8369
|
+
146,
|
|
8370
|
+
147,
|
|
8371
|
+
148,
|
|
8372
|
+
149,
|
|
8373
|
+
150,
|
|
8374
|
+
151,
|
|
8375
|
+
152,
|
|
8376
|
+
153,
|
|
8377
|
+
154,
|
|
8378
|
+
162,
|
|
8379
|
+
163,
|
|
8380
|
+
164,
|
|
8381
|
+
165,
|
|
8382
|
+
166,
|
|
8383
|
+
167,
|
|
8384
|
+
168,
|
|
8385
|
+
169,
|
|
8386
|
+
170,
|
|
8387
|
+
178,
|
|
8388
|
+
179,
|
|
8389
|
+
180,
|
|
8390
|
+
181,
|
|
8391
|
+
182,
|
|
8392
|
+
183,
|
|
8393
|
+
184,
|
|
8394
|
+
185,
|
|
8395
|
+
186,
|
|
8396
|
+
194,
|
|
8397
|
+
195,
|
|
8398
|
+
196,
|
|
8399
|
+
197,
|
|
8400
|
+
198,
|
|
8401
|
+
199,
|
|
8402
|
+
200,
|
|
8403
|
+
201,
|
|
8404
|
+
202,
|
|
8405
|
+
210,
|
|
8406
|
+
211,
|
|
8407
|
+
212,
|
|
8408
|
+
213,
|
|
8409
|
+
214,
|
|
8410
|
+
215,
|
|
8411
|
+
216,
|
|
8412
|
+
217,
|
|
8413
|
+
218,
|
|
8414
|
+
225,
|
|
8415
|
+
226,
|
|
8416
|
+
227,
|
|
8417
|
+
228,
|
|
8418
|
+
229,
|
|
8419
|
+
230,
|
|
8420
|
+
231,
|
|
8421
|
+
232,
|
|
8422
|
+
233,
|
|
8423
|
+
234,
|
|
8424
|
+
241,
|
|
8425
|
+
242,
|
|
8426
|
+
243,
|
|
8427
|
+
244,
|
|
8428
|
+
245,
|
|
8429
|
+
246,
|
|
8430
|
+
247,
|
|
8431
|
+
248,
|
|
8432
|
+
249,
|
|
8433
|
+
250
|
|
8434
|
+
];
|
|
8435
|
+
const STD_AC_CHR_NCODES = [
|
|
8436
|
+
0,
|
|
8437
|
+
2,
|
|
8438
|
+
1,
|
|
8439
|
+
2,
|
|
8440
|
+
4,
|
|
8441
|
+
4,
|
|
8442
|
+
3,
|
|
8443
|
+
4,
|
|
8444
|
+
7,
|
|
8445
|
+
5,
|
|
8446
|
+
4,
|
|
8447
|
+
4,
|
|
8448
|
+
0,
|
|
8449
|
+
1,
|
|
8450
|
+
2,
|
|
8451
|
+
119
|
|
8452
|
+
];
|
|
8453
|
+
const STD_AC_CHR_VALUES = [
|
|
8454
|
+
0,
|
|
8455
|
+
1,
|
|
8456
|
+
2,
|
|
8457
|
+
3,
|
|
8458
|
+
17,
|
|
8459
|
+
4,
|
|
8460
|
+
5,
|
|
8461
|
+
33,
|
|
8462
|
+
49,
|
|
8463
|
+
6,
|
|
8464
|
+
18,
|
|
8465
|
+
65,
|
|
8466
|
+
81,
|
|
8467
|
+
7,
|
|
8468
|
+
97,
|
|
8469
|
+
113,
|
|
8470
|
+
19,
|
|
8471
|
+
34,
|
|
8472
|
+
50,
|
|
8473
|
+
129,
|
|
8474
|
+
8,
|
|
8475
|
+
20,
|
|
8476
|
+
66,
|
|
8477
|
+
145,
|
|
8478
|
+
161,
|
|
8479
|
+
177,
|
|
8480
|
+
193,
|
|
8481
|
+
9,
|
|
8482
|
+
35,
|
|
8483
|
+
51,
|
|
8484
|
+
82,
|
|
8485
|
+
240,
|
|
8486
|
+
21,
|
|
8487
|
+
98,
|
|
8488
|
+
114,
|
|
8489
|
+
209,
|
|
8490
|
+
10,
|
|
8491
|
+
22,
|
|
8492
|
+
36,
|
|
8493
|
+
52,
|
|
8494
|
+
225,
|
|
8495
|
+
37,
|
|
8496
|
+
241,
|
|
8497
|
+
23,
|
|
8498
|
+
24,
|
|
8499
|
+
25,
|
|
8500
|
+
26,
|
|
8501
|
+
38,
|
|
8502
|
+
39,
|
|
8503
|
+
40,
|
|
8504
|
+
41,
|
|
8505
|
+
42,
|
|
8506
|
+
53,
|
|
8507
|
+
54,
|
|
8508
|
+
55,
|
|
8509
|
+
56,
|
|
8510
|
+
57,
|
|
8511
|
+
58,
|
|
8512
|
+
67,
|
|
8513
|
+
68,
|
|
8514
|
+
69,
|
|
8515
|
+
70,
|
|
8516
|
+
71,
|
|
8517
|
+
72,
|
|
8518
|
+
73,
|
|
8519
|
+
74,
|
|
8520
|
+
83,
|
|
8521
|
+
84,
|
|
8522
|
+
85,
|
|
8523
|
+
86,
|
|
8524
|
+
87,
|
|
8525
|
+
88,
|
|
8526
|
+
89,
|
|
8527
|
+
90,
|
|
8528
|
+
99,
|
|
8529
|
+
100,
|
|
8530
|
+
101,
|
|
8531
|
+
102,
|
|
8532
|
+
103,
|
|
8533
|
+
104,
|
|
8534
|
+
105,
|
|
8535
|
+
106,
|
|
8536
|
+
115,
|
|
8537
|
+
116,
|
|
8538
|
+
117,
|
|
8539
|
+
118,
|
|
8540
|
+
119,
|
|
8541
|
+
120,
|
|
8542
|
+
121,
|
|
8543
|
+
122,
|
|
8544
|
+
130,
|
|
8545
|
+
131,
|
|
8546
|
+
132,
|
|
8547
|
+
133,
|
|
8548
|
+
134,
|
|
8549
|
+
135,
|
|
8550
|
+
136,
|
|
8551
|
+
137,
|
|
8552
|
+
138,
|
|
8553
|
+
146,
|
|
8554
|
+
147,
|
|
8555
|
+
148,
|
|
8556
|
+
149,
|
|
8557
|
+
150,
|
|
8558
|
+
151,
|
|
8559
|
+
152,
|
|
8560
|
+
153,
|
|
8561
|
+
154,
|
|
8562
|
+
162,
|
|
8563
|
+
163,
|
|
8564
|
+
164,
|
|
8565
|
+
165,
|
|
8566
|
+
166,
|
|
8567
|
+
167,
|
|
8568
|
+
168,
|
|
8569
|
+
169,
|
|
8570
|
+
170,
|
|
8571
|
+
178,
|
|
8572
|
+
179,
|
|
8573
|
+
180,
|
|
8574
|
+
181,
|
|
8575
|
+
182,
|
|
8576
|
+
183,
|
|
8577
|
+
184,
|
|
8578
|
+
185,
|
|
8579
|
+
186,
|
|
8580
|
+
194,
|
|
8581
|
+
195,
|
|
8582
|
+
196,
|
|
8583
|
+
197,
|
|
8584
|
+
198,
|
|
8585
|
+
199,
|
|
8586
|
+
200,
|
|
8587
|
+
201,
|
|
8588
|
+
202,
|
|
8589
|
+
210,
|
|
8590
|
+
211,
|
|
8591
|
+
212,
|
|
8592
|
+
213,
|
|
8593
|
+
214,
|
|
8594
|
+
215,
|
|
8595
|
+
216,
|
|
8596
|
+
217,
|
|
8597
|
+
218,
|
|
8598
|
+
226,
|
|
8599
|
+
227,
|
|
8600
|
+
228,
|
|
8601
|
+
229,
|
|
8602
|
+
230,
|
|
8603
|
+
231,
|
|
8604
|
+
232,
|
|
8605
|
+
233,
|
|
8606
|
+
234,
|
|
8607
|
+
242,
|
|
8608
|
+
243,
|
|
8609
|
+
244,
|
|
8610
|
+
245,
|
|
8611
|
+
246,
|
|
8612
|
+
247,
|
|
8613
|
+
248,
|
|
8614
|
+
249,
|
|
8615
|
+
250
|
|
8616
|
+
];
|
|
8617
|
+
const YDC = buildHuffman(STD_DC_LUM_NCODES, STD_DC_LUM_VALUES);
|
|
8618
|
+
const CDC = buildHuffman(STD_DC_CHR_NCODES, STD_DC_CHR_VALUES);
|
|
8619
|
+
const YAC = buildHuffman(STD_AC_LUM_NCODES, STD_AC_LUM_VALUES);
|
|
8620
|
+
const CAC = buildHuffman(STD_AC_CHR_NCODES, STD_AC_CHR_VALUES);
|
|
8621
|
+
//#endregion
|
|
8622
|
+
//#region src/resources-image.ts
|
|
8623
|
+
/**
|
|
8624
|
+
* Build-time PNG/JPEG pixel helpers for page resources.
|
|
8625
|
+
*
|
|
8626
|
+
* PNG is decoded and re-encoded for resize/crop. JPEG is encode-only so a
|
|
8627
|
+
* `format=jpeg` transform can change the container after the pixel pass.
|
|
8628
|
+
*/
|
|
8629
|
+
const PNG_SIGNATURE = Buffer.from([
|
|
8630
|
+
137,
|
|
8631
|
+
80,
|
|
8632
|
+
78,
|
|
8633
|
+
71,
|
|
8634
|
+
13,
|
|
8635
|
+
10,
|
|
8636
|
+
26,
|
|
8637
|
+
10
|
|
8638
|
+
]);
|
|
8639
|
+
function isPng(buffer) {
|
|
8640
|
+
return buffer.length >= 8 && PNG_SIGNATURE.equals(buffer.subarray(0, 8));
|
|
8641
|
+
}
|
|
8642
|
+
function decodePng(buffer) {
|
|
8643
|
+
if (!isPng(buffer)) throw new Error("not a PNG");
|
|
8644
|
+
let width = 0;
|
|
8645
|
+
let height = 0;
|
|
8646
|
+
let bitDepth = 0;
|
|
8647
|
+
let colorType = 0;
|
|
8648
|
+
const idat = [];
|
|
8649
|
+
let offset = 8;
|
|
8650
|
+
while (offset + 12 <= buffer.length) {
|
|
8651
|
+
const length = buffer.readUInt32BE(offset);
|
|
8652
|
+
const type = buffer.toString("ascii", offset + 4, offset + 8);
|
|
8653
|
+
const start = offset + 8;
|
|
8654
|
+
const end = start + length;
|
|
8655
|
+
if (end + 4 > buffer.length) break;
|
|
8656
|
+
const chunk = buffer.subarray(start, end);
|
|
8657
|
+
if (type === "IHDR") {
|
|
8658
|
+
width = chunk.readUInt32BE(0);
|
|
8659
|
+
height = chunk.readUInt32BE(4);
|
|
8660
|
+
bitDepth = chunk[8] ?? 0;
|
|
8661
|
+
colorType = chunk[9] ?? 0;
|
|
8662
|
+
} else if (type === "IDAT") idat.push(Buffer.from(chunk));
|
|
8663
|
+
else if (type === "IEND") break;
|
|
8664
|
+
offset = end + 4;
|
|
8665
|
+
}
|
|
8666
|
+
if (bitDepth !== 8 || colorType !== 2 && colorType !== 6) throw new Error("unsupported PNG");
|
|
8667
|
+
const channels = colorType === 6 ? 4 : 3;
|
|
8668
|
+
const raw = (0, node_zlib.inflateSync)(Buffer.concat(idat));
|
|
8669
|
+
const stride = width * channels;
|
|
8670
|
+
const data = new Uint8Array(width * height * 4);
|
|
8671
|
+
let src = 0;
|
|
8672
|
+
const prior = new Uint8Array(stride);
|
|
8673
|
+
const recon = new Uint8Array(stride);
|
|
8674
|
+
for (let y = 0; y < height; y++) {
|
|
8675
|
+
const filter = raw[src++] ?? 0;
|
|
8676
|
+
for (let x = 0; x < stride; x++) {
|
|
8677
|
+
const sample = raw[src++] ?? 0;
|
|
8678
|
+
const a = x >= channels ? recon[x - channels] : 0;
|
|
8679
|
+
const b = prior[x] ?? 0;
|
|
8680
|
+
const c = x >= channels ? prior[x - channels] : 0;
|
|
8681
|
+
recon[x] = sample + paethPredict(filter, a, b, c) & 255;
|
|
8682
|
+
}
|
|
8683
|
+
for (let x = 0; x < width; x++) {
|
|
8684
|
+
const i = x * channels;
|
|
8685
|
+
const o = (y * width + x) * 4;
|
|
8686
|
+
data[o] = recon[i] ?? 0;
|
|
8687
|
+
data[o + 1] = recon[i + 1] ?? 0;
|
|
8688
|
+
data[o + 2] = recon[i + 2] ?? 0;
|
|
8689
|
+
data[o + 3] = channels === 4 ? recon[i + 3] ?? 255 : 255;
|
|
8690
|
+
}
|
|
8691
|
+
prior.set(recon);
|
|
8692
|
+
}
|
|
8693
|
+
return {
|
|
8694
|
+
width,
|
|
8695
|
+
height,
|
|
8696
|
+
data
|
|
8697
|
+
};
|
|
8698
|
+
}
|
|
8699
|
+
function paethPredict(filter, a, b, c) {
|
|
8700
|
+
switch (filter) {
|
|
8701
|
+
case 0: return 0;
|
|
8702
|
+
case 1: return a;
|
|
8703
|
+
case 2: return b;
|
|
8704
|
+
case 3: return a + b >> 1;
|
|
8705
|
+
case 4: {
|
|
8706
|
+
const p = a + b - c;
|
|
8707
|
+
const pa = Math.abs(p - a);
|
|
8708
|
+
const pb = Math.abs(p - b);
|
|
8709
|
+
const pc = Math.abs(p - c);
|
|
8710
|
+
if (pa <= pb && pa <= pc) return a;
|
|
8711
|
+
if (pb <= pc) return b;
|
|
8712
|
+
return c;
|
|
8713
|
+
}
|
|
8714
|
+
default: throw new Error("unsupported PNG filter");
|
|
8715
|
+
}
|
|
8716
|
+
}
|
|
8717
|
+
function encodePng(image) {
|
|
8718
|
+
const { width, height, data } = image;
|
|
8719
|
+
const raw = Buffer.alloc((width * 4 + 1) * height);
|
|
8720
|
+
let offset = 0;
|
|
8721
|
+
for (let y = 0; y < height; y++) {
|
|
8722
|
+
raw[offset++] = 0;
|
|
8723
|
+
raw.set(data.subarray(y * width * 4, (y + 1) * width * 4), offset);
|
|
8724
|
+
offset += width * 4;
|
|
8725
|
+
}
|
|
8726
|
+
const ihdr = Buffer.alloc(13);
|
|
8727
|
+
ihdr.writeUInt32BE(width, 0);
|
|
8728
|
+
ihdr.writeUInt32BE(height, 4);
|
|
8729
|
+
ihdr[8] = 8;
|
|
8730
|
+
ihdr[9] = 6;
|
|
8731
|
+
return Buffer.concat([
|
|
8732
|
+
PNG_SIGNATURE,
|
|
8733
|
+
pngChunk("IHDR", ihdr),
|
|
8734
|
+
pngChunk("IDAT", (0, node_zlib.deflateSync)(raw)),
|
|
8735
|
+
pngChunk("IEND", Buffer.alloc(0))
|
|
8736
|
+
]);
|
|
8737
|
+
}
|
|
8738
|
+
function pngChunk(type, data) {
|
|
8739
|
+
const body = Buffer.concat([Buffer.from(type, "ascii"), data]);
|
|
8740
|
+
const chunk = Buffer.alloc(12 + data.length);
|
|
8741
|
+
chunk.writeUInt32BE(data.length, 0);
|
|
8742
|
+
body.copy(chunk, 4);
|
|
8743
|
+
chunk.writeUInt32BE(crc32(body), 8 + data.length);
|
|
8744
|
+
return chunk;
|
|
8745
|
+
}
|
|
8746
|
+
function crc32(data) {
|
|
8747
|
+
let crc = 4294967295;
|
|
8748
|
+
for (const byte of data) {
|
|
8749
|
+
crc ^= byte;
|
|
8750
|
+
for (let i = 0; i < 8; i++) crc = crc & 1 ? crc >>> 1 ^ 3988292384 : crc >>> 1;
|
|
8751
|
+
}
|
|
8752
|
+
return (crc ^ 4294967295) >>> 0;
|
|
8753
|
+
}
|
|
8754
|
+
function resizeNearest(image, width, height) {
|
|
8755
|
+
const data = new Uint8Array(width * height * 4);
|
|
8756
|
+
for (let y = 0; y < height; y++) {
|
|
8757
|
+
const sy = Math.min(image.height - 1, Math.floor(y * image.height / height));
|
|
8758
|
+
for (let x = 0; x < width; x++) {
|
|
8759
|
+
const sx = Math.min(image.width - 1, Math.floor(x * image.width / width));
|
|
8760
|
+
data.set(image.data.subarray((sy * image.width + sx) * 4, (sy * image.width + sx) * 4 + 4), (y * width + x) * 4);
|
|
8761
|
+
}
|
|
8762
|
+
}
|
|
8763
|
+
return {
|
|
8764
|
+
width,
|
|
8765
|
+
height,
|
|
8766
|
+
data
|
|
8767
|
+
};
|
|
8768
|
+
}
|
|
8769
|
+
function cropImage(image, x, y, width, height) {
|
|
8770
|
+
const left = Math.max(0, Math.min(image.width, Math.floor(x)));
|
|
8771
|
+
const top = Math.max(0, Math.min(image.height, Math.floor(y)));
|
|
8772
|
+
const cropW = Math.max(1, Math.min(image.width - left, Math.floor(width)));
|
|
8773
|
+
const cropH = Math.max(1, Math.min(image.height - top, Math.floor(height)));
|
|
8774
|
+
const data = new Uint8Array(cropW * cropH * 4);
|
|
8775
|
+
for (let row = 0; row < cropH; row++) {
|
|
8776
|
+
const src = ((top + row) * image.width + left) * 4;
|
|
8777
|
+
data.set(image.data.subarray(src, src + cropW * 4), row * cropW * 4);
|
|
8778
|
+
}
|
|
8779
|
+
return {
|
|
8780
|
+
width: cropW,
|
|
8781
|
+
height: cropH,
|
|
8782
|
+
data
|
|
8783
|
+
};
|
|
8784
|
+
}
|
|
8785
|
+
function coverCrop(image, width, height) {
|
|
8786
|
+
const scale = Math.max(width / image.width, height / image.height);
|
|
8787
|
+
const scaled = resizeNearest(image, Math.max(width, Math.round(image.width * scale)), Math.max(height, Math.round(image.height * scale)));
|
|
8788
|
+
return cropImage(scaled, Math.max(0, Math.floor((scaled.width - width) / 2)), Math.max(0, Math.floor((scaled.height - height) / 2)), width, height);
|
|
8789
|
+
}
|
|
8790
|
+
//#endregion
|
|
8791
|
+
//#region src/resources-process.ts
|
|
8792
|
+
/**
|
|
8793
|
+
* Page-resource HTML rewriting and transform writes.
|
|
8794
|
+
*/
|
|
8795
|
+
const IMG_TAG = /<img\b[^>]*>/gi;
|
|
8796
|
+
const SRC_ATTR = /\bsrc\s*=\s*(?:"([^"]*)"|'([^']*)')/i;
|
|
8797
|
+
async function processPageResources(input) {
|
|
8798
|
+
if (!input.options.enabled) return {
|
|
8799
|
+
html: input.html,
|
|
8800
|
+
files: [],
|
|
8801
|
+
errors: [],
|
|
8802
|
+
fatal: []
|
|
8803
|
+
};
|
|
8804
|
+
const bundleRoot = node_path.dirname(input.inputPath);
|
|
8805
|
+
const outputDir = node_path.dirname(input.outputPath);
|
|
8806
|
+
const files = [];
|
|
8807
|
+
const errors = [];
|
|
8808
|
+
const fatal = [];
|
|
8809
|
+
let html = input.html;
|
|
8810
|
+
const tags = input.html.match(IMG_TAG) ?? [];
|
|
8811
|
+
for (const tag of tags) {
|
|
8812
|
+
const srcMatch = tag.match(SRC_ATTR);
|
|
8813
|
+
const rawSrc = srcMatch?.[1] ?? srcMatch?.[2];
|
|
8814
|
+
if (!rawSrc) continue;
|
|
8815
|
+
const src = unescapeHtml(rawSrc);
|
|
8816
|
+
const parsed = parseResourceSrc(src);
|
|
8817
|
+
if (!parsed) continue;
|
|
8818
|
+
const resolved = resolveBundlePath(parsed.pathname, bundleRoot, input.srcDir);
|
|
8819
|
+
if (!resolved.ok) {
|
|
8820
|
+
const message = `[ox-content] page resource ${JSON.stringify(src)} on ${input.inputPath} is outside the page bundle`;
|
|
8821
|
+
errors.push(message);
|
|
8822
|
+
fatal.push(message);
|
|
8823
|
+
continue;
|
|
8824
|
+
}
|
|
8825
|
+
let stat;
|
|
8826
|
+
try {
|
|
8827
|
+
stat = await node_fs_promises.stat(resolved.absolute);
|
|
8828
|
+
} catch {
|
|
8829
|
+
const message = `[ox-content] missing page resource ${JSON.stringify(parsed.pathname)} on ${input.inputPath}`;
|
|
8830
|
+
errors.push(message);
|
|
8831
|
+
if (input.options.missing === "error") fatal.push(message);
|
|
8832
|
+
continue;
|
|
8833
|
+
}
|
|
8834
|
+
const transformError = validateTransform(parsed.transform, input.options);
|
|
8835
|
+
if (transformError) {
|
|
8836
|
+
const message = `[ox-content] ${transformError} for ${JSON.stringify(src)} on ${input.inputPath}`;
|
|
8837
|
+
errors.push(message);
|
|
8838
|
+
fatal.push(message);
|
|
8839
|
+
continue;
|
|
8840
|
+
}
|
|
8841
|
+
const hasTransform = hasPixelOrFormatTransform(parsed.transform);
|
|
8842
|
+
const outputName = hasTransform ? transformedFileName(parsed.pathname, parsed.transform, resourceCacheKey(resolved.absolute, stat.mtimeMs, parsed.transform)) : node_path.basename(resolved.absolute);
|
|
8843
|
+
const outputFile = node_path.join(outputDir, outputName);
|
|
8844
|
+
try {
|
|
8845
|
+
if (hasTransform) await writeTransformedResource({
|
|
8846
|
+
sourcePath: resolved.absolute,
|
|
8847
|
+
outputFile,
|
|
8848
|
+
cacheDir: input.cacheDir,
|
|
8849
|
+
mtimeMs: stat.mtimeMs,
|
|
8850
|
+
transform: parsed.transform
|
|
8851
|
+
});
|
|
8852
|
+
else {
|
|
8853
|
+
await node_fs_promises.mkdir(outputDir, { recursive: true });
|
|
8854
|
+
await node_fs_promises.copyFile(resolved.absolute, outputFile);
|
|
8855
|
+
}
|
|
8856
|
+
files.push(outputFile);
|
|
8857
|
+
const rewritten = tag.replace(rawSrc, escapeAttribute(outputName));
|
|
8858
|
+
html = html.replace(tag, rewritten);
|
|
8859
|
+
} catch (error) {
|
|
8860
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
8861
|
+
const message = `[ox-content] failed to process page resource ${JSON.stringify(src)} on ${input.inputPath}: ${detail}`;
|
|
8862
|
+
errors.push(message);
|
|
8863
|
+
fatal.push(message);
|
|
8864
|
+
}
|
|
8865
|
+
}
|
|
8866
|
+
return {
|
|
8867
|
+
html,
|
|
8868
|
+
files,
|
|
8869
|
+
errors,
|
|
8870
|
+
fatal
|
|
8871
|
+
};
|
|
8872
|
+
}
|
|
8873
|
+
function resolveBundlePath(pathname, bundleRoot, contentRoot) {
|
|
8874
|
+
if (node_path.isAbsolute(pathname) || pathname.includes("\0")) return { ok: false };
|
|
8875
|
+
const absolute = node_path.resolve(bundleRoot, pathname);
|
|
8876
|
+
if (!isInsideRoot$1(bundleRoot, absolute) || !isInsideRoot$1(contentRoot, absolute)) return { ok: false };
|
|
8877
|
+
return {
|
|
8878
|
+
ok: true,
|
|
8879
|
+
absolute
|
|
6089
8880
|
};
|
|
6090
8881
|
}
|
|
6091
|
-
function
|
|
6092
|
-
|
|
8882
|
+
function validateTransform(transform, options) {
|
|
8883
|
+
if (transform.width && options.widths.length > 0 && !options.widths.includes(transform.width)) return `width ${transform.width} is not in resources.widths`;
|
|
8884
|
+
if (transform.format && !options.formats.includes(transform.format)) return `format ${transform.format} is not in resources.formats`;
|
|
6093
8885
|
}
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
if (!options.enabled) return;
|
|
6097
|
-
const existingHrefs = new Set(pages.map((page) => outputToHref(page.outputPath, outDir, base)));
|
|
6098
|
-
for (const page of pages) {
|
|
6099
|
-
const { id, sibling } = versionLocation(page.outputPath, outDir, options);
|
|
6100
|
-
page.html = applyVersionChrome(page.html, options, id, sibling, base, existingHrefs);
|
|
6101
|
-
}
|
|
8886
|
+
function hasPixelOrFormatTransform(transform) {
|
|
8887
|
+
return Boolean(transform.width || transform.height || transform.crop || transform.format);
|
|
6102
8888
|
}
|
|
6103
|
-
|
|
6104
|
-
const
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
const
|
|
8889
|
+
function transformedFileName(pathname, transform, cacheKey) {
|
|
8890
|
+
const stem = node_path.basename(pathname).replace(/\.[^.]+$/, "") || "resource";
|
|
8891
|
+
const ext = outputExtension(pathname, transform.format);
|
|
8892
|
+
return `${stem}.${cacheKey.slice(0, 12)}.${ext}`;
|
|
8893
|
+
}
|
|
8894
|
+
function outputExtension(pathname, format) {
|
|
8895
|
+
if (format === "jpeg") return "jpg";
|
|
8896
|
+
if (format) return format;
|
|
8897
|
+
const ext = node_path.extname(pathname).slice(1).toLowerCase();
|
|
8898
|
+
return ext === "jpeg" ? "jpg" : ext || "png";
|
|
8899
|
+
}
|
|
8900
|
+
async function writeTransformedResource(input) {
|
|
8901
|
+
const key = resourceCacheKey(input.sourcePath, input.mtimeMs, input.transform);
|
|
8902
|
+
const ext = node_path.extname(input.outputFile);
|
|
8903
|
+
const cacheFile = node_path.join(input.cacheDir, `${key}${ext}`);
|
|
6112
8904
|
try {
|
|
6113
|
-
await node_fs_promises.
|
|
6114
|
-
|
|
6115
|
-
|
|
8905
|
+
await node_fs_promises.copyFile(cacheFile, input.outputFile);
|
|
8906
|
+
return;
|
|
8907
|
+
} catch {}
|
|
8908
|
+
const output = transformResourceBuffer(await node_fs_promises.readFile(input.sourcePath), input.sourcePath, input.transform);
|
|
8909
|
+
if (output.length > 8388608) throw new Error("transform produced an oversized file");
|
|
8910
|
+
await node_fs_promises.mkdir(node_path.dirname(input.outputFile), { recursive: true });
|
|
8911
|
+
await node_fs_promises.mkdir(input.cacheDir, { recursive: true });
|
|
8912
|
+
await node_fs_promises.writeFile(cacheFile, output);
|
|
8913
|
+
await node_fs_promises.writeFile(input.outputFile, output);
|
|
8914
|
+
}
|
|
8915
|
+
function transformResourceBuffer(source, sourcePath, transform) {
|
|
8916
|
+
const needsPixels = Boolean(transform.width || transform.height || transform.crop);
|
|
8917
|
+
if (!needsPixels && !transform.format) return source;
|
|
8918
|
+
if (!needsPixels && transform.format) {
|
|
8919
|
+
if (!isPng(source)) {
|
|
8920
|
+
if (transform.format === formatFromPath(sourcePath)) return source;
|
|
8921
|
+
throw new Error(`cannot convert ${node_path.extname(sourcePath) || "source"} to ${transform.format}`);
|
|
8922
|
+
}
|
|
8923
|
+
return encodeFormat(decodePng(source), transform.format);
|
|
8924
|
+
}
|
|
8925
|
+
if (!isPng(source)) throw new Error("resize/crop requires a PNG source");
|
|
8926
|
+
return encodeFormat(applyPixelTransform(decodePng(source), transform), transform.format ?? "png");
|
|
8927
|
+
}
|
|
8928
|
+
function applyPixelTransform(image, transform) {
|
|
8929
|
+
const crop = transform.crop;
|
|
8930
|
+
if (crop && crop !== "center") {
|
|
8931
|
+
const parts = crop.split(",").map((part) => Number(part.trim()));
|
|
8932
|
+
if (parts.length === 4 && parts.every((part) => Number.isFinite(part))) return cropImage(image, parts[0], parts[1], parts[2], parts[3]);
|
|
8933
|
+
throw new Error(`invalid crop ${crop}`);
|
|
8934
|
+
}
|
|
8935
|
+
const width = transform.width;
|
|
8936
|
+
const height = transform.height;
|
|
8937
|
+
if (crop === "center") {
|
|
8938
|
+
if (!width || !height) throw new Error("crop=center requires width and height");
|
|
8939
|
+
return coverCrop(image, width, height);
|
|
8940
|
+
}
|
|
8941
|
+
if (width && height) return resizeNearest(image, width, height);
|
|
8942
|
+
if (width) return resizeNearest(image, width, Math.max(1, Math.round(image.height * width / image.width)));
|
|
8943
|
+
if (height) return resizeNearest(image, Math.max(1, Math.round(image.width * height / image.height)), height);
|
|
8944
|
+
return image;
|
|
8945
|
+
}
|
|
8946
|
+
function encodeFormat(image, format) {
|
|
8947
|
+
if (format === "jpeg") return encodeJpeg(image);
|
|
8948
|
+
if (format === "png") return encodePng(image);
|
|
8949
|
+
if (format === "webp") throw new Error("webp encoding requires a webp source without pixel transforms");
|
|
8950
|
+
throw new Error(`unsupported format ${format}`);
|
|
8951
|
+
}
|
|
8952
|
+
function formatFromPath(filePath) {
|
|
8953
|
+
const ext = node_path.extname(filePath).slice(1).toLowerCase();
|
|
8954
|
+
return ext === "jpg" ? "jpeg" : ext;
|
|
8955
|
+
}
|
|
8956
|
+
function unescapeHtml(value) {
|
|
8957
|
+
return value.replaceAll("&", "&").replaceAll(""", "\"").replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
8958
|
+
}
|
|
8959
|
+
function escapeAttribute(value) {
|
|
8960
|
+
return value.replaceAll("&", "&").replaceAll("\"", """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
8961
|
+
}
|
|
8962
|
+
//#endregion
|
|
8963
|
+
//#region src/resources.ts
|
|
8964
|
+
/**
|
|
8965
|
+
* Opt-in page-bundle resources and build-time image processing.
|
|
8966
|
+
*
|
|
8967
|
+
* A page directory is the bundle root. Sibling images are addressable with
|
|
8968
|
+
* relative URLs. Resize/crop/format query transforms run at build time and
|
|
8969
|
+
* are cached by source mtime plus transform params. Paths that leave the
|
|
8970
|
+
* bundle or `srcDir` are never processed.
|
|
8971
|
+
*/
|
|
8972
|
+
const DEFAULT_FORMATS = [
|
|
8973
|
+
"png",
|
|
8974
|
+
"jpeg",
|
|
8975
|
+
"webp"
|
|
8976
|
+
];
|
|
8977
|
+
const HOSTILE_SRC = /^(?:javascript|data|vbscript):/i;
|
|
8978
|
+
var PageResourceError = class extends Error {
|
|
8979
|
+
issues;
|
|
8980
|
+
constructor(issues) {
|
|
8981
|
+
super(issues.join("\n"));
|
|
8982
|
+
this.name = "PageResourceError";
|
|
8983
|
+
this.issues = issues;
|
|
6116
8984
|
}
|
|
6117
|
-
|
|
8985
|
+
};
|
|
8986
|
+
/**
|
|
8987
|
+
* Resolves `resources`. Omitted / `false` stay off. `true` or `{}` enables
|
|
8988
|
+
* defaults. An object enables the feature and overrides only set fields.
|
|
8989
|
+
*/
|
|
8990
|
+
function resolveResourcesOptions(value) {
|
|
8991
|
+
if (!value) return {
|
|
8992
|
+
enabled: false,
|
|
8993
|
+
formats: [...DEFAULT_FORMATS],
|
|
8994
|
+
widths: [],
|
|
8995
|
+
missing: "error"
|
|
8996
|
+
};
|
|
8997
|
+
if (value === true) return {
|
|
8998
|
+
enabled: true,
|
|
8999
|
+
formats: [...DEFAULT_FORMATS],
|
|
9000
|
+
widths: [],
|
|
9001
|
+
missing: "error"
|
|
9002
|
+
};
|
|
9003
|
+
return {
|
|
9004
|
+
enabled: true,
|
|
9005
|
+
formats: normalizeFormats(value.formats),
|
|
9006
|
+
widths: normalizeWidths(value.widths),
|
|
9007
|
+
missing: value.missing === "warn" ? "warn" : "error"
|
|
9008
|
+
};
|
|
6118
9009
|
}
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
9010
|
+
/**
|
|
9011
|
+
* Cache key for a source file plus transform. Changing mtime or params
|
|
9012
|
+
* produces a different key so stale derivatives are not reused.
|
|
9013
|
+
*/
|
|
9014
|
+
function resourceCacheKey(sourcePath, mtimeMs, transform) {
|
|
9015
|
+
return (0, node_crypto.createHash)("sha256").update(sourcePath).update("\0").update(String(mtimeMs)).update("\0").update(JSON.stringify(normalizeTransform(transform))).digest("hex");
|
|
6123
9016
|
}
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
9017
|
+
/** True when `candidate` stays inside `root` after resolve. */
|
|
9018
|
+
function isInsideRoot$1(root, candidate) {
|
|
9019
|
+
const resolvedRoot = node_path.resolve(root);
|
|
9020
|
+
const resolved = node_path.resolve(candidate);
|
|
9021
|
+
const relative = node_path.relative(resolvedRoot, resolved);
|
|
9022
|
+
return relative === "" || !relative.startsWith("..") && !node_path.isAbsolute(relative);
|
|
6128
9023
|
}
|
|
6129
|
-
function
|
|
9024
|
+
function parseResourceSrc(src) {
|
|
9025
|
+
const trimmed = src.trim();
|
|
9026
|
+
if (!trimmed || isRemoteOrAbsolute(trimmed) || HOSTILE_SRC.test(trimmed.replace(/\s+/g, ""))) return;
|
|
9027
|
+
const withoutHash = trimmed.split("#")[0] ?? trimmed;
|
|
9028
|
+
const queryIndex = withoutHash.indexOf("?");
|
|
9029
|
+
const pathname = queryIndex === -1 ? withoutHash : withoutHash.slice(0, queryIndex);
|
|
9030
|
+
const query = queryIndex === -1 ? "" : withoutHash.slice(queryIndex + 1);
|
|
9031
|
+
if (!pathname || pathname.includes("\0")) return;
|
|
9032
|
+
const params = new URLSearchParams(query);
|
|
6130
9033
|
return {
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
9034
|
+
pathname,
|
|
9035
|
+
transform: {
|
|
9036
|
+
width: parsePositiveInt(params.get("width") ?? params.get("w")),
|
|
9037
|
+
height: parsePositiveInt(params.get("height") ?? params.get("h")),
|
|
9038
|
+
crop: params.get("crop")?.trim() || void 0,
|
|
9039
|
+
format: normalizeFormat(params.get("format") ?? void 0)
|
|
9040
|
+
}
|
|
6135
9041
|
};
|
|
6136
9042
|
}
|
|
6137
|
-
function
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
const resolved = [];
|
|
6141
|
-
for (const entry of entries) {
|
|
6142
|
-
if (!entry || typeof entry.id !== "string" || typeof entry.label !== "string") continue;
|
|
6143
|
-
const id = entry.id.trim();
|
|
6144
|
-
const label = entry.label.trim();
|
|
6145
|
-
if (!id || !label || seen.has(id)) continue;
|
|
6146
|
-
const prefix = sanitizePrefix(typeof entry.prefix === "string" ? entry.prefix : "");
|
|
6147
|
-
if (entry.prefix && !prefix) continue;
|
|
6148
|
-
const dir = typeof entry.dir === "string" && entry.dir.trim() ? entry.dir.trim() : void 0;
|
|
6149
|
-
if (dir && (dir.includes("\0") || dir.includes(".."))) continue;
|
|
6150
|
-
seen.add(id);
|
|
6151
|
-
resolved.push({
|
|
6152
|
-
id,
|
|
6153
|
-
label,
|
|
6154
|
-
prefix,
|
|
6155
|
-
dir,
|
|
6156
|
-
banner: normalizeBanner(entry.banner)
|
|
6157
|
-
});
|
|
6158
|
-
}
|
|
6159
|
-
return resolved;
|
|
9043
|
+
function isRemoteOrAbsolute(src) {
|
|
9044
|
+
const compact = src.replace(/\s+/g, "");
|
|
9045
|
+
return /^[a-z][a-z0-9+.-]*:/i.test(compact) || compact.startsWith("//") || compact.startsWith("/");
|
|
6160
9046
|
}
|
|
6161
|
-
function
|
|
6162
|
-
|
|
9047
|
+
function parsePositiveInt(raw) {
|
|
9048
|
+
if (!raw) return;
|
|
9049
|
+
if (!/^[0-9]+$/.test(raw)) return;
|
|
9050
|
+
const value = Number(raw);
|
|
9051
|
+
return value > 0 ? value : void 0;
|
|
6163
9052
|
}
|
|
6164
|
-
function
|
|
6165
|
-
|
|
6166
|
-
const
|
|
6167
|
-
return
|
|
9053
|
+
function normalizeFormats(formats) {
|
|
9054
|
+
if (!formats?.length) return [...DEFAULT_FORMATS];
|
|
9055
|
+
const normalized = formats.map((format) => normalizeFormat(format)).filter((format) => Boolean(format));
|
|
9056
|
+
return normalized.length > 0 ? [...new Set(normalized)] : [...DEFAULT_FORMATS];
|
|
6168
9057
|
}
|
|
6169
|
-
function
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
9058
|
+
function normalizeWidths(widths) {
|
|
9059
|
+
if (!widths?.length) return [];
|
|
9060
|
+
return [...new Set(widths.filter((width) => Number.isInteger(width) && width > 0))];
|
|
9061
|
+
}
|
|
9062
|
+
function normalizeFormat(format) {
|
|
9063
|
+
if (!format) return;
|
|
9064
|
+
const value = format.trim().toLowerCase();
|
|
9065
|
+
if (value === "jpg") return "jpeg";
|
|
9066
|
+
return value || void 0;
|
|
9067
|
+
}
|
|
9068
|
+
function normalizeTransform(transform) {
|
|
9069
|
+
return {
|
|
9070
|
+
width: transform.width,
|
|
9071
|
+
height: transform.height,
|
|
9072
|
+
crop: transform.crop,
|
|
9073
|
+
format: transform.format
|
|
9074
|
+
};
|
|
6174
9075
|
}
|
|
6175
9076
|
//#endregion
|
|
6176
9077
|
//#region src/version-navigation.ts
|
|
@@ -6345,14 +9246,18 @@ function resolveSsgOptions(ssg) {
|
|
|
6345
9246
|
bare: false,
|
|
6346
9247
|
generateOgImage: false,
|
|
6347
9248
|
lastUpdated: false,
|
|
9249
|
+
contributors: resolveContributorsOption(void 0),
|
|
6348
9250
|
pagination: false,
|
|
6349
9251
|
breadcrumbs: false,
|
|
9252
|
+
jsonLd: false,
|
|
6350
9253
|
readerChrome: false,
|
|
6351
9254
|
localeSwitcher: false,
|
|
6352
9255
|
a11y: false,
|
|
6353
9256
|
pageChrome: false,
|
|
6354
9257
|
notFound: resolveNotFoundOptions(void 0),
|
|
6355
|
-
team: resolveTeamOptions(void 0)
|
|
9258
|
+
team: resolveTeamOptions(void 0),
|
|
9259
|
+
blog: resolveBlogOptions(void 0),
|
|
9260
|
+
sectionIndex: resolveSectionIndexOptions(void 0)
|
|
6356
9261
|
};
|
|
6357
9262
|
if (ssg === true || ssg === void 0) return {
|
|
6358
9263
|
enabled: true,
|
|
@@ -6361,14 +9266,18 @@ function resolveSsgOptions(ssg) {
|
|
|
6361
9266
|
bare: false,
|
|
6362
9267
|
generateOgImage: false,
|
|
6363
9268
|
lastUpdated: false,
|
|
9269
|
+
contributors: resolveContributorsOption(void 0),
|
|
6364
9270
|
pagination: false,
|
|
6365
9271
|
breadcrumbs: false,
|
|
9272
|
+
jsonLd: false,
|
|
6366
9273
|
readerChrome: false,
|
|
6367
9274
|
localeSwitcher: false,
|
|
6368
9275
|
a11y: false,
|
|
6369
9276
|
pageChrome: false,
|
|
6370
9277
|
notFound: resolveNotFoundOptions(void 0),
|
|
6371
9278
|
team: resolveTeamOptions(void 0),
|
|
9279
|
+
blog: resolveBlogOptions(void 0),
|
|
9280
|
+
sectionIndex: resolveSectionIndexOptions(void 0),
|
|
6372
9281
|
theme: require_vitepress.resolveTheme(void 0)
|
|
6373
9282
|
};
|
|
6374
9283
|
return {
|
|
@@ -6385,22 +9294,56 @@ function resolveSsgOptions(ssg) {
|
|
|
6385
9294
|
ogImage: ssg.ogImage,
|
|
6386
9295
|
generateOgImage: ssg.generateOgImage ?? false,
|
|
6387
9296
|
lastUpdated: ssg.lastUpdated ?? false,
|
|
9297
|
+
contributors: resolveContributorsOption(ssg.contributors),
|
|
6388
9298
|
pagination: resolvePaginationOption(ssg.pagination),
|
|
6389
9299
|
breadcrumbs: resolvePaginationOption(ssg.breadcrumbs),
|
|
9300
|
+
jsonLd: resolveJsonLdOption(ssg.jsonLd),
|
|
6390
9301
|
readerChrome: resolveReaderChromeOption(ssg.readerChrome),
|
|
6391
9302
|
localeSwitcher: resolveLocaleSwitcherOption(ssg.localeSwitcher),
|
|
6392
9303
|
a11y: resolveA11yOption(ssg.a11y),
|
|
6393
9304
|
pageChrome: require_vitepress.resolvePageChromeOption(ssg.pageChrome),
|
|
6394
9305
|
notFound: resolveNotFoundOptions(ssg.notFound),
|
|
6395
9306
|
team: resolveTeamOptions(ssg.team),
|
|
9307
|
+
blog: resolveBlogOptions(ssg.blog),
|
|
9308
|
+
sectionIndex: resolveSectionIndexOptions(ssg.sectionIndex),
|
|
6396
9309
|
siteUrl: ssg.siteUrl,
|
|
6397
9310
|
theme: require_vitepress.resolveTheme(ssg.theme),
|
|
6398
9311
|
navigation: ssg.navigation
|
|
6399
9312
|
};
|
|
6400
9313
|
}
|
|
9314
|
+
function contributorsForPage(context, inputPath) {
|
|
9315
|
+
const option = context.ssgOptions.contributors;
|
|
9316
|
+
if (!option) return;
|
|
9317
|
+
try {
|
|
9318
|
+
return applyContributorOptions(context.napi?.getGitContributors(inputPath, context.root) ?? [], option);
|
|
9319
|
+
} catch {
|
|
9320
|
+
return [];
|
|
9321
|
+
}
|
|
9322
|
+
}
|
|
6401
9323
|
function resolvePaginationOption(value) {
|
|
6402
9324
|
return value === true || typeof value === "object" && value !== null;
|
|
6403
9325
|
}
|
|
9326
|
+
function resolveJsonLdOption(value) {
|
|
9327
|
+
if (value === true) return { breadcrumbs: true };
|
|
9328
|
+
if (value && typeof value === "object") {
|
|
9329
|
+
const publisher = resolveJsonLdPublisher(value.publisher);
|
|
9330
|
+
return {
|
|
9331
|
+
breadcrumbs: value.breadcrumbs !== false,
|
|
9332
|
+
...publisher ? { publisher } : {}
|
|
9333
|
+
};
|
|
9334
|
+
}
|
|
9335
|
+
return false;
|
|
9336
|
+
}
|
|
9337
|
+
function resolveJsonLdPublisher(publisher) {
|
|
9338
|
+
if (!publisher || typeof publisher !== "object") return;
|
|
9339
|
+
const name = publisher.name?.trim();
|
|
9340
|
+
const url = publisher.url?.trim();
|
|
9341
|
+
if (!name && !url) return;
|
|
9342
|
+
return {
|
|
9343
|
+
...name ? { name } : {},
|
|
9344
|
+
...url ? { url } : {}
|
|
9345
|
+
};
|
|
9346
|
+
}
|
|
6404
9347
|
function resolveReaderChromeOption(value) {
|
|
6405
9348
|
if (value === true) return {
|
|
6406
9349
|
copy: true,
|
|
@@ -6529,7 +9472,7 @@ function localeCodesFor(locales) {
|
|
|
6529
9472
|
async function generateHtmlPage(pageData, navGroups, siteName, base, ogImage, theme, locale, availableLocales, pagination = false, readerChrome = false, breadcrumbs = false, localeSwitcher = false, localePaths, a11y = false, team = {
|
|
6530
9473
|
enabled: false,
|
|
6531
9474
|
members: []
|
|
6532
|
-
}, pageChrome = false, breadcrumbRootHref) {
|
|
9475
|
+
}, pageChrome = false, breadcrumbRootHref, jsonLd = false, siteUrl) {
|
|
6533
9476
|
const mod = await require_vitepress.importNapiModule();
|
|
6534
9477
|
const tocForRust = pageData.toc.map(toRustTocEntry);
|
|
6535
9478
|
const navGroupsForRust = convertNavGroupsForRust(navGroups);
|
|
@@ -6571,6 +9514,7 @@ async function generateHtmlPage(pageData, navGroups, siteName, base, ogImage, th
|
|
|
6571
9514
|
content: pageData.content,
|
|
6572
9515
|
toc: tocForRust,
|
|
6573
9516
|
lastUpdated: pageData.lastUpdated,
|
|
9517
|
+
contributors: pageData.contributors,
|
|
6574
9518
|
path: pageData.path,
|
|
6575
9519
|
entryPage: entryPageForRust,
|
|
6576
9520
|
prev: pageData.prev,
|
|
@@ -6597,7 +9541,12 @@ async function generateHtmlPage(pageData, navGroups, siteName, base, ogImage, th
|
|
|
6597
9541
|
localePaths,
|
|
6598
9542
|
a11y: a11y ? { skipLinkLabel: a11y.skipLinkLabel } : void 0,
|
|
6599
9543
|
team,
|
|
6600
|
-
pageChrome
|
|
9544
|
+
pageChrome,
|
|
9545
|
+
jsonLd: jsonLd ? {
|
|
9546
|
+
breadcrumbs: jsonLd.breadcrumbs,
|
|
9547
|
+
publisher: jsonLd.publisher,
|
|
9548
|
+
siteUrl
|
|
9549
|
+
} : void 0
|
|
6601
9550
|
});
|
|
6602
9551
|
}
|
|
6603
9552
|
async function externalizeSharedPageAssets(pages, outDir, base) {
|
|
@@ -6684,10 +9633,31 @@ async function buildSsg(options, root) {
|
|
|
6684
9633
|
errors.push(...collected.errors);
|
|
6685
9634
|
const { outputPages, listedPages } = applyPublishState(context, collected);
|
|
6686
9635
|
remapPermalinkNav(context, listedPages);
|
|
9636
|
+
await applyPageResources(context, outputPages, generatedFiles, errors);
|
|
6687
9637
|
await generateOgImageAssets(context, collected, generatedFiles, errors);
|
|
6688
9638
|
injectRelatedPages(outputPages, listedPages, context.options.taxonomies);
|
|
9639
|
+
const blog = context.options.blog ?? context.ssgOptions.blog;
|
|
9640
|
+
await injectBlogPostMeta({
|
|
9641
|
+
pages: outputPages,
|
|
9642
|
+
listed: listedPages,
|
|
9643
|
+
options: blog,
|
|
9644
|
+
srcDir: context.srcDir,
|
|
9645
|
+
collections: context.options.collections,
|
|
9646
|
+
base: context.base
|
|
9647
|
+
});
|
|
6689
9648
|
const generatedPages = await generateHtmlPages(context, outputPages, collected, errors);
|
|
6690
9649
|
await appendNotFoundPage(generatedPages, context, collected, errors);
|
|
9650
|
+
await appendSectionIndexPages({
|
|
9651
|
+
generatedPages,
|
|
9652
|
+
collectedPages: collected.pageResults,
|
|
9653
|
+
listedPages,
|
|
9654
|
+
options: context.ssgOptions.sectionIndex,
|
|
9655
|
+
outDir: context.outDir,
|
|
9656
|
+
base: context.base,
|
|
9657
|
+
extension: context.ssgOptions.extension,
|
|
9658
|
+
errors,
|
|
9659
|
+
render: (page) => renderSsgPage(context, toSectionIndexProcessResult(page), collected, listedPages)
|
|
9660
|
+
});
|
|
6691
9661
|
await appendTaxonomyPages({
|
|
6692
9662
|
generatedPages,
|
|
6693
9663
|
listedPages,
|
|
@@ -6697,8 +9667,20 @@ async function buildSsg(options, root) {
|
|
|
6697
9667
|
errors,
|
|
6698
9668
|
render: (page) => renderSsgPage(context, toTaxonomyProcessResult(page), collected, listedPages)
|
|
6699
9669
|
});
|
|
9670
|
+
await appendBlogPages({
|
|
9671
|
+
generatedPages,
|
|
9672
|
+
listedPages,
|
|
9673
|
+
options: blog,
|
|
9674
|
+
collections: context.options.collections,
|
|
9675
|
+
srcDir: context.srcDir,
|
|
9676
|
+
outDir: context.outDir,
|
|
9677
|
+
base: context.base,
|
|
9678
|
+
errors,
|
|
9679
|
+
render: (page) => renderSsgPage(context, toBlogProcessResult(page), collected, listedPages)
|
|
9680
|
+
});
|
|
6700
9681
|
await applyDocumentationVersions(generatedPages, context, errors);
|
|
6701
9682
|
await writeGeneratedPages(generatedPages, context, generatedFiles, listedPages, outputPages, errors);
|
|
9683
|
+
if (options.math?.enabled) generatedFiles.push(...await copyKatexAssets(outDir));
|
|
6702
9684
|
return {
|
|
6703
9685
|
files: generatedFiles,
|
|
6704
9686
|
errors,
|
|
@@ -6727,7 +9709,7 @@ async function createBuildSsgContext(options, root, srcDir, outDir, markdownFile
|
|
|
6727
9709
|
navItems: resolveNavigationGroups(ssgOptions.navigation, base, ssgOptions.extension) ?? (ssgOptions.theme?.sidebar.length ? buildThemeNavItems(ssgOptions.theme.sidebar, base, ssgOptions.extension) : buildNavItems(markdownFiles, srcDir, base, ssgOptions.extension)),
|
|
6728
9710
|
siteName: await resolveSiteName$1(root, ssgOptions),
|
|
6729
9711
|
shouldGenerateOgImages: shouldGenerateOgImages(options),
|
|
6730
|
-
napi: ssgOptions.lastUpdated ? await require_vitepress.importNapiModule() : void 0
|
|
9712
|
+
napi: ssgOptions.lastUpdated || ssgOptions.contributors ? await require_vitepress.importNapiModule() : void 0
|
|
6731
9713
|
};
|
|
6732
9714
|
}
|
|
6733
9715
|
/**
|
|
@@ -6752,6 +9734,27 @@ async function resolveSiteName$1(root, ssgOptions) {
|
|
|
6752
9734
|
return "Documentation";
|
|
6753
9735
|
}
|
|
6754
9736
|
}
|
|
9737
|
+
async function applyPageResources(context, pages, generatedFiles, errors) {
|
|
9738
|
+
const options = context.options.resources;
|
|
9739
|
+
if (!options?.enabled) return;
|
|
9740
|
+
const cacheDir = path.join(context.root, ".cache", "ox-content-resources");
|
|
9741
|
+
const fatal = [];
|
|
9742
|
+
for (const page of pages) {
|
|
9743
|
+
const processed = await processPageResources({
|
|
9744
|
+
html: page.transformedHtml,
|
|
9745
|
+
inputPath: page.inputPath,
|
|
9746
|
+
outputPath: page.routePaths.outputPath,
|
|
9747
|
+
srcDir: context.srcDir,
|
|
9748
|
+
options,
|
|
9749
|
+
cacheDir
|
|
9750
|
+
});
|
|
9751
|
+
page.transformedHtml = processed.html;
|
|
9752
|
+
generatedFiles.push(...processed.files);
|
|
9753
|
+
errors.push(...processed.errors);
|
|
9754
|
+
fatal.push(...processed.fatal);
|
|
9755
|
+
}
|
|
9756
|
+
if (fatal.length > 0) throw new PageResourceError(fatal);
|
|
9757
|
+
}
|
|
6755
9758
|
function applyPermalinkRoutes(context, collected) {
|
|
6756
9759
|
if (!context.options.permalinks?.enabled && !context.options.cascade?.enabled) return;
|
|
6757
9760
|
const routed = applySsgPageRoutes({
|
|
@@ -6831,7 +9834,8 @@ async function transformSsgPage(context, inputPath) {
|
|
|
6831
9834
|
transformedHtml,
|
|
6832
9835
|
title,
|
|
6833
9836
|
description: frontmatter.description,
|
|
6834
|
-
lastUpdated: context.napi?.getGitLastUpdated(inputPath, context.root) ?? void 0,
|
|
9837
|
+
lastUpdated: context.ssgOptions.lastUpdated ? context.napi?.getGitLastUpdated(inputPath, context.root) ?? void 0 : void 0,
|
|
9838
|
+
contributors: contributorsForPage(context, inputPath),
|
|
6835
9839
|
frontmatter,
|
|
6836
9840
|
toc: result.toc
|
|
6837
9841
|
};
|
|
@@ -6986,7 +9990,7 @@ async function renderSsgPage(context, pageResult, collected, allPageResults) {
|
|
|
6986
9990
|
return generateHtmlPage(pageData, navItems, context.siteName, context.base, pageOgImage, theme, locale, i18n ? i18n.locales : void 0, context.ssgOptions.pagination, context.ssgOptions.readerChrome, context.ssgOptions.breadcrumbs, context.ssgOptions.localeSwitcher, localePaths, context.ssgOptions.a11y, context.ssgOptions.team ?? {
|
|
6987
9991
|
enabled: false,
|
|
6988
9992
|
members: []
|
|
6989
|
-
}, context.ssgOptions.pageChrome, versionNavigation?.root.href);
|
|
9993
|
+
}, context.ssgOptions.pageChrome, versionNavigation?.root.href, context.ssgOptions.jsonLd, context.ssgOptions.siteUrl);
|
|
6990
9994
|
}
|
|
6991
9995
|
function rewritePagerOverride(pager, context) {
|
|
6992
9996
|
return pager?.href ? {
|
|
@@ -7002,6 +10006,7 @@ function toThemePageData(pageResult) {
|
|
|
7002
10006
|
html: pageResult.transformedHtml,
|
|
7003
10007
|
toc: pageResult.toc,
|
|
7004
10008
|
lastUpdated: pageResult.lastUpdated,
|
|
10009
|
+
contributors: pageResult.contributors,
|
|
7005
10010
|
path: pageResult.inputPath,
|
|
7006
10011
|
url: pageResult.routePaths.href,
|
|
7007
10012
|
frontmatter: pageResult.frontmatter,
|
|
@@ -7032,6 +10037,7 @@ function createSsgPageData(pageResult) {
|
|
|
7032
10037
|
content: pageResult.transformedHtml,
|
|
7033
10038
|
toc: pageResult.toc,
|
|
7034
10039
|
lastUpdated: pageResult.lastUpdated,
|
|
10040
|
+
contributors: pageResult.contributors,
|
|
7035
10041
|
frontmatter,
|
|
7036
10042
|
path: pageResult.routePaths.urlPath,
|
|
7037
10043
|
href: pageResult.routePaths.href,
|
|
@@ -7131,6 +10137,17 @@ async function applyDocumentationVersions(generatedPages, context, errors) {
|
|
|
7131
10137
|
redirects: snapContext.options.redirects?.map
|
|
7132
10138
|
});
|
|
7133
10139
|
const snapPages = await generateHtmlPages(snapContext, outputPages, snapCollected, errors);
|
|
10140
|
+
await appendSectionIndexPages({
|
|
10141
|
+
generatedPages: snapPages,
|
|
10142
|
+
collectedPages: snapCollected.pageResults,
|
|
10143
|
+
listedPages,
|
|
10144
|
+
options: snapContext.ssgOptions.sectionIndex,
|
|
10145
|
+
outDir: snapContext.outDir,
|
|
10146
|
+
base: snapContext.base,
|
|
10147
|
+
extension: snapContext.ssgOptions.extension,
|
|
10148
|
+
errors,
|
|
10149
|
+
render: (page) => renderSsgPage(snapContext, toSectionIndexProcessResult(page), snapCollected, listedPages)
|
|
10150
|
+
});
|
|
7134
10151
|
generatedPages.push(...snapPages);
|
|
7135
10152
|
if (context.options.search?.enabled) try {
|
|
7136
10153
|
await writeSnapshotSearchIndex({
|
|
@@ -7157,6 +10174,21 @@ function pageAliases(frontmatter) {
|
|
|
7157
10174
|
async function writeGeneratedPages(generatedPages, context, generatedFiles, listedPages, outputPages, errors) {
|
|
7158
10175
|
const optimizedOutput = await externalizeSharedPageAssets(generatedPages, context.outDir, context.base);
|
|
7159
10176
|
generatedFiles.push(...optimizedOutput.assets);
|
|
10177
|
+
const pwa = await writePwaFiles({
|
|
10178
|
+
outDir: context.outDir,
|
|
10179
|
+
siteUrl: context.ssgOptions.siteUrl,
|
|
10180
|
+
base: context.base,
|
|
10181
|
+
siteName: context.siteName,
|
|
10182
|
+
options: context.options.pwa
|
|
10183
|
+
});
|
|
10184
|
+
generatedFiles.push(...pwa.files);
|
|
10185
|
+
if (pwa.warning) {
|
|
10186
|
+
errors.push(pwa.warning);
|
|
10187
|
+
console.warn(pwa.warning);
|
|
10188
|
+
} else if (!context.ssgOptions.bare && context.options.pwa?.enabled) for (const page of optimizedOutput.pages) page.html = injectPwaPageTags(page.html, {
|
|
10189
|
+
options: context.options.pwa,
|
|
10190
|
+
base: context.base
|
|
10191
|
+
});
|
|
7160
10192
|
for (const page of optimizedOutput.pages) {
|
|
7161
10193
|
await fs_promises.mkdir(path.dirname(page.outputPath), { recursive: true });
|
|
7162
10194
|
await fs_promises.writeFile(page.outputPath, page.html, "utf-8");
|
|
@@ -7419,7 +10451,7 @@ async function renderPage$1(filePath, options, navGroups, siteName, base, root,
|
|
|
7419
10451
|
let html = await generateHtmlPage(pageData, localizedNav, siteName, base, options.ssg.ogImage, theme, locale, i18n ? i18n.locales : void 0, options.ssg.pagination, options.ssg.readerChrome, options.ssg.breadcrumbs, options.ssg.localeSwitcher, localePaths, options.ssg.a11y, options.ssg.team ?? {
|
|
7420
10452
|
enabled: false,
|
|
7421
10453
|
members: []
|
|
7422
|
-
}, options.ssg.pageChrome);
|
|
10454
|
+
}, options.ssg.pageChrome, void 0, options.ssg.jsonLd, options.ssg.siteUrl);
|
|
7423
10455
|
html = injectViteHmrClient(html);
|
|
7424
10456
|
return html;
|
|
7425
10457
|
}
|
|
@@ -7951,13 +10983,6 @@ function resolveCardOptions(options) {
|
|
|
7951
10983
|
return { enabled: options.enabled ?? true };
|
|
7952
10984
|
}
|
|
7953
10985
|
//#endregion
|
|
7954
|
-
//#region src/file-tree-options.ts
|
|
7955
|
-
function resolveFileTreeOptions(options) {
|
|
7956
|
-
if (!options) return { enabled: false };
|
|
7957
|
-
if (options === true) return { enabled: true };
|
|
7958
|
-
return { enabled: options.enabled ?? true };
|
|
7959
|
-
}
|
|
7960
|
-
//#endregion
|
|
7961
10986
|
//#region src/include-options.ts
|
|
7962
10987
|
function resolveIncludeOptions(options) {
|
|
7963
10988
|
if (!options) return { enabled: false };
|
|
@@ -8078,6 +11103,353 @@ async function* renderMarkdownStream(chunks, options = {}) {
|
|
|
8078
11103
|
yield renderer.finish();
|
|
8079
11104
|
}
|
|
8080
11105
|
//#endregion
|
|
11106
|
+
//#region src/mdx-islands.ts
|
|
11107
|
+
/**
|
|
11108
|
+
* Discover registered MDX islands from the mdast tree or rendered HTML.
|
|
11109
|
+
*
|
|
11110
|
+
* Framework plugins use this instead of a source regex when MDX is on, so
|
|
11111
|
+
* nested JSX, expression attributes, and fragments stay visible. Names that
|
|
11112
|
+
* are not in the global `components` map and are not document-local import
|
|
11113
|
+
* bindings are left as static HTML.
|
|
11114
|
+
*/
|
|
11115
|
+
const OX_ISLAND_NAME = /data-ox-island="([^"]+)"/g;
|
|
11116
|
+
/**
|
|
11117
|
+
* Collect named MDX JSX tags from a parsed mdast tree (JSON from NAPI `parse()`).
|
|
11118
|
+
* Fragments (`name: null`) and non-JSX nodes are ignored. Walks nested children
|
|
11119
|
+
* so inner islands are found.
|
|
11120
|
+
*/
|
|
11121
|
+
function collectMdxJsxNamesFromAst(ast) {
|
|
11122
|
+
const names = /* @__PURE__ */ new Set();
|
|
11123
|
+
walkMdast(ast, names);
|
|
11124
|
+
return [...names];
|
|
11125
|
+
}
|
|
11126
|
+
/**
|
|
11127
|
+
* Collect `data-ox-island` names from Rust-rendered HTML.
|
|
11128
|
+
* Used when an AST walk is unavailable.
|
|
11129
|
+
*/
|
|
11130
|
+
function collectMdxIslandNamesFromHtml(html) {
|
|
11131
|
+
const names = /* @__PURE__ */ new Set();
|
|
11132
|
+
OX_ISLAND_NAME.lastIndex = 0;
|
|
11133
|
+
let match;
|
|
11134
|
+
while ((match = OX_ISLAND_NAME.exec(html)) !== null) {
|
|
11135
|
+
const name = match[1];
|
|
11136
|
+
if (name) names.add(decodeHtmlAttr$1(name));
|
|
11137
|
+
}
|
|
11138
|
+
return [...names];
|
|
11139
|
+
}
|
|
11140
|
+
/** Keep names that exist on the global component map, in first-seen order. */
|
|
11141
|
+
function intersectRegisteredComponentNames(names, components) {
|
|
11142
|
+
return intersectHydratableComponentNames(names, components);
|
|
11143
|
+
}
|
|
11144
|
+
/**
|
|
11145
|
+
* Keep names that are either globally registered or document-local bindings.
|
|
11146
|
+
*/
|
|
11147
|
+
function intersectHydratableComponentNames(names, components, localNames) {
|
|
11148
|
+
const local = localNames ? new Set(localNames) : null;
|
|
11149
|
+
const used = [];
|
|
11150
|
+
for (const name of names) if ((local?.has(name) || isRegisteredComponent(name, components)) && !used.includes(name)) used.push(name);
|
|
11151
|
+
return used;
|
|
11152
|
+
}
|
|
11153
|
+
/**
|
|
11154
|
+
* Resolve registered island names for an MDX document.
|
|
11155
|
+
*
|
|
11156
|
+
* Prefers a NAPI `parse()` AST walk. Falls back to rendered `data-ox-island`
|
|
11157
|
+
* names so plugins still hydrate if #659 metadata is not present.
|
|
11158
|
+
*/
|
|
11159
|
+
async function discoverRegisteredMdxComponents(input) {
|
|
11160
|
+
return intersectHydratableComponentNames(await tryCollectNamesFromParse(input.source) ?? (input.html !== void 0 ? collectMdxIslandNamesFromHtml(input.html) : []), input.components, input.localNames);
|
|
11161
|
+
}
|
|
11162
|
+
function isRegisteredComponent(name, components) {
|
|
11163
|
+
if (isMapRegistry$1(components)) return components.has(name);
|
|
11164
|
+
if (isPlainObjectRegistry(components)) return Object.prototype.hasOwnProperty.call(components, name);
|
|
11165
|
+
for (const entry of components) if (entry === name) return true;
|
|
11166
|
+
return false;
|
|
11167
|
+
}
|
|
11168
|
+
async function tryCollectNamesFromParse(source) {
|
|
11169
|
+
try {
|
|
11170
|
+
const parsed = (await require_vitepress.importNapiModule()).parse(source, {
|
|
11171
|
+
mdx: true,
|
|
11172
|
+
gfm: true
|
|
11173
|
+
});
|
|
11174
|
+
if (!parsed.ast) return null;
|
|
11175
|
+
return collectMdxJsxNamesFromAst(JSON.parse(parsed.ast));
|
|
11176
|
+
} catch {
|
|
11177
|
+
return null;
|
|
11178
|
+
}
|
|
11179
|
+
}
|
|
11180
|
+
function walkMdast(node, names) {
|
|
11181
|
+
if (!node || typeof node !== "object") return;
|
|
11182
|
+
const record = node;
|
|
11183
|
+
if ((record.type === "mdxJsxFlowElement" || record.type === "mdxJsxTextElement") && typeof record.name === "string" && record.name) names.add(record.name);
|
|
11184
|
+
if (Array.isArray(record.children)) for (const child of record.children) walkMdast(child, names);
|
|
11185
|
+
}
|
|
11186
|
+
function isMapRegistry$1(value) {
|
|
11187
|
+
return typeof value === "object" && value !== null && typeof value.has === "function" && typeof value.get === "function";
|
|
11188
|
+
}
|
|
11189
|
+
function isPlainObjectRegistry(value) {
|
|
11190
|
+
return Object.prototype.toString.call(value) === "[object Object]";
|
|
11191
|
+
}
|
|
11192
|
+
function decodeHtmlAttr$1(value) {
|
|
11193
|
+
return value.replaceAll(""", "\"").replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&");
|
|
11194
|
+
}
|
|
11195
|
+
//#endregion
|
|
11196
|
+
//#region src/document-imports.ts
|
|
11197
|
+
/**
|
|
11198
|
+
* Resolve MDX component imports relative to the document that declared them.
|
|
11199
|
+
*
|
|
11200
|
+
* Only `./` and `../` specifiers become island bindings. Bare, package, and
|
|
11201
|
+
* remote specifiers are reported and ignored. A specifier that leaves the
|
|
11202
|
+
* configured content root is rejected with a diagnostic.
|
|
11203
|
+
*/
|
|
11204
|
+
function resolveContentRootPath(input) {
|
|
11205
|
+
if (input.contentRoot) return node_path.default.resolve(input.contentRoot);
|
|
11206
|
+
const root = input.root ?? process.cwd();
|
|
11207
|
+
return node_path.default.resolve(root, input.srcDir ?? ".");
|
|
11208
|
+
}
|
|
11209
|
+
function stripViteQuery(id) {
|
|
11210
|
+
return id.split("?")[0].split("#")[0];
|
|
11211
|
+
}
|
|
11212
|
+
function resolveDocumentComponentImports(input) {
|
|
11213
|
+
const documentPath = stripViteQuery(input.documentPath);
|
|
11214
|
+
const documentDir = node_path.default.dirname(documentPath);
|
|
11215
|
+
const contentRoot = resolveContentRootPath(input);
|
|
11216
|
+
const diagnostics = [];
|
|
11217
|
+
const candidates = [];
|
|
11218
|
+
for (const statement of input.imports) {
|
|
11219
|
+
const specifier = statement.source;
|
|
11220
|
+
if (!isRelativeSpecifier(specifier)) {
|
|
11221
|
+
diagnostics.push({
|
|
11222
|
+
code: "not-relative",
|
|
11223
|
+
message: `Document component import "${specifier}" is not relative and was ignored.`,
|
|
11224
|
+
specifier
|
|
11225
|
+
});
|
|
11226
|
+
continue;
|
|
11227
|
+
}
|
|
11228
|
+
for (const spec of statement.specifiers) {
|
|
11229
|
+
if (spec.kind === "namespace") continue;
|
|
11230
|
+
const resolvedPath = resolveExistingPath(node_path.default.resolve(documentDir, specifier));
|
|
11231
|
+
if (!isInsideRoot(resolvedPath, contentRoot)) {
|
|
11232
|
+
diagnostics.push({
|
|
11233
|
+
code: "escapes-root",
|
|
11234
|
+
message: `Document component import "${specifier}" escapes the content root.`,
|
|
11235
|
+
specifier,
|
|
11236
|
+
localName: spec.local
|
|
11237
|
+
});
|
|
11238
|
+
continue;
|
|
11239
|
+
}
|
|
11240
|
+
candidates.push({
|
|
11241
|
+
localName: spec.local,
|
|
11242
|
+
specifier,
|
|
11243
|
+
resolvedPath,
|
|
11244
|
+
importPathRelativeToDocument: toDocumentRelativeImport(documentDir, resolvedPath),
|
|
11245
|
+
imported: spec.imported,
|
|
11246
|
+
kind: spec.kind
|
|
11247
|
+
});
|
|
11248
|
+
}
|
|
11249
|
+
}
|
|
11250
|
+
const counts = /* @__PURE__ */ new Map();
|
|
11251
|
+
for (const binding of candidates) counts.set(binding.localName, (counts.get(binding.localName) ?? 0) + 1);
|
|
11252
|
+
const bindings = [];
|
|
11253
|
+
const reportedDuplicates = /* @__PURE__ */ new Set();
|
|
11254
|
+
for (const binding of candidates) {
|
|
11255
|
+
if ((counts.get(binding.localName) ?? 0) > 1) {
|
|
11256
|
+
if (!reportedDuplicates.has(binding.localName)) {
|
|
11257
|
+
reportedDuplicates.add(binding.localName);
|
|
11258
|
+
diagnostics.push({
|
|
11259
|
+
code: "duplicate-binding",
|
|
11260
|
+
message: `Document component name "${binding.localName}" is imported more than once.`,
|
|
11261
|
+
specifier: binding.specifier,
|
|
11262
|
+
localName: binding.localName
|
|
11263
|
+
});
|
|
11264
|
+
}
|
|
11265
|
+
continue;
|
|
11266
|
+
}
|
|
11267
|
+
bindings.push(binding);
|
|
11268
|
+
}
|
|
11269
|
+
return {
|
|
11270
|
+
bindings,
|
|
11271
|
+
diagnostics
|
|
11272
|
+
};
|
|
11273
|
+
}
|
|
11274
|
+
function isRelativeSpecifier(source) {
|
|
11275
|
+
return source.startsWith("./") || source.startsWith("../");
|
|
11276
|
+
}
|
|
11277
|
+
function resolveExistingPath(filePath) {
|
|
11278
|
+
try {
|
|
11279
|
+
return node_fs.default.realpathSync(filePath);
|
|
11280
|
+
} catch {
|
|
11281
|
+
return node_path.default.normalize(filePath);
|
|
11282
|
+
}
|
|
11283
|
+
}
|
|
11284
|
+
function isInsideRoot(resolvedPath, root) {
|
|
11285
|
+
const relative = node_path.default.relative(resolveExistingPath(root), resolvedPath);
|
|
11286
|
+
return relative === "" || !relative.startsWith(`..${node_path.default.sep}`) && relative !== ".." && !node_path.default.isAbsolute(relative);
|
|
11287
|
+
}
|
|
11288
|
+
function toDocumentRelativeImport(documentDir, resolvedPath) {
|
|
11289
|
+
const relative = node_path.default.relative(documentDir, resolvedPath).replace(/\\/g, "/");
|
|
11290
|
+
return relative.startsWith(".") ? relative : `./${relative}`;
|
|
11291
|
+
}
|
|
11292
|
+
//#endregion
|
|
11293
|
+
//#region src/document-islands.ts
|
|
11294
|
+
/**
|
|
11295
|
+
* Combine document-local import resolution with MDX island discovery.
|
|
11296
|
+
*/
|
|
11297
|
+
async function discoverDocumentMdxIslands(input) {
|
|
11298
|
+
const resolved = resolveDocumentComponentImports({
|
|
11299
|
+
imports: input.imports,
|
|
11300
|
+
documentPath: input.documentPath,
|
|
11301
|
+
contentRoot: input.contentRoot ?? resolveContentRootPath(input),
|
|
11302
|
+
srcDir: input.srcDir
|
|
11303
|
+
});
|
|
11304
|
+
const localBindings = new Map(resolved.bindings.map((binding) => [binding.localName, binding]));
|
|
11305
|
+
return {
|
|
11306
|
+
usedComponents: await discoverRegisteredMdxComponents({
|
|
11307
|
+
source: input.source,
|
|
11308
|
+
html: input.html,
|
|
11309
|
+
components: input.components,
|
|
11310
|
+
localNames: localBindings.keys()
|
|
11311
|
+
}),
|
|
11312
|
+
localBindings,
|
|
11313
|
+
diagnostics: resolved.diagnostics
|
|
11314
|
+
};
|
|
11315
|
+
}
|
|
11316
|
+
//#endregion
|
|
11317
|
+
//#region src/island-codegen.ts
|
|
11318
|
+
/**
|
|
11319
|
+
* Emit static component imports for framework Markdown modules.
|
|
11320
|
+
*
|
|
11321
|
+
* Document-local bindings win over the global `components` map for that file
|
|
11322
|
+
* only. Two documents that bind the same local name therefore emit different
|
|
11323
|
+
* specifiers and do not share one module id.
|
|
11324
|
+
*/
|
|
11325
|
+
function renderIslandComponentImports(usedComponents, input) {
|
|
11326
|
+
const documentDir = node_path.default.dirname(stripViteQuery(input.documentPath));
|
|
11327
|
+
const root = input.root || process.cwd();
|
|
11328
|
+
return usedComponents.map((name) => {
|
|
11329
|
+
const local = input.localBindings?.get(name);
|
|
11330
|
+
if (local) return renderLocalImport(local);
|
|
11331
|
+
const componentPath = getGlobalComponentPath(input.globalComponents, name);
|
|
11332
|
+
if (!componentPath) return "";
|
|
11333
|
+
return renderGlobalImport(name, componentPath, documentDir, root);
|
|
11334
|
+
}).filter(Boolean).join("\n");
|
|
11335
|
+
}
|
|
11336
|
+
function renderLocalImport(binding) {
|
|
11337
|
+
const specifier = binding.importPathRelativeToDocument.replace(/\\/g, "/");
|
|
11338
|
+
if (binding.kind === "default") return `import ${binding.localName} from '${specifier}';`;
|
|
11339
|
+
if (binding.imported === binding.localName) return `import { ${binding.imported} } from '${specifier}';`;
|
|
11340
|
+
return `import { ${binding.imported} as ${binding.localName} } from '${specifier}';`;
|
|
11341
|
+
}
|
|
11342
|
+
function renderGlobalImport(name, componentPath, documentDir, root) {
|
|
11343
|
+
const absolutePath = node_path.default.resolve(root, componentPath.replace(/^\.\//, ""));
|
|
11344
|
+
const relativePath = node_path.default.relative(documentDir, absolutePath).replace(/\\/g, "/");
|
|
11345
|
+
return `import ${name} from '${relativePath.startsWith(".") ? relativePath : `./${relativePath}`}';`;
|
|
11346
|
+
}
|
|
11347
|
+
function getGlobalComponentPath(components, name) {
|
|
11348
|
+
if (isMapRegistry(components)) return components.get(name);
|
|
11349
|
+
return Object.prototype.hasOwnProperty.call(components, name) ? components[name] : void 0;
|
|
11350
|
+
}
|
|
11351
|
+
function isMapRegistry(value) {
|
|
11352
|
+
return typeof value === "object" && value !== null && typeof value.has === "function" && typeof value.get === "function";
|
|
11353
|
+
}
|
|
11354
|
+
//#endregion
|
|
11355
|
+
//#region src/island-ssr.ts
|
|
11356
|
+
const RUST_PAYLOAD_KEYS = /* @__PURE__ */ new Set([
|
|
11357
|
+
"props",
|
|
11358
|
+
"expressions",
|
|
11359
|
+
"spreads"
|
|
11360
|
+
]);
|
|
11361
|
+
const PAYLOAD_SCRIPT = /^\s*<script type="application\/json">[\s\S]*?<\/script>/;
|
|
11362
|
+
async function applyIslandSsrHtml(html, renderIsland, filePath, names) {
|
|
11363
|
+
const allowed = names ? new Set(names) : null;
|
|
11364
|
+
const islands = findIslandRanges(html);
|
|
11365
|
+
let output = html;
|
|
11366
|
+
for (const island of islands.toReversed()) {
|
|
11367
|
+
if (allowed && !allowed.has(island.name)) continue;
|
|
11368
|
+
const script = output.slice(island.innerStart, island.closeStart).match(PAYLOAD_SCRIPT)?.[0] ?? "";
|
|
11369
|
+
const props = parseIslandProps(island.propsAttr, script);
|
|
11370
|
+
const ssrHtml = await renderIsland(island.name, props, filePath);
|
|
11371
|
+
output = output.slice(0, island.innerStart) + script + ssrHtml + output.slice(island.closeStart);
|
|
11372
|
+
}
|
|
11373
|
+
return output;
|
|
11374
|
+
}
|
|
11375
|
+
function findIslandRanges(html) {
|
|
11376
|
+
const ranges = [];
|
|
11377
|
+
const openRe = /<(div|span)\b([^>]*\bdata-ox-island="([^"]+)"[^>]*)>/gi;
|
|
11378
|
+
let match;
|
|
11379
|
+
while ((match = openRe.exec(html)) !== null) {
|
|
11380
|
+
const tag = match[1];
|
|
11381
|
+
const name = decodeHtmlAttr(match[3] ?? "");
|
|
11382
|
+
if (!tag || !name) continue;
|
|
11383
|
+
const innerStart = match.index + match[0].length;
|
|
11384
|
+
const closeStart = findMatchingClose(html, innerStart, tag);
|
|
11385
|
+
ranges.push({
|
|
11386
|
+
name,
|
|
11387
|
+
innerStart,
|
|
11388
|
+
closeStart,
|
|
11389
|
+
propsAttr: matchAttr(match[2] ?? "", "data-ox-props")
|
|
11390
|
+
});
|
|
11391
|
+
}
|
|
11392
|
+
return ranges;
|
|
11393
|
+
}
|
|
11394
|
+
function findMatchingClose(html, from, tag) {
|
|
11395
|
+
const openNeedle = `<${tag}`;
|
|
11396
|
+
const closeNeedle = `</${tag}>`;
|
|
11397
|
+
let depth = 1;
|
|
11398
|
+
let cursor = from;
|
|
11399
|
+
while (cursor < html.length) {
|
|
11400
|
+
const nextOpen = indexOfTagOpen(html, openNeedle, cursor);
|
|
11401
|
+
const nextClose = html.indexOf(closeNeedle, cursor);
|
|
11402
|
+
if (nextClose === -1) return html.length;
|
|
11403
|
+
if (nextOpen !== -1 && nextOpen < nextClose) {
|
|
11404
|
+
depth += 1;
|
|
11405
|
+
cursor = nextOpen + openNeedle.length;
|
|
11406
|
+
} else {
|
|
11407
|
+
depth -= 1;
|
|
11408
|
+
if (depth === 0) return nextClose;
|
|
11409
|
+
cursor = nextClose + closeNeedle.length;
|
|
11410
|
+
}
|
|
11411
|
+
}
|
|
11412
|
+
return html.length;
|
|
11413
|
+
}
|
|
11414
|
+
function indexOfTagOpen(html, openNeedle, from) {
|
|
11415
|
+
let cursor = from;
|
|
11416
|
+
while (cursor < html.length) {
|
|
11417
|
+
const index = html.indexOf(openNeedle, cursor);
|
|
11418
|
+
if (index === -1) return -1;
|
|
11419
|
+
const next = html[index + openNeedle.length];
|
|
11420
|
+
if (next === " " || next === ">" || next === " " || next === "\n" || next === "/") return index;
|
|
11421
|
+
cursor = index + openNeedle.length;
|
|
11422
|
+
}
|
|
11423
|
+
return -1;
|
|
11424
|
+
}
|
|
11425
|
+
function matchAttr(attrs, name) {
|
|
11426
|
+
const match = new RegExp(`\\b${name}="([^"]*)"`, "i").exec(attrs);
|
|
11427
|
+
return match?.[1] === void 0 ? void 0 : decodeHtmlAttr(match[1]);
|
|
11428
|
+
}
|
|
11429
|
+
function parseIslandProps(propsAttr, script) {
|
|
11430
|
+
const fromAttr = propsAttr ? tryParseJson(propsAttr) : void 0;
|
|
11431
|
+
if (fromAttr) return unwrapIslandProps(fromAttr);
|
|
11432
|
+
const scriptBody = script.match(/<script type="application\/json">([\s\S]*?)<\/script>/i)?.[1];
|
|
11433
|
+
return scriptBody ? unwrapIslandProps(tryParseJson(scriptBody) ?? {}) : {};
|
|
11434
|
+
}
|
|
11435
|
+
function tryParseJson(value) {
|
|
11436
|
+
try {
|
|
11437
|
+
return JSON.parse(value);
|
|
11438
|
+
} catch {
|
|
11439
|
+
return;
|
|
11440
|
+
}
|
|
11441
|
+
}
|
|
11442
|
+
function unwrapIslandProps(parsed) {
|
|
11443
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
11444
|
+
const record = parsed;
|
|
11445
|
+
const keys = Object.keys(record);
|
|
11446
|
+
if (keys.length > 0 && keys.every((key) => RUST_PAYLOAD_KEYS.has(key)) && record.props && typeof record.props === "object" && !Array.isArray(record.props)) return record.props;
|
|
11447
|
+
return record;
|
|
11448
|
+
}
|
|
11449
|
+
function decodeHtmlAttr(value) {
|
|
11450
|
+
return value.replaceAll(""", "\"").replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&");
|
|
11451
|
+
}
|
|
11452
|
+
//#endregion
|
|
8081
11453
|
//#region src/framework.ts
|
|
8082
11454
|
function createFrameworkMarkdownOptions(options) {
|
|
8083
11455
|
return {
|
|
@@ -8094,6 +11466,7 @@ function createFrameworkMarkdownOptions(options) {
|
|
|
8094
11466
|
lastUpdated: false,
|
|
8095
11467
|
pagination: false,
|
|
8096
11468
|
breadcrumbs: false,
|
|
11469
|
+
jsonLd: false,
|
|
8097
11470
|
readerChrome: false,
|
|
8098
11471
|
localeSwitcher: false,
|
|
8099
11472
|
a11y: false,
|
|
@@ -8104,6 +11477,10 @@ function createFrameworkMarkdownOptions(options) {
|
|
|
8104
11477
|
robots: true,
|
|
8105
11478
|
llms: true
|
|
8106
11479
|
},
|
|
11480
|
+
pwa: {
|
|
11481
|
+
enabled: false,
|
|
11482
|
+
offline: true
|
|
11483
|
+
},
|
|
8107
11484
|
publishState: {
|
|
8108
11485
|
enabled: false,
|
|
8109
11486
|
includeDrafts: false
|
|
@@ -8119,6 +11496,7 @@ function createFrameworkMarkdownOptions(options) {
|
|
|
8119
11496
|
allowExternal: false
|
|
8120
11497
|
},
|
|
8121
11498
|
gfm: options.gfm,
|
|
11499
|
+
mdx: options.mdx,
|
|
8122
11500
|
frontmatter: options.frontmatter ?? false,
|
|
8123
11501
|
toc: options.toc,
|
|
8124
11502
|
tocMaxDepth: options.tocMaxDepth,
|
|
@@ -8191,7 +11569,11 @@ function createFrameworkMarkdownOptions(options) {
|
|
|
8191
11569
|
includes: { enabled: false },
|
|
8192
11570
|
cards: { enabled: false },
|
|
8193
11571
|
steps: { enabled: false },
|
|
8194
|
-
fileTree: {
|
|
11572
|
+
fileTree: {
|
|
11573
|
+
enabled: false,
|
|
11574
|
+
defaultOpen: true,
|
|
11575
|
+
icons: true
|
|
11576
|
+
},
|
|
8195
11577
|
sanitize: { enabled: false },
|
|
8196
11578
|
editThisPage: {
|
|
8197
11579
|
enabled: false,
|
|
@@ -9026,6 +12408,7 @@ function oxContent(options = {}) {
|
|
|
9026
12408
|
createCollectionsPlugin(resolvedOptions, getRoot),
|
|
9027
12409
|
createSearchPlugin(resolvedOptions, getRoot)
|
|
9028
12410
|
];
|
|
12411
|
+
if (resolvedOptions.math.enabled) plugins.push(createKatexAssetsPlugin());
|
|
9029
12412
|
if (resolvedOptions.i18n) plugins.push(createI18nPlugin(resolvedOptions));
|
|
9030
12413
|
if (resolvedOptions.ogViewer) plugins.push(createOgViewerPlugin(resolvedOptions));
|
|
9031
12414
|
return plugins;
|
|
@@ -9173,6 +12556,7 @@ function createSsgPlugin(resolvedOptions, getRoot, ssgDevCache) {
|
|
|
9173
12556
|
for (const error of result.errors) console.warn(`[ox-content] ${error}`);
|
|
9174
12557
|
} catch (err) {
|
|
9175
12558
|
console.error("[ox-content] SSG build failed:", err);
|
|
12559
|
+
if (err instanceof PageResourceError) throw err;
|
|
9176
12560
|
}
|
|
9177
12561
|
}
|
|
9178
12562
|
};
|
|
@@ -9278,9 +12662,12 @@ function resolveOptions(options) {
|
|
|
9278
12662
|
permalinks: resolvePermalinksOptions(options.permalinks),
|
|
9279
12663
|
cascade: resolveCascadeOptions(options.cascade),
|
|
9280
12664
|
redirects: resolveRedirectsOptions(options.redirects),
|
|
12665
|
+
blog: resolveBlogOptions(options.blog ?? (typeof options.ssg === "object" && options.ssg ? options.ssg.blog : void 0)),
|
|
9281
12666
|
feeds: resolveFeedsOptions(options.feeds),
|
|
12667
|
+
pwa: resolvePwaOptions(options.pwa),
|
|
9282
12668
|
taxonomies: resolveTaxonomiesOptions(options.taxonomies),
|
|
9283
12669
|
versions: resolveVersionsOptions(options.versions),
|
|
12670
|
+
resources: resolveResourcesOptions(options.resources),
|
|
9284
12671
|
gfm: options.gfm ?? true,
|
|
9285
12672
|
mdx: options.mdx,
|
|
9286
12673
|
footnotes: options.footnotes ?? true,
|
|
@@ -9306,6 +12693,7 @@ function resolveOptions(options) {
|
|
|
9306
12693
|
cjkEmphasis: options.cjkEmphasis ?? false,
|
|
9307
12694
|
codeBlockLint: resolveCodeBlockLintOptions(options.codeBlockLint),
|
|
9308
12695
|
codeBlockTypecheck: resolveCodeBlockTypecheckOptions(options.codeBlockTypecheck),
|
|
12696
|
+
typedHover: resolveTypedHoverOptions(options.typedHover),
|
|
9309
12697
|
docsTests: resolveDocsTestOptions(options.docsTests),
|
|
9310
12698
|
mermaid: options.mermaid ?? false,
|
|
9311
12699
|
math: resolveMathOptions(options.math),
|
|
@@ -9553,9 +12941,9 @@ function resolveCodeAnnotationsOptions(options) {
|
|
|
9553
12941
|
/**
|
|
9554
12942
|
* Generates virtual module content.
|
|
9555
12943
|
*/
|
|
9556
|
-
function generateVirtualModule(path$
|
|
9557
|
-
if (path$
|
|
9558
|
-
if (path$
|
|
12944
|
+
function generateVirtualModule(path$5, options) {
|
|
12945
|
+
if (path$5 === "config") return `export default ${JSON.stringify(options)};`;
|
|
12946
|
+
if (path$5 === "runtime") {
|
|
9559
12947
|
const base = normalizeRuntimeBase(options.base);
|
|
9560
12948
|
return `
|
|
9561
12949
|
export const base = ${JSON.stringify(base)};
|
|
@@ -9609,6 +12997,8 @@ exports.DocsTestRunError = DocsTestRunError;
|
|
|
9609
12997
|
exports.Fragment = require_jsx_html.Fragment;
|
|
9610
12998
|
exports.IncrementalMarkdownParser = IncrementalMarkdownParser;
|
|
9611
12999
|
exports.IncrementalMarkdownRenderer = IncrementalMarkdownRenderer;
|
|
13000
|
+
exports.PageResourceError = PageResourceError;
|
|
13001
|
+
exports.applyIslandSsrHtml = applyIslandSsrHtml;
|
|
9612
13002
|
exports.buildCollectionManifest = buildCollectionManifest;
|
|
9613
13003
|
exports.buildSearchIndex = buildSearchIndex;
|
|
9614
13004
|
exports.buildSsg = buildSsg;
|
|
@@ -9617,6 +13007,8 @@ exports.clearRenderContext = clearRenderContext;
|
|
|
9617
13007
|
exports.collectDocsTests = collectDocsTests;
|
|
9618
13008
|
exports.collectGitHubRepos = collectGitHubRepos;
|
|
9619
13009
|
exports.collectGitHubSources = collectGitHubSources;
|
|
13010
|
+
exports.collectMdxIslandNamesFromHtml = collectMdxIslandNamesFromHtml;
|
|
13011
|
+
exports.collectMdxJsxNamesFromAst = collectMdxJsxNamesFromAst;
|
|
9620
13012
|
exports.collectOgpUrls = collectOgpUrls;
|
|
9621
13013
|
exports.convertVitePressNav = require_vitepress.convertVitePressNav;
|
|
9622
13014
|
exports.convertVitePressSidebar = require_vitepress.convertVitePressSidebar;
|
|
@@ -9630,6 +13022,8 @@ exports.defaultTheme = require_vitepress.defaultTheme;
|
|
|
9630
13022
|
exports.defineCollection = defineCollection;
|
|
9631
13023
|
exports.defineCollections = defineCollections;
|
|
9632
13024
|
exports.defineTheme = require_vitepress.defineTheme;
|
|
13025
|
+
exports.discoverDocumentMdxIslands = discoverDocumentMdxIslands;
|
|
13026
|
+
exports.discoverRegisteredMdxComponents = discoverRegisteredMdxComponents;
|
|
9633
13027
|
exports.each = require_jsx_html.each;
|
|
9634
13028
|
exports.escapeSvelteMarkup = escapeSvelteMarkup;
|
|
9635
13029
|
exports.extractCodeBlocks = extractCodeBlocks;
|
|
@@ -9652,7 +13046,11 @@ exports.generateVirtualModule = generateVirtualModule;
|
|
|
9652
13046
|
exports.generateVitePressMigrationConfig = require_vitepress.generateVitePressMigrationConfig;
|
|
9653
13047
|
exports.hasIslands = hasIslands;
|
|
9654
13048
|
exports.inferType = inferType;
|
|
13049
|
+
exports.intersectHydratableComponentNames = intersectHydratableComponentNames;
|
|
13050
|
+
exports.intersectRegisteredComponentNames = intersectRegisteredComponentNames;
|
|
9655
13051
|
exports.isMarkdownFilePath = isMarkdownFilePath;
|
|
13052
|
+
exports.isMdxFilePath = isMdxFilePath;
|
|
13053
|
+
exports.isRegisteredComponent = isRegisteredComponent;
|
|
9656
13054
|
exports.jsx = require_jsx_html.jsx;
|
|
9657
13055
|
exports.jsxs = require_jsx_html.jsxs;
|
|
9658
13056
|
exports.lintCodeBlocks = lintCodeBlocks;
|
|
@@ -9673,6 +13071,7 @@ exports.prefetchGitHubRepos = prefetchGitHubRepos;
|
|
|
9673
13071
|
exports.prefetchGitHubSources = prefetchGitHubSources;
|
|
9674
13072
|
exports.prefetchOgpData = prefetchOgpData;
|
|
9675
13073
|
exports.raw = require_jsx_html.raw;
|
|
13074
|
+
exports.readingTimeMinutes = readingTimeMinutes;
|
|
9676
13075
|
exports.renderAllPages = renderAllPages;
|
|
9677
13076
|
exports.renderHtmlToFrameworkCode = renderHtmlToFrameworkCode;
|
|
9678
13077
|
exports.renderHtmlToReactComponent = renderHtmlToReactComponent;
|
|
@@ -9680,15 +13079,20 @@ exports.renderHtmlToReactCreateElement = renderHtmlToReactCreateElement;
|
|
|
9680
13079
|
exports.renderHtmlToSvelteComponent = renderHtmlToSvelteComponent;
|
|
9681
13080
|
exports.renderHtmlToVueComponent = renderHtmlToVueComponent;
|
|
9682
13081
|
exports.renderHtmlToVueH = renderHtmlToVueH;
|
|
13082
|
+
exports.renderIslandComponentImports = renderIslandComponentImports;
|
|
9683
13083
|
exports.renderMarkdownStream = renderMarkdownStream;
|
|
9684
13084
|
exports.renderPage = renderPage;
|
|
9685
13085
|
exports.renderToString = require_jsx_html.renderToString;
|
|
9686
13086
|
exports.resolveBadgeOptions = resolveBadgeOptions;
|
|
13087
|
+
exports.resolveBlogCollectionName = resolveBlogCollectionName;
|
|
13088
|
+
exports.resolveBlogOptions = resolveBlogOptions;
|
|
9687
13089
|
exports.resolveBuiltinEmbedOptions = resolveBuiltinEmbedOptions;
|
|
9688
13090
|
exports.resolveCardOptions = resolveCardOptions;
|
|
9689
13091
|
exports.resolveCascadeOptions = resolveCascadeOptions;
|
|
9690
13092
|
exports.resolveCollectionsOptions = resolveCollectionsOptions;
|
|
13093
|
+
exports.resolveContentRootPath = resolveContentRootPath;
|
|
9691
13094
|
exports.resolveDocsOptions = resolveDocsOptions;
|
|
13095
|
+
exports.resolveDocumentComponentImports = resolveDocumentComponentImports;
|
|
9692
13096
|
exports.resolveFeedsOptions = resolveFeedsOptions;
|
|
9693
13097
|
exports.resolveFileTreeOptions = resolveFileTreeOptions;
|
|
9694
13098
|
exports.resolveHeaderNavItems = require_vitepress.resolveHeaderNavItems;
|
|
@@ -9697,24 +13101,30 @@ exports.resolveImageOptions = resolveImageOptions;
|
|
|
9697
13101
|
exports.resolveIncludeOptions = resolveIncludeOptions;
|
|
9698
13102
|
exports.resolveLocaleLabel = require_vitepress.resolveLocaleLabel;
|
|
9699
13103
|
exports.resolveMathOptions = resolveMathOptions;
|
|
13104
|
+
exports.resolveMdxForFilePath = resolveMdxForFilePath;
|
|
9700
13105
|
exports.resolveNotFoundOptions = resolveNotFoundOptions;
|
|
9701
13106
|
exports.resolveOgImageOptions = resolveOgImageOptions;
|
|
9702
13107
|
exports.resolvePageChromeOption = require_vitepress.resolvePageChromeOption;
|
|
9703
13108
|
exports.resolvePermalinksOptions = resolvePermalinksOptions;
|
|
9704
13109
|
exports.resolvePublishStateOptions = resolvePublishStateOptions;
|
|
13110
|
+
exports.resolvePwaOptions = resolvePwaOptions;
|
|
9705
13111
|
exports.resolveRedirectsOptions = resolveRedirectsOptions;
|
|
13112
|
+
exports.resolveResourcesOptions = resolveResourcesOptions;
|
|
9706
13113
|
exports.resolveSearchOptions = resolveSearchOptions;
|
|
13114
|
+
exports.resolveSectionIndexOptions = resolveSectionIndexOptions;
|
|
9707
13115
|
exports.resolveSiteMapsOptions = resolveSiteMapsOptions;
|
|
9708
13116
|
exports.resolveSsgOptions = resolveSsgOptions;
|
|
9709
13117
|
exports.resolveStepsOptions = resolveStepsOptions;
|
|
9710
13118
|
exports.resolveTaxonomiesOptions = resolveTaxonomiesOptions;
|
|
9711
13119
|
exports.resolveTeamOptions = resolveTeamOptions;
|
|
9712
13120
|
exports.resolveTheme = require_vitepress.resolveTheme;
|
|
13121
|
+
exports.resolveTypedHoverOptions = resolveTypedHoverOptions;
|
|
9713
13122
|
exports.resolveVersionsOptions = resolveVersionsOptions;
|
|
9714
13123
|
exports.runDocsTests = runDocsTests;
|
|
9715
13124
|
exports.setRenderContext = setRenderContext;
|
|
9716
13125
|
exports.shouldLintMarkdownFile = shouldLintMarkdownFile;
|
|
9717
13126
|
exports.stripMarkdownExtension = stripMarkdownExtension;
|
|
13127
|
+
exports.stripViteQuery = stripViteQuery;
|
|
9718
13128
|
exports.transformAllPlugins = transformAllPlugins;
|
|
9719
13129
|
exports.transformGitHub = transformGitHub;
|
|
9720
13130
|
exports.transformIslands = transformIslands;
|