@ox-content/vite-plugin 2.88.0 → 2.89.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1286 -1186
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +470 -404
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +470 -404
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1286 -1186
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -2767,6 +2767,45 @@ async function resolveTemplate(options, root) {
|
|
|
2767
2767
|
}
|
|
2768
2768
|
}
|
|
2769
2769
|
/**
|
|
2770
|
+
* Matches this package and every subpath it exports.
|
|
2771
|
+
*
|
|
2772
|
+
* A template's natural runtime is whatever renders it, and for the
|
|
2773
|
+
* framework-less kinds that is this package: `renderToString`, `raw`, `when`
|
|
2774
|
+
* and `each` live at its root, and the JSX runtime under `./jsx-runtime`.
|
|
2775
|
+
* Inlining them instead drags the entire plugin — chokidar, fsevents and all
|
|
2776
|
+
* — into the template bundle, which is what made importing it fail outright.
|
|
2777
|
+
*/
|
|
2778
|
+
const OX_CONTENT_PACKAGE = /^@ox-content\/vite-plugin(\/.*)?$/;
|
|
2779
|
+
/**
|
|
2780
|
+
* Whether `id` is a bare specifier, and so resolvable at runtime rather than
|
|
2781
|
+
* something the template bundle has to inline.
|
|
2782
|
+
*
|
|
2783
|
+
* Template bundles are written to `<root>/.cache/og-images/` and imported
|
|
2784
|
+
* from there, so Node resolves anything left external against the project's
|
|
2785
|
+
* own `node_modules`. Relative and absolute imports still bundle, which is
|
|
2786
|
+
* what a template actually needs — its own components travel with it.
|
|
2787
|
+
*/
|
|
2788
|
+
function isBareSpecifier(id) {
|
|
2789
|
+
if (id.startsWith(".") || id.startsWith("/") || id.startsWith("\0")) return false;
|
|
2790
|
+
return !/^[a-zA-Z]:[\\/]/.test(id);
|
|
2791
|
+
}
|
|
2792
|
+
/**
|
|
2793
|
+
* Rolldown input options for a `.ts` template bundle.
|
|
2794
|
+
*
|
|
2795
|
+
* A `.ts` template is the framework-less kind, so it has no single runtime to
|
|
2796
|
+
* externalize the way the `.vue`, `.svelte` and `.tsx` paths do — anything
|
|
2797
|
+
* from `node_modules` is better resolved at import time than inlined. Nothing
|
|
2798
|
+
* on this path has a compiler plugin, so nothing here needed bundling to be
|
|
2799
|
+
* loadable in the first place.
|
|
2800
|
+
*/
|
|
2801
|
+
function tsTemplateBundleOptions(templatePath) {
|
|
2802
|
+
return {
|
|
2803
|
+
input: templatePath,
|
|
2804
|
+
platform: "node",
|
|
2805
|
+
external: (id) => isBareSpecifier(id)
|
|
2806
|
+
};
|
|
2807
|
+
}
|
|
2808
|
+
/**
|
|
2770
2809
|
* Resolves a plain TypeScript template (existing behavior).
|
|
2771
2810
|
*/
|
|
2772
2811
|
async function resolveTsTemplate(templatePath, options, root) {
|
|
@@ -2775,10 +2814,7 @@ async function resolveTsTemplate(templatePath, options, root) {
|
|
|
2775
2814
|
const cacheDir = path.join(root, ".cache", "og-images");
|
|
2776
2815
|
await fs.mkdir(cacheDir, { recursive: true });
|
|
2777
2816
|
const outfile = path.join(cacheDir, "_template.mjs");
|
|
2778
|
-
const bundle = await rolldown(
|
|
2779
|
-
input: templatePath,
|
|
2780
|
-
platform: "node"
|
|
2781
|
-
});
|
|
2817
|
+
const bundle = await rolldown(tsTemplateBundleOptions(templatePath));
|
|
2782
2818
|
await bundle.write({
|
|
2783
2819
|
file: outfile,
|
|
2784
2820
|
format: "esm"
|
|
@@ -2800,11 +2836,16 @@ async function resolveVueTemplate(templatePath, options, root) {
|
|
|
2800
2836
|
const cacheDir = path.join(root, ".cache", "og-images");
|
|
2801
2837
|
await fs.mkdir(cacheDir, { recursive: true });
|
|
2802
2838
|
const outfile = path.join(cacheDir, "_template_vue.mjs");
|
|
2839
|
+
const plugins = options.vuePlugin === "vizejs" ? await getVizejsPlugin() : [createVueCompilerPlugin()];
|
|
2803
2840
|
const bundle = await rolldown({
|
|
2804
2841
|
input: templatePath,
|
|
2805
2842
|
platform: "node",
|
|
2806
|
-
external: [
|
|
2807
|
-
|
|
2843
|
+
external: [
|
|
2844
|
+
"vue",
|
|
2845
|
+
"vue/server-renderer",
|
|
2846
|
+
OX_CONTENT_PACKAGE
|
|
2847
|
+
],
|
|
2848
|
+
plugins
|
|
2808
2849
|
});
|
|
2809
2850
|
await bundle.write({
|
|
2810
2851
|
file: outfile,
|
|
@@ -2906,7 +2947,8 @@ async function resolveSvelteTemplate(templatePath, root) {
|
|
|
2906
2947
|
"svelte",
|
|
2907
2948
|
"svelte/server",
|
|
2908
2949
|
"svelte/internal",
|
|
2909
|
-
"svelte/internal/server"
|
|
2950
|
+
"svelte/internal/server",
|
|
2951
|
+
OX_CONTENT_PACKAGE
|
|
2910
2952
|
],
|
|
2911
2953
|
plugins: [createSvelteCompilerPlugin()]
|
|
2912
2954
|
});
|
|
@@ -2965,7 +3007,8 @@ async function resolveReactTemplate(templatePath, root) {
|
|
|
2965
3007
|
"react/jsx-runtime",
|
|
2966
3008
|
"react/jsx-dev-runtime",
|
|
2967
3009
|
"react-dom",
|
|
2968
|
-
"react-dom/server"
|
|
3010
|
+
"react-dom/server",
|
|
3011
|
+
OX_CONTENT_PACKAGE
|
|
2969
3012
|
],
|
|
2970
3013
|
transform: { jsx: "react-jsx" }
|
|
2971
3014
|
});
|
|
@@ -3297,674 +3340,724 @@ initIslands((el, props) => {
|
|
|
3297
3340
|
`;
|
|
3298
3341
|
}
|
|
3299
3342
|
//#endregion
|
|
3300
|
-
//#region src/
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3343
|
+
//#region src/page-context.ts
|
|
3344
|
+
var page_context_exports = /* @__PURE__ */ require_vitepress.__exportAll({
|
|
3345
|
+
clearRenderContext: () => clearRenderContext,
|
|
3346
|
+
generateFrontmatterTypes: () => generateFrontmatterTypes,
|
|
3347
|
+
inferType: () => inferType,
|
|
3348
|
+
setRenderContext: () => setRenderContext,
|
|
3349
|
+
useIsActive: () => useIsActive,
|
|
3350
|
+
useNav: () => useNav,
|
|
3351
|
+
usePageProps: () => usePageProps,
|
|
3352
|
+
useRenderContext: () => useRenderContext,
|
|
3353
|
+
useSiteConfig: () => useSiteConfig
|
|
3354
|
+
});
|
|
3304
3355
|
/**
|
|
3305
|
-
*
|
|
3306
|
-
*
|
|
3307
|
-
*
|
|
3308
|
-
* @deprecated Use `generateHtmlPage`/`buildSsg` instead.
|
|
3356
|
+
* Sets the current render context.
|
|
3357
|
+
* Called internally during page rendering.
|
|
3358
|
+
* @internal
|
|
3309
3359
|
*/
|
|
3310
|
-
|
|
3360
|
+
function setRenderContext(ctx) {
|
|
3361
|
+
currentContext = ctx;
|
|
3362
|
+
}
|
|
3311
3363
|
/**
|
|
3312
|
-
*
|
|
3364
|
+
* Clears the current render context.
|
|
3365
|
+
* Called internally after page rendering.
|
|
3366
|
+
* @internal
|
|
3313
3367
|
*/
|
|
3314
|
-
function
|
|
3315
|
-
|
|
3316
|
-
enabled: false,
|
|
3317
|
-
extension: ".html",
|
|
3318
|
-
clean: false,
|
|
3319
|
-
bare: false,
|
|
3320
|
-
generateOgImage: false,
|
|
3321
|
-
lastUpdated: false
|
|
3322
|
-
};
|
|
3323
|
-
if (ssg === true || ssg === void 0) return {
|
|
3324
|
-
enabled: true,
|
|
3325
|
-
extension: ".html",
|
|
3326
|
-
clean: false,
|
|
3327
|
-
bare: false,
|
|
3328
|
-
generateOgImage: false,
|
|
3329
|
-
lastUpdated: false,
|
|
3330
|
-
theme: require_vitepress.resolveTheme(void 0)
|
|
3331
|
-
};
|
|
3332
|
-
return {
|
|
3333
|
-
enabled: ssg.enabled ?? true,
|
|
3334
|
-
extension: ssg.extension ?? ".html",
|
|
3335
|
-
clean: ssg.clean ?? false,
|
|
3336
|
-
bare: ssg.bare ?? false,
|
|
3337
|
-
siteName: ssg.siteName,
|
|
3338
|
-
ogImage: ssg.ogImage,
|
|
3339
|
-
generateOgImage: ssg.generateOgImage ?? false,
|
|
3340
|
-
lastUpdated: ssg.lastUpdated ?? false,
|
|
3341
|
-
siteUrl: ssg.siteUrl,
|
|
3342
|
-
theme: require_vitepress.resolveTheme(ssg.theme),
|
|
3343
|
-
navigation: ssg.navigation
|
|
3344
|
-
};
|
|
3368
|
+
function clearRenderContext() {
|
|
3369
|
+
currentContext = null;
|
|
3345
3370
|
}
|
|
3346
3371
|
/**
|
|
3347
|
-
*
|
|
3372
|
+
* Gets the current page props.
|
|
3373
|
+
*
|
|
3374
|
+
* @returns The current page props
|
|
3375
|
+
* @throws Error if called outside of a render context
|
|
3376
|
+
*
|
|
3377
|
+
* @example
|
|
3378
|
+
* ```tsx
|
|
3379
|
+
* function PageTitle() {
|
|
3380
|
+
* const page = usePageProps();
|
|
3381
|
+
* return <h1>{page.title}</h1>;
|
|
3382
|
+
* }
|
|
3383
|
+
* ```
|
|
3348
3384
|
*/
|
|
3349
|
-
function
|
|
3350
|
-
|
|
3385
|
+
function usePageProps() {
|
|
3386
|
+
if (!currentContext) throw new Error("[ox-content] usePageProps() must be called during page rendering. Make sure you are using it inside a theme component.");
|
|
3387
|
+
return currentContext.page;
|
|
3351
3388
|
}
|
|
3352
3389
|
/**
|
|
3353
|
-
*
|
|
3390
|
+
* Gets the site configuration.
|
|
3391
|
+
*
|
|
3392
|
+
* @returns The site configuration
|
|
3393
|
+
* @throws Error if called outside of a render context
|
|
3394
|
+
*
|
|
3395
|
+
* @example
|
|
3396
|
+
* ```tsx
|
|
3397
|
+
* function SiteHeader() {
|
|
3398
|
+
* const site = useSiteConfig();
|
|
3399
|
+
* return <header>{site.name}</header>;
|
|
3400
|
+
* }
|
|
3401
|
+
* ```
|
|
3354
3402
|
*/
|
|
3355
|
-
function
|
|
3356
|
-
|
|
3403
|
+
function useSiteConfig() {
|
|
3404
|
+
if (!currentContext) throw new Error("[ox-content] useSiteConfig() must be called during page rendering. Make sure you are using it inside a theme component.");
|
|
3405
|
+
return currentContext.site;
|
|
3357
3406
|
}
|
|
3358
3407
|
/**
|
|
3359
|
-
*
|
|
3360
|
-
*
|
|
3361
|
-
*
|
|
3408
|
+
* Gets the full render context.
|
|
3409
|
+
*
|
|
3410
|
+
* @returns The complete render context
|
|
3411
|
+
* @throws Error if called outside of a render context
|
|
3412
|
+
*
|
|
3413
|
+
* @example
|
|
3414
|
+
* ```tsx
|
|
3415
|
+
* function Layout({ children }) {
|
|
3416
|
+
* const ctx = useRenderContext();
|
|
3417
|
+
* return (
|
|
3418
|
+
* <html>
|
|
3419
|
+
* <head><title>{ctx.page.title} - {ctx.site.name}</title></head>
|
|
3420
|
+
* <body>{children}</body>
|
|
3421
|
+
* </html>
|
|
3422
|
+
* );
|
|
3423
|
+
* }
|
|
3424
|
+
* ```
|
|
3362
3425
|
*/
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
return
|
|
3366
|
-
title: item.title,
|
|
3367
|
-
path: item.path,
|
|
3368
|
-
href: item.href,
|
|
3369
|
-
children: item.children?.map(toRustNavItem),
|
|
3370
|
-
collapsed: item.collapsed,
|
|
3371
|
-
stickyCollapsed: item.stickyCollapsed
|
|
3372
|
-
};
|
|
3373
|
-
}
|
|
3374
|
-
function convertNavGroupsForRust(navGroups) {
|
|
3375
|
-
const cached = navGroupsForRustCache.get(navGroups);
|
|
3376
|
-
if (cached) return cached;
|
|
3377
|
-
const converted = navGroups.map((group) => ({
|
|
3378
|
-
title: group.title,
|
|
3379
|
-
collapsed: group.collapsed,
|
|
3380
|
-
stickyCollapsed: group.stickyCollapsed,
|
|
3381
|
-
items: group.items.map(toRustNavItem)
|
|
3382
|
-
}));
|
|
3383
|
-
navGroupsForRustCache.set(navGroups, converted);
|
|
3384
|
-
return converted;
|
|
3426
|
+
function useRenderContext() {
|
|
3427
|
+
if (!currentContext) throw new Error("[ox-content] useRenderContext() must be called during page rendering. Make sure you are using it inside a theme component.");
|
|
3428
|
+
return currentContext;
|
|
3385
3429
|
}
|
|
3386
3430
|
/**
|
|
3387
|
-
*
|
|
3388
|
-
*
|
|
3389
|
-
*
|
|
3431
|
+
* Gets the navigation groups.
|
|
3432
|
+
*
|
|
3433
|
+
* @example
|
|
3434
|
+
* ```tsx
|
|
3435
|
+
* function Sidebar() {
|
|
3436
|
+
* const nav = useNav();
|
|
3437
|
+
* return (
|
|
3438
|
+
* <nav>
|
|
3439
|
+
* {each(nav, (group) => (
|
|
3440
|
+
* <div>
|
|
3441
|
+
* <h3>{group.title}</h3>
|
|
3442
|
+
* <ul>
|
|
3443
|
+
* {each(group.items, (item) => (
|
|
3444
|
+
* <li><a href={item.href}>{item.title}</a></li>
|
|
3445
|
+
* ))}
|
|
3446
|
+
* </ul>
|
|
3447
|
+
* </div>
|
|
3448
|
+
* ))}
|
|
3449
|
+
* </nav>
|
|
3450
|
+
* );
|
|
3451
|
+
* }
|
|
3452
|
+
* ```
|
|
3390
3453
|
*/
|
|
3391
|
-
function
|
|
3392
|
-
return
|
|
3393
|
-
depth: entry.depth,
|
|
3394
|
-
text: entry.text,
|
|
3395
|
-
slug: entry.slug,
|
|
3396
|
-
children: entry.children?.map(toRustTocEntry) ?? []
|
|
3397
|
-
};
|
|
3454
|
+
function useNav() {
|
|
3455
|
+
return useSiteConfig().nav;
|
|
3398
3456
|
}
|
|
3399
3457
|
/**
|
|
3400
|
-
*
|
|
3401
|
-
*
|
|
3402
|
-
*
|
|
3458
|
+
* Checks if the given path is the current page.
|
|
3459
|
+
*
|
|
3460
|
+
* @example
|
|
3461
|
+
* ```tsx
|
|
3462
|
+
* function NavLink({ href, children }) {
|
|
3463
|
+
* const isActive = useIsActive(href);
|
|
3464
|
+
* return <a href={href} class={isActive ? 'active' : ''}>{children}</a>;
|
|
3465
|
+
* }
|
|
3466
|
+
* ```
|
|
3403
3467
|
*/
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
if (cached) return cached;
|
|
3408
|
-
const converted = locales.map((locale) => ({
|
|
3409
|
-
code: locale.code,
|
|
3410
|
-
name: locale.name,
|
|
3411
|
-
dir: locale.dir ?? "ltr"
|
|
3412
|
-
}));
|
|
3413
|
-
rustLocalesCache.set(locales, converted);
|
|
3414
|
-
return converted;
|
|
3468
|
+
function useIsActive(path) {
|
|
3469
|
+
const page = usePageProps();
|
|
3470
|
+
return page.path === path || page.url === path;
|
|
3415
3471
|
}
|
|
3416
3472
|
/**
|
|
3417
|
-
*
|
|
3418
|
-
* `i18n.locales` reference is stable across a build, so the `.map` to codes
|
|
3419
|
-
* runs once instead of once per page.
|
|
3473
|
+
* Infers TypeScript types from frontmatter values.
|
|
3420
3474
|
*/
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
if (
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3475
|
+
function inferType(value) {
|
|
3476
|
+
if (value === null) return "null";
|
|
3477
|
+
if (value === void 0) return "undefined";
|
|
3478
|
+
if (typeof value === "string") return "string";
|
|
3479
|
+
if (typeof value === "number") return "number";
|
|
3480
|
+
if (typeof value === "boolean") return "boolean";
|
|
3481
|
+
if (Array.isArray(value)) {
|
|
3482
|
+
if (value.length === 0) return "unknown[]";
|
|
3483
|
+
const itemTypes = [...new Set(value.map(inferType))];
|
|
3484
|
+
if (itemTypes.length === 1) return `${itemTypes[0]}[]`;
|
|
3485
|
+
return `(${itemTypes.join(" | ")})[]`;
|
|
3486
|
+
}
|
|
3487
|
+
if (typeof value === "object") {
|
|
3488
|
+
const entries = Object.entries(value);
|
|
3489
|
+
if (entries.length === 0) return "Record<string, unknown>";
|
|
3490
|
+
return `{ ${entries.map(([k, v]) => `${k}: ${inferType(v)}`).join("; ")} }`;
|
|
3491
|
+
}
|
|
3492
|
+
return "unknown";
|
|
3428
3493
|
}
|
|
3429
3494
|
/**
|
|
3430
|
-
* Generates
|
|
3495
|
+
* Generates TypeScript interface from frontmatter samples.
|
|
3431
3496
|
*/
|
|
3432
|
-
|
|
3433
|
-
const
|
|
3434
|
-
const
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
title: f.title,
|
|
3463
|
-
details: f.details,
|
|
3464
|
-
link: f.link,
|
|
3465
|
-
linkText: f.linkText
|
|
3466
|
-
}))
|
|
3467
|
-
} : void 0;
|
|
3468
|
-
return mod.generateSsgHtml({
|
|
3469
|
-
title: pageData.title,
|
|
3470
|
-
description: pageData.description,
|
|
3471
|
-
content: pageData.content,
|
|
3472
|
-
toc: tocForRust,
|
|
3473
|
-
lastUpdated: pageData.lastUpdated,
|
|
3474
|
-
path: pageData.path,
|
|
3475
|
-
entryPage: entryPageForRust
|
|
3476
|
-
}, navGroupsForRust, {
|
|
3477
|
-
siteName,
|
|
3478
|
-
base,
|
|
3479
|
-
ogImage,
|
|
3480
|
-
theme: themeForRust,
|
|
3481
|
-
locale,
|
|
3482
|
-
availableLocales: availableLocales ? toRustLocales(availableLocales) : void 0
|
|
3483
|
-
});
|
|
3484
|
-
}
|
|
3485
|
-
async function externalizeSharedPageAssets(pages, outDir, base) {
|
|
3486
|
-
const optimized = (await require_vitepress.importNapiModule()).externalizeSsgAssets(pages, outDir, base);
|
|
3487
|
-
await Promise.all(optimized.assets.map(async (asset) => {
|
|
3488
|
-
await fs_promises.mkdir(path.dirname(asset.outputPath), { recursive: true });
|
|
3489
|
-
await fs_promises.writeFile(asset.outputPath, asset.content, "utf-8");
|
|
3490
|
-
}));
|
|
3491
|
-
return {
|
|
3492
|
-
pages: optimized.pages,
|
|
3493
|
-
assets: optimized.assets.map((asset) => asset.outputPath)
|
|
3494
|
-
};
|
|
3497
|
+
function generateFrontmatterTypes(samples, interfaceName = "PageFrontmatter") {
|
|
3498
|
+
const fields = /* @__PURE__ */ new Map();
|
|
3499
|
+
for (const sample of samples) for (const [key, value] of Object.entries(sample)) {
|
|
3500
|
+
const existing = fields.get(key) ?? {
|
|
3501
|
+
types: /* @__PURE__ */ new Set(),
|
|
3502
|
+
count: 0
|
|
3503
|
+
};
|
|
3504
|
+
existing.types.add(inferType(value));
|
|
3505
|
+
existing.count++;
|
|
3506
|
+
fields.set(key, existing);
|
|
3507
|
+
}
|
|
3508
|
+
const lines = [
|
|
3509
|
+
"/**",
|
|
3510
|
+
" * Auto-generated frontmatter type based on your pages.",
|
|
3511
|
+
" * DO NOT EDIT - this file is generated by ox-content.",
|
|
3512
|
+
" */",
|
|
3513
|
+
"",
|
|
3514
|
+
`export interface ${interfaceName} {`
|
|
3515
|
+
];
|
|
3516
|
+
for (const [name, { types, count }] of fields) {
|
|
3517
|
+
const isOptional = count < samples.length;
|
|
3518
|
+
const typeStr = [...types].join(" | ");
|
|
3519
|
+
const optionalMark = isOptional ? "?" : "";
|
|
3520
|
+
lines.push(` ${name}${optionalMark}: ${typeStr};`);
|
|
3521
|
+
}
|
|
3522
|
+
lines.push("}");
|
|
3523
|
+
lines.push("");
|
|
3524
|
+
lines.push(`export type PageProps = import('@ox-content/vite-plugin').PageProps<${interfaceName}>;`);
|
|
3525
|
+
lines.push("");
|
|
3526
|
+
return lines.join("\n");
|
|
3495
3527
|
}
|
|
3528
|
+
var currentContext;
|
|
3529
|
+
var init_page_context = require_vitepress.__esmMin((() => {
|
|
3530
|
+
currentContext = null;
|
|
3531
|
+
}));
|
|
3532
|
+
//#endregion
|
|
3533
|
+
//#region src/theme-renderer.ts
|
|
3496
3534
|
/**
|
|
3497
|
-
*
|
|
3535
|
+
* Theme Renderer for Static HTML Generation
|
|
3536
|
+
*
|
|
3537
|
+
* Renders JSX theme components to static HTML strings.
|
|
3538
|
+
* No client-side JavaScript is included by default.
|
|
3498
3539
|
*/
|
|
3499
|
-
|
|
3500
|
-
return require_vitepress.importNapiModuleSync().getSsgUrlPath(inputPath, srcDir);
|
|
3501
|
-
}
|
|
3540
|
+
init_page_context();
|
|
3502
3541
|
/**
|
|
3503
|
-
*
|
|
3542
|
+
* Renders a page using the theme component.
|
|
3543
|
+
*
|
|
3544
|
+
* @param page - Page data to render
|
|
3545
|
+
* @param options - Theme render options
|
|
3546
|
+
* @returns Rendered HTML string
|
|
3504
3547
|
*/
|
|
3505
|
-
function
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3548
|
+
function renderPage(page, options) {
|
|
3549
|
+
const { theme, siteName, base, nav, pages } = options;
|
|
3550
|
+
setRenderContext({
|
|
3551
|
+
page: {
|
|
3552
|
+
title: page.title,
|
|
3553
|
+
description: page.description,
|
|
3554
|
+
html: page.html,
|
|
3555
|
+
toc: page.toc,
|
|
3556
|
+
lastUpdated: page.lastUpdated,
|
|
3557
|
+
path: page.path,
|
|
3558
|
+
url: page.url,
|
|
3559
|
+
frontmatter: page.frontmatter,
|
|
3560
|
+
layout: page.layout
|
|
3561
|
+
},
|
|
3562
|
+
site: {
|
|
3563
|
+
name: siteName,
|
|
3564
|
+
base,
|
|
3565
|
+
nav,
|
|
3566
|
+
pages: pages.map((p) => ({
|
|
3567
|
+
title: p.title,
|
|
3568
|
+
description: p.description,
|
|
3569
|
+
html: p.html,
|
|
3570
|
+
toc: p.toc,
|
|
3571
|
+
lastUpdated: p.lastUpdated,
|
|
3572
|
+
path: p.path,
|
|
3573
|
+
url: p.url,
|
|
3574
|
+
frontmatter: p.frontmatter,
|
|
3575
|
+
layout: p.layout
|
|
3576
|
+
}))
|
|
3577
|
+
}
|
|
3578
|
+
});
|
|
3579
|
+
try {
|
|
3580
|
+
const result = theme({ children: require_jsx_html.raw(page.html) });
|
|
3581
|
+
const html = require_jsx_html.renderToString(result);
|
|
3582
|
+
if (!html.trimStart().toLowerCase().startsWith("<!doctype")) return `<!DOCTYPE html>\n${html}`;
|
|
3583
|
+
return html;
|
|
3584
|
+
} finally {
|
|
3585
|
+
clearRenderContext();
|
|
3586
|
+
}
|
|
3515
3587
|
}
|
|
3516
3588
|
/**
|
|
3517
|
-
*
|
|
3589
|
+
* Renders all pages and generates type definitions.
|
|
3590
|
+
*
|
|
3591
|
+
* @param pages - All pages to render
|
|
3592
|
+
* @param options - Theme render options
|
|
3593
|
+
* @returns Map of output paths to rendered HTML
|
|
3518
3594
|
*/
|
|
3519
|
-
function
|
|
3520
|
-
|
|
3595
|
+
async function renderAllPages(pages, options) {
|
|
3596
|
+
const results = /* @__PURE__ */ new Map();
|
|
3597
|
+
for (const page of pages) {
|
|
3598
|
+
const html = renderPage(page, {
|
|
3599
|
+
...options,
|
|
3600
|
+
pages
|
|
3601
|
+
});
|
|
3602
|
+
results.set(page.url, html);
|
|
3603
|
+
}
|
|
3604
|
+
if (options.typesOutDir) await generateTypes(pages, options.typesOutDir);
|
|
3605
|
+
return results;
|
|
3521
3606
|
}
|
|
3522
3607
|
/**
|
|
3523
|
-
*
|
|
3608
|
+
* Generates TypeScript type definitions from page frontmatter.
|
|
3609
|
+
*
|
|
3610
|
+
* @param pages - All pages
|
|
3611
|
+
* @param outDir - Output directory for types
|
|
3524
3612
|
*/
|
|
3525
|
-
async function
|
|
3526
|
-
|
|
3613
|
+
async function generateTypes(pages, outDir) {
|
|
3614
|
+
const types = generateFrontmatterTypes(pages.map((p) => p.frontmatter));
|
|
3615
|
+
const typesPath = (0, node_path.join)(outDir, "page-props.d.ts");
|
|
3616
|
+
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(typesPath), { recursive: true });
|
|
3617
|
+
await (0, node_fs_promises.writeFile)(typesPath, types, "utf-8");
|
|
3527
3618
|
}
|
|
3528
3619
|
/**
|
|
3529
|
-
*
|
|
3620
|
+
* Default theme component.
|
|
3621
|
+
* A minimal theme that renders page content with basic styling.
|
|
3530
3622
|
*/
|
|
3531
|
-
function
|
|
3532
|
-
|
|
3623
|
+
function DefaultTheme({ children }) {
|
|
3624
|
+
const { usePageProps, useSiteConfig } = (init_page_context(), require_vitepress.__toCommonJS(page_context_exports));
|
|
3625
|
+
const page = usePageProps();
|
|
3626
|
+
const site = useSiteConfig();
|
|
3627
|
+
return { __html: `<!DOCTYPE html>
|
|
3628
|
+
<html lang="en">
|
|
3629
|
+
<head>
|
|
3630
|
+
<meta charset="UTF-8">
|
|
3631
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3632
|
+
<title>${escapeHtml(page.title)} - ${escapeHtml(site.name)}</title>
|
|
3633
|
+
${page.description ? `<meta name="description" content="${escapeHtml(page.description)}">` : ""}
|
|
3634
|
+
<style>
|
|
3635
|
+
:root {
|
|
3636
|
+
--octc-color-primary: #4f6fae;
|
|
3637
|
+
--octc-color-text: #131a30;
|
|
3638
|
+
--octc-color-bg: #ffffff;
|
|
3639
|
+
--octc-color-bg-alt: #f5f7fb;
|
|
3640
|
+
--octc-color-text-muted: #4f607b;
|
|
3641
|
+
--octc-color-border: #d2dbea;
|
|
3642
|
+
}
|
|
3643
|
+
body {
|
|
3644
|
+
font-family: "IBM Plex Sans", "Avenir Next", "Segoe UI Variable", "Segoe UI", sans-serif;
|
|
3645
|
+
line-height: 1.7;
|
|
3646
|
+
color: var(--octc-color-text);
|
|
3647
|
+
background: var(--octc-color-bg);
|
|
3648
|
+
max-width: 800px;
|
|
3649
|
+
margin: 0 auto;
|
|
3650
|
+
padding: 2rem;
|
|
3651
|
+
}
|
|
3652
|
+
a { color: var(--octc-color-primary); }
|
|
3653
|
+
</style>
|
|
3654
|
+
</head>
|
|
3655
|
+
<body>
|
|
3656
|
+
<header>
|
|
3657
|
+
<h1>${escapeHtml(site.name)}</h1>
|
|
3658
|
+
</header>
|
|
3659
|
+
<main>
|
|
3660
|
+
${children.__html}
|
|
3661
|
+
</main>
|
|
3662
|
+
</body>
|
|
3663
|
+
</html>` };
|
|
3533
3664
|
}
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
*/
|
|
3537
|
-
function buildThemeNavItems(sidebar, base, extension) {
|
|
3538
|
-
return require_vitepress.importNapiModuleSync().buildSsgThemeNavItems(sidebar, base, extension);
|
|
3665
|
+
function escapeHtml(str) {
|
|
3666
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
3539
3667
|
}
|
|
3540
3668
|
/**
|
|
3541
|
-
*
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3669
|
+
* Creates a theme with layout switching support.
|
|
3670
|
+
*
|
|
3671
|
+
* @example
|
|
3672
|
+
* ```tsx
|
|
3673
|
+
* import { createTheme } from '@ox-content/vite-plugin';
|
|
3674
|
+
* import { DefaultLayout } from './layouts/Default';
|
|
3675
|
+
* import { EntryLayout } from './layouts/Entry';
|
|
3676
|
+
*
|
|
3677
|
+
* export default createTheme({
|
|
3678
|
+
* layouts: {
|
|
3679
|
+
* default: DefaultLayout,
|
|
3680
|
+
* entry: EntryLayout,
|
|
3681
|
+
* },
|
|
3682
|
+
* });
|
|
3683
|
+
* ```
|
|
3684
|
+
*/
|
|
3685
|
+
function createTheme(config) {
|
|
3686
|
+
const { layouts, defaultLayout = "default" } = config;
|
|
3687
|
+
return function ThemeWithLayouts({ children }) {
|
|
3688
|
+
const layoutName = usePageProps().layout ?? defaultLayout;
|
|
3689
|
+
const Layout = layouts[layoutName] ?? layouts[defaultLayout];
|
|
3690
|
+
if (!Layout) throw new Error(`[ox-content] Layout "${layoutName}" not found. Available layouts: ${Object.keys(layouts).join(", ")}`);
|
|
3691
|
+
return Layout({ children });
|
|
3563
3692
|
};
|
|
3564
3693
|
}
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3694
|
+
//#endregion
|
|
3695
|
+
//#region src/ssg.ts
|
|
3696
|
+
/**
|
|
3697
|
+
* SSG (Static Site Generation) module for ox-content
|
|
3698
|
+
*/
|
|
3699
|
+
/**
|
|
3700
|
+
* Deprecated compatibility export for consumers that imported the former
|
|
3701
|
+
* TypeScript SSG template. HTML generation is Rust-backed now.
|
|
3702
|
+
*
|
|
3703
|
+
* @deprecated Use `generateHtmlPage`/`buildSsg` instead.
|
|
3704
|
+
*/
|
|
3705
|
+
const DEFAULT_HTML_TEMPLATE = "<!-- ox-content default HTML template is Rust-backed -->";
|
|
3706
|
+
/**
|
|
3707
|
+
* Resolves SSG options with defaults.
|
|
3708
|
+
*/
|
|
3709
|
+
function resolveSsgOptions(ssg) {
|
|
3710
|
+
if (ssg === false) return {
|
|
3711
|
+
enabled: false,
|
|
3712
|
+
extension: ".html",
|
|
3713
|
+
clean: false,
|
|
3714
|
+
bare: false,
|
|
3715
|
+
generateOgImage: false,
|
|
3716
|
+
lastUpdated: false
|
|
3717
|
+
};
|
|
3718
|
+
if (ssg === true || ssg === void 0) return {
|
|
3719
|
+
enabled: true,
|
|
3720
|
+
extension: ".html",
|
|
3721
|
+
clean: false,
|
|
3722
|
+
bare: false,
|
|
3723
|
+
generateOgImage: false,
|
|
3724
|
+
lastUpdated: false,
|
|
3725
|
+
theme: require_vitepress.resolveTheme(void 0)
|
|
3726
|
+
};
|
|
3577
3727
|
return {
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3728
|
+
enabled: ssg.enabled ?? true,
|
|
3729
|
+
extension: ssg.extension ?? ".html",
|
|
3730
|
+
clean: ssg.clean ?? false,
|
|
3731
|
+
bare: ssg.bare ?? false,
|
|
3732
|
+
render: ssg.render,
|
|
3733
|
+
lang: ssg.lang,
|
|
3734
|
+
head: ssg.head,
|
|
3735
|
+
bodyStart: ssg.bodyStart,
|
|
3736
|
+
bodyEnd: ssg.bodyEnd,
|
|
3737
|
+
siteName: ssg.siteName,
|
|
3738
|
+
ogImage: ssg.ogImage,
|
|
3739
|
+
generateOgImage: ssg.generateOgImage ?? false,
|
|
3740
|
+
lastUpdated: ssg.lastUpdated ?? false,
|
|
3741
|
+
siteUrl: ssg.siteUrl,
|
|
3742
|
+
theme: require_vitepress.resolveTheme(ssg.theme),
|
|
3743
|
+
navigation: ssg.navigation
|
|
3588
3744
|
};
|
|
3589
3745
|
}
|
|
3590
3746
|
/**
|
|
3591
|
-
*
|
|
3592
|
-
*
|
|
3593
|
-
* `ssg.bare` deliberately does not turn this off. Bare mode only drops the
|
|
3594
|
-
* generated page shell, and bringing your own shell is exactly the case where
|
|
3595
|
-
* per-page OG images are still wanted — the images are written to the output
|
|
3596
|
-
* tree and the consumer injects the `<meta>` tags itself. Nothing in the bare
|
|
3597
|
-
* HTML references them, because bare output has no `<head>` to put them in.
|
|
3747
|
+
* Extracts title from content or frontmatter.
|
|
3598
3748
|
*/
|
|
3599
|
-
function
|
|
3600
|
-
return
|
|
3749
|
+
function extractTitle$1(content, frontmatter) {
|
|
3750
|
+
return require_vitepress.importNapiModuleSync().extractSsgTitle(content, typeof frontmatter.title === "string" ? frontmatter.title : void 0);
|
|
3601
3751
|
}
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3752
|
+
/**
|
|
3753
|
+
* Generates a bare HTML page carrying head metadata and injected markup.
|
|
3754
|
+
*
|
|
3755
|
+
* Bare mode leaves the shell to the consumer, but the metadata here is
|
|
3756
|
+
* already computed for the themed page and cannot be recovered afterwards —
|
|
3757
|
+
* the generated OG image in particular was only discoverable by guessing at
|
|
3758
|
+
* the output directory. A page with none of it set renders exactly what bare
|
|
3759
|
+
* mode emitted before, which keeps the no-JS size baseline honest.
|
|
3760
|
+
*/
|
|
3761
|
+
function generateBarePage(page) {
|
|
3762
|
+
return require_vitepress.importNapiModuleSync().generateSsgBarePage(page);
|
|
3611
3763
|
}
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3764
|
+
/**
|
|
3765
|
+
* Per-build cache for the Rust-facing nav conversion. `navGroups` is the same
|
|
3766
|
+
* `context.navItems` reference for every page in a build, so the deep recursive
|
|
3767
|
+
* copy below only needs to run once per build instead of once per page.
|
|
3768
|
+
*/
|
|
3769
|
+
const navGroupsForRustCache = /* @__PURE__ */ new WeakMap();
|
|
3770
|
+
function toRustNavItem(item) {
|
|
3771
|
+
return {
|
|
3772
|
+
title: item.title,
|
|
3773
|
+
path: item.path,
|
|
3774
|
+
href: item.href,
|
|
3775
|
+
children: item.children?.map(toRustNavItem),
|
|
3776
|
+
collapsed: item.collapsed,
|
|
3777
|
+
stickyCollapsed: item.stickyCollapsed
|
|
3619
3778
|
};
|
|
3620
|
-
for (const inputPath of markdownFiles) try {
|
|
3621
|
-
const pageResult = await transformSsgPage(context, inputPath);
|
|
3622
|
-
collected.pageResults.push(pageResult);
|
|
3623
|
-
collectOgImageEntry(context, pageResult, collected);
|
|
3624
|
-
} catch (err) {
|
|
3625
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
3626
|
-
collected.errors.push(`Failed to process ${inputPath}: ${errorMessage}`);
|
|
3627
|
-
}
|
|
3628
|
-
return collected;
|
|
3629
3779
|
}
|
|
3630
|
-
|
|
3631
|
-
const
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3780
|
+
function convertNavGroupsForRust(navGroups) {
|
|
3781
|
+
const cached = navGroupsForRustCache.get(navGroups);
|
|
3782
|
+
if (cached) return cached;
|
|
3783
|
+
const converted = navGroups.map((group) => ({
|
|
3784
|
+
title: group.title,
|
|
3785
|
+
collapsed: group.collapsed,
|
|
3786
|
+
stickyCollapsed: group.stickyCollapsed,
|
|
3787
|
+
items: group.items.map(toRustNavItem)
|
|
3788
|
+
}));
|
|
3789
|
+
navGroupsForRustCache.set(navGroups, converted);
|
|
3790
|
+
return converted;
|
|
3791
|
+
}
|
|
3792
|
+
/**
|
|
3793
|
+
* Converts a `TocEntry` tree into the plain shape the Rust binding expects.
|
|
3794
|
+
* Hoisted to module scope so it isn't reallocated for every page; the
|
|
3795
|
+
* per-page `.map` over `pageData.toc` still runs since the TOC is page-specific.
|
|
3796
|
+
*/
|
|
3797
|
+
function toRustTocEntry(entry) {
|
|
3639
3798
|
return {
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
description: frontmatter.description,
|
|
3645
|
-
lastUpdated: context.napi?.getGitLastUpdated(inputPath, context.root) ?? void 0,
|
|
3646
|
-
frontmatter,
|
|
3647
|
-
toc: result.toc
|
|
3799
|
+
depth: entry.depth,
|
|
3800
|
+
text: entry.text,
|
|
3801
|
+
slug: entry.slug,
|
|
3802
|
+
children: entry.children?.map(toRustTocEntry) ?? []
|
|
3648
3803
|
};
|
|
3649
3804
|
}
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
if (hasIslands(transformedHtml)) transformedHtml = (await transformIslands(transformedHtml)).html;
|
|
3667
|
-
return restoreMermaidSvgs(transformedHtml, mermaidSvgs);
|
|
3805
|
+
/**
|
|
3806
|
+
* Per-build cache for the Rust-facing locale list. `i18n.locales` is the same
|
|
3807
|
+
* reference for every page in a build, so this mapping (and the `?? "ltr"`
|
|
3808
|
+
* default) only runs once per build instead of once per page.
|
|
3809
|
+
*/
|
|
3810
|
+
const rustLocalesCache = /* @__PURE__ */ new WeakMap();
|
|
3811
|
+
function toRustLocales(locales) {
|
|
3812
|
+
const cached = rustLocalesCache.get(locales);
|
|
3813
|
+
if (cached) return cached;
|
|
3814
|
+
const converted = locales.map((locale) => ({
|
|
3815
|
+
code: locale.code,
|
|
3816
|
+
name: locale.name,
|
|
3817
|
+
dir: locale.dir ?? "ltr"
|
|
3818
|
+
}));
|
|
3819
|
+
rustLocalesCache.set(locales, converted);
|
|
3820
|
+
return converted;
|
|
3668
3821
|
}
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
collected.ogImageInputPaths.push(pageResult.inputPath);
|
|
3682
|
-
collected.ogImageUrlMap.set(pageResult.inputPath, pageResult.routePaths.ogImageUrl);
|
|
3822
|
+
/**
|
|
3823
|
+
* Per-build cache for the locale-code list passed to `getSsgPageLocale`. The
|
|
3824
|
+
* `i18n.locales` reference is stable across a build, so the `.map` to codes
|
|
3825
|
+
* runs once instead of once per page.
|
|
3826
|
+
*/
|
|
3827
|
+
const localeCodesCache = /* @__PURE__ */ new WeakMap();
|
|
3828
|
+
function localeCodesFor(locales) {
|
|
3829
|
+
const cached = localeCodesCache.get(locales);
|
|
3830
|
+
if (cached) return cached;
|
|
3831
|
+
const codes = locales.map((locale) => locale.code);
|
|
3832
|
+
localeCodesCache.set(locales, codes);
|
|
3833
|
+
return codes;
|
|
3683
3834
|
}
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
}
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
inputPath: pageResult.inputPath,
|
|
3723
|
-
outputPath: pageResult.routePaths.outputPath,
|
|
3724
|
-
html: await renderSsgPage(context, pageResult, collected.ogImageUrlMap)
|
|
3725
|
-
});
|
|
3726
|
-
} catch (err) {
|
|
3727
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
3728
|
-
errors.push(`Failed to generate HTML for ${pageResult.inputPath}: ${errorMessage}`);
|
|
3729
|
-
}
|
|
3730
|
-
return generatedPages;
|
|
3731
|
-
}
|
|
3732
|
-
async function renderSsgPage(context, pageResult, ogImageUrlMap) {
|
|
3733
|
-
if (context.ssgOptions.bare) return generateBareHtmlPage(pageResult.transformedHtml, pageResult.title);
|
|
3734
|
-
const pageData = createSsgPageData(pageResult);
|
|
3735
|
-
const pageOgImage = context.shouldGenerateOgImages && ogImageUrlMap.has(pageResult.inputPath) ? ogImageUrlMap.get(pageResult.inputPath) : context.ssgOptions.ogImage;
|
|
3736
|
-
return generateHtmlPage(pageData, context.navItems, context.siteName, context.base, pageOgImage, context.ssgOptions.theme, getPageLocale(pageData.path, context.options.i18n), context.options.i18n ? context.options.i18n.locales : void 0);
|
|
3737
|
-
}
|
|
3738
|
-
function createSsgPageData(pageResult) {
|
|
3739
|
-
const { frontmatter } = pageResult;
|
|
3740
|
-
const entryPage = frontmatter.layout === "entry" ? {
|
|
3741
|
-
hero: frontmatter.hero,
|
|
3742
|
-
features: frontmatter.features
|
|
3835
|
+
/**
|
|
3836
|
+
* Generates HTML page with navigation using Rust NAPI bindings.
|
|
3837
|
+
*/
|
|
3838
|
+
async function generateHtmlPage(pageData, navGroups, siteName, base, ogImage, theme, locale, availableLocales) {
|
|
3839
|
+
const mod = await require_vitepress.importNapiModule();
|
|
3840
|
+
const tocForRust = pageData.toc.map(toRustTocEntry);
|
|
3841
|
+
const navGroupsForRust = convertNavGroupsForRust(navGroups);
|
|
3842
|
+
const themeForRust = theme ? require_vitepress.themeToNapi(theme) : void 0;
|
|
3843
|
+
const entryPageForRust = pageData.entryPage ? {
|
|
3844
|
+
hero: pageData.entryPage.hero ? {
|
|
3845
|
+
name: pageData.entryPage.hero.name,
|
|
3846
|
+
text: pageData.entryPage.hero.text,
|
|
3847
|
+
tagline: pageData.entryPage.hero.tagline,
|
|
3848
|
+
notice: pageData.entryPage.hero.notice ? {
|
|
3849
|
+
title: pageData.entryPage.hero.notice.title,
|
|
3850
|
+
body: pageData.entryPage.hero.notice.body
|
|
3851
|
+
} : void 0,
|
|
3852
|
+
image: pageData.entryPage.hero.image ? {
|
|
3853
|
+
src: pageData.entryPage.hero.image.src,
|
|
3854
|
+
lightSrc: pageData.entryPage.hero.image.lightSrc,
|
|
3855
|
+
darkSrc: pageData.entryPage.hero.image.darkSrc,
|
|
3856
|
+
alt: pageData.entryPage.hero.image.alt,
|
|
3857
|
+
width: pageData.entryPage.hero.image.width,
|
|
3858
|
+
height: pageData.entryPage.hero.image.height
|
|
3859
|
+
} : void 0,
|
|
3860
|
+
actions: pageData.entryPage.hero.actions?.map((a) => ({
|
|
3861
|
+
theme: a.theme,
|
|
3862
|
+
text: a.text,
|
|
3863
|
+
link: a.link
|
|
3864
|
+
}))
|
|
3865
|
+
} : void 0,
|
|
3866
|
+
features: pageData.entryPage.features?.map((f) => ({
|
|
3867
|
+
icon: f.icon,
|
|
3868
|
+
title: f.title,
|
|
3869
|
+
details: f.details,
|
|
3870
|
+
link: f.link,
|
|
3871
|
+
linkText: f.linkText
|
|
3872
|
+
}))
|
|
3743
3873
|
} : void 0;
|
|
3874
|
+
return mod.generateSsgHtml({
|
|
3875
|
+
title: pageData.title,
|
|
3876
|
+
description: pageData.description,
|
|
3877
|
+
content: pageData.content,
|
|
3878
|
+
toc: tocForRust,
|
|
3879
|
+
lastUpdated: pageData.lastUpdated,
|
|
3880
|
+
path: pageData.path,
|
|
3881
|
+
entryPage: entryPageForRust
|
|
3882
|
+
}, navGroupsForRust, {
|
|
3883
|
+
siteName,
|
|
3884
|
+
base,
|
|
3885
|
+
ogImage,
|
|
3886
|
+
theme: themeForRust,
|
|
3887
|
+
locale,
|
|
3888
|
+
availableLocales: availableLocales ? toRustLocales(availableLocales) : void 0
|
|
3889
|
+
});
|
|
3890
|
+
}
|
|
3891
|
+
async function externalizeSharedPageAssets(pages, outDir, base) {
|
|
3892
|
+
const optimized = (await require_vitepress.importNapiModule()).externalizeSsgAssets(pages, outDir, base);
|
|
3893
|
+
await Promise.all(optimized.assets.map(async (asset) => {
|
|
3894
|
+
await fs_promises.mkdir(path.dirname(asset.outputPath), { recursive: true });
|
|
3895
|
+
await fs_promises.writeFile(asset.outputPath, asset.content, "utf-8");
|
|
3896
|
+
}));
|
|
3744
3897
|
return {
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
content: pageResult.transformedHtml,
|
|
3748
|
-
toc: pageResult.toc,
|
|
3749
|
-
lastUpdated: pageResult.lastUpdated,
|
|
3750
|
-
frontmatter,
|
|
3751
|
-
path: pageResult.routePaths.urlPath,
|
|
3752
|
-
href: pageResult.routePaths.href,
|
|
3753
|
-
entryPage
|
|
3898
|
+
pages: optimized.pages,
|
|
3899
|
+
assets: optimized.assets.map((asset) => asset.outputPath)
|
|
3754
3900
|
};
|
|
3755
3901
|
}
|
|
3756
|
-
async function writeGeneratedPages(generatedPages, context, generatedFiles) {
|
|
3757
|
-
const optimizedOutput = await externalizeSharedPageAssets(generatedPages, context.outDir, context.base);
|
|
3758
|
-
generatedFiles.push(...optimizedOutput.assets);
|
|
3759
|
-
for (const page of optimizedOutput.pages) {
|
|
3760
|
-
await fs_promises.mkdir(path.dirname(page.outputPath), { recursive: true });
|
|
3761
|
-
await fs_promises.writeFile(page.outputPath, page.html, "utf-8");
|
|
3762
|
-
generatedFiles.push(page.outputPath);
|
|
3763
|
-
}
|
|
3764
|
-
}
|
|
3765
|
-
//#endregion
|
|
3766
|
-
//#region src/search.ts
|
|
3767
3902
|
/**
|
|
3768
|
-
*
|
|
3769
|
-
*
|
|
3770
|
-
* Generates search index at build time and provides client-side search.
|
|
3903
|
+
* Converts a markdown file path to a relative URL path.
|
|
3771
3904
|
*/
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
if (!oxContent$1) try {
|
|
3775
|
-
oxContent$1 = await require_vitepress.importNapiModule();
|
|
3776
|
-
} catch {
|
|
3777
|
-
console.warn("[ox-content] Native bindings not available, search disabled");
|
|
3778
|
-
return null;
|
|
3779
|
-
}
|
|
3780
|
-
return oxContent$1;
|
|
3905
|
+
function getUrlPath$1(inputPath, srcDir) {
|
|
3906
|
+
return require_vitepress.importNapiModuleSync().getSsgUrlPath(inputPath, srcDir);
|
|
3781
3907
|
}
|
|
3782
3908
|
/**
|
|
3783
|
-
* Resolves
|
|
3909
|
+
* Resolves manual navigation config to the format used by the built-in SSG renderer.
|
|
3784
3910
|
*/
|
|
3785
|
-
function
|
|
3786
|
-
if (
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
return
|
|
3795
|
-
enabled: opts.enabled ?? true,
|
|
3796
|
-
limit: opts.limit ?? 10,
|
|
3797
|
-
prefix: opts.prefix ?? true,
|
|
3798
|
-
placeholder: opts.placeholder ?? "Search documentation...",
|
|
3799
|
-
hotkey: opts.hotkey ?? "/"
|
|
3800
|
-
};
|
|
3911
|
+
function resolveNavigationGroups(navigation, base, extension) {
|
|
3912
|
+
if (!navigation) return;
|
|
3913
|
+
return require_vitepress.importNapiModuleSync().resolveSsgNavigationGroups(navigation, base, extension);
|
|
3914
|
+
}
|
|
3915
|
+
function getPageLocale(urlPath, i18n) {
|
|
3916
|
+
if (!i18n) return void 0;
|
|
3917
|
+
return require_vitepress.importNapiModuleSync().getSsgPageLocale(urlPath, i18n.defaultLocale, localeCodesFor(i18n.locales)) ?? void 0;
|
|
3918
|
+
}
|
|
3919
|
+
function getRoutePaths(inputPath, srcDir, outDir, base, extension, siteUrl) {
|
|
3920
|
+
return require_vitepress.importNapiModuleSync().resolveSsgRoutePaths(inputPath, srcDir, outDir, base, extension, siteUrl);
|
|
3801
3921
|
}
|
|
3802
3922
|
/**
|
|
3803
|
-
*
|
|
3923
|
+
* Formats a file/dir name as a title.
|
|
3804
3924
|
*/
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
if (!napi) return JSON.stringify({
|
|
3808
|
-
documents: [],
|
|
3809
|
-
index: {},
|
|
3810
|
-
df: {},
|
|
3811
|
-
avg_dl: 0,
|
|
3812
|
-
doc_count: 0
|
|
3813
|
-
});
|
|
3814
|
-
return napi.buildSearchIndexFromDirectory(srcDir, base, [...extensions]);
|
|
3925
|
+
function formatTitle(name) {
|
|
3926
|
+
return require_vitepress.importNapiModuleSync().formatSsgTitle(name);
|
|
3815
3927
|
}
|
|
3816
3928
|
/**
|
|
3817
|
-
*
|
|
3929
|
+
* Collects all markdown files from the source directory.
|
|
3818
3930
|
*/
|
|
3819
|
-
async function
|
|
3820
|
-
|
|
3821
|
-
if (!napi) return;
|
|
3822
|
-
napi.writeSearchIndex(indexJson, outDir);
|
|
3931
|
+
async function collectMarkdownFiles(srcDir, extensions = DEFAULT_MARKDOWN_EXTENSIONS) {
|
|
3932
|
+
return require_vitepress.importNapiModuleSync().collectSsgMarkdownFiles(srcDir, [...extensions]);
|
|
3823
3933
|
}
|
|
3824
3934
|
/**
|
|
3825
|
-
*
|
|
3826
|
-
* This is injected into the bundle as a virtual module.
|
|
3935
|
+
* Builds navigation items from markdown files, grouped by directory.
|
|
3827
3936
|
*/
|
|
3828
|
-
function
|
|
3829
|
-
return require_vitepress.importNapiModuleSync().
|
|
3937
|
+
function buildNavItems(markdownFiles, srcDir, base, extension) {
|
|
3938
|
+
return require_vitepress.importNapiModuleSync().buildSsgNavItems(markdownFiles, srcDir, base, extension);
|
|
3830
3939
|
}
|
|
3831
|
-
//#endregion
|
|
3832
|
-
//#region src/dev-server.ts
|
|
3833
3940
|
/**
|
|
3834
|
-
*
|
|
3835
|
-
*
|
|
3836
|
-
* Serves fully-rendered HTML pages (with navigation, theme, etc.)
|
|
3837
|
-
* during `vite dev`, matching the SSG build output.
|
|
3941
|
+
* Builds navigation items from an explicit theme sidebar tree.
|
|
3838
3942
|
*/
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
".ts",
|
|
3843
|
-
".css",
|
|
3844
|
-
".scss",
|
|
3845
|
-
".less",
|
|
3846
|
-
".svg",
|
|
3847
|
-
".png",
|
|
3848
|
-
".jpg",
|
|
3849
|
-
".jpeg",
|
|
3850
|
-
".gif",
|
|
3851
|
-
".webp",
|
|
3852
|
-
".ico",
|
|
3853
|
-
".woff",
|
|
3854
|
-
".woff2",
|
|
3855
|
-
".ttf",
|
|
3856
|
-
".eot",
|
|
3857
|
-
".json",
|
|
3858
|
-
".map",
|
|
3859
|
-
".mp4",
|
|
3860
|
-
".webm",
|
|
3861
|
-
".mp3",
|
|
3862
|
-
".pdf"
|
|
3863
|
-
]);
|
|
3864
|
-
/** Vite internal URL prefixes to skip. */
|
|
3865
|
-
const VITE_INTERNAL_PREFIXES = [
|
|
3866
|
-
"/@vite/",
|
|
3867
|
-
"/@fs/",
|
|
3868
|
-
"/@id/",
|
|
3869
|
-
"/__"
|
|
3870
|
-
];
|
|
3943
|
+
function buildThemeNavItems(sidebar, base, extension) {
|
|
3944
|
+
return require_vitepress.importNapiModuleSync().buildSsgThemeNavItems(sidebar, base, extension);
|
|
3945
|
+
}
|
|
3871
3946
|
/**
|
|
3872
|
-
*
|
|
3947
|
+
* Builds all markdown files to static HTML.
|
|
3873
3948
|
*/
|
|
3874
|
-
function
|
|
3875
|
-
|
|
3876
|
-
if (
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
await fs_promises.access(filePath);
|
|
3898
|
-
return filePath;
|
|
3899
|
-
} catch {}
|
|
3900
|
-
}
|
|
3901
|
-
for (const extension of extensions) {
|
|
3902
|
-
const indexPath = path.join(srcDir, routePath, `index${extension}`);
|
|
3903
|
-
try {
|
|
3904
|
-
await fs_promises.access(indexPath);
|
|
3905
|
-
return indexPath;
|
|
3906
|
-
} catch {}
|
|
3907
|
-
}
|
|
3908
|
-
return null;
|
|
3949
|
+
async function buildSsg(options, root) {
|
|
3950
|
+
const ssgOptions = options.ssg;
|
|
3951
|
+
if (!ssgOptions.enabled) return {
|
|
3952
|
+
files: [],
|
|
3953
|
+
errors: [],
|
|
3954
|
+
ogImages: {}
|
|
3955
|
+
};
|
|
3956
|
+
const srcDir = path.resolve(root, options.srcDir);
|
|
3957
|
+
const outDir = path.resolve(root, options.outDir);
|
|
3958
|
+
const generatedFiles = [];
|
|
3959
|
+
const errors = [];
|
|
3960
|
+
await cleanOutputDirectory(ssgOptions, outDir);
|
|
3961
|
+
const markdownFiles = await collectMarkdownFiles(srcDir, options.extensions);
|
|
3962
|
+
const context = await createBuildSsgContext(options, root, srcDir, outDir, markdownFiles);
|
|
3963
|
+
const collected = await collectPageResults(context, markdownFiles);
|
|
3964
|
+
errors.push(...collected.errors);
|
|
3965
|
+
await generateOgImageAssets(context, collected, generatedFiles, errors);
|
|
3966
|
+
await writeGeneratedPages(await generateHtmlPages(context, collected.pageResults, collected, errors), context, generatedFiles);
|
|
3967
|
+
return {
|
|
3968
|
+
files: generatedFiles,
|
|
3969
|
+
errors,
|
|
3970
|
+
ogImages: Object.fromEntries(collected.ogImageUrlMap)
|
|
3971
|
+
};
|
|
3909
3972
|
}
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3973
|
+
async function cleanOutputDirectory(ssgOptions, outDir) {
|
|
3974
|
+
if (!ssgOptions.clean) return;
|
|
3975
|
+
try {
|
|
3976
|
+
await fs_promises.rm(outDir, {
|
|
3977
|
+
recursive: true,
|
|
3978
|
+
force: true
|
|
3979
|
+
});
|
|
3980
|
+
} catch {}
|
|
3915
3981
|
}
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
function createDevServerCache() {
|
|
3982
|
+
async function createBuildSsgContext(options, root, srcDir, outDir, markdownFiles) {
|
|
3983
|
+
const ssgOptions = options.ssg;
|
|
3984
|
+
const base = options.base.endsWith("/") ? options.base : options.base + "/";
|
|
3920
3985
|
return {
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3986
|
+
options,
|
|
3987
|
+
ssgOptions,
|
|
3988
|
+
root,
|
|
3989
|
+
srcDir,
|
|
3990
|
+
outDir,
|
|
3991
|
+
base,
|
|
3992
|
+
navItems: resolveNavigationGroups(ssgOptions.navigation, base, ssgOptions.extension) ?? (ssgOptions.theme?.sidebar.length ? buildThemeNavItems(ssgOptions.theme.sidebar, base, ssgOptions.extension) : buildNavItems(markdownFiles, srcDir, base, ssgOptions.extension)),
|
|
3993
|
+
siteName: await resolveSiteName$1(root, ssgOptions),
|
|
3994
|
+
shouldGenerateOgImages: shouldGenerateOgImages(options),
|
|
3995
|
+
napi: ssgOptions.lastUpdated ? await require_vitepress.importNapiModule() : void 0
|
|
3924
3996
|
};
|
|
3925
3997
|
}
|
|
3926
3998
|
/**
|
|
3927
|
-
*
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
* Invalidate page cache for a specific file (called on file change).
|
|
3999
|
+
* Whether this build emits one Open Graph image per page.
|
|
4000
|
+
*
|
|
4001
|
+
* `ssg.bare` deliberately does not turn this off. Bare mode only drops the
|
|
4002
|
+
* generated page shell, and bringing your own shell is exactly the case where
|
|
4003
|
+
* per-page OG images are still wanted — the images are written to the output
|
|
4004
|
+
* tree and the consumer injects the `<meta>` tags itself. Nothing in the bare
|
|
4005
|
+
* HTML references them, because bare output has no `<head>` to put them in.
|
|
3935
4006
|
*/
|
|
3936
|
-
function
|
|
3937
|
-
|
|
4007
|
+
function shouldGenerateOgImages(options) {
|
|
4008
|
+
return options.ogImage || options.ssg.generateOgImage;
|
|
3938
4009
|
}
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
*/
|
|
3942
|
-
async function resolveSiteName(options, root) {
|
|
3943
|
-
if (options.ssg.siteName) return options.ssg.siteName;
|
|
4010
|
+
async function resolveSiteName$1(root, ssgOptions) {
|
|
4011
|
+
if (ssgOptions.siteName) return ssgOptions.siteName;
|
|
3944
4012
|
try {
|
|
3945
4013
|
const pkgPath = path.join(root, "package.json");
|
|
3946
4014
|
const pkg = JSON.parse(await fs_promises.readFile(pkgPath, "utf-8"));
|
|
3947
|
-
|
|
3948
|
-
} catch {
|
|
3949
|
-
|
|
4015
|
+
return pkg.name ? formatTitle(pkg.name) : "Documentation";
|
|
4016
|
+
} catch {
|
|
4017
|
+
return "Documentation";
|
|
4018
|
+
}
|
|
3950
4019
|
}
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
4020
|
+
async function collectPageResults(context, markdownFiles) {
|
|
4021
|
+
const collected = {
|
|
4022
|
+
pageResults: [],
|
|
4023
|
+
ogImageEntries: [],
|
|
4024
|
+
ogImageInputPaths: [],
|
|
4025
|
+
ogImageUrlMap: /* @__PURE__ */ new Map(),
|
|
4026
|
+
errors: []
|
|
4027
|
+
};
|
|
4028
|
+
for (const inputPath of markdownFiles) try {
|
|
4029
|
+
const pageResult = await transformSsgPage(context, inputPath);
|
|
4030
|
+
collected.pageResults.push(pageResult);
|
|
4031
|
+
collectOgImageEntry(context, pageResult, collected);
|
|
4032
|
+
} catch (err) {
|
|
4033
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4034
|
+
collected.errors.push(`Failed to process ${inputPath}: ${errorMessage}`);
|
|
4035
|
+
}
|
|
4036
|
+
return collected;
|
|
4037
|
+
}
|
|
4038
|
+
async function transformSsgPage(context, inputPath) {
|
|
4039
|
+
const result = await transformMarkdown(await fs_promises.readFile(inputPath, "utf-8"), inputPath, context.options, {
|
|
3959
4040
|
convertMdLinks: true,
|
|
3960
|
-
baseUrl: base,
|
|
3961
|
-
sourcePath:
|
|
4041
|
+
baseUrl: context.base,
|
|
4042
|
+
sourcePath: inputPath
|
|
3962
4043
|
});
|
|
3963
4044
|
const frontmatter = require_vitepress.normalizeVitePressFrontmatter(result.frontmatter);
|
|
3964
|
-
|
|
3965
|
-
const
|
|
3966
|
-
|
|
3967
|
-
|
|
4045
|
+
const transformedHtml = await transformSsgHtml(result.html, context.options);
|
|
4046
|
+
const title = extractTitle$1(transformedHtml, frontmatter);
|
|
4047
|
+
return {
|
|
4048
|
+
inputPath,
|
|
4049
|
+
routePaths: getRoutePaths(inputPath, context.srcDir, context.outDir, context.base, context.ssgOptions.extension, context.ssgOptions.siteUrl),
|
|
4050
|
+
transformedHtml,
|
|
4051
|
+
title,
|
|
4052
|
+
description: frontmatter.description,
|
|
4053
|
+
lastUpdated: context.napi?.getGitLastUpdated(inputPath, context.root) ?? void 0,
|
|
4054
|
+
frontmatter,
|
|
4055
|
+
toc: result.toc
|
|
4056
|
+
};
|
|
4057
|
+
}
|
|
4058
|
+
async function transformSsgHtml(html, options) {
|
|
4059
|
+
const { html: protectedHtml, svgs: mermaidSvgs } = protectMermaidSvgs(html);
|
|
4060
|
+
let transformedHtml = await transformAllPlugins(protectedHtml, {
|
|
3968
4061
|
tabs: true,
|
|
3969
4062
|
youtube: true,
|
|
3970
4063
|
github: options.embeds.github,
|
|
@@ -3979,83 +4072,443 @@ async function renderPage$1(filePath, options, navGroups, siteName, base, root)
|
|
|
3979
4072
|
githubToken: process.env.GITHUB_TOKEN
|
|
3980
4073
|
});
|
|
3981
4074
|
if (hasIslands(transformedHtml)) transformedHtml = (await transformIslands(transformedHtml)).html;
|
|
3982
|
-
|
|
3983
|
-
const title = extractTitle$1(transformedHtml, frontmatter);
|
|
3984
|
-
const description = frontmatter.description;
|
|
3985
|
-
let entryPage;
|
|
3986
|
-
if (frontmatter.layout === "entry") entryPage = {
|
|
3987
|
-
hero: frontmatter.hero,
|
|
3988
|
-
features: frontmatter.features
|
|
3989
|
-
};
|
|
3990
|
-
let html = await generateHtmlPage({
|
|
3991
|
-
title,
|
|
3992
|
-
description,
|
|
3993
|
-
content: transformedHtml,
|
|
3994
|
-
toc: result.toc,
|
|
3995
|
-
frontmatter,
|
|
3996
|
-
path: getUrlPath$1(filePath, srcDir),
|
|
3997
|
-
href: getUrlPath$1(filePath, srcDir) || "/",
|
|
3998
|
-
entryPage
|
|
3999
|
-
}, navGroups, siteName, base, options.ssg.ogImage, options.ssg.theme);
|
|
4000
|
-
html = injectViteHmrClient(html);
|
|
4001
|
-
return html;
|
|
4075
|
+
return restoreMermaidSvgs(transformedHtml, mermaidSvgs);
|
|
4002
4076
|
}
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
try {
|
|
4018
|
-
const cached = cache.pages.get(filePath);
|
|
4019
|
-
if (cached) {
|
|
4020
|
-
res.setHeader("Content-Type", "text/html");
|
|
4021
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
4022
|
-
res.end(cached);
|
|
4023
|
-
return;
|
|
4024
|
-
}
|
|
4025
|
-
if (!cache.siteName) cache.siteName = await resolveSiteName(options, root);
|
|
4026
|
-
if (!cache.navGroups) {
|
|
4027
|
-
const markdownFiles = await collectMarkdownFiles(srcDir, options.extensions);
|
|
4028
|
-
cache.navGroups = resolveNavigationGroups(options.ssg.navigation, base, options.ssg.extension) ?? (options.ssg.theme?.sidebar.length ? buildThemeNavItems(options.ssg.theme.sidebar, base, options.ssg.extension) : buildNavItems(markdownFiles, srcDir, base, options.ssg.extension));
|
|
4029
|
-
}
|
|
4030
|
-
const html = await renderPage$1(filePath, options, cache.navGroups, cache.siteName, base, root);
|
|
4031
|
-
cache.pages.set(filePath, html);
|
|
4032
|
-
res.setHeader("Content-Type", "text/html");
|
|
4033
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
4034
|
-
res.end(html);
|
|
4035
|
-
} catch (err) {
|
|
4036
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
4037
|
-
console.error(`[ox-content:dev] Failed to render ${filePath}:`, message);
|
|
4038
|
-
next();
|
|
4039
|
-
}
|
|
4040
|
-
};
|
|
4077
|
+
function collectOgImageEntry(context, pageResult, collected) {
|
|
4078
|
+
if (!context.shouldGenerateOgImages) return;
|
|
4079
|
+
const { layout: _layout, ...frontmatterRest } = pageResult.frontmatter;
|
|
4080
|
+
collected.ogImageEntries.push({
|
|
4081
|
+
props: {
|
|
4082
|
+
...frontmatterRest,
|
|
4083
|
+
title: pageResult.title,
|
|
4084
|
+
description: pageResult.description,
|
|
4085
|
+
siteName: context.siteName
|
|
4086
|
+
},
|
|
4087
|
+
outputPath: pageResult.routePaths.ogImagePath
|
|
4088
|
+
});
|
|
4089
|
+
collected.ogImageInputPaths.push(pageResult.inputPath);
|
|
4090
|
+
collected.ogImageUrlMap.set(pageResult.inputPath, pageResult.routePaths.ogImageUrl);
|
|
4041
4091
|
}
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
for (const
|
|
4057
|
-
|
|
4058
|
-
|
|
4092
|
+
async function generateOgImageAssets(context, collected, generatedFiles, errors) {
|
|
4093
|
+
if (!context.shouldGenerateOgImages || collected.ogImageEntries.length === 0) return;
|
|
4094
|
+
try {
|
|
4095
|
+
const ogResults = await generateOgImages(collected.ogImageEntries, context.options.ogImageOptions, context.root);
|
|
4096
|
+
if (clearMissingBrowserOgImages(ogResults, collected)) return;
|
|
4097
|
+
reportOgImageResults(ogResults, collected, generatedFiles, errors);
|
|
4098
|
+
} catch (err) {
|
|
4099
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4100
|
+
console.warn(`[ox-content:og-image] Batch generation failed: ${errorMessage}`);
|
|
4101
|
+
collected.ogImageUrlMap.clear();
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
4104
|
+
function clearMissingBrowserOgImages(ogResults, collected) {
|
|
4105
|
+
if (!(ogResults.length > 0 && ogResults.every((result) => result.error === "Chromium not available"))) return false;
|
|
4106
|
+
for (const inputPath of collected.ogImageInputPaths) collected.ogImageUrlMap.delete(inputPath);
|
|
4107
|
+
return true;
|
|
4108
|
+
}
|
|
4109
|
+
function reportOgImageResults(ogResults, collected, generatedFiles, errors) {
|
|
4110
|
+
let ogSuccessCount = 0;
|
|
4111
|
+
for (let i = 0; i < ogResults.length; i++) {
|
|
4112
|
+
const result = ogResults[i];
|
|
4113
|
+
if (result.error) {
|
|
4114
|
+
errors.push(`OG image failed for ${result.outputPath}: ${result.error}`);
|
|
4115
|
+
collected.ogImageUrlMap.delete(collected.ogImageInputPaths[i]);
|
|
4116
|
+
} else {
|
|
4117
|
+
generatedFiles.push(result.outputPath);
|
|
4118
|
+
ogSuccessCount++;
|
|
4119
|
+
}
|
|
4120
|
+
}
|
|
4121
|
+
if (ogSuccessCount > 0) {
|
|
4122
|
+
const cachedCount = ogResults.filter((result) => result.cached && !result.error).length;
|
|
4123
|
+
console.log(`[ox-content:og-image] Generated ${ogSuccessCount} OG images` + (cachedCount > 0 ? ` (${cachedCount} from cache)` : ""));
|
|
4124
|
+
}
|
|
4125
|
+
}
|
|
4126
|
+
async function generateHtmlPages(context, pageResults, collected, errors) {
|
|
4127
|
+
const generatedPages = [];
|
|
4128
|
+
for (const pageResult of pageResults) try {
|
|
4129
|
+
generatedPages.push({
|
|
4130
|
+
inputPath: pageResult.inputPath,
|
|
4131
|
+
outputPath: pageResult.routePaths.outputPath,
|
|
4132
|
+
html: await renderSsgPage(context, pageResult, collected, pageResults)
|
|
4133
|
+
});
|
|
4134
|
+
} catch (err) {
|
|
4135
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4136
|
+
errors.push(`Failed to generate HTML for ${pageResult.inputPath}: ${errorMessage}`);
|
|
4137
|
+
}
|
|
4138
|
+
return generatedPages;
|
|
4139
|
+
}
|
|
4140
|
+
async function renderSsgPage(context, pageResult, collected, allPageResults) {
|
|
4141
|
+
const { ogImageUrlMap } = collected;
|
|
4142
|
+
const pageOgImage = context.shouldGenerateOgImages && ogImageUrlMap.has(pageResult.inputPath) ? ogImageUrlMap.get(pageResult.inputPath) : context.ssgOptions.ogImage;
|
|
4143
|
+
if (context.ssgOptions.render) return renderPage(toThemePageData(pageResult), {
|
|
4144
|
+
theme: context.ssgOptions.render,
|
|
4145
|
+
siteName: context.siteName,
|
|
4146
|
+
base: context.base,
|
|
4147
|
+
nav: context.navItems,
|
|
4148
|
+
pages: allPageResults.map(toThemePageData)
|
|
4149
|
+
});
|
|
4150
|
+
if (context.ssgOptions.bare) return generateBarePage({
|
|
4151
|
+
title: pageResult.title,
|
|
4152
|
+
content: pageResult.transformedHtml,
|
|
4153
|
+
lang: context.ssgOptions.lang ?? getPageLocale(pageResult.routePaths.urlPath, context.options.i18n),
|
|
4154
|
+
description: pageResult.description,
|
|
4155
|
+
canonicalUrl: canonicalPageUrl(context, pageResult.routePaths.urlPath),
|
|
4156
|
+
siteName: context.ssgOptions.siteName,
|
|
4157
|
+
ogImage: pageOgImage,
|
|
4158
|
+
head: context.ssgOptions.head,
|
|
4159
|
+
bodyStart: context.ssgOptions.bodyStart,
|
|
4160
|
+
bodyEnd: context.ssgOptions.bodyEnd
|
|
4161
|
+
});
|
|
4162
|
+
const pageData = createSsgPageData(pageResult);
|
|
4163
|
+
return generateHtmlPage(pageData, context.navItems, context.siteName, context.base, pageOgImage, context.ssgOptions.theme, getPageLocale(pageData.path, context.options.i18n), context.options.i18n ? context.options.i18n.locales : void 0);
|
|
4164
|
+
}
|
|
4165
|
+
/** Maps an internal page result onto the theme renderer's page shape. */
|
|
4166
|
+
function toThemePageData(pageResult) {
|
|
4167
|
+
return {
|
|
4168
|
+
title: pageResult.title,
|
|
4169
|
+
description: pageResult.description,
|
|
4170
|
+
html: pageResult.transformedHtml,
|
|
4171
|
+
toc: pageResult.toc,
|
|
4172
|
+
lastUpdated: pageResult.lastUpdated,
|
|
4173
|
+
path: pageResult.inputPath,
|
|
4174
|
+
url: pageResult.routePaths.href,
|
|
4175
|
+
frontmatter: pageResult.frontmatter,
|
|
4176
|
+
layout: typeof pageResult.frontmatter.layout === "string" ? pageResult.frontmatter.layout : void 0
|
|
4177
|
+
};
|
|
4178
|
+
}
|
|
4179
|
+
/**
|
|
4180
|
+
* Absolute URL of a page, or `undefined` when `ssg.siteUrl` is not set.
|
|
4181
|
+
*
|
|
4182
|
+
* Built the same way `get_og_image_url` builds the image URL next to it, so
|
|
4183
|
+
* the canonical link and `og:image` always agree about where the page lives.
|
|
4184
|
+
*/
|
|
4185
|
+
function canonicalPageUrl(context, urlPath) {
|
|
4186
|
+
const siteUrl = context.ssgOptions.siteUrl?.replace(/\/+$/, "");
|
|
4187
|
+
if (!siteUrl) return;
|
|
4188
|
+
if (urlPath === "/" || urlPath === "") return `${siteUrl}${context.base}`;
|
|
4189
|
+
return `${siteUrl}${context.base}${urlPath}/`;
|
|
4190
|
+
}
|
|
4191
|
+
function createSsgPageData(pageResult) {
|
|
4192
|
+
const { frontmatter } = pageResult;
|
|
4193
|
+
const entryPage = frontmatter.layout === "entry" ? {
|
|
4194
|
+
hero: frontmatter.hero,
|
|
4195
|
+
features: frontmatter.features
|
|
4196
|
+
} : void 0;
|
|
4197
|
+
return {
|
|
4198
|
+
title: pageResult.title,
|
|
4199
|
+
description: pageResult.description,
|
|
4200
|
+
content: pageResult.transformedHtml,
|
|
4201
|
+
toc: pageResult.toc,
|
|
4202
|
+
lastUpdated: pageResult.lastUpdated,
|
|
4203
|
+
frontmatter,
|
|
4204
|
+
path: pageResult.routePaths.urlPath,
|
|
4205
|
+
href: pageResult.routePaths.href,
|
|
4206
|
+
entryPage
|
|
4207
|
+
};
|
|
4208
|
+
}
|
|
4209
|
+
async function writeGeneratedPages(generatedPages, context, generatedFiles) {
|
|
4210
|
+
const optimizedOutput = await externalizeSharedPageAssets(generatedPages, context.outDir, context.base);
|
|
4211
|
+
generatedFiles.push(...optimizedOutput.assets);
|
|
4212
|
+
for (const page of optimizedOutput.pages) {
|
|
4213
|
+
await fs_promises.mkdir(path.dirname(page.outputPath), { recursive: true });
|
|
4214
|
+
await fs_promises.writeFile(page.outputPath, page.html, "utf-8");
|
|
4215
|
+
generatedFiles.push(page.outputPath);
|
|
4216
|
+
}
|
|
4217
|
+
}
|
|
4218
|
+
//#endregion
|
|
4219
|
+
//#region src/search.ts
|
|
4220
|
+
/**
|
|
4221
|
+
* Full-text search functionality for Ox Content.
|
|
4222
|
+
*
|
|
4223
|
+
* Generates search index at build time and provides client-side search.
|
|
4224
|
+
*/
|
|
4225
|
+
let oxContent$1 = null;
|
|
4226
|
+
async function getOxContent() {
|
|
4227
|
+
if (!oxContent$1) try {
|
|
4228
|
+
oxContent$1 = await require_vitepress.importNapiModule();
|
|
4229
|
+
} catch {
|
|
4230
|
+
console.warn("[ox-content] Native bindings not available, search disabled");
|
|
4231
|
+
return null;
|
|
4232
|
+
}
|
|
4233
|
+
return oxContent$1;
|
|
4234
|
+
}
|
|
4235
|
+
/**
|
|
4236
|
+
* Resolves search options with defaults.
|
|
4237
|
+
*/
|
|
4238
|
+
function resolveSearchOptions(options) {
|
|
4239
|
+
if (options === false) return {
|
|
4240
|
+
enabled: false,
|
|
4241
|
+
limit: 10,
|
|
4242
|
+
prefix: true,
|
|
4243
|
+
placeholder: "Search documentation...",
|
|
4244
|
+
hotkey: "/"
|
|
4245
|
+
};
|
|
4246
|
+
const opts = typeof options === "object" ? options : {};
|
|
4247
|
+
return {
|
|
4248
|
+
enabled: opts.enabled ?? true,
|
|
4249
|
+
limit: opts.limit ?? 10,
|
|
4250
|
+
prefix: opts.prefix ?? true,
|
|
4251
|
+
placeholder: opts.placeholder ?? "Search documentation...",
|
|
4252
|
+
hotkey: opts.hotkey ?? "/"
|
|
4253
|
+
};
|
|
4254
|
+
}
|
|
4255
|
+
/**
|
|
4256
|
+
* Builds the search index from Markdown files.
|
|
4257
|
+
*/
|
|
4258
|
+
async function buildSearchIndex(srcDir, base, extensions = DEFAULT_MARKDOWN_EXTENSIONS) {
|
|
4259
|
+
const napi = await getOxContent();
|
|
4260
|
+
if (!napi) return JSON.stringify({
|
|
4261
|
+
documents: [],
|
|
4262
|
+
index: {},
|
|
4263
|
+
df: {},
|
|
4264
|
+
avg_dl: 0,
|
|
4265
|
+
doc_count: 0
|
|
4266
|
+
});
|
|
4267
|
+
return napi.buildSearchIndexFromDirectory(srcDir, base, [...extensions]);
|
|
4268
|
+
}
|
|
4269
|
+
/**
|
|
4270
|
+
* Writes the search index to a file.
|
|
4271
|
+
*/
|
|
4272
|
+
async function writeSearchIndex(indexJson, outDir) {
|
|
4273
|
+
const napi = await getOxContent();
|
|
4274
|
+
if (!napi) return;
|
|
4275
|
+
napi.writeSearchIndex(indexJson, outDir);
|
|
4276
|
+
}
|
|
4277
|
+
/**
|
|
4278
|
+
* Client-side search module code.
|
|
4279
|
+
* This is injected into the bundle as a virtual module.
|
|
4280
|
+
*/
|
|
4281
|
+
function generateSearchModule(options, indexPath) {
|
|
4282
|
+
return require_vitepress.importNapiModuleSync().generateSearchModuleFromOptions(options, indexPath);
|
|
4283
|
+
}
|
|
4284
|
+
//#endregion
|
|
4285
|
+
//#region src/dev-server.ts
|
|
4286
|
+
/**
|
|
4287
|
+
* Dev server middleware for ox-content SSG.
|
|
4288
|
+
*
|
|
4289
|
+
* Serves fully-rendered HTML pages (with navigation, theme, etc.)
|
|
4290
|
+
* during `vite dev`, matching the SSG build output.
|
|
4291
|
+
*/
|
|
4292
|
+
/** File extensions to skip in the middleware. */
|
|
4293
|
+
const SKIP_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
4294
|
+
".js",
|
|
4295
|
+
".ts",
|
|
4296
|
+
".css",
|
|
4297
|
+
".scss",
|
|
4298
|
+
".less",
|
|
4299
|
+
".svg",
|
|
4300
|
+
".png",
|
|
4301
|
+
".jpg",
|
|
4302
|
+
".jpeg",
|
|
4303
|
+
".gif",
|
|
4304
|
+
".webp",
|
|
4305
|
+
".ico",
|
|
4306
|
+
".woff",
|
|
4307
|
+
".woff2",
|
|
4308
|
+
".ttf",
|
|
4309
|
+
".eot",
|
|
4310
|
+
".json",
|
|
4311
|
+
".map",
|
|
4312
|
+
".mp4",
|
|
4313
|
+
".webm",
|
|
4314
|
+
".mp3",
|
|
4315
|
+
".pdf"
|
|
4316
|
+
]);
|
|
4317
|
+
/** Vite internal URL prefixes to skip. */
|
|
4318
|
+
const VITE_INTERNAL_PREFIXES = [
|
|
4319
|
+
"/@vite/",
|
|
4320
|
+
"/@fs/",
|
|
4321
|
+
"/@id/",
|
|
4322
|
+
"/__"
|
|
4323
|
+
];
|
|
4324
|
+
/**
|
|
4325
|
+
* Check if a request URL should be skipped by the dev server middleware.
|
|
4326
|
+
*/
|
|
4327
|
+
function shouldSkip(url) {
|
|
4328
|
+
for (const prefix of VITE_INTERNAL_PREFIXES) if (url.startsWith(prefix)) return true;
|
|
4329
|
+
if (url.includes("/node_modules/")) return true;
|
|
4330
|
+
const extMatch = url.match(/\.([a-zA-Z0-9]+)(?:\?|$)/);
|
|
4331
|
+
if (extMatch) {
|
|
4332
|
+
const ext = "." + extMatch[1].toLowerCase();
|
|
4333
|
+
if (SKIP_EXTENSIONS.has(ext)) return true;
|
|
4334
|
+
}
|
|
4335
|
+
return false;
|
|
4336
|
+
}
|
|
4337
|
+
/**
|
|
4338
|
+
* Resolve a request URL to a markdown file path.
|
|
4339
|
+
* Returns null if no matching file exists.
|
|
4340
|
+
*/
|
|
4341
|
+
async function resolveMarkdownFile(url, srcDir, extensions) {
|
|
4342
|
+
let pathname = url.split("?")[0].split("#")[0];
|
|
4343
|
+
if (pathname.endsWith("/index.html")) pathname = pathname.slice(0, -11) || "/";
|
|
4344
|
+
if (pathname !== "/" && pathname.endsWith("/")) pathname = pathname.slice(0, -1);
|
|
4345
|
+
const routePath = pathname === "/" ? "" : pathname.slice(1);
|
|
4346
|
+
const directCandidates = pathname === "/" ? extensions.map((extension) => `index${extension}`) : isMarkdownFilePath(routePath, extensions) ? [routePath] : extensions.map((extension) => `${routePath}${extension}`);
|
|
4347
|
+
for (const relativePath of directCandidates) {
|
|
4348
|
+
const filePath = path.join(srcDir, relativePath);
|
|
4349
|
+
try {
|
|
4350
|
+
await fs_promises.access(filePath);
|
|
4351
|
+
return filePath;
|
|
4352
|
+
} catch {}
|
|
4353
|
+
}
|
|
4354
|
+
for (const extension of extensions) {
|
|
4355
|
+
const indexPath = path.join(srcDir, routePath, `index${extension}`);
|
|
4356
|
+
try {
|
|
4357
|
+
await fs_promises.access(indexPath);
|
|
4358
|
+
return indexPath;
|
|
4359
|
+
} catch {}
|
|
4360
|
+
}
|
|
4361
|
+
return null;
|
|
4362
|
+
}
|
|
4363
|
+
/**
|
|
4364
|
+
* Inject Vite HMR client script into the HTML.
|
|
4365
|
+
*/
|
|
4366
|
+
function injectViteHmrClient(html) {
|
|
4367
|
+
return html.replace("</head>", "<script type=\"module\" src=\"/@vite/client\"><\/script>\n<script type=\"module\">\nif (import.meta.hot) {\n const reexecuteBodyScripts = () => {\n const scripts = Array.from(document.body.querySelectorAll('script'));\n for (const script of scripts) {\n const nextScript = document.createElement('script');\n for (const attr of script.attributes) {\n nextScript.setAttribute(attr.name, attr.value);\n }\n nextScript.textContent = script.textContent;\n script.replaceWith(nextScript);\n }\n };\n\n const applyHotUpdate = async () => {\n const nextUrl = new URL(window.location.href);\n nextUrl.searchParams.set('__ox_hmr', String(Date.now()));\n\n const scrollX = window.scrollX;\n const scrollY = window.scrollY;\n const theme = document.documentElement.getAttribute('data-theme');\n\n const response = await fetch(nextUrl.toString(), {\n cache: 'no-store',\n headers: {\n 'x-ox-content-hmr': '1',\n },\n });\n\n if (!response.ok) {\n throw new Error('Failed to fetch updated page');\n }\n\n const nextHtml = await response.text();\n const nextDocument = new DOMParser().parseFromString(nextHtml, 'text/html');\n\n if (!nextDocument.body) {\n throw new Error('Updated page is missing a body');\n }\n\n document.title = nextDocument.title;\n document.body.innerHTML = nextDocument.body.innerHTML;\n reexecuteBodyScripts();\n\n if (theme) {\n document.documentElement.setAttribute('data-theme', theme);\n }\n\n window.scrollTo({ left: scrollX, top: scrollY });\n };\n\n let pendingUpdate = Promise.resolve();\n\n import.meta.hot.on('ox-content:update', () => {\n pendingUpdate = pendingUpdate\n .then(() => applyHotUpdate())\n .catch((error) => {\n console.warn('[ox-content] HMR patch failed, falling back to reload.', error);\n location.reload();\n });\n });\n}\n<\/script>\n</head>");
|
|
4368
|
+
}
|
|
4369
|
+
/**
|
|
4370
|
+
* Create a dev server cache instance.
|
|
4371
|
+
*/
|
|
4372
|
+
function createDevServerCache() {
|
|
4373
|
+
return {
|
|
4374
|
+
navGroups: null,
|
|
4375
|
+
pages: /* @__PURE__ */ new Map(),
|
|
4376
|
+
siteName: null
|
|
4377
|
+
};
|
|
4378
|
+
}
|
|
4379
|
+
/**
|
|
4380
|
+
* Invalidate navigation cache (called on file add/unlink).
|
|
4381
|
+
*/
|
|
4382
|
+
function invalidateNavCache(cache) {
|
|
4383
|
+
cache.navGroups = null;
|
|
4384
|
+
cache.pages.clear();
|
|
4385
|
+
}
|
|
4386
|
+
/**
|
|
4387
|
+
* Invalidate page cache for a specific file (called on file change).
|
|
4388
|
+
*/
|
|
4389
|
+
function invalidatePageCache(cache, filePath) {
|
|
4390
|
+
cache.pages.delete(filePath);
|
|
4391
|
+
}
|
|
4392
|
+
/**
|
|
4393
|
+
* Resolve site name from options or package.json.
|
|
4394
|
+
*/
|
|
4395
|
+
async function resolveSiteName(options, root) {
|
|
4396
|
+
if (options.ssg.siteName) return options.ssg.siteName;
|
|
4397
|
+
try {
|
|
4398
|
+
const pkgPath = path.join(root, "package.json");
|
|
4399
|
+
const pkg = JSON.parse(await fs_promises.readFile(pkgPath, "utf-8"));
|
|
4400
|
+
if (pkg.name) return formatTitle(pkg.name);
|
|
4401
|
+
} catch {}
|
|
4402
|
+
return "Documentation";
|
|
4403
|
+
}
|
|
4404
|
+
/**
|
|
4405
|
+
* Render a single markdown page to full HTML.
|
|
4406
|
+
*/
|
|
4407
|
+
async function renderPage$1(filePath, options, navGroups, siteName, base, root) {
|
|
4408
|
+
const srcDir = path.resolve(root, options.srcDir);
|
|
4409
|
+
require_tabs.resetTabGroupCounter();
|
|
4410
|
+
resetIslandCounter();
|
|
4411
|
+
const result = await transformMarkdown(await fs_promises.readFile(filePath, "utf-8"), filePath, options, {
|
|
4412
|
+
convertMdLinks: true,
|
|
4413
|
+
baseUrl: base,
|
|
4414
|
+
sourcePath: filePath
|
|
4415
|
+
});
|
|
4416
|
+
const frontmatter = require_vitepress.normalizeVitePressFrontmatter(result.frontmatter);
|
|
4417
|
+
let transformedHtml = result.html;
|
|
4418
|
+
const { html: protectedHtml, svgs: mermaidSvgs } = protectMermaidSvgs(transformedHtml);
|
|
4419
|
+
transformedHtml = protectedHtml;
|
|
4420
|
+
transformedHtml = await transformAllPlugins(transformedHtml, {
|
|
4421
|
+
tabs: true,
|
|
4422
|
+
youtube: true,
|
|
4423
|
+
github: options.embeds.github,
|
|
4424
|
+
openGraph: options.embeds.openGraph,
|
|
4425
|
+
pm: options.embeds.pm,
|
|
4426
|
+
spotify: options.embeds.spotify,
|
|
4427
|
+
stackBlitz: options.embeds.stackBlitz,
|
|
4428
|
+
twitter: options.embeds.twitter,
|
|
4429
|
+
bluesky: options.embeds.bluesky,
|
|
4430
|
+
webContainer: options.embeds.webContainer,
|
|
4431
|
+
mermaid: true,
|
|
4432
|
+
githubToken: process.env.GITHUB_TOKEN
|
|
4433
|
+
});
|
|
4434
|
+
if (hasIslands(transformedHtml)) transformedHtml = (await transformIslands(transformedHtml)).html;
|
|
4435
|
+
transformedHtml = restoreMermaidSvgs(transformedHtml, mermaidSvgs);
|
|
4436
|
+
const title = extractTitle$1(transformedHtml, frontmatter);
|
|
4437
|
+
const description = frontmatter.description;
|
|
4438
|
+
let entryPage;
|
|
4439
|
+
if (frontmatter.layout === "entry") entryPage = {
|
|
4440
|
+
hero: frontmatter.hero,
|
|
4441
|
+
features: frontmatter.features
|
|
4442
|
+
};
|
|
4443
|
+
let html = await generateHtmlPage({
|
|
4444
|
+
title,
|
|
4445
|
+
description,
|
|
4446
|
+
content: transformedHtml,
|
|
4447
|
+
toc: result.toc,
|
|
4448
|
+
frontmatter,
|
|
4449
|
+
path: getUrlPath$1(filePath, srcDir),
|
|
4450
|
+
href: getUrlPath$1(filePath, srcDir) || "/",
|
|
4451
|
+
entryPage
|
|
4452
|
+
}, navGroups, siteName, base, options.ssg.ogImage, options.ssg.theme);
|
|
4453
|
+
html = injectViteHmrClient(html);
|
|
4454
|
+
return html;
|
|
4455
|
+
}
|
|
4456
|
+
/**
|
|
4457
|
+
* Create the dev server middleware for SSG page serving.
|
|
4458
|
+
*/
|
|
4459
|
+
function createDevServerMiddleware(options, root, cache) {
|
|
4460
|
+
const srcDir = path.resolve(root, options.srcDir);
|
|
4461
|
+
const base = options.base.endsWith("/") ? options.base : options.base + "/";
|
|
4462
|
+
return async (req, res, next) => {
|
|
4463
|
+
const url = req.url;
|
|
4464
|
+
if (!url) return next();
|
|
4465
|
+
let routeUrl = url;
|
|
4466
|
+
if (base !== "/" && routeUrl.startsWith(base)) routeUrl = "/" + routeUrl.slice(base.length);
|
|
4467
|
+
if (shouldSkip(routeUrl)) return next();
|
|
4468
|
+
const filePath = await resolveMarkdownFile(routeUrl, srcDir, options.extensions);
|
|
4469
|
+
if (!filePath) return next();
|
|
4470
|
+
try {
|
|
4471
|
+
const cached = cache.pages.get(filePath);
|
|
4472
|
+
if (cached) {
|
|
4473
|
+
res.setHeader("Content-Type", "text/html");
|
|
4474
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
4475
|
+
res.end(cached);
|
|
4476
|
+
return;
|
|
4477
|
+
}
|
|
4478
|
+
if (!cache.siteName) cache.siteName = await resolveSiteName(options, root);
|
|
4479
|
+
if (!cache.navGroups) {
|
|
4480
|
+
const markdownFiles = await collectMarkdownFiles(srcDir, options.extensions);
|
|
4481
|
+
cache.navGroups = resolveNavigationGroups(options.ssg.navigation, base, options.ssg.extension) ?? (options.ssg.theme?.sidebar.length ? buildThemeNavItems(options.ssg.theme.sidebar, base, options.ssg.extension) : buildNavItems(markdownFiles, srcDir, base, options.ssg.extension));
|
|
4482
|
+
}
|
|
4483
|
+
const html = await renderPage$1(filePath, options, cache.navGroups, cache.siteName, base, root);
|
|
4484
|
+
cache.pages.set(filePath, html);
|
|
4485
|
+
res.setHeader("Content-Type", "text/html");
|
|
4486
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
4487
|
+
res.end(html);
|
|
4488
|
+
} catch (err) {
|
|
4489
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4490
|
+
console.error(`[ox-content:dev] Failed to render ${filePath}:`, message);
|
|
4491
|
+
next();
|
|
4492
|
+
}
|
|
4493
|
+
};
|
|
4494
|
+
}
|
|
4495
|
+
//#endregion
|
|
4496
|
+
//#region src/og-viewer.ts
|
|
4497
|
+
/**
|
|
4498
|
+
* OG Viewer - Dev tool for previewing Open Graph metadata
|
|
4499
|
+
*
|
|
4500
|
+
* Accessible at /__og-viewer during development.
|
|
4501
|
+
* Shows all pages with their OG metadata, validation warnings,
|
|
4502
|
+
* and social card previews.
|
|
4503
|
+
*/
|
|
4504
|
+
function parseFrontmatter(content) {
|
|
4505
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
4506
|
+
if (!match) return {};
|
|
4507
|
+
const yaml = match[1];
|
|
4508
|
+
const result = {};
|
|
4509
|
+
for (const line of yaml.split("\n")) {
|
|
4510
|
+
const kv = line.match(/^(\w[\w-]*):\s*(.*)$/);
|
|
4511
|
+
if (!kv) continue;
|
|
4059
4512
|
const [, key, rawValue] = kv;
|
|
4060
4513
|
let value = rawValue.trim();
|
|
4061
4514
|
if (typeof value === "string" && value.startsWith("[") && value.endsWith("]")) value = value.slice(1, -1).split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
|
|
@@ -5547,562 +6000,209 @@ async function runStandardSpellcheckDocuments(maskedDocuments, options) {
|
|
|
5547
6000
|
} catch (error) {
|
|
5548
6001
|
const imports = standard.imports.join(", ");
|
|
5549
6002
|
const message = imports.length > 0 ? `[ox-content] Failed to load standard dictionaries from ${imports}. Verify the imports and install the referenced CSpell packages.` : "[ox-content] Failed to load the configured standard dictionaries.";
|
|
5550
|
-
throw new Error(message, { cause: error });
|
|
5551
|
-
}
|
|
5552
|
-
}
|
|
5553
|
-
function createStandardSpellcheckSettings(options, locale) {
|
|
5554
|
-
return {
|
|
5555
|
-
import: options.dictionary.standard ? options.dictionary.standard.imports : [],
|
|
5556
|
-
ignoreWords: options.dictionary.ignoredWords,
|
|
5557
|
-
language: locale,
|
|
5558
|
-
version: "0.2",
|
|
5559
|
-
words: [...options.dictionary.words ?? [], ...Object.values(options.dictionary.byLanguage ?? {}).flat()]
|
|
5560
|
-
};
|
|
5561
|
-
}
|
|
5562
|
-
async function loadCspellLib() {
|
|
5563
|
-
cspellLibPromise ??= import("cspell-lib");
|
|
5564
|
-
return cspellLibPromise;
|
|
5565
|
-
}
|
|
5566
|
-
function mapStandardIssueToDiagnostic(issue, languages, newlineOffsets) {
|
|
5567
|
-
const line = getLineNumberAtOffset(newlineOffsets, issue.line.offset);
|
|
5568
|
-
const column = issue.offset - issue.line.offset + 1;
|
|
5569
|
-
return {
|
|
5570
|
-
column,
|
|
5571
|
-
endColumn: column + (issue.length ?? issue.text.length),
|
|
5572
|
-
endLine: line,
|
|
5573
|
-
language: inferStandardIssueLanguage(issue.text, languages),
|
|
5574
|
-
line,
|
|
5575
|
-
message: `Unknown word "${issue.text}".`,
|
|
5576
|
-
ruleId: "spellcheck",
|
|
5577
|
-
severity: "warning",
|
|
5578
|
-
suggestions: issue.suggestions?.slice(0, 3)
|
|
5579
|
-
};
|
|
5580
|
-
}
|
|
5581
|
-
function getLineNumberAtOffset(newlineOffsets, offset) {
|
|
5582
|
-
let lo = 0;
|
|
5583
|
-
let hi = newlineOffsets.length;
|
|
5584
|
-
while (lo < hi) {
|
|
5585
|
-
const mid = lo + hi >>> 1;
|
|
5586
|
-
if (newlineOffsets[mid] < offset) lo = mid + 1;
|
|
5587
|
-
else hi = mid;
|
|
5588
|
-
}
|
|
5589
|
-
return lo + 1;
|
|
5590
|
-
}
|
|
5591
|
-
function inferStandardIssueLanguage(word, languages) {
|
|
5592
|
-
if (/[\p{Script=Hiragana}\p{Script=Katakana}]/u.test(word) && languages.includes("ja")) return "ja";
|
|
5593
|
-
if (/[\p{Script=Han}]/u.test(word)) {
|
|
5594
|
-
if (languages.includes("zh") && !languages.includes("ja")) return "zh";
|
|
5595
|
-
if (languages.includes("ja") && !languages.includes("zh")) return "ja";
|
|
5596
|
-
}
|
|
5597
|
-
if (/[\p{Script=Latin}]/u.test(word)) {
|
|
5598
|
-
const latinLanguages = languages.filter((language) => language !== "ja" && language !== "zh");
|
|
5599
|
-
if (latinLanguages.length === 1) return latinLanguages[0];
|
|
5600
|
-
return inferLatinLanguageFromCharacters(word, latinLanguages);
|
|
5601
|
-
}
|
|
5602
|
-
}
|
|
5603
|
-
function inferLatinLanguageFromCharacters(word, languages) {
|
|
5604
|
-
if (languages.includes("pl") && /[ąćęłńóśźż]/iu.test(word)) return "pl";
|
|
5605
|
-
if (languages.includes("de") && /[äöüß]/iu.test(word)) return "de";
|
|
5606
|
-
if (languages.includes("fr") && /[àâæçéèêëîïôœùûüÿ]/iu.test(word)) return "fr";
|
|
5607
|
-
}
|
|
5608
|
-
function summarizeDiagnostics(diagnostics) {
|
|
5609
|
-
let errorCount = 0;
|
|
5610
|
-
let warningCount = 0;
|
|
5611
|
-
let infoCount = 0;
|
|
5612
|
-
for (const diagnostic of diagnostics) if (diagnostic.severity === "error") errorCount += 1;
|
|
5613
|
-
else if (diagnostic.severity === "warning") warningCount += 1;
|
|
5614
|
-
else infoCount += 1;
|
|
5615
|
-
return {
|
|
5616
|
-
diagnostics,
|
|
5617
|
-
errorCount,
|
|
5618
|
-
infoCount,
|
|
5619
|
-
warningCount
|
|
5620
|
-
};
|
|
5621
|
-
}
|
|
5622
|
-
function createEmptyLintResult$1() {
|
|
5623
|
-
return summarizeDiagnostics([]);
|
|
5624
|
-
}
|
|
5625
|
-
function sortDiagnostics(diagnostics) {
|
|
5626
|
-
return [...diagnostics].sort((left, right) => {
|
|
5627
|
-
if (left.line !== right.line) return left.line - right.line;
|
|
5628
|
-
if (left.column !== right.column) return left.column - right.column;
|
|
5629
|
-
return left.ruleId.localeCompare(right.ruleId);
|
|
5630
|
-
});
|
|
5631
|
-
}
|
|
5632
|
-
//#endregion
|
|
5633
|
-
//#region src/lint-files.ts
|
|
5634
|
-
const DEFAULT_LINT_FILE_INCLUDE = [
|
|
5635
|
-
"**/*.md",
|
|
5636
|
-
"**/*.markdown",
|
|
5637
|
-
"**/*.mdx"
|
|
5638
|
-
];
|
|
5639
|
-
const DEFAULT_LINT_FILE_EXCLUDE = [
|
|
5640
|
-
"**/node_modules/**",
|
|
5641
|
-
"**/.git/**",
|
|
5642
|
-
"**/dist/**"
|
|
5643
|
-
];
|
|
5644
|
-
/**
|
|
5645
|
-
* Returns true if the file path is included by the configured glob filters.
|
|
5646
|
-
*/
|
|
5647
|
-
function shouldLintMarkdownFile(filePath, options = {}) {
|
|
5648
|
-
const resolvedOptions = resolveMarkdownLintFileOptions(options);
|
|
5649
|
-
return shouldLintAbsoluteFile(node_path.resolve(resolvedOptions.cwd, filePath), resolvedOptions);
|
|
5650
|
-
}
|
|
5651
|
-
/**
|
|
5652
|
-
* Lints a single Markdown file using project-style include/exclude settings.
|
|
5653
|
-
*
|
|
5654
|
-
* If the file is filtered out by `include` / `exclude`, the returned result is
|
|
5655
|
-
* marked as `skipped` and contains no diagnostics.
|
|
5656
|
-
*/
|
|
5657
|
-
async function lintMarkdownFile(filePath, options = {}) {
|
|
5658
|
-
const resolvedOptions = resolveMarkdownLintFileOptions(options);
|
|
5659
|
-
return lintMarkdownFileWithResolvedOptions(node_path.resolve(resolvedOptions.cwd, filePath), resolvedOptions);
|
|
5660
|
-
}
|
|
5661
|
-
/**
|
|
5662
|
-
* Lints all Markdown files matched by the configured include/exclude patterns.
|
|
5663
|
-
*/
|
|
5664
|
-
async function lintMarkdownFiles(options = {}) {
|
|
5665
|
-
const resolvedOptions = resolveMarkdownLintFileOptions(options);
|
|
5666
|
-
const matchedFiles = await collectMarkdownLintFileEntries(resolvedOptions);
|
|
5667
|
-
const results = await lintMarkdownDocumentsAsync(await Promise.all(matchedFiles.map((file) => node_fs_promises.readFile(file.filePath, "utf-8"))), resolvedOptions.lintOptions);
|
|
5668
|
-
const files = matchedFiles.map((file, index) => ({
|
|
5669
|
-
...results[index] ?? createEmptyLintResult(),
|
|
5670
|
-
filePath: file.filePath,
|
|
5671
|
-
relativePath: file.relativePath,
|
|
5672
|
-
skipped: false
|
|
5673
|
-
}));
|
|
5674
|
-
const diagnostics = files.flatMap((fileResult) => fileResult.diagnostics.map((diagnostic) => ({
|
|
5675
|
-
...diagnostic,
|
|
5676
|
-
filePath: fileResult.filePath,
|
|
5677
|
-
relativePath: fileResult.relativePath
|
|
5678
|
-
})));
|
|
5679
|
-
return {
|
|
5680
|
-
checkedFileCount: files.length,
|
|
5681
|
-
diagnostics,
|
|
5682
|
-
errorCount: files.reduce((count, fileResult) => count + fileResult.errorCount, 0),
|
|
5683
|
-
files,
|
|
5684
|
-
infoCount: files.reduce((count, fileResult) => count + fileResult.infoCount, 0),
|
|
5685
|
-
warningCount: files.reduce((count, fileResult) => count + fileResult.warningCount, 0)
|
|
5686
|
-
};
|
|
5687
|
-
}
|
|
5688
|
-
function resolveMarkdownLintFileOptions(options) {
|
|
5689
|
-
return {
|
|
5690
|
-
cwd: node_path.resolve(options.cwd ?? process.cwd()),
|
|
5691
|
-
exclude: [.../* @__PURE__ */ new Set([...options.exclude ?? DEFAULT_LINT_FILE_EXCLUDE, ...options.ignore ?? []])],
|
|
5692
|
-
include: [...new Set(options.include ?? DEFAULT_LINT_FILE_INCLUDE)],
|
|
5693
|
-
lintOptions: {
|
|
5694
|
-
dictionary: options.dictionary,
|
|
5695
|
-
languages: options.languages,
|
|
5696
|
-
rules: options.rules
|
|
5697
|
-
}
|
|
5698
|
-
};
|
|
5699
|
-
}
|
|
5700
|
-
async function lintMarkdownFileWithResolvedOptions(filePath, options) {
|
|
5701
|
-
const absoluteFilePath = node_path.resolve(filePath);
|
|
5702
|
-
const relativePath = normalizePath(node_path.relative(options.cwd, absoluteFilePath));
|
|
5703
|
-
if (!shouldLintAbsoluteFile(absoluteFilePath, options)) return {
|
|
5704
|
-
...createEmptyLintResult(),
|
|
5705
|
-
filePath: absoluteFilePath,
|
|
5706
|
-
relativePath,
|
|
5707
|
-
skipped: true
|
|
5708
|
-
};
|
|
5709
|
-
return {
|
|
5710
|
-
...await lintMarkdownAsync(await node_fs_promises.readFile(absoluteFilePath, "utf-8"), options.lintOptions),
|
|
5711
|
-
filePath: absoluteFilePath,
|
|
5712
|
-
relativePath,
|
|
5713
|
-
skipped: false
|
|
5714
|
-
};
|
|
5715
|
-
}
|
|
5716
|
-
async function collectMarkdownLintFileEntries(options) {
|
|
5717
|
-
const files = /* @__PURE__ */ new Map();
|
|
5718
|
-
for (const pattern of options.include) {
|
|
5719
|
-
const matches = await (0, glob.glob)(pattern, {
|
|
5720
|
-
absolute: true,
|
|
5721
|
-
cwd: options.cwd,
|
|
5722
|
-
ignore: options.exclude,
|
|
5723
|
-
nodir: true
|
|
5724
|
-
});
|
|
5725
|
-
for (const filePath of matches) {
|
|
5726
|
-
const absoluteFilePath = node_path.resolve(filePath);
|
|
5727
|
-
if (shouldLintAbsoluteFile(absoluteFilePath, options)) files.set(absoluteFilePath, {
|
|
5728
|
-
filePath: absoluteFilePath,
|
|
5729
|
-
relativePath: normalizePath(node_path.relative(options.cwd, absoluteFilePath))
|
|
5730
|
-
});
|
|
5731
|
-
}
|
|
5732
|
-
}
|
|
5733
|
-
return [...files.values()].sort((left, right) => left.filePath.localeCompare(right.filePath));
|
|
5734
|
-
}
|
|
5735
|
-
function shouldLintAbsoluteFile(filePath, options) {
|
|
5736
|
-
const absolutePath = normalizePath(node_path.resolve(filePath));
|
|
5737
|
-
const relativePath = normalizePath(node_path.relative(options.cwd, absolutePath));
|
|
5738
|
-
const matches = (patterns) => patterns.some((pattern) => {
|
|
5739
|
-
const normalizedPattern = normalizePath(pattern);
|
|
5740
|
-
return node_path.matchesGlob(relativePath, normalizedPattern) || node_path.matchesGlob(absolutePath, normalizedPattern);
|
|
5741
|
-
});
|
|
5742
|
-
return matches(options.include) && !matches(options.exclude);
|
|
5743
|
-
}
|
|
5744
|
-
function normalizePath(value) {
|
|
5745
|
-
return value.split(node_path.sep).join("/");
|
|
6003
|
+
throw new Error(message, { cause: error });
|
|
6004
|
+
}
|
|
5746
6005
|
}
|
|
5747
|
-
function
|
|
6006
|
+
function createStandardSpellcheckSettings(options, locale) {
|
|
5748
6007
|
return {
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
6008
|
+
import: options.dictionary.standard ? options.dictionary.standard.imports : [],
|
|
6009
|
+
ignoreWords: options.dictionary.ignoredWords,
|
|
6010
|
+
language: locale,
|
|
6011
|
+
version: "0.2",
|
|
6012
|
+
words: [...options.dictionary.words ?? [], ...Object.values(options.dictionary.byLanguage ?? {}).flat()]
|
|
5753
6013
|
};
|
|
5754
6014
|
}
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
clearRenderContext: () => clearRenderContext,
|
|
5759
|
-
generateFrontmatterTypes: () => generateFrontmatterTypes,
|
|
5760
|
-
inferType: () => inferType,
|
|
5761
|
-
setRenderContext: () => setRenderContext,
|
|
5762
|
-
useIsActive: () => useIsActive,
|
|
5763
|
-
useNav: () => useNav,
|
|
5764
|
-
usePageProps: () => usePageProps,
|
|
5765
|
-
useRenderContext: () => useRenderContext,
|
|
5766
|
-
useSiteConfig: () => useSiteConfig
|
|
5767
|
-
});
|
|
5768
|
-
/**
|
|
5769
|
-
* Sets the current render context.
|
|
5770
|
-
* Called internally during page rendering.
|
|
5771
|
-
* @internal
|
|
5772
|
-
*/
|
|
5773
|
-
function setRenderContext(ctx) {
|
|
5774
|
-
currentContext = ctx;
|
|
5775
|
-
}
|
|
5776
|
-
/**
|
|
5777
|
-
* Clears the current render context.
|
|
5778
|
-
* Called internally after page rendering.
|
|
5779
|
-
* @internal
|
|
5780
|
-
*/
|
|
5781
|
-
function clearRenderContext() {
|
|
5782
|
-
currentContext = null;
|
|
5783
|
-
}
|
|
5784
|
-
/**
|
|
5785
|
-
* Gets the current page props.
|
|
5786
|
-
*
|
|
5787
|
-
* @returns The current page props
|
|
5788
|
-
* @throws Error if called outside of a render context
|
|
5789
|
-
*
|
|
5790
|
-
* @example
|
|
5791
|
-
* ```tsx
|
|
5792
|
-
* function PageTitle() {
|
|
5793
|
-
* const page = usePageProps();
|
|
5794
|
-
* return <h1>{page.title}</h1>;
|
|
5795
|
-
* }
|
|
5796
|
-
* ```
|
|
5797
|
-
*/
|
|
5798
|
-
function usePageProps() {
|
|
5799
|
-
if (!currentContext) throw new Error("[ox-content] usePageProps() must be called during page rendering. Make sure you are using it inside a theme component.");
|
|
5800
|
-
return currentContext.page;
|
|
5801
|
-
}
|
|
5802
|
-
/**
|
|
5803
|
-
* Gets the site configuration.
|
|
5804
|
-
*
|
|
5805
|
-
* @returns The site configuration
|
|
5806
|
-
* @throws Error if called outside of a render context
|
|
5807
|
-
*
|
|
5808
|
-
* @example
|
|
5809
|
-
* ```tsx
|
|
5810
|
-
* function SiteHeader() {
|
|
5811
|
-
* const site = useSiteConfig();
|
|
5812
|
-
* return <header>{site.name}</header>;
|
|
5813
|
-
* }
|
|
5814
|
-
* ```
|
|
5815
|
-
*/
|
|
5816
|
-
function useSiteConfig() {
|
|
5817
|
-
if (!currentContext) throw new Error("[ox-content] useSiteConfig() must be called during page rendering. Make sure you are using it inside a theme component.");
|
|
5818
|
-
return currentContext.site;
|
|
5819
|
-
}
|
|
5820
|
-
/**
|
|
5821
|
-
* Gets the full render context.
|
|
5822
|
-
*
|
|
5823
|
-
* @returns The complete render context
|
|
5824
|
-
* @throws Error if called outside of a render context
|
|
5825
|
-
*
|
|
5826
|
-
* @example
|
|
5827
|
-
* ```tsx
|
|
5828
|
-
* function Layout({ children }) {
|
|
5829
|
-
* const ctx = useRenderContext();
|
|
5830
|
-
* return (
|
|
5831
|
-
* <html>
|
|
5832
|
-
* <head><title>{ctx.page.title} - {ctx.site.name}</title></head>
|
|
5833
|
-
* <body>{children}</body>
|
|
5834
|
-
* </html>
|
|
5835
|
-
* );
|
|
5836
|
-
* }
|
|
5837
|
-
* ```
|
|
5838
|
-
*/
|
|
5839
|
-
function useRenderContext() {
|
|
5840
|
-
if (!currentContext) throw new Error("[ox-content] useRenderContext() must be called during page rendering. Make sure you are using it inside a theme component.");
|
|
5841
|
-
return currentContext;
|
|
5842
|
-
}
|
|
5843
|
-
/**
|
|
5844
|
-
* Gets the navigation groups.
|
|
5845
|
-
*
|
|
5846
|
-
* @example
|
|
5847
|
-
* ```tsx
|
|
5848
|
-
* function Sidebar() {
|
|
5849
|
-
* const nav = useNav();
|
|
5850
|
-
* return (
|
|
5851
|
-
* <nav>
|
|
5852
|
-
* {each(nav, (group) => (
|
|
5853
|
-
* <div>
|
|
5854
|
-
* <h3>{group.title}</h3>
|
|
5855
|
-
* <ul>
|
|
5856
|
-
* {each(group.items, (item) => (
|
|
5857
|
-
* <li><a href={item.href}>{item.title}</a></li>
|
|
5858
|
-
* ))}
|
|
5859
|
-
* </ul>
|
|
5860
|
-
* </div>
|
|
5861
|
-
* ))}
|
|
5862
|
-
* </nav>
|
|
5863
|
-
* );
|
|
5864
|
-
* }
|
|
5865
|
-
* ```
|
|
5866
|
-
*/
|
|
5867
|
-
function useNav() {
|
|
5868
|
-
return useSiteConfig().nav;
|
|
6015
|
+
async function loadCspellLib() {
|
|
6016
|
+
cspellLibPromise ??= import("cspell-lib");
|
|
6017
|
+
return cspellLibPromise;
|
|
5869
6018
|
}
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
6019
|
+
function mapStandardIssueToDiagnostic(issue, languages, newlineOffsets) {
|
|
6020
|
+
const line = getLineNumberAtOffset(newlineOffsets, issue.line.offset);
|
|
6021
|
+
const column = issue.offset - issue.line.offset + 1;
|
|
6022
|
+
return {
|
|
6023
|
+
column,
|
|
6024
|
+
endColumn: column + (issue.length ?? issue.text.length),
|
|
6025
|
+
endLine: line,
|
|
6026
|
+
language: inferStandardIssueLanguage(issue.text, languages),
|
|
6027
|
+
line,
|
|
6028
|
+
message: `Unknown word "${issue.text}".`,
|
|
6029
|
+
ruleId: "spellcheck",
|
|
6030
|
+
severity: "warning",
|
|
6031
|
+
suggestions: issue.suggestions?.slice(0, 3)
|
|
6032
|
+
};
|
|
5884
6033
|
}
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
if (typeof value === "number") return "number";
|
|
5893
|
-
if (typeof value === "boolean") return "boolean";
|
|
5894
|
-
if (Array.isArray(value)) {
|
|
5895
|
-
if (value.length === 0) return "unknown[]";
|
|
5896
|
-
const itemTypes = [...new Set(value.map(inferType))];
|
|
5897
|
-
if (itemTypes.length === 1) return `${itemTypes[0]}[]`;
|
|
5898
|
-
return `(${itemTypes.join(" | ")})[]`;
|
|
5899
|
-
}
|
|
5900
|
-
if (typeof value === "object") {
|
|
5901
|
-
const entries = Object.entries(value);
|
|
5902
|
-
if (entries.length === 0) return "Record<string, unknown>";
|
|
5903
|
-
return `{ ${entries.map(([k, v]) => `${k}: ${inferType(v)}`).join("; ")} }`;
|
|
6034
|
+
function getLineNumberAtOffset(newlineOffsets, offset) {
|
|
6035
|
+
let lo = 0;
|
|
6036
|
+
let hi = newlineOffsets.length;
|
|
6037
|
+
while (lo < hi) {
|
|
6038
|
+
const mid = lo + hi >>> 1;
|
|
6039
|
+
if (newlineOffsets[mid] < offset) lo = mid + 1;
|
|
6040
|
+
else hi = mid;
|
|
5904
6041
|
}
|
|
5905
|
-
return
|
|
6042
|
+
return lo + 1;
|
|
5906
6043
|
}
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
for (const sample of samples) for (const [key, value] of Object.entries(sample)) {
|
|
5913
|
-
const existing = fields.get(key) ?? {
|
|
5914
|
-
types: /* @__PURE__ */ new Set(),
|
|
5915
|
-
count: 0
|
|
5916
|
-
};
|
|
5917
|
-
existing.types.add(inferType(value));
|
|
5918
|
-
existing.count++;
|
|
5919
|
-
fields.set(key, existing);
|
|
6044
|
+
function inferStandardIssueLanguage(word, languages) {
|
|
6045
|
+
if (/[\p{Script=Hiragana}\p{Script=Katakana}]/u.test(word) && languages.includes("ja")) return "ja";
|
|
6046
|
+
if (/[\p{Script=Han}]/u.test(word)) {
|
|
6047
|
+
if (languages.includes("zh") && !languages.includes("ja")) return "zh";
|
|
6048
|
+
if (languages.includes("ja") && !languages.includes("zh")) return "ja";
|
|
5920
6049
|
}
|
|
5921
|
-
|
|
5922
|
-
"
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
" */",
|
|
5926
|
-
"",
|
|
5927
|
-
`export interface ${interfaceName} {`
|
|
5928
|
-
];
|
|
5929
|
-
for (const [name, { types, count }] of fields) {
|
|
5930
|
-
const isOptional = count < samples.length;
|
|
5931
|
-
const typeStr = [...types].join(" | ");
|
|
5932
|
-
const optionalMark = isOptional ? "?" : "";
|
|
5933
|
-
lines.push(` ${name}${optionalMark}: ${typeStr};`);
|
|
6050
|
+
if (/[\p{Script=Latin}]/u.test(word)) {
|
|
6051
|
+
const latinLanguages = languages.filter((language) => language !== "ja" && language !== "zh");
|
|
6052
|
+
if (latinLanguages.length === 1) return latinLanguages[0];
|
|
6053
|
+
return inferLatinLanguageFromCharacters(word, latinLanguages);
|
|
5934
6054
|
}
|
|
5935
|
-
lines.push("}");
|
|
5936
|
-
lines.push("");
|
|
5937
|
-
lines.push(`export type PageProps = import('@ox-content/vite-plugin').PageProps<${interfaceName}>;`);
|
|
5938
|
-
lines.push("");
|
|
5939
|
-
return lines.join("\n");
|
|
5940
6055
|
}
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
6056
|
+
function inferLatinLanguageFromCharacters(word, languages) {
|
|
6057
|
+
if (languages.includes("pl") && /[ąćęłńóśźż]/iu.test(word)) return "pl";
|
|
6058
|
+
if (languages.includes("de") && /[äöüß]/iu.test(word)) return "de";
|
|
6059
|
+
if (languages.includes("fr") && /[àâæçéèêëîïôœùûüÿ]/iu.test(word)) return "fr";
|
|
6060
|
+
}
|
|
6061
|
+
function summarizeDiagnostics(diagnostics) {
|
|
6062
|
+
let errorCount = 0;
|
|
6063
|
+
let warningCount = 0;
|
|
6064
|
+
let infoCount = 0;
|
|
6065
|
+
for (const diagnostic of diagnostics) if (diagnostic.severity === "error") errorCount += 1;
|
|
6066
|
+
else if (diagnostic.severity === "warning") warningCount += 1;
|
|
6067
|
+
else infoCount += 1;
|
|
6068
|
+
return {
|
|
6069
|
+
diagnostics,
|
|
6070
|
+
errorCount,
|
|
6071
|
+
infoCount,
|
|
6072
|
+
warningCount
|
|
6073
|
+
};
|
|
6074
|
+
}
|
|
6075
|
+
function createEmptyLintResult$1() {
|
|
6076
|
+
return summarizeDiagnostics([]);
|
|
6077
|
+
}
|
|
6078
|
+
function sortDiagnostics(diagnostics) {
|
|
6079
|
+
return [...diagnostics].sort((left, right) => {
|
|
6080
|
+
if (left.line !== right.line) return left.line - right.line;
|
|
6081
|
+
if (left.column !== right.column) return left.column - right.column;
|
|
6082
|
+
return left.ruleId.localeCompare(right.ruleId);
|
|
6083
|
+
});
|
|
6084
|
+
}
|
|
5945
6085
|
//#endregion
|
|
5946
|
-
//#region src/
|
|
6086
|
+
//#region src/lint-files.ts
|
|
6087
|
+
const DEFAULT_LINT_FILE_INCLUDE = [
|
|
6088
|
+
"**/*.md",
|
|
6089
|
+
"**/*.markdown",
|
|
6090
|
+
"**/*.mdx"
|
|
6091
|
+
];
|
|
6092
|
+
const DEFAULT_LINT_FILE_EXCLUDE = [
|
|
6093
|
+
"**/node_modules/**",
|
|
6094
|
+
"**/.git/**",
|
|
6095
|
+
"**/dist/**"
|
|
6096
|
+
];
|
|
5947
6097
|
/**
|
|
5948
|
-
*
|
|
5949
|
-
*
|
|
5950
|
-
* Renders JSX theme components to static HTML strings.
|
|
5951
|
-
* No client-side JavaScript is included by default.
|
|
6098
|
+
* Returns true if the file path is included by the configured glob filters.
|
|
5952
6099
|
*/
|
|
5953
|
-
|
|
6100
|
+
function shouldLintMarkdownFile(filePath, options = {}) {
|
|
6101
|
+
const resolvedOptions = resolveMarkdownLintFileOptions(options);
|
|
6102
|
+
return shouldLintAbsoluteFile(node_path.resolve(resolvedOptions.cwd, filePath), resolvedOptions);
|
|
6103
|
+
}
|
|
5954
6104
|
/**
|
|
5955
|
-
*
|
|
6105
|
+
* Lints a single Markdown file using project-style include/exclude settings.
|
|
5956
6106
|
*
|
|
5957
|
-
*
|
|
5958
|
-
*
|
|
5959
|
-
* @returns Rendered HTML string
|
|
6107
|
+
* If the file is filtered out by `include` / `exclude`, the returned result is
|
|
6108
|
+
* marked as `skipped` and contains no diagnostics.
|
|
5960
6109
|
*/
|
|
5961
|
-
function
|
|
5962
|
-
const
|
|
5963
|
-
|
|
5964
|
-
page: {
|
|
5965
|
-
title: page.title,
|
|
5966
|
-
description: page.description,
|
|
5967
|
-
html: page.html,
|
|
5968
|
-
toc: page.toc,
|
|
5969
|
-
lastUpdated: page.lastUpdated,
|
|
5970
|
-
path: page.path,
|
|
5971
|
-
url: page.url,
|
|
5972
|
-
frontmatter: page.frontmatter,
|
|
5973
|
-
layout: page.layout
|
|
5974
|
-
},
|
|
5975
|
-
site: {
|
|
5976
|
-
name: siteName,
|
|
5977
|
-
base,
|
|
5978
|
-
nav,
|
|
5979
|
-
pages: pages.map((p) => ({
|
|
5980
|
-
title: p.title,
|
|
5981
|
-
description: p.description,
|
|
5982
|
-
html: p.html,
|
|
5983
|
-
toc: p.toc,
|
|
5984
|
-
lastUpdated: p.lastUpdated,
|
|
5985
|
-
path: p.path,
|
|
5986
|
-
url: p.url,
|
|
5987
|
-
frontmatter: p.frontmatter,
|
|
5988
|
-
layout: p.layout
|
|
5989
|
-
}))
|
|
5990
|
-
}
|
|
5991
|
-
});
|
|
5992
|
-
try {
|
|
5993
|
-
const result = theme({ children: require_jsx_html.raw(page.html) });
|
|
5994
|
-
const html = require_jsx_html.renderToString(result);
|
|
5995
|
-
if (!html.trimStart().toLowerCase().startsWith("<!doctype")) return `<!DOCTYPE html>\n${html}`;
|
|
5996
|
-
return html;
|
|
5997
|
-
} finally {
|
|
5998
|
-
clearRenderContext();
|
|
5999
|
-
}
|
|
6110
|
+
async function lintMarkdownFile(filePath, options = {}) {
|
|
6111
|
+
const resolvedOptions = resolveMarkdownLintFileOptions(options);
|
|
6112
|
+
return lintMarkdownFileWithResolvedOptions(node_path.resolve(resolvedOptions.cwd, filePath), resolvedOptions);
|
|
6000
6113
|
}
|
|
6001
6114
|
/**
|
|
6002
|
-
*
|
|
6003
|
-
*
|
|
6004
|
-
* @param pages - All pages to render
|
|
6005
|
-
* @param options - Theme render options
|
|
6006
|
-
* @returns Map of output paths to rendered HTML
|
|
6115
|
+
* Lints all Markdown files matched by the configured include/exclude patterns.
|
|
6007
6116
|
*/
|
|
6008
|
-
async function
|
|
6009
|
-
const
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6117
|
+
async function lintMarkdownFiles(options = {}) {
|
|
6118
|
+
const resolvedOptions = resolveMarkdownLintFileOptions(options);
|
|
6119
|
+
const matchedFiles = await collectMarkdownLintFileEntries(resolvedOptions);
|
|
6120
|
+
const results = await lintMarkdownDocumentsAsync(await Promise.all(matchedFiles.map((file) => node_fs_promises.readFile(file.filePath, "utf-8"))), resolvedOptions.lintOptions);
|
|
6121
|
+
const files = matchedFiles.map((file, index) => ({
|
|
6122
|
+
...results[index] ?? createEmptyLintResult(),
|
|
6123
|
+
filePath: file.filePath,
|
|
6124
|
+
relativePath: file.relativePath,
|
|
6125
|
+
skipped: false
|
|
6126
|
+
}));
|
|
6127
|
+
const diagnostics = files.flatMap((fileResult) => fileResult.diagnostics.map((diagnostic) => ({
|
|
6128
|
+
...diagnostic,
|
|
6129
|
+
filePath: fileResult.filePath,
|
|
6130
|
+
relativePath: fileResult.relativePath
|
|
6131
|
+
})));
|
|
6132
|
+
return {
|
|
6133
|
+
checkedFileCount: files.length,
|
|
6134
|
+
diagnostics,
|
|
6135
|
+
errorCount: files.reduce((count, fileResult) => count + fileResult.errorCount, 0),
|
|
6136
|
+
files,
|
|
6137
|
+
infoCount: files.reduce((count, fileResult) => count + fileResult.infoCount, 0),
|
|
6138
|
+
warningCount: files.reduce((count, fileResult) => count + fileResult.warningCount, 0)
|
|
6139
|
+
};
|
|
6140
|
+
}
|
|
6141
|
+
function resolveMarkdownLintFileOptions(options) {
|
|
6142
|
+
return {
|
|
6143
|
+
cwd: node_path.resolve(options.cwd ?? process.cwd()),
|
|
6144
|
+
exclude: [.../* @__PURE__ */ new Set([...options.exclude ?? DEFAULT_LINT_FILE_EXCLUDE, ...options.ignore ?? []])],
|
|
6145
|
+
include: [...new Set(options.include ?? DEFAULT_LINT_FILE_INCLUDE)],
|
|
6146
|
+
lintOptions: {
|
|
6147
|
+
dictionary: options.dictionary,
|
|
6148
|
+
languages: options.languages,
|
|
6149
|
+
rules: options.rules
|
|
6150
|
+
}
|
|
6151
|
+
};
|
|
6152
|
+
}
|
|
6153
|
+
async function lintMarkdownFileWithResolvedOptions(filePath, options) {
|
|
6154
|
+
const absoluteFilePath = node_path.resolve(filePath);
|
|
6155
|
+
const relativePath = normalizePath(node_path.relative(options.cwd, absoluteFilePath));
|
|
6156
|
+
if (!shouldLintAbsoluteFile(absoluteFilePath, options)) return {
|
|
6157
|
+
...createEmptyLintResult(),
|
|
6158
|
+
filePath: absoluteFilePath,
|
|
6159
|
+
relativePath,
|
|
6160
|
+
skipped: true
|
|
6161
|
+
};
|
|
6162
|
+
return {
|
|
6163
|
+
...await lintMarkdownAsync(await node_fs_promises.readFile(absoluteFilePath, "utf-8"), options.lintOptions),
|
|
6164
|
+
filePath: absoluteFilePath,
|
|
6165
|
+
relativePath,
|
|
6166
|
+
skipped: false
|
|
6167
|
+
};
|
|
6168
|
+
}
|
|
6169
|
+
async function collectMarkdownLintFileEntries(options) {
|
|
6170
|
+
const files = /* @__PURE__ */ new Map();
|
|
6171
|
+
for (const pattern of options.include) {
|
|
6172
|
+
const matches = await (0, glob.glob)(pattern, {
|
|
6173
|
+
absolute: true,
|
|
6174
|
+
cwd: options.cwd,
|
|
6175
|
+
ignore: options.exclude,
|
|
6176
|
+
nodir: true
|
|
6014
6177
|
});
|
|
6015
|
-
|
|
6178
|
+
for (const filePath of matches) {
|
|
6179
|
+
const absoluteFilePath = node_path.resolve(filePath);
|
|
6180
|
+
if (shouldLintAbsoluteFile(absoluteFilePath, options)) files.set(absoluteFilePath, {
|
|
6181
|
+
filePath: absoluteFilePath,
|
|
6182
|
+
relativePath: normalizePath(node_path.relative(options.cwd, absoluteFilePath))
|
|
6183
|
+
});
|
|
6184
|
+
}
|
|
6016
6185
|
}
|
|
6017
|
-
|
|
6018
|
-
return results;
|
|
6019
|
-
}
|
|
6020
|
-
/**
|
|
6021
|
-
* Generates TypeScript type definitions from page frontmatter.
|
|
6022
|
-
*
|
|
6023
|
-
* @param pages - All pages
|
|
6024
|
-
* @param outDir - Output directory for types
|
|
6025
|
-
*/
|
|
6026
|
-
async function generateTypes(pages, outDir) {
|
|
6027
|
-
const types = generateFrontmatterTypes(pages.map((p) => p.frontmatter));
|
|
6028
|
-
const typesPath = (0, node_path.join)(outDir, "page-props.d.ts");
|
|
6029
|
-
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(typesPath), { recursive: true });
|
|
6030
|
-
await (0, node_fs_promises.writeFile)(typesPath, types, "utf-8");
|
|
6186
|
+
return [...files.values()].sort((left, right) => left.filePath.localeCompare(right.filePath));
|
|
6031
6187
|
}
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
return { __html: `<!DOCTYPE html>
|
|
6041
|
-
<html lang="en">
|
|
6042
|
-
<head>
|
|
6043
|
-
<meta charset="UTF-8">
|
|
6044
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6045
|
-
<title>${escapeHtml(page.title)} - ${escapeHtml(site.name)}</title>
|
|
6046
|
-
${page.description ? `<meta name="description" content="${escapeHtml(page.description)}">` : ""}
|
|
6047
|
-
<style>
|
|
6048
|
-
:root {
|
|
6049
|
-
--octc-color-primary: #4f6fae;
|
|
6050
|
-
--octc-color-text: #131a30;
|
|
6051
|
-
--octc-color-bg: #ffffff;
|
|
6052
|
-
--octc-color-bg-alt: #f5f7fb;
|
|
6053
|
-
--octc-color-text-muted: #4f607b;
|
|
6054
|
-
--octc-color-border: #d2dbea;
|
|
6055
|
-
}
|
|
6056
|
-
body {
|
|
6057
|
-
font-family: "IBM Plex Sans", "Avenir Next", "Segoe UI Variable", "Segoe UI", sans-serif;
|
|
6058
|
-
line-height: 1.7;
|
|
6059
|
-
color: var(--octc-color-text);
|
|
6060
|
-
background: var(--octc-color-bg);
|
|
6061
|
-
max-width: 800px;
|
|
6062
|
-
margin: 0 auto;
|
|
6063
|
-
padding: 2rem;
|
|
6064
|
-
}
|
|
6065
|
-
a { color: var(--octc-color-primary); }
|
|
6066
|
-
</style>
|
|
6067
|
-
</head>
|
|
6068
|
-
<body>
|
|
6069
|
-
<header>
|
|
6070
|
-
<h1>${escapeHtml(site.name)}</h1>
|
|
6071
|
-
</header>
|
|
6072
|
-
<main>
|
|
6073
|
-
${children.__html}
|
|
6074
|
-
</main>
|
|
6075
|
-
</body>
|
|
6076
|
-
</html>` };
|
|
6188
|
+
function shouldLintAbsoluteFile(filePath, options) {
|
|
6189
|
+
const absolutePath = normalizePath(node_path.resolve(filePath));
|
|
6190
|
+
const relativePath = normalizePath(node_path.relative(options.cwd, absolutePath));
|
|
6191
|
+
const matches = (patterns) => patterns.some((pattern) => {
|
|
6192
|
+
const normalizedPattern = normalizePath(pattern);
|
|
6193
|
+
return node_path.matchesGlob(relativePath, normalizedPattern) || node_path.matchesGlob(absolutePath, normalizedPattern);
|
|
6194
|
+
});
|
|
6195
|
+
return matches(options.include) && !matches(options.exclude);
|
|
6077
6196
|
}
|
|
6078
|
-
function
|
|
6079
|
-
return
|
|
6197
|
+
function normalizePath(value) {
|
|
6198
|
+
return value.split(node_path.sep).join("/");
|
|
6080
6199
|
}
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
* import { DefaultLayout } from './layouts/Default';
|
|
6088
|
-
* import { EntryLayout } from './layouts/Entry';
|
|
6089
|
-
*
|
|
6090
|
-
* export default createTheme({
|
|
6091
|
-
* layouts: {
|
|
6092
|
-
* default: DefaultLayout,
|
|
6093
|
-
* entry: EntryLayout,
|
|
6094
|
-
* },
|
|
6095
|
-
* });
|
|
6096
|
-
* ```
|
|
6097
|
-
*/
|
|
6098
|
-
function createTheme(config) {
|
|
6099
|
-
const { layouts, defaultLayout = "default" } = config;
|
|
6100
|
-
return function ThemeWithLayouts({ children }) {
|
|
6101
|
-
const { usePageProps } = (init_page_context(), require_vitepress.__toCommonJS(page_context_exports));
|
|
6102
|
-
const layoutName = usePageProps().layout ?? defaultLayout;
|
|
6103
|
-
const Layout = layouts[layoutName] ?? layouts[defaultLayout];
|
|
6104
|
-
if (!Layout) throw new Error(`[ox-content] Layout "${layoutName}" not found. Available layouts: ${Object.keys(layouts).join(", ")}`);
|
|
6105
|
-
return Layout({ children });
|
|
6200
|
+
function createEmptyLintResult() {
|
|
6201
|
+
return {
|
|
6202
|
+
diagnostics: [],
|
|
6203
|
+
errorCount: 0,
|
|
6204
|
+
infoCount: 0,
|
|
6205
|
+
warningCount: 0
|
|
6106
6206
|
};
|
|
6107
6207
|
}
|
|
6108
6208
|
//#endregion
|