@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.mjs
CHANGED
|
@@ -7,18 +7,20 @@ import * as path$2 from "path";
|
|
|
7
7
|
import { unified } from "unified";
|
|
8
8
|
import rehypeParsePlugin from "rehype-parse";
|
|
9
9
|
import rehypeStringifyPlugin from "rehype-stringify";
|
|
10
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
10
|
+
import fs, { createReadStream, existsSync, readFileSync } from "node:fs";
|
|
11
11
|
import * as path$1 from "node:path";
|
|
12
|
-
import path, { dirname, join } from "node:path";
|
|
13
|
-
import * as fs$
|
|
14
|
-
import { access, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
12
|
+
import path, { dirname, extname, join, relative, resolve, sep } from "node:path";
|
|
13
|
+
import * as fs$2 from "node:fs/promises";
|
|
14
|
+
import { access, copyFile, cp, mkdir, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
15
15
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
16
16
|
import { tmpdir } from "node:os";
|
|
17
17
|
import { promisify } from "node:util";
|
|
18
18
|
import { execFile, spawn } from "node:child_process";
|
|
19
|
-
import * as fs$
|
|
19
|
+
import * as fs$3 from "fs/promises";
|
|
20
20
|
import * as crypto from "crypto";
|
|
21
|
-
import
|
|
21
|
+
import { createHash } from "node:crypto";
|
|
22
|
+
import { deflateSync, inflateSync } from "node:zlib";
|
|
23
|
+
import * as fs$1 from "fs";
|
|
22
24
|
import { glob } from "glob";
|
|
23
25
|
//#region src/markdown.ts
|
|
24
26
|
const DEFAULT_MARKDOWN_EXTENSIONS = [
|
|
@@ -245,6 +247,15 @@ async function highlightCode(html) {
|
|
|
245
247
|
const result = await unified().use(rehypeParse$3, { fragment: true }).use(rehypeNativeHighlight).use(rehypeStringify$3).process(html);
|
|
246
248
|
return String(result);
|
|
247
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* Highlight every code block in a rendered page, preserving original classes
|
|
252
|
+
* and per-line metadata when the native document pass cannot read the markup.
|
|
253
|
+
*/
|
|
254
|
+
async function highlightPageHtml(html, mergeHighlightedCodeBlocks) {
|
|
255
|
+
const native = await highlightDocumentNatively(html);
|
|
256
|
+
if (native && native.skipped.length === 0) return native.html;
|
|
257
|
+
return mergeHighlightedCodeBlocks(html, await highlightCode(html));
|
|
258
|
+
}
|
|
248
259
|
//#endregion
|
|
249
260
|
//#region src/plugins/mermaid.ts
|
|
250
261
|
/**
|
|
@@ -336,6 +347,69 @@ function warnMissingMmdcOnce() {
|
|
|
336
347
|
*/
|
|
337
348
|
const mermaidClientScript = "";
|
|
338
349
|
//#endregion
|
|
350
|
+
//#region src/plugins/math.ts
|
|
351
|
+
/**
|
|
352
|
+
* Build-time KaTeX rendering for opt-in `$…$` / `$$…$$` math.
|
|
353
|
+
*
|
|
354
|
+
* KaTeX is an optional peer. Sites that never enable `math` do not install it,
|
|
355
|
+
* and the published plugin does not bundle or depend on it.
|
|
356
|
+
*/
|
|
357
|
+
const KATEX_ASSET_DIR = "__ox_katex__";
|
|
358
|
+
const MATH_TAG = /<(span|div) class="ox-math ox-math-(inline|block)" data-ox-tex="([^"]*)">[\s\S]*?<\/\1>/g;
|
|
359
|
+
let missingWarned = false;
|
|
360
|
+
/**
|
|
361
|
+
* Replaces rust `ox-math` placeholders with static KaTeX HTML.
|
|
362
|
+
* Leaves the escaped TeX fallback when `katex` is not installed.
|
|
363
|
+
*/
|
|
364
|
+
async function renderKatexMath(html) {
|
|
365
|
+
if (!html.includes("data-ox-tex")) return html;
|
|
366
|
+
const katex = loadKatex();
|
|
367
|
+
if (!katex) {
|
|
368
|
+
warnMissingKatexOnce();
|
|
369
|
+
return html;
|
|
370
|
+
}
|
|
371
|
+
return html.replace(MATH_TAG, (_match, tag, kind, encoded) => {
|
|
372
|
+
return `<${tag} class="ox-math ox-math-${kind}">${katex.renderToString(decodeHtmlAttr$2(encoded), {
|
|
373
|
+
displayMode: kind === "block",
|
|
374
|
+
throwOnError: false,
|
|
375
|
+
trust: false,
|
|
376
|
+
output: "htmlAndMathml"
|
|
377
|
+
})}</${tag}>`;
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
/** Directory that contains `katex.min.css` and `fonts/`, or `null`. */
|
|
381
|
+
function resolveKatexDist() {
|
|
382
|
+
for (const resolver of createKatexResolvers()) try {
|
|
383
|
+
return join(dirname(resolver.resolve("katex/package.json")), "dist");
|
|
384
|
+
} catch {}
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
function loadKatex() {
|
|
388
|
+
for (const resolver of createKatexResolvers()) try {
|
|
389
|
+
const loaded = resolver(resolver.resolve("katex"));
|
|
390
|
+
if (typeof loaded.renderToString === "function") return loaded;
|
|
391
|
+
if (loaded.default && typeof loaded.default.renderToString === "function") return loaded.default;
|
|
392
|
+
} catch {}
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
function createKatexResolvers() {
|
|
396
|
+
const consumerRequire = createRequire(join(process.cwd(), "noop.js"));
|
|
397
|
+
const resolvers = [consumerRequire];
|
|
398
|
+
try {
|
|
399
|
+
resolvers.push(createRequire(consumerRequire.resolve("@ox-content/vite-plugin")));
|
|
400
|
+
} catch {}
|
|
401
|
+
resolvers.push(createRequire(import.meta.url));
|
|
402
|
+
return resolvers;
|
|
403
|
+
}
|
|
404
|
+
function decodeHtmlAttr$2(value) {
|
|
405
|
+
return value.replaceAll(""", "\"").replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&");
|
|
406
|
+
}
|
|
407
|
+
function warnMissingKatexOnce() {
|
|
408
|
+
if (missingWarned) return;
|
|
409
|
+
missingWarned = true;
|
|
410
|
+
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.");
|
|
411
|
+
}
|
|
412
|
+
//#endregion
|
|
339
413
|
//#region src/plugins/pm.ts
|
|
340
414
|
/**
|
|
341
415
|
* Package Manager Tabs Plugin
|
|
@@ -553,15 +627,15 @@ function assetRecord(src, media) {
|
|
|
553
627
|
//#region src/plugins/twitter/render.ts
|
|
554
628
|
function renderFetchedTweet(permalink, data, assets, options) {
|
|
555
629
|
const profile = `https://x.com/${encodeURIComponent(data.user.screen_name)}`;
|
|
556
|
-
const author = escapeHtml$
|
|
557
|
-
const handle = escapeHtml$
|
|
558
|
-
const avatar = assets.avatar ? `<img class="ox-tweet__avatar" src="${escapeAttribute(assets.avatar)}" alt="" width="48" height="48" loading="lazy" decoding="async">` : "";
|
|
630
|
+
const author = escapeHtml$6(data.user.name);
|
|
631
|
+
const handle = escapeHtml$6(data.user.screen_name);
|
|
632
|
+
const avatar = assets.avatar ? `<img class="ox-tweet__avatar" src="${escapeAttribute$2(assets.avatar)}" alt="" width="48" height="48" loading="lazy" decoding="async">` : "";
|
|
559
633
|
const media = renderMedia(assets);
|
|
560
634
|
const footer = renderFooter(permalink, data.created_at, options.lang);
|
|
561
635
|
return [
|
|
562
636
|
"<figure class=\"ox-tweet ox-tweet--fetched\">",
|
|
563
637
|
"<header class=\"ox-tweet__header\">",
|
|
564
|
-
`<a class="ox-tweet__profile" href="${escapeAttribute(profile)}" target="_blank" rel="noopener noreferrer">`,
|
|
638
|
+
`<a class="ox-tweet__profile" href="${escapeAttribute$2(profile)}" target="_blank" rel="noopener noreferrer">`,
|
|
565
639
|
avatar,
|
|
566
640
|
`<span class="ox-tweet__author-name">${author}</span>`,
|
|
567
641
|
`<span class="ox-tweet__author-handle">@${handle}</span>`,
|
|
@@ -584,7 +658,7 @@ function renderTweetText(data) {
|
|
|
584
658
|
if (entity.kind === "url") {
|
|
585
659
|
const href = entity.expanded_url ?? entity.url;
|
|
586
660
|
const label = entity.display_url ?? href;
|
|
587
|
-
output += `<a href="${escapeAttribute(href)}" target="_blank" rel="noopener noreferrer">${escapeHtml$
|
|
661
|
+
output += `<a href="${escapeAttribute$2(href)}" target="_blank" rel="noopener noreferrer">${escapeHtml$6(label)}</a>`;
|
|
588
662
|
}
|
|
589
663
|
cursor = entityEnd;
|
|
590
664
|
}
|
|
@@ -607,12 +681,12 @@ function renderMedia(assets) {
|
|
|
607
681
|
if (assets.media.length === 0) return "";
|
|
608
682
|
const images = assets.media.map((item) => {
|
|
609
683
|
const size = [item.width ? ` width="${item.width}"` : "", item.height ? ` height="${item.height}"` : ""].join("");
|
|
610
|
-
return `<img class="ox-tweet__media-item" src="${escapeAttribute(item.src)}" alt="${escapeAttribute(item.alt ?? "")}"${size} loading="lazy" decoding="async">`;
|
|
684
|
+
return `<img class="ox-tweet__media-item" src="${escapeAttribute$2(item.src)}" alt="${escapeAttribute$2(item.alt ?? "")}"${size} loading="lazy" decoding="async">`;
|
|
611
685
|
}).join("");
|
|
612
686
|
return `<div class="ox-tweet__media" data-count="${assets.media.length}">${images}</div>`;
|
|
613
687
|
}
|
|
614
688
|
function renderFooter(permalink, createdAt, lang) {
|
|
615
|
-
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>`;
|
|
689
|
+
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>`;
|
|
616
690
|
const date = new Date(createdAt);
|
|
617
691
|
if (Number.isNaN(date.valueOf())) return renderFooter(permalink, void 0, lang);
|
|
618
692
|
const iso = date.toISOString();
|
|
@@ -628,15 +702,15 @@ function renderFooter(permalink, createdAt, lang) {
|
|
|
628
702
|
timeZone: "UTC"
|
|
629
703
|
}).format(date);
|
|
630
704
|
}
|
|
631
|
-
return `<footer class="ox-tweet__footer"><a class="ox-tweet__permalink" href="${escapeAttribute(permalink)}" target="_blank" rel="noopener noreferrer"><time datetime="${iso}">${escapeHtml$
|
|
705
|
+
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>`;
|
|
632
706
|
}
|
|
633
707
|
function escapeText(value) {
|
|
634
|
-
return escapeHtml$
|
|
708
|
+
return escapeHtml$6(value).replaceAll("\n", "<br>");
|
|
635
709
|
}
|
|
636
|
-
function escapeAttribute(value) {
|
|
637
|
-
return escapeHtml$
|
|
710
|
+
function escapeAttribute$2(value) {
|
|
711
|
+
return escapeHtml$6(value).replaceAll("`", "`");
|
|
638
712
|
}
|
|
639
|
-
function escapeHtml$
|
|
713
|
+
function escapeHtml$6(value) {
|
|
640
714
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
641
715
|
}
|
|
642
716
|
//#endregion
|
|
@@ -757,6 +831,11 @@ function sourceKey(source) {
|
|
|
757
831
|
function formatLineRange(lines) {
|
|
758
832
|
return lines.start === lines.end ? `L${lines.start}` : `L${lines.start}-L${lines.end}`;
|
|
759
833
|
}
|
|
834
|
+
function summarizeCommitMessage(message) {
|
|
835
|
+
const firstLine = message.split(/\r?\n/, 1)[0]?.replace(/\s+/g, " ").trim() ?? "";
|
|
836
|
+
if (firstLine.length <= 120) return firstLine;
|
|
837
|
+
return `${firstLine.slice(0, 119)}…`;
|
|
838
|
+
}
|
|
760
839
|
function parseGitHubLineRange(value) {
|
|
761
840
|
if (!value) return void 0;
|
|
762
841
|
const match = value.trim().match(/^#?L?(\d+)(?:-L?(\d+))?$/i);
|
|
@@ -831,6 +910,24 @@ function githubHeaders(options) {
|
|
|
831
910
|
if (options.token) headers.Authorization = `Bearer ${options.token}`;
|
|
832
911
|
return headers;
|
|
833
912
|
}
|
|
913
|
+
async function fetchSourceCommit(source, options) {
|
|
914
|
+
try {
|
|
915
|
+
const apiUrl = `https://api.github.com/repos/${source.repo}/commits?path=${encodeURIComponent(source.path)}&sha=${encodeURIComponent(source.ref)}&per_page=1`;
|
|
916
|
+
const response = await fetch(apiUrl, { headers: githubHeaders(options) });
|
|
917
|
+
if (!response.ok) return;
|
|
918
|
+
const item = (await response.json())[0];
|
|
919
|
+
const sha = item?.sha;
|
|
920
|
+
const message = item?.commit?.message ? summarizeCommitMessage(item.commit.message) : "";
|
|
921
|
+
if (!sha || !message) return;
|
|
922
|
+
return {
|
|
923
|
+
sha,
|
|
924
|
+
message,
|
|
925
|
+
html_url: item.html_url ?? `https://github.com/${source.repo}/commit/${sha}`
|
|
926
|
+
};
|
|
927
|
+
} catch {
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
834
931
|
/**
|
|
835
932
|
* Fetch repository data from GitHub API.
|
|
836
933
|
*/
|
|
@@ -869,7 +966,7 @@ async function fetchGitHubSource(source, options) {
|
|
|
869
966
|
}
|
|
870
967
|
try {
|
|
871
968
|
const apiUrl = `https://api.github.com/repos/${source.repo}/contents/${encodePath(source.path)}?ref=${encodeURIComponent(source.ref)}`;
|
|
872
|
-
const response = await fetch(apiUrl, { headers: githubHeaders(options) });
|
|
969
|
+
const [response, commit] = await Promise.all([fetch(apiUrl, { headers: githubHeaders(options) }), fetchSourceCommit(source, options)]);
|
|
873
970
|
if (!response.ok) {
|
|
874
971
|
console.warn(`Failed to fetch GitHub source ${source.permalink}: ${response.status}`);
|
|
875
972
|
return null;
|
|
@@ -886,7 +983,8 @@ async function fetchGitHubSource(source, options) {
|
|
|
886
983
|
content,
|
|
887
984
|
size: data.size ?? Buffer$1.byteLength(content),
|
|
888
985
|
html_url: data.html_url ?? source.permalink,
|
|
889
|
-
language: inferLanguage(source.path)
|
|
986
|
+
language: inferLanguage(source.path),
|
|
987
|
+
...commit ? { commit } : {}
|
|
890
988
|
};
|
|
891
989
|
if (options.cache) sourceCache.set(key, {
|
|
892
990
|
data: sourceData,
|
|
@@ -1180,19 +1278,76 @@ function normalizeSourceLines(content) {
|
|
|
1180
1278
|
if (lines.length > 1 && lines.at(-1) === "") lines.pop();
|
|
1181
1279
|
return lines.length > 0 ? lines : [""];
|
|
1182
1280
|
}
|
|
1281
|
+
function text(value) {
|
|
1282
|
+
return {
|
|
1283
|
+
type: "text",
|
|
1284
|
+
value
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
function createSourceLines(lines, start) {
|
|
1288
|
+
return lines.flatMap((line, index) => {
|
|
1289
|
+
const lineNumber = start + index;
|
|
1290
|
+
const span = {
|
|
1291
|
+
type: "element",
|
|
1292
|
+
tagName: "span",
|
|
1293
|
+
properties: {
|
|
1294
|
+
className: ["line"],
|
|
1295
|
+
"data-line": String(lineNumber),
|
|
1296
|
+
"data-line-number": String(lineNumber)
|
|
1297
|
+
},
|
|
1298
|
+
children: [text(line)]
|
|
1299
|
+
};
|
|
1300
|
+
return index === 0 ? [span] : [text("\n"), span];
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
function createCommitMeta(commit) {
|
|
1304
|
+
return {
|
|
1305
|
+
type: "element",
|
|
1306
|
+
tagName: "a",
|
|
1307
|
+
properties: {
|
|
1308
|
+
className: ["ox-github-code-commit"],
|
|
1309
|
+
href: commit.html_url,
|
|
1310
|
+
target: "_blank",
|
|
1311
|
+
rel: "noopener noreferrer",
|
|
1312
|
+
title: commit.message
|
|
1313
|
+
},
|
|
1314
|
+
children: [{
|
|
1315
|
+
type: "element",
|
|
1316
|
+
tagName: "span",
|
|
1317
|
+
properties: { className: ["ox-github-code-sha"] },
|
|
1318
|
+
children: [text(commit.sha.slice(0, 7))]
|
|
1319
|
+
}, {
|
|
1320
|
+
type: "element",
|
|
1321
|
+
tagName: "span",
|
|
1322
|
+
properties: { className: ["ox-github-code-commit-message"] },
|
|
1323
|
+
children: [text(commit.message)]
|
|
1324
|
+
}]
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1183
1327
|
function createGitHubSourceCard(source, lines, options) {
|
|
1184
1328
|
const allLines = normalizeSourceLines(source.content);
|
|
1185
1329
|
const start = Math.min(lines?.start ?? 1, allLines.length);
|
|
1186
1330
|
const end = lines ? Math.min(lines.end, allLines.length) : Math.min(allLines.length, options.maxSourceLines);
|
|
1187
1331
|
const selectedLines = allLines.slice(start - 1, end);
|
|
1188
|
-
const
|
|
1332
|
+
const loc = selectedLines.length;
|
|
1333
|
+
const rangeLabel = formatLineRange({
|
|
1189
1334
|
start,
|
|
1190
1335
|
end
|
|
1191
|
-
};
|
|
1192
|
-
const
|
|
1193
|
-
const rangeLabel = formatLineRange(lineRange);
|
|
1194
|
-
const locLabel = !lines && end < allLines.length ? `${rangeLabel} of ${allLines.length} LOC` : `${rangeLabel} - ${loc} LOC`;
|
|
1336
|
+
});
|
|
1337
|
+
const locLabel = !lines && end < allLines.length ? `${rangeLabel} of ${allLines.length} LOC` : `${rangeLabel} · ${loc} LOC`;
|
|
1195
1338
|
const languageClass = source.language ? [`language-${source.language}`] : [];
|
|
1339
|
+
const heading = [{
|
|
1340
|
+
type: "element",
|
|
1341
|
+
tagName: "a",
|
|
1342
|
+
properties: {
|
|
1343
|
+
className: ["ox-github-code-title"],
|
|
1344
|
+
href: source.permalink,
|
|
1345
|
+
target: "_blank",
|
|
1346
|
+
rel: "noopener noreferrer"
|
|
1347
|
+
},
|
|
1348
|
+
children: [text(`${source.repo}/${source.path}`)]
|
|
1349
|
+
}];
|
|
1350
|
+
if (source.commit) heading.push(createCommitMeta(source.commit));
|
|
1196
1351
|
return {
|
|
1197
1352
|
type: "element",
|
|
1198
1353
|
tagName: "figure",
|
|
@@ -1207,65 +1362,34 @@ function createGitHubSourceCard(source, lines, options) {
|
|
|
1207
1362
|
properties: { className: ["ox-github-code-header"] },
|
|
1208
1363
|
children: [{
|
|
1209
1364
|
type: "element",
|
|
1210
|
-
tagName: "
|
|
1211
|
-
properties: {
|
|
1212
|
-
|
|
1213
|
-
href: source.permalink,
|
|
1214
|
-
target: "_blank",
|
|
1215
|
-
rel: "noopener noreferrer"
|
|
1216
|
-
},
|
|
1217
|
-
children: [{
|
|
1218
|
-
type: "text",
|
|
1219
|
-
value: `${source.repo}/${source.path}`
|
|
1220
|
-
}]
|
|
1365
|
+
tagName: "div",
|
|
1366
|
+
properties: { className: ["ox-github-code-heading"] },
|
|
1367
|
+
children: heading
|
|
1221
1368
|
}, {
|
|
1222
1369
|
type: "element",
|
|
1223
1370
|
tagName: "span",
|
|
1224
1371
|
properties: { className: ["ox-github-code-loc"] },
|
|
1225
|
-
children: [
|
|
1226
|
-
type: "text",
|
|
1227
|
-
value: locLabel
|
|
1228
|
-
}]
|
|
1372
|
+
children: [text(locLabel)]
|
|
1229
1373
|
}]
|
|
1230
1374
|
}, {
|
|
1231
1375
|
type: "element",
|
|
1232
1376
|
tagName: "pre",
|
|
1233
1377
|
properties: {
|
|
1234
|
-
className: [
|
|
1378
|
+
className: [
|
|
1379
|
+
"ox-github-code-block",
|
|
1380
|
+
"ox-code-block",
|
|
1381
|
+
"line-numbers-mode",
|
|
1382
|
+
...languageClass
|
|
1383
|
+
],
|
|
1384
|
+
"data-line-numbers": "true",
|
|
1385
|
+
"data-line-number-start": String(start),
|
|
1235
1386
|
...source.language ? { "data-language": source.language } : {}
|
|
1236
1387
|
},
|
|
1237
1388
|
children: [{
|
|
1238
1389
|
type: "element",
|
|
1239
1390
|
tagName: "code",
|
|
1240
1391
|
properties: { className: languageClass },
|
|
1241
|
-
children: selectedLines
|
|
1242
|
-
const lineNumber = start + index;
|
|
1243
|
-
return {
|
|
1244
|
-
type: "element",
|
|
1245
|
-
tagName: "span",
|
|
1246
|
-
properties: {
|
|
1247
|
-
className: ["line", "ox-github-code-line"],
|
|
1248
|
-
"data-line": String(lineNumber)
|
|
1249
|
-
},
|
|
1250
|
-
children: [{
|
|
1251
|
-
type: "element",
|
|
1252
|
-
tagName: "span",
|
|
1253
|
-
properties: { className: ["ox-github-code-line-number"] },
|
|
1254
|
-
children: [{
|
|
1255
|
-
type: "text",
|
|
1256
|
-
value: String(lineNumber)
|
|
1257
|
-
}]
|
|
1258
|
-
}, {
|
|
1259
|
-
type: "element",
|
|
1260
|
-
tagName: "span",
|
|
1261
|
-
properties: { className: ["ox-github-code-line-content"] },
|
|
1262
|
-
children: [{
|
|
1263
|
-
type: "text",
|
|
1264
|
-
value: line || " "
|
|
1265
|
-
}]
|
|
1266
|
-
}]
|
|
1267
|
-
};
|
|
1268
|
-
})
|
|
1392
|
+
children: createSourceLines(selectedLines, start)
|
|
1269
1393
|
}]
|
|
1270
1394
|
}]
|
|
1271
1395
|
};
|
|
@@ -1864,6 +1988,368 @@ function normalizeDiagnostic(diagnostic) {
|
|
|
1864
1988
|
};
|
|
1865
1989
|
}
|
|
1866
1990
|
//#endregion
|
|
1991
|
+
//#region src/typed-hover-generate.ts
|
|
1992
|
+
async function loadTsgoApi() {
|
|
1993
|
+
try {
|
|
1994
|
+
return await import("./api.mjs");
|
|
1995
|
+
} catch {
|
|
1996
|
+
return;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
async function generateTypedHoverAttachments(fences, tsgoCommand) {
|
|
2000
|
+
if (fences.length === 0) return [];
|
|
2001
|
+
const apiMod = await loadTsgoApi();
|
|
2002
|
+
if (!apiMod) return fences.map((fence) => ({
|
|
2003
|
+
code: fence.code,
|
|
2004
|
+
hovers: []
|
|
2005
|
+
}));
|
|
2006
|
+
const temp = await mkdtemp(join(tmpdir(), "ox-content-typed-hover-"));
|
|
2007
|
+
const api = new apiMod.API({
|
|
2008
|
+
cwd: temp,
|
|
2009
|
+
...tsgoCommand ? { tsserverPath: tsgoCommand } : {}
|
|
2010
|
+
});
|
|
2011
|
+
try {
|
|
2012
|
+
const files = await Promise.all(fences.map(async (fence, index) => {
|
|
2013
|
+
const extension = fence.language.toLowerCase() === "tsx" ? "tsx" : "ts";
|
|
2014
|
+
const file = join(temp, `snippet-${index}.${extension}`);
|
|
2015
|
+
await writeFile(file, fence.code);
|
|
2016
|
+
return {
|
|
2017
|
+
fence,
|
|
2018
|
+
file
|
|
2019
|
+
};
|
|
2020
|
+
}));
|
|
2021
|
+
const snapshot = api.updateSnapshot({ openFiles: files.map((item) => item.file) });
|
|
2022
|
+
return files.map(({ fence, file }) => {
|
|
2023
|
+
const project = snapshot.getDefaultProjectForFile(file);
|
|
2024
|
+
if (!project) return {
|
|
2025
|
+
code: fence.code,
|
|
2026
|
+
hovers: []
|
|
2027
|
+
};
|
|
2028
|
+
const hovers = [];
|
|
2029
|
+
for (const ident of collectIdentifierRanges(fence.code)) {
|
|
2030
|
+
const type = project.checker.getTypeAtPosition(file, ident.start);
|
|
2031
|
+
if (!type || type.isErrorType?.()) continue;
|
|
2032
|
+
const widened = project.checker.getBaseTypeOfLiteralType(type) ?? type;
|
|
2033
|
+
const text = project.checker.typeToString(widened);
|
|
2034
|
+
if (text) hovers.push({
|
|
2035
|
+
start: ident.start,
|
|
2036
|
+
end: ident.end,
|
|
2037
|
+
type: text
|
|
2038
|
+
});
|
|
2039
|
+
}
|
|
2040
|
+
return {
|
|
2041
|
+
code: fence.code,
|
|
2042
|
+
hovers
|
|
2043
|
+
};
|
|
2044
|
+
});
|
|
2045
|
+
} finally {
|
|
2046
|
+
api.close();
|
|
2047
|
+
await rm(temp, {
|
|
2048
|
+
recursive: true,
|
|
2049
|
+
force: true
|
|
2050
|
+
});
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
const IDENTIFIER_KEYWORDS = /* @__PURE__ */ new Set([
|
|
2054
|
+
"abstract",
|
|
2055
|
+
"any",
|
|
2056
|
+
"as",
|
|
2057
|
+
"asserts",
|
|
2058
|
+
"async",
|
|
2059
|
+
"await",
|
|
2060
|
+
"bigint",
|
|
2061
|
+
"boolean",
|
|
2062
|
+
"break",
|
|
2063
|
+
"case",
|
|
2064
|
+
"catch",
|
|
2065
|
+
"class",
|
|
2066
|
+
"const",
|
|
2067
|
+
"continue",
|
|
2068
|
+
"debugger",
|
|
2069
|
+
"declare",
|
|
2070
|
+
"default",
|
|
2071
|
+
"delete",
|
|
2072
|
+
"do",
|
|
2073
|
+
"else",
|
|
2074
|
+
"enum",
|
|
2075
|
+
"export",
|
|
2076
|
+
"extends",
|
|
2077
|
+
"false",
|
|
2078
|
+
"finally",
|
|
2079
|
+
"for",
|
|
2080
|
+
"from",
|
|
2081
|
+
"function",
|
|
2082
|
+
"if",
|
|
2083
|
+
"implements",
|
|
2084
|
+
"import",
|
|
2085
|
+
"in",
|
|
2086
|
+
"infer",
|
|
2087
|
+
"instanceof",
|
|
2088
|
+
"interface",
|
|
2089
|
+
"is",
|
|
2090
|
+
"keyof",
|
|
2091
|
+
"let",
|
|
2092
|
+
"never",
|
|
2093
|
+
"new",
|
|
2094
|
+
"null",
|
|
2095
|
+
"number",
|
|
2096
|
+
"object",
|
|
2097
|
+
"of",
|
|
2098
|
+
"package",
|
|
2099
|
+
"private",
|
|
2100
|
+
"protected",
|
|
2101
|
+
"public",
|
|
2102
|
+
"readonly",
|
|
2103
|
+
"return",
|
|
2104
|
+
"satisfies",
|
|
2105
|
+
"static",
|
|
2106
|
+
"string",
|
|
2107
|
+
"super",
|
|
2108
|
+
"switch",
|
|
2109
|
+
"symbol",
|
|
2110
|
+
"this",
|
|
2111
|
+
"throw",
|
|
2112
|
+
"true",
|
|
2113
|
+
"try",
|
|
2114
|
+
"type",
|
|
2115
|
+
"typeof",
|
|
2116
|
+
"undefined",
|
|
2117
|
+
"unique",
|
|
2118
|
+
"unknown",
|
|
2119
|
+
"using",
|
|
2120
|
+
"var",
|
|
2121
|
+
"void",
|
|
2122
|
+
"while",
|
|
2123
|
+
"with",
|
|
2124
|
+
"yield"
|
|
2125
|
+
]);
|
|
2126
|
+
function collectIdentifierRanges(code) {
|
|
2127
|
+
const ranges = [];
|
|
2128
|
+
let index = 0;
|
|
2129
|
+
while (index < code.length) {
|
|
2130
|
+
const char = code[index];
|
|
2131
|
+
if (char === "/" && code[index + 1] === "/") {
|
|
2132
|
+
index = code.indexOf("\n", index);
|
|
2133
|
+
if (index === -1) break;
|
|
2134
|
+
continue;
|
|
2135
|
+
}
|
|
2136
|
+
if (char === "/" && code[index + 1] === "*") {
|
|
2137
|
+
const close = code.indexOf("*/", index + 2);
|
|
2138
|
+
index = close === -1 ? code.length : close + 2;
|
|
2139
|
+
continue;
|
|
2140
|
+
}
|
|
2141
|
+
if (char === "\"" || char === "'" || char === "`") {
|
|
2142
|
+
index = skipQuoted(code, index, char);
|
|
2143
|
+
continue;
|
|
2144
|
+
}
|
|
2145
|
+
if (/[A-Za-z_$]/.test(char)) {
|
|
2146
|
+
const start = index;
|
|
2147
|
+
index += 1;
|
|
2148
|
+
while (index < code.length && /[\w$]/.test(code[index])) index += 1;
|
|
2149
|
+
const name = code.slice(start, index);
|
|
2150
|
+
if (!IDENTIFIER_KEYWORDS.has(name)) ranges.push({
|
|
2151
|
+
start,
|
|
2152
|
+
end: index
|
|
2153
|
+
});
|
|
2154
|
+
continue;
|
|
2155
|
+
}
|
|
2156
|
+
index += 1;
|
|
2157
|
+
}
|
|
2158
|
+
return ranges;
|
|
2159
|
+
}
|
|
2160
|
+
function skipQuoted(code, start, quote) {
|
|
2161
|
+
let index = start + 1;
|
|
2162
|
+
while (index < code.length) {
|
|
2163
|
+
if (code[index] === "\\") {
|
|
2164
|
+
index += 2;
|
|
2165
|
+
continue;
|
|
2166
|
+
}
|
|
2167
|
+
if (code[index] === quote) return index + 1;
|
|
2168
|
+
index += 1;
|
|
2169
|
+
}
|
|
2170
|
+
return code.length;
|
|
2171
|
+
}
|
|
2172
|
+
//#endregion
|
|
2173
|
+
//#region src/typed-hover.ts
|
|
2174
|
+
const DEFAULT_LANGUAGES$1 = ["ts", "tsx"];
|
|
2175
|
+
function resolveTypedHoverOptions(options) {
|
|
2176
|
+
if (!options) return {
|
|
2177
|
+
enabled: false,
|
|
2178
|
+
languages: [...DEFAULT_LANGUAGES$1]
|
|
2179
|
+
};
|
|
2180
|
+
if (options === true) return {
|
|
2181
|
+
enabled: true,
|
|
2182
|
+
languages: [...DEFAULT_LANGUAGES$1]
|
|
2183
|
+
};
|
|
2184
|
+
return {
|
|
2185
|
+
enabled: options.enabled ?? true,
|
|
2186
|
+
languages: options.languages ?? [...DEFAULT_LANGUAGES$1],
|
|
2187
|
+
tsgoCommand: options.tsgoCommand
|
|
2188
|
+
};
|
|
2189
|
+
}
|
|
2190
|
+
function hasTypedHoverMeta(meta) {
|
|
2191
|
+
return meta.split(/\s+/).some((token) => token === "twoslash");
|
|
2192
|
+
}
|
|
2193
|
+
function serializeTypedHoverPayload(payload) {
|
|
2194
|
+
return JSON.stringify(payload).replace(/</g, "\\u003c").replace(/>/g, "\\u003e");
|
|
2195
|
+
}
|
|
2196
|
+
async function applyTypedHover(source, html, options) {
|
|
2197
|
+
if (!options?.enabled || !source.includes("```")) return html;
|
|
2198
|
+
const languages = new Set(options.languages.map((language) => language.toLowerCase()));
|
|
2199
|
+
const fences = (await extractCodeBlocks(source)).filter((block) => {
|
|
2200
|
+
return languages.has(block.language.toLowerCase()) && hasTypedHoverMeta(block.meta);
|
|
2201
|
+
});
|
|
2202
|
+
if (fences.length === 0) return html;
|
|
2203
|
+
try {
|
|
2204
|
+
return attachTypedHoverPayloads(html, await generateTypedHoverAttachments(fences, options.tsgoCommand));
|
|
2205
|
+
} catch {
|
|
2206
|
+
return html;
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
function attachTypedHoverPayloads(html, attachments) {
|
|
2210
|
+
const unused = attachments.filter((item) => item.hovers.length > 0);
|
|
2211
|
+
if (unused.length === 0) return html;
|
|
2212
|
+
let attached = 0;
|
|
2213
|
+
const next = html.replace(/<pre(\b[^>]*)><code(\b[^>]*)>([\s\S]*?)<\/code><\/pre>/g, (full, preAttrs, codeAttrs, inner) => {
|
|
2214
|
+
if (unused.length === 0) return full;
|
|
2215
|
+
if (!isTypeScriptFence(codeAttrs)) return full;
|
|
2216
|
+
const text = decodeHtmlEntities(inner.replace(/<[^>]+>/g, ""));
|
|
2217
|
+
const index = unused.findIndex((item) => normalizeFenceText(item.code) === normalizeFenceText(text));
|
|
2218
|
+
if (index === -1) return full;
|
|
2219
|
+
const item = unused.splice(index, 1)[0];
|
|
2220
|
+
if (!item) return full;
|
|
2221
|
+
attached += 1;
|
|
2222
|
+
const wrapped = wrapHoverRanges(inner, item.hovers);
|
|
2223
|
+
return `${withTypedHoverClass(`<pre${preAttrs}`)}><code${codeAttrs}>${wrapped}</code></pre>\n<script type="application/json" class="ox-typed-hover-data">${serializeTypedHoverPayload({ hovers: item.hovers })}<\/script>`;
|
|
2224
|
+
});
|
|
2225
|
+
if (attached === 0) return html;
|
|
2226
|
+
return `${next}${TYPED_HOVER_STYLE}${TYPED_HOVER_CLIENT}`;
|
|
2227
|
+
}
|
|
2228
|
+
function isTypeScriptFence(codeAttrs) {
|
|
2229
|
+
const match = codeAttrs.match(/class="([^"]*)"/);
|
|
2230
|
+
if (!match?.[1]) return false;
|
|
2231
|
+
return match[1].split(/\s+/).some((token) => {
|
|
2232
|
+
const language = token.replace(/^language-/, "").toLowerCase();
|
|
2233
|
+
return language === "ts" || language === "tsx" || language === "typescript" || language === "typescriptreact";
|
|
2234
|
+
});
|
|
2235
|
+
}
|
|
2236
|
+
function withTypedHoverClass(openPre) {
|
|
2237
|
+
if (/\bclass="/.test(openPre)) return openPre.replace(/\bclass="([^"]*)"/, (_, classes) => {
|
|
2238
|
+
return `class="${classes} ox-typed-hover"`;
|
|
2239
|
+
});
|
|
2240
|
+
return `${openPre} class="ox-typed-hover"`;
|
|
2241
|
+
}
|
|
2242
|
+
function wrapHoverRanges(inner, hovers) {
|
|
2243
|
+
const ranges = [...hovers].sort((a, b) => a.start - b.start || b.end - a.end);
|
|
2244
|
+
let output = "";
|
|
2245
|
+
let htmlIndex = 0;
|
|
2246
|
+
let sourceOffset = 0;
|
|
2247
|
+
let rangeIndex = 0;
|
|
2248
|
+
let openUntil = -1;
|
|
2249
|
+
let openHoverIndex = -1;
|
|
2250
|
+
const startRange = () => {
|
|
2251
|
+
while (rangeIndex < ranges.length && ranges[rangeIndex].start < sourceOffset) rangeIndex += 1;
|
|
2252
|
+
const range = ranges[rangeIndex];
|
|
2253
|
+
if (!range || range.start !== sourceOffset || openUntil !== -1) return;
|
|
2254
|
+
output += `<span class="ox-typed-hover-token" tabindex="0" data-ox-typed-hover="${rangeIndex}">`;
|
|
2255
|
+
openUntil = range.end;
|
|
2256
|
+
openHoverIndex = rangeIndex;
|
|
2257
|
+
rangeIndex += 1;
|
|
2258
|
+
};
|
|
2259
|
+
const endRange = () => {
|
|
2260
|
+
if (openUntil === sourceOffset && openHoverIndex !== -1) {
|
|
2261
|
+
output += "</span>";
|
|
2262
|
+
openUntil = -1;
|
|
2263
|
+
openHoverIndex = -1;
|
|
2264
|
+
}
|
|
2265
|
+
};
|
|
2266
|
+
while (htmlIndex < inner.length) {
|
|
2267
|
+
const char = inner[htmlIndex];
|
|
2268
|
+
if (char === "<") {
|
|
2269
|
+
const close = inner.indexOf(">", htmlIndex);
|
|
2270
|
+
const tag = close === -1 ? inner.slice(htmlIndex) : inner.slice(htmlIndex, close + 1);
|
|
2271
|
+
output += tag;
|
|
2272
|
+
htmlIndex += tag.length;
|
|
2273
|
+
continue;
|
|
2274
|
+
}
|
|
2275
|
+
startRange();
|
|
2276
|
+
if (char === "&") {
|
|
2277
|
+
const semi = inner.indexOf(";", htmlIndex);
|
|
2278
|
+
const entity = semi === -1 ? inner.slice(htmlIndex) : inner.slice(htmlIndex, semi + 1);
|
|
2279
|
+
output += entity;
|
|
2280
|
+
htmlIndex += entity.length;
|
|
2281
|
+
sourceOffset += 1;
|
|
2282
|
+
endRange();
|
|
2283
|
+
continue;
|
|
2284
|
+
}
|
|
2285
|
+
output += char;
|
|
2286
|
+
htmlIndex += 1;
|
|
2287
|
+
sourceOffset += 1;
|
|
2288
|
+
endRange();
|
|
2289
|
+
}
|
|
2290
|
+
if (openHoverIndex !== -1) output += "</span>";
|
|
2291
|
+
return output;
|
|
2292
|
+
}
|
|
2293
|
+
function normalizeFenceText(value) {
|
|
2294
|
+
return decodeHtmlEntities(value).replace(/\r\n/g, "\n").trim();
|
|
2295
|
+
}
|
|
2296
|
+
function decodeHtmlEntities(value) {
|
|
2297
|
+
return value.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&").replace(/"/g, "\"").replace(/'/g, "'");
|
|
2298
|
+
}
|
|
2299
|
+
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>`;
|
|
2300
|
+
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>`;
|
|
2301
|
+
//#endregion
|
|
2302
|
+
//#region src/file-tree-options.ts
|
|
2303
|
+
const disabled = {
|
|
2304
|
+
enabled: false,
|
|
2305
|
+
defaultOpen: true,
|
|
2306
|
+
icons: true
|
|
2307
|
+
};
|
|
2308
|
+
function resolveFileTreeOptions(options) {
|
|
2309
|
+
if (!options) return { ...disabled };
|
|
2310
|
+
if (options === true) return enabledDefaults();
|
|
2311
|
+
if (options.enabled === false) return {
|
|
2312
|
+
...enabledDefaults(),
|
|
2313
|
+
enabled: false,
|
|
2314
|
+
...resolveIcons(options.icons)
|
|
2315
|
+
};
|
|
2316
|
+
return {
|
|
2317
|
+
enabled: options.enabled ?? true,
|
|
2318
|
+
defaultOpen: options.defaultOpen ?? true,
|
|
2319
|
+
...resolveIcons(options.icons)
|
|
2320
|
+
};
|
|
2321
|
+
}
|
|
2322
|
+
function toJsFileTreeOptions(options) {
|
|
2323
|
+
if (!options?.enabled) return void 0;
|
|
2324
|
+
return {
|
|
2325
|
+
enabled: true,
|
|
2326
|
+
defaultOpen: options.defaultOpen,
|
|
2327
|
+
icons: options.icons,
|
|
2328
|
+
iconFolder: options.iconFolder,
|
|
2329
|
+
iconFolderOpen: options.iconFolderOpen,
|
|
2330
|
+
iconFile: options.iconFile,
|
|
2331
|
+
iconFiles: options.iconFiles
|
|
2332
|
+
};
|
|
2333
|
+
}
|
|
2334
|
+
function enabledDefaults() {
|
|
2335
|
+
return {
|
|
2336
|
+
enabled: true,
|
|
2337
|
+
defaultOpen: true,
|
|
2338
|
+
icons: true
|
|
2339
|
+
};
|
|
2340
|
+
}
|
|
2341
|
+
function resolveIcons(icons) {
|
|
2342
|
+
if (icons === false) return { icons: false };
|
|
2343
|
+
if (icons === true || icons == null) return { icons: true };
|
|
2344
|
+
return {
|
|
2345
|
+
icons: true,
|
|
2346
|
+
iconFolder: icons.folder,
|
|
2347
|
+
iconFolderOpen: icons.folderOpen,
|
|
2348
|
+
iconFile: icons.file,
|
|
2349
|
+
iconFiles: icons.files
|
|
2350
|
+
};
|
|
2351
|
+
}
|
|
2352
|
+
//#endregion
|
|
1867
2353
|
//#region src/transform.ts
|
|
1868
2354
|
/**
|
|
1869
2355
|
* The NAPI load, cached as the promise rather than as its result.
|
|
@@ -1974,7 +2460,7 @@ async function transformMarkdown(source, filePath, options, ssgOptions) {
|
|
|
1974
2460
|
} : void 0,
|
|
1975
2461
|
cards: options.cards?.enabled ? { enabled: true } : void 0,
|
|
1976
2462
|
steps: options.steps?.enabled ? { enabled: true } : void 0,
|
|
1977
|
-
fileTree: options.fileTree
|
|
2463
|
+
fileTree: toJsFileTreeOptions(options.fileTree),
|
|
1978
2464
|
sanitize: void 0,
|
|
1979
2465
|
editThisPage: options.editThisPage?.enabled ? {
|
|
1980
2466
|
enabled: true,
|
|
@@ -1992,26 +2478,27 @@ async function transformMarkdown(source, filePath, options, ssgOptions) {
|
|
|
1992
2478
|
if (options.mermaid) html = await transformMermaidStatic(html);
|
|
1993
2479
|
const { html: protectedHtml, svgs } = protectMermaidSvgs(html);
|
|
1994
2480
|
html = protectedHtml;
|
|
1995
|
-
if (options.highlight)
|
|
1996
|
-
const native = await highlightDocumentNatively(html);
|
|
1997
|
-
if (native && native.skipped.length === 0) html = native.html;
|
|
1998
|
-
else {
|
|
1999
|
-
const originalHtml = html;
|
|
2000
|
-
const highlightedHtml = await highlightCode(html);
|
|
2001
|
-
html = napi.mergeHighlightedCodeBlocks(originalHtml, highlightedHtml);
|
|
2002
|
-
}
|
|
2003
|
-
}
|
|
2481
|
+
if (options.highlight) html = await highlightPageHtml(html, napi.mergeHighlightedCodeBlocks);
|
|
2004
2482
|
html = await transformBuiltinEmbeds(html, options.embeds ?? {
|
|
2005
2483
|
github: {},
|
|
2006
2484
|
openGraph: {}
|
|
2007
2485
|
});
|
|
2486
|
+
if (options.highlight && html.includes("ox-github-code-block")) html = await highlightPageHtml(html, napi.mergeHighlightedCodeBlocks);
|
|
2008
2487
|
html = restoreMermaidSvgs(html, svgs);
|
|
2009
2488
|
if (options.sanitize?.enabled) html = napi.sanitizeHtml(html, toJsSanitizeOptions(options.sanitize));
|
|
2489
|
+
if (isMathEnabled(options.math)) html = await renderKatexMath(html);
|
|
2490
|
+
const imports = result.imports ?? [];
|
|
2491
|
+
const exports = result.exports ?? [];
|
|
2492
|
+
const components = result.components ?? [];
|
|
2493
|
+
html = await applyTypedHover(source, html, options.typedHover);
|
|
2010
2494
|
return {
|
|
2011
|
-
code: generateModuleCode(html, frontmatter, toc,
|
|
2495
|
+
code: generateModuleCode(html, frontmatter, toc, imports, exports, components, filePath),
|
|
2012
2496
|
html,
|
|
2013
2497
|
frontmatter,
|
|
2014
|
-
toc
|
|
2498
|
+
toc,
|
|
2499
|
+
imports,
|
|
2500
|
+
exports,
|
|
2501
|
+
components
|
|
2015
2502
|
};
|
|
2016
2503
|
}
|
|
2017
2504
|
async function runCodeBlockTypecheck(source, options) {
|
|
@@ -2070,8 +2557,11 @@ function normalizeTocEntry(entry) {
|
|
|
2070
2557
|
}
|
|
2071
2558
|
/**
|
|
2072
2559
|
* Generates the JavaScript module code.
|
|
2560
|
+
*
|
|
2561
|
+
* MDX metadata is serialized as JSON. User `import` / `export` source is never
|
|
2562
|
+
* emitted as live JavaScript, so transform does not execute module side effects.
|
|
2073
2563
|
*/
|
|
2074
|
-
function generateModuleCode(html, frontmatter, toc,
|
|
2564
|
+
function generateModuleCode(html, frontmatter, toc, imports, exports, components, filePath) {
|
|
2075
2565
|
return `
|
|
2076
2566
|
// Generated by @ox-content/vite-plugin
|
|
2077
2567
|
// Source: ${filePath}
|
|
@@ -2091,6 +2581,21 @@ export const frontmatter = ${JSON.stringify(frontmatter)};
|
|
|
2091
2581
|
*/
|
|
2092
2582
|
export const toc = ${JSON.stringify(toc)};
|
|
2093
2583
|
|
|
2584
|
+
/**
|
|
2585
|
+
* MDX import statements collected from the AST.
|
|
2586
|
+
*/
|
|
2587
|
+
export const imports = ${JSON.stringify(imports)};
|
|
2588
|
+
|
|
2589
|
+
/**
|
|
2590
|
+
* MDX export names collected from the AST.
|
|
2591
|
+
*/
|
|
2592
|
+
export const exports = ${JSON.stringify(exports)};
|
|
2593
|
+
|
|
2594
|
+
/**
|
|
2595
|
+
* Unique JSX component names collected from the AST.
|
|
2596
|
+
*/
|
|
2597
|
+
export const components = ${JSON.stringify(components)};
|
|
2598
|
+
|
|
2094
2599
|
/**
|
|
2095
2600
|
* Default export with all data.
|
|
2096
2601
|
*/
|
|
@@ -2098,6 +2603,9 @@ export default {
|
|
|
2098
2603
|
html,
|
|
2099
2604
|
frontmatter,
|
|
2100
2605
|
toc,
|
|
2606
|
+
imports,
|
|
2607
|
+
exports,
|
|
2608
|
+
components,
|
|
2101
2609
|
};
|
|
2102
2610
|
|
|
2103
2611
|
// HMR support
|
|
@@ -2548,7 +3056,7 @@ function formatChromiumUnavailableDetail(err) {
|
|
|
2548
3056
|
/**
|
|
2549
3057
|
* Escapes HTML special characters.
|
|
2550
3058
|
*/
|
|
2551
|
-
function escapeHtml$
|
|
3059
|
+
function escapeHtml$5(str) {
|
|
2552
3060
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2553
3061
|
}
|
|
2554
3062
|
function normalizeBrandValue(str) {
|
|
@@ -2611,12 +3119,12 @@ function getDefaultTemplate() {
|
|
|
2611
3119
|
const isBrandCard = normalizeBrandValue(title) === normalizeBrandValue(rawBrand);
|
|
2612
3120
|
const heroTitle = isBrandCard ? "High-performance Markdown toolkit" : title;
|
|
2613
3121
|
const heroDescription = isBrandCard ? "Rust-powered docs and high-performance Markdown tooling." : description && description.trim().length > 0 ? description : "Rust-powered docs and Markdown tooling.";
|
|
2614
|
-
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$
|
|
3122
|
+
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>` : "";
|
|
2615
3123
|
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;">
|
|
2616
3124
|
<div style="position:relative;z-index:1;display:flex;flex-direction:column;height:100%;">
|
|
2617
3125
|
<div style="display:flex;align-items:flex-start;">${renderWordmarkSvg()}</div>
|
|
2618
3126
|
<div style="display:flex;flex-direction:column;justify-content:center;gap:24px;max-width:860px;flex:1;padding:22px 0 0;">
|
|
2619
|
-
<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$
|
|
3127
|
+
<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>
|
|
2620
3128
|
${descriptionHtml}
|
|
2621
3129
|
</div>
|
|
2622
3130
|
</div>
|
|
@@ -2650,7 +3158,7 @@ function computeCacheKey(templateSource, props, width, height) {
|
|
|
2650
3158
|
async function getCached(cacheDir, key) {
|
|
2651
3159
|
const filePath = path$2.join(cacheDir, `${key}.png`);
|
|
2652
3160
|
try {
|
|
2653
|
-
return await fs$
|
|
3161
|
+
return await fs$3.readFile(filePath);
|
|
2654
3162
|
} catch {
|
|
2655
3163
|
return null;
|
|
2656
3164
|
}
|
|
@@ -2659,9 +3167,9 @@ async function getCached(cacheDir, key) {
|
|
|
2659
3167
|
* Writes a PNG buffer to the cache.
|
|
2660
3168
|
*/
|
|
2661
3169
|
async function writeCache(cacheDir, key, png) {
|
|
2662
|
-
await fs$
|
|
3170
|
+
await fs$3.mkdir(cacheDir, { recursive: true });
|
|
2663
3171
|
const filePath = path$2.join(cacheDir, `${key}.png`);
|
|
2664
|
-
await fs$
|
|
3172
|
+
await fs$3.writeFile(filePath, png);
|
|
2665
3173
|
}
|
|
2666
3174
|
//#endregion
|
|
2667
3175
|
//#region \0@oxc-project+runtime@0.143.0/helpers/esm/usingCtx.js
|
|
@@ -3147,6 +3655,80 @@ async function renderSinglePage(entry, templateFn, templateSource, options, cach
|
|
|
3147
3655
|
}
|
|
3148
3656
|
}
|
|
3149
3657
|
//#endregion
|
|
3658
|
+
//#region src/plugins/math-assets.ts
|
|
3659
|
+
/**
|
|
3660
|
+
* Serve and copy KaTeX CSS/fonts only when the optional `katex` package exists.
|
|
3661
|
+
*/
|
|
3662
|
+
/**
|
|
3663
|
+
* Copies `katex.min.css` and `fonts/` into the SSG output.
|
|
3664
|
+
* Returns an empty list when KaTeX is not installed.
|
|
3665
|
+
*/
|
|
3666
|
+
async function copyKatexAssets(outDir) {
|
|
3667
|
+
const dist = resolveKatexDist();
|
|
3668
|
+
if (!dist) return [];
|
|
3669
|
+
const dest = join(outDir, KATEX_ASSET_DIR);
|
|
3670
|
+
await mkdir(join(dest, "fonts"), { recursive: true });
|
|
3671
|
+
const cssDest = join(dest, "katex.min.css");
|
|
3672
|
+
await copyFile(join(dist, "katex.min.css"), cssDest);
|
|
3673
|
+
await cp(join(dist, "fonts"), join(dest, "fonts"), { recursive: true });
|
|
3674
|
+
return [cssDest];
|
|
3675
|
+
}
|
|
3676
|
+
/** Dev-server middleware that serves `/__ox_katex__/*` from `katex/dist`. */
|
|
3677
|
+
function createKatexAssetsPlugin() {
|
|
3678
|
+
return {
|
|
3679
|
+
name: "ox-content:katex-assets",
|
|
3680
|
+
configureServer(server) {
|
|
3681
|
+
const dist = resolveKatexDist();
|
|
3682
|
+
if (!dist) return;
|
|
3683
|
+
server.middlewares.use((req, res, next) => {
|
|
3684
|
+
const url = req.url ?? "";
|
|
3685
|
+
const marker = `/${KATEX_ASSET_DIR}/`;
|
|
3686
|
+
const index = url.indexOf(marker);
|
|
3687
|
+
if (index === -1) {
|
|
3688
|
+
next();
|
|
3689
|
+
return;
|
|
3690
|
+
}
|
|
3691
|
+
const rel = decodeURIComponent(url.slice(index + marker.length).split("?")[0] ?? "");
|
|
3692
|
+
const file = safeKatexFile(dist, rel);
|
|
3693
|
+
if (!file) {
|
|
3694
|
+
res.statusCode = 404;
|
|
3695
|
+
res.end();
|
|
3696
|
+
return;
|
|
3697
|
+
}
|
|
3698
|
+
stat(file).then((info) => {
|
|
3699
|
+
if (!info.isFile()) {
|
|
3700
|
+
res.statusCode = 404;
|
|
3701
|
+
res.end();
|
|
3702
|
+
return;
|
|
3703
|
+
}
|
|
3704
|
+
res.setHeader("Content-Type", katexContentType(file));
|
|
3705
|
+
createReadStream(file).pipe(res);
|
|
3706
|
+
}).catch(() => {
|
|
3707
|
+
res.statusCode = 404;
|
|
3708
|
+
res.end();
|
|
3709
|
+
});
|
|
3710
|
+
});
|
|
3711
|
+
}
|
|
3712
|
+
};
|
|
3713
|
+
}
|
|
3714
|
+
function safeKatexFile(dist, rel) {
|
|
3715
|
+
if (!rel || rel.includes("\0") || rel.split(/[\\/]/).includes("..")) return null;
|
|
3716
|
+
const full = resolve(dist, rel);
|
|
3717
|
+
const root = resolve(dist) + sep;
|
|
3718
|
+
if (full !== resolve(dist) && !full.startsWith(root)) return null;
|
|
3719
|
+
const inside = relative(dist, full);
|
|
3720
|
+
if (inside.startsWith("..") || inside.includes(`..${sep}`)) return null;
|
|
3721
|
+
return full;
|
|
3722
|
+
}
|
|
3723
|
+
function katexContentType(file) {
|
|
3724
|
+
const ext = extname(file);
|
|
3725
|
+
if (ext === ".css") return "text/css; charset=utf-8";
|
|
3726
|
+
if (ext === ".woff2") return "font/woff2";
|
|
3727
|
+
if (ext === ".woff") return "font/woff";
|
|
3728
|
+
if (ext === ".ttf") return "font/ttf";
|
|
3729
|
+
return "application/octet-stream";
|
|
3730
|
+
}
|
|
3731
|
+
//#endregion
|
|
3150
3732
|
//#region src/island/parse.ts
|
|
3151
3733
|
/**
|
|
3152
3734
|
* Island Parser
|
|
@@ -3755,6 +4337,7 @@ function renderPage(page, options) {
|
|
|
3755
4337
|
html: page.html,
|
|
3756
4338
|
toc: page.toc,
|
|
3757
4339
|
lastUpdated: page.lastUpdated,
|
|
4340
|
+
contributors: page.contributors,
|
|
3758
4341
|
path: page.path,
|
|
3759
4342
|
url: page.url,
|
|
3760
4343
|
frontmatter: page.frontmatter,
|
|
@@ -3770,6 +4353,7 @@ function renderPage(page, options) {
|
|
|
3770
4353
|
html: p.html,
|
|
3771
4354
|
toc: p.toc,
|
|
3772
4355
|
lastUpdated: p.lastUpdated,
|
|
4356
|
+
contributors: p.contributors,
|
|
3773
4357
|
path: p.path,
|
|
3774
4358
|
url: p.url,
|
|
3775
4359
|
frontmatter: p.frontmatter,
|
|
@@ -3830,8 +4414,8 @@ function DefaultTheme({ children }) {
|
|
|
3830
4414
|
<head>
|
|
3831
4415
|
<meta charset="UTF-8">
|
|
3832
4416
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3833
|
-
<title>${escapeHtml$
|
|
3834
|
-
${page.description ? `<meta name="description" content="${escapeHtml$
|
|
4417
|
+
<title>${escapeHtml$4(page.title)} - ${escapeHtml$4(site.name)}</title>
|
|
4418
|
+
${page.description ? `<meta name="description" content="${escapeHtml$4(page.description)}">` : ""}
|
|
3835
4419
|
<style>
|
|
3836
4420
|
:root {
|
|
3837
4421
|
--octc-color-primary: #4f6fae;
|
|
@@ -3855,7 +4439,7 @@ function DefaultTheme({ children }) {
|
|
|
3855
4439
|
</head>
|
|
3856
4440
|
<body>
|
|
3857
4441
|
<header>
|
|
3858
|
-
<h1>${escapeHtml$
|
|
4442
|
+
<h1>${escapeHtml$4(site.name)}</h1>
|
|
3859
4443
|
</header>
|
|
3860
4444
|
<main>
|
|
3861
4445
|
${children.__html}
|
|
@@ -3863,7 +4447,7 @@ function DefaultTheme({ children }) {
|
|
|
3863
4447
|
</body>
|
|
3864
4448
|
</html>` };
|
|
3865
4449
|
}
|
|
3866
|
-
function escapeHtml$
|
|
4450
|
+
function escapeHtml$4(str) {
|
|
3867
4451
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
3868
4452
|
}
|
|
3869
4453
|
/**
|
|
@@ -3900,7 +4484,7 @@ function createTheme(config) {
|
|
|
3900
4484
|
* String bodies follow `ox_content_ssg::generate_site_maps`. The Vite plugin
|
|
3901
4485
|
* writes those files during SSG without adding a NAPI surface.
|
|
3902
4486
|
*/
|
|
3903
|
-
const MISSING_SITE_URL$
|
|
4487
|
+
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";
|
|
3904
4488
|
/**
|
|
3905
4489
|
* Resolves `siteMaps` with defaults.
|
|
3906
4490
|
*
|
|
@@ -3927,7 +4511,7 @@ function resolveSiteMapsOptions(value) {
|
|
|
3927
4511
|
/** Builds sitemap / robots / llms bodies without writing files. */
|
|
3928
4512
|
function generateSiteMaps(input) {
|
|
3929
4513
|
if (!input.options?.enabled) return {};
|
|
3930
|
-
if (!hasSiteUrl$
|
|
4514
|
+
if (!hasSiteUrl$2(input.siteUrl)) return { warning: MISSING_SITE_URL$2 };
|
|
3931
4515
|
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);
|
|
3932
4516
|
const result = { sitemapXml: generateSitemapXml(published) };
|
|
3933
4517
|
if (input.options.robots) result.robotsTxt = generateRobotsTxt(input.sitemapLoc ?? "");
|
|
@@ -3954,20 +4538,20 @@ async function writeSiteMapFiles(input) {
|
|
|
3954
4538
|
[generated.llmsTxt, "llms.txt"]
|
|
3955
4539
|
].filter((entry) => entry[0] != null);
|
|
3956
4540
|
if (outputs.length === 0) return { files: [] };
|
|
3957
|
-
await fs$
|
|
4541
|
+
await fs$2.mkdir(input.outDir, { recursive: true });
|
|
3958
4542
|
const files = [];
|
|
3959
4543
|
for (const [body, name] of outputs) {
|
|
3960
4544
|
const outputPath = path$1.join(input.outDir, name);
|
|
3961
|
-
await fs$
|
|
4545
|
+
await fs$2.writeFile(outputPath, body, "utf8");
|
|
3962
4546
|
files.push(outputPath);
|
|
3963
4547
|
}
|
|
3964
4548
|
return { files };
|
|
3965
4549
|
}
|
|
3966
|
-
function hasSiteUrl$
|
|
4550
|
+
function hasSiteUrl$2(siteUrl) {
|
|
3967
4551
|
return Boolean(siteUrl && siteUrl.trim());
|
|
3968
4552
|
}
|
|
3969
4553
|
function absoluteSitemapUrl(siteUrl, base) {
|
|
3970
|
-
if (!hasSiteUrl$
|
|
4554
|
+
if (!hasSiteUrl$2(siteUrl)) return "";
|
|
3971
4555
|
return `${(siteUrl ?? "").trim().replace(/\/+$/, "")}${!base || base === "/" ? "/" : base.endsWith("/") ? base : `${base}/`}sitemap.xml`;
|
|
3972
4556
|
}
|
|
3973
4557
|
function generateSitemapXml(pages) {
|
|
@@ -4153,7 +4737,7 @@ function resolvePageRoutes(input) {
|
|
|
4153
4737
|
if (!input.permalinks?.enabled) return {
|
|
4154
4738
|
pages: cascaded.map((page) => ({
|
|
4155
4739
|
source: page.source,
|
|
4156
|
-
urlPath: normalizeUrlPath(page.fileUrl),
|
|
4740
|
+
urlPath: normalizeUrlPath$1(page.fileUrl),
|
|
4157
4741
|
frontmatter: page.frontmatter
|
|
4158
4742
|
})),
|
|
4159
4743
|
errors: []
|
|
@@ -4181,7 +4765,7 @@ function resolvePageRoutes(input) {
|
|
|
4181
4765
|
errors
|
|
4182
4766
|
};
|
|
4183
4767
|
}
|
|
4184
|
-
function normalizeUrlPath(value) {
|
|
4768
|
+
function normalizeUrlPath$1(value) {
|
|
4185
4769
|
const segments = pathSegments(value);
|
|
4186
4770
|
return segments.length === 0 ? "/" : segments.join("/");
|
|
4187
4771
|
}
|
|
@@ -4215,10 +4799,10 @@ function applyCascade(pages, options) {
|
|
|
4215
4799
|
});
|
|
4216
4800
|
}
|
|
4217
4801
|
function resolveOne(page) {
|
|
4218
|
-
const fileUrl = normalizeUrlPath(page.fileUrl);
|
|
4802
|
+
const fileUrl = normalizeUrlPath$1(page.fileUrl);
|
|
4219
4803
|
const permalink = readString(page.frontmatter.permalink);
|
|
4220
4804
|
if (permalink !== void 0) {
|
|
4221
|
-
const url = isSafePermalink(permalink) ? normalizeUrlPath(permalink) : void 0;
|
|
4805
|
+
const url = isSafePermalink(permalink) ? normalizeUrlPath$1(permalink) : void 0;
|
|
4222
4806
|
return url ? { urlPath: url } : {
|
|
4223
4807
|
urlPath: fileUrl,
|
|
4224
4808
|
error: `[ox-content] rejected permalink ${JSON.stringify(permalink)} on ${page.source} (path escape); using the file-tree URL`
|
|
@@ -4237,7 +4821,7 @@ function resolveOne(page) {
|
|
|
4237
4821
|
function rewriteSlug(fileUrl, slug) {
|
|
4238
4822
|
const trimmed = slug.trim();
|
|
4239
4823
|
if (trimmed.includes("/") || !isSafePermalink(trimmed)) return;
|
|
4240
|
-
const normalized = normalizeUrlPath(trimmed);
|
|
4824
|
+
const normalized = normalizeUrlPath$1(trimmed);
|
|
4241
4825
|
if (normalized === "/") return;
|
|
4242
4826
|
if (fileUrl === "/") return normalized;
|
|
4243
4827
|
const segments = fileUrl.split("/").filter(Boolean);
|
|
@@ -4354,8 +4938,8 @@ function applyCollectionRoutes(manifest, permalinks, cascade) {
|
|
|
4354
4938
|
}
|
|
4355
4939
|
/** Updates auto-nav hrefs after permalinks change a page URL. */
|
|
4356
4940
|
function remapNavGroups(nav, kept, skippedFileUrls) {
|
|
4357
|
-
const skipped = new Set(skippedFileUrls.map(normalizeUrlPath));
|
|
4358
|
-
const byFile = new Map(kept.map((page) => [normalizeUrlPath(page.fileUrl), page]));
|
|
4941
|
+
const skipped = new Set(skippedFileUrls.map(normalizeUrlPath$1));
|
|
4942
|
+
const byFile = new Map(kept.map((page) => [normalizeUrlPath$1(page.fileUrl), page]));
|
|
4359
4943
|
return nav.map((group) => ({
|
|
4360
4944
|
...group,
|
|
4361
4945
|
items: remapNavItems(group.items, byFile, skipped)
|
|
@@ -4367,7 +4951,7 @@ function routePathsFromUrl(urlPath, srcDir, outDir, base, extension, siteUrl) {
|
|
|
4367
4951
|
}
|
|
4368
4952
|
function remapNavItems(items, byFile, skipped) {
|
|
4369
4953
|
return items.flatMap((item) => {
|
|
4370
|
-
const key = normalizeUrlPath(item.path);
|
|
4954
|
+
const key = normalizeUrlPath$1(item.path);
|
|
4371
4955
|
if (skipped.has(key)) return [];
|
|
4372
4956
|
const hit = byFile.get(key);
|
|
4373
4957
|
const children = item.children ? remapNavItems(item.children, byFile, skipped) : void 0;
|
|
@@ -4484,16 +5068,16 @@ function planRedirectFiles(input) {
|
|
|
4484
5068
|
async function writeRedirectFiles(input) {
|
|
4485
5069
|
const plan = planRedirectFiles(input);
|
|
4486
5070
|
if (plan.files.length === 0 && !plan.netlify && !plan.headers && !plan.json) return { files: [] };
|
|
4487
|
-
await fs$
|
|
5071
|
+
await fs$2.mkdir(input.outDir, { recursive: true });
|
|
4488
5072
|
const files = [];
|
|
4489
5073
|
for (const entry of plan.files) {
|
|
4490
5074
|
const outputPath = path$1.join(input.outDir, entry.relativePath);
|
|
4491
5075
|
try {
|
|
4492
|
-
await fs$
|
|
5076
|
+
await fs$2.access(outputPath);
|
|
4493
5077
|
continue;
|
|
4494
5078
|
} catch {
|
|
4495
|
-
await fs$
|
|
4496
|
-
await fs$
|
|
5079
|
+
await fs$2.mkdir(path$1.dirname(outputPath), { recursive: true });
|
|
5080
|
+
await fs$2.writeFile(outputPath, entry.html, "utf8");
|
|
4497
5081
|
files.push(outputPath);
|
|
4498
5082
|
}
|
|
4499
5083
|
}
|
|
@@ -4504,14 +5088,14 @@ async function writeRedirectFiles(input) {
|
|
|
4504
5088
|
]) {
|
|
4505
5089
|
if (!body) continue;
|
|
4506
5090
|
const outputPath = path$1.join(input.outDir, name);
|
|
4507
|
-
await fs$
|
|
5091
|
+
await fs$2.writeFile(outputPath, body, "utf8");
|
|
4508
5092
|
files.push(outputPath);
|
|
4509
5093
|
}
|
|
4510
5094
|
return { files };
|
|
4511
5095
|
}
|
|
4512
5096
|
/** Static HTML redirect body. `dest` is escaped. */
|
|
4513
5097
|
function generateRedirectHtml(dest) {
|
|
4514
|
-
const escaped = escapeHtml$
|
|
5098
|
+
const escaped = escapeHtml$3(dest);
|
|
4515
5099
|
return `\
|
|
4516
5100
|
<!DOCTYPE html>
|
|
4517
5101
|
<html lang="en">
|
|
@@ -4596,7 +5180,7 @@ function upsert(files, index, occupied, from, to, base) {
|
|
|
4596
5180
|
html
|
|
4597
5181
|
});
|
|
4598
5182
|
}
|
|
4599
|
-
function escapeHtml$
|
|
5183
|
+
function escapeHtml$3(value) {
|
|
4600
5184
|
return value.replace(/[&<>"']/g, (ch) => {
|
|
4601
5185
|
switch (ch) {
|
|
4602
5186
|
case "&": return "&";
|
|
@@ -4994,7 +5578,7 @@ function createNativeTransformOptions(options) {
|
|
|
4994
5578
|
} : void 0,
|
|
4995
5579
|
cards: options.cards?.enabled ? { enabled: true } : void 0,
|
|
4996
5580
|
steps: options.steps?.enabled ? { enabled: true } : void 0,
|
|
4997
|
-
fileTree: options.fileTree
|
|
5581
|
+
fileTree: toJsFileTreeOptions(options.fileTree),
|
|
4998
5582
|
editThisPage: options.editThisPage?.enabled ? {
|
|
4999
5583
|
enabled: true,
|
|
5000
5584
|
repoUrl: options.editThisPage.repoUrl,
|
|
@@ -5272,8 +5856,8 @@ function weekdayUtc(year, month, day) {
|
|
|
5272
5856
|
* String bodies follow `ox_content_ssg::generate_feeds`. The Vite plugin
|
|
5273
5857
|
* writes those files during SSG without adding a NAPI surface.
|
|
5274
5858
|
*/
|
|
5275
|
-
const MISSING_SITE_URL = "[ox-content] feeds is enabled but ssg.siteUrl is not set; RSS, Atom, and JSON feeds were not written";
|
|
5276
|
-
const DEFAULT_FORMATS = [
|
|
5859
|
+
const MISSING_SITE_URL$1 = "[ox-content] feeds is enabled but ssg.siteUrl is not set; RSS, Atom, and JSON feeds were not written";
|
|
5860
|
+
const DEFAULT_FORMATS$1 = [
|
|
5277
5861
|
"rss",
|
|
5278
5862
|
"atom",
|
|
5279
5863
|
"json"
|
|
@@ -5290,19 +5874,19 @@ const DEFAULT_PATH = "/";
|
|
|
5290
5874
|
function resolveFeedsOptions(value) {
|
|
5291
5875
|
if (!value) return {
|
|
5292
5876
|
enabled: false,
|
|
5293
|
-
formats: [...DEFAULT_FORMATS],
|
|
5877
|
+
formats: [...DEFAULT_FORMATS$1],
|
|
5294
5878
|
limit: DEFAULT_LIMIT,
|
|
5295
5879
|
path: DEFAULT_PATH
|
|
5296
5880
|
};
|
|
5297
5881
|
if (value === true) return {
|
|
5298
5882
|
enabled: true,
|
|
5299
|
-
formats: [...DEFAULT_FORMATS],
|
|
5883
|
+
formats: [...DEFAULT_FORMATS$1],
|
|
5300
5884
|
limit: DEFAULT_LIMIT,
|
|
5301
5885
|
path: DEFAULT_PATH
|
|
5302
5886
|
};
|
|
5303
5887
|
return {
|
|
5304
5888
|
enabled: true,
|
|
5305
|
-
formats: normalizeFormats(value.formats),
|
|
5889
|
+
formats: normalizeFormats$1(value.formats),
|
|
5306
5890
|
collection: value.collection,
|
|
5307
5891
|
limit: value.limit ?? DEFAULT_LIMIT,
|
|
5308
5892
|
path: value.path ?? DEFAULT_PATH
|
|
@@ -5317,7 +5901,7 @@ function resolveFeedCollectionName(requested, collectionNames) {
|
|
|
5317
5901
|
/** Builds RSS / Atom / JSON Feed bodies without writing files. */
|
|
5318
5902
|
function generateFeeds(input) {
|
|
5319
5903
|
if (!input.options?.enabled) return {};
|
|
5320
|
-
if (!hasSiteUrl(input.siteUrl)) return { warning: MISSING_SITE_URL };
|
|
5904
|
+
if (!hasSiteUrl$1(input.siteUrl)) return { warning: MISSING_SITE_URL$1 };
|
|
5321
5905
|
const published = publishedItems(input);
|
|
5322
5906
|
const doc = feedDocument(input);
|
|
5323
5907
|
const result = {};
|
|
@@ -5340,17 +5924,17 @@ async function writeFeedFiles(input) {
|
|
|
5340
5924
|
].filter((entry) => entry[0] != null);
|
|
5341
5925
|
if (outputs.length === 0) return { files: [] };
|
|
5342
5926
|
const dest = outputDir(input.outDir, input.options?.path ?? DEFAULT_PATH);
|
|
5343
|
-
await fs$
|
|
5927
|
+
await fs$2.mkdir(dest, { recursive: true });
|
|
5344
5928
|
const files = [];
|
|
5345
5929
|
for (const [body, name] of outputs) {
|
|
5346
5930
|
const outputPath = path$1.join(dest, name);
|
|
5347
|
-
await fs$
|
|
5931
|
+
await fs$2.writeFile(outputPath, body, "utf8");
|
|
5348
5932
|
files.push(outputPath);
|
|
5349
5933
|
}
|
|
5350
5934
|
return { files };
|
|
5351
5935
|
}
|
|
5352
|
-
function normalizeFormats(formats) {
|
|
5353
|
-
if (!formats) return [...DEFAULT_FORMATS];
|
|
5936
|
+
function normalizeFormats$1(formats) {
|
|
5937
|
+
if (!formats) return [...DEFAULT_FORMATS$1];
|
|
5354
5938
|
const seen = /* @__PURE__ */ new Set();
|
|
5355
5939
|
const resolved = [];
|
|
5356
5940
|
for (const format of formats) if ((format === "rss" || format === "atom" || format === "json") && !seen.has(format)) {
|
|
@@ -5359,7 +5943,7 @@ function normalizeFormats(formats) {
|
|
|
5359
5943
|
}
|
|
5360
5944
|
return resolved;
|
|
5361
5945
|
}
|
|
5362
|
-
function hasSiteUrl(siteUrl) {
|
|
5946
|
+
function hasSiteUrl$1(siteUrl) {
|
|
5363
5947
|
return Boolean(siteUrl && siteUrl.trim());
|
|
5364
5948
|
}
|
|
5365
5949
|
function homePageUrl(siteUrl, base = "/") {
|
|
@@ -5411,7 +5995,7 @@ function normalizeItem(item, input) {
|
|
|
5411
5995
|
title: item.title ?? "",
|
|
5412
5996
|
description: typeof item.description === "string" ? item.description : void 0,
|
|
5413
5997
|
loc: item.loc || itemLoc(input, item),
|
|
5414
|
-
date: parseDate(dateField(item.date ?? item.frontmatter?.date)) ?? parseDate(dateField(item.lastUpdated ?? item.frontmatter?.lastUpdated))
|
|
5998
|
+
date: parseDate(dateField$1(item.date ?? item.frontmatter?.date)) ?? parseDate(dateField$1(item.lastUpdated ?? item.frontmatter?.lastUpdated))
|
|
5415
5999
|
};
|
|
5416
6000
|
}
|
|
5417
6001
|
function itemLoc(input, item) {
|
|
@@ -5419,49 +6003,254 @@ function itemLoc(input, item) {
|
|
|
5419
6003
|
const urlPath = (item.path ?? "").replace(/^\/+|\/+$/g, "");
|
|
5420
6004
|
return urlPath ? `${home}${urlPath}/` : home;
|
|
5421
6005
|
}
|
|
5422
|
-
function dateField(value) {
|
|
6006
|
+
function dateField$1(value) {
|
|
5423
6007
|
if (typeof value === "string" && value.trim()) return value.trim();
|
|
5424
6008
|
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
5425
6009
|
if (value instanceof Date && !Number.isNaN(value.getTime())) return value.toISOString();
|
|
5426
6010
|
}
|
|
5427
6011
|
//#endregion
|
|
5428
|
-
//#region src/
|
|
6012
|
+
//#region src/pwa.ts
|
|
5429
6013
|
/**
|
|
5430
|
-
*
|
|
6014
|
+
* Opt-in web app manifest and conservative service worker.
|
|
6015
|
+
*
|
|
6016
|
+
* The Vite plugin writes those files during SSG without adding a NAPI surface.
|
|
6017
|
+
* Enabling `offline` injects a tiny client script that registers `sw.js`.
|
|
5431
6018
|
*/
|
|
5432
|
-
|
|
5433
|
-
|
|
6019
|
+
const MISSING_SITE_URL = "[ox-content] pwa is enabled but ssg.siteUrl is not set; manifest.webmanifest and sw.js were not written";
|
|
6020
|
+
const DEFAULT_THEME_COLOR = "#000000";
|
|
6021
|
+
const DEFAULT_BACKGROUND_COLOR = "#ffffff";
|
|
6022
|
+
const MANIFEST_NAME = "manifest.webmanifest";
|
|
6023
|
+
const SERVICE_WORKER_NAME = "sw.js";
|
|
6024
|
+
/**
|
|
6025
|
+
* Resolves `pwa` with defaults.
|
|
6026
|
+
*
|
|
6027
|
+
* `false` / omitted stays off. `true` enables the manifest and offline
|
|
6028
|
+
* service worker. An object enables the feature and overrides only the
|
|
6029
|
+
* fields the site set.
|
|
6030
|
+
*/
|
|
6031
|
+
function resolvePwaOptions(value) {
|
|
6032
|
+
if (!value) return {
|
|
6033
|
+
enabled: false,
|
|
6034
|
+
offline: true
|
|
6035
|
+
};
|
|
6036
|
+
if (value === true) return {
|
|
6037
|
+
enabled: true,
|
|
6038
|
+
offline: true
|
|
6039
|
+
};
|
|
6040
|
+
return {
|
|
6041
|
+
enabled: true,
|
|
6042
|
+
offline: value.offline ?? true,
|
|
6043
|
+
name: value.name,
|
|
6044
|
+
shortName: value.shortName,
|
|
6045
|
+
themeColor: value.themeColor,
|
|
6046
|
+
backgroundColor: value.backgroundColor,
|
|
6047
|
+
startUrl: value.startUrl
|
|
6048
|
+
};
|
|
5434
6049
|
}
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
6050
|
+
/** Builds manifest / service-worker bodies without writing files. */
|
|
6051
|
+
function generatePwa(input) {
|
|
6052
|
+
if (!input.options?.enabled) return {};
|
|
6053
|
+
if (!hasSiteUrl(input.siteUrl)) return { warning: MISSING_SITE_URL };
|
|
6054
|
+
const base = normalizeBase(input.base);
|
|
6055
|
+
const name = sanitizeManifestText(input.options.name ?? input.siteName ?? "");
|
|
6056
|
+
const shortName = sanitizeManifestText(input.options.shortName ?? name);
|
|
6057
|
+
const startUrl = sanitizeStartUrl(input.options.startUrl, base);
|
|
6058
|
+
const themeColor = sanitizeColor(input.options.themeColor, DEFAULT_THEME_COLOR);
|
|
6059
|
+
const backgroundColor = sanitizeColor(input.options.backgroundColor, DEFAULT_BACKGROUND_COLOR);
|
|
6060
|
+
const result = { manifest: `${escapeJsonScript(JSON.stringify({
|
|
6061
|
+
name,
|
|
6062
|
+
short_name: shortName,
|
|
6063
|
+
start_url: startUrl,
|
|
6064
|
+
scope: base,
|
|
6065
|
+
display: "standalone",
|
|
6066
|
+
background_color: backgroundColor,
|
|
6067
|
+
theme_color: themeColor
|
|
6068
|
+
}, null, 2))}\n` };
|
|
6069
|
+
if (input.options.offline) result.serviceWorker = generateServiceWorker(base);
|
|
6070
|
+
return result;
|
|
5438
6071
|
}
|
|
5439
|
-
|
|
6072
|
+
/** Writes enabled PWA files into `outDir`. */
|
|
6073
|
+
async function writePwaFiles(input) {
|
|
6074
|
+
const generated = generatePwa(input);
|
|
6075
|
+
if (generated.warning) return {
|
|
6076
|
+
files: [],
|
|
6077
|
+
warning: generated.warning
|
|
6078
|
+
};
|
|
6079
|
+
const outputs = [[generated.manifest, MANIFEST_NAME], [generated.serviceWorker, SERVICE_WORKER_NAME]].filter((entry) => entry[0] != null);
|
|
6080
|
+
if (outputs.length === 0) return { files: [] };
|
|
6081
|
+
await fs$2.mkdir(input.outDir, { recursive: true });
|
|
6082
|
+
const files = [];
|
|
6083
|
+
for (const [body, name] of outputs) {
|
|
6084
|
+
const outputPath = path$1.join(input.outDir, name);
|
|
6085
|
+
await fs$2.writeFile(outputPath, body, "utf8");
|
|
6086
|
+
files.push(outputPath);
|
|
6087
|
+
}
|
|
6088
|
+
return { files };
|
|
6089
|
+
}
|
|
6090
|
+
/**
|
|
6091
|
+
* Injects `rel=manifest` (and the service-worker register script when offline)
|
|
6092
|
+
* into a themed HTML document. Bare / fragment HTML is left unchanged.
|
|
6093
|
+
*/
|
|
6094
|
+
function injectPwaPageTags(html, input) {
|
|
6095
|
+
if (!input.options?.enabled || !isThemedDocument(html)) return html;
|
|
6096
|
+
const base = normalizeBase(input.base);
|
|
6097
|
+
const manifestHref = escapeAttribute$1(`${base}${MANIFEST_NAME}`);
|
|
6098
|
+
const themeColor = sanitizeColor(input.options.themeColor, DEFAULT_THEME_COLOR);
|
|
6099
|
+
let next = insertBeforeTag(html, "</head>", ` ${[`<link rel="manifest" href="${manifestHref}">`, `<meta name="theme-color" content="${escapeAttribute$1(themeColor)}">`].join("\n ")}\n`);
|
|
6100
|
+
if (input.options.offline) {
|
|
6101
|
+
const script = `<script>if("serviceWorker"in navigator)navigator.serviceWorker.register(${JSON.stringify(`${base}${SERVICE_WORKER_NAME}`)})<\/script>`;
|
|
6102
|
+
next = insertBeforeTag(next, "</body>", ` ${script}\n`);
|
|
6103
|
+
}
|
|
6104
|
+
return next;
|
|
6105
|
+
}
|
|
6106
|
+
function generateServiceWorker(base) {
|
|
6107
|
+
return `/* ox-content PWA service worker */
|
|
6108
|
+
const CACHE = "ox-content-pwa-v1";
|
|
6109
|
+
const ASSET_PREFIX = ${JSON.stringify(`${base}assets/`)};
|
|
6110
|
+
|
|
6111
|
+
self.addEventListener("install", (event) => {
|
|
6112
|
+
event.waitUntil(self.skipWaiting());
|
|
6113
|
+
});
|
|
6114
|
+
|
|
6115
|
+
self.addEventListener("activate", (event) => {
|
|
6116
|
+
event.waitUntil(self.clients.claim());
|
|
6117
|
+
});
|
|
6118
|
+
|
|
6119
|
+
self.addEventListener("fetch", (event) => {
|
|
6120
|
+
const request = event.request;
|
|
6121
|
+
if (request.method !== "GET") return;
|
|
6122
|
+
const url = new URL(request.url);
|
|
6123
|
+
if (url.origin !== self.location.origin) return;
|
|
6124
|
+
|
|
6125
|
+
if (isHashedAsset(url.pathname)) {
|
|
6126
|
+
event.respondWith(cacheFirst(request));
|
|
6127
|
+
return;
|
|
6128
|
+
}
|
|
6129
|
+
|
|
6130
|
+
if (isHtmlPage(request)) {
|
|
6131
|
+
event.respondWith(networkFirst(request));
|
|
6132
|
+
}
|
|
6133
|
+
});
|
|
6134
|
+
|
|
6135
|
+
function isHashedAsset(pathname) {
|
|
6136
|
+
if (!pathname.startsWith(ASSET_PREFIX)) return false;
|
|
6137
|
+
return /-[0-9a-f]{8,}\\.[a-z0-9]+$/i.test(pathname);
|
|
6138
|
+
}
|
|
6139
|
+
|
|
6140
|
+
function isHtmlPage(request) {
|
|
6141
|
+
if (request.mode === "navigate") return true;
|
|
6142
|
+
const accept = request.headers.get("accept") || "";
|
|
6143
|
+
return accept.includes("text/html");
|
|
6144
|
+
}
|
|
6145
|
+
|
|
6146
|
+
async function cacheFirst(request) {
|
|
6147
|
+
const cache = await caches.open(CACHE);
|
|
6148
|
+
const cached = await cache.match(request);
|
|
6149
|
+
if (cached) return cached;
|
|
6150
|
+
const response = await fetch(request);
|
|
6151
|
+
if (response.ok) cache.put(request, response.clone());
|
|
6152
|
+
return response;
|
|
6153
|
+
}
|
|
6154
|
+
|
|
6155
|
+
async function networkFirst(request) {
|
|
6156
|
+
const cache = await caches.open(CACHE);
|
|
6157
|
+
try {
|
|
6158
|
+
const response = await fetch(request);
|
|
6159
|
+
if (response.ok) cache.put(request, response.clone());
|
|
6160
|
+
return response;
|
|
6161
|
+
} catch (error) {
|
|
6162
|
+
const cached = await cache.match(request);
|
|
6163
|
+
if (cached) return cached;
|
|
6164
|
+
throw error;
|
|
6165
|
+
}
|
|
6166
|
+
}
|
|
6167
|
+
`;
|
|
6168
|
+
}
|
|
6169
|
+
function hasSiteUrl(siteUrl) {
|
|
6170
|
+
return Boolean(siteUrl && siteUrl.trim());
|
|
6171
|
+
}
|
|
6172
|
+
function normalizeBase(base) {
|
|
6173
|
+
if (!base || base === "/") return "/";
|
|
6174
|
+
return base.endsWith("/") ? base : `${base}/`;
|
|
6175
|
+
}
|
|
6176
|
+
function sanitizeManifestText(value) {
|
|
6177
|
+
return value.split(/\s+/u).filter(Boolean).join(" ");
|
|
6178
|
+
}
|
|
6179
|
+
function sanitizeColor(value, fallback) {
|
|
6180
|
+
if (!value) return fallback;
|
|
6181
|
+
const trimmed = value.trim();
|
|
6182
|
+
if (/^#[0-9A-Fa-f]{3,8}$/.test(trimmed)) return trimmed;
|
|
6183
|
+
if (/^[a-zA-Z][a-zA-Z0-9-]{0,31}$/.test(trimmed)) return trimmed;
|
|
6184
|
+
return fallback;
|
|
6185
|
+
}
|
|
6186
|
+
function sanitizeStartUrl(value, base) {
|
|
6187
|
+
if (!value) return base;
|
|
6188
|
+
const trimmed = value.trim();
|
|
6189
|
+
if (!trimmed.startsWith("/") || trimmed.startsWith("//")) return base;
|
|
6190
|
+
if (/[\n\r\t<>"'`]/.test(trimmed)) return base;
|
|
6191
|
+
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed)) return base;
|
|
6192
|
+
return trimmed;
|
|
6193
|
+
}
|
|
6194
|
+
function isThemedDocument(html) {
|
|
6195
|
+
return /<\/head>/i.test(html) && /<\/body>/i.test(html);
|
|
6196
|
+
}
|
|
6197
|
+
function insertBeforeTag(html, tag, snippet) {
|
|
6198
|
+
const index = html.toLowerCase().lastIndexOf(tag.toLowerCase());
|
|
6199
|
+
if (index === -1) return html;
|
|
6200
|
+
return `${html.slice(0, index)}${snippet}${html.slice(index)}`;
|
|
6201
|
+
}
|
|
6202
|
+
function escapeJsonScript(value) {
|
|
6203
|
+
return value.replace(/[<>]/g, (ch) => ch === "<" ? "\\u003c" : "\\u003e");
|
|
6204
|
+
}
|
|
6205
|
+
function escapeAttribute$1(value) {
|
|
6206
|
+
return value.replace(/[&<>"']/g, (ch) => {
|
|
6207
|
+
switch (ch) {
|
|
6208
|
+
case "&": return "&";
|
|
6209
|
+
case "<": return "<";
|
|
6210
|
+
case ">": return ">";
|
|
6211
|
+
case "\"": return """;
|
|
6212
|
+
default: return "'";
|
|
6213
|
+
}
|
|
6214
|
+
});
|
|
6215
|
+
}
|
|
6216
|
+
//#endregion
|
|
6217
|
+
//#region src/taxonomies-html.ts
|
|
6218
|
+
/**
|
|
6219
|
+
* Escaped taxonomy HTML and confined output paths.
|
|
6220
|
+
*/
|
|
6221
|
+
function relatedMarkup(pages) {
|
|
6222
|
+
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>`;
|
|
6223
|
+
}
|
|
6224
|
+
function listPageContent(terms, base, urlName) {
|
|
6225
|
+
const items = terms.map((term) => listItem$1(siteHref$3(base, urlName, term.slug), term.label)).join("");
|
|
6226
|
+
return `<h1>${escapeHtml$2(displayTaxonomyName(urlName))}</h1><ul class="ox-taxonomy">${items}</ul>`;
|
|
6227
|
+
}
|
|
6228
|
+
function termPageContent(term) {
|
|
5440
6229
|
const items = [...term.pages].sort((left, right) => {
|
|
5441
6230
|
const titleCmp = left.title.localeCompare(right.title);
|
|
5442
6231
|
return titleCmp !== 0 ? titleCmp : left.routePaths.href.localeCompare(right.routePaths.href);
|
|
5443
|
-
}).map((page) => listItem(page.routePaths.href, page.title)).join("");
|
|
5444
|
-
return `<h1>${escapeHtml$
|
|
6232
|
+
}).map((page) => listItem$1(page.routePaths.href, page.title)).join("");
|
|
6233
|
+
return `<h1>${escapeHtml$2(term.label)}</h1><ul class="ox-taxonomy-term">${items}</ul>`;
|
|
5445
6234
|
}
|
|
5446
6235
|
function displayTaxonomyName(name) {
|
|
5447
6236
|
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
5448
6237
|
}
|
|
5449
|
-
function siteHref$
|
|
6238
|
+
function siteHref$3(base, ...segments) {
|
|
5450
6239
|
const prefix = !base || base === "/" ? "/" : base.endsWith("/") ? base : `${base}/`;
|
|
5451
6240
|
const rest = segments.filter(Boolean).join("/");
|
|
5452
6241
|
return rest ? `${prefix}${rest}/` : prefix;
|
|
5453
6242
|
}
|
|
5454
|
-
function containedPath(outDir, ...segments) {
|
|
6243
|
+
function containedPath$2(outDir, ...segments) {
|
|
5455
6244
|
const root = path$1.resolve(outDir);
|
|
5456
6245
|
const resolved = path$1.resolve(root, ...segments);
|
|
5457
6246
|
const prefix = root.endsWith(path$1.sep) ? root : `${root}${path$1.sep}`;
|
|
5458
6247
|
if (resolved === root || !resolved.startsWith(prefix)) return;
|
|
5459
6248
|
return resolved;
|
|
5460
6249
|
}
|
|
5461
|
-
function listItem(href, label) {
|
|
5462
|
-
return `<li><a href="${escapeHtml$
|
|
6250
|
+
function listItem$1(href, label) {
|
|
6251
|
+
return `<li><a href="${escapeHtml$2(href)}">${escapeHtml$2(label)}</a></li>`;
|
|
5463
6252
|
}
|
|
5464
|
-
function escapeHtml$
|
|
6253
|
+
function escapeHtml$2(value) {
|
|
5465
6254
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
5466
6255
|
}
|
|
5467
6256
|
//#endregion
|
|
@@ -5474,7 +6263,7 @@ function escapeHtml$1(value) {
|
|
|
5474
6263
|
*/
|
|
5475
6264
|
const DEFAULT_TAXONOMIES = ["tags", "categories"];
|
|
5476
6265
|
const DEFAULT_RELATED_LIMIT = 5;
|
|
5477
|
-
const HOSTILE_TERM = /^(?:javascript|data):/i;
|
|
6266
|
+
const HOSTILE_TERM$1 = /^(?:javascript|data):/i;
|
|
5478
6267
|
/**
|
|
5479
6268
|
* Resolves `taxonomies` with defaults.
|
|
5480
6269
|
*
|
|
@@ -5505,7 +6294,7 @@ function resolveTaxonomiesOptions(value) {
|
|
|
5505
6294
|
*/
|
|
5506
6295
|
function termSlug(term) {
|
|
5507
6296
|
const trimmed = term.trim();
|
|
5508
|
-
if (!trimmed || HOSTILE_TERM.test(trimmed) || trimmed.includes("..") || trimmed.includes("//")) return;
|
|
6297
|
+
if (!trimmed || HOSTILE_TERM$1.test(trimmed) || trimmed.includes("..") || trimmed.includes("//")) return;
|
|
5509
6298
|
return trimmed.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || void 0;
|
|
5510
6299
|
}
|
|
5511
6300
|
/** Appends related-page HTML to source pages that share a listed term. */
|
|
@@ -5563,8 +6352,8 @@ function taxonomyPageSpecs(listed, options, outDir, base) {
|
|
|
5563
6352
|
for (const taxonomy of options.taxonomies) {
|
|
5564
6353
|
const urlName = taxonomy.toLowerCase();
|
|
5565
6354
|
const terms = collectTerms(listed, taxonomy);
|
|
5566
|
-
const listHref = siteHref$
|
|
5567
|
-
const listOutput = containedPath(outDir, urlName, "index.html");
|
|
6355
|
+
const listHref = siteHref$3(base, urlName);
|
|
6356
|
+
const listOutput = containedPath$2(outDir, urlName, "index.html");
|
|
5568
6357
|
if (listOutput) pages.push({
|
|
5569
6358
|
title: displayTaxonomyName(urlName),
|
|
5570
6359
|
content: listPageContent(terms, base, urlName),
|
|
@@ -5573,14 +6362,14 @@ function taxonomyPageSpecs(listed, options, outDir, base) {
|
|
|
5573
6362
|
href: listHref
|
|
5574
6363
|
});
|
|
5575
6364
|
for (const term of terms) {
|
|
5576
|
-
const outputPath = containedPath(outDir, urlName, term.slug, "index.html");
|
|
6365
|
+
const outputPath = containedPath$2(outDir, urlName, term.slug, "index.html");
|
|
5577
6366
|
if (!outputPath) continue;
|
|
5578
6367
|
pages.push({
|
|
5579
6368
|
title: term.label,
|
|
5580
6369
|
content: termPageContent(term),
|
|
5581
6370
|
outputPath,
|
|
5582
6371
|
urlPath: `${urlName}/${term.slug}`,
|
|
5583
|
-
href: siteHref$
|
|
6372
|
+
href: siteHref$3(base, urlName, term.slug)
|
|
5584
6373
|
});
|
|
5585
6374
|
}
|
|
5586
6375
|
}
|
|
@@ -5588,7 +6377,7 @@ function taxonomyPageSpecs(listed, options, outDir, base) {
|
|
|
5588
6377
|
}
|
|
5589
6378
|
function collectTerms(listed, taxonomy) {
|
|
5590
6379
|
const buckets = /* @__PURE__ */ new Map();
|
|
5591
|
-
for (const page of listed) for (const label of termsFromValue(page.frontmatter[taxonomy])) {
|
|
6380
|
+
for (const page of listed) for (const label of termsFromValue$1(page.frontmatter[taxonomy])) {
|
|
5592
6381
|
const slug = termSlug(label);
|
|
5593
6382
|
if (!slug) continue;
|
|
5594
6383
|
const existing = buckets.get(slug);
|
|
@@ -5603,13 +6392,13 @@ function collectTerms(listed, taxonomy) {
|
|
|
5603
6392
|
}
|
|
5604
6393
|
function pageTermKeys(page, taxonomies) {
|
|
5605
6394
|
const keys = /* @__PURE__ */ new Set();
|
|
5606
|
-
for (const taxonomy of taxonomies) for (const label of termsFromValue(page.frontmatter[taxonomy])) {
|
|
6395
|
+
for (const taxonomy of taxonomies) for (const label of termsFromValue$1(page.frontmatter[taxonomy])) {
|
|
5607
6396
|
const slug = termSlug(label);
|
|
5608
6397
|
if (slug) keys.add(`${taxonomy.toLowerCase()}\0${slug}`);
|
|
5609
6398
|
}
|
|
5610
6399
|
return keys;
|
|
5611
6400
|
}
|
|
5612
|
-
function termsFromValue(value) {
|
|
6401
|
+
function termsFromValue$1(value) {
|
|
5613
6402
|
if (typeof value === "string") return value.trim() ? [value.trim()] : [];
|
|
5614
6403
|
if (!Array.isArray(value)) return [];
|
|
5615
6404
|
return value.flatMap((item) => typeof item === "string" && item.trim() ? [item.trim()] : []);
|
|
@@ -5633,55 +6422,840 @@ function normalizeRelatedLimit(value) {
|
|
|
5633
6422
|
if (typeof value === "number" && Number.isFinite(value) && value >= 0) return Math.floor(value);
|
|
5634
6423
|
return DEFAULT_RELATED_LIMIT;
|
|
5635
6424
|
}
|
|
5636
|
-
function samePage(left, right) {
|
|
5637
|
-
if (left.inputPath && right.inputPath) return left.inputPath === right.inputPath;
|
|
5638
|
-
return left.routePaths.href === right.routePaths.href;
|
|
6425
|
+
function samePage(left, right) {
|
|
6426
|
+
if (left.inputPath && right.inputPath) return left.inputPath === right.inputPath;
|
|
6427
|
+
return left.routePaths.href === right.routePaths.href;
|
|
6428
|
+
}
|
|
6429
|
+
function sharedCount(left, right) {
|
|
6430
|
+
let count = 0;
|
|
6431
|
+
for (const key of left) if (right.has(key)) count += 1;
|
|
6432
|
+
return count;
|
|
6433
|
+
}
|
|
6434
|
+
//#endregion
|
|
6435
|
+
//#region src/team.ts
|
|
6436
|
+
/**
|
|
6437
|
+
* Resolves `ssg.team` with defaults.
|
|
6438
|
+
*
|
|
6439
|
+
* `false` / omitted stays off. `true` enables an empty member list.
|
|
6440
|
+
* An object enables the feature and keeps the members the site set.
|
|
6441
|
+
*/
|
|
6442
|
+
function resolveTeamOptions(value) {
|
|
6443
|
+
if (!value) return {
|
|
6444
|
+
enabled: false,
|
|
6445
|
+
members: []
|
|
6446
|
+
};
|
|
6447
|
+
if (value === true) return {
|
|
6448
|
+
enabled: true,
|
|
6449
|
+
members: []
|
|
6450
|
+
};
|
|
6451
|
+
return {
|
|
6452
|
+
enabled: true,
|
|
6453
|
+
members: normalizeMembers(value.members)
|
|
6454
|
+
};
|
|
6455
|
+
}
|
|
6456
|
+
function normalizeMembers(members) {
|
|
6457
|
+
if (!Array.isArray(members)) return [];
|
|
6458
|
+
return members.flatMap((member) => {
|
|
6459
|
+
if (!member || typeof member.name !== "string") return [];
|
|
6460
|
+
const links = Array.isArray(member.links) ? member.links.flatMap((link) => {
|
|
6461
|
+
if (!link || typeof link.label !== "string" || typeof link.href !== "string") return [];
|
|
6462
|
+
return [{
|
|
6463
|
+
label: link.label,
|
|
6464
|
+
href: link.href
|
|
6465
|
+
}];
|
|
6466
|
+
}) : void 0;
|
|
6467
|
+
return [{
|
|
6468
|
+
name: member.name,
|
|
6469
|
+
role: typeof member.role === "string" ? member.role : void 0,
|
|
6470
|
+
avatar: typeof member.avatar === "string" ? member.avatar : void 0,
|
|
6471
|
+
links
|
|
6472
|
+
}];
|
|
6473
|
+
});
|
|
6474
|
+
}
|
|
6475
|
+
//#endregion
|
|
6476
|
+
//#region src/contributors.ts
|
|
6477
|
+
/**
|
|
6478
|
+
* Opt-in git contributor list helpers.
|
|
6479
|
+
*
|
|
6480
|
+
* Resolution and ignore filtering live here. `git log` is read in Rust
|
|
6481
|
+
* (`getGitContributors`) and names are rendered from `PageData.contributors`.
|
|
6482
|
+
*/
|
|
6483
|
+
/**
|
|
6484
|
+
* Resolves `ssg.contributors` with defaults.
|
|
6485
|
+
*
|
|
6486
|
+
* `false` / omitted stays off. `true` enables names only.
|
|
6487
|
+
* An object enables the feature and keeps `ignore` / `avatars`.
|
|
6488
|
+
*/
|
|
6489
|
+
function resolveContributorsOption(value) {
|
|
6490
|
+
if (!value) return false;
|
|
6491
|
+
if (value === true) return {
|
|
6492
|
+
ignore: [],
|
|
6493
|
+
avatars: false
|
|
6494
|
+
};
|
|
6495
|
+
return {
|
|
6496
|
+
ignore: Array.isArray(value.ignore) ? value.ignore.filter((entry) => typeof entry === "string") : [],
|
|
6497
|
+
avatars: value.avatars === true
|
|
6498
|
+
};
|
|
6499
|
+
}
|
|
6500
|
+
function filterGitContributors(contributors, ignore) {
|
|
6501
|
+
if (ignore.length === 0) return contributors.filter((contributor) => contributor.name.trim());
|
|
6502
|
+
const needles = new Set(ignore.map((entry) => entry.toLowerCase()));
|
|
6503
|
+
return contributors.filter((contributor) => {
|
|
6504
|
+
const name = contributor.name.trim();
|
|
6505
|
+
if (!name) return false;
|
|
6506
|
+
if (needles.has(name.toLowerCase())) return false;
|
|
6507
|
+
const email = contributor.email?.trim().toLowerCase();
|
|
6508
|
+
return !email || !needles.has(email);
|
|
6509
|
+
});
|
|
6510
|
+
}
|
|
6511
|
+
function gravatarAvatar(email) {
|
|
6512
|
+
return `https://www.gravatar.com/avatar/${createHash("md5").update(email.trim().toLowerCase()).digest("hex")}?d=mp&s=40`;
|
|
6513
|
+
}
|
|
6514
|
+
function applyContributorOptions(raw, option) {
|
|
6515
|
+
return filterGitContributors(raw, option.ignore).map((contributor) => ({
|
|
6516
|
+
name: contributor.name.trim(),
|
|
6517
|
+
avatar: option.avatars && contributor.email?.trim() ? gravatarAvatar(contributor.email) : void 0
|
|
6518
|
+
}));
|
|
6519
|
+
}
|
|
6520
|
+
//#endregion
|
|
6521
|
+
//#region src/blog-options.ts
|
|
6522
|
+
const DEFAULT_PAGE_SIZE = 10;
|
|
6523
|
+
function resolveBlogOptions(value) {
|
|
6524
|
+
if (!value) return {
|
|
6525
|
+
enabled: false,
|
|
6526
|
+
authors: {},
|
|
6527
|
+
pageSize: DEFAULT_PAGE_SIZE
|
|
6528
|
+
};
|
|
6529
|
+
if (value === true) return {
|
|
6530
|
+
enabled: true,
|
|
6531
|
+
authors: {},
|
|
6532
|
+
pageSize: DEFAULT_PAGE_SIZE
|
|
6533
|
+
};
|
|
6534
|
+
return {
|
|
6535
|
+
enabled: true,
|
|
6536
|
+
collection: value.collection,
|
|
6537
|
+
authors: normalizeAuthors(value.authors),
|
|
6538
|
+
pageSize: normalizePageSize(value.pageSize)
|
|
6539
|
+
};
|
|
6540
|
+
}
|
|
6541
|
+
/**
|
|
6542
|
+
* Picks a collection named `blog`, else the only configured collection.
|
|
6543
|
+
*
|
|
6544
|
+
* An explicit name always wins. Several collections and no `blog` name
|
|
6545
|
+
* require `blog.collection`.
|
|
6546
|
+
*/
|
|
6547
|
+
function resolveBlogCollectionName(requested, collectionNames) {
|
|
6548
|
+
if (requested) return requested;
|
|
6549
|
+
if (collectionNames.includes("blog")) return "blog";
|
|
6550
|
+
if (collectionNames.length === 1) return collectionNames[0];
|
|
6551
|
+
}
|
|
6552
|
+
function normalizeAuthors(authors) {
|
|
6553
|
+
if (!authors || typeof authors !== "object") return {};
|
|
6554
|
+
const resolved = {};
|
|
6555
|
+
for (const [key, value] of Object.entries(authors)) {
|
|
6556
|
+
if (!value || typeof value.name !== "string") continue;
|
|
6557
|
+
resolved[key] = {
|
|
6558
|
+
name: value.name,
|
|
6559
|
+
bio: typeof value.bio === "string" ? value.bio : void 0,
|
|
6560
|
+
url: typeof value.url === "string" ? value.url : void 0
|
|
6561
|
+
};
|
|
6562
|
+
}
|
|
6563
|
+
return resolved;
|
|
6564
|
+
}
|
|
6565
|
+
function normalizePageSize(value) {
|
|
6566
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 1) return Math.floor(value);
|
|
6567
|
+
return DEFAULT_PAGE_SIZE;
|
|
6568
|
+
}
|
|
6569
|
+
//#endregion
|
|
6570
|
+
//#region src/blog-reading.ts
|
|
6571
|
+
/**
|
|
6572
|
+
* Deterministic blog reading-time estimates.
|
|
6573
|
+
*/
|
|
6574
|
+
const LATIN_WORDS_PER_MINUTE = 200;
|
|
6575
|
+
const CJK_CHARS_PER_MINUTE = 500;
|
|
6576
|
+
function readingTimeMinutes(markdown) {
|
|
6577
|
+
const body = stripInlineCode(stripFences(stripFrontmatter(markdown)));
|
|
6578
|
+
let latin = 0;
|
|
6579
|
+
let cjk = 0;
|
|
6580
|
+
let latinRun = false;
|
|
6581
|
+
for (const char of body) {
|
|
6582
|
+
const code = char.codePointAt(0) ?? 0;
|
|
6583
|
+
if (isCjkCodePoint(code)) {
|
|
6584
|
+
cjk += 1;
|
|
6585
|
+
latinRun = false;
|
|
6586
|
+
continue;
|
|
6587
|
+
}
|
|
6588
|
+
if (isLatinWordChar(code)) {
|
|
6589
|
+
if (!latinRun) {
|
|
6590
|
+
latin += 1;
|
|
6591
|
+
latinRun = true;
|
|
6592
|
+
}
|
|
6593
|
+
continue;
|
|
6594
|
+
}
|
|
6595
|
+
if (char === "'" || char === "’") continue;
|
|
6596
|
+
latinRun = false;
|
|
6597
|
+
}
|
|
6598
|
+
if (latin === 0 && cjk === 0) return 0;
|
|
6599
|
+
return Math.max(1, Math.ceil(latin / LATIN_WORDS_PER_MINUTE + cjk / CJK_CHARS_PER_MINUTE));
|
|
6600
|
+
}
|
|
6601
|
+
function stripFrontmatter(markdown) {
|
|
6602
|
+
if (!markdown.startsWith("---")) return markdown;
|
|
6603
|
+
const match = markdown.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
|
|
6604
|
+
return match ? markdown.slice(match[0].length) : markdown;
|
|
6605
|
+
}
|
|
6606
|
+
function stripFences(text) {
|
|
6607
|
+
return text.replace(/```[\s\S]*?(?:```|$)/g, " ");
|
|
6608
|
+
}
|
|
6609
|
+
function stripInlineCode(text) {
|
|
6610
|
+
return text.replace(/`[^`\n]*`/g, " ");
|
|
6611
|
+
}
|
|
6612
|
+
function isCjkCodePoint(code) {
|
|
6613
|
+
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;
|
|
6614
|
+
}
|
|
6615
|
+
function isLatinWordChar(code) {
|
|
6616
|
+
return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122;
|
|
6617
|
+
}
|
|
6618
|
+
//#endregion
|
|
6619
|
+
//#region src/blog-html.ts
|
|
6620
|
+
/**
|
|
6621
|
+
* Escaped blog index, tag, archive, and post-meta HTML.
|
|
6622
|
+
*/
|
|
6623
|
+
/** `https:` or a same-origin path starting with `/` but not `//`. */
|
|
6624
|
+
function isSafeBlogUrl(value) {
|
|
6625
|
+
const trimmed = value.trim();
|
|
6626
|
+
if (trimmed.length === 0 || trimmed.split("").some((ch) => ch === "\n" || ch === "\r" || ch === "\0" || ch === " ")) return false;
|
|
6627
|
+
if (trimmed.startsWith("//")) return false;
|
|
6628
|
+
if (trimmed.startsWith("/")) return true;
|
|
6629
|
+
return trimmed.toLowerCase().startsWith("https:");
|
|
6630
|
+
}
|
|
6631
|
+
function postMetaMarkup(meta) {
|
|
6632
|
+
const parts = [`<p class="ox-blog-meta__reading-time">${escapeHtml$1(String(meta.minutes))} min read</p>`];
|
|
6633
|
+
if (meta.authors.length > 0) {
|
|
6634
|
+
const items = meta.authors.map((author) => authorMarkup(author)).join("");
|
|
6635
|
+
parts.push(`<ul class="ox-blog-meta__authors">${items}</ul>`);
|
|
6636
|
+
}
|
|
6637
|
+
if (meta.tags.length > 0) {
|
|
6638
|
+
const items = meta.tags.map((tag) => `<li><a href="${escapeHtml$1(tag.href)}">${escapeHtml$1(tag.label)}</a></li>`).join("");
|
|
6639
|
+
parts.push(`<ul class="ox-blog-meta__tags">${items}</ul>`);
|
|
6640
|
+
}
|
|
6641
|
+
return `<aside class="ox-blog-meta">${parts.join("")}</aside>\n`;
|
|
6642
|
+
}
|
|
6643
|
+
function indexPageContent(items, pager) {
|
|
6644
|
+
const list = items.map((item) => listItem(item)).join("");
|
|
6645
|
+
const links = [];
|
|
6646
|
+
if (pager.newerHref) links.push(`<a href="${escapeHtml$1(pager.newerHref)}" rel="prev">Newer</a>`);
|
|
6647
|
+
if (pager.olderHref) links.push(`<a href="${escapeHtml$1(pager.olderHref)}" rel="next">Older</a>`);
|
|
6648
|
+
return `<h1>Blog</h1><ul class="ox-blog">${list}</ul>${links.length > 0 ? `<nav class="ox-blog-pager">${links.join("")}</nav>` : ""}`;
|
|
6649
|
+
}
|
|
6650
|
+
function tagPageContent(label, items) {
|
|
6651
|
+
const list = items.map((item) => listItem(item)).join("");
|
|
6652
|
+
return `<h1>${escapeHtml$1(label)}</h1><ul class="ox-blog-tag">${list}</ul>`;
|
|
6653
|
+
}
|
|
6654
|
+
function archiveIndexContent(years) {
|
|
6655
|
+
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>`;
|
|
6656
|
+
}
|
|
6657
|
+
function archiveYearContent(year, months, items) {
|
|
6658
|
+
const monthList = months.map((entry) => `<li><a href="${escapeHtml$1(entry.href)}">${escapeHtml$1(entry.month)}</a></li>`).join("");
|
|
6659
|
+
const posts = items.map((item) => listItem(item)).join("");
|
|
6660
|
+
return `<h1>${escapeHtml$1(year)}</h1><ul class="ox-blog-archive-months">${monthList}</ul><ul class="ox-blog">${posts}</ul>`;
|
|
6661
|
+
}
|
|
6662
|
+
function archiveMonthContent(label, items) {
|
|
6663
|
+
const list = items.map((item) => listItem(item)).join("");
|
|
6664
|
+
return `<h1>${escapeHtml$1(label)}</h1><ul class="ox-blog">${list}</ul>`;
|
|
6665
|
+
}
|
|
6666
|
+
function siteHref$2(base, ...segments) {
|
|
6667
|
+
const prefix = !base || base === "/" ? "/" : base.endsWith("/") ? base : `${base}/`;
|
|
6668
|
+
const rest = segments.filter(Boolean).join("/");
|
|
6669
|
+
return rest ? `${prefix}${rest}/` : prefix;
|
|
6670
|
+
}
|
|
6671
|
+
function containedPath$1(outDir, ...segments) {
|
|
6672
|
+
const root = path$1.resolve(outDir);
|
|
6673
|
+
const resolved = path$1.resolve(root, ...segments);
|
|
6674
|
+
const prefix = root.endsWith(path$1.sep) ? root : `${root}${path$1.sep}`;
|
|
6675
|
+
if (resolved === root || !resolved.startsWith(prefix)) return;
|
|
6676
|
+
return resolved;
|
|
6677
|
+
}
|
|
6678
|
+
function escapeHtml$1(value) {
|
|
6679
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
6680
|
+
}
|
|
6681
|
+
function authorMarkup(author) {
|
|
6682
|
+
const name = escapeHtml$1(author.name);
|
|
6683
|
+
const url = author.url?.trim();
|
|
6684
|
+
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>`;
|
|
6685
|
+
}
|
|
6686
|
+
function listItem(item) {
|
|
6687
|
+
const time = item.dateLabel ? ` <time datetime="${escapeHtml$1(item.dateLabel)}">${escapeHtml$1(item.dateLabel)}</time>` : "";
|
|
6688
|
+
return `<li><a href="${escapeHtml$1(item.href)}">${escapeHtml$1(item.title)}</a>${time}</li>`;
|
|
6689
|
+
}
|
|
6690
|
+
//#endregion
|
|
6691
|
+
//#region src/blog-posts.ts
|
|
6692
|
+
/**
|
|
6693
|
+
* Blog post selection, tags, authors, and dates.
|
|
6694
|
+
*/
|
|
6695
|
+
const HOSTILE_TERM = /^(?:javascript|data):/i;
|
|
6696
|
+
function selectBlogPosts(listed, options, srcDir, collections) {
|
|
6697
|
+
if (isAmbiguousCollection(options, collections)) return;
|
|
6698
|
+
const names = collectionNames(collections);
|
|
6699
|
+
const name = resolveBlogCollectionName(options.collection, names);
|
|
6700
|
+
const sources = name && collections?.enabled ? collections.collections[name]?.source : void 0;
|
|
6701
|
+
return listed.filter((page) => {
|
|
6702
|
+
if (isExcludedPost(page.frontmatter)) return false;
|
|
6703
|
+
if (!sources) return true;
|
|
6704
|
+
return pageMatchesSources(page.inputPath, srcDir, sources);
|
|
6705
|
+
});
|
|
6706
|
+
}
|
|
6707
|
+
function isAmbiguousCollection(options, collections) {
|
|
6708
|
+
if (options.collection) return false;
|
|
6709
|
+
const names = collectionNames(collections);
|
|
6710
|
+
return names.length > 1 && !names.includes("blog");
|
|
6711
|
+
}
|
|
6712
|
+
function collectionNames(collections) {
|
|
6713
|
+
if (!collections?.enabled) return [];
|
|
6714
|
+
return Object.keys(collections.collections);
|
|
6715
|
+
}
|
|
6716
|
+
function pageMatchesSources(inputPath, srcDir, sources) {
|
|
6717
|
+
const relative = path$1.relative(srcDir, inputPath).split(path$1.sep).join("/");
|
|
6718
|
+
return sources.some((source) => matchGlob(relative, source));
|
|
6719
|
+
}
|
|
6720
|
+
function matchGlob(relative, pattern) {
|
|
6721
|
+
const normalized = pattern.replace(/^\/+/, "");
|
|
6722
|
+
let out = "^";
|
|
6723
|
+
for (let i = 0; i < normalized.length; i += 1) {
|
|
6724
|
+
if (normalized.startsWith("**/", i)) {
|
|
6725
|
+
out += "(?:.*/)?";
|
|
6726
|
+
i += 2;
|
|
6727
|
+
continue;
|
|
6728
|
+
}
|
|
6729
|
+
const ch = normalized[i] ?? "";
|
|
6730
|
+
if (ch === "*") {
|
|
6731
|
+
out += "[^/]*";
|
|
6732
|
+
continue;
|
|
6733
|
+
}
|
|
6734
|
+
if (ch === "?") {
|
|
6735
|
+
out += "[^/]";
|
|
6736
|
+
continue;
|
|
6737
|
+
}
|
|
6738
|
+
if (/[.+^${}()|[\]\\]/.test(ch)) {
|
|
6739
|
+
out += `\\${ch}`;
|
|
6740
|
+
continue;
|
|
6741
|
+
}
|
|
6742
|
+
out += ch;
|
|
6743
|
+
}
|
|
6744
|
+
out += "$";
|
|
6745
|
+
return new RegExp(out).test(relative);
|
|
6746
|
+
}
|
|
6747
|
+
function sortPosts(posts) {
|
|
6748
|
+
return [...posts].sort((left, right) => {
|
|
6749
|
+
const dateCmp = (pageUnix(right.frontmatter) ?? Number.NEGATIVE_INFINITY) - (pageUnix(left.frontmatter) ?? Number.NEGATIVE_INFINITY);
|
|
6750
|
+
if (dateCmp !== 0) return dateCmp;
|
|
6751
|
+
return left.routePaths.href < right.routePaths.href ? -1 : left.routePaths.href > right.routePaths.href ? 1 : 0;
|
|
6752
|
+
});
|
|
6753
|
+
}
|
|
6754
|
+
function collectTags(posts) {
|
|
6755
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
6756
|
+
for (const page of posts) for (const label of termsFromValue(page.frontmatter.tags)) {
|
|
6757
|
+
const slug = tagSlug(label);
|
|
6758
|
+
if (!slug) continue;
|
|
6759
|
+
const existing = buckets.get(slug);
|
|
6760
|
+
if (existing) existing.pages.push(page);
|
|
6761
|
+
else buckets.set(slug, {
|
|
6762
|
+
label,
|
|
6763
|
+
slug,
|
|
6764
|
+
pages: [page]
|
|
6765
|
+
});
|
|
6766
|
+
}
|
|
6767
|
+
return [...buckets.values()].sort((left, right) => left.label.localeCompare(right.label));
|
|
6768
|
+
}
|
|
6769
|
+
function datedPosts(posts) {
|
|
6770
|
+
const dated = [];
|
|
6771
|
+
for (const page of posts) {
|
|
6772
|
+
const parsed = pageDate(page.frontmatter);
|
|
6773
|
+
if (!parsed) continue;
|
|
6774
|
+
dated.push({
|
|
6775
|
+
page,
|
|
6776
|
+
year: String(parsed.year).padStart(4, "0"),
|
|
6777
|
+
month: String(parsed.month).padStart(2, "0"),
|
|
6778
|
+
label: `${String(parsed.year).padStart(4, "0")}-${String(parsed.month).padStart(2, "0")}-${String(parsed.day).padStart(2, "0")}`
|
|
6779
|
+
});
|
|
6780
|
+
}
|
|
6781
|
+
return dated;
|
|
6782
|
+
}
|
|
6783
|
+
function uniqueYears(dated) {
|
|
6784
|
+
return [...new Set(dated.map((entry) => entry.year))].sort((left, right) => right.localeCompare(left));
|
|
6785
|
+
}
|
|
6786
|
+
function uniqueMonths(dated) {
|
|
6787
|
+
return [...new Set(dated.map((entry) => entry.month))].sort((left, right) => left.localeCompare(right));
|
|
6788
|
+
}
|
|
6789
|
+
function toListItem(page) {
|
|
6790
|
+
const parsed = pageDate(page.frontmatter);
|
|
6791
|
+
return {
|
|
6792
|
+
title: page.title,
|
|
6793
|
+
href: page.routePaths.href,
|
|
6794
|
+
dateLabel: parsed ? `${String(parsed.year).padStart(4, "0")}-${String(parsed.month).padStart(2, "0")}-${String(parsed.day).padStart(2, "0")}` : void 0
|
|
6795
|
+
};
|
|
6796
|
+
}
|
|
6797
|
+
function resolvePostAuthors(frontmatter, map) {
|
|
6798
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6799
|
+
const authors = [];
|
|
6800
|
+
for (const key of authorKeys(frontmatter)) {
|
|
6801
|
+
if (seen.has(key)) continue;
|
|
6802
|
+
seen.add(key);
|
|
6803
|
+
authors.push(map[key] ?? { name: key });
|
|
6804
|
+
}
|
|
6805
|
+
return authors;
|
|
6806
|
+
}
|
|
6807
|
+
function authorKeys(frontmatter) {
|
|
6808
|
+
return [...keysFromValue(frontmatter.author), ...keysFromValue(frontmatter.authors)];
|
|
6809
|
+
}
|
|
6810
|
+
function keysFromValue(value) {
|
|
6811
|
+
if (typeof value === "string") return value.trim() ? [value.trim()] : [];
|
|
6812
|
+
if (!Array.isArray(value)) return [];
|
|
6813
|
+
return value.flatMap((item) => typeof item === "string" && item.trim() ? [item.trim()] : []);
|
|
6814
|
+
}
|
|
6815
|
+
function postTagLinks(frontmatter, base) {
|
|
6816
|
+
const links = [];
|
|
6817
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6818
|
+
for (const label of termsFromValue(frontmatter.tags)) {
|
|
6819
|
+
const slug = tagSlug(label);
|
|
6820
|
+
if (!slug || seen.has(slug)) continue;
|
|
6821
|
+
seen.add(slug);
|
|
6822
|
+
links.push({
|
|
6823
|
+
label,
|
|
6824
|
+
href: siteHref$2(base, "blog", "tags", slug)
|
|
6825
|
+
});
|
|
6826
|
+
}
|
|
6827
|
+
return links;
|
|
6828
|
+
}
|
|
6829
|
+
function tagSlug(term) {
|
|
6830
|
+
const trimmed = term.trim();
|
|
6831
|
+
if (!trimmed || HOSTILE_TERM.test(trimmed) || trimmed.includes("..") || trimmed.includes("//")) return;
|
|
6832
|
+
return trimmed.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || void 0;
|
|
6833
|
+
}
|
|
6834
|
+
function termsFromValue(value) {
|
|
6835
|
+
if (typeof value === "string") return value.trim() ? [value.trim()] : [];
|
|
6836
|
+
if (!Array.isArray(value)) return [];
|
|
6837
|
+
return value.flatMap((item) => typeof item === "string" && item.trim() ? [item.trim()] : []);
|
|
6838
|
+
}
|
|
6839
|
+
function isExcludedPost(frontmatter) {
|
|
6840
|
+
return frontmatter.draft === true || frontmatter.unlisted === true;
|
|
6841
|
+
}
|
|
6842
|
+
function pageDate(frontmatter) {
|
|
6843
|
+
return parseDate(dateField(frontmatter.date));
|
|
6844
|
+
}
|
|
6845
|
+
function pageUnix(frontmatter) {
|
|
6846
|
+
return pageDate(frontmatter)?.unix;
|
|
6847
|
+
}
|
|
6848
|
+
function dateField(value) {
|
|
6849
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
6850
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
6851
|
+
if (value instanceof Date && !Number.isNaN(value.getTime())) return value.toISOString();
|
|
6852
|
+
}
|
|
6853
|
+
//#endregion
|
|
6854
|
+
//#region src/blog-pages.ts
|
|
6855
|
+
/**
|
|
6856
|
+
* Generated blog index, tag, and archive pages.
|
|
6857
|
+
*/
|
|
6858
|
+
const AMBIGUOUS_COLLECTION = "[ox-content] blog is enabled but multiple collections are configured; set blog.collection";
|
|
6859
|
+
async function injectBlogPostMeta(input) {
|
|
6860
|
+
if (!input.options?.enabled) return;
|
|
6861
|
+
const posts = selectBlogPosts(input.listed, input.options, input.srcDir, input.collections);
|
|
6862
|
+
if (posts === void 0) return;
|
|
6863
|
+
const listedPaths = new Set(posts.map((page) => page.inputPath));
|
|
6864
|
+
for (const page of input.pages) {
|
|
6865
|
+
if (!listedPaths.has(page.inputPath)) continue;
|
|
6866
|
+
const markdown = await readMarkdown(page.inputPath);
|
|
6867
|
+
page.transformedHtml = postMetaMarkup({
|
|
6868
|
+
authors: resolvePostAuthors(page.frontmatter, input.options.authors),
|
|
6869
|
+
minutes: readingTimeMinutes(markdown),
|
|
6870
|
+
tags: postTagLinks(page.frontmatter, input.base)
|
|
6871
|
+
}) + page.transformedHtml;
|
|
6872
|
+
}
|
|
6873
|
+
}
|
|
6874
|
+
/** Maps a generated blog page onto the SSG render shape. */
|
|
6875
|
+
function toBlogProcessResult(page) {
|
|
6876
|
+
return {
|
|
6877
|
+
inputPath: page.outputPath,
|
|
6878
|
+
routePaths: {
|
|
6879
|
+
outputPath: page.outputPath,
|
|
6880
|
+
urlPath: page.urlPath,
|
|
6881
|
+
href: page.href,
|
|
6882
|
+
ogImagePath: "",
|
|
6883
|
+
ogImageUrl: ""
|
|
6884
|
+
},
|
|
6885
|
+
transformedHtml: page.content,
|
|
6886
|
+
title: page.title,
|
|
6887
|
+
frontmatter: {},
|
|
6888
|
+
toc: []
|
|
6889
|
+
};
|
|
6890
|
+
}
|
|
6891
|
+
/** Renders index, tag, and archive pages and appends them to the build. */
|
|
6892
|
+
async function appendBlogPages(input) {
|
|
6893
|
+
if (!input.options?.enabled) return;
|
|
6894
|
+
if (isAmbiguousCollection(input.options, input.collections)) {
|
|
6895
|
+
input.errors.push(AMBIGUOUS_COLLECTION);
|
|
6896
|
+
return;
|
|
6897
|
+
}
|
|
6898
|
+
const posts = selectBlogPosts(input.listedPages, input.options, input.srcDir, input.collections);
|
|
6899
|
+
if (posts === void 0) return;
|
|
6900
|
+
for (const spec of blogPageSpecs(posts, input.options, input.outDir, input.base)) try {
|
|
6901
|
+
input.generatedPages.push({
|
|
6902
|
+
inputPath: spec.outputPath,
|
|
6903
|
+
outputPath: spec.outputPath,
|
|
6904
|
+
html: await input.render(spec)
|
|
6905
|
+
});
|
|
6906
|
+
} catch (err) {
|
|
6907
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6908
|
+
input.errors.push(`Failed to generate blog page ${spec.href}: ${message}`);
|
|
6909
|
+
}
|
|
6910
|
+
}
|
|
6911
|
+
function blogPageSpecs(posts, options, outDir, base) {
|
|
6912
|
+
const sorted = sortPosts(posts);
|
|
6913
|
+
const pages = [];
|
|
6914
|
+
const pageSize = options.pageSize;
|
|
6915
|
+
const pageCount = Math.max(1, Math.ceil(sorted.length / pageSize) || 1);
|
|
6916
|
+
const totalPages = sorted.length === 0 ? 1 : pageCount;
|
|
6917
|
+
for (let pageNumber = 1; pageNumber <= totalPages; pageNumber += 1) {
|
|
6918
|
+
const slice = sorted.slice((pageNumber - 1) * pageSize, pageNumber * pageSize);
|
|
6919
|
+
const isFirst = pageNumber === 1;
|
|
6920
|
+
const urlPath = isFirst ? "blog" : `blog/page/${pageNumber}`;
|
|
6921
|
+
const outputPath = isFirst ? containedPath$1(outDir, "blog", "index.html") : containedPath$1(outDir, "blog", "page", String(pageNumber), "index.html");
|
|
6922
|
+
if (!outputPath) continue;
|
|
6923
|
+
pages.push({
|
|
6924
|
+
title: isFirst ? "Blog" : `Blog · page ${pageNumber}`,
|
|
6925
|
+
content: indexPageContent(slice.map(toListItem), {
|
|
6926
|
+
newerHref: isFirst ? void 0 : siteHref$2(base, ...pageNumber === 2 ? ["blog"] : [
|
|
6927
|
+
"blog",
|
|
6928
|
+
"page",
|
|
6929
|
+
String(pageNumber - 1)
|
|
6930
|
+
]),
|
|
6931
|
+
olderHref: pageNumber < totalPages ? siteHref$2(base, "blog", "page", String(pageNumber + 1)) : void 0
|
|
6932
|
+
}),
|
|
6933
|
+
outputPath,
|
|
6934
|
+
urlPath,
|
|
6935
|
+
href: siteHref$2(base, ...urlPath.split("/"))
|
|
6936
|
+
});
|
|
6937
|
+
}
|
|
6938
|
+
const tags = collectTags(sorted);
|
|
6939
|
+
for (const tag of tags) {
|
|
6940
|
+
const outputPath = containedPath$1(outDir, "blog", "tags", tag.slug, "index.html");
|
|
6941
|
+
if (!outputPath) continue;
|
|
6942
|
+
pages.push({
|
|
6943
|
+
title: tag.label,
|
|
6944
|
+
content: tagPageContent(tag.label, tag.pages.map(toListItem)),
|
|
6945
|
+
outputPath,
|
|
6946
|
+
urlPath: `blog/tags/${tag.slug}`,
|
|
6947
|
+
href: siteHref$2(base, "blog", "tags", tag.slug)
|
|
6948
|
+
});
|
|
6949
|
+
}
|
|
6950
|
+
const dated = datedPosts(sorted);
|
|
6951
|
+
if (dated.length > 0) {
|
|
6952
|
+
const years = uniqueYears(dated);
|
|
6953
|
+
const archiveIndex = containedPath$1(outDir, "blog", "archive", "index.html");
|
|
6954
|
+
if (archiveIndex) pages.push({
|
|
6955
|
+
title: "Archive",
|
|
6956
|
+
content: archiveIndexContent(years.map((year) => ({
|
|
6957
|
+
year,
|
|
6958
|
+
href: siteHref$2(base, "blog", "archive", year)
|
|
6959
|
+
}))),
|
|
6960
|
+
outputPath: archiveIndex,
|
|
6961
|
+
urlPath: "blog/archive",
|
|
6962
|
+
href: siteHref$2(base, "blog", "archive")
|
|
6963
|
+
});
|
|
6964
|
+
for (const year of years) {
|
|
6965
|
+
const yearPosts = dated.filter((entry) => entry.year === year);
|
|
6966
|
+
const months = uniqueMonths(yearPosts);
|
|
6967
|
+
const yearPath = containedPath$1(outDir, "blog", "archive", year, "index.html");
|
|
6968
|
+
if (yearPath) pages.push({
|
|
6969
|
+
title: year,
|
|
6970
|
+
content: archiveYearContent(year, months.map((month) => ({
|
|
6971
|
+
month: `${year}-${month}`,
|
|
6972
|
+
href: siteHref$2(base, "blog", "archive", year, month)
|
|
6973
|
+
})), yearPosts.map((entry) => toListItem(entry.page))),
|
|
6974
|
+
outputPath: yearPath,
|
|
6975
|
+
urlPath: `blog/archive/${year}`,
|
|
6976
|
+
href: siteHref$2(base, "blog", "archive", year)
|
|
6977
|
+
});
|
|
6978
|
+
for (const month of months) {
|
|
6979
|
+
const monthPosts = yearPosts.filter((entry) => entry.month === month);
|
|
6980
|
+
const monthPath = containedPath$1(outDir, "blog", "archive", year, month, "index.html");
|
|
6981
|
+
if (!monthPath) continue;
|
|
6982
|
+
pages.push({
|
|
6983
|
+
title: `${year}-${month}`,
|
|
6984
|
+
content: archiveMonthContent(`${year}-${month}`, monthPosts.map((entry) => toListItem(entry.page))),
|
|
6985
|
+
outputPath: monthPath,
|
|
6986
|
+
urlPath: `blog/archive/${year}/${month}`,
|
|
6987
|
+
href: siteHref$2(base, "blog", "archive", year, month)
|
|
6988
|
+
});
|
|
6989
|
+
}
|
|
6990
|
+
}
|
|
6991
|
+
}
|
|
6992
|
+
return pages;
|
|
6993
|
+
}
|
|
6994
|
+
async function readMarkdown(inputPath) {
|
|
6995
|
+
try {
|
|
6996
|
+
return await fs$2.readFile(inputPath, "utf8");
|
|
6997
|
+
} catch {
|
|
6998
|
+
return "";
|
|
6999
|
+
}
|
|
7000
|
+
}
|
|
7001
|
+
//#endregion
|
|
7002
|
+
//#region src/section-index-html.ts
|
|
7003
|
+
/**
|
|
7004
|
+
* Section-index listing HTML and href safety.
|
|
7005
|
+
*
|
|
7006
|
+
* Titles are escaped. `javascript:` / `data:` / `vbscript:` / `file:` hrefs
|
|
7007
|
+
* are dropped. The NAPI helper is preferred when present.
|
|
7008
|
+
*/
|
|
7009
|
+
const HOSTILE_SCHEME = /^(?:javascript|data|vbscript|file):/i;
|
|
7010
|
+
/** `https:`-free, same-origin or relative href. `javascript:` is rejected. */
|
|
7011
|
+
function isSafeSectionHref(value) {
|
|
7012
|
+
const trimmed = value.trim();
|
|
7013
|
+
if (!trimmed || /[\n\r\0\t]/.test(trimmed) || trimmed.startsWith("//")) return false;
|
|
7014
|
+
if (trimmed.startsWith("/")) return true;
|
|
7015
|
+
if (trimmed.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/)) return false;
|
|
7016
|
+
return !HOSTILE_SCHEME.test(trimmed);
|
|
7017
|
+
}
|
|
7018
|
+
/** Escapes text and attribute values in generated listing markup. */
|
|
7019
|
+
function escapeSectionIndexHtml(value) {
|
|
7020
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
7021
|
+
}
|
|
7022
|
+
/** Renders the listing body. Titles are escaped; hostile hrefs are dropped. */
|
|
7023
|
+
function renderSectionIndexHtml(title, items, style) {
|
|
7024
|
+
try {
|
|
7025
|
+
const napi = importNapiModuleSync();
|
|
7026
|
+
if (typeof napi.renderSsgSectionIndex === "function") return napi.renderSsgSectionIndex(title, items.map((item) => ({
|
|
7027
|
+
title: item.title,
|
|
7028
|
+
href: item.href,
|
|
7029
|
+
description: item.description
|
|
7030
|
+
})), style);
|
|
7031
|
+
} catch {}
|
|
7032
|
+
return renderSectionIndexHtmlLocal(title, items, style);
|
|
7033
|
+
}
|
|
7034
|
+
function renderSectionIndexHtmlLocal(title, items, style) {
|
|
7035
|
+
const safe = items.filter((item) => isSafeSectionHref(item.href));
|
|
7036
|
+
const modifier = style === "list" ? "list" : "cards";
|
|
7037
|
+
const listClass = style === "list" ? "ox-section-index__list" : "ox-section-index__cards";
|
|
7038
|
+
const body = safe.map((item) => renderItem(item, style)).join("");
|
|
7039
|
+
return `<nav class="ox-section-index ox-section-index--${modifier}" aria-label="Section pages"><h1>${escapeSectionIndexHtml(title)}</h1><ul class="${listClass}">${body}</ul></nav>`;
|
|
7040
|
+
}
|
|
7041
|
+
function renderItem(item, style) {
|
|
7042
|
+
const href = escapeSectionIndexHtml(item.href.trim());
|
|
7043
|
+
const label = escapeSectionIndexHtml(item.title);
|
|
7044
|
+
if (style === "list") return `<li><a href="${href}">${label}</a></li>`;
|
|
7045
|
+
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>`;
|
|
7046
|
+
}
|
|
7047
|
+
//#endregion
|
|
7048
|
+
//#region src/section-index-paths.ts
|
|
7049
|
+
/**
|
|
7050
|
+
* Section-index URL, title, and output-path helpers.
|
|
7051
|
+
*/
|
|
7052
|
+
function pageTitle(page) {
|
|
7053
|
+
if (page.title.trim()) return page.title;
|
|
7054
|
+
return formatSectionTitle(path$1.basename(page.inputPath ?? page.routePaths.urlPath).replace(/\.[^.]+$/, "") || page.routePaths.urlPath);
|
|
7055
|
+
}
|
|
7056
|
+
function sectionTitle(dir) {
|
|
7057
|
+
if (!dir) return "Home";
|
|
7058
|
+
return formatSectionTitle(dir.slice(dir.lastIndexOf("/") + 1));
|
|
7059
|
+
}
|
|
7060
|
+
function formatSectionTitle(name) {
|
|
7061
|
+
try {
|
|
7062
|
+
return importNapiModuleSync().formatSsgTitle(name);
|
|
7063
|
+
} catch {
|
|
7064
|
+
if (!name) return "Untitled";
|
|
7065
|
+
return name.charAt(0).toUpperCase() + name.slice(1).replace(/[-_]+/g, " ");
|
|
7066
|
+
}
|
|
7067
|
+
}
|
|
7068
|
+
function normalizeUrlPath(urlPath) {
|
|
7069
|
+
if (!urlPath || urlPath === "/") return "";
|
|
7070
|
+
return urlPath.replace(/^\/+|\/+$/g, "");
|
|
7071
|
+
}
|
|
7072
|
+
function parentDir(urlPath) {
|
|
7073
|
+
const normalized = normalizeUrlPath(urlPath);
|
|
7074
|
+
if (!normalized) return null;
|
|
7075
|
+
const index = normalized.lastIndexOf("/");
|
|
7076
|
+
return index === -1 ? "" : normalized.slice(0, index);
|
|
7077
|
+
}
|
|
7078
|
+
function firstChildDir(urlPath, parent) {
|
|
7079
|
+
const normalized = normalizeUrlPath(urlPath);
|
|
7080
|
+
if (!normalized) return;
|
|
7081
|
+
if (!parent) {
|
|
7082
|
+
const slash = normalized.indexOf("/");
|
|
7083
|
+
return slash === -1 ? void 0 : normalized.slice(0, slash);
|
|
7084
|
+
}
|
|
7085
|
+
const prefix = `${parent}/`;
|
|
7086
|
+
if (!normalized.startsWith(prefix) || normalized === parent) return;
|
|
7087
|
+
const rest = normalized.slice(prefix.length);
|
|
7088
|
+
const slash = rest.indexOf("/");
|
|
7089
|
+
return slash === -1 ? void 0 : `${parent}/${rest.slice(0, slash)}`;
|
|
7090
|
+
}
|
|
7091
|
+
function sectionHref(base, dir, extension) {
|
|
7092
|
+
const prefix = !base || base === "/" ? "/" : base.endsWith("/") ? base : `${base}/`;
|
|
7093
|
+
const ext = extension.startsWith(".") ? extension : `.${extension}`;
|
|
7094
|
+
return dir ? `${prefix}${dir}/index${ext}` : `${prefix}index${ext}`;
|
|
7095
|
+
}
|
|
7096
|
+
function sectionOutputPath(outDir, dir, extension) {
|
|
7097
|
+
const ext = extension.startsWith(".") ? extension : `.${extension}`;
|
|
7098
|
+
return containedPath(outDir, ...dir ? [...dir.split("/").filter(Boolean), `index${ext}`] : [`index${ext}`]);
|
|
7099
|
+
}
|
|
7100
|
+
function dirFromOutputPath(outputPath, outDir) {
|
|
7101
|
+
return path$1.relative(path$1.resolve(outDir), path$1.resolve(outputPath)).replaceAll(path$1.sep, "/").replace(/\/index\.[^/]+$/u, "").replace(/^index\.[^/]+$/u, "").replace(/^\/+|\/+$/g, "");
|
|
7102
|
+
}
|
|
7103
|
+
function containedPath(outDir, ...segments) {
|
|
7104
|
+
const root = path$1.resolve(outDir);
|
|
7105
|
+
const resolved = path$1.resolve(root, ...segments);
|
|
7106
|
+
const prefix = root.endsWith(path$1.sep) ? root : `${root}${path$1.sep}`;
|
|
7107
|
+
if (resolved !== root && !resolved.startsWith(prefix)) return;
|
|
7108
|
+
if (segments.some((segment) => segment === ".." || segment.includes("\0"))) return;
|
|
7109
|
+
return resolved;
|
|
7110
|
+
}
|
|
7111
|
+
//#endregion
|
|
7112
|
+
//#region src/section-index.ts
|
|
7113
|
+
/**
|
|
7114
|
+
* Opt-in generated section index pages.
|
|
7115
|
+
*
|
|
7116
|
+
* Resolution and directory walking live here. Listing HTML is rendered in
|
|
7117
|
+
* Rust (`ox_content_ssg::render_section_index`) when the NAPI helper is
|
|
7118
|
+
* available; a matching TypeScript renderer covers the same escape / href
|
|
7119
|
+
* rules so the SSG path stays safe either way. The Vite plugin appends
|
|
7120
|
+
* themed HTML during SSG and never overwrites an existing index page.
|
|
7121
|
+
*/
|
|
7122
|
+
/**
|
|
7123
|
+
* Resolves `ssg.sectionIndex` with defaults.
|
|
7124
|
+
*
|
|
7125
|
+
* `false` / omitted stays off. `true` enables card listings. An object
|
|
7126
|
+
* enables the feature and overrides only the fields the site set.
|
|
7127
|
+
*/
|
|
7128
|
+
function resolveSectionIndexOptions(value) {
|
|
7129
|
+
if (!value) return {
|
|
7130
|
+
enabled: false,
|
|
7131
|
+
style: "cards"
|
|
7132
|
+
};
|
|
7133
|
+
if (value === true) return {
|
|
7134
|
+
enabled: true,
|
|
7135
|
+
style: "cards"
|
|
7136
|
+
};
|
|
7137
|
+
return {
|
|
7138
|
+
enabled: true,
|
|
7139
|
+
style: value.style === "list" ? "list" : "cards"
|
|
7140
|
+
};
|
|
7141
|
+
}
|
|
7142
|
+
/** Maps a generated section index onto the SSG render shape. */
|
|
7143
|
+
function toSectionIndexProcessResult(page) {
|
|
7144
|
+
return {
|
|
7145
|
+
inputPath: page.outputPath,
|
|
7146
|
+
routePaths: {
|
|
7147
|
+
outputPath: page.outputPath,
|
|
7148
|
+
urlPath: page.urlPath,
|
|
7149
|
+
href: page.href,
|
|
7150
|
+
ogImagePath: "",
|
|
7151
|
+
ogImageUrl: ""
|
|
7152
|
+
},
|
|
7153
|
+
transformedHtml: page.content,
|
|
7154
|
+
title: page.title,
|
|
7155
|
+
frontmatter: {},
|
|
7156
|
+
toc: []
|
|
7157
|
+
};
|
|
7158
|
+
}
|
|
7159
|
+
/** Appends generated section indexes for directories that have no real index. */
|
|
7160
|
+
async function appendSectionIndexPages(input) {
|
|
7161
|
+
if (!input.options?.enabled) return;
|
|
7162
|
+
const existingOutputs = new Set(input.generatedPages.map((page) => path$1.normalize(page.outputPath)));
|
|
7163
|
+
for (const spec of sectionIndexSpecs(input.collectedPages, input.listedPages, input.options, input.outDir, input.base, input.extension)) {
|
|
7164
|
+
if (existingOutputs.has(path$1.normalize(spec.outputPath))) continue;
|
|
7165
|
+
try {
|
|
7166
|
+
const html = await input.render(spec);
|
|
7167
|
+
input.generatedPages.push({
|
|
7168
|
+
inputPath: spec.outputPath,
|
|
7169
|
+
outputPath: spec.outputPath,
|
|
7170
|
+
html
|
|
7171
|
+
});
|
|
7172
|
+
existingOutputs.add(path$1.normalize(spec.outputPath));
|
|
7173
|
+
} catch (err) {
|
|
7174
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7175
|
+
input.errors.push(`Failed to generate section index ${spec.href}: ${message}`);
|
|
7176
|
+
}
|
|
7177
|
+
}
|
|
7178
|
+
}
|
|
7179
|
+
function sectionIndexSpecs(collected, listed, options, outDir, base, extension) {
|
|
7180
|
+
const occupied = /* @__PURE__ */ new Set();
|
|
7181
|
+
for (const page of collected) occupied.add(normalizeUrlPath(page.routePaths.urlPath));
|
|
7182
|
+
for (const page of collected) {
|
|
7183
|
+
const output = page.routePaths.outputPath;
|
|
7184
|
+
if (output) occupied.add(dirFromOutputPath(output, outDir));
|
|
7185
|
+
}
|
|
7186
|
+
const visible = listed.filter((page) => !isHiddenByFlags(page.frontmatter));
|
|
7187
|
+
const childrenByDir = /* @__PURE__ */ new Map();
|
|
7188
|
+
for (const page of visible) {
|
|
7189
|
+
const urlPath = normalizeUrlPath(page.routePaths.urlPath);
|
|
7190
|
+
const parent = parentDir(urlPath);
|
|
7191
|
+
if (parent === null) continue;
|
|
7192
|
+
pushChild(childrenByDir, parent, {
|
|
7193
|
+
title: pageTitle(page),
|
|
7194
|
+
href: page.routePaths.href,
|
|
7195
|
+
description: page.description
|
|
7196
|
+
});
|
|
7197
|
+
let ancestor = parent;
|
|
7198
|
+
while (ancestor !== "") {
|
|
7199
|
+
const grand = parentDir(ancestor);
|
|
7200
|
+
if (grand === null) break;
|
|
7201
|
+
const nested = firstChildDir(urlPath, grand);
|
|
7202
|
+
if (nested) pushUniqueDir(childrenByDir, grand, nested, visible, base, extension);
|
|
7203
|
+
ancestor = grand;
|
|
7204
|
+
}
|
|
7205
|
+
}
|
|
7206
|
+
const pages = [];
|
|
7207
|
+
const dirs = [...childrenByDir.keys()].sort();
|
|
7208
|
+
for (const dir of dirs) {
|
|
7209
|
+
if (occupied.has(dir)) continue;
|
|
7210
|
+
const children = uniqueItems(childrenByDir.get(dir) ?? []).filter((item) => isSafeSectionHref(item.href));
|
|
7211
|
+
if (children.length === 0) continue;
|
|
7212
|
+
children.sort((left, right) => {
|
|
7213
|
+
const titleCmp = left.title.localeCompare(right.title);
|
|
7214
|
+
return titleCmp !== 0 ? titleCmp : left.href.localeCompare(right.href);
|
|
7215
|
+
});
|
|
7216
|
+
const outputPath = sectionOutputPath(outDir, dir, extension);
|
|
7217
|
+
if (!outputPath) continue;
|
|
7218
|
+
const title = sectionTitle(dir);
|
|
7219
|
+
pages.push({
|
|
7220
|
+
title,
|
|
7221
|
+
content: renderSectionIndexHtml(title, children, options.style),
|
|
7222
|
+
outputPath,
|
|
7223
|
+
urlPath: dir || "/",
|
|
7224
|
+
href: sectionHref(base, dir, extension)
|
|
7225
|
+
});
|
|
7226
|
+
}
|
|
7227
|
+
return pages;
|
|
7228
|
+
}
|
|
7229
|
+
function pushChild(map, dir, item) {
|
|
7230
|
+
const list = map.get(dir);
|
|
7231
|
+
if (list) {
|
|
7232
|
+
list.push(item);
|
|
7233
|
+
return;
|
|
7234
|
+
}
|
|
7235
|
+
map.set(dir, [item]);
|
|
5639
7236
|
}
|
|
5640
|
-
function
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
7237
|
+
function pushUniqueDir(map, parent, childDir, visible, base, extension) {
|
|
7238
|
+
const href = sectionHref(base, childDir, extension);
|
|
7239
|
+
if (map.get(parent)?.some((item) => item.href === href)) return;
|
|
7240
|
+
const indexPage = visible.find((page) => normalizeUrlPath(page.routePaths.urlPath) === childDir);
|
|
7241
|
+
pushChild(map, parent, {
|
|
7242
|
+
title: indexPage ? pageTitle(indexPage) : sectionTitle(childDir),
|
|
7243
|
+
href: indexPage?.routePaths.href ?? href,
|
|
7244
|
+
description: indexPage?.description
|
|
7245
|
+
});
|
|
5644
7246
|
}
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
if (!value) return {
|
|
5655
|
-
enabled: false,
|
|
5656
|
-
members: []
|
|
5657
|
-
};
|
|
5658
|
-
if (value === true) return {
|
|
5659
|
-
enabled: true,
|
|
5660
|
-
members: []
|
|
5661
|
-
};
|
|
5662
|
-
return {
|
|
5663
|
-
enabled: true,
|
|
5664
|
-
members: normalizeMembers(value.members)
|
|
5665
|
-
};
|
|
7247
|
+
function uniqueItems(items) {
|
|
7248
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7249
|
+
const unique = [];
|
|
7250
|
+
for (const item of items) {
|
|
7251
|
+
if (seen.has(item.href)) continue;
|
|
7252
|
+
seen.add(item.href);
|
|
7253
|
+
unique.push(item);
|
|
7254
|
+
}
|
|
7255
|
+
return unique;
|
|
5666
7256
|
}
|
|
5667
|
-
function
|
|
5668
|
-
|
|
5669
|
-
return members.flatMap((member) => {
|
|
5670
|
-
if (!member || typeof member.name !== "string") return [];
|
|
5671
|
-
const links = Array.isArray(member.links) ? member.links.flatMap((link) => {
|
|
5672
|
-
if (!link || typeof link.label !== "string" || typeof link.href !== "string") return [];
|
|
5673
|
-
return [{
|
|
5674
|
-
label: link.label,
|
|
5675
|
-
href: link.href
|
|
5676
|
-
}];
|
|
5677
|
-
}) : void 0;
|
|
5678
|
-
return [{
|
|
5679
|
-
name: member.name,
|
|
5680
|
-
role: typeof member.role === "string" ? member.role : void 0,
|
|
5681
|
-
avatar: typeof member.avatar === "string" ? member.avatar : void 0,
|
|
5682
|
-
links
|
|
5683
|
-
}];
|
|
5684
|
-
});
|
|
7257
|
+
function isHiddenByFlags(frontmatter) {
|
|
7258
|
+
return frontmatter.draft === true || frontmatter.unlisted === true;
|
|
5685
7259
|
}
|
|
5686
7260
|
//#endregion
|
|
5687
7261
|
//#region src/search-provider.ts
|
|
@@ -6077,93 +7651,1419 @@ function versionLocation(outputPath, outDir, options) {
|
|
|
6077
7651
|
};
|
|
6078
7652
|
}
|
|
6079
7653
|
return {
|
|
6080
|
-
id: options.current,
|
|
6081
|
-
sibling: normalized
|
|
7654
|
+
id: options.current,
|
|
7655
|
+
sibling: normalized
|
|
7656
|
+
};
|
|
7657
|
+
}
|
|
7658
|
+
function outputToHref(outputPath, outDir, base) {
|
|
7659
|
+
return siteHref$1(base, "", relativeUrl(outputPath, outDir));
|
|
7660
|
+
}
|
|
7661
|
+
/** Applies switcher / banner / search rewrite after every version tree is generated. */
|
|
7662
|
+
function decorateVersionedPages(pages, options, outDir, base) {
|
|
7663
|
+
if (!options.enabled) return;
|
|
7664
|
+
const existingHrefs = new Set(pages.map((page) => outputToHref(page.outputPath, outDir, base)));
|
|
7665
|
+
for (const page of pages) {
|
|
7666
|
+
const { id, sibling } = versionLocation(page.outputPath, outDir, options);
|
|
7667
|
+
page.html = applyVersionChrome(page.html, options, id, sibling, base, existingHrefs);
|
|
7668
|
+
}
|
|
7669
|
+
}
|
|
7670
|
+
async function writeSnapshotSearchIndex(input) {
|
|
7671
|
+
const prefix = sanitizePrefix(input.prefix);
|
|
7672
|
+
if (!prefix) return;
|
|
7673
|
+
const destDir = path$1.join(input.outDir, prefix);
|
|
7674
|
+
const prefixBase = searchIndexUrl(input.base, prefix).replace(/search-index\.json$/, "");
|
|
7675
|
+
const json = await buildSearchIndex(input.srcDir, prefixBase, input.extensions, input.publishState, [], input.mdx);
|
|
7676
|
+
await fs$2.mkdir(destDir, { recursive: true });
|
|
7677
|
+
await writeSearchIndex(json, destDir);
|
|
7678
|
+
const dest = path$1.join(destDir, "search-index.json");
|
|
7679
|
+
try {
|
|
7680
|
+
await fs$2.access(dest);
|
|
7681
|
+
} catch {
|
|
7682
|
+
await fs$2.writeFile(dest, json, "utf8");
|
|
7683
|
+
}
|
|
7684
|
+
return dest;
|
|
7685
|
+
}
|
|
7686
|
+
function applyVersionChrome(html, options, activeId, siblingPath, base, existingHrefs) {
|
|
7687
|
+
if (!options.enabled) return html;
|
|
7688
|
+
const active = options.entries.find((entry) => entry.id === activeId);
|
|
7689
|
+
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 ?? ""));
|
|
7690
|
+
}
|
|
7691
|
+
function sanitizePrefix(prefix) {
|
|
7692
|
+
const trimmed = prefix.trim().replace(/^\/+|\/+$/g, "");
|
|
7693
|
+
if (!trimmed) return "";
|
|
7694
|
+
return PREFIX_RE.test(trimmed) && !trimmed.includes("..") ? trimmed : "";
|
|
7695
|
+
}
|
|
7696
|
+
function defaultCurrentEntry() {
|
|
7697
|
+
return {
|
|
7698
|
+
id: DEFAULT_CURRENT_ID,
|
|
7699
|
+
label: "Latest",
|
|
7700
|
+
prefix: "",
|
|
7701
|
+
banner: false
|
|
7702
|
+
};
|
|
7703
|
+
}
|
|
7704
|
+
function normalizeEntries(entries) {
|
|
7705
|
+
if (!entries) return [];
|
|
7706
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7707
|
+
const resolved = [];
|
|
7708
|
+
for (const entry of entries) {
|
|
7709
|
+
if (!entry || typeof entry.id !== "string" || typeof entry.label !== "string") continue;
|
|
7710
|
+
const id = entry.id.trim();
|
|
7711
|
+
const label = entry.label.trim();
|
|
7712
|
+
if (!id || !label || seen.has(id)) continue;
|
|
7713
|
+
const prefix = sanitizePrefix(typeof entry.prefix === "string" ? entry.prefix : "");
|
|
7714
|
+
if (entry.prefix && !prefix) continue;
|
|
7715
|
+
const dir = typeof entry.dir === "string" && entry.dir.trim() ? entry.dir.trim() : void 0;
|
|
7716
|
+
if (dir && (dir.includes("\0") || dir.includes(".."))) continue;
|
|
7717
|
+
seen.add(id);
|
|
7718
|
+
resolved.push({
|
|
7719
|
+
id,
|
|
7720
|
+
label,
|
|
7721
|
+
prefix,
|
|
7722
|
+
dir,
|
|
7723
|
+
banner: normalizeBanner(entry.banner)
|
|
7724
|
+
});
|
|
7725
|
+
}
|
|
7726
|
+
return resolved;
|
|
7727
|
+
}
|
|
7728
|
+
function normalizeBanner(value) {
|
|
7729
|
+
return value === "unreleased" || value === "unmaintained" ? value : false;
|
|
7730
|
+
}
|
|
7731
|
+
function siteHref$1(base, prefix, rest) {
|
|
7732
|
+
const root = !base || base === "/" ? "/" : base.endsWith("/") ? base : `${base}/`;
|
|
7733
|
+
const parts = [prefix, rest].filter((part) => part && part !== "/");
|
|
7734
|
+
return parts.length === 0 ? root : `${root}${parts.join("/")}/`;
|
|
7735
|
+
}
|
|
7736
|
+
function relativeUrl(outputPath, outDir) {
|
|
7737
|
+
const rel = path$1.posix.normalize(path$1.relative(path$1.resolve(outDir), path$1.resolve(outputPath)).replaceAll(path$1.sep, "/"));
|
|
7738
|
+
if (rel.startsWith("..")) return "";
|
|
7739
|
+
const dir = rel.endsWith("/index.html") ? rel.slice(0, -11) : rel.replace(/\.html$/, "");
|
|
7740
|
+
return dir === "." ? "" : dir;
|
|
7741
|
+
}
|
|
7742
|
+
//#endregion
|
|
7743
|
+
//#region src/resources-jpeg.ts
|
|
7744
|
+
function encodeJpeg(image, quality = 80) {
|
|
7745
|
+
const yQuant = scaleQuant(LUM_QUANT, quality);
|
|
7746
|
+
const cQuant = scaleQuant(CHR_QUANT, quality);
|
|
7747
|
+
const width = image.width;
|
|
7748
|
+
const height = image.height;
|
|
7749
|
+
const duY = /* @__PURE__ */ new Int32Array(64);
|
|
7750
|
+
const duCb = /* @__PURE__ */ new Int32Array(64);
|
|
7751
|
+
const duCr = /* @__PURE__ */ new Int32Array(64);
|
|
7752
|
+
const bits = new BitWriter();
|
|
7753
|
+
let dcY = 0;
|
|
7754
|
+
let dcCb = 0;
|
|
7755
|
+
let dcCr = 0;
|
|
7756
|
+
for (let y = 0; y < height; y += 8) for (let x = 0; x < width; x += 8) {
|
|
7757
|
+
sampleBlock(image, x, y, duY, duCb, duCr);
|
|
7758
|
+
dcY = encodeBlock(bits, duY, yQuant, dcY, YDC, YAC);
|
|
7759
|
+
dcCb = encodeBlock(bits, duCb, cQuant, dcCb, CDC, CAC);
|
|
7760
|
+
dcCr = encodeBlock(bits, duCr, cQuant, dcCr, CDC, CAC);
|
|
7761
|
+
}
|
|
7762
|
+
bits.flush();
|
|
7763
|
+
return Buffer.concat([
|
|
7764
|
+
jpegHeader(width, height, yQuant, cQuant),
|
|
7765
|
+
bits.toBuffer(),
|
|
7766
|
+
Buffer.from([255, 217])
|
|
7767
|
+
]);
|
|
7768
|
+
}
|
|
7769
|
+
function sampleBlock(image, left, top, yOut, cbOut, crOut) {
|
|
7770
|
+
for (let j = 0; j < 8; j++) {
|
|
7771
|
+
const y = Math.min(image.height - 1, top + j);
|
|
7772
|
+
for (let i = 0; i < 8; i++) {
|
|
7773
|
+
const x = Math.min(image.width - 1, left + i);
|
|
7774
|
+
const p = (y * image.width + x) * 4;
|
|
7775
|
+
const r = image.data[p] ?? 0;
|
|
7776
|
+
const g = image.data[p + 1] ?? 0;
|
|
7777
|
+
const b = image.data[p + 2] ?? 0;
|
|
7778
|
+
const idx = j * 8 + i;
|
|
7779
|
+
yOut[idx] = (66 * r + 129 * g + 25 * b + 128 >> 8) - 128;
|
|
7780
|
+
cbOut[idx] = -38 * r - 74 * g + 112 * b + 128 >> 8;
|
|
7781
|
+
crOut[idx] = 112 * r - 94 * g - 18 * b + 128 >> 8;
|
|
7782
|
+
}
|
|
7783
|
+
}
|
|
7784
|
+
}
|
|
7785
|
+
function encodeBlock(bits, block, quant, lastDc, dcTable, acTable) {
|
|
7786
|
+
const dct = forwardDct(block);
|
|
7787
|
+
const zz = /* @__PURE__ */ new Int32Array(64);
|
|
7788
|
+
for (let i = 0; i < 64; i++) zz[i] = Math.round(dct[ZIGZAG[i]] / quant[i]);
|
|
7789
|
+
const dc = zz[0] ?? 0;
|
|
7790
|
+
writeCoeff(bits, dc - lastDc, dcTable);
|
|
7791
|
+
let zeroRun = 0;
|
|
7792
|
+
for (let i = 1; i < 64; i++) {
|
|
7793
|
+
const value = zz[i] ?? 0;
|
|
7794
|
+
if (value === 0) {
|
|
7795
|
+
zeroRun++;
|
|
7796
|
+
continue;
|
|
7797
|
+
}
|
|
7798
|
+
while (zeroRun > 15) {
|
|
7799
|
+
writeCode(bits, acTable, 240);
|
|
7800
|
+
zeroRun -= 16;
|
|
7801
|
+
}
|
|
7802
|
+
writeCoeff(bits, value, acTable, zeroRun);
|
|
7803
|
+
zeroRun = 0;
|
|
7804
|
+
}
|
|
7805
|
+
if (zeroRun > 0) writeCode(bits, acTable, 0);
|
|
7806
|
+
return dc;
|
|
7807
|
+
}
|
|
7808
|
+
function writeCoeff(bits, value, table, run = 0) {
|
|
7809
|
+
const category = bitCategory(value);
|
|
7810
|
+
writeCode(bits, table, run << 4 | category);
|
|
7811
|
+
if (category > 0) bits.writeBits(value < 0 ? value + ((1 << category) - 1) : value, category);
|
|
7812
|
+
}
|
|
7813
|
+
function writeCode(bits, table, symbol) {
|
|
7814
|
+
const entry = table.get(symbol);
|
|
7815
|
+
if (!entry) throw new Error("missing Huffman code");
|
|
7816
|
+
bits.writeBits(entry.code, entry.len);
|
|
7817
|
+
}
|
|
7818
|
+
function bitCategory(value) {
|
|
7819
|
+
const abs = Math.abs(value);
|
|
7820
|
+
if (!Number.isFinite(abs) || abs === 0) return 0;
|
|
7821
|
+
return Math.min(11, Math.ceil(Math.log2(abs + 1)));
|
|
7822
|
+
}
|
|
7823
|
+
function forwardDct(block) {
|
|
7824
|
+
const out = /* @__PURE__ */ new Float64Array(64);
|
|
7825
|
+
for (let v = 0; v < 8; v++) for (let u = 0; u < 8; u++) {
|
|
7826
|
+
let sum = 0;
|
|
7827
|
+
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);
|
|
7828
|
+
const cu = u === 0 ? Math.SQRT1_2 : 1;
|
|
7829
|
+
const cv = v === 0 ? Math.SQRT1_2 : 1;
|
|
7830
|
+
out[v * 8 + u] = .25 * cu * cv * sum;
|
|
7831
|
+
}
|
|
7832
|
+
return out;
|
|
7833
|
+
}
|
|
7834
|
+
function scaleQuant(base, quality) {
|
|
7835
|
+
const q = Math.max(1, Math.min(100, quality));
|
|
7836
|
+
const scale = q < 50 ? Math.floor(5e3 / q) : Math.floor(200 - q * 2);
|
|
7837
|
+
return base.map((value) => Math.max(1, Math.min(255, Math.floor((value * scale + 50) / 100))));
|
|
7838
|
+
}
|
|
7839
|
+
function jpegHeader(width, height, yQuant, cQuant) {
|
|
7840
|
+
const chunks = [
|
|
7841
|
+
Buffer.from([255, 216]),
|
|
7842
|
+
jfifApp0(),
|
|
7843
|
+
dqt(0, yQuant),
|
|
7844
|
+
dqt(1, cQuant),
|
|
7845
|
+
sof(width, height),
|
|
7846
|
+
dht(0, 0, STD_DC_LUM_NCODES, STD_DC_LUM_VALUES),
|
|
7847
|
+
dht(0, 1, STD_DC_CHR_NCODES, STD_DC_CHR_VALUES),
|
|
7848
|
+
dht(1, 0, STD_AC_LUM_NCODES, STD_AC_LUM_VALUES),
|
|
7849
|
+
dht(1, 1, STD_AC_CHR_NCODES, STD_AC_CHR_VALUES),
|
|
7850
|
+
sos()
|
|
7851
|
+
];
|
|
7852
|
+
return Buffer.concat(chunks);
|
|
7853
|
+
}
|
|
7854
|
+
function jfifApp0() {
|
|
7855
|
+
return Buffer.from([
|
|
7856
|
+
255,
|
|
7857
|
+
224,
|
|
7858
|
+
0,
|
|
7859
|
+
16,
|
|
7860
|
+
74,
|
|
7861
|
+
70,
|
|
7862
|
+
73,
|
|
7863
|
+
70,
|
|
7864
|
+
0,
|
|
7865
|
+
1,
|
|
7866
|
+
1,
|
|
7867
|
+
0,
|
|
7868
|
+
0,
|
|
7869
|
+
1,
|
|
7870
|
+
0,
|
|
7871
|
+
1,
|
|
7872
|
+
0,
|
|
7873
|
+
0
|
|
7874
|
+
]);
|
|
7875
|
+
}
|
|
7876
|
+
function dqt(id, table) {
|
|
7877
|
+
const out = Buffer.alloc(69);
|
|
7878
|
+
out[0] = 255;
|
|
7879
|
+
out[1] = 219;
|
|
7880
|
+
out.writeUInt16BE(67, 2);
|
|
7881
|
+
out[4] = id;
|
|
7882
|
+
for (let i = 0; i < 64; i++) out[5 + i] = table[i] ?? 1;
|
|
7883
|
+
return out;
|
|
7884
|
+
}
|
|
7885
|
+
function sof(width, height) {
|
|
7886
|
+
const out = Buffer.from([
|
|
7887
|
+
255,
|
|
7888
|
+
192,
|
|
7889
|
+
0,
|
|
7890
|
+
17,
|
|
7891
|
+
8,
|
|
7892
|
+
0,
|
|
7893
|
+
0,
|
|
7894
|
+
0,
|
|
7895
|
+
0,
|
|
7896
|
+
3,
|
|
7897
|
+
1,
|
|
7898
|
+
17,
|
|
7899
|
+
0,
|
|
7900
|
+
2,
|
|
7901
|
+
17,
|
|
7902
|
+
1,
|
|
7903
|
+
3,
|
|
7904
|
+
17,
|
|
7905
|
+
1
|
|
7906
|
+
]);
|
|
7907
|
+
out.writeUInt16BE(height, 5);
|
|
7908
|
+
out.writeUInt16BE(width, 7);
|
|
7909
|
+
return out;
|
|
7910
|
+
}
|
|
7911
|
+
function dht(cls, id, ncodes, values) {
|
|
7912
|
+
const out = Buffer.alloc(21 + values.length);
|
|
7913
|
+
out[0] = 255;
|
|
7914
|
+
out[1] = 196;
|
|
7915
|
+
out.writeUInt16BE(19 + values.length, 2);
|
|
7916
|
+
out[4] = cls << 4 | id;
|
|
7917
|
+
Buffer.from(ncodes).copy(out, 5);
|
|
7918
|
+
Buffer.from(values).copy(out, 21);
|
|
7919
|
+
return out;
|
|
7920
|
+
}
|
|
7921
|
+
function sos() {
|
|
7922
|
+
return Buffer.from([
|
|
7923
|
+
255,
|
|
7924
|
+
218,
|
|
7925
|
+
0,
|
|
7926
|
+
12,
|
|
7927
|
+
3,
|
|
7928
|
+
1,
|
|
7929
|
+
0,
|
|
7930
|
+
2,
|
|
7931
|
+
17,
|
|
7932
|
+
3,
|
|
7933
|
+
17,
|
|
7934
|
+
0,
|
|
7935
|
+
63,
|
|
7936
|
+
0
|
|
7937
|
+
]);
|
|
7938
|
+
}
|
|
7939
|
+
var BitWriter = class {
|
|
7940
|
+
bytes = [];
|
|
7941
|
+
bits = 0;
|
|
7942
|
+
length = 0;
|
|
7943
|
+
writeBits(value, count) {
|
|
7944
|
+
for (let i = count - 1; i >= 0; i--) {
|
|
7945
|
+
this.bits = this.bits << 1 | value >> i & 1;
|
|
7946
|
+
this.length++;
|
|
7947
|
+
if (this.length === 8) this.pushByte();
|
|
7948
|
+
}
|
|
7949
|
+
}
|
|
7950
|
+
flush() {
|
|
7951
|
+
if (this.length > 0) {
|
|
7952
|
+
this.bits <<= 8 - this.length;
|
|
7953
|
+
this.pushByte();
|
|
7954
|
+
}
|
|
7955
|
+
}
|
|
7956
|
+
toBuffer() {
|
|
7957
|
+
return Buffer.from(this.bytes);
|
|
7958
|
+
}
|
|
7959
|
+
pushByte() {
|
|
7960
|
+
this.bytes.push(this.bits & 255);
|
|
7961
|
+
if ((this.bits & 255) === 255) this.bytes.push(0);
|
|
7962
|
+
this.bits = 0;
|
|
7963
|
+
this.length = 0;
|
|
7964
|
+
}
|
|
7965
|
+
};
|
|
7966
|
+
function buildHuffman(ncodes, values) {
|
|
7967
|
+
const table = /* @__PURE__ */ new Map();
|
|
7968
|
+
let code = 0;
|
|
7969
|
+
let index = 0;
|
|
7970
|
+
for (let len = 1; len <= 16; len++) {
|
|
7971
|
+
const count = ncodes[len - 1] ?? 0;
|
|
7972
|
+
for (let i = 0; i < count; i++) {
|
|
7973
|
+
table.set(values[index++] ?? 0, {
|
|
7974
|
+
code,
|
|
7975
|
+
len
|
|
7976
|
+
});
|
|
7977
|
+
code++;
|
|
7978
|
+
}
|
|
7979
|
+
code <<= 1;
|
|
7980
|
+
}
|
|
7981
|
+
return table;
|
|
7982
|
+
}
|
|
7983
|
+
const ZIGZAG = [
|
|
7984
|
+
0,
|
|
7985
|
+
1,
|
|
7986
|
+
8,
|
|
7987
|
+
16,
|
|
7988
|
+
9,
|
|
7989
|
+
2,
|
|
7990
|
+
3,
|
|
7991
|
+
10,
|
|
7992
|
+
17,
|
|
7993
|
+
24,
|
|
7994
|
+
32,
|
|
7995
|
+
25,
|
|
7996
|
+
18,
|
|
7997
|
+
11,
|
|
7998
|
+
4,
|
|
7999
|
+
5,
|
|
8000
|
+
12,
|
|
8001
|
+
19,
|
|
8002
|
+
26,
|
|
8003
|
+
33,
|
|
8004
|
+
40,
|
|
8005
|
+
48,
|
|
8006
|
+
41,
|
|
8007
|
+
34,
|
|
8008
|
+
27,
|
|
8009
|
+
20,
|
|
8010
|
+
13,
|
|
8011
|
+
6,
|
|
8012
|
+
7,
|
|
8013
|
+
14,
|
|
8014
|
+
21,
|
|
8015
|
+
28,
|
|
8016
|
+
35,
|
|
8017
|
+
42,
|
|
8018
|
+
49,
|
|
8019
|
+
56,
|
|
8020
|
+
57,
|
|
8021
|
+
50,
|
|
8022
|
+
43,
|
|
8023
|
+
36,
|
|
8024
|
+
29,
|
|
8025
|
+
22,
|
|
8026
|
+
15,
|
|
8027
|
+
23,
|
|
8028
|
+
30,
|
|
8029
|
+
37,
|
|
8030
|
+
44,
|
|
8031
|
+
51,
|
|
8032
|
+
58,
|
|
8033
|
+
59,
|
|
8034
|
+
52,
|
|
8035
|
+
45,
|
|
8036
|
+
38,
|
|
8037
|
+
31,
|
|
8038
|
+
39,
|
|
8039
|
+
46,
|
|
8040
|
+
53,
|
|
8041
|
+
60,
|
|
8042
|
+
61,
|
|
8043
|
+
54,
|
|
8044
|
+
47,
|
|
8045
|
+
55,
|
|
8046
|
+
62,
|
|
8047
|
+
63
|
|
8048
|
+
];
|
|
8049
|
+
const LUM_QUANT = [
|
|
8050
|
+
16,
|
|
8051
|
+
11,
|
|
8052
|
+
10,
|
|
8053
|
+
16,
|
|
8054
|
+
24,
|
|
8055
|
+
40,
|
|
8056
|
+
51,
|
|
8057
|
+
61,
|
|
8058
|
+
12,
|
|
8059
|
+
12,
|
|
8060
|
+
14,
|
|
8061
|
+
19,
|
|
8062
|
+
26,
|
|
8063
|
+
58,
|
|
8064
|
+
60,
|
|
8065
|
+
55,
|
|
8066
|
+
14,
|
|
8067
|
+
13,
|
|
8068
|
+
16,
|
|
8069
|
+
24,
|
|
8070
|
+
40,
|
|
8071
|
+
57,
|
|
8072
|
+
69,
|
|
8073
|
+
56,
|
|
8074
|
+
14,
|
|
8075
|
+
17,
|
|
8076
|
+
22,
|
|
8077
|
+
29,
|
|
8078
|
+
51,
|
|
8079
|
+
87,
|
|
8080
|
+
80,
|
|
8081
|
+
62,
|
|
8082
|
+
18,
|
|
8083
|
+
22,
|
|
8084
|
+
37,
|
|
8085
|
+
56,
|
|
8086
|
+
68,
|
|
8087
|
+
109,
|
|
8088
|
+
103,
|
|
8089
|
+
77,
|
|
8090
|
+
24,
|
|
8091
|
+
35,
|
|
8092
|
+
55,
|
|
8093
|
+
64,
|
|
8094
|
+
81,
|
|
8095
|
+
104,
|
|
8096
|
+
113,
|
|
8097
|
+
92,
|
|
8098
|
+
49,
|
|
8099
|
+
64,
|
|
8100
|
+
78,
|
|
8101
|
+
87,
|
|
8102
|
+
103,
|
|
8103
|
+
121,
|
|
8104
|
+
120,
|
|
8105
|
+
101,
|
|
8106
|
+
72,
|
|
8107
|
+
92,
|
|
8108
|
+
95,
|
|
8109
|
+
98,
|
|
8110
|
+
112,
|
|
8111
|
+
100,
|
|
8112
|
+
103,
|
|
8113
|
+
99
|
|
8114
|
+
];
|
|
8115
|
+
const CHR_QUANT = [
|
|
8116
|
+
17,
|
|
8117
|
+
18,
|
|
8118
|
+
24,
|
|
8119
|
+
47,
|
|
8120
|
+
99,
|
|
8121
|
+
99,
|
|
8122
|
+
99,
|
|
8123
|
+
99,
|
|
8124
|
+
18,
|
|
8125
|
+
21,
|
|
8126
|
+
26,
|
|
8127
|
+
66,
|
|
8128
|
+
99,
|
|
8129
|
+
99,
|
|
8130
|
+
99,
|
|
8131
|
+
99,
|
|
8132
|
+
24,
|
|
8133
|
+
26,
|
|
8134
|
+
56,
|
|
8135
|
+
99,
|
|
8136
|
+
99,
|
|
8137
|
+
99,
|
|
8138
|
+
99,
|
|
8139
|
+
99,
|
|
8140
|
+
47,
|
|
8141
|
+
66,
|
|
8142
|
+
99,
|
|
8143
|
+
99,
|
|
8144
|
+
99,
|
|
8145
|
+
99,
|
|
8146
|
+
99,
|
|
8147
|
+
99,
|
|
8148
|
+
99,
|
|
8149
|
+
99,
|
|
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
|
+
];
|
|
8181
|
+
const STD_DC_LUM_NCODES = [
|
|
8182
|
+
0,
|
|
8183
|
+
1,
|
|
8184
|
+
5,
|
|
8185
|
+
1,
|
|
8186
|
+
1,
|
|
8187
|
+
1,
|
|
8188
|
+
1,
|
|
8189
|
+
1,
|
|
8190
|
+
1,
|
|
8191
|
+
0,
|
|
8192
|
+
0,
|
|
8193
|
+
0,
|
|
8194
|
+
0,
|
|
8195
|
+
0,
|
|
8196
|
+
0,
|
|
8197
|
+
0
|
|
8198
|
+
];
|
|
8199
|
+
const STD_DC_LUM_VALUES = [
|
|
8200
|
+
0,
|
|
8201
|
+
1,
|
|
8202
|
+
2,
|
|
8203
|
+
3,
|
|
8204
|
+
4,
|
|
8205
|
+
5,
|
|
8206
|
+
6,
|
|
8207
|
+
7,
|
|
8208
|
+
8,
|
|
8209
|
+
9,
|
|
8210
|
+
10,
|
|
8211
|
+
11
|
|
8212
|
+
];
|
|
8213
|
+
const STD_DC_CHR_NCODES = [
|
|
8214
|
+
0,
|
|
8215
|
+
3,
|
|
8216
|
+
1,
|
|
8217
|
+
1,
|
|
8218
|
+
1,
|
|
8219
|
+
1,
|
|
8220
|
+
1,
|
|
8221
|
+
1,
|
|
8222
|
+
1,
|
|
8223
|
+
1,
|
|
8224
|
+
1,
|
|
8225
|
+
0,
|
|
8226
|
+
0,
|
|
8227
|
+
0,
|
|
8228
|
+
0,
|
|
8229
|
+
0
|
|
8230
|
+
];
|
|
8231
|
+
const STD_DC_CHR_VALUES = [
|
|
8232
|
+
0,
|
|
8233
|
+
1,
|
|
8234
|
+
2,
|
|
8235
|
+
3,
|
|
8236
|
+
4,
|
|
8237
|
+
5,
|
|
8238
|
+
6,
|
|
8239
|
+
7,
|
|
8240
|
+
8,
|
|
8241
|
+
9,
|
|
8242
|
+
10,
|
|
8243
|
+
11
|
|
8244
|
+
];
|
|
8245
|
+
const STD_AC_LUM_NCODES = [
|
|
8246
|
+
0,
|
|
8247
|
+
2,
|
|
8248
|
+
1,
|
|
8249
|
+
3,
|
|
8250
|
+
3,
|
|
8251
|
+
2,
|
|
8252
|
+
4,
|
|
8253
|
+
3,
|
|
8254
|
+
5,
|
|
8255
|
+
5,
|
|
8256
|
+
4,
|
|
8257
|
+
4,
|
|
8258
|
+
0,
|
|
8259
|
+
0,
|
|
8260
|
+
1,
|
|
8261
|
+
125
|
|
8262
|
+
];
|
|
8263
|
+
const STD_AC_LUM_VALUES = [
|
|
8264
|
+
1,
|
|
8265
|
+
2,
|
|
8266
|
+
3,
|
|
8267
|
+
0,
|
|
8268
|
+
4,
|
|
8269
|
+
17,
|
|
8270
|
+
5,
|
|
8271
|
+
18,
|
|
8272
|
+
33,
|
|
8273
|
+
49,
|
|
8274
|
+
65,
|
|
8275
|
+
6,
|
|
8276
|
+
19,
|
|
8277
|
+
81,
|
|
8278
|
+
97,
|
|
8279
|
+
7,
|
|
8280
|
+
34,
|
|
8281
|
+
113,
|
|
8282
|
+
20,
|
|
8283
|
+
50,
|
|
8284
|
+
129,
|
|
8285
|
+
145,
|
|
8286
|
+
161,
|
|
8287
|
+
8,
|
|
8288
|
+
35,
|
|
8289
|
+
66,
|
|
8290
|
+
177,
|
|
8291
|
+
193,
|
|
8292
|
+
21,
|
|
8293
|
+
82,
|
|
8294
|
+
209,
|
|
8295
|
+
240,
|
|
8296
|
+
36,
|
|
8297
|
+
51,
|
|
8298
|
+
98,
|
|
8299
|
+
114,
|
|
8300
|
+
130,
|
|
8301
|
+
9,
|
|
8302
|
+
10,
|
|
8303
|
+
22,
|
|
8304
|
+
23,
|
|
8305
|
+
24,
|
|
8306
|
+
25,
|
|
8307
|
+
26,
|
|
8308
|
+
37,
|
|
8309
|
+
38,
|
|
8310
|
+
39,
|
|
8311
|
+
40,
|
|
8312
|
+
41,
|
|
8313
|
+
42,
|
|
8314
|
+
52,
|
|
8315
|
+
53,
|
|
8316
|
+
54,
|
|
8317
|
+
55,
|
|
8318
|
+
56,
|
|
8319
|
+
57,
|
|
8320
|
+
58,
|
|
8321
|
+
67,
|
|
8322
|
+
68,
|
|
8323
|
+
69,
|
|
8324
|
+
70,
|
|
8325
|
+
71,
|
|
8326
|
+
72,
|
|
8327
|
+
73,
|
|
8328
|
+
74,
|
|
8329
|
+
83,
|
|
8330
|
+
84,
|
|
8331
|
+
85,
|
|
8332
|
+
86,
|
|
8333
|
+
87,
|
|
8334
|
+
88,
|
|
8335
|
+
89,
|
|
8336
|
+
90,
|
|
8337
|
+
99,
|
|
8338
|
+
100,
|
|
8339
|
+
101,
|
|
8340
|
+
102,
|
|
8341
|
+
103,
|
|
8342
|
+
104,
|
|
8343
|
+
105,
|
|
8344
|
+
106,
|
|
8345
|
+
115,
|
|
8346
|
+
116,
|
|
8347
|
+
117,
|
|
8348
|
+
118,
|
|
8349
|
+
119,
|
|
8350
|
+
120,
|
|
8351
|
+
121,
|
|
8352
|
+
122,
|
|
8353
|
+
131,
|
|
8354
|
+
132,
|
|
8355
|
+
133,
|
|
8356
|
+
134,
|
|
8357
|
+
135,
|
|
8358
|
+
136,
|
|
8359
|
+
137,
|
|
8360
|
+
138,
|
|
8361
|
+
146,
|
|
8362
|
+
147,
|
|
8363
|
+
148,
|
|
8364
|
+
149,
|
|
8365
|
+
150,
|
|
8366
|
+
151,
|
|
8367
|
+
152,
|
|
8368
|
+
153,
|
|
8369
|
+
154,
|
|
8370
|
+
162,
|
|
8371
|
+
163,
|
|
8372
|
+
164,
|
|
8373
|
+
165,
|
|
8374
|
+
166,
|
|
8375
|
+
167,
|
|
8376
|
+
168,
|
|
8377
|
+
169,
|
|
8378
|
+
170,
|
|
8379
|
+
178,
|
|
8380
|
+
179,
|
|
8381
|
+
180,
|
|
8382
|
+
181,
|
|
8383
|
+
182,
|
|
8384
|
+
183,
|
|
8385
|
+
184,
|
|
8386
|
+
185,
|
|
8387
|
+
186,
|
|
8388
|
+
194,
|
|
8389
|
+
195,
|
|
8390
|
+
196,
|
|
8391
|
+
197,
|
|
8392
|
+
198,
|
|
8393
|
+
199,
|
|
8394
|
+
200,
|
|
8395
|
+
201,
|
|
8396
|
+
202,
|
|
8397
|
+
210,
|
|
8398
|
+
211,
|
|
8399
|
+
212,
|
|
8400
|
+
213,
|
|
8401
|
+
214,
|
|
8402
|
+
215,
|
|
8403
|
+
216,
|
|
8404
|
+
217,
|
|
8405
|
+
218,
|
|
8406
|
+
225,
|
|
8407
|
+
226,
|
|
8408
|
+
227,
|
|
8409
|
+
228,
|
|
8410
|
+
229,
|
|
8411
|
+
230,
|
|
8412
|
+
231,
|
|
8413
|
+
232,
|
|
8414
|
+
233,
|
|
8415
|
+
234,
|
|
8416
|
+
241,
|
|
8417
|
+
242,
|
|
8418
|
+
243,
|
|
8419
|
+
244,
|
|
8420
|
+
245,
|
|
8421
|
+
246,
|
|
8422
|
+
247,
|
|
8423
|
+
248,
|
|
8424
|
+
249,
|
|
8425
|
+
250
|
|
8426
|
+
];
|
|
8427
|
+
const STD_AC_CHR_NCODES = [
|
|
8428
|
+
0,
|
|
8429
|
+
2,
|
|
8430
|
+
1,
|
|
8431
|
+
2,
|
|
8432
|
+
4,
|
|
8433
|
+
4,
|
|
8434
|
+
3,
|
|
8435
|
+
4,
|
|
8436
|
+
7,
|
|
8437
|
+
5,
|
|
8438
|
+
4,
|
|
8439
|
+
4,
|
|
8440
|
+
0,
|
|
8441
|
+
1,
|
|
8442
|
+
2,
|
|
8443
|
+
119
|
|
8444
|
+
];
|
|
8445
|
+
const STD_AC_CHR_VALUES = [
|
|
8446
|
+
0,
|
|
8447
|
+
1,
|
|
8448
|
+
2,
|
|
8449
|
+
3,
|
|
8450
|
+
17,
|
|
8451
|
+
4,
|
|
8452
|
+
5,
|
|
8453
|
+
33,
|
|
8454
|
+
49,
|
|
8455
|
+
6,
|
|
8456
|
+
18,
|
|
8457
|
+
65,
|
|
8458
|
+
81,
|
|
8459
|
+
7,
|
|
8460
|
+
97,
|
|
8461
|
+
113,
|
|
8462
|
+
19,
|
|
8463
|
+
34,
|
|
8464
|
+
50,
|
|
8465
|
+
129,
|
|
8466
|
+
8,
|
|
8467
|
+
20,
|
|
8468
|
+
66,
|
|
8469
|
+
145,
|
|
8470
|
+
161,
|
|
8471
|
+
177,
|
|
8472
|
+
193,
|
|
8473
|
+
9,
|
|
8474
|
+
35,
|
|
8475
|
+
51,
|
|
8476
|
+
82,
|
|
8477
|
+
240,
|
|
8478
|
+
21,
|
|
8479
|
+
98,
|
|
8480
|
+
114,
|
|
8481
|
+
209,
|
|
8482
|
+
10,
|
|
8483
|
+
22,
|
|
8484
|
+
36,
|
|
8485
|
+
52,
|
|
8486
|
+
225,
|
|
8487
|
+
37,
|
|
8488
|
+
241,
|
|
8489
|
+
23,
|
|
8490
|
+
24,
|
|
8491
|
+
25,
|
|
8492
|
+
26,
|
|
8493
|
+
38,
|
|
8494
|
+
39,
|
|
8495
|
+
40,
|
|
8496
|
+
41,
|
|
8497
|
+
42,
|
|
8498
|
+
53,
|
|
8499
|
+
54,
|
|
8500
|
+
55,
|
|
8501
|
+
56,
|
|
8502
|
+
57,
|
|
8503
|
+
58,
|
|
8504
|
+
67,
|
|
8505
|
+
68,
|
|
8506
|
+
69,
|
|
8507
|
+
70,
|
|
8508
|
+
71,
|
|
8509
|
+
72,
|
|
8510
|
+
73,
|
|
8511
|
+
74,
|
|
8512
|
+
83,
|
|
8513
|
+
84,
|
|
8514
|
+
85,
|
|
8515
|
+
86,
|
|
8516
|
+
87,
|
|
8517
|
+
88,
|
|
8518
|
+
89,
|
|
8519
|
+
90,
|
|
8520
|
+
99,
|
|
8521
|
+
100,
|
|
8522
|
+
101,
|
|
8523
|
+
102,
|
|
8524
|
+
103,
|
|
8525
|
+
104,
|
|
8526
|
+
105,
|
|
8527
|
+
106,
|
|
8528
|
+
115,
|
|
8529
|
+
116,
|
|
8530
|
+
117,
|
|
8531
|
+
118,
|
|
8532
|
+
119,
|
|
8533
|
+
120,
|
|
8534
|
+
121,
|
|
8535
|
+
122,
|
|
8536
|
+
130,
|
|
8537
|
+
131,
|
|
8538
|
+
132,
|
|
8539
|
+
133,
|
|
8540
|
+
134,
|
|
8541
|
+
135,
|
|
8542
|
+
136,
|
|
8543
|
+
137,
|
|
8544
|
+
138,
|
|
8545
|
+
146,
|
|
8546
|
+
147,
|
|
8547
|
+
148,
|
|
8548
|
+
149,
|
|
8549
|
+
150,
|
|
8550
|
+
151,
|
|
8551
|
+
152,
|
|
8552
|
+
153,
|
|
8553
|
+
154,
|
|
8554
|
+
162,
|
|
8555
|
+
163,
|
|
8556
|
+
164,
|
|
8557
|
+
165,
|
|
8558
|
+
166,
|
|
8559
|
+
167,
|
|
8560
|
+
168,
|
|
8561
|
+
169,
|
|
8562
|
+
170,
|
|
8563
|
+
178,
|
|
8564
|
+
179,
|
|
8565
|
+
180,
|
|
8566
|
+
181,
|
|
8567
|
+
182,
|
|
8568
|
+
183,
|
|
8569
|
+
184,
|
|
8570
|
+
185,
|
|
8571
|
+
186,
|
|
8572
|
+
194,
|
|
8573
|
+
195,
|
|
8574
|
+
196,
|
|
8575
|
+
197,
|
|
8576
|
+
198,
|
|
8577
|
+
199,
|
|
8578
|
+
200,
|
|
8579
|
+
201,
|
|
8580
|
+
202,
|
|
8581
|
+
210,
|
|
8582
|
+
211,
|
|
8583
|
+
212,
|
|
8584
|
+
213,
|
|
8585
|
+
214,
|
|
8586
|
+
215,
|
|
8587
|
+
216,
|
|
8588
|
+
217,
|
|
8589
|
+
218,
|
|
8590
|
+
226,
|
|
8591
|
+
227,
|
|
8592
|
+
228,
|
|
8593
|
+
229,
|
|
8594
|
+
230,
|
|
8595
|
+
231,
|
|
8596
|
+
232,
|
|
8597
|
+
233,
|
|
8598
|
+
234,
|
|
8599
|
+
242,
|
|
8600
|
+
243,
|
|
8601
|
+
244,
|
|
8602
|
+
245,
|
|
8603
|
+
246,
|
|
8604
|
+
247,
|
|
8605
|
+
248,
|
|
8606
|
+
249,
|
|
8607
|
+
250
|
|
8608
|
+
];
|
|
8609
|
+
const YDC = buildHuffman(STD_DC_LUM_NCODES, STD_DC_LUM_VALUES);
|
|
8610
|
+
const CDC = buildHuffman(STD_DC_CHR_NCODES, STD_DC_CHR_VALUES);
|
|
8611
|
+
const YAC = buildHuffman(STD_AC_LUM_NCODES, STD_AC_LUM_VALUES);
|
|
8612
|
+
const CAC = buildHuffman(STD_AC_CHR_NCODES, STD_AC_CHR_VALUES);
|
|
8613
|
+
//#endregion
|
|
8614
|
+
//#region src/resources-image.ts
|
|
8615
|
+
/**
|
|
8616
|
+
* Build-time PNG/JPEG pixel helpers for page resources.
|
|
8617
|
+
*
|
|
8618
|
+
* PNG is decoded and re-encoded for resize/crop. JPEG is encode-only so a
|
|
8619
|
+
* `format=jpeg` transform can change the container after the pixel pass.
|
|
8620
|
+
*/
|
|
8621
|
+
const PNG_SIGNATURE = Buffer.from([
|
|
8622
|
+
137,
|
|
8623
|
+
80,
|
|
8624
|
+
78,
|
|
8625
|
+
71,
|
|
8626
|
+
13,
|
|
8627
|
+
10,
|
|
8628
|
+
26,
|
|
8629
|
+
10
|
|
8630
|
+
]);
|
|
8631
|
+
function isPng(buffer) {
|
|
8632
|
+
return buffer.length >= 8 && PNG_SIGNATURE.equals(buffer.subarray(0, 8));
|
|
8633
|
+
}
|
|
8634
|
+
function decodePng(buffer) {
|
|
8635
|
+
if (!isPng(buffer)) throw new Error("not a PNG");
|
|
8636
|
+
let width = 0;
|
|
8637
|
+
let height = 0;
|
|
8638
|
+
let bitDepth = 0;
|
|
8639
|
+
let colorType = 0;
|
|
8640
|
+
const idat = [];
|
|
8641
|
+
let offset = 8;
|
|
8642
|
+
while (offset + 12 <= buffer.length) {
|
|
8643
|
+
const length = buffer.readUInt32BE(offset);
|
|
8644
|
+
const type = buffer.toString("ascii", offset + 4, offset + 8);
|
|
8645
|
+
const start = offset + 8;
|
|
8646
|
+
const end = start + length;
|
|
8647
|
+
if (end + 4 > buffer.length) break;
|
|
8648
|
+
const chunk = buffer.subarray(start, end);
|
|
8649
|
+
if (type === "IHDR") {
|
|
8650
|
+
width = chunk.readUInt32BE(0);
|
|
8651
|
+
height = chunk.readUInt32BE(4);
|
|
8652
|
+
bitDepth = chunk[8] ?? 0;
|
|
8653
|
+
colorType = chunk[9] ?? 0;
|
|
8654
|
+
} else if (type === "IDAT") idat.push(Buffer.from(chunk));
|
|
8655
|
+
else if (type === "IEND") break;
|
|
8656
|
+
offset = end + 4;
|
|
8657
|
+
}
|
|
8658
|
+
if (bitDepth !== 8 || colorType !== 2 && colorType !== 6) throw new Error("unsupported PNG");
|
|
8659
|
+
const channels = colorType === 6 ? 4 : 3;
|
|
8660
|
+
const raw = inflateSync(Buffer.concat(idat));
|
|
8661
|
+
const stride = width * channels;
|
|
8662
|
+
const data = new Uint8Array(width * height * 4);
|
|
8663
|
+
let src = 0;
|
|
8664
|
+
const prior = new Uint8Array(stride);
|
|
8665
|
+
const recon = new Uint8Array(stride);
|
|
8666
|
+
for (let y = 0; y < height; y++) {
|
|
8667
|
+
const filter = raw[src++] ?? 0;
|
|
8668
|
+
for (let x = 0; x < stride; x++) {
|
|
8669
|
+
const sample = raw[src++] ?? 0;
|
|
8670
|
+
const a = x >= channels ? recon[x - channels] : 0;
|
|
8671
|
+
const b = prior[x] ?? 0;
|
|
8672
|
+
const c = x >= channels ? prior[x - channels] : 0;
|
|
8673
|
+
recon[x] = sample + paethPredict(filter, a, b, c) & 255;
|
|
8674
|
+
}
|
|
8675
|
+
for (let x = 0; x < width; x++) {
|
|
8676
|
+
const i = x * channels;
|
|
8677
|
+
const o = (y * width + x) * 4;
|
|
8678
|
+
data[o] = recon[i] ?? 0;
|
|
8679
|
+
data[o + 1] = recon[i + 1] ?? 0;
|
|
8680
|
+
data[o + 2] = recon[i + 2] ?? 0;
|
|
8681
|
+
data[o + 3] = channels === 4 ? recon[i + 3] ?? 255 : 255;
|
|
8682
|
+
}
|
|
8683
|
+
prior.set(recon);
|
|
8684
|
+
}
|
|
8685
|
+
return {
|
|
8686
|
+
width,
|
|
8687
|
+
height,
|
|
8688
|
+
data
|
|
8689
|
+
};
|
|
8690
|
+
}
|
|
8691
|
+
function paethPredict(filter, a, b, c) {
|
|
8692
|
+
switch (filter) {
|
|
8693
|
+
case 0: return 0;
|
|
8694
|
+
case 1: return a;
|
|
8695
|
+
case 2: return b;
|
|
8696
|
+
case 3: return a + b >> 1;
|
|
8697
|
+
case 4: {
|
|
8698
|
+
const p = a + b - c;
|
|
8699
|
+
const pa = Math.abs(p - a);
|
|
8700
|
+
const pb = Math.abs(p - b);
|
|
8701
|
+
const pc = Math.abs(p - c);
|
|
8702
|
+
if (pa <= pb && pa <= pc) return a;
|
|
8703
|
+
if (pb <= pc) return b;
|
|
8704
|
+
return c;
|
|
8705
|
+
}
|
|
8706
|
+
default: throw new Error("unsupported PNG filter");
|
|
8707
|
+
}
|
|
8708
|
+
}
|
|
8709
|
+
function encodePng(image) {
|
|
8710
|
+
const { width, height, data } = image;
|
|
8711
|
+
const raw = Buffer.alloc((width * 4 + 1) * height);
|
|
8712
|
+
let offset = 0;
|
|
8713
|
+
for (let y = 0; y < height; y++) {
|
|
8714
|
+
raw[offset++] = 0;
|
|
8715
|
+
raw.set(data.subarray(y * width * 4, (y + 1) * width * 4), offset);
|
|
8716
|
+
offset += width * 4;
|
|
8717
|
+
}
|
|
8718
|
+
const ihdr = Buffer.alloc(13);
|
|
8719
|
+
ihdr.writeUInt32BE(width, 0);
|
|
8720
|
+
ihdr.writeUInt32BE(height, 4);
|
|
8721
|
+
ihdr[8] = 8;
|
|
8722
|
+
ihdr[9] = 6;
|
|
8723
|
+
return Buffer.concat([
|
|
8724
|
+
PNG_SIGNATURE,
|
|
8725
|
+
pngChunk("IHDR", ihdr),
|
|
8726
|
+
pngChunk("IDAT", deflateSync(raw)),
|
|
8727
|
+
pngChunk("IEND", Buffer.alloc(0))
|
|
8728
|
+
]);
|
|
8729
|
+
}
|
|
8730
|
+
function pngChunk(type, data) {
|
|
8731
|
+
const body = Buffer.concat([Buffer.from(type, "ascii"), data]);
|
|
8732
|
+
const chunk = Buffer.alloc(12 + data.length);
|
|
8733
|
+
chunk.writeUInt32BE(data.length, 0);
|
|
8734
|
+
body.copy(chunk, 4);
|
|
8735
|
+
chunk.writeUInt32BE(crc32(body), 8 + data.length);
|
|
8736
|
+
return chunk;
|
|
8737
|
+
}
|
|
8738
|
+
function crc32(data) {
|
|
8739
|
+
let crc = 4294967295;
|
|
8740
|
+
for (const byte of data) {
|
|
8741
|
+
crc ^= byte;
|
|
8742
|
+
for (let i = 0; i < 8; i++) crc = crc & 1 ? crc >>> 1 ^ 3988292384 : crc >>> 1;
|
|
8743
|
+
}
|
|
8744
|
+
return (crc ^ 4294967295) >>> 0;
|
|
8745
|
+
}
|
|
8746
|
+
function resizeNearest(image, width, height) {
|
|
8747
|
+
const data = new Uint8Array(width * height * 4);
|
|
8748
|
+
for (let y = 0; y < height; y++) {
|
|
8749
|
+
const sy = Math.min(image.height - 1, Math.floor(y * image.height / height));
|
|
8750
|
+
for (let x = 0; x < width; x++) {
|
|
8751
|
+
const sx = Math.min(image.width - 1, Math.floor(x * image.width / width));
|
|
8752
|
+
data.set(image.data.subarray((sy * image.width + sx) * 4, (sy * image.width + sx) * 4 + 4), (y * width + x) * 4);
|
|
8753
|
+
}
|
|
8754
|
+
}
|
|
8755
|
+
return {
|
|
8756
|
+
width,
|
|
8757
|
+
height,
|
|
8758
|
+
data
|
|
8759
|
+
};
|
|
8760
|
+
}
|
|
8761
|
+
function cropImage(image, x, y, width, height) {
|
|
8762
|
+
const left = Math.max(0, Math.min(image.width, Math.floor(x)));
|
|
8763
|
+
const top = Math.max(0, Math.min(image.height, Math.floor(y)));
|
|
8764
|
+
const cropW = Math.max(1, Math.min(image.width - left, Math.floor(width)));
|
|
8765
|
+
const cropH = Math.max(1, Math.min(image.height - top, Math.floor(height)));
|
|
8766
|
+
const data = new Uint8Array(cropW * cropH * 4);
|
|
8767
|
+
for (let row = 0; row < cropH; row++) {
|
|
8768
|
+
const src = ((top + row) * image.width + left) * 4;
|
|
8769
|
+
data.set(image.data.subarray(src, src + cropW * 4), row * cropW * 4);
|
|
8770
|
+
}
|
|
8771
|
+
return {
|
|
8772
|
+
width: cropW,
|
|
8773
|
+
height: cropH,
|
|
8774
|
+
data
|
|
8775
|
+
};
|
|
8776
|
+
}
|
|
8777
|
+
function coverCrop(image, width, height) {
|
|
8778
|
+
const scale = Math.max(width / image.width, height / image.height);
|
|
8779
|
+
const scaled = resizeNearest(image, Math.max(width, Math.round(image.width * scale)), Math.max(height, Math.round(image.height * scale)));
|
|
8780
|
+
return cropImage(scaled, Math.max(0, Math.floor((scaled.width - width) / 2)), Math.max(0, Math.floor((scaled.height - height) / 2)), width, height);
|
|
8781
|
+
}
|
|
8782
|
+
//#endregion
|
|
8783
|
+
//#region src/resources-process.ts
|
|
8784
|
+
/**
|
|
8785
|
+
* Page-resource HTML rewriting and transform writes.
|
|
8786
|
+
*/
|
|
8787
|
+
const IMG_TAG = /<img\b[^>]*>/gi;
|
|
8788
|
+
const SRC_ATTR = /\bsrc\s*=\s*(?:"([^"]*)"|'([^']*)')/i;
|
|
8789
|
+
async function processPageResources(input) {
|
|
8790
|
+
if (!input.options.enabled) return {
|
|
8791
|
+
html: input.html,
|
|
8792
|
+
files: [],
|
|
8793
|
+
errors: [],
|
|
8794
|
+
fatal: []
|
|
8795
|
+
};
|
|
8796
|
+
const bundleRoot = path$1.dirname(input.inputPath);
|
|
8797
|
+
const outputDir = path$1.dirname(input.outputPath);
|
|
8798
|
+
const files = [];
|
|
8799
|
+
const errors = [];
|
|
8800
|
+
const fatal = [];
|
|
8801
|
+
let html = input.html;
|
|
8802
|
+
const tags = input.html.match(IMG_TAG) ?? [];
|
|
8803
|
+
for (const tag of tags) {
|
|
8804
|
+
const srcMatch = tag.match(SRC_ATTR);
|
|
8805
|
+
const rawSrc = srcMatch?.[1] ?? srcMatch?.[2];
|
|
8806
|
+
if (!rawSrc) continue;
|
|
8807
|
+
const src = unescapeHtml(rawSrc);
|
|
8808
|
+
const parsed = parseResourceSrc(src);
|
|
8809
|
+
if (!parsed) continue;
|
|
8810
|
+
const resolved = resolveBundlePath(parsed.pathname, bundleRoot, input.srcDir);
|
|
8811
|
+
if (!resolved.ok) {
|
|
8812
|
+
const message = `[ox-content] page resource ${JSON.stringify(src)} on ${input.inputPath} is outside the page bundle`;
|
|
8813
|
+
errors.push(message);
|
|
8814
|
+
fatal.push(message);
|
|
8815
|
+
continue;
|
|
8816
|
+
}
|
|
8817
|
+
let stat;
|
|
8818
|
+
try {
|
|
8819
|
+
stat = await fs$2.stat(resolved.absolute);
|
|
8820
|
+
} catch {
|
|
8821
|
+
const message = `[ox-content] missing page resource ${JSON.stringify(parsed.pathname)} on ${input.inputPath}`;
|
|
8822
|
+
errors.push(message);
|
|
8823
|
+
if (input.options.missing === "error") fatal.push(message);
|
|
8824
|
+
continue;
|
|
8825
|
+
}
|
|
8826
|
+
const transformError = validateTransform(parsed.transform, input.options);
|
|
8827
|
+
if (transformError) {
|
|
8828
|
+
const message = `[ox-content] ${transformError} for ${JSON.stringify(src)} on ${input.inputPath}`;
|
|
8829
|
+
errors.push(message);
|
|
8830
|
+
fatal.push(message);
|
|
8831
|
+
continue;
|
|
8832
|
+
}
|
|
8833
|
+
const hasTransform = hasPixelOrFormatTransform(parsed.transform);
|
|
8834
|
+
const outputName = hasTransform ? transformedFileName(parsed.pathname, parsed.transform, resourceCacheKey(resolved.absolute, stat.mtimeMs, parsed.transform)) : path$1.basename(resolved.absolute);
|
|
8835
|
+
const outputFile = path$1.join(outputDir, outputName);
|
|
8836
|
+
try {
|
|
8837
|
+
if (hasTransform) await writeTransformedResource({
|
|
8838
|
+
sourcePath: resolved.absolute,
|
|
8839
|
+
outputFile,
|
|
8840
|
+
cacheDir: input.cacheDir,
|
|
8841
|
+
mtimeMs: stat.mtimeMs,
|
|
8842
|
+
transform: parsed.transform
|
|
8843
|
+
});
|
|
8844
|
+
else {
|
|
8845
|
+
await fs$2.mkdir(outputDir, { recursive: true });
|
|
8846
|
+
await fs$2.copyFile(resolved.absolute, outputFile);
|
|
8847
|
+
}
|
|
8848
|
+
files.push(outputFile);
|
|
8849
|
+
const rewritten = tag.replace(rawSrc, escapeAttribute(outputName));
|
|
8850
|
+
html = html.replace(tag, rewritten);
|
|
8851
|
+
} catch (error) {
|
|
8852
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
8853
|
+
const message = `[ox-content] failed to process page resource ${JSON.stringify(src)} on ${input.inputPath}: ${detail}`;
|
|
8854
|
+
errors.push(message);
|
|
8855
|
+
fatal.push(message);
|
|
8856
|
+
}
|
|
8857
|
+
}
|
|
8858
|
+
return {
|
|
8859
|
+
html,
|
|
8860
|
+
files,
|
|
8861
|
+
errors,
|
|
8862
|
+
fatal
|
|
8863
|
+
};
|
|
8864
|
+
}
|
|
8865
|
+
function resolveBundlePath(pathname, bundleRoot, contentRoot) {
|
|
8866
|
+
if (path$1.isAbsolute(pathname) || pathname.includes("\0")) return { ok: false };
|
|
8867
|
+
const absolute = path$1.resolve(bundleRoot, pathname);
|
|
8868
|
+
if (!isInsideRoot$1(bundleRoot, absolute) || !isInsideRoot$1(contentRoot, absolute)) return { ok: false };
|
|
8869
|
+
return {
|
|
8870
|
+
ok: true,
|
|
8871
|
+
absolute
|
|
6082
8872
|
};
|
|
6083
8873
|
}
|
|
6084
|
-
function
|
|
6085
|
-
|
|
8874
|
+
function validateTransform(transform, options) {
|
|
8875
|
+
if (transform.width && options.widths.length > 0 && !options.widths.includes(transform.width)) return `width ${transform.width} is not in resources.widths`;
|
|
8876
|
+
if (transform.format && !options.formats.includes(transform.format)) return `format ${transform.format} is not in resources.formats`;
|
|
6086
8877
|
}
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
if (!options.enabled) return;
|
|
6090
|
-
const existingHrefs = new Set(pages.map((page) => outputToHref(page.outputPath, outDir, base)));
|
|
6091
|
-
for (const page of pages) {
|
|
6092
|
-
const { id, sibling } = versionLocation(page.outputPath, outDir, options);
|
|
6093
|
-
page.html = applyVersionChrome(page.html, options, id, sibling, base, existingHrefs);
|
|
6094
|
-
}
|
|
8878
|
+
function hasPixelOrFormatTransform(transform) {
|
|
8879
|
+
return Boolean(transform.width || transform.height || transform.crop || transform.format);
|
|
6095
8880
|
}
|
|
6096
|
-
|
|
6097
|
-
const
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
const
|
|
8881
|
+
function transformedFileName(pathname, transform, cacheKey) {
|
|
8882
|
+
const stem = path$1.basename(pathname).replace(/\.[^.]+$/, "") || "resource";
|
|
8883
|
+
const ext = outputExtension(pathname, transform.format);
|
|
8884
|
+
return `${stem}.${cacheKey.slice(0, 12)}.${ext}`;
|
|
8885
|
+
}
|
|
8886
|
+
function outputExtension(pathname, format) {
|
|
8887
|
+
if (format === "jpeg") return "jpg";
|
|
8888
|
+
if (format) return format;
|
|
8889
|
+
const ext = path$1.extname(pathname).slice(1).toLowerCase();
|
|
8890
|
+
return ext === "jpeg" ? "jpg" : ext || "png";
|
|
8891
|
+
}
|
|
8892
|
+
async function writeTransformedResource(input) {
|
|
8893
|
+
const key = resourceCacheKey(input.sourcePath, input.mtimeMs, input.transform);
|
|
8894
|
+
const ext = path$1.extname(input.outputFile);
|
|
8895
|
+
const cacheFile = path$1.join(input.cacheDir, `${key}${ext}`);
|
|
6105
8896
|
try {
|
|
6106
|
-
await fs$
|
|
6107
|
-
|
|
6108
|
-
|
|
8897
|
+
await fs$2.copyFile(cacheFile, input.outputFile);
|
|
8898
|
+
return;
|
|
8899
|
+
} catch {}
|
|
8900
|
+
const output = transformResourceBuffer(await fs$2.readFile(input.sourcePath), input.sourcePath, input.transform);
|
|
8901
|
+
if (output.length > 8388608) throw new Error("transform produced an oversized file");
|
|
8902
|
+
await fs$2.mkdir(path$1.dirname(input.outputFile), { recursive: true });
|
|
8903
|
+
await fs$2.mkdir(input.cacheDir, { recursive: true });
|
|
8904
|
+
await fs$2.writeFile(cacheFile, output);
|
|
8905
|
+
await fs$2.writeFile(input.outputFile, output);
|
|
8906
|
+
}
|
|
8907
|
+
function transformResourceBuffer(source, sourcePath, transform) {
|
|
8908
|
+
const needsPixels = Boolean(transform.width || transform.height || transform.crop);
|
|
8909
|
+
if (!needsPixels && !transform.format) return source;
|
|
8910
|
+
if (!needsPixels && transform.format) {
|
|
8911
|
+
if (!isPng(source)) {
|
|
8912
|
+
if (transform.format === formatFromPath(sourcePath)) return source;
|
|
8913
|
+
throw new Error(`cannot convert ${path$1.extname(sourcePath) || "source"} to ${transform.format}`);
|
|
8914
|
+
}
|
|
8915
|
+
return encodeFormat(decodePng(source), transform.format);
|
|
8916
|
+
}
|
|
8917
|
+
if (!isPng(source)) throw new Error("resize/crop requires a PNG source");
|
|
8918
|
+
return encodeFormat(applyPixelTransform(decodePng(source), transform), transform.format ?? "png");
|
|
8919
|
+
}
|
|
8920
|
+
function applyPixelTransform(image, transform) {
|
|
8921
|
+
const crop = transform.crop;
|
|
8922
|
+
if (crop && crop !== "center") {
|
|
8923
|
+
const parts = crop.split(",").map((part) => Number(part.trim()));
|
|
8924
|
+
if (parts.length === 4 && parts.every((part) => Number.isFinite(part))) return cropImage(image, parts[0], parts[1], parts[2], parts[3]);
|
|
8925
|
+
throw new Error(`invalid crop ${crop}`);
|
|
8926
|
+
}
|
|
8927
|
+
const width = transform.width;
|
|
8928
|
+
const height = transform.height;
|
|
8929
|
+
if (crop === "center") {
|
|
8930
|
+
if (!width || !height) throw new Error("crop=center requires width and height");
|
|
8931
|
+
return coverCrop(image, width, height);
|
|
8932
|
+
}
|
|
8933
|
+
if (width && height) return resizeNearest(image, width, height);
|
|
8934
|
+
if (width) return resizeNearest(image, width, Math.max(1, Math.round(image.height * width / image.width)));
|
|
8935
|
+
if (height) return resizeNearest(image, Math.max(1, Math.round(image.width * height / image.height)), height);
|
|
8936
|
+
return image;
|
|
8937
|
+
}
|
|
8938
|
+
function encodeFormat(image, format) {
|
|
8939
|
+
if (format === "jpeg") return encodeJpeg(image);
|
|
8940
|
+
if (format === "png") return encodePng(image);
|
|
8941
|
+
if (format === "webp") throw new Error("webp encoding requires a webp source without pixel transforms");
|
|
8942
|
+
throw new Error(`unsupported format ${format}`);
|
|
8943
|
+
}
|
|
8944
|
+
function formatFromPath(filePath) {
|
|
8945
|
+
const ext = path$1.extname(filePath).slice(1).toLowerCase();
|
|
8946
|
+
return ext === "jpg" ? "jpeg" : ext;
|
|
8947
|
+
}
|
|
8948
|
+
function unescapeHtml(value) {
|
|
8949
|
+
return value.replaceAll("&", "&").replaceAll(""", "\"").replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
8950
|
+
}
|
|
8951
|
+
function escapeAttribute(value) {
|
|
8952
|
+
return value.replaceAll("&", "&").replaceAll("\"", """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
8953
|
+
}
|
|
8954
|
+
//#endregion
|
|
8955
|
+
//#region src/resources.ts
|
|
8956
|
+
/**
|
|
8957
|
+
* Opt-in page-bundle resources and build-time image processing.
|
|
8958
|
+
*
|
|
8959
|
+
* A page directory is the bundle root. Sibling images are addressable with
|
|
8960
|
+
* relative URLs. Resize/crop/format query transforms run at build time and
|
|
8961
|
+
* are cached by source mtime plus transform params. Paths that leave the
|
|
8962
|
+
* bundle or `srcDir` are never processed.
|
|
8963
|
+
*/
|
|
8964
|
+
const DEFAULT_FORMATS = [
|
|
8965
|
+
"png",
|
|
8966
|
+
"jpeg",
|
|
8967
|
+
"webp"
|
|
8968
|
+
];
|
|
8969
|
+
const HOSTILE_SRC = /^(?:javascript|data|vbscript):/i;
|
|
8970
|
+
var PageResourceError = class extends Error {
|
|
8971
|
+
issues;
|
|
8972
|
+
constructor(issues) {
|
|
8973
|
+
super(issues.join("\n"));
|
|
8974
|
+
this.name = "PageResourceError";
|
|
8975
|
+
this.issues = issues;
|
|
6109
8976
|
}
|
|
6110
|
-
|
|
8977
|
+
};
|
|
8978
|
+
/**
|
|
8979
|
+
* Resolves `resources`. Omitted / `false` stay off. `true` or `{}` enables
|
|
8980
|
+
* defaults. An object enables the feature and overrides only set fields.
|
|
8981
|
+
*/
|
|
8982
|
+
function resolveResourcesOptions(value) {
|
|
8983
|
+
if (!value) return {
|
|
8984
|
+
enabled: false,
|
|
8985
|
+
formats: [...DEFAULT_FORMATS],
|
|
8986
|
+
widths: [],
|
|
8987
|
+
missing: "error"
|
|
8988
|
+
};
|
|
8989
|
+
if (value === true) return {
|
|
8990
|
+
enabled: true,
|
|
8991
|
+
formats: [...DEFAULT_FORMATS],
|
|
8992
|
+
widths: [],
|
|
8993
|
+
missing: "error"
|
|
8994
|
+
};
|
|
8995
|
+
return {
|
|
8996
|
+
enabled: true,
|
|
8997
|
+
formats: normalizeFormats(value.formats),
|
|
8998
|
+
widths: normalizeWidths(value.widths),
|
|
8999
|
+
missing: value.missing === "warn" ? "warn" : "error"
|
|
9000
|
+
};
|
|
6111
9001
|
}
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
9002
|
+
/**
|
|
9003
|
+
* Cache key for a source file plus transform. Changing mtime or params
|
|
9004
|
+
* produces a different key so stale derivatives are not reused.
|
|
9005
|
+
*/
|
|
9006
|
+
function resourceCacheKey(sourcePath, mtimeMs, transform) {
|
|
9007
|
+
return createHash("sha256").update(sourcePath).update("\0").update(String(mtimeMs)).update("\0").update(JSON.stringify(normalizeTransform(transform))).digest("hex");
|
|
6116
9008
|
}
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
9009
|
+
/** True when `candidate` stays inside `root` after resolve. */
|
|
9010
|
+
function isInsideRoot$1(root, candidate) {
|
|
9011
|
+
const resolvedRoot = path$1.resolve(root);
|
|
9012
|
+
const resolved = path$1.resolve(candidate);
|
|
9013
|
+
const relative = path$1.relative(resolvedRoot, resolved);
|
|
9014
|
+
return relative === "" || !relative.startsWith("..") && !path$1.isAbsolute(relative);
|
|
6121
9015
|
}
|
|
6122
|
-
function
|
|
9016
|
+
function parseResourceSrc(src) {
|
|
9017
|
+
const trimmed = src.trim();
|
|
9018
|
+
if (!trimmed || isRemoteOrAbsolute(trimmed) || HOSTILE_SRC.test(trimmed.replace(/\s+/g, ""))) return;
|
|
9019
|
+
const withoutHash = trimmed.split("#")[0] ?? trimmed;
|
|
9020
|
+
const queryIndex = withoutHash.indexOf("?");
|
|
9021
|
+
const pathname = queryIndex === -1 ? withoutHash : withoutHash.slice(0, queryIndex);
|
|
9022
|
+
const query = queryIndex === -1 ? "" : withoutHash.slice(queryIndex + 1);
|
|
9023
|
+
if (!pathname || pathname.includes("\0")) return;
|
|
9024
|
+
const params = new URLSearchParams(query);
|
|
6123
9025
|
return {
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
9026
|
+
pathname,
|
|
9027
|
+
transform: {
|
|
9028
|
+
width: parsePositiveInt(params.get("width") ?? params.get("w")),
|
|
9029
|
+
height: parsePositiveInt(params.get("height") ?? params.get("h")),
|
|
9030
|
+
crop: params.get("crop")?.trim() || void 0,
|
|
9031
|
+
format: normalizeFormat(params.get("format") ?? void 0)
|
|
9032
|
+
}
|
|
6128
9033
|
};
|
|
6129
9034
|
}
|
|
6130
|
-
function
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
const resolved = [];
|
|
6134
|
-
for (const entry of entries) {
|
|
6135
|
-
if (!entry || typeof entry.id !== "string" || typeof entry.label !== "string") continue;
|
|
6136
|
-
const id = entry.id.trim();
|
|
6137
|
-
const label = entry.label.trim();
|
|
6138
|
-
if (!id || !label || seen.has(id)) continue;
|
|
6139
|
-
const prefix = sanitizePrefix(typeof entry.prefix === "string" ? entry.prefix : "");
|
|
6140
|
-
if (entry.prefix && !prefix) continue;
|
|
6141
|
-
const dir = typeof entry.dir === "string" && entry.dir.trim() ? entry.dir.trim() : void 0;
|
|
6142
|
-
if (dir && (dir.includes("\0") || dir.includes(".."))) continue;
|
|
6143
|
-
seen.add(id);
|
|
6144
|
-
resolved.push({
|
|
6145
|
-
id,
|
|
6146
|
-
label,
|
|
6147
|
-
prefix,
|
|
6148
|
-
dir,
|
|
6149
|
-
banner: normalizeBanner(entry.banner)
|
|
6150
|
-
});
|
|
6151
|
-
}
|
|
6152
|
-
return resolved;
|
|
9035
|
+
function isRemoteOrAbsolute(src) {
|
|
9036
|
+
const compact = src.replace(/\s+/g, "");
|
|
9037
|
+
return /^[a-z][a-z0-9+.-]*:/i.test(compact) || compact.startsWith("//") || compact.startsWith("/");
|
|
6153
9038
|
}
|
|
6154
|
-
function
|
|
6155
|
-
|
|
9039
|
+
function parsePositiveInt(raw) {
|
|
9040
|
+
if (!raw) return;
|
|
9041
|
+
if (!/^[0-9]+$/.test(raw)) return;
|
|
9042
|
+
const value = Number(raw);
|
|
9043
|
+
return value > 0 ? value : void 0;
|
|
6156
9044
|
}
|
|
6157
|
-
function
|
|
6158
|
-
|
|
6159
|
-
const
|
|
6160
|
-
return
|
|
9045
|
+
function normalizeFormats(formats) {
|
|
9046
|
+
if (!formats?.length) return [...DEFAULT_FORMATS];
|
|
9047
|
+
const normalized = formats.map((format) => normalizeFormat(format)).filter((format) => Boolean(format));
|
|
9048
|
+
return normalized.length > 0 ? [...new Set(normalized)] : [...DEFAULT_FORMATS];
|
|
6161
9049
|
}
|
|
6162
|
-
function
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
9050
|
+
function normalizeWidths(widths) {
|
|
9051
|
+
if (!widths?.length) return [];
|
|
9052
|
+
return [...new Set(widths.filter((width) => Number.isInteger(width) && width > 0))];
|
|
9053
|
+
}
|
|
9054
|
+
function normalizeFormat(format) {
|
|
9055
|
+
if (!format) return;
|
|
9056
|
+
const value = format.trim().toLowerCase();
|
|
9057
|
+
if (value === "jpg") return "jpeg";
|
|
9058
|
+
return value || void 0;
|
|
9059
|
+
}
|
|
9060
|
+
function normalizeTransform(transform) {
|
|
9061
|
+
return {
|
|
9062
|
+
width: transform.width,
|
|
9063
|
+
height: transform.height,
|
|
9064
|
+
crop: transform.crop,
|
|
9065
|
+
format: transform.format
|
|
9066
|
+
};
|
|
6167
9067
|
}
|
|
6168
9068
|
//#endregion
|
|
6169
9069
|
//#region src/version-navigation.ts
|
|
@@ -6338,14 +9238,18 @@ function resolveSsgOptions(ssg) {
|
|
|
6338
9238
|
bare: false,
|
|
6339
9239
|
generateOgImage: false,
|
|
6340
9240
|
lastUpdated: false,
|
|
9241
|
+
contributors: resolveContributorsOption(void 0),
|
|
6341
9242
|
pagination: false,
|
|
6342
9243
|
breadcrumbs: false,
|
|
9244
|
+
jsonLd: false,
|
|
6343
9245
|
readerChrome: false,
|
|
6344
9246
|
localeSwitcher: false,
|
|
6345
9247
|
a11y: false,
|
|
6346
9248
|
pageChrome: false,
|
|
6347
9249
|
notFound: resolveNotFoundOptions(void 0),
|
|
6348
|
-
team: resolveTeamOptions(void 0)
|
|
9250
|
+
team: resolveTeamOptions(void 0),
|
|
9251
|
+
blog: resolveBlogOptions(void 0),
|
|
9252
|
+
sectionIndex: resolveSectionIndexOptions(void 0)
|
|
6349
9253
|
};
|
|
6350
9254
|
if (ssg === true || ssg === void 0) return {
|
|
6351
9255
|
enabled: true,
|
|
@@ -6354,14 +9258,18 @@ function resolveSsgOptions(ssg) {
|
|
|
6354
9258
|
bare: false,
|
|
6355
9259
|
generateOgImage: false,
|
|
6356
9260
|
lastUpdated: false,
|
|
9261
|
+
contributors: resolveContributorsOption(void 0),
|
|
6357
9262
|
pagination: false,
|
|
6358
9263
|
breadcrumbs: false,
|
|
9264
|
+
jsonLd: false,
|
|
6359
9265
|
readerChrome: false,
|
|
6360
9266
|
localeSwitcher: false,
|
|
6361
9267
|
a11y: false,
|
|
6362
9268
|
pageChrome: false,
|
|
6363
9269
|
notFound: resolveNotFoundOptions(void 0),
|
|
6364
9270
|
team: resolveTeamOptions(void 0),
|
|
9271
|
+
blog: resolveBlogOptions(void 0),
|
|
9272
|
+
sectionIndex: resolveSectionIndexOptions(void 0),
|
|
6365
9273
|
theme: resolveTheme(void 0)
|
|
6366
9274
|
};
|
|
6367
9275
|
return {
|
|
@@ -6378,22 +9286,56 @@ function resolveSsgOptions(ssg) {
|
|
|
6378
9286
|
ogImage: ssg.ogImage,
|
|
6379
9287
|
generateOgImage: ssg.generateOgImage ?? false,
|
|
6380
9288
|
lastUpdated: ssg.lastUpdated ?? false,
|
|
9289
|
+
contributors: resolveContributorsOption(ssg.contributors),
|
|
6381
9290
|
pagination: resolvePaginationOption(ssg.pagination),
|
|
6382
9291
|
breadcrumbs: resolvePaginationOption(ssg.breadcrumbs),
|
|
9292
|
+
jsonLd: resolveJsonLdOption(ssg.jsonLd),
|
|
6383
9293
|
readerChrome: resolveReaderChromeOption(ssg.readerChrome),
|
|
6384
9294
|
localeSwitcher: resolveLocaleSwitcherOption(ssg.localeSwitcher),
|
|
6385
9295
|
a11y: resolveA11yOption(ssg.a11y),
|
|
6386
9296
|
pageChrome: resolvePageChromeOption(ssg.pageChrome),
|
|
6387
9297
|
notFound: resolveNotFoundOptions(ssg.notFound),
|
|
6388
9298
|
team: resolveTeamOptions(ssg.team),
|
|
9299
|
+
blog: resolveBlogOptions(ssg.blog),
|
|
9300
|
+
sectionIndex: resolveSectionIndexOptions(ssg.sectionIndex),
|
|
6389
9301
|
siteUrl: ssg.siteUrl,
|
|
6390
9302
|
theme: resolveTheme(ssg.theme),
|
|
6391
9303
|
navigation: ssg.navigation
|
|
6392
9304
|
};
|
|
6393
9305
|
}
|
|
9306
|
+
function contributorsForPage(context, inputPath) {
|
|
9307
|
+
const option = context.ssgOptions.contributors;
|
|
9308
|
+
if (!option) return;
|
|
9309
|
+
try {
|
|
9310
|
+
return applyContributorOptions(context.napi?.getGitContributors(inputPath, context.root) ?? [], option);
|
|
9311
|
+
} catch {
|
|
9312
|
+
return [];
|
|
9313
|
+
}
|
|
9314
|
+
}
|
|
6394
9315
|
function resolvePaginationOption(value) {
|
|
6395
9316
|
return value === true || typeof value === "object" && value !== null;
|
|
6396
9317
|
}
|
|
9318
|
+
function resolveJsonLdOption(value) {
|
|
9319
|
+
if (value === true) return { breadcrumbs: true };
|
|
9320
|
+
if (value && typeof value === "object") {
|
|
9321
|
+
const publisher = resolveJsonLdPublisher(value.publisher);
|
|
9322
|
+
return {
|
|
9323
|
+
breadcrumbs: value.breadcrumbs !== false,
|
|
9324
|
+
...publisher ? { publisher } : {}
|
|
9325
|
+
};
|
|
9326
|
+
}
|
|
9327
|
+
return false;
|
|
9328
|
+
}
|
|
9329
|
+
function resolveJsonLdPublisher(publisher) {
|
|
9330
|
+
if (!publisher || typeof publisher !== "object") return;
|
|
9331
|
+
const name = publisher.name?.trim();
|
|
9332
|
+
const url = publisher.url?.trim();
|
|
9333
|
+
if (!name && !url) return;
|
|
9334
|
+
return {
|
|
9335
|
+
...name ? { name } : {},
|
|
9336
|
+
...url ? { url } : {}
|
|
9337
|
+
};
|
|
9338
|
+
}
|
|
6397
9339
|
function resolveReaderChromeOption(value) {
|
|
6398
9340
|
if (value === true) return {
|
|
6399
9341
|
copy: true,
|
|
@@ -6522,7 +9464,7 @@ function localeCodesFor(locales) {
|
|
|
6522
9464
|
async function generateHtmlPage(pageData, navGroups, siteName, base, ogImage, theme, locale, availableLocales, pagination = false, readerChrome = false, breadcrumbs = false, localeSwitcher = false, localePaths, a11y = false, team = {
|
|
6523
9465
|
enabled: false,
|
|
6524
9466
|
members: []
|
|
6525
|
-
}, pageChrome = false, breadcrumbRootHref) {
|
|
9467
|
+
}, pageChrome = false, breadcrumbRootHref, jsonLd = false, siteUrl) {
|
|
6526
9468
|
const mod = await importNapiModule();
|
|
6527
9469
|
const tocForRust = pageData.toc.map(toRustTocEntry);
|
|
6528
9470
|
const navGroupsForRust = convertNavGroupsForRust(navGroups);
|
|
@@ -6564,6 +9506,7 @@ async function generateHtmlPage(pageData, navGroups, siteName, base, ogImage, th
|
|
|
6564
9506
|
content: pageData.content,
|
|
6565
9507
|
toc: tocForRust,
|
|
6566
9508
|
lastUpdated: pageData.lastUpdated,
|
|
9509
|
+
contributors: pageData.contributors,
|
|
6567
9510
|
path: pageData.path,
|
|
6568
9511
|
entryPage: entryPageForRust,
|
|
6569
9512
|
prev: pageData.prev,
|
|
@@ -6590,14 +9533,19 @@ async function generateHtmlPage(pageData, navGroups, siteName, base, ogImage, th
|
|
|
6590
9533
|
localePaths,
|
|
6591
9534
|
a11y: a11y ? { skipLinkLabel: a11y.skipLinkLabel } : void 0,
|
|
6592
9535
|
team,
|
|
6593
|
-
pageChrome
|
|
9536
|
+
pageChrome,
|
|
9537
|
+
jsonLd: jsonLd ? {
|
|
9538
|
+
breadcrumbs: jsonLd.breadcrumbs,
|
|
9539
|
+
publisher: jsonLd.publisher,
|
|
9540
|
+
siteUrl
|
|
9541
|
+
} : void 0
|
|
6594
9542
|
});
|
|
6595
9543
|
}
|
|
6596
9544
|
async function externalizeSharedPageAssets(pages, outDir, base) {
|
|
6597
9545
|
const optimized = (await importNapiModule()).externalizeSsgAssets(pages, outDir, base);
|
|
6598
9546
|
await Promise.all(optimized.assets.map(async (asset) => {
|
|
6599
|
-
await fs$
|
|
6600
|
-
await fs$
|
|
9547
|
+
await fs$3.mkdir(path$2.dirname(asset.outputPath), { recursive: true });
|
|
9548
|
+
await fs$3.writeFile(asset.outputPath, asset.content, "utf-8");
|
|
6601
9549
|
}));
|
|
6602
9550
|
return {
|
|
6603
9551
|
pages: optimized.pages,
|
|
@@ -6677,10 +9625,31 @@ async function buildSsg(options, root) {
|
|
|
6677
9625
|
errors.push(...collected.errors);
|
|
6678
9626
|
const { outputPages, listedPages } = applyPublishState(context, collected);
|
|
6679
9627
|
remapPermalinkNav(context, listedPages);
|
|
9628
|
+
await applyPageResources(context, outputPages, generatedFiles, errors);
|
|
6680
9629
|
await generateOgImageAssets(context, collected, generatedFiles, errors);
|
|
6681
9630
|
injectRelatedPages(outputPages, listedPages, context.options.taxonomies);
|
|
9631
|
+
const blog = context.options.blog ?? context.ssgOptions.blog;
|
|
9632
|
+
await injectBlogPostMeta({
|
|
9633
|
+
pages: outputPages,
|
|
9634
|
+
listed: listedPages,
|
|
9635
|
+
options: blog,
|
|
9636
|
+
srcDir: context.srcDir,
|
|
9637
|
+
collections: context.options.collections,
|
|
9638
|
+
base: context.base
|
|
9639
|
+
});
|
|
6682
9640
|
const generatedPages = await generateHtmlPages(context, outputPages, collected, errors);
|
|
6683
9641
|
await appendNotFoundPage(generatedPages, context, collected, errors);
|
|
9642
|
+
await appendSectionIndexPages({
|
|
9643
|
+
generatedPages,
|
|
9644
|
+
collectedPages: collected.pageResults,
|
|
9645
|
+
listedPages,
|
|
9646
|
+
options: context.ssgOptions.sectionIndex,
|
|
9647
|
+
outDir: context.outDir,
|
|
9648
|
+
base: context.base,
|
|
9649
|
+
extension: context.ssgOptions.extension,
|
|
9650
|
+
errors,
|
|
9651
|
+
render: (page) => renderSsgPage(context, toSectionIndexProcessResult(page), collected, listedPages)
|
|
9652
|
+
});
|
|
6684
9653
|
await appendTaxonomyPages({
|
|
6685
9654
|
generatedPages,
|
|
6686
9655
|
listedPages,
|
|
@@ -6690,8 +9659,20 @@ async function buildSsg(options, root) {
|
|
|
6690
9659
|
errors,
|
|
6691
9660
|
render: (page) => renderSsgPage(context, toTaxonomyProcessResult(page), collected, listedPages)
|
|
6692
9661
|
});
|
|
9662
|
+
await appendBlogPages({
|
|
9663
|
+
generatedPages,
|
|
9664
|
+
listedPages,
|
|
9665
|
+
options: blog,
|
|
9666
|
+
collections: context.options.collections,
|
|
9667
|
+
srcDir: context.srcDir,
|
|
9668
|
+
outDir: context.outDir,
|
|
9669
|
+
base: context.base,
|
|
9670
|
+
errors,
|
|
9671
|
+
render: (page) => renderSsgPage(context, toBlogProcessResult(page), collected, listedPages)
|
|
9672
|
+
});
|
|
6693
9673
|
await applyDocumentationVersions(generatedPages, context, errors);
|
|
6694
9674
|
await writeGeneratedPages(generatedPages, context, generatedFiles, listedPages, outputPages, errors);
|
|
9675
|
+
if (options.math?.enabled) generatedFiles.push(...await copyKatexAssets(outDir));
|
|
6695
9676
|
return {
|
|
6696
9677
|
files: generatedFiles,
|
|
6697
9678
|
errors,
|
|
@@ -6701,7 +9682,7 @@ async function buildSsg(options, root) {
|
|
|
6701
9682
|
async function cleanOutputDirectory(ssgOptions, outDir) {
|
|
6702
9683
|
if (!ssgOptions.clean) return;
|
|
6703
9684
|
try {
|
|
6704
|
-
await fs$
|
|
9685
|
+
await fs$3.rm(outDir, {
|
|
6705
9686
|
recursive: true,
|
|
6706
9687
|
force: true
|
|
6707
9688
|
});
|
|
@@ -6720,7 +9701,7 @@ async function createBuildSsgContext(options, root, srcDir, outDir, markdownFile
|
|
|
6720
9701
|
navItems: resolveNavigationGroups(ssgOptions.navigation, base, ssgOptions.extension) ?? (ssgOptions.theme?.sidebar.length ? buildThemeNavItems(ssgOptions.theme.sidebar, base, ssgOptions.extension) : buildNavItems(markdownFiles, srcDir, base, ssgOptions.extension)),
|
|
6721
9702
|
siteName: await resolveSiteName$1(root, ssgOptions),
|
|
6722
9703
|
shouldGenerateOgImages: shouldGenerateOgImages(options),
|
|
6723
|
-
napi: ssgOptions.lastUpdated ? await importNapiModule() : void 0
|
|
9704
|
+
napi: ssgOptions.lastUpdated || ssgOptions.contributors ? await importNapiModule() : void 0
|
|
6724
9705
|
};
|
|
6725
9706
|
}
|
|
6726
9707
|
/**
|
|
@@ -6739,12 +9720,33 @@ async function resolveSiteName$1(root, ssgOptions) {
|
|
|
6739
9720
|
if (ssgOptions.siteName) return ssgOptions.siteName;
|
|
6740
9721
|
try {
|
|
6741
9722
|
const pkgPath = path$2.join(root, "package.json");
|
|
6742
|
-
const pkg = JSON.parse(await fs$
|
|
9723
|
+
const pkg = JSON.parse(await fs$3.readFile(pkgPath, "utf-8"));
|
|
6743
9724
|
return pkg.name ? formatTitle(pkg.name) : "Documentation";
|
|
6744
9725
|
} catch {
|
|
6745
9726
|
return "Documentation";
|
|
6746
9727
|
}
|
|
6747
9728
|
}
|
|
9729
|
+
async function applyPageResources(context, pages, generatedFiles, errors) {
|
|
9730
|
+
const options = context.options.resources;
|
|
9731
|
+
if (!options?.enabled) return;
|
|
9732
|
+
const cacheDir = path$2.join(context.root, ".cache", "ox-content-resources");
|
|
9733
|
+
const fatal = [];
|
|
9734
|
+
for (const page of pages) {
|
|
9735
|
+
const processed = await processPageResources({
|
|
9736
|
+
html: page.transformedHtml,
|
|
9737
|
+
inputPath: page.inputPath,
|
|
9738
|
+
outputPath: page.routePaths.outputPath,
|
|
9739
|
+
srcDir: context.srcDir,
|
|
9740
|
+
options,
|
|
9741
|
+
cacheDir
|
|
9742
|
+
});
|
|
9743
|
+
page.transformedHtml = processed.html;
|
|
9744
|
+
generatedFiles.push(...processed.files);
|
|
9745
|
+
errors.push(...processed.errors);
|
|
9746
|
+
fatal.push(...processed.fatal);
|
|
9747
|
+
}
|
|
9748
|
+
if (fatal.length > 0) throw new PageResourceError(fatal);
|
|
9749
|
+
}
|
|
6748
9750
|
function applyPermalinkRoutes(context, collected) {
|
|
6749
9751
|
if (!context.options.permalinks?.enabled && !context.options.cascade?.enabled) return;
|
|
6750
9752
|
const routed = applySsgPageRoutes({
|
|
@@ -6810,7 +9812,7 @@ function applyPublishState(context, collected) {
|
|
|
6810
9812
|
};
|
|
6811
9813
|
}
|
|
6812
9814
|
async function transformSsgPage(context, inputPath) {
|
|
6813
|
-
const result = await transformMarkdown(await fs$
|
|
9815
|
+
const result = await transformMarkdown(await fs$3.readFile(inputPath, "utf-8"), inputPath, context.options, {
|
|
6814
9816
|
convertMdLinks: true,
|
|
6815
9817
|
baseUrl: context.base,
|
|
6816
9818
|
sourcePath: inputPath
|
|
@@ -6824,7 +9826,8 @@ async function transformSsgPage(context, inputPath) {
|
|
|
6824
9826
|
transformedHtml,
|
|
6825
9827
|
title,
|
|
6826
9828
|
description: frontmatter.description,
|
|
6827
|
-
lastUpdated: context.napi?.getGitLastUpdated(inputPath, context.root) ?? void 0,
|
|
9829
|
+
lastUpdated: context.ssgOptions.lastUpdated ? context.napi?.getGitLastUpdated(inputPath, context.root) ?? void 0 : void 0,
|
|
9830
|
+
contributors: contributorsForPage(context, inputPath),
|
|
6828
9831
|
frontmatter,
|
|
6829
9832
|
toc: result.toc
|
|
6830
9833
|
};
|
|
@@ -6979,7 +9982,7 @@ async function renderSsgPage(context, pageResult, collected, allPageResults) {
|
|
|
6979
9982
|
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 ?? {
|
|
6980
9983
|
enabled: false,
|
|
6981
9984
|
members: []
|
|
6982
|
-
}, context.ssgOptions.pageChrome, versionNavigation?.root.href);
|
|
9985
|
+
}, context.ssgOptions.pageChrome, versionNavigation?.root.href, context.ssgOptions.jsonLd, context.ssgOptions.siteUrl);
|
|
6983
9986
|
}
|
|
6984
9987
|
function rewritePagerOverride(pager, context) {
|
|
6985
9988
|
return pager?.href ? {
|
|
@@ -6995,6 +9998,7 @@ function toThemePageData(pageResult) {
|
|
|
6995
9998
|
html: pageResult.transformedHtml,
|
|
6996
9999
|
toc: pageResult.toc,
|
|
6997
10000
|
lastUpdated: pageResult.lastUpdated,
|
|
10001
|
+
contributors: pageResult.contributors,
|
|
6998
10002
|
path: pageResult.inputPath,
|
|
6999
10003
|
url: pageResult.routePaths.href,
|
|
7000
10004
|
frontmatter: pageResult.frontmatter,
|
|
@@ -7025,6 +10029,7 @@ function createSsgPageData(pageResult) {
|
|
|
7025
10029
|
content: pageResult.transformedHtml,
|
|
7026
10030
|
toc: pageResult.toc,
|
|
7027
10031
|
lastUpdated: pageResult.lastUpdated,
|
|
10032
|
+
contributors: pageResult.contributors,
|
|
7028
10033
|
frontmatter,
|
|
7029
10034
|
path: pageResult.routePaths.urlPath,
|
|
7030
10035
|
href: pageResult.routePaths.href,
|
|
@@ -7041,7 +10046,7 @@ async function appendNotFoundPage(generatedPages, context, collected, errors) {
|
|
|
7041
10046
|
const sourcePath = resolveNotFoundSourcePath(context.srcDir, notFound.source);
|
|
7042
10047
|
const outputPath = resolveNotFoundOutputPath(context.outDir, notFound.output);
|
|
7043
10048
|
try {
|
|
7044
|
-
const pageResult = await transformNotFoundMarkdown(context, sourcePath, await fileExists(sourcePath) ? await fs$
|
|
10049
|
+
const pageResult = await transformNotFoundMarkdown(context, sourcePath, await fileExists(sourcePath) ? await fs$3.readFile(sourcePath, "utf8") : FALLBACK_NOT_FOUND_MARKDOWN);
|
|
7045
10050
|
pageResult.routePaths = {
|
|
7046
10051
|
...pageResult.routePaths,
|
|
7047
10052
|
outputPath,
|
|
@@ -7059,7 +10064,7 @@ async function appendNotFoundPage(generatedPages, context, collected, errors) {
|
|
|
7059
10064
|
}
|
|
7060
10065
|
async function fileExists(filePath) {
|
|
7061
10066
|
try {
|
|
7062
|
-
await fs$
|
|
10067
|
+
await fs$3.access(filePath);
|
|
7063
10068
|
return true;
|
|
7064
10069
|
} catch {
|
|
7065
10070
|
return false;
|
|
@@ -7124,6 +10129,17 @@ async function applyDocumentationVersions(generatedPages, context, errors) {
|
|
|
7124
10129
|
redirects: snapContext.options.redirects?.map
|
|
7125
10130
|
});
|
|
7126
10131
|
const snapPages = await generateHtmlPages(snapContext, outputPages, snapCollected, errors);
|
|
10132
|
+
await appendSectionIndexPages({
|
|
10133
|
+
generatedPages: snapPages,
|
|
10134
|
+
collectedPages: snapCollected.pageResults,
|
|
10135
|
+
listedPages,
|
|
10136
|
+
options: snapContext.ssgOptions.sectionIndex,
|
|
10137
|
+
outDir: snapContext.outDir,
|
|
10138
|
+
base: snapContext.base,
|
|
10139
|
+
extension: snapContext.ssgOptions.extension,
|
|
10140
|
+
errors,
|
|
10141
|
+
render: (page) => renderSsgPage(snapContext, toSectionIndexProcessResult(page), snapCollected, listedPages)
|
|
10142
|
+
});
|
|
7127
10143
|
generatedPages.push(...snapPages);
|
|
7128
10144
|
if (context.options.search?.enabled) try {
|
|
7129
10145
|
await writeSnapshotSearchIndex({
|
|
@@ -7150,9 +10166,24 @@ function pageAliases(frontmatter) {
|
|
|
7150
10166
|
async function writeGeneratedPages(generatedPages, context, generatedFiles, listedPages, outputPages, errors) {
|
|
7151
10167
|
const optimizedOutput = await externalizeSharedPageAssets(generatedPages, context.outDir, context.base);
|
|
7152
10168
|
generatedFiles.push(...optimizedOutput.assets);
|
|
10169
|
+
const pwa = await writePwaFiles({
|
|
10170
|
+
outDir: context.outDir,
|
|
10171
|
+
siteUrl: context.ssgOptions.siteUrl,
|
|
10172
|
+
base: context.base,
|
|
10173
|
+
siteName: context.siteName,
|
|
10174
|
+
options: context.options.pwa
|
|
10175
|
+
});
|
|
10176
|
+
generatedFiles.push(...pwa.files);
|
|
10177
|
+
if (pwa.warning) {
|
|
10178
|
+
errors.push(pwa.warning);
|
|
10179
|
+
console.warn(pwa.warning);
|
|
10180
|
+
} else if (!context.ssgOptions.bare && context.options.pwa?.enabled) for (const page of optimizedOutput.pages) page.html = injectPwaPageTags(page.html, {
|
|
10181
|
+
options: context.options.pwa,
|
|
10182
|
+
base: context.base
|
|
10183
|
+
});
|
|
7153
10184
|
for (const page of optimizedOutput.pages) {
|
|
7154
|
-
await fs$
|
|
7155
|
-
await fs$
|
|
10185
|
+
await fs$3.mkdir(path$2.dirname(page.outputPath), { recursive: true });
|
|
10186
|
+
await fs$3.writeFile(page.outputPath, page.html, "utf-8");
|
|
7156
10187
|
generatedFiles.push(page.outputPath);
|
|
7157
10188
|
}
|
|
7158
10189
|
const siteMaps = await writeSiteMapFiles({
|
|
@@ -7277,14 +10308,14 @@ async function resolveMarkdownFile(url, srcDir, extensions) {
|
|
|
7277
10308
|
for (const relativePath of directCandidates) {
|
|
7278
10309
|
const filePath = path$2.join(srcDir, relativePath);
|
|
7279
10310
|
try {
|
|
7280
|
-
await fs$
|
|
10311
|
+
await fs$3.access(filePath);
|
|
7281
10312
|
return filePath;
|
|
7282
10313
|
} catch {}
|
|
7283
10314
|
}
|
|
7284
10315
|
for (const extension of extensions) {
|
|
7285
10316
|
const indexPath = path$2.join(srcDir, routePath, `index${extension}`);
|
|
7286
10317
|
try {
|
|
7287
|
-
await fs$
|
|
10318
|
+
await fs$3.access(indexPath);
|
|
7288
10319
|
return indexPath;
|
|
7289
10320
|
} catch {}
|
|
7290
10321
|
}
|
|
@@ -7328,7 +10359,7 @@ async function resolveSiteName(options, root) {
|
|
|
7328
10359
|
if (options.ssg.siteName) return options.ssg.siteName;
|
|
7329
10360
|
try {
|
|
7330
10361
|
const pkgPath = path$2.join(root, "package.json");
|
|
7331
|
-
const pkg = JSON.parse(await fs$
|
|
10362
|
+
const pkg = JSON.parse(await fs$3.readFile(pkgPath, "utf-8"));
|
|
7332
10363
|
if (pkg.name) return formatTitle(pkg.name);
|
|
7333
10364
|
} catch {}
|
|
7334
10365
|
return "Documentation";
|
|
@@ -7340,7 +10371,7 @@ async function renderPage$1(filePath, options, navGroups, siteName, base, root,
|
|
|
7340
10371
|
const srcDir = path$2.resolve(root, options.srcDir);
|
|
7341
10372
|
resetTabGroupCounter();
|
|
7342
10373
|
resetIslandCounter();
|
|
7343
|
-
const result = await transformMarkdown(await fs$
|
|
10374
|
+
const result = await transformMarkdown(await fs$3.readFile(filePath, "utf-8"), filePath, options, {
|
|
7344
10375
|
convertMdLinks: true,
|
|
7345
10376
|
baseUrl: base,
|
|
7346
10377
|
sourcePath: filePath
|
|
@@ -7412,7 +10443,7 @@ async function renderPage$1(filePath, options, navGroups, siteName, base, root,
|
|
|
7412
10443
|
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 ?? {
|
|
7413
10444
|
enabled: false,
|
|
7414
10445
|
members: []
|
|
7415
|
-
}, options.ssg.pageChrome);
|
|
10446
|
+
}, options.ssg.pageChrome, void 0, options.ssg.jsonLd, options.ssg.siteUrl);
|
|
7416
10447
|
html = injectViteHmrClient(html);
|
|
7417
10448
|
return html;
|
|
7418
10449
|
}
|
|
@@ -7540,7 +10571,7 @@ async function collectPages(options, root) {
|
|
|
7540
10571
|
const pages = [];
|
|
7541
10572
|
const generateOgImage = options.ogImage || options.ssg.generateOgImage;
|
|
7542
10573
|
for (const file of files.sort()) {
|
|
7543
|
-
const content = fs.readFileSync(file, "utf-8");
|
|
10574
|
+
const content = fs$1.readFileSync(file, "utf-8");
|
|
7544
10575
|
const frontmatter = normalizeVitePressFrontmatter(parseFrontmatter(content));
|
|
7545
10576
|
if (frontmatter.layout === "entry") continue;
|
|
7546
10577
|
const title = extractTitle(content, frontmatter);
|
|
@@ -7864,7 +10895,7 @@ function createI18nPlugin(resolvedOptions) {
|
|
|
7864
10895
|
async buildStart() {
|
|
7865
10896
|
if (!i18nOptions || !i18nOptions.check) return;
|
|
7866
10897
|
const dictDir = path$2.resolve(root, i18nOptions.dir);
|
|
7867
|
-
if (!fs.existsSync(dictDir)) {
|
|
10898
|
+
if (!fs$1.existsSync(dictDir)) {
|
|
7868
10899
|
console.warn(`[ox-content:i18n] Dictionary directory not found: ${dictDir}`);
|
|
7869
10900
|
return;
|
|
7870
10901
|
}
|
|
@@ -7880,7 +10911,7 @@ function createI18nPlugin(resolvedOptions) {
|
|
|
7880
10911
|
configureServer(server) {
|
|
7881
10912
|
if (!i18nOptions) return;
|
|
7882
10913
|
const dictDir = path$2.resolve(root, i18nOptions.dir);
|
|
7883
|
-
if (fs.existsSync(dictDir)) {
|
|
10914
|
+
if (fs$1.existsSync(dictDir)) {
|
|
7884
10915
|
server.watcher.add(dictDir);
|
|
7885
10916
|
server.watcher.on("change", (filePath) => {
|
|
7886
10917
|
if (!filePath.startsWith(dictDir)) return;
|
|
@@ -7944,13 +10975,6 @@ function resolveCardOptions(options) {
|
|
|
7944
10975
|
return { enabled: options.enabled ?? true };
|
|
7945
10976
|
}
|
|
7946
10977
|
//#endregion
|
|
7947
|
-
//#region src/file-tree-options.ts
|
|
7948
|
-
function resolveFileTreeOptions(options) {
|
|
7949
|
-
if (!options) return { enabled: false };
|
|
7950
|
-
if (options === true) return { enabled: true };
|
|
7951
|
-
return { enabled: options.enabled ?? true };
|
|
7952
|
-
}
|
|
7953
|
-
//#endregion
|
|
7954
10978
|
//#region src/include-options.ts
|
|
7955
10979
|
function resolveIncludeOptions(options) {
|
|
7956
10980
|
if (!options) return { enabled: false };
|
|
@@ -8071,6 +11095,353 @@ async function* renderMarkdownStream(chunks, options = {}) {
|
|
|
8071
11095
|
yield renderer.finish();
|
|
8072
11096
|
}
|
|
8073
11097
|
//#endregion
|
|
11098
|
+
//#region src/mdx-islands.ts
|
|
11099
|
+
/**
|
|
11100
|
+
* Discover registered MDX islands from the mdast tree or rendered HTML.
|
|
11101
|
+
*
|
|
11102
|
+
* Framework plugins use this instead of a source regex when MDX is on, so
|
|
11103
|
+
* nested JSX, expression attributes, and fragments stay visible. Names that
|
|
11104
|
+
* are not in the global `components` map and are not document-local import
|
|
11105
|
+
* bindings are left as static HTML.
|
|
11106
|
+
*/
|
|
11107
|
+
const OX_ISLAND_NAME = /data-ox-island="([^"]+)"/g;
|
|
11108
|
+
/**
|
|
11109
|
+
* Collect named MDX JSX tags from a parsed mdast tree (JSON from NAPI `parse()`).
|
|
11110
|
+
* Fragments (`name: null`) and non-JSX nodes are ignored. Walks nested children
|
|
11111
|
+
* so inner islands are found.
|
|
11112
|
+
*/
|
|
11113
|
+
function collectMdxJsxNamesFromAst(ast) {
|
|
11114
|
+
const names = /* @__PURE__ */ new Set();
|
|
11115
|
+
walkMdast(ast, names);
|
|
11116
|
+
return [...names];
|
|
11117
|
+
}
|
|
11118
|
+
/**
|
|
11119
|
+
* Collect `data-ox-island` names from Rust-rendered HTML.
|
|
11120
|
+
* Used when an AST walk is unavailable.
|
|
11121
|
+
*/
|
|
11122
|
+
function collectMdxIslandNamesFromHtml(html) {
|
|
11123
|
+
const names = /* @__PURE__ */ new Set();
|
|
11124
|
+
OX_ISLAND_NAME.lastIndex = 0;
|
|
11125
|
+
let match;
|
|
11126
|
+
while ((match = OX_ISLAND_NAME.exec(html)) !== null) {
|
|
11127
|
+
const name = match[1];
|
|
11128
|
+
if (name) names.add(decodeHtmlAttr$1(name));
|
|
11129
|
+
}
|
|
11130
|
+
return [...names];
|
|
11131
|
+
}
|
|
11132
|
+
/** Keep names that exist on the global component map, in first-seen order. */
|
|
11133
|
+
function intersectRegisteredComponentNames(names, components) {
|
|
11134
|
+
return intersectHydratableComponentNames(names, components);
|
|
11135
|
+
}
|
|
11136
|
+
/**
|
|
11137
|
+
* Keep names that are either globally registered or document-local bindings.
|
|
11138
|
+
*/
|
|
11139
|
+
function intersectHydratableComponentNames(names, components, localNames) {
|
|
11140
|
+
const local = localNames ? new Set(localNames) : null;
|
|
11141
|
+
const used = [];
|
|
11142
|
+
for (const name of names) if ((local?.has(name) || isRegisteredComponent(name, components)) && !used.includes(name)) used.push(name);
|
|
11143
|
+
return used;
|
|
11144
|
+
}
|
|
11145
|
+
/**
|
|
11146
|
+
* Resolve registered island names for an MDX document.
|
|
11147
|
+
*
|
|
11148
|
+
* Prefers a NAPI `parse()` AST walk. Falls back to rendered `data-ox-island`
|
|
11149
|
+
* names so plugins still hydrate if #659 metadata is not present.
|
|
11150
|
+
*/
|
|
11151
|
+
async function discoverRegisteredMdxComponents(input) {
|
|
11152
|
+
return intersectHydratableComponentNames(await tryCollectNamesFromParse(input.source) ?? (input.html !== void 0 ? collectMdxIslandNamesFromHtml(input.html) : []), input.components, input.localNames);
|
|
11153
|
+
}
|
|
11154
|
+
function isRegisteredComponent(name, components) {
|
|
11155
|
+
if (isMapRegistry$1(components)) return components.has(name);
|
|
11156
|
+
if (isPlainObjectRegistry(components)) return Object.prototype.hasOwnProperty.call(components, name);
|
|
11157
|
+
for (const entry of components) if (entry === name) return true;
|
|
11158
|
+
return false;
|
|
11159
|
+
}
|
|
11160
|
+
async function tryCollectNamesFromParse(source) {
|
|
11161
|
+
try {
|
|
11162
|
+
const parsed = (await importNapiModule()).parse(source, {
|
|
11163
|
+
mdx: true,
|
|
11164
|
+
gfm: true
|
|
11165
|
+
});
|
|
11166
|
+
if (!parsed.ast) return null;
|
|
11167
|
+
return collectMdxJsxNamesFromAst(JSON.parse(parsed.ast));
|
|
11168
|
+
} catch {
|
|
11169
|
+
return null;
|
|
11170
|
+
}
|
|
11171
|
+
}
|
|
11172
|
+
function walkMdast(node, names) {
|
|
11173
|
+
if (!node || typeof node !== "object") return;
|
|
11174
|
+
const record = node;
|
|
11175
|
+
if ((record.type === "mdxJsxFlowElement" || record.type === "mdxJsxTextElement") && typeof record.name === "string" && record.name) names.add(record.name);
|
|
11176
|
+
if (Array.isArray(record.children)) for (const child of record.children) walkMdast(child, names);
|
|
11177
|
+
}
|
|
11178
|
+
function isMapRegistry$1(value) {
|
|
11179
|
+
return typeof value === "object" && value !== null && typeof value.has === "function" && typeof value.get === "function";
|
|
11180
|
+
}
|
|
11181
|
+
function isPlainObjectRegistry(value) {
|
|
11182
|
+
return Object.prototype.toString.call(value) === "[object Object]";
|
|
11183
|
+
}
|
|
11184
|
+
function decodeHtmlAttr$1(value) {
|
|
11185
|
+
return value.replaceAll(""", "\"").replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&");
|
|
11186
|
+
}
|
|
11187
|
+
//#endregion
|
|
11188
|
+
//#region src/document-imports.ts
|
|
11189
|
+
/**
|
|
11190
|
+
* Resolve MDX component imports relative to the document that declared them.
|
|
11191
|
+
*
|
|
11192
|
+
* Only `./` and `../` specifiers become island bindings. Bare, package, and
|
|
11193
|
+
* remote specifiers are reported and ignored. A specifier that leaves the
|
|
11194
|
+
* configured content root is rejected with a diagnostic.
|
|
11195
|
+
*/
|
|
11196
|
+
function resolveContentRootPath(input) {
|
|
11197
|
+
if (input.contentRoot) return path.resolve(input.contentRoot);
|
|
11198
|
+
const root = input.root ?? process.cwd();
|
|
11199
|
+
return path.resolve(root, input.srcDir ?? ".");
|
|
11200
|
+
}
|
|
11201
|
+
function stripViteQuery(id) {
|
|
11202
|
+
return id.split("?")[0].split("#")[0];
|
|
11203
|
+
}
|
|
11204
|
+
function resolveDocumentComponentImports(input) {
|
|
11205
|
+
const documentPath = stripViteQuery(input.documentPath);
|
|
11206
|
+
const documentDir = path.dirname(documentPath);
|
|
11207
|
+
const contentRoot = resolveContentRootPath(input);
|
|
11208
|
+
const diagnostics = [];
|
|
11209
|
+
const candidates = [];
|
|
11210
|
+
for (const statement of input.imports) {
|
|
11211
|
+
const specifier = statement.source;
|
|
11212
|
+
if (!isRelativeSpecifier(specifier)) {
|
|
11213
|
+
diagnostics.push({
|
|
11214
|
+
code: "not-relative",
|
|
11215
|
+
message: `Document component import "${specifier}" is not relative and was ignored.`,
|
|
11216
|
+
specifier
|
|
11217
|
+
});
|
|
11218
|
+
continue;
|
|
11219
|
+
}
|
|
11220
|
+
for (const spec of statement.specifiers) {
|
|
11221
|
+
if (spec.kind === "namespace") continue;
|
|
11222
|
+
const resolvedPath = resolveExistingPath(path.resolve(documentDir, specifier));
|
|
11223
|
+
if (!isInsideRoot(resolvedPath, contentRoot)) {
|
|
11224
|
+
diagnostics.push({
|
|
11225
|
+
code: "escapes-root",
|
|
11226
|
+
message: `Document component import "${specifier}" escapes the content root.`,
|
|
11227
|
+
specifier,
|
|
11228
|
+
localName: spec.local
|
|
11229
|
+
});
|
|
11230
|
+
continue;
|
|
11231
|
+
}
|
|
11232
|
+
candidates.push({
|
|
11233
|
+
localName: spec.local,
|
|
11234
|
+
specifier,
|
|
11235
|
+
resolvedPath,
|
|
11236
|
+
importPathRelativeToDocument: toDocumentRelativeImport(documentDir, resolvedPath),
|
|
11237
|
+
imported: spec.imported,
|
|
11238
|
+
kind: spec.kind
|
|
11239
|
+
});
|
|
11240
|
+
}
|
|
11241
|
+
}
|
|
11242
|
+
const counts = /* @__PURE__ */ new Map();
|
|
11243
|
+
for (const binding of candidates) counts.set(binding.localName, (counts.get(binding.localName) ?? 0) + 1);
|
|
11244
|
+
const bindings = [];
|
|
11245
|
+
const reportedDuplicates = /* @__PURE__ */ new Set();
|
|
11246
|
+
for (const binding of candidates) {
|
|
11247
|
+
if ((counts.get(binding.localName) ?? 0) > 1) {
|
|
11248
|
+
if (!reportedDuplicates.has(binding.localName)) {
|
|
11249
|
+
reportedDuplicates.add(binding.localName);
|
|
11250
|
+
diagnostics.push({
|
|
11251
|
+
code: "duplicate-binding",
|
|
11252
|
+
message: `Document component name "${binding.localName}" is imported more than once.`,
|
|
11253
|
+
specifier: binding.specifier,
|
|
11254
|
+
localName: binding.localName
|
|
11255
|
+
});
|
|
11256
|
+
}
|
|
11257
|
+
continue;
|
|
11258
|
+
}
|
|
11259
|
+
bindings.push(binding);
|
|
11260
|
+
}
|
|
11261
|
+
return {
|
|
11262
|
+
bindings,
|
|
11263
|
+
diagnostics
|
|
11264
|
+
};
|
|
11265
|
+
}
|
|
11266
|
+
function isRelativeSpecifier(source) {
|
|
11267
|
+
return source.startsWith("./") || source.startsWith("../");
|
|
11268
|
+
}
|
|
11269
|
+
function resolveExistingPath(filePath) {
|
|
11270
|
+
try {
|
|
11271
|
+
return fs.realpathSync(filePath);
|
|
11272
|
+
} catch {
|
|
11273
|
+
return path.normalize(filePath);
|
|
11274
|
+
}
|
|
11275
|
+
}
|
|
11276
|
+
function isInsideRoot(resolvedPath, root) {
|
|
11277
|
+
const relative = path.relative(resolveExistingPath(root), resolvedPath);
|
|
11278
|
+
return relative === "" || !relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative);
|
|
11279
|
+
}
|
|
11280
|
+
function toDocumentRelativeImport(documentDir, resolvedPath) {
|
|
11281
|
+
const relative = path.relative(documentDir, resolvedPath).replace(/\\/g, "/");
|
|
11282
|
+
return relative.startsWith(".") ? relative : `./${relative}`;
|
|
11283
|
+
}
|
|
11284
|
+
//#endregion
|
|
11285
|
+
//#region src/document-islands.ts
|
|
11286
|
+
/**
|
|
11287
|
+
* Combine document-local import resolution with MDX island discovery.
|
|
11288
|
+
*/
|
|
11289
|
+
async function discoverDocumentMdxIslands(input) {
|
|
11290
|
+
const resolved = resolveDocumentComponentImports({
|
|
11291
|
+
imports: input.imports,
|
|
11292
|
+
documentPath: input.documentPath,
|
|
11293
|
+
contentRoot: input.contentRoot ?? resolveContentRootPath(input),
|
|
11294
|
+
srcDir: input.srcDir
|
|
11295
|
+
});
|
|
11296
|
+
const localBindings = new Map(resolved.bindings.map((binding) => [binding.localName, binding]));
|
|
11297
|
+
return {
|
|
11298
|
+
usedComponents: await discoverRegisteredMdxComponents({
|
|
11299
|
+
source: input.source,
|
|
11300
|
+
html: input.html,
|
|
11301
|
+
components: input.components,
|
|
11302
|
+
localNames: localBindings.keys()
|
|
11303
|
+
}),
|
|
11304
|
+
localBindings,
|
|
11305
|
+
diagnostics: resolved.diagnostics
|
|
11306
|
+
};
|
|
11307
|
+
}
|
|
11308
|
+
//#endregion
|
|
11309
|
+
//#region src/island-codegen.ts
|
|
11310
|
+
/**
|
|
11311
|
+
* Emit static component imports for framework Markdown modules.
|
|
11312
|
+
*
|
|
11313
|
+
* Document-local bindings win over the global `components` map for that file
|
|
11314
|
+
* only. Two documents that bind the same local name therefore emit different
|
|
11315
|
+
* specifiers and do not share one module id.
|
|
11316
|
+
*/
|
|
11317
|
+
function renderIslandComponentImports(usedComponents, input) {
|
|
11318
|
+
const documentDir = path.dirname(stripViteQuery(input.documentPath));
|
|
11319
|
+
const root = input.root || process.cwd();
|
|
11320
|
+
return usedComponents.map((name) => {
|
|
11321
|
+
const local = input.localBindings?.get(name);
|
|
11322
|
+
if (local) return renderLocalImport(local);
|
|
11323
|
+
const componentPath = getGlobalComponentPath(input.globalComponents, name);
|
|
11324
|
+
if (!componentPath) return "";
|
|
11325
|
+
return renderGlobalImport(name, componentPath, documentDir, root);
|
|
11326
|
+
}).filter(Boolean).join("\n");
|
|
11327
|
+
}
|
|
11328
|
+
function renderLocalImport(binding) {
|
|
11329
|
+
const specifier = binding.importPathRelativeToDocument.replace(/\\/g, "/");
|
|
11330
|
+
if (binding.kind === "default") return `import ${binding.localName} from '${specifier}';`;
|
|
11331
|
+
if (binding.imported === binding.localName) return `import { ${binding.imported} } from '${specifier}';`;
|
|
11332
|
+
return `import { ${binding.imported} as ${binding.localName} } from '${specifier}';`;
|
|
11333
|
+
}
|
|
11334
|
+
function renderGlobalImport(name, componentPath, documentDir, root) {
|
|
11335
|
+
const absolutePath = path.resolve(root, componentPath.replace(/^\.\//, ""));
|
|
11336
|
+
const relativePath = path.relative(documentDir, absolutePath).replace(/\\/g, "/");
|
|
11337
|
+
return `import ${name} from '${relativePath.startsWith(".") ? relativePath : `./${relativePath}`}';`;
|
|
11338
|
+
}
|
|
11339
|
+
function getGlobalComponentPath(components, name) {
|
|
11340
|
+
if (isMapRegistry(components)) return components.get(name);
|
|
11341
|
+
return Object.prototype.hasOwnProperty.call(components, name) ? components[name] : void 0;
|
|
11342
|
+
}
|
|
11343
|
+
function isMapRegistry(value) {
|
|
11344
|
+
return typeof value === "object" && value !== null && typeof value.has === "function" && typeof value.get === "function";
|
|
11345
|
+
}
|
|
11346
|
+
//#endregion
|
|
11347
|
+
//#region src/island-ssr.ts
|
|
11348
|
+
const RUST_PAYLOAD_KEYS = /* @__PURE__ */ new Set([
|
|
11349
|
+
"props",
|
|
11350
|
+
"expressions",
|
|
11351
|
+
"spreads"
|
|
11352
|
+
]);
|
|
11353
|
+
const PAYLOAD_SCRIPT = /^\s*<script type="application\/json">[\s\S]*?<\/script>/;
|
|
11354
|
+
async function applyIslandSsrHtml(html, renderIsland, filePath, names) {
|
|
11355
|
+
const allowed = names ? new Set(names) : null;
|
|
11356
|
+
const islands = findIslandRanges(html);
|
|
11357
|
+
let output = html;
|
|
11358
|
+
for (const island of islands.toReversed()) {
|
|
11359
|
+
if (allowed && !allowed.has(island.name)) continue;
|
|
11360
|
+
const script = output.slice(island.innerStart, island.closeStart).match(PAYLOAD_SCRIPT)?.[0] ?? "";
|
|
11361
|
+
const props = parseIslandProps(island.propsAttr, script);
|
|
11362
|
+
const ssrHtml = await renderIsland(island.name, props, filePath);
|
|
11363
|
+
output = output.slice(0, island.innerStart) + script + ssrHtml + output.slice(island.closeStart);
|
|
11364
|
+
}
|
|
11365
|
+
return output;
|
|
11366
|
+
}
|
|
11367
|
+
function findIslandRanges(html) {
|
|
11368
|
+
const ranges = [];
|
|
11369
|
+
const openRe = /<(div|span)\b([^>]*\bdata-ox-island="([^"]+)"[^>]*)>/gi;
|
|
11370
|
+
let match;
|
|
11371
|
+
while ((match = openRe.exec(html)) !== null) {
|
|
11372
|
+
const tag = match[1];
|
|
11373
|
+
const name = decodeHtmlAttr(match[3] ?? "");
|
|
11374
|
+
if (!tag || !name) continue;
|
|
11375
|
+
const innerStart = match.index + match[0].length;
|
|
11376
|
+
const closeStart = findMatchingClose(html, innerStart, tag);
|
|
11377
|
+
ranges.push({
|
|
11378
|
+
name,
|
|
11379
|
+
innerStart,
|
|
11380
|
+
closeStart,
|
|
11381
|
+
propsAttr: matchAttr(match[2] ?? "", "data-ox-props")
|
|
11382
|
+
});
|
|
11383
|
+
}
|
|
11384
|
+
return ranges;
|
|
11385
|
+
}
|
|
11386
|
+
function findMatchingClose(html, from, tag) {
|
|
11387
|
+
const openNeedle = `<${tag}`;
|
|
11388
|
+
const closeNeedle = `</${tag}>`;
|
|
11389
|
+
let depth = 1;
|
|
11390
|
+
let cursor = from;
|
|
11391
|
+
while (cursor < html.length) {
|
|
11392
|
+
const nextOpen = indexOfTagOpen(html, openNeedle, cursor);
|
|
11393
|
+
const nextClose = html.indexOf(closeNeedle, cursor);
|
|
11394
|
+
if (nextClose === -1) return html.length;
|
|
11395
|
+
if (nextOpen !== -1 && nextOpen < nextClose) {
|
|
11396
|
+
depth += 1;
|
|
11397
|
+
cursor = nextOpen + openNeedle.length;
|
|
11398
|
+
} else {
|
|
11399
|
+
depth -= 1;
|
|
11400
|
+
if (depth === 0) return nextClose;
|
|
11401
|
+
cursor = nextClose + closeNeedle.length;
|
|
11402
|
+
}
|
|
11403
|
+
}
|
|
11404
|
+
return html.length;
|
|
11405
|
+
}
|
|
11406
|
+
function indexOfTagOpen(html, openNeedle, from) {
|
|
11407
|
+
let cursor = from;
|
|
11408
|
+
while (cursor < html.length) {
|
|
11409
|
+
const index = html.indexOf(openNeedle, cursor);
|
|
11410
|
+
if (index === -1) return -1;
|
|
11411
|
+
const next = html[index + openNeedle.length];
|
|
11412
|
+
if (next === " " || next === ">" || next === " " || next === "\n" || next === "/") return index;
|
|
11413
|
+
cursor = index + openNeedle.length;
|
|
11414
|
+
}
|
|
11415
|
+
return -1;
|
|
11416
|
+
}
|
|
11417
|
+
function matchAttr(attrs, name) {
|
|
11418
|
+
const match = new RegExp(`\\b${name}="([^"]*)"`, "i").exec(attrs);
|
|
11419
|
+
return match?.[1] === void 0 ? void 0 : decodeHtmlAttr(match[1]);
|
|
11420
|
+
}
|
|
11421
|
+
function parseIslandProps(propsAttr, script) {
|
|
11422
|
+
const fromAttr = propsAttr ? tryParseJson(propsAttr) : void 0;
|
|
11423
|
+
if (fromAttr) return unwrapIslandProps(fromAttr);
|
|
11424
|
+
const scriptBody = script.match(/<script type="application\/json">([\s\S]*?)<\/script>/i)?.[1];
|
|
11425
|
+
return scriptBody ? unwrapIslandProps(tryParseJson(scriptBody) ?? {}) : {};
|
|
11426
|
+
}
|
|
11427
|
+
function tryParseJson(value) {
|
|
11428
|
+
try {
|
|
11429
|
+
return JSON.parse(value);
|
|
11430
|
+
} catch {
|
|
11431
|
+
return;
|
|
11432
|
+
}
|
|
11433
|
+
}
|
|
11434
|
+
function unwrapIslandProps(parsed) {
|
|
11435
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
11436
|
+
const record = parsed;
|
|
11437
|
+
const keys = Object.keys(record);
|
|
11438
|
+
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;
|
|
11439
|
+
return record;
|
|
11440
|
+
}
|
|
11441
|
+
function decodeHtmlAttr(value) {
|
|
11442
|
+
return value.replaceAll(""", "\"").replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&");
|
|
11443
|
+
}
|
|
11444
|
+
//#endregion
|
|
8074
11445
|
//#region src/framework.ts
|
|
8075
11446
|
function createFrameworkMarkdownOptions(options) {
|
|
8076
11447
|
return {
|
|
@@ -8087,6 +11458,7 @@ function createFrameworkMarkdownOptions(options) {
|
|
|
8087
11458
|
lastUpdated: false,
|
|
8088
11459
|
pagination: false,
|
|
8089
11460
|
breadcrumbs: false,
|
|
11461
|
+
jsonLd: false,
|
|
8090
11462
|
readerChrome: false,
|
|
8091
11463
|
localeSwitcher: false,
|
|
8092
11464
|
a11y: false,
|
|
@@ -8097,6 +11469,10 @@ function createFrameworkMarkdownOptions(options) {
|
|
|
8097
11469
|
robots: true,
|
|
8098
11470
|
llms: true
|
|
8099
11471
|
},
|
|
11472
|
+
pwa: {
|
|
11473
|
+
enabled: false,
|
|
11474
|
+
offline: true
|
|
11475
|
+
},
|
|
8100
11476
|
publishState: {
|
|
8101
11477
|
enabled: false,
|
|
8102
11478
|
includeDrafts: false
|
|
@@ -8112,6 +11488,7 @@ function createFrameworkMarkdownOptions(options) {
|
|
|
8112
11488
|
allowExternal: false
|
|
8113
11489
|
},
|
|
8114
11490
|
gfm: options.gfm,
|
|
11491
|
+
mdx: options.mdx,
|
|
8115
11492
|
frontmatter: options.frontmatter ?? false,
|
|
8116
11493
|
toc: options.toc,
|
|
8117
11494
|
tocMaxDepth: options.tocMaxDepth,
|
|
@@ -8184,7 +11561,11 @@ function createFrameworkMarkdownOptions(options) {
|
|
|
8184
11561
|
includes: { enabled: false },
|
|
8185
11562
|
cards: { enabled: false },
|
|
8186
11563
|
steps: { enabled: false },
|
|
8187
|
-
fileTree: {
|
|
11564
|
+
fileTree: {
|
|
11565
|
+
enabled: false,
|
|
11566
|
+
defaultOpen: true,
|
|
11567
|
+
icons: true
|
|
11568
|
+
},
|
|
8188
11569
|
sanitize: { enabled: false },
|
|
8189
11570
|
editThisPage: {
|
|
8190
11571
|
enabled: false,
|
|
@@ -8282,7 +11663,7 @@ async function collectMarkdownDocsTests(options, cwd) {
|
|
|
8282
11663
|
const blocks = [];
|
|
8283
11664
|
let index = 0;
|
|
8284
11665
|
for (const [sourcePath, relativePath] of [...files.entries()].sort((left, right) => left[0].localeCompare(right[0]))) {
|
|
8285
|
-
const extracted = await extractDocsTests(await fs$
|
|
11666
|
+
const extracted = await extractDocsTests(await fs$2.readFile(sourcePath, "utf-8"), {
|
|
8286
11667
|
languages: options.languages,
|
|
8287
11668
|
requireMeta: options.requireMeta
|
|
8288
11669
|
});
|
|
@@ -8368,18 +11749,18 @@ async function writeDocsTestFiles(options) {
|
|
|
8368
11749
|
...options,
|
|
8369
11750
|
cwd
|
|
8370
11751
|
});
|
|
8371
|
-
if (clean) await fs$
|
|
11752
|
+
if (clean) await fs$2.rm(generatedDir, {
|
|
8372
11753
|
recursive: true,
|
|
8373
11754
|
force: true
|
|
8374
11755
|
});
|
|
8375
|
-
await fs$
|
|
11756
|
+
await fs$2.mkdir(generatedDir, { recursive: true });
|
|
8376
11757
|
return {
|
|
8377
11758
|
cwd,
|
|
8378
11759
|
generatedDir,
|
|
8379
11760
|
blocks,
|
|
8380
11761
|
files: await Promise.all(blocks.map(async (block) => {
|
|
8381
11762
|
const filePath = path$1.join(generatedDir, docsTestFileName(block));
|
|
8382
|
-
await fs$
|
|
11763
|
+
await fs$2.writeFile(filePath, renderDocsTestFile(block, options), "utf-8");
|
|
8383
11764
|
return {
|
|
8384
11765
|
filePath,
|
|
8385
11766
|
sourcePath: block.sourcePath,
|
|
@@ -8864,7 +12245,7 @@ async function lintMarkdownFile(filePath, options = {}) {
|
|
|
8864
12245
|
async function lintMarkdownFiles(options = {}) {
|
|
8865
12246
|
const resolvedOptions = resolveMarkdownLintFileOptions(options);
|
|
8866
12247
|
const matchedFiles = await collectMarkdownLintFileEntries(resolvedOptions);
|
|
8867
|
-
const results = await lintMatchedMarkdownFiles(matchedFiles, await Promise.all(matchedFiles.map((file) => fs$
|
|
12248
|
+
const results = await lintMatchedMarkdownFiles(matchedFiles, await Promise.all(matchedFiles.map((file) => fs$2.readFile(file.filePath, "utf-8"))), resolvedOptions.lintOptions);
|
|
8868
12249
|
const files = matchedFiles.map((file, index) => ({
|
|
8869
12250
|
...results[index] ?? createEmptyLintResult(),
|
|
8870
12251
|
filePath: file.filePath,
|
|
@@ -8908,7 +12289,7 @@ async function lintMarkdownFileWithResolvedOptions(filePath, options) {
|
|
|
8908
12289
|
skipped: true
|
|
8909
12290
|
};
|
|
8910
12291
|
return {
|
|
8911
|
-
...await lintMarkdownAsync(await fs$
|
|
12292
|
+
...await lintMarkdownAsync(await fs$2.readFile(absoluteFilePath, "utf-8"), {
|
|
8912
12293
|
...options.lintOptions,
|
|
8913
12294
|
mdx: resolveMdxForFilePath(absoluteFilePath, options.lintOptions.mdx)
|
|
8914
12295
|
}),
|
|
@@ -9019,6 +12400,7 @@ function oxContent(options = {}) {
|
|
|
9019
12400
|
createCollectionsPlugin(resolvedOptions, getRoot),
|
|
9020
12401
|
createSearchPlugin(resolvedOptions, getRoot)
|
|
9021
12402
|
];
|
|
12403
|
+
if (resolvedOptions.math.enabled) plugins.push(createKatexAssetsPlugin());
|
|
9022
12404
|
if (resolvedOptions.i18n) plugins.push(createI18nPlugin(resolvedOptions));
|
|
9023
12405
|
if (resolvedOptions.ogViewer) plugins.push(createOgViewerPlugin(resolvedOptions));
|
|
9024
12406
|
return plugins;
|
|
@@ -9166,6 +12548,7 @@ function createSsgPlugin(resolvedOptions, getRoot, ssgDevCache) {
|
|
|
9166
12548
|
for (const error of result.errors) console.warn(`[ox-content] ${error}`);
|
|
9167
12549
|
} catch (err) {
|
|
9168
12550
|
console.error("[ox-content] SSG build failed:", err);
|
|
12551
|
+
if (err instanceof PageResourceError) throw err;
|
|
9169
12552
|
}
|
|
9170
12553
|
}
|
|
9171
12554
|
};
|
|
@@ -9271,9 +12654,12 @@ function resolveOptions(options) {
|
|
|
9271
12654
|
permalinks: resolvePermalinksOptions(options.permalinks),
|
|
9272
12655
|
cascade: resolveCascadeOptions(options.cascade),
|
|
9273
12656
|
redirects: resolveRedirectsOptions(options.redirects),
|
|
12657
|
+
blog: resolveBlogOptions(options.blog ?? (typeof options.ssg === "object" && options.ssg ? options.ssg.blog : void 0)),
|
|
9274
12658
|
feeds: resolveFeedsOptions(options.feeds),
|
|
12659
|
+
pwa: resolvePwaOptions(options.pwa),
|
|
9275
12660
|
taxonomies: resolveTaxonomiesOptions(options.taxonomies),
|
|
9276
12661
|
versions: resolveVersionsOptions(options.versions),
|
|
12662
|
+
resources: resolveResourcesOptions(options.resources),
|
|
9277
12663
|
gfm: options.gfm ?? true,
|
|
9278
12664
|
mdx: options.mdx,
|
|
9279
12665
|
footnotes: options.footnotes ?? true,
|
|
@@ -9299,6 +12685,7 @@ function resolveOptions(options) {
|
|
|
9299
12685
|
cjkEmphasis: options.cjkEmphasis ?? false,
|
|
9300
12686
|
codeBlockLint: resolveCodeBlockLintOptions(options.codeBlockLint),
|
|
9301
12687
|
codeBlockTypecheck: resolveCodeBlockTypecheckOptions(options.codeBlockTypecheck),
|
|
12688
|
+
typedHover: resolveTypedHoverOptions(options.typedHover),
|
|
9302
12689
|
docsTests: resolveDocsTestOptions(options.docsTests),
|
|
9303
12690
|
mermaid: options.mermaid ?? false,
|
|
9304
12691
|
math: resolveMathOptions(options.math),
|
|
@@ -9595,6 +12982,6 @@ function normalizeRuntimeBase(base) {
|
|
|
9595
12982
|
return withLeading.endsWith("/") ? withLeading : `${withLeading}/`;
|
|
9596
12983
|
}
|
|
9597
12984
|
//#endregion
|
|
9598
|
-
export { DEFAULT_HTML_TEMPLATE, DEFAULT_MARKDOWN_EXTENSIONS, DefaultTheme, DocsTestRunError, Fragment, IncrementalMarkdownParser, IncrementalMarkdownRenderer, buildCollectionManifest, buildSearchIndex, buildSsg, classifyPublishState, clearRenderContext, collectDocsTests, collectGitHubRepos, collectGitHubSources, collectOgpUrls, convertVitePressNav, convertVitePressSidebar, createFrameworkMarkdownOptions, createI18nPlugin, createIncrementalMarkdownParser, createIncrementalMarkdownRenderer, createMarkdownEnvironment, createTheme, defaultTheme, defineCollection, defineCollections, defineTheme, each, escapeSvelteMarkup, extractCodeBlocks, extractDocs, extractDocsTests, extractIslandInfo, extractVideoId, fetchGitHubSource, fetchOgpData, fetchRepoData, fromVitePressConfig, generateCollectionsVirtualModule, generateFrontmatterTypes, generateHydrationScript, generateMarkdown, generateOgImages, generateTabsCSS, generateTypes, generateVirtualModule, generateVitePressMigrationConfig, hasIslands, inferType, isMarkdownFilePath, jsx, jsxs, lintCodeBlocks, lintMarkdown, lintMarkdownAsync, lintMarkdownFile, lintMarkdownFiles, mergeThemes, mermaidClientScript, normalizeMarkdownExtensions, normalizeVitePressFrontmatter, oxContent, parseGitHubLineRange, parseGitHubPermalink, parsePageChromeFlags, partitionPublishedPages, prefetchGitHubRepos, prefetchGitHubSources, prefetchOgpData, raw, renderAllPages, renderHtmlToFrameworkCode, renderHtmlToReactComponent, renderHtmlToReactCreateElement, renderHtmlToSvelteComponent, renderHtmlToVueComponent, renderHtmlToVueH, renderMarkdownStream, renderPage, renderToString, resolveBadgeOptions, resolveBuiltinEmbedOptions, resolveCardOptions, resolveCascadeOptions, resolveCollectionsOptions, resolveDocsOptions, resolveFeedsOptions, resolveFileTreeOptions, resolveHeaderNavItems, resolveI18nOptions, resolveImageOptions, resolveIncludeOptions, resolveLocaleLabel, resolveMathOptions, resolveNotFoundOptions, resolveOgImageOptions, resolvePageChromeOption, resolvePermalinksOptions, resolvePublishStateOptions, resolveRedirectsOptions, resolveSearchOptions, resolveSiteMapsOptions, resolveSsgOptions, resolveStepsOptions, resolveTaxonomiesOptions, resolveTeamOptions, resolveTheme, resolveVersionsOptions, runDocsTests, setRenderContext, shouldLintMarkdownFile, stripMarkdownExtension, transformAllPlugins, transformGitHub, transformIslands, transformMarkdown, transformMermaidStatic, transformOgp, transformTabs, transformYouTube, typecheckCodeBlocks, useIsActive, useNav, usePageProps, useRenderContext, useSiteConfig, when, writeDocs, writeDocsTestFiles, writeSearchIndex };
|
|
12985
|
+
export { DEFAULT_HTML_TEMPLATE, DEFAULT_MARKDOWN_EXTENSIONS, DefaultTheme, DocsTestRunError, Fragment, IncrementalMarkdownParser, IncrementalMarkdownRenderer, PageResourceError, applyIslandSsrHtml, buildCollectionManifest, buildSearchIndex, buildSsg, classifyPublishState, clearRenderContext, collectDocsTests, collectGitHubRepos, collectGitHubSources, collectMdxIslandNamesFromHtml, collectMdxJsxNamesFromAst, collectOgpUrls, convertVitePressNav, convertVitePressSidebar, createFrameworkMarkdownOptions, createI18nPlugin, createIncrementalMarkdownParser, createIncrementalMarkdownRenderer, createMarkdownEnvironment, createTheme, defaultTheme, defineCollection, defineCollections, defineTheme, discoverDocumentMdxIslands, discoverRegisteredMdxComponents, each, escapeSvelteMarkup, extractCodeBlocks, extractDocs, extractDocsTests, extractIslandInfo, extractVideoId, fetchGitHubSource, fetchOgpData, fetchRepoData, fromVitePressConfig, generateCollectionsVirtualModule, generateFrontmatterTypes, generateHydrationScript, generateMarkdown, generateOgImages, generateTabsCSS, generateTypes, generateVirtualModule, generateVitePressMigrationConfig, hasIslands, inferType, intersectHydratableComponentNames, intersectRegisteredComponentNames, isMarkdownFilePath, isMdxFilePath, isRegisteredComponent, jsx, jsxs, lintCodeBlocks, lintMarkdown, lintMarkdownAsync, lintMarkdownFile, lintMarkdownFiles, mergeThemes, mermaidClientScript, normalizeMarkdownExtensions, normalizeVitePressFrontmatter, oxContent, parseGitHubLineRange, parseGitHubPermalink, parsePageChromeFlags, partitionPublishedPages, prefetchGitHubRepos, prefetchGitHubSources, prefetchOgpData, raw, readingTimeMinutes, renderAllPages, renderHtmlToFrameworkCode, renderHtmlToReactComponent, renderHtmlToReactCreateElement, renderHtmlToSvelteComponent, renderHtmlToVueComponent, renderHtmlToVueH, renderIslandComponentImports, renderMarkdownStream, renderPage, renderToString, resolveBadgeOptions, resolveBlogCollectionName, resolveBlogOptions, resolveBuiltinEmbedOptions, resolveCardOptions, resolveCascadeOptions, resolveCollectionsOptions, resolveContentRootPath, resolveDocsOptions, resolveDocumentComponentImports, resolveFeedsOptions, resolveFileTreeOptions, resolveHeaderNavItems, resolveI18nOptions, resolveImageOptions, resolveIncludeOptions, resolveLocaleLabel, resolveMathOptions, resolveMdxForFilePath, resolveNotFoundOptions, resolveOgImageOptions, resolvePageChromeOption, resolvePermalinksOptions, resolvePublishStateOptions, resolvePwaOptions, resolveRedirectsOptions, resolveResourcesOptions, resolveSearchOptions, resolveSectionIndexOptions, resolveSiteMapsOptions, resolveSsgOptions, resolveStepsOptions, resolveTaxonomiesOptions, resolveTeamOptions, resolveTheme, resolveTypedHoverOptions, resolveVersionsOptions, runDocsTests, setRenderContext, shouldLintMarkdownFile, stripMarkdownExtension, stripViteQuery, transformAllPlugins, transformGitHub, transformIslands, transformMarkdown, transformMermaidStatic, transformOgp, transformTabs, transformYouTube, typecheckCodeBlocks, useIsActive, useNav, usePageProps, useRenderContext, useSiteConfig, when, writeDocs, writeDocsTestFiles, writeSearchIndex };
|
|
9599
12986
|
|
|
9600
12987
|
//# sourceMappingURL=index.mjs.map
|