@ox-content/vite-plugin 2.7.0 → 2.9.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/github.cjs +8 -2
- package/dist/github.cjs.map +1 -1
- package/dist/github.mjs +299 -2
- package/dist/github.mjs.map +1 -0
- package/dist/index.cjs +99 -1088
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +5 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +180 -1206
- package/dist/index.mjs.map +1 -1
- package/dist/mermaid.cjs +22 -0
- package/dist/mermaid.cjs.map +1 -1
- package/dist/mermaid.mjs +57 -1
- package/dist/mermaid.mjs.map +1 -1
- package/dist/ogp.cjs +24 -3
- package/dist/ogp.cjs.map +1 -1
- package/dist/ogp.mjs +307 -2
- package/dist/ogp.mjs.map +1 -0
- package/dist/tabs.mjs +188 -2
- package/dist/tabs.mjs.map +1 -0
- package/dist/youtube.mjs +117 -2
- package/dist/youtube.mjs.map +1 -0
- package/package.json +2 -2
- package/dist/github2.mjs +0 -286
- package/dist/github2.mjs.map +0 -1
- package/dist/mermaid2.mjs +0 -2
- package/dist/ogp2.mjs +0 -279
- package/dist/ogp2.mjs.map +0 -1
- package/dist/tabs2.mjs +0 -182
- package/dist/tabs2.mjs.map +0 -1
- package/dist/youtube2.mjs +0 -112
- package/dist/youtube2.mjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -17,7 +17,6 @@ let node_path = require("node:path");
|
|
|
17
17
|
node_path = require_chunk.__toESM(node_path);
|
|
18
18
|
let fs = require("fs");
|
|
19
19
|
fs = require_chunk.__toESM(fs);
|
|
20
|
-
let node_crypto = require("node:crypto");
|
|
21
20
|
let fs_promises = require("fs/promises");
|
|
22
21
|
fs_promises = require_chunk.__toESM(fs_promises);
|
|
23
22
|
let glob = require("glob");
|
|
@@ -7087,241 +7086,11 @@ if (import.meta.hot) {
|
|
|
7087
7086
|
}
|
|
7088
7087
|
//#endregion
|
|
7089
7088
|
//#region src/nav-generator.ts
|
|
7090
|
-
/**
|
|
7091
|
-
* Navigation Metadata Generator for API Documentation
|
|
7092
|
-
*
|
|
7093
|
-
* This module provides utilities for generating sidebar navigation structures
|
|
7094
|
-
* from extracted documentation. It automatically:
|
|
7095
|
-
*
|
|
7096
|
-
* - **Extracts file information**: Gets display names and file paths
|
|
7097
|
-
* - **Formats names**: Converts technical names to readable titles
|
|
7098
|
-
* - **Generates TypeScript**: Creates importable nav.ts files
|
|
7099
|
-
* - **Maintains hierarchy**: Supports nested navigation structures
|
|
7100
|
-
*
|
|
7101
|
-
* ## Generated Navigation Format
|
|
7102
|
-
*
|
|
7103
|
-
* The generated navigation is TypeScript-based for type safety and IDE support:
|
|
7104
|
-
*
|
|
7105
|
-
* ```typescript
|
|
7106
|
-
* export const apiNav: NavItem[] = [
|
|
7107
|
-
* { title: 'Overview', path: '/api/index' },
|
|
7108
|
-
* { title: 'Transform', path: '/api/transform' },
|
|
7109
|
-
* { title: 'Types', path: '/api/types' },
|
|
7110
|
-
* // ... auto-generated from documentation
|
|
7111
|
-
* ] as const;
|
|
7112
|
-
* ```
|
|
7113
|
-
*
|
|
7114
|
-
* ## Integration
|
|
7115
|
-
*
|
|
7116
|
-
* The generated nav.ts file can be imported directly:
|
|
7117
|
-
*
|
|
7118
|
-
* ```typescript
|
|
7119
|
-
* // In your Vue/React component
|
|
7120
|
-
* import { apiNav } from '../api/nav';
|
|
7121
|
-
*
|
|
7122
|
-
* const apiItems = apiNav.map(item => ({
|
|
7123
|
-
* ...item,
|
|
7124
|
-
* file: () => import(`../api/${item.path.split('/').pop()}.md`)
|
|
7125
|
-
* }));
|
|
7126
|
-
* ```
|
|
7127
|
-
*
|
|
7128
|
-
* @example
|
|
7129
|
-
* ```typescript
|
|
7130
|
-
* import { generateNavMetadata, generateNavCode } from './nav-generator';
|
|
7131
|
-
*
|
|
7132
|
-
* const extracted = [
|
|
7133
|
-
* { file: 'transform.ts', entries: [...] },
|
|
7134
|
-
* { file: 'types.ts', entries: [...] },
|
|
7135
|
-
* ];
|
|
7136
|
-
*
|
|
7137
|
-
* const navItems = generateNavMetadata(extracted);
|
|
7138
|
-
* // => [
|
|
7139
|
-
* // { title: 'Transform', path: '/api/transform' },
|
|
7140
|
-
* // { title: 'Types', path: '/api/types' },
|
|
7141
|
-
* // ]
|
|
7142
|
-
*
|
|
7143
|
-
* const code = generateNavCode(navItems);
|
|
7144
|
-
* // => TypeScript code ready to write to nav.ts
|
|
7145
|
-
* ```
|
|
7146
|
-
*/
|
|
7147
|
-
/**
|
|
7148
|
-
* Generates sidebar navigation metadata from extracted documentation.
|
|
7149
|
-
*
|
|
7150
|
-
* Takes an array of extracted documentation and produces a flat navigation
|
|
7151
|
-
* structure suitable for sidebar menus. Items are:
|
|
7152
|
-
* - Sorted alphabetically by display name
|
|
7153
|
-
* - Formatted with readable titles
|
|
7154
|
-
* - Prefixed with the specified base path
|
|
7155
|
-
*
|
|
7156
|
-
* ## Naming Conventions
|
|
7157
|
-
*
|
|
7158
|
-
* - `transform.ts` → `{ title: 'Transform', path: '/api/transform' }`
|
|
7159
|
-
* - `nav-generator.ts` → `{ title: 'Nav Generator', path: '/api/nav-generator' }`
|
|
7160
|
-
* - `index.ts` or `index-module.ts` → `{ title: 'Overview', path: '/api/index' }`
|
|
7161
|
-
* - `types.ts` → `{ title: 'Types', path: '/api/types' }`
|
|
7162
|
-
*
|
|
7163
|
-
* ## Sorting
|
|
7164
|
-
*
|
|
7165
|
-
* Items are sorted alphabetically by display title for consistent ordering.
|
|
7166
|
-
* Special item 'Overview' sorts naturally with others (O comes after most letters).
|
|
7167
|
-
*
|
|
7168
|
-
* ## Path Generation
|
|
7169
|
-
*
|
|
7170
|
-
* The generated paths are used to import corresponding Markdown files:
|
|
7171
|
-
* - Path `/api/transform` → Import from `../api/transform.md`
|
|
7172
|
-
* - Path `/api/index` → Import from `../api/index.md`
|
|
7173
|
-
*
|
|
7174
|
-
* @param docs - Array of extracted documentation (file + entries)
|
|
7175
|
-
* @param basePath - Base path prefix for navigation URLs (default: '/api')
|
|
7176
|
-
* Use '/api' for main API docs, '/helpers' for utilities, etc.
|
|
7177
|
-
*
|
|
7178
|
-
* @returns Array of navigation items ready to use or export to TypeScript
|
|
7179
|
-
*
|
|
7180
|
-
* @example
|
|
7181
|
-
* ```typescript
|
|
7182
|
-
* const navItems = generateNavMetadata(
|
|
7183
|
-
* [
|
|
7184
|
-
* { file: 'transform.ts', entries: [...] },
|
|
7185
|
-
* { file: 'docs.ts', entries: [...] },
|
|
7186
|
-
* { file: 'types.ts', entries: [...] },
|
|
7187
|
-
* ],
|
|
7188
|
-
* '/api'
|
|
7189
|
-
* );
|
|
7190
|
-
*
|
|
7191
|
-
* // Returns:
|
|
7192
|
-
* // [
|
|
7193
|
-
* // { title: 'Docs', path: '/api/docs' },
|
|
7194
|
-
* // { title: 'Transform', path: '/api/transform' },
|
|
7195
|
-
* // { title: 'Types', path: '/api/types' },
|
|
7196
|
-
* // ]
|
|
7197
|
-
* ```
|
|
7198
|
-
*
|
|
7199
|
-
* @see generateNavCode For converting these items to TypeScript code
|
|
7200
|
-
*/
|
|
7201
7089
|
function generateNavMetadata(docs, basePath = "/api") {
|
|
7202
|
-
return
|
|
7203
|
-
const aName = getDocDisplayName(a.file);
|
|
7204
|
-
const bName = getDocDisplayName(b.file);
|
|
7205
|
-
return aName.localeCompare(bName);
|
|
7206
|
-
}).map((doc) => ({
|
|
7207
|
-
title: getDocDisplayName(doc.file),
|
|
7208
|
-
path: `${basePath}/${getDocFileName(doc.file)}`
|
|
7209
|
-
}));
|
|
7210
|
-
}
|
|
7211
|
-
/**
|
|
7212
|
-
* Gets the human-readable display name for a documentation file.
|
|
7213
|
-
*
|
|
7214
|
-
* Transforms file paths and names into proper title case:
|
|
7215
|
-
* - Extracts base name (e.g., 'transform.ts' → 'transform')
|
|
7216
|
-
* - Converts kebab-case to Title Case (e.g., 'nav-generator' → 'Nav Generator')
|
|
7217
|
-
* - Converts camelCase to Title Case (e.g., 'transformMarkdown' → 'Transform Markdown')
|
|
7218
|
-
* - Handles special cases (index → 'Overview')
|
|
7219
|
-
*
|
|
7220
|
-
* ## Examples
|
|
7221
|
-
*
|
|
7222
|
-
* - `'/path/to/transform.ts'` → `'Transform'`
|
|
7223
|
-
* - `'nav-generator.ts'` → `'Nav Generator'`
|
|
7224
|
-
* - `'index.ts'` → `'Overview'`
|
|
7225
|
-
* - `'index-module.ts'` → `'Overview'`
|
|
7226
|
-
* - `'myFunction.ts'` → `'My Function'` (with camelCase handling)
|
|
7227
|
-
*
|
|
7228
|
-
* @param filePath - Full or relative file path
|
|
7229
|
-
* @returns Formatted display name suitable for UI labels
|
|
7230
|
-
*
|
|
7231
|
-
* @internal
|
|
7232
|
-
*/
|
|
7233
|
-
function getDocDisplayName(filePath) {
|
|
7234
|
-
const fileName = path.default.basename(filePath, path.default.extname(filePath));
|
|
7235
|
-
if (fileName === "index" || fileName === "index-module") return "Overview";
|
|
7236
|
-
return fileName.replace(/[-_]([a-z])/g, (_, char) => " " + char.toUpperCase()).replace(/^[a-z]/, (char) => char.toUpperCase());
|
|
7237
|
-
}
|
|
7238
|
-
/**
|
|
7239
|
-
* Gets the file name (without extension) for use in navigation paths.
|
|
7240
|
-
*
|
|
7241
|
-
* This handles filename conflicts that may occur during generation:
|
|
7242
|
-
* - Preserves most names as-is
|
|
7243
|
-
* - Special handling for index files to maintain consistency
|
|
7244
|
-
*
|
|
7245
|
-
* @param filePath - Source file path
|
|
7246
|
-
* @returns File name without extension, ready for URL paths
|
|
7247
|
-
*
|
|
7248
|
-
* @internal
|
|
7249
|
-
*/
|
|
7250
|
-
function getDocFileName(filePath) {
|
|
7251
|
-
const fileName = path.default.basename(filePath, path.default.extname(filePath));
|
|
7252
|
-
if (fileName === "index") return "index";
|
|
7253
|
-
return fileName;
|
|
7090
|
+
return require_mermaid.importNapiModuleSync().generateDocsNavMetadata(docs.map((doc) => doc.file), basePath);
|
|
7254
7091
|
}
|
|
7255
|
-
/**
|
|
7256
|
-
* Generates TypeScript code for navigation metadata export.
|
|
7257
|
-
*
|
|
7258
|
-
* Creates a complete, self-contained TypeScript file that:
|
|
7259
|
-
* - Defines the NavItem interface
|
|
7260
|
-
* - Exports navigation items as a const
|
|
7261
|
-
* - Uses `as const` for type-safe literal types
|
|
7262
|
-
* - Includes auto-generation notice
|
|
7263
|
-
*
|
|
7264
|
-
* The generated code is production-ready and suitable for direct import
|
|
7265
|
-
* in Vue, React, or vanilla TypeScript applications.
|
|
7266
|
-
*
|
|
7267
|
-
* ## Generated Code Example
|
|
7268
|
-
*
|
|
7269
|
-
* ```typescript
|
|
7270
|
-
* export interface NavItem {
|
|
7271
|
-
* title: string;
|
|
7272
|
-
* path: string;
|
|
7273
|
-
* children?: NavItem[];
|
|
7274
|
-
* }
|
|
7275
|
-
*
|
|
7276
|
-
* export const apiNav: NavItem[] = [
|
|
7277
|
-
* { "title": "Docs", "path": "/api/docs" },
|
|
7278
|
-
* { "title": "Transform", "path": "/api/transform" },
|
|
7279
|
-
* // ...
|
|
7280
|
-
* ] as const;
|
|
7281
|
-
* ```
|
|
7282
|
-
*
|
|
7283
|
-
* ## Features
|
|
7284
|
-
*
|
|
7285
|
-
* - **Type Safety**: Includes NavItem interface definition
|
|
7286
|
-
* - **Readonly**: Uses `as const` to ensure immutability
|
|
7287
|
-
* - **IDE Support**: Full IntelliSense and autocomplete
|
|
7288
|
-
* - **Self-Documenting**: Includes notice that file is auto-generated
|
|
7289
|
-
*
|
|
7290
|
-
* @param navItems - Array of navigation items to export
|
|
7291
|
-
* @param exportName - Name of the exported const (default: 'apiNav')
|
|
7292
|
-
* Use custom names for different navigation sections
|
|
7293
|
-
*
|
|
7294
|
-
* @returns Complete TypeScript source code as string,
|
|
7295
|
-
* ready to write to a .ts file
|
|
7296
|
-
*
|
|
7297
|
-
* @example
|
|
7298
|
-
* ```typescript
|
|
7299
|
-
* const navItems = [
|
|
7300
|
-
* { title: 'Home', path: '/api/index' },
|
|
7301
|
-
* { title: 'Transform', path: '/api/transform' },
|
|
7302
|
-
* ];
|
|
7303
|
-
*
|
|
7304
|
-
* const code = generateNavCode(navItems, 'apiNav');
|
|
7305
|
-
* await fs.promises.writeFile('docs/api/nav.ts', code, 'utf-8');
|
|
7306
|
-
* ```
|
|
7307
|
-
*
|
|
7308
|
-
* @see generateNavMetadata For generating NavItem arrays from extracted docs
|
|
7309
|
-
*/
|
|
7310
7092
|
function generateNavCode(navItems, exportName = "apiNav") {
|
|
7311
|
-
return
|
|
7312
|
-
* Auto-generated API documentation navigation.
|
|
7313
|
-
* This file is automatically generated by the docs plugin.
|
|
7314
|
-
* Do not edit manually.
|
|
7315
|
-
*/
|
|
7316
|
-
|
|
7317
|
-
export interface NavItem {
|
|
7318
|
-
title: string;
|
|
7319
|
-
path: string;
|
|
7320
|
-
children?: NavItem[];
|
|
7321
|
-
}
|
|
7322
|
-
|
|
7323
|
-
export const ${exportName}: NavItem[] = ${JSON.stringify(navItems, null, 2)} as const;
|
|
7324
|
-
`;
|
|
7093
|
+
return require_mermaid.importNapiModuleSync().generateDocsNavCode(navItems, exportName);
|
|
7325
7094
|
}
|
|
7326
7095
|
//#endregion
|
|
7327
7096
|
//#region src/docs.ts
|
|
@@ -7648,86 +7417,6 @@ function buildDocsData(docs) {
|
|
|
7648
7417
|
}))
|
|
7649
7418
|
};
|
|
7650
7419
|
}
|
|
7651
|
-
function consumeJSDocType(value) {
|
|
7652
|
-
const trimmed = value.trimStart();
|
|
7653
|
-
if (!trimmed.startsWith("{")) return { rest: trimmed };
|
|
7654
|
-
let depth = 0;
|
|
7655
|
-
for (let index = 0; index < trimmed.length; index++) {
|
|
7656
|
-
const char = trimmed[index];
|
|
7657
|
-
if (char === "{") depth++;
|
|
7658
|
-
else if (char === "}") {
|
|
7659
|
-
depth--;
|
|
7660
|
-
if (depth === 0) return {
|
|
7661
|
-
type: trimmed.slice(1, index).trim() || void 0,
|
|
7662
|
-
rest: trimmed.slice(index + 1).trimStart()
|
|
7663
|
-
};
|
|
7664
|
-
}
|
|
7665
|
-
}
|
|
7666
|
-
return { rest: trimmed };
|
|
7667
|
-
}
|
|
7668
|
-
function cleanTagDescription(value) {
|
|
7669
|
-
return value.trim().replace(/^-\s*/, "").trim();
|
|
7670
|
-
}
|
|
7671
|
-
function splitTagNameAndDescription(value) {
|
|
7672
|
-
const trimmed = value.trimStart();
|
|
7673
|
-
if (trimmed.startsWith("[")) {
|
|
7674
|
-
const closeIndex = trimmed.indexOf("]");
|
|
7675
|
-
if (closeIndex >= 0) return {
|
|
7676
|
-
name: trimmed.slice(0, closeIndex + 1),
|
|
7677
|
-
description: trimmed.slice(closeIndex + 1).trimStart()
|
|
7678
|
-
};
|
|
7679
|
-
}
|
|
7680
|
-
const match = /^(\S+)(?:\s+([\s\S]*))?$/u.exec(trimmed);
|
|
7681
|
-
return {
|
|
7682
|
-
name: match?.[1] ?? "",
|
|
7683
|
-
description: match?.[2] ?? ""
|
|
7684
|
-
};
|
|
7685
|
-
}
|
|
7686
|
-
function parseParamTagValue(value) {
|
|
7687
|
-
const { type, rest } = consumeJSDocType(value);
|
|
7688
|
-
const { name: rawName, description } = splitTagNameAndDescription(rest);
|
|
7689
|
-
let name = rawName.trim();
|
|
7690
|
-
if (!name) return null;
|
|
7691
|
-
let optional = false;
|
|
7692
|
-
let defaultValue;
|
|
7693
|
-
const optionalMatch = /^\[(.*)\]$/u.exec(name);
|
|
7694
|
-
if (optionalMatch) {
|
|
7695
|
-
optional = true;
|
|
7696
|
-
const [innerName, innerDefault] = optionalMatch[1].split(/=(.*)/su);
|
|
7697
|
-
name = innerName.trim();
|
|
7698
|
-
defaultValue = innerDefault?.trim() || void 0;
|
|
7699
|
-
}
|
|
7700
|
-
if (!name) return null;
|
|
7701
|
-
return {
|
|
7702
|
-
name,
|
|
7703
|
-
type: type || "unknown",
|
|
7704
|
-
description: cleanTagDescription(description),
|
|
7705
|
-
optional: optional || void 0,
|
|
7706
|
-
default: defaultValue
|
|
7707
|
-
};
|
|
7708
|
-
}
|
|
7709
|
-
function parseReturnsTagValue(value) {
|
|
7710
|
-
const { type, rest } = consumeJSDocType(value);
|
|
7711
|
-
return {
|
|
7712
|
-
type: type || "unknown",
|
|
7713
|
-
description: cleanTagDescription(rest)
|
|
7714
|
-
};
|
|
7715
|
-
}
|
|
7716
|
-
function normalizeReturnType(value) {
|
|
7717
|
-
const parsed = parseReturnsTagValue(value);
|
|
7718
|
-
return parsed.type === "unknown" ? value : parsed.type;
|
|
7719
|
-
}
|
|
7720
|
-
function mergeParam(params, next) {
|
|
7721
|
-
const existing = params.find((param) => param.name === next.name);
|
|
7722
|
-
if (!existing) {
|
|
7723
|
-
params.push(next);
|
|
7724
|
-
return;
|
|
7725
|
-
}
|
|
7726
|
-
if (next.type && (existing.type === "unknown" || next.type !== "unknown")) existing.type = next.type;
|
|
7727
|
-
if (next.description) existing.description = next.description;
|
|
7728
|
-
if (next.optional) existing.optional = true;
|
|
7729
|
-
if (next.default) existing.default = next.default;
|
|
7730
|
-
}
|
|
7731
7420
|
/**
|
|
7732
7421
|
* Extracts JSDoc documentation from source files in specified directories.
|
|
7733
7422
|
*
|
|
@@ -7794,13 +7483,13 @@ function mergeParam(params, next) {
|
|
|
7794
7483
|
* ```
|
|
7795
7484
|
*/
|
|
7796
7485
|
async function extractDocs(srcDirs, options) {
|
|
7797
|
-
const
|
|
7798
|
-
if (!
|
|
7486
|
+
const extractFileDocEntries = (await require_mermaid.importNapiModule()).extractFileDocEntries;
|
|
7487
|
+
if (!extractFileDocEntries) throw new Error("[ox-content] extractFileDocEntries is not available from @ox-content/napi.");
|
|
7799
7488
|
const results = [];
|
|
7800
7489
|
for (const srcDir of srcDirs) {
|
|
7801
7490
|
const files = await findFiles(srcDir, options);
|
|
7802
7491
|
for (const file of files) {
|
|
7803
|
-
const entries =
|
|
7492
|
+
const entries = extractFileDocEntries(file, options.private);
|
|
7804
7493
|
if (entries.length > 0) results.push({
|
|
7805
7494
|
file,
|
|
7806
7495
|
entries
|
|
@@ -7851,132 +7540,6 @@ function isExcluded(file, patterns) {
|
|
|
7851
7540
|
return false;
|
|
7852
7541
|
});
|
|
7853
7542
|
}
|
|
7854
|
-
function parseNapiDocItem(item) {
|
|
7855
|
-
const kind = normalizeNapiKind(item.kind);
|
|
7856
|
-
if (!kind) return null;
|
|
7857
|
-
const params = [];
|
|
7858
|
-
const examples = [];
|
|
7859
|
-
const tags = {};
|
|
7860
|
-
let description = "";
|
|
7861
|
-
let returns;
|
|
7862
|
-
let isPrivate = false;
|
|
7863
|
-
const rawLines = (item.jsdoc ?? "").split("\n").map((line) => {
|
|
7864
|
-
const trimmedStart = line.trimStart();
|
|
7865
|
-
const withoutStar = trimmedStart.startsWith("*") ? trimmedStart.slice(1) : trimmedStart;
|
|
7866
|
-
return withoutStar.startsWith(" ") ? withoutStar.slice(1) : withoutStar;
|
|
7867
|
-
});
|
|
7868
|
-
const cleanedLines = rawLines.map((line) => line.trim()).filter(Boolean);
|
|
7869
|
-
let currentExample = "";
|
|
7870
|
-
let inExample = false;
|
|
7871
|
-
let rawLineIndex = 0;
|
|
7872
|
-
for (const lineText of cleanedLines) {
|
|
7873
|
-
while (rawLineIndex < rawLines.length && rawLines[rawLineIndex].trim() !== lineText) rawLineIndex++;
|
|
7874
|
-
const rawLine = rawLineIndex < rawLines.length ? rawLines[rawLineIndex] : lineText;
|
|
7875
|
-
rawLineIndex++;
|
|
7876
|
-
if (lineText.startsWith("@")) {
|
|
7877
|
-
if (inExample) {
|
|
7878
|
-
examples.push(currentExample.trim());
|
|
7879
|
-
currentExample = "";
|
|
7880
|
-
inExample = false;
|
|
7881
|
-
}
|
|
7882
|
-
const tagMatch = /^@(\S+)\s*([\s\S]*)$/u.exec(lineText);
|
|
7883
|
-
if (tagMatch) {
|
|
7884
|
-
const [, tagName, tagValue = ""] = tagMatch;
|
|
7885
|
-
switch (tagName) {
|
|
7886
|
-
case "param":
|
|
7887
|
-
case "arg":
|
|
7888
|
-
case "argument": {
|
|
7889
|
-
const param = parseParamTagValue(tagValue);
|
|
7890
|
-
if (param) mergeParam(params, param);
|
|
7891
|
-
break;
|
|
7892
|
-
}
|
|
7893
|
-
case "returns":
|
|
7894
|
-
case "return":
|
|
7895
|
-
returns = parseReturnsTagValue(tagValue);
|
|
7896
|
-
break;
|
|
7897
|
-
case "example":
|
|
7898
|
-
inExample = true;
|
|
7899
|
-
currentExample = tagValue.trim() ? `${tagValue.trim()}\n` : "";
|
|
7900
|
-
break;
|
|
7901
|
-
case "private":
|
|
7902
|
-
isPrivate = true;
|
|
7903
|
-
break;
|
|
7904
|
-
default: tags[tagName] = tagValue.trim();
|
|
7905
|
-
}
|
|
7906
|
-
}
|
|
7907
|
-
} else if (inExample) currentExample += rawLine + "\n";
|
|
7908
|
-
else if (!description) description = lineText;
|
|
7909
|
-
else description += "\n" + lineText;
|
|
7910
|
-
}
|
|
7911
|
-
if (inExample && currentExample) examples.push(currentExample.trim());
|
|
7912
|
-
for (const param of item.params) {
|
|
7913
|
-
if (params.length > 0 && param.name === "param" && !param.typeAnnotation && !param.description && !param.defaultValue) continue;
|
|
7914
|
-
mergeParam(params, {
|
|
7915
|
-
name: param.name,
|
|
7916
|
-
type: param.typeAnnotation ?? "unknown",
|
|
7917
|
-
description: param.description ?? "",
|
|
7918
|
-
optional: param.optional || void 0,
|
|
7919
|
-
default: param.defaultValue
|
|
7920
|
-
});
|
|
7921
|
-
}
|
|
7922
|
-
if (!returns && item.returnType) returns = {
|
|
7923
|
-
type: normalizeReturnType(item.returnType),
|
|
7924
|
-
description: ""
|
|
7925
|
-
};
|
|
7926
|
-
else if (returns && item.returnType) returns.type = normalizeReturnType(item.returnType);
|
|
7927
|
-
if (!description) description = item.doc ?? "";
|
|
7928
|
-
for (const tag of item.tags) {
|
|
7929
|
-
if (tag.tag === "param" || tag.tag === "arg" || tag.tag === "argument" || tag.tag === "returns" || tag.tag === "return") {
|
|
7930
|
-
if (tag.tag === "param" || tag.tag === "arg" || tag.tag === "argument") {
|
|
7931
|
-
const param = parseParamTagValue(tag.value);
|
|
7932
|
-
if (param) mergeParam(params, param);
|
|
7933
|
-
} else {
|
|
7934
|
-
const parsedReturns = parseReturnsTagValue(tag.value);
|
|
7935
|
-
if (!returns) returns = parsedReturns;
|
|
7936
|
-
else {
|
|
7937
|
-
returns.type = returns.type === "unknown" ? parsedReturns.type : returns.type;
|
|
7938
|
-
returns.description ||= parsedReturns.description;
|
|
7939
|
-
}
|
|
7940
|
-
}
|
|
7941
|
-
continue;
|
|
7942
|
-
}
|
|
7943
|
-
if (tag.tag === "example") {
|
|
7944
|
-
if (tag.value && !examples.includes(tag.value)) examples.push(tag.value);
|
|
7945
|
-
continue;
|
|
7946
|
-
}
|
|
7947
|
-
if (tag.tag === "private") {
|
|
7948
|
-
isPrivate = true;
|
|
7949
|
-
continue;
|
|
7950
|
-
}
|
|
7951
|
-
if (!tags[tag.tag]) tags[tag.tag] = tag.value;
|
|
7952
|
-
}
|
|
7953
|
-
return {
|
|
7954
|
-
name: item.name,
|
|
7955
|
-
kind,
|
|
7956
|
-
description,
|
|
7957
|
-
params: params.length > 0 ? params : void 0,
|
|
7958
|
-
returns,
|
|
7959
|
-
examples: examples.length > 0 ? examples : void 0,
|
|
7960
|
-
tags: Object.keys(tags).length > 0 ? tags : void 0,
|
|
7961
|
-
private: isPrivate,
|
|
7962
|
-
file: item.sourcePath,
|
|
7963
|
-
line: item.line,
|
|
7964
|
-
endLine: item.endLine,
|
|
7965
|
-
signature: item.signature
|
|
7966
|
-
};
|
|
7967
|
-
}
|
|
7968
|
-
function normalizeNapiKind(kind) {
|
|
7969
|
-
switch (kind) {
|
|
7970
|
-
case "function":
|
|
7971
|
-
case "class":
|
|
7972
|
-
case "interface":
|
|
7973
|
-
case "type":
|
|
7974
|
-
case "variable":
|
|
7975
|
-
case "module": return kind;
|
|
7976
|
-
case "enum": return "type";
|
|
7977
|
-
default: return null;
|
|
7978
|
-
}
|
|
7979
|
-
}
|
|
7980
7543
|
/**
|
|
7981
7544
|
* Generates Markdown documentation from extracted docs.
|
|
7982
7545
|
*/
|
|
@@ -10857,10 +10420,7 @@ function renderTemplate(template, data) {
|
|
|
10857
10420
|
* Extracts title from content or frontmatter.
|
|
10858
10421
|
*/
|
|
10859
10422
|
function extractTitle$1(content, frontmatter) {
|
|
10860
|
-
|
|
10861
|
-
const h1Match = content.match(/<h1[^>]*>([^<]+)<\/h1>/i);
|
|
10862
|
-
if (h1Match) return h1Match[1].trim();
|
|
10863
|
-
return "Untitled";
|
|
10423
|
+
return require_mermaid.importNapiModuleSync().extractSsgTitle(content, typeof frontmatter.title === "string" ? frontmatter.title : void 0);
|
|
10864
10424
|
}
|
|
10865
10425
|
/**
|
|
10866
10426
|
* Generates bare HTML page (no navigation, no styles).
|
|
@@ -10874,7 +10434,7 @@ function generateBareHtmlPage(content, title) {
|
|
|
10874
10434
|
/**
|
|
10875
10435
|
* Generates HTML page with navigation using Rust NAPI bindings.
|
|
10876
10436
|
*/
|
|
10877
|
-
async function generateHtmlPage(pageData, navGroups, siteName, base, ogImage, theme) {
|
|
10437
|
+
async function generateHtmlPage(pageData, navGroups, siteName, base, ogImage, theme, locale, availableLocales) {
|
|
10878
10438
|
const mod = await require_mermaid.importNapiModule();
|
|
10879
10439
|
const tocForRust = pageData.toc.map((entry) => ({
|
|
10880
10440
|
depth: entry.depth,
|
|
@@ -10937,221 +10497,44 @@ async function generateHtmlPage(pageData, navGroups, siteName, base, ogImage, th
|
|
|
10937
10497
|
siteName,
|
|
10938
10498
|
base,
|
|
10939
10499
|
ogImage,
|
|
10940
|
-
theme: themeForRust
|
|
10500
|
+
theme: themeForRust,
|
|
10501
|
+
locale,
|
|
10502
|
+
availableLocales: availableLocales?.map((l) => ({
|
|
10503
|
+
code: l.code,
|
|
10504
|
+
name: l.name,
|
|
10505
|
+
dir: l.dir ?? "ltr"
|
|
10506
|
+
}))
|
|
10941
10507
|
});
|
|
10942
10508
|
}
|
|
10943
|
-
const SSG_STYLE_BLOCK_RE = /[ \t]*<!-- ox-content:styles:start -->\s*<style>([\s\S]*?)<\/style>\s*<!-- ox-content:styles:end -->/;
|
|
10944
|
-
const SSG_SCRIPT_BLOCK_RE = /[ \t]*<!-- ox-content:scripts:start -->\s*<script>([\s\S]*?)<\/script>\s*<!-- ox-content:scripts:end -->/;
|
|
10945
|
-
const FIRST_INLINE_STYLE_RE = /[ \t]*<style>([\s\S]*?)<\/style>/;
|
|
10946
|
-
const LAST_INLINE_BODY_SCRIPT_RE = /[ \t]*<script>([\s\S]*?)<\/script>\s*<\/body>/;
|
|
10947
|
-
const CSS_SECTION_RE = /\/\* ox-content:css:([a-z0-9-]+):start \*\/\s*([\s\S]*?)\s*\/\* ox-content:css:\1:end \*\//g;
|
|
10948
|
-
const SEARCH_CHUNK_RE = /\/\/ ox-content:search:start\s*([\s\S]*?)\s*\/\/ ox-content:search:end/;
|
|
10949
|
-
const SEARCH_CHUNK_PLACEHOLDER = "__OX_CONTENT_SEARCH_CHUNK__";
|
|
10950
|
-
const CORE_CSS_SECTION_NAMES = new Set(["base", "footer"]);
|
|
10951
|
-
const THEME_INLINE_CSS_MAX_BYTES = 2048;
|
|
10952
|
-
function createContentHash(content) {
|
|
10953
|
-
return (0, node_crypto.createHash)("sha256").update(content).digest("hex").slice(0, 10);
|
|
10954
|
-
}
|
|
10955
|
-
function sanitizeChunkLabel(label) {
|
|
10956
|
-
return label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "asset";
|
|
10957
|
-
}
|
|
10958
|
-
function toPublicAssetPath(base, fileName) {
|
|
10959
|
-
return `${base.endsWith("/") ? base : `${base}/`}assets/${fileName}`;
|
|
10960
|
-
}
|
|
10961
|
-
function hasRelativeCssUrls(css) {
|
|
10962
|
-
let cursor = 0;
|
|
10963
|
-
while (cursor < css.length) {
|
|
10964
|
-
const urlIndex = css.indexOf("url(", cursor);
|
|
10965
|
-
if (urlIndex === -1) return false;
|
|
10966
|
-
let valueStart = urlIndex + 4;
|
|
10967
|
-
while (valueStart < css.length && /\s/.test(css[valueStart])) valueStart++;
|
|
10968
|
-
const quote = css[valueStart] === "\"" || css[valueStart] === "'" ? css[valueStart] : "";
|
|
10969
|
-
if (quote) valueStart++;
|
|
10970
|
-
let valueEnd = valueStart;
|
|
10971
|
-
while (valueEnd < css.length) {
|
|
10972
|
-
const char = css[valueEnd];
|
|
10973
|
-
if (quote) {
|
|
10974
|
-
if (char === "\\") {
|
|
10975
|
-
valueEnd += 2;
|
|
10976
|
-
continue;
|
|
10977
|
-
}
|
|
10978
|
-
if (char === quote) break;
|
|
10979
|
-
} else if (char === ")") break;
|
|
10980
|
-
valueEnd++;
|
|
10981
|
-
}
|
|
10982
|
-
const value = css.slice(valueStart, valueEnd).trim();
|
|
10983
|
-
if (value && !value.startsWith("data:") && !value.startsWith("http:") && !value.startsWith("https:") && !value.startsWith("//") && !value.startsWith("/") && !value.startsWith("#") && !value.startsWith("blob:") && !value.startsWith("var(")) return true;
|
|
10984
|
-
cursor = valueEnd + 1;
|
|
10985
|
-
}
|
|
10986
|
-
return false;
|
|
10987
|
-
}
|
|
10988
|
-
function createSharedAssetChunk(type, label, content, outDir, base) {
|
|
10989
|
-
const hash = createContentHash(content);
|
|
10990
|
-
const fileName = `ox-content-${sanitizeChunkLabel(label)}-${hash}.${type}`;
|
|
10991
|
-
return {
|
|
10992
|
-
outputPath: path.join(outDir, "assets", fileName),
|
|
10993
|
-
publicPath: toPublicAssetPath(base, fileName),
|
|
10994
|
-
content
|
|
10995
|
-
};
|
|
10996
|
-
}
|
|
10997
|
-
function extractCssSections(cssContent) {
|
|
10998
|
-
return Array.from(cssContent.matchAll(CSS_SECTION_RE)).map(([, name, content]) => ({
|
|
10999
|
-
name,
|
|
11000
|
-
content: content.trim()
|
|
11001
|
-
})).filter((section) => section.content.length > 0);
|
|
11002
|
-
}
|
|
11003
|
-
function getOrCreateSharedChunk(chunks, type, label, content, outDir, base) {
|
|
11004
|
-
let chunk = chunks.get(content);
|
|
11005
|
-
if (!chunk) {
|
|
11006
|
-
chunk = createSharedAssetChunk(type, label, content, outDir, base);
|
|
11007
|
-
chunks.set(content, chunk);
|
|
11008
|
-
}
|
|
11009
|
-
return chunk;
|
|
11010
|
-
}
|
|
11011
|
-
function buildStyleReplacement(cssContent, cssChunks, outDir, base) {
|
|
11012
|
-
const sections = extractCssSections(cssContent);
|
|
11013
|
-
const effectiveSections = sections.length > 0 ? sections : [{
|
|
11014
|
-
name: "css",
|
|
11015
|
-
content: cssContent.trim()
|
|
11016
|
-
}];
|
|
11017
|
-
const coreContent = effectiveSections.filter((section) => CORE_CSS_SECTION_NAMES.has(section.name)).map((section) => section.content).join("\n").trim();
|
|
11018
|
-
const fragments = [];
|
|
11019
|
-
if (coreContent) {
|
|
11020
|
-
const coreChunk = getOrCreateSharedChunk(cssChunks, "css", "core", coreContent, outDir, base);
|
|
11021
|
-
fragments.push(` <link rel="stylesheet" href="${coreChunk.publicPath}">`);
|
|
11022
|
-
}
|
|
11023
|
-
for (const section of effectiveSections) {
|
|
11024
|
-
if (CORE_CSS_SECTION_NAMES.has(section.name)) continue;
|
|
11025
|
-
if (section.name === "theme" && (hasRelativeCssUrls(section.content) || section.content.length <= THEME_INLINE_CSS_MAX_BYTES) || hasRelativeCssUrls(section.content)) {
|
|
11026
|
-
fragments.push(` <style>${section.content}</style>`);
|
|
11027
|
-
continue;
|
|
11028
|
-
}
|
|
11029
|
-
const chunk = getOrCreateSharedChunk(cssChunks, "css", section.name, section.content, outDir, base);
|
|
11030
|
-
fragments.push(` <link rel="stylesheet" href="${chunk.publicPath}">`);
|
|
11031
|
-
}
|
|
11032
|
-
return fragments.join("\n");
|
|
11033
|
-
}
|
|
11034
|
-
function buildScriptReplacement(jsContent, jsChunks, outDir, base) {
|
|
11035
|
-
const searchMatch = jsContent.match(SEARCH_CHUNK_RE);
|
|
11036
|
-
if (searchMatch && jsContent.includes(SEARCH_CHUNK_PLACEHOLDER)) {
|
|
11037
|
-
const searchContent = searchMatch[1].trim();
|
|
11038
|
-
if (searchContent) {
|
|
11039
|
-
const searchChunk = getOrCreateSharedChunk(jsChunks, "js", "search", searchContent, outDir, base);
|
|
11040
|
-
const coreContent = jsContent.replace(SEARCH_CHUNK_RE, "").replaceAll(SEARCH_CHUNK_PLACEHOLDER, searchChunk.publicPath).trim();
|
|
11041
|
-
if (coreContent) return ` <script defer src="${getOrCreateSharedChunk(jsChunks, "js", "core", coreContent, outDir, base).publicPath}"><\/script>`;
|
|
11042
|
-
}
|
|
11043
|
-
}
|
|
11044
|
-
const fallbackContent = jsContent.trim();
|
|
11045
|
-
if (!fallbackContent) return "";
|
|
11046
|
-
return ` <script defer src="${getOrCreateSharedChunk(jsChunks, "js", "js", fallbackContent, outDir, base).publicPath}"><\/script>`;
|
|
11047
|
-
}
|
|
11048
10509
|
async function externalizeSharedPageAssets(pages, outDir, base) {
|
|
11049
|
-
const
|
|
11050
|
-
|
|
11051
|
-
|
|
11052
|
-
|
|
11053
|
-
const styleMatch = html.match(SSG_STYLE_BLOCK_RE);
|
|
11054
|
-
if (styleMatch) {
|
|
11055
|
-
const replacement = buildStyleReplacement(styleMatch[1], cssChunks, outDir, base);
|
|
11056
|
-
html = html.replace(SSG_STYLE_BLOCK_RE, replacement);
|
|
11057
|
-
} else {
|
|
11058
|
-
const inlineStyleMatch = html.match(FIRST_INLINE_STYLE_RE);
|
|
11059
|
-
if (inlineStyleMatch) {
|
|
11060
|
-
const replacement = buildStyleReplacement(inlineStyleMatch[1], cssChunks, outDir, base);
|
|
11061
|
-
html = html.replace(FIRST_INLINE_STYLE_RE, replacement);
|
|
11062
|
-
}
|
|
11063
|
-
}
|
|
11064
|
-
const scriptMatch = html.match(SSG_SCRIPT_BLOCK_RE);
|
|
11065
|
-
if (scriptMatch) {
|
|
11066
|
-
const replacement = buildScriptReplacement(scriptMatch[1], jsChunks, outDir, base);
|
|
11067
|
-
html = html.replace(SSG_SCRIPT_BLOCK_RE, replacement);
|
|
11068
|
-
} else {
|
|
11069
|
-
const inlineScriptMatch = html.match(LAST_INLINE_BODY_SCRIPT_RE);
|
|
11070
|
-
if (inlineScriptMatch) {
|
|
11071
|
-
const replacement = buildScriptReplacement(inlineScriptMatch[1], jsChunks, outDir, base);
|
|
11072
|
-
html = html.replace(LAST_INLINE_BODY_SCRIPT_RE, replacement ? `${replacement}\n</body>` : "</body>");
|
|
11073
|
-
}
|
|
11074
|
-
}
|
|
11075
|
-
return {
|
|
11076
|
-
...page,
|
|
11077
|
-
html
|
|
11078
|
-
};
|
|
11079
|
-
});
|
|
11080
|
-
const chunks = [...cssChunks.values(), ...jsChunks.values()];
|
|
11081
|
-
await Promise.all(chunks.map(async (chunk) => {
|
|
11082
|
-
await fs_promises.mkdir(path.dirname(chunk.outputPath), { recursive: true });
|
|
11083
|
-
await fs_promises.writeFile(chunk.outputPath, chunk.content, "utf-8");
|
|
10510
|
+
const optimized = (await require_mermaid.importNapiModule()).externalizeSsgAssets(pages, outDir, base);
|
|
10511
|
+
await Promise.all(optimized.assets.map(async (asset) => {
|
|
10512
|
+
await fs_promises.mkdir(path.dirname(asset.outputPath), { recursive: true });
|
|
10513
|
+
await fs_promises.writeFile(asset.outputPath, asset.content, "utf-8");
|
|
11084
10514
|
}));
|
|
11085
10515
|
return {
|
|
11086
|
-
pages:
|
|
11087
|
-
assets:
|
|
10516
|
+
pages: optimized.pages,
|
|
10517
|
+
assets: optimized.assets.map((asset) => asset.outputPath)
|
|
11088
10518
|
};
|
|
11089
10519
|
}
|
|
11090
10520
|
/**
|
|
11091
|
-
* Converts a markdown file path to its corresponding HTML output path.
|
|
11092
|
-
*/
|
|
11093
|
-
function getOutputPath(inputPath, srcDir, outDir, extension) {
|
|
11094
|
-
const baseName = path.relative(srcDir, inputPath).replace(/\.(?:md|markdown)$/i, extension);
|
|
11095
|
-
if (baseName.endsWith(`index${extension}`)) return path.join(outDir, baseName);
|
|
11096
|
-
const dirName = baseName.replace(new RegExp(`\\${extension}$`), "");
|
|
11097
|
-
return path.join(outDir, dirName, `index${extension}`);
|
|
11098
|
-
}
|
|
11099
|
-
/**
|
|
11100
10521
|
* Converts a markdown file path to a relative URL path.
|
|
11101
10522
|
*/
|
|
11102
10523
|
function getUrlPath$1(inputPath, srcDir) {
|
|
11103
|
-
|
|
11104
|
-
if (baseName === "index" || baseName.endsWith("/index")) return baseName.replace(/\/?index$/, "") || "/";
|
|
11105
|
-
return baseName;
|
|
10524
|
+
return require_mermaid.importNapiModuleSync().getSsgUrlPath(inputPath, srcDir);
|
|
11106
10525
|
}
|
|
11107
|
-
|
|
11108
|
-
|
|
11109
|
-
|
|
11110
|
-
function getHref(inputPath, srcDir, base, extension) {
|
|
11111
|
-
const urlPath = getUrlPath$1(inputPath, srcDir);
|
|
11112
|
-
if (urlPath === "/" || urlPath === "") return `${base}index${extension}`;
|
|
11113
|
-
return `${base}${urlPath}/index${extension}`;
|
|
10526
|
+
function getPageLocale(urlPath, i18n) {
|
|
10527
|
+
if (!i18n) return void 0;
|
|
10528
|
+
return require_mermaid.importNapiModuleSync().getSsgPageLocale(urlPath, i18n.defaultLocale, i18n.locales.map((locale) => locale.code)) ?? void 0;
|
|
11114
10529
|
}
|
|
11115
|
-
|
|
11116
|
-
|
|
11117
|
-
*/
|
|
11118
|
-
function getOgImagePath(inputPath, srcDir, outDir) {
|
|
11119
|
-
const baseName = path.relative(srcDir, inputPath).replace(/\.(?:md|markdown)$/i, "");
|
|
11120
|
-
if (baseName === "index" || baseName.endsWith("/index")) {
|
|
11121
|
-
const dirPath = baseName.replace(/\/?index$/, "") || "";
|
|
11122
|
-
return path.join(outDir, dirPath, "og-image.png");
|
|
11123
|
-
}
|
|
11124
|
-
return path.join(outDir, baseName, "og-image.png");
|
|
11125
|
-
}
|
|
11126
|
-
/**
|
|
11127
|
-
* Gets the OG image URL for use in meta tags.
|
|
11128
|
-
* If siteUrl is provided, returns an absolute URL (required for SNS sharing).
|
|
11129
|
-
*/
|
|
11130
|
-
function getOgImageUrl(inputPath, srcDir, base, siteUrl) {
|
|
11131
|
-
const urlPath = getUrlPath$1(inputPath, srcDir);
|
|
11132
|
-
let relativePath;
|
|
11133
|
-
if (urlPath === "/" || urlPath === "") relativePath = `${base}og-image.png`;
|
|
11134
|
-
else relativePath = `${base}${urlPath}/og-image.png`;
|
|
11135
|
-
if (siteUrl) return `${siteUrl.replace(/\/$/, "")}${relativePath}`;
|
|
11136
|
-
return relativePath;
|
|
11137
|
-
}
|
|
11138
|
-
/**
|
|
11139
|
-
* Gets display title from file path.
|
|
11140
|
-
*/
|
|
11141
|
-
function getDisplayTitle(filePath) {
|
|
11142
|
-
const fileName = path.basename(filePath, path.extname(filePath));
|
|
11143
|
-
if (fileName === "index") {
|
|
11144
|
-
const dirName = path.basename(path.dirname(filePath));
|
|
11145
|
-
if (dirName && dirName !== ".") return formatTitle(dirName);
|
|
11146
|
-
return "Home";
|
|
11147
|
-
}
|
|
11148
|
-
return formatTitle(fileName);
|
|
10530
|
+
function getRoutePaths(inputPath, srcDir, outDir, base, extension, siteUrl) {
|
|
10531
|
+
return require_mermaid.importNapiModuleSync().resolveSsgRoutePaths(inputPath, srcDir, outDir, base, extension, siteUrl);
|
|
11149
10532
|
}
|
|
11150
10533
|
/**
|
|
11151
10534
|
* Formats a file/dir name as a title.
|
|
11152
10535
|
*/
|
|
11153
10536
|
function formatTitle(name) {
|
|
11154
|
-
return
|
|
10537
|
+
return require_mermaid.importNapiModuleSync().formatSsgTitle(name);
|
|
11155
10538
|
}
|
|
11156
10539
|
/**
|
|
11157
10540
|
* Collects all markdown files from the source directory.
|
|
@@ -11170,110 +10553,13 @@ async function collectMarkdownFiles$1(srcDir) {
|
|
|
11170
10553
|
* Builds navigation items from markdown files, grouped by directory.
|
|
11171
10554
|
*/
|
|
11172
10555
|
function buildNavItems(markdownFiles, srcDir, base, extension) {
|
|
11173
|
-
|
|
11174
|
-
const groupOrder = [
|
|
11175
|
-
"",
|
|
11176
|
-
"examples",
|
|
11177
|
-
"packages",
|
|
11178
|
-
"api"
|
|
11179
|
-
];
|
|
11180
|
-
for (const file of markdownFiles) {
|
|
11181
|
-
const parts = path.relative(srcDir, file).split(path.sep);
|
|
11182
|
-
let groupKey = "";
|
|
11183
|
-
if (parts.length > 1) groupKey = parts[0];
|
|
11184
|
-
if (!groups.has(groupKey)) groups.set(groupKey, []);
|
|
11185
|
-
const urlPath = getUrlPath$1(file, srcDir);
|
|
11186
|
-
let title;
|
|
11187
|
-
if (urlPath === "/" || urlPath === "") title = "Overview";
|
|
11188
|
-
else title = getDisplayTitle(file);
|
|
11189
|
-
groups.get(groupKey).push({
|
|
11190
|
-
title,
|
|
11191
|
-
path: urlPath,
|
|
11192
|
-
href: getHref(file, srcDir, base, extension)
|
|
11193
|
-
});
|
|
11194
|
-
}
|
|
11195
|
-
const sortItems = (items) => {
|
|
11196
|
-
return items.sort((a, b) => {
|
|
11197
|
-
const aIsRoot = a.path === "/" || a.path === "";
|
|
11198
|
-
const bIsRoot = b.path === "/" || b.path === "";
|
|
11199
|
-
if (aIsRoot && !bIsRoot) return -1;
|
|
11200
|
-
if (!aIsRoot && bIsRoot) return 1;
|
|
11201
|
-
return a.title.localeCompare(b.title);
|
|
11202
|
-
});
|
|
11203
|
-
};
|
|
11204
|
-
const result = [];
|
|
11205
|
-
for (const key of groupOrder) {
|
|
11206
|
-
const items = groups.get(key);
|
|
11207
|
-
if (items && items.length > 0) {
|
|
11208
|
-
result.push({
|
|
11209
|
-
title: key === "" ? "Guide" : formatTitle(key),
|
|
11210
|
-
items: sortItems(items)
|
|
11211
|
-
});
|
|
11212
|
-
groups.delete(key);
|
|
11213
|
-
}
|
|
11214
|
-
}
|
|
11215
|
-
for (const [key, items] of groups) if (items.length > 0) result.push({
|
|
11216
|
-
title: formatTitle(key),
|
|
11217
|
-
items: sortItems(items)
|
|
11218
|
-
});
|
|
11219
|
-
return result;
|
|
11220
|
-
}
|
|
11221
|
-
function isSafeSidebarLink(link) {
|
|
11222
|
-
const trimmed = link.trim();
|
|
11223
|
-
if (trimmed.startsWith("//")) return false;
|
|
11224
|
-
return !/^[a-z][a-z0-9+.-]*:/i.test(trimmed) || /^(https?:|mailto:)/i.test(trimmed);
|
|
11225
|
-
}
|
|
11226
|
-
function sidebarPath(link) {
|
|
11227
|
-
if (!link || !isSafeSidebarLink(link)) return "";
|
|
11228
|
-
if (/^(https?:|mailto:|#)/i.test(link.trim())) return "";
|
|
11229
|
-
const bare = link.trim().split("#", 1)[0].split("?", 1)[0].replace(/^\/+/, "").replace(/\/$/, "").replace(/\.(md|markdown)$/i, "");
|
|
11230
|
-
if (!bare || bare === "index") return "/";
|
|
11231
|
-
return bare.replace(/\/index$/, "");
|
|
11232
|
-
}
|
|
11233
|
-
function sidebarHref(link, base, extension) {
|
|
11234
|
-
if (!link) return "#";
|
|
11235
|
-
const trimmed = link.trim();
|
|
11236
|
-
if (!isSafeSidebarLink(trimmed)) return "#";
|
|
11237
|
-
if (/^(https?:|mailto:|#)/i.test(trimmed)) return trimmed;
|
|
11238
|
-
const hash = trimmed.includes("#") ? `#${trimmed.split("#").slice(1).join("#")}` : "";
|
|
11239
|
-
const withoutExt = trimmed.split("#", 1)[0].replace(/^\/+/, "").replace(/\/$/, "").replace(/\.(md|markdown)$/i, "");
|
|
11240
|
-
return `${base}${!withoutExt || withoutExt === "index" ? "index" : `${withoutExt.replace(/\/index$/, "")}/index`}${extension}${hash}`;
|
|
10556
|
+
return require_mermaid.importNapiModuleSync().buildSsgNavItems(markdownFiles, srcDir, base, extension);
|
|
11241
10557
|
}
|
|
11242
10558
|
/**
|
|
11243
10559
|
* Builds navigation items from an explicit theme sidebar tree.
|
|
11244
10560
|
*/
|
|
11245
10561
|
function buildThemeNavItems(sidebar, base, extension) {
|
|
11246
|
-
|
|
11247
|
-
const navItem = {
|
|
11248
|
-
title: item.text ?? item.link ?? "Untitled",
|
|
11249
|
-
path: sidebarPath(item.link),
|
|
11250
|
-
href: sidebarHref(item.link, base, extension)
|
|
11251
|
-
};
|
|
11252
|
-
if (item.items?.length) navItem.children = item.items.map(toNavItem);
|
|
11253
|
-
if (item.collapsed !== void 0) navItem.collapsed = item.collapsed;
|
|
11254
|
-
return navItem;
|
|
11255
|
-
};
|
|
11256
|
-
const groups = [];
|
|
11257
|
-
let looseItems = [];
|
|
11258
|
-
const flushLooseItems = () => {
|
|
11259
|
-
if (looseItems.length > 0) {
|
|
11260
|
-
groups.push({
|
|
11261
|
-
title: "Guide",
|
|
11262
|
-
items: looseItems
|
|
11263
|
-
});
|
|
11264
|
-
looseItems = [];
|
|
11265
|
-
}
|
|
11266
|
-
};
|
|
11267
|
-
for (const item of sidebar) if (item.items?.length && !item.link) {
|
|
11268
|
-
flushLooseItems();
|
|
11269
|
-
groups.push({
|
|
11270
|
-
title: item.text ?? "Guide",
|
|
11271
|
-
items: item.items.map(toNavItem),
|
|
11272
|
-
collapsed: item.collapsed
|
|
11273
|
-
});
|
|
11274
|
-
} else looseItems.push(toNavItem(item));
|
|
11275
|
-
flushLooseItems();
|
|
11276
|
-
return groups;
|
|
10562
|
+
return require_mermaid.importNapiModuleSync().buildSsgThemeNavItems(sidebar, base, extension);
|
|
11277
10563
|
}
|
|
11278
10564
|
/**
|
|
11279
10565
|
* Builds all markdown files to static HTML.
|
|
@@ -11332,8 +10618,10 @@ async function buildSsg(options, root) {
|
|
|
11332
10618
|
transformedHtml = restoreMermaidSvgs(transformedHtml, mermaidSvgs);
|
|
11333
10619
|
const title = extractTitle$1(transformedHtml, result.frontmatter);
|
|
11334
10620
|
const description = result.frontmatter.description;
|
|
10621
|
+
const routePaths = getRoutePaths(inputPath, srcDir, outDir, base, ssgOptions.extension, ssgOptions.siteUrl);
|
|
11335
10622
|
pageResults.push({
|
|
11336
10623
|
inputPath,
|
|
10624
|
+
routePaths,
|
|
11337
10625
|
transformedHtml,
|
|
11338
10626
|
title,
|
|
11339
10627
|
description,
|
|
@@ -11342,7 +10630,6 @@ async function buildSsg(options, root) {
|
|
|
11342
10630
|
toc: result.toc
|
|
11343
10631
|
});
|
|
11344
10632
|
if (shouldGenerateOgImages) {
|
|
11345
|
-
const ogImageOutputPath = getOgImagePath(inputPath, srcDir, outDir);
|
|
11346
10633
|
const { layout: _layout, ...frontmatterRest } = result.frontmatter;
|
|
11347
10634
|
ogImageEntries.push({
|
|
11348
10635
|
props: {
|
|
@@ -11351,10 +10638,10 @@ async function buildSsg(options, root) {
|
|
|
11351
10638
|
description,
|
|
11352
10639
|
siteName
|
|
11353
10640
|
},
|
|
11354
|
-
outputPath:
|
|
10641
|
+
outputPath: routePaths.ogImagePath
|
|
11355
10642
|
});
|
|
11356
10643
|
ogImageInputPaths.push(inputPath);
|
|
11357
|
-
ogImageUrlMap.set(inputPath,
|
|
10644
|
+
ogImageUrlMap.set(inputPath, routePaths.ogImageUrl);
|
|
11358
10645
|
}
|
|
11359
10646
|
} catch (err) {
|
|
11360
10647
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -11383,7 +10670,7 @@ async function buildSsg(options, root) {
|
|
|
11383
10670
|
ogImageUrlMap.clear();
|
|
11384
10671
|
}
|
|
11385
10672
|
for (const pageResult of pageResults) try {
|
|
11386
|
-
const { inputPath, transformedHtml, title, description, lastUpdated, frontmatter, toc } = pageResult;
|
|
10673
|
+
const { inputPath, routePaths, transformedHtml, title, description, lastUpdated, frontmatter, toc } = pageResult;
|
|
11387
10674
|
let pageOgImage = ssgOptions.ogImage;
|
|
11388
10675
|
if (shouldGenerateOgImages && ogImageUrlMap.has(inputPath)) pageOgImage = ogImageUrlMap.get(inputPath);
|
|
11389
10676
|
let entryPage;
|
|
@@ -11393,21 +10680,23 @@ async function buildSsg(options, root) {
|
|
|
11393
10680
|
};
|
|
11394
10681
|
let html;
|
|
11395
10682
|
if (ssgOptions.bare) html = generateBareHtmlPage(transformedHtml, title);
|
|
11396
|
-
else
|
|
11397
|
-
|
|
11398
|
-
|
|
11399
|
-
|
|
11400
|
-
|
|
11401
|
-
|
|
11402
|
-
|
|
11403
|
-
|
|
11404
|
-
|
|
11405
|
-
|
|
11406
|
-
|
|
11407
|
-
|
|
10683
|
+
else {
|
|
10684
|
+
const pageData = {
|
|
10685
|
+
title,
|
|
10686
|
+
description,
|
|
10687
|
+
content: transformedHtml,
|
|
10688
|
+
toc,
|
|
10689
|
+
lastUpdated,
|
|
10690
|
+
frontmatter,
|
|
10691
|
+
path: routePaths.urlPath,
|
|
10692
|
+
href: routePaths.href,
|
|
10693
|
+
entryPage
|
|
10694
|
+
};
|
|
10695
|
+
html = await generateHtmlPage(pageData, navItems, siteName, base, pageOgImage, ssgOptions.theme, getPageLocale(pageData.path, options.i18n), options.i18n ? options.i18n.locales : void 0);
|
|
10696
|
+
}
|
|
11408
10697
|
generatedPages.push({
|
|
11409
10698
|
inputPath,
|
|
11410
|
-
outputPath,
|
|
10699
|
+
outputPath: routePaths.outputPath,
|
|
11411
10700
|
html
|
|
11412
10701
|
});
|
|
11413
10702
|
} catch (err) {
|
|
@@ -11535,234 +10824,7 @@ async function writeSearchIndex(indexJson, outDir) {
|
|
|
11535
10824
|
* This is injected into the bundle as a virtual module.
|
|
11536
10825
|
*/
|
|
11537
10826
|
function generateSearchModule(options, indexPath) {
|
|
11538
|
-
return
|
|
11539
|
-
// Search module generated by ox-content
|
|
11540
|
-
const searchOptions = ${JSON.stringify(options)};
|
|
11541
|
-
|
|
11542
|
-
let searchIndex = null;
|
|
11543
|
-
let indexPromise = null;
|
|
11544
|
-
|
|
11545
|
-
function parseScopedQuery(query) {
|
|
11546
|
-
const scopes = [];
|
|
11547
|
-
const terms = [];
|
|
11548
|
-
|
|
11549
|
-
for (const part of query.trim().split(/\\s+/).filter(Boolean)) {
|
|
11550
|
-
if (part.startsWith('@') && part.length > 1) {
|
|
11551
|
-
scopes.push(part.slice(1).toLowerCase());
|
|
11552
|
-
} else {
|
|
11553
|
-
terms.push(part);
|
|
11554
|
-
}
|
|
11555
|
-
}
|
|
11556
|
-
|
|
11557
|
-
return {
|
|
11558
|
-
text: terms.join(' ').trim(),
|
|
11559
|
-
scopes: [...new Set(scopes)],
|
|
11560
|
-
};
|
|
11561
|
-
}
|
|
11562
|
-
|
|
11563
|
-
function getScopesForDoc(doc) {
|
|
11564
|
-
const source = (doc.id || doc.url || '').replace(/^\\/+/, '').toLowerCase();
|
|
11565
|
-
const segments = source.split('/').filter(Boolean);
|
|
11566
|
-
|
|
11567
|
-
if (segments.length <= 1) {
|
|
11568
|
-
return [];
|
|
11569
|
-
}
|
|
11570
|
-
|
|
11571
|
-
const scopes = [];
|
|
11572
|
-
let current = '';
|
|
11573
|
-
for (const segment of segments.slice(0, -1)) {
|
|
11574
|
-
current = current ? current + '/' + segment : segment;
|
|
11575
|
-
scopes.push(current);
|
|
11576
|
-
}
|
|
11577
|
-
|
|
11578
|
-
return scopes;
|
|
11579
|
-
}
|
|
11580
|
-
|
|
11581
|
-
function matchesScopes(doc, scopes) {
|
|
11582
|
-
if (!scopes.length) {
|
|
11583
|
-
return true;
|
|
11584
|
-
}
|
|
11585
|
-
|
|
11586
|
-
const docScopes = new Set(getScopesForDoc(doc));
|
|
11587
|
-
return scopes.some(scope => docScopes.has(scope));
|
|
11588
|
-
}
|
|
11589
|
-
|
|
11590
|
-
// Tokenizer for queries
|
|
11591
|
-
function tokenizeQuery(text) {
|
|
11592
|
-
const tokens = [];
|
|
11593
|
-
let current = '';
|
|
11594
|
-
|
|
11595
|
-
for (const char of text) {
|
|
11596
|
-
const isCjk = /[\\u4E00-\\u9FFF\\u3400-\\u4DBF\\u3040-\\u309F\\u30A0-\\u30FF\\uAC00-\\uD7AF]/.test(char);
|
|
11597
|
-
|
|
11598
|
-
if (isCjk) {
|
|
11599
|
-
if (current) {
|
|
11600
|
-
tokens.push(current.toLowerCase());
|
|
11601
|
-
current = '';
|
|
11602
|
-
}
|
|
11603
|
-
tokens.push(char);
|
|
11604
|
-
} else if (/[a-zA-Z0-9_]/.test(char)) {
|
|
11605
|
-
current += char;
|
|
11606
|
-
} else if (current) {
|
|
11607
|
-
tokens.push(current.toLowerCase());
|
|
11608
|
-
current = '';
|
|
11609
|
-
}
|
|
11610
|
-
}
|
|
11611
|
-
|
|
11612
|
-
if (current) {
|
|
11613
|
-
tokens.push(current.toLowerCase());
|
|
11614
|
-
}
|
|
11615
|
-
|
|
11616
|
-
return tokens;
|
|
11617
|
-
}
|
|
11618
|
-
|
|
11619
|
-
// BM25 scoring
|
|
11620
|
-
function computeIdf(df, docCount) {
|
|
11621
|
-
return Math.log((docCount - df + 0.5) / (df + 0.5) + 1.0);
|
|
11622
|
-
}
|
|
11623
|
-
|
|
11624
|
-
function getFieldBoost(field) {
|
|
11625
|
-
switch (field) {
|
|
11626
|
-
case 'Title': return 10.0;
|
|
11627
|
-
case 'Heading': return 5.0;
|
|
11628
|
-
case 'Body': return 1.0;
|
|
11629
|
-
case 'Code': return 0.5;
|
|
11630
|
-
default: return 1.0;
|
|
11631
|
-
}
|
|
11632
|
-
}
|
|
11633
|
-
|
|
11634
|
-
// Load the index
|
|
11635
|
-
async function loadIndex() {
|
|
11636
|
-
if (searchIndex) return searchIndex;
|
|
11637
|
-
if (indexPromise) return indexPromise;
|
|
11638
|
-
|
|
11639
|
-
indexPromise = fetch('${indexPath}')
|
|
11640
|
-
.then(res => res.json())
|
|
11641
|
-
.then(data => {
|
|
11642
|
-
searchIndex = data;
|
|
11643
|
-
return data;
|
|
11644
|
-
})
|
|
11645
|
-
.catch(err => {
|
|
11646
|
-
console.error('[ox-content] Failed to load search index:', err);
|
|
11647
|
-
return null;
|
|
11648
|
-
});
|
|
11649
|
-
|
|
11650
|
-
return indexPromise;
|
|
11651
|
-
}
|
|
11652
|
-
|
|
11653
|
-
// Search function
|
|
11654
|
-
export async function search(query, options = {}) {
|
|
11655
|
-
const index = await loadIndex();
|
|
11656
|
-
|
|
11657
|
-
if (!index) {
|
|
11658
|
-
return [];
|
|
11659
|
-
}
|
|
11660
|
-
|
|
11661
|
-
const parsedQuery = parseScopedQuery(query);
|
|
11662
|
-
|
|
11663
|
-
if (!parsedQuery.text && parsedQuery.scopes.length === 0) {
|
|
11664
|
-
return [];
|
|
11665
|
-
}
|
|
11666
|
-
|
|
11667
|
-
const limit = options.limit ?? searchOptions.limit;
|
|
11668
|
-
const prefix = options.prefix ?? searchOptions.prefix;
|
|
11669
|
-
const tokens = tokenizeQuery(parsedQuery.text);
|
|
11670
|
-
|
|
11671
|
-
const k1 = 1.2;
|
|
11672
|
-
const b = 0.75;
|
|
11673
|
-
const docScores = new Map();
|
|
11674
|
-
|
|
11675
|
-
if (tokens.length === 0) {
|
|
11676
|
-
index.documents.forEach((doc, docIdx) => {
|
|
11677
|
-
if (matchesScopes(doc, parsedQuery.scopes)) {
|
|
11678
|
-
docScores.set(docIdx, { score: 0, matches: new Set() });
|
|
11679
|
-
}
|
|
11680
|
-
});
|
|
11681
|
-
}
|
|
11682
|
-
|
|
11683
|
-
for (let i = 0; i < tokens.length; i++) {
|
|
11684
|
-
const token = tokens[i];
|
|
11685
|
-
const isLast = i === tokens.length - 1;
|
|
11686
|
-
|
|
11687
|
-
// Find matching terms
|
|
11688
|
-
let matchingTerms = [];
|
|
11689
|
-
if (prefix && isLast && token.length >= 2) {
|
|
11690
|
-
matchingTerms = Object.keys(index.index).filter(term => term.startsWith(token));
|
|
11691
|
-
} else if (index.index[token]) {
|
|
11692
|
-
matchingTerms = [token];
|
|
11693
|
-
}
|
|
11694
|
-
|
|
11695
|
-
for (const term of matchingTerms) {
|
|
11696
|
-
const postings = index.index[term] || [];
|
|
11697
|
-
const df = index.df[term] || 1;
|
|
11698
|
-
const idf = computeIdf(df, index.doc_count);
|
|
11699
|
-
|
|
11700
|
-
for (const posting of postings) {
|
|
11701
|
-
const doc = index.documents[posting.doc_idx];
|
|
11702
|
-
if (!doc) continue;
|
|
11703
|
-
if (!matchesScopes(doc, parsedQuery.scopes)) continue;
|
|
11704
|
-
|
|
11705
|
-
const docLen = doc.body.length;
|
|
11706
|
-
const tf = posting.tf;
|
|
11707
|
-
const boost = getFieldBoost(posting.field);
|
|
11708
|
-
|
|
11709
|
-
const score = idf * ((tf * (k1 + 1.0)) / (tf + k1 * (1.0 - b + b * docLen / index.avg_dl))) * boost;
|
|
11710
|
-
|
|
11711
|
-
if (!docScores.has(posting.doc_idx)) {
|
|
11712
|
-
docScores.set(posting.doc_idx, { score: 0, matches: new Set() });
|
|
11713
|
-
}
|
|
11714
|
-
const entry = docScores.get(posting.doc_idx);
|
|
11715
|
-
entry.score += score;
|
|
11716
|
-
entry.matches.add(term);
|
|
11717
|
-
}
|
|
11718
|
-
}
|
|
11719
|
-
}
|
|
11720
|
-
|
|
11721
|
-
// Convert to results
|
|
11722
|
-
const results = Array.from(docScores.entries())
|
|
11723
|
-
.map(([docIdx, data]) => {
|
|
11724
|
-
const doc = index.documents[docIdx];
|
|
11725
|
-
const matches = Array.from(data.matches);
|
|
11726
|
-
const scopes = getScopesForDoc(doc);
|
|
11727
|
-
|
|
11728
|
-
// Generate snippet
|
|
11729
|
-
let snippet = '';
|
|
11730
|
-
if (doc.body) {
|
|
11731
|
-
const bodyLower = doc.body.toLowerCase();
|
|
11732
|
-
let firstPos = -1;
|
|
11733
|
-
for (const match of matches) {
|
|
11734
|
-
const pos = bodyLower.indexOf(match);
|
|
11735
|
-
if (pos !== -1 && (firstPos === -1 || pos < firstPos)) {
|
|
11736
|
-
firstPos = pos;
|
|
11737
|
-
}
|
|
11738
|
-
}
|
|
11739
|
-
|
|
11740
|
-
const start = firstPos === -1 ? 0 : Math.max(0, firstPos - 50);
|
|
11741
|
-
const end = Math.min(doc.body.length, start + 150);
|
|
11742
|
-
snippet = doc.body.slice(start, end);
|
|
11743
|
-
if (start > 0) snippet = '...' + snippet;
|
|
11744
|
-
if (end < doc.body.length) snippet = snippet + '...';
|
|
11745
|
-
}
|
|
11746
|
-
|
|
11747
|
-
return {
|
|
11748
|
-
id: doc.id,
|
|
11749
|
-
title: doc.title,
|
|
11750
|
-
url: doc.url,
|
|
11751
|
-
score: data.score,
|
|
11752
|
-
matches,
|
|
11753
|
-
snippet,
|
|
11754
|
-
scopes,
|
|
11755
|
-
};
|
|
11756
|
-
})
|
|
11757
|
-
.sort((a, b) => b.score - a.score || a.title.localeCompare(b.title))
|
|
11758
|
-
.slice(0, limit);
|
|
11759
|
-
|
|
11760
|
-
return results;
|
|
11761
|
-
}
|
|
11762
|
-
|
|
11763
|
-
export { searchOptions };
|
|
11764
|
-
export default { search, searchOptions, loadIndex };
|
|
11765
|
-
`;
|
|
10827
|
+
return require_mermaid.importNapiModuleSync().generateSearchModule(JSON.stringify(options), indexPath);
|
|
11766
10828
|
}
|
|
11767
10829
|
//#endregion
|
|
11768
10830
|
//#region src/dev-server.ts
|
|
@@ -12403,7 +11465,7 @@ function createI18nPlugin(resolvedOptions) {
|
|
|
12403
11465
|
}
|
|
12404
11466
|
server.middlewares.use((req, _res, next) => {
|
|
12405
11467
|
if (!req.url) return next();
|
|
12406
|
-
const localeMatch = req.url.match(/^\/([
|
|
11468
|
+
const localeMatch = req.url.match(/^\/([A-Za-z]{2,3}(?:-[A-Za-z0-9]+)*)(\/|$)/);
|
|
12407
11469
|
if (localeMatch) {
|
|
12408
11470
|
const localeCode = localeMatch[1];
|
|
12409
11471
|
if (i18nOptions.locales.some((l) => l.code === localeCode)) req.__oxLocale = localeCode;
|
|
@@ -12418,104 +11480,18 @@ function createI18nPlugin(resolvedOptions) {
|
|
|
12418
11480
|
*/
|
|
12419
11481
|
function generateI18nModule(options, root) {
|
|
12420
11482
|
const dictDir = path.resolve(root, options.dir);
|
|
12421
|
-
const
|
|
12422
|
-
|
|
12423
|
-
|
|
11483
|
+
const config = {
|
|
11484
|
+
defaultLocale: options.defaultLocale,
|
|
11485
|
+
locales: options.locales,
|
|
11486
|
+
hideDefaultLocale: options.hideDefaultLocale
|
|
11487
|
+
};
|
|
12424
11488
|
try {
|
|
12425
11489
|
const napi = require("@ox-content/napi");
|
|
12426
|
-
if (napi.
|
|
12427
|
-
|
|
12428
|
-
|
|
12429
|
-
} else dictionariesCode = JSON.stringify(loadDictionariesFallback(options, dictDir));
|
|
12430
|
-
} catch {
|
|
12431
|
-
try {
|
|
12432
|
-
dictionariesCode = JSON.stringify(loadDictionariesFallback(options, dictDir));
|
|
12433
|
-
} catch {}
|
|
12434
|
-
}
|
|
12435
|
-
return `
|
|
12436
|
-
export const i18nConfig = {
|
|
12437
|
-
enabled: true,
|
|
12438
|
-
defaultLocale: ${defaultLocale},
|
|
12439
|
-
locales: ${localesJson},
|
|
12440
|
-
hideDefaultLocale: ${JSON.stringify(options.hideDefaultLocale)},
|
|
12441
|
-
};
|
|
12442
|
-
|
|
12443
|
-
export const dictionaries = ${dictionariesCode};
|
|
12444
|
-
|
|
12445
|
-
export function t(key, params, locale) {
|
|
12446
|
-
const dict = dictionaries[locale || i18nConfig.defaultLocale] || {};
|
|
12447
|
-
let message = dict[key];
|
|
12448
|
-
if (!message) {
|
|
12449
|
-
const fallback = dictionaries[i18nConfig.defaultLocale] || {};
|
|
12450
|
-
message = fallback[key] || key;
|
|
12451
|
-
}
|
|
12452
|
-
if (params) {
|
|
12453
|
-
for (const [k, v] of Object.entries(params)) {
|
|
12454
|
-
message = message.replace(new RegExp('\\\\{\\\\$' + k + '\\\\}', 'g'), String(v));
|
|
12455
|
-
}
|
|
12456
|
-
}
|
|
12457
|
-
return message;
|
|
12458
|
-
}
|
|
12459
|
-
|
|
12460
|
-
export function getLocaleFromPath(pathname) {
|
|
12461
|
-
const match = pathname.match(/^\\/([a-z]{2}(?:-[a-zA-Z]+)?)(\\//|$)/);
|
|
12462
|
-
if (match) {
|
|
12463
|
-
const code = match[1];
|
|
12464
|
-
if (i18nConfig.locales.some(l => l.code === code)) {
|
|
12465
|
-
return code;
|
|
12466
|
-
}
|
|
12467
|
-
}
|
|
12468
|
-
return i18nConfig.defaultLocale;
|
|
12469
|
-
}
|
|
12470
|
-
|
|
12471
|
-
export function localePath(pathname, locale) {
|
|
12472
|
-
const current = getLocaleFromPath(pathname);
|
|
12473
|
-
let clean = pathname;
|
|
12474
|
-
if (current !== i18nConfig.defaultLocale || !i18nConfig.hideDefaultLocale) {
|
|
12475
|
-
clean = pathname.replace(new RegExp('^/' + current + '(/|$)'), '/');
|
|
12476
|
-
}
|
|
12477
|
-
if (locale === i18nConfig.defaultLocale && i18nConfig.hideDefaultLocale) {
|
|
12478
|
-
return clean || '/';
|
|
12479
|
-
}
|
|
12480
|
-
return '/' + locale + (clean.startsWith('/') ? clean : '/' + clean);
|
|
12481
|
-
}
|
|
12482
|
-
|
|
12483
|
-
export default { i18nConfig, dictionaries, t, getLocaleFromPath, localePath };
|
|
12484
|
-
`;
|
|
12485
|
-
}
|
|
12486
|
-
/**
|
|
12487
|
-
* Flattens a nested object into dot-separated keys.
|
|
12488
|
-
*/
|
|
12489
|
-
function flattenObject(obj, prefix, result) {
|
|
12490
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
12491
|
-
const fullKey = `${prefix}.${key}`;
|
|
12492
|
-
if (typeof value === "string") result[fullKey] = value;
|
|
12493
|
-
else if (typeof value === "object" && value !== null && !Array.isArray(value)) flattenObject(value, fullKey, result);
|
|
12494
|
-
else result[fullKey] = String(value);
|
|
12495
|
-
}
|
|
12496
|
-
}
|
|
12497
|
-
/**
|
|
12498
|
-
* Fallback dictionary loading using TS-based JSON file reading.
|
|
12499
|
-
*/
|
|
12500
|
-
function loadDictionariesFallback(options, dictDir) {
|
|
12501
|
-
const dictData = {};
|
|
12502
|
-
for (const locale of options.locales) {
|
|
12503
|
-
const localeDir = path.join(dictDir, locale.code);
|
|
12504
|
-
if (!fs.existsSync(localeDir)) continue;
|
|
12505
|
-
const files = fs.readdirSync(localeDir);
|
|
12506
|
-
const localeDict = {};
|
|
12507
|
-
for (const file of files) {
|
|
12508
|
-
if (!file.endsWith(".json")) continue;
|
|
12509
|
-
const filePath = path.join(localeDir, file);
|
|
12510
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
12511
|
-
const namespace = path.basename(file, ".json");
|
|
12512
|
-
try {
|
|
12513
|
-
flattenObject(JSON.parse(content), namespace, localeDict);
|
|
12514
|
-
} catch {}
|
|
12515
|
-
}
|
|
12516
|
-
dictData[locale.code] = localeDict;
|
|
11490
|
+
if (typeof napi.generateI18nModule === "function") return napi.generateI18nModule(dictDir, config);
|
|
11491
|
+
} catch (error) {
|
|
11492
|
+
throw new Error(`[ox-content:i18n] Failed to load @ox-content/napi for i18n module generation: ${String(error)}`);
|
|
12517
11493
|
}
|
|
12518
|
-
|
|
11494
|
+
throw new Error("[ox-content:i18n] @ox-content/napi does not expose generateI18nModule. Please rebuild the NAPI package.");
|
|
12519
11495
|
}
|
|
12520
11496
|
/**
|
|
12521
11497
|
* Collects translation keys from source files using NAPI extractTranslationKeys.
|
|
@@ -13711,20 +12687,54 @@ function resolveCodeAnnotationsOptions(options) {
|
|
|
13711
12687
|
/**
|
|
13712
12688
|
* Generates virtual module content.
|
|
13713
12689
|
*/
|
|
13714
|
-
function generateVirtualModule(path$
|
|
13715
|
-
if (path$
|
|
13716
|
-
if (path$
|
|
12690
|
+
function generateVirtualModule(path$1, options) {
|
|
12691
|
+
if (path$1 === "config") return `export default ${JSON.stringify(options)};`;
|
|
12692
|
+
if (path$1 === "runtime") {
|
|
12693
|
+
const base = normalizeRuntimeBase(options.base);
|
|
12694
|
+
return `
|
|
12695
|
+
export const base = ${JSON.stringify(base)};
|
|
12696
|
+
export const runtimeConfig = { base };
|
|
12697
|
+
|
|
12698
|
+
export function isExternalUrl(value) {
|
|
12699
|
+
return /^(?:https?:)?\\/\\//i.test(value) || /^(?:mailto|tel):/i.test(value);
|
|
12700
|
+
}
|
|
12701
|
+
|
|
12702
|
+
export function withBase(pathname = "") {
|
|
12703
|
+
const value = String(pathname);
|
|
12704
|
+
if (!value || value === "/") return base;
|
|
12705
|
+
if (value.startsWith("#") || isExternalUrl(value)) return value;
|
|
12706
|
+
return base + (value.startsWith("/") ? value.slice(1) : value);
|
|
12707
|
+
}
|
|
12708
|
+
|
|
12709
|
+
export function withoutBase(pathname = "") {
|
|
12710
|
+
const value = String(pathname);
|
|
12711
|
+
if (base === "/" || value.startsWith("#") || isExternalUrl(value)) return value;
|
|
12712
|
+
const bareBase = base.slice(0, -1);
|
|
12713
|
+
if (value === bareBase) return "/";
|
|
12714
|
+
if (value.startsWith(base)) return "/" + value.slice(base.length);
|
|
12715
|
+
return value;
|
|
12716
|
+
}
|
|
12717
|
+
|
|
13717
12718
|
export function useMarkdown() {
|
|
13718
12719
|
return {
|
|
12720
|
+
base,
|
|
12721
|
+
withBase,
|
|
12722
|
+
withoutBase,
|
|
13719
12723
|
render: (content) => {
|
|
13720
|
-
// Client-side rendering if needed
|
|
13721
12724
|
return content;
|
|
13722
12725
|
},
|
|
13723
12726
|
};
|
|
13724
12727
|
}
|
|
13725
12728
|
`;
|
|
12729
|
+
}
|
|
13726
12730
|
return "export default {};";
|
|
13727
12731
|
}
|
|
12732
|
+
function normalizeRuntimeBase(base) {
|
|
12733
|
+
const trimmed = base.trim();
|
|
12734
|
+
if (!trimmed || trimmed === "/") return "/";
|
|
12735
|
+
const withLeading = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
12736
|
+
return withLeading.endsWith("/") ? withLeading : `${withLeading}/`;
|
|
12737
|
+
}
|
|
13728
12738
|
//#endregion
|
|
13729
12739
|
exports.DEFAULT_HTML_TEMPLATE = DEFAULT_HTML_TEMPLATE;
|
|
13730
12740
|
exports.DefaultTheme = DefaultTheme;
|
|
@@ -13751,6 +12761,7 @@ exports.generateMarkdown = generateMarkdown;
|
|
|
13751
12761
|
exports.generateOgImages = generateOgImages;
|
|
13752
12762
|
exports.generateTabsCSS = require_tabs.generateTabsCSS;
|
|
13753
12763
|
exports.generateTypes = generateTypes;
|
|
12764
|
+
exports.generateVirtualModule = generateVirtualModule;
|
|
13754
12765
|
exports.hasIslands = hasIslands;
|
|
13755
12766
|
exports.inferType = inferType;
|
|
13756
12767
|
exports.jsx = jsx;
|