@ox-content/vite-plugin 2.8.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.mjs +299 -2
- package/dist/github.mjs.map +1 -0
- package/dist/index.cjs +40 -1167
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +120 -1283
- 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.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 -291
- package/dist/github2.mjs.map +0 -1
- package/dist/mermaid2.mjs +0 -2
- package/dist/ogp2.mjs +0 -299
- 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
|
-
}));
|
|
7090
|
+
return require_mermaid.importNapiModuleSync().generateDocsNavMetadata(docs.map((doc) => doc.file), basePath);
|
|
7210
7091
|
}
|
|
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;
|
|
7254
|
-
}
|
|
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).
|
|
@@ -10946,223 +10506,35 @@ async function generateHtmlPage(pageData, navGroups, siteName, base, ogImage, th
|
|
|
10946
10506
|
}))
|
|
10947
10507
|
});
|
|
10948
10508
|
}
|
|
10949
|
-
const SSG_STYLE_BLOCK_RE = /[ \t]*<!-- ox-content:styles:start -->\s*<style>([\s\S]*?)<\/style>\s*<!-- ox-content:styles:end -->/;
|
|
10950
|
-
const SSG_SCRIPT_BLOCK_RE = /[ \t]*<!-- ox-content:scripts:start -->\s*<script>([\s\S]*?)<\/script>\s*<!-- ox-content:scripts:end -->/;
|
|
10951
|
-
const FIRST_INLINE_STYLE_RE = /[ \t]*<style>([\s\S]*?)<\/style>/;
|
|
10952
|
-
const LAST_INLINE_BODY_SCRIPT_RE = /[ \t]*<script>([\s\S]*?)<\/script>\s*<\/body>/;
|
|
10953
|
-
const CSS_SECTION_RE = /\/\* ox-content:css:([a-z0-9-]+):start \*\/\s*([\s\S]*?)\s*\/\* ox-content:css:\1:end \*\//g;
|
|
10954
|
-
const SEARCH_CHUNK_RE = /\/\/ ox-content:search:start\s*([\s\S]*?)\s*\/\/ ox-content:search:end/;
|
|
10955
|
-
const SEARCH_CHUNK_PLACEHOLDER = "__OX_CONTENT_SEARCH_CHUNK__";
|
|
10956
|
-
const CORE_CSS_SECTION_NAMES = new Set(["base", "footer"]);
|
|
10957
|
-
const THEME_INLINE_CSS_MAX_BYTES = 2048;
|
|
10958
|
-
function createContentHash(content) {
|
|
10959
|
-
return (0, node_crypto.createHash)("sha256").update(content).digest("hex").slice(0, 10);
|
|
10960
|
-
}
|
|
10961
|
-
function sanitizeChunkLabel(label) {
|
|
10962
|
-
return label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "asset";
|
|
10963
|
-
}
|
|
10964
|
-
function toPublicAssetPath(base, fileName) {
|
|
10965
|
-
return `${base.endsWith("/") ? base : `${base}/`}assets/${fileName}`;
|
|
10966
|
-
}
|
|
10967
|
-
function hasRelativeCssUrls(css) {
|
|
10968
|
-
let cursor = 0;
|
|
10969
|
-
while (cursor < css.length) {
|
|
10970
|
-
const urlIndex = css.indexOf("url(", cursor);
|
|
10971
|
-
if (urlIndex === -1) return false;
|
|
10972
|
-
let valueStart = urlIndex + 4;
|
|
10973
|
-
while (valueStart < css.length && /\s/.test(css[valueStart])) valueStart++;
|
|
10974
|
-
const quote = css[valueStart] === "\"" || css[valueStart] === "'" ? css[valueStart] : "";
|
|
10975
|
-
if (quote) valueStart++;
|
|
10976
|
-
let valueEnd = valueStart;
|
|
10977
|
-
while (valueEnd < css.length) {
|
|
10978
|
-
const char = css[valueEnd];
|
|
10979
|
-
if (quote) {
|
|
10980
|
-
if (char === "\\") {
|
|
10981
|
-
valueEnd += 2;
|
|
10982
|
-
continue;
|
|
10983
|
-
}
|
|
10984
|
-
if (char === quote) break;
|
|
10985
|
-
} else if (char === ")") break;
|
|
10986
|
-
valueEnd++;
|
|
10987
|
-
}
|
|
10988
|
-
const value = css.slice(valueStart, valueEnd).trim();
|
|
10989
|
-
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;
|
|
10990
|
-
cursor = valueEnd + 1;
|
|
10991
|
-
}
|
|
10992
|
-
return false;
|
|
10993
|
-
}
|
|
10994
|
-
function createSharedAssetChunk(type, label, content, outDir, base) {
|
|
10995
|
-
const hash = createContentHash(content);
|
|
10996
|
-
const fileName = `ox-content-${sanitizeChunkLabel(label)}-${hash}.${type}`;
|
|
10997
|
-
return {
|
|
10998
|
-
outputPath: path.join(outDir, "assets", fileName),
|
|
10999
|
-
publicPath: toPublicAssetPath(base, fileName),
|
|
11000
|
-
content
|
|
11001
|
-
};
|
|
11002
|
-
}
|
|
11003
|
-
function extractCssSections(cssContent) {
|
|
11004
|
-
return Array.from(cssContent.matchAll(CSS_SECTION_RE)).map(([, name, content]) => ({
|
|
11005
|
-
name,
|
|
11006
|
-
content: content.trim()
|
|
11007
|
-
})).filter((section) => section.content.length > 0);
|
|
11008
|
-
}
|
|
11009
|
-
function getOrCreateSharedChunk(chunks, type, label, content, outDir, base) {
|
|
11010
|
-
let chunk = chunks.get(content);
|
|
11011
|
-
if (!chunk) {
|
|
11012
|
-
chunk = createSharedAssetChunk(type, label, content, outDir, base);
|
|
11013
|
-
chunks.set(content, chunk);
|
|
11014
|
-
}
|
|
11015
|
-
return chunk;
|
|
11016
|
-
}
|
|
11017
|
-
function buildStyleReplacement(cssContent, cssChunks, outDir, base) {
|
|
11018
|
-
const sections = extractCssSections(cssContent);
|
|
11019
|
-
const effectiveSections = sections.length > 0 ? sections : [{
|
|
11020
|
-
name: "css",
|
|
11021
|
-
content: cssContent.trim()
|
|
11022
|
-
}];
|
|
11023
|
-
const coreContent = effectiveSections.filter((section) => CORE_CSS_SECTION_NAMES.has(section.name)).map((section) => section.content).join("\n").trim();
|
|
11024
|
-
const fragments = [];
|
|
11025
|
-
if (coreContent) {
|
|
11026
|
-
const coreChunk = getOrCreateSharedChunk(cssChunks, "css", "core", coreContent, outDir, base);
|
|
11027
|
-
fragments.push(` <link rel="stylesheet" href="${coreChunk.publicPath}">`);
|
|
11028
|
-
}
|
|
11029
|
-
for (const section of effectiveSections) {
|
|
11030
|
-
if (CORE_CSS_SECTION_NAMES.has(section.name)) continue;
|
|
11031
|
-
if (section.name === "theme" && (hasRelativeCssUrls(section.content) || section.content.length <= THEME_INLINE_CSS_MAX_BYTES) || hasRelativeCssUrls(section.content)) {
|
|
11032
|
-
fragments.push(` <style>${section.content}</style>`);
|
|
11033
|
-
continue;
|
|
11034
|
-
}
|
|
11035
|
-
const chunk = getOrCreateSharedChunk(cssChunks, "css", section.name, section.content, outDir, base);
|
|
11036
|
-
fragments.push(` <link rel="stylesheet" href="${chunk.publicPath}">`);
|
|
11037
|
-
}
|
|
11038
|
-
return fragments.join("\n");
|
|
11039
|
-
}
|
|
11040
|
-
function buildScriptReplacement(jsContent, jsChunks, outDir, base) {
|
|
11041
|
-
const searchMatch = jsContent.match(SEARCH_CHUNK_RE);
|
|
11042
|
-
if (searchMatch && jsContent.includes(SEARCH_CHUNK_PLACEHOLDER)) {
|
|
11043
|
-
const searchContent = searchMatch[1].trim();
|
|
11044
|
-
if (searchContent) {
|
|
11045
|
-
const searchChunk = getOrCreateSharedChunk(jsChunks, "js", "search", searchContent, outDir, base);
|
|
11046
|
-
const coreContent = jsContent.replace(SEARCH_CHUNK_RE, "").replaceAll(SEARCH_CHUNK_PLACEHOLDER, searchChunk.publicPath).trim();
|
|
11047
|
-
if (coreContent) return ` <script defer src="${getOrCreateSharedChunk(jsChunks, "js", "core", coreContent, outDir, base).publicPath}"><\/script>`;
|
|
11048
|
-
}
|
|
11049
|
-
}
|
|
11050
|
-
const fallbackContent = jsContent.trim();
|
|
11051
|
-
if (!fallbackContent) return "";
|
|
11052
|
-
return ` <script defer src="${getOrCreateSharedChunk(jsChunks, "js", "js", fallbackContent, outDir, base).publicPath}"><\/script>`;
|
|
11053
|
-
}
|
|
11054
10509
|
async function externalizeSharedPageAssets(pages, outDir, base) {
|
|
11055
|
-
const
|
|
11056
|
-
|
|
11057
|
-
|
|
11058
|
-
|
|
11059
|
-
const styleMatch = html.match(SSG_STYLE_BLOCK_RE);
|
|
11060
|
-
if (styleMatch) {
|
|
11061
|
-
const replacement = buildStyleReplacement(styleMatch[1], cssChunks, outDir, base);
|
|
11062
|
-
html = html.replace(SSG_STYLE_BLOCK_RE, replacement);
|
|
11063
|
-
} else {
|
|
11064
|
-
const inlineStyleMatch = html.match(FIRST_INLINE_STYLE_RE);
|
|
11065
|
-
if (inlineStyleMatch) {
|
|
11066
|
-
const replacement = buildStyleReplacement(inlineStyleMatch[1], cssChunks, outDir, base);
|
|
11067
|
-
html = html.replace(FIRST_INLINE_STYLE_RE, replacement);
|
|
11068
|
-
}
|
|
11069
|
-
}
|
|
11070
|
-
const scriptMatch = html.match(SSG_SCRIPT_BLOCK_RE);
|
|
11071
|
-
if (scriptMatch) {
|
|
11072
|
-
const replacement = buildScriptReplacement(scriptMatch[1], jsChunks, outDir, base);
|
|
11073
|
-
html = html.replace(SSG_SCRIPT_BLOCK_RE, replacement);
|
|
11074
|
-
} else {
|
|
11075
|
-
const inlineScriptMatch = html.match(LAST_INLINE_BODY_SCRIPT_RE);
|
|
11076
|
-
if (inlineScriptMatch) {
|
|
11077
|
-
const replacement = buildScriptReplacement(inlineScriptMatch[1], jsChunks, outDir, base);
|
|
11078
|
-
html = html.replace(LAST_INLINE_BODY_SCRIPT_RE, replacement ? `${replacement}\n</body>` : "</body>");
|
|
11079
|
-
}
|
|
11080
|
-
}
|
|
11081
|
-
return {
|
|
11082
|
-
...page,
|
|
11083
|
-
html
|
|
11084
|
-
};
|
|
11085
|
-
});
|
|
11086
|
-
const chunks = [...cssChunks.values(), ...jsChunks.values()];
|
|
11087
|
-
await Promise.all(chunks.map(async (chunk) => {
|
|
11088
|
-
await fs_promises.mkdir(path.dirname(chunk.outputPath), { recursive: true });
|
|
11089
|
-
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");
|
|
11090
10514
|
}));
|
|
11091
10515
|
return {
|
|
11092
|
-
pages:
|
|
11093
|
-
assets:
|
|
10516
|
+
pages: optimized.pages,
|
|
10517
|
+
assets: optimized.assets.map((asset) => asset.outputPath)
|
|
11094
10518
|
};
|
|
11095
10519
|
}
|
|
11096
10520
|
/**
|
|
11097
|
-
* Converts a markdown file path to its corresponding HTML output path.
|
|
11098
|
-
*/
|
|
11099
|
-
function getOutputPath(inputPath, srcDir, outDir, extension) {
|
|
11100
|
-
const baseName = path.relative(srcDir, inputPath).replace(/\.(?:md|markdown)$/i, extension);
|
|
11101
|
-
if (baseName.endsWith(`index${extension}`)) return path.join(outDir, baseName);
|
|
11102
|
-
const dirName = baseName.replace(new RegExp(`\\${extension}$`), "");
|
|
11103
|
-
return path.join(outDir, dirName, `index${extension}`);
|
|
11104
|
-
}
|
|
11105
|
-
/**
|
|
11106
10521
|
* Converts a markdown file path to a relative URL path.
|
|
11107
10522
|
*/
|
|
11108
10523
|
function getUrlPath$1(inputPath, srcDir) {
|
|
11109
|
-
|
|
11110
|
-
if (baseName === "index" || baseName.endsWith("/index")) return baseName.replace(/\/?index$/, "") || "/";
|
|
11111
|
-
return baseName;
|
|
11112
|
-
}
|
|
11113
|
-
/**
|
|
11114
|
-
* Converts a markdown file path to an href.
|
|
11115
|
-
*/
|
|
11116
|
-
function getHref(inputPath, srcDir, base, extension) {
|
|
11117
|
-
const urlPath = getUrlPath$1(inputPath, srcDir);
|
|
11118
|
-
if (urlPath === "/" || urlPath === "") return `${base}index${extension}`;
|
|
11119
|
-
return `${base}${urlPath}/index${extension}`;
|
|
10524
|
+
return require_mermaid.importNapiModuleSync().getSsgUrlPath(inputPath, srcDir);
|
|
11120
10525
|
}
|
|
11121
10526
|
function getPageLocale(urlPath, i18n) {
|
|
11122
10527
|
if (!i18n) return void 0;
|
|
11123
|
-
|
|
11124
|
-
return i18n.locales.some((l) => l.code === firstSegment) ? firstSegment : i18n.defaultLocale;
|
|
11125
|
-
}
|
|
11126
|
-
/**
|
|
11127
|
-
* Gets the OG image output path for a given markdown file.
|
|
11128
|
-
*/
|
|
11129
|
-
function getOgImagePath(inputPath, srcDir, outDir) {
|
|
11130
|
-
const baseName = path.relative(srcDir, inputPath).replace(/\.(?:md|markdown)$/i, "");
|
|
11131
|
-
if (baseName === "index" || baseName.endsWith("/index")) {
|
|
11132
|
-
const dirPath = baseName.replace(/\/?index$/, "") || "";
|
|
11133
|
-
return path.join(outDir, dirPath, "og-image.png");
|
|
11134
|
-
}
|
|
11135
|
-
return path.join(outDir, baseName, "og-image.png");
|
|
11136
|
-
}
|
|
11137
|
-
/**
|
|
11138
|
-
* Gets the OG image URL for use in meta tags.
|
|
11139
|
-
* If siteUrl is provided, returns an absolute URL (required for SNS sharing).
|
|
11140
|
-
*/
|
|
11141
|
-
function getOgImageUrl(inputPath, srcDir, base, siteUrl) {
|
|
11142
|
-
const urlPath = getUrlPath$1(inputPath, srcDir);
|
|
11143
|
-
let relativePath;
|
|
11144
|
-
if (urlPath === "/" || urlPath === "") relativePath = `${base}og-image.png`;
|
|
11145
|
-
else relativePath = `${base}${urlPath}/og-image.png`;
|
|
11146
|
-
if (siteUrl) return `${siteUrl.replace(/\/$/, "")}${relativePath}`;
|
|
11147
|
-
return relativePath;
|
|
10528
|
+
return require_mermaid.importNapiModuleSync().getSsgPageLocale(urlPath, i18n.defaultLocale, i18n.locales.map((locale) => locale.code)) ?? void 0;
|
|
11148
10529
|
}
|
|
11149
|
-
|
|
11150
|
-
|
|
11151
|
-
*/
|
|
11152
|
-
function getDisplayTitle(filePath) {
|
|
11153
|
-
const fileName = path.basename(filePath, path.extname(filePath));
|
|
11154
|
-
if (fileName === "index") {
|
|
11155
|
-
const dirName = path.basename(path.dirname(filePath));
|
|
11156
|
-
if (dirName && dirName !== ".") return formatTitle(dirName);
|
|
11157
|
-
return "Home";
|
|
11158
|
-
}
|
|
11159
|
-
return formatTitle(fileName);
|
|
10530
|
+
function getRoutePaths(inputPath, srcDir, outDir, base, extension, siteUrl) {
|
|
10531
|
+
return require_mermaid.importNapiModuleSync().resolveSsgRoutePaths(inputPath, srcDir, outDir, base, extension, siteUrl);
|
|
11160
10532
|
}
|
|
11161
10533
|
/**
|
|
11162
10534
|
* Formats a file/dir name as a title.
|
|
11163
10535
|
*/
|
|
11164
10536
|
function formatTitle(name) {
|
|
11165
|
-
return
|
|
10537
|
+
return require_mermaid.importNapiModuleSync().formatSsgTitle(name);
|
|
11166
10538
|
}
|
|
11167
10539
|
/**
|
|
11168
10540
|
* Collects all markdown files from the source directory.
|
|
@@ -11181,110 +10553,13 @@ async function collectMarkdownFiles$1(srcDir) {
|
|
|
11181
10553
|
* Builds navigation items from markdown files, grouped by directory.
|
|
11182
10554
|
*/
|
|
11183
10555
|
function buildNavItems(markdownFiles, srcDir, base, extension) {
|
|
11184
|
-
|
|
11185
|
-
const groupOrder = [
|
|
11186
|
-
"",
|
|
11187
|
-
"examples",
|
|
11188
|
-
"packages",
|
|
11189
|
-
"api"
|
|
11190
|
-
];
|
|
11191
|
-
for (const file of markdownFiles) {
|
|
11192
|
-
const parts = path.relative(srcDir, file).split(path.sep);
|
|
11193
|
-
let groupKey = "";
|
|
11194
|
-
if (parts.length > 1) groupKey = parts[0];
|
|
11195
|
-
if (!groups.has(groupKey)) groups.set(groupKey, []);
|
|
11196
|
-
const urlPath = getUrlPath$1(file, srcDir);
|
|
11197
|
-
let title;
|
|
11198
|
-
if (urlPath === "/" || urlPath === "") title = "Overview";
|
|
11199
|
-
else title = getDisplayTitle(file);
|
|
11200
|
-
groups.get(groupKey).push({
|
|
11201
|
-
title,
|
|
11202
|
-
path: urlPath,
|
|
11203
|
-
href: getHref(file, srcDir, base, extension)
|
|
11204
|
-
});
|
|
11205
|
-
}
|
|
11206
|
-
const sortItems = (items) => {
|
|
11207
|
-
return items.sort((a, b) => {
|
|
11208
|
-
const aIsRoot = a.path === "/" || a.path === "";
|
|
11209
|
-
const bIsRoot = b.path === "/" || b.path === "";
|
|
11210
|
-
if (aIsRoot && !bIsRoot) return -1;
|
|
11211
|
-
if (!aIsRoot && bIsRoot) return 1;
|
|
11212
|
-
return a.title.localeCompare(b.title);
|
|
11213
|
-
});
|
|
11214
|
-
};
|
|
11215
|
-
const result = [];
|
|
11216
|
-
for (const key of groupOrder) {
|
|
11217
|
-
const items = groups.get(key);
|
|
11218
|
-
if (items && items.length > 0) {
|
|
11219
|
-
result.push({
|
|
11220
|
-
title: key === "" ? "Guide" : formatTitle(key),
|
|
11221
|
-
items: sortItems(items)
|
|
11222
|
-
});
|
|
11223
|
-
groups.delete(key);
|
|
11224
|
-
}
|
|
11225
|
-
}
|
|
11226
|
-
for (const [key, items] of groups) if (items.length > 0) result.push({
|
|
11227
|
-
title: formatTitle(key),
|
|
11228
|
-
items: sortItems(items)
|
|
11229
|
-
});
|
|
11230
|
-
return result;
|
|
11231
|
-
}
|
|
11232
|
-
function isSafeSidebarLink(link) {
|
|
11233
|
-
const trimmed = link.trim();
|
|
11234
|
-
if (trimmed.startsWith("//")) return false;
|
|
11235
|
-
return !/^[a-z][a-z0-9+.-]*:/i.test(trimmed) || /^(https?:|mailto:)/i.test(trimmed);
|
|
11236
|
-
}
|
|
11237
|
-
function sidebarPath(link) {
|
|
11238
|
-
if (!link || !isSafeSidebarLink(link)) return "";
|
|
11239
|
-
if (/^(https?:|mailto:|#)/i.test(link.trim())) return "";
|
|
11240
|
-
const bare = link.trim().split("#", 1)[0].split("?", 1)[0].replace(/^\/+/, "").replace(/\/$/, "").replace(/\.(md|markdown)$/i, "");
|
|
11241
|
-
if (!bare || bare === "index") return "/";
|
|
11242
|
-
return bare.replace(/\/index$/, "");
|
|
11243
|
-
}
|
|
11244
|
-
function sidebarHref(link, base, extension) {
|
|
11245
|
-
if (!link) return "#";
|
|
11246
|
-
const trimmed = link.trim();
|
|
11247
|
-
if (!isSafeSidebarLink(trimmed)) return "#";
|
|
11248
|
-
if (/^(https?:|mailto:|#)/i.test(trimmed)) return trimmed;
|
|
11249
|
-
const hash = trimmed.includes("#") ? `#${trimmed.split("#").slice(1).join("#")}` : "";
|
|
11250
|
-
const withoutExt = trimmed.split("#", 1)[0].replace(/^\/+/, "").replace(/\/$/, "").replace(/\.(md|markdown)$/i, "");
|
|
11251
|
-
return `${base}${!withoutExt || withoutExt === "index" ? "index" : `${withoutExt.replace(/\/index$/, "")}/index`}${extension}${hash}`;
|
|
10556
|
+
return require_mermaid.importNapiModuleSync().buildSsgNavItems(markdownFiles, srcDir, base, extension);
|
|
11252
10557
|
}
|
|
11253
10558
|
/**
|
|
11254
10559
|
* Builds navigation items from an explicit theme sidebar tree.
|
|
11255
10560
|
*/
|
|
11256
10561
|
function buildThemeNavItems(sidebar, base, extension) {
|
|
11257
|
-
|
|
11258
|
-
const navItem = {
|
|
11259
|
-
title: item.text ?? item.link ?? "Untitled",
|
|
11260
|
-
path: sidebarPath(item.link),
|
|
11261
|
-
href: sidebarHref(item.link, base, extension)
|
|
11262
|
-
};
|
|
11263
|
-
if (item.items?.length) navItem.children = item.items.map(toNavItem);
|
|
11264
|
-
if (item.collapsed !== void 0) navItem.collapsed = item.collapsed;
|
|
11265
|
-
return navItem;
|
|
11266
|
-
};
|
|
11267
|
-
const groups = [];
|
|
11268
|
-
let looseItems = [];
|
|
11269
|
-
const flushLooseItems = () => {
|
|
11270
|
-
if (looseItems.length > 0) {
|
|
11271
|
-
groups.push({
|
|
11272
|
-
title: "Guide",
|
|
11273
|
-
items: looseItems
|
|
11274
|
-
});
|
|
11275
|
-
looseItems = [];
|
|
11276
|
-
}
|
|
11277
|
-
};
|
|
11278
|
-
for (const item of sidebar) if (item.items?.length && !item.link) {
|
|
11279
|
-
flushLooseItems();
|
|
11280
|
-
groups.push({
|
|
11281
|
-
title: item.text ?? "Guide",
|
|
11282
|
-
items: item.items.map(toNavItem),
|
|
11283
|
-
collapsed: item.collapsed
|
|
11284
|
-
});
|
|
11285
|
-
} else looseItems.push(toNavItem(item));
|
|
11286
|
-
flushLooseItems();
|
|
11287
|
-
return groups;
|
|
10562
|
+
return require_mermaid.importNapiModuleSync().buildSsgThemeNavItems(sidebar, base, extension);
|
|
11288
10563
|
}
|
|
11289
10564
|
/**
|
|
11290
10565
|
* Builds all markdown files to static HTML.
|
|
@@ -11343,8 +10618,10 @@ async function buildSsg(options, root) {
|
|
|
11343
10618
|
transformedHtml = restoreMermaidSvgs(transformedHtml, mermaidSvgs);
|
|
11344
10619
|
const title = extractTitle$1(transformedHtml, result.frontmatter);
|
|
11345
10620
|
const description = result.frontmatter.description;
|
|
10621
|
+
const routePaths = getRoutePaths(inputPath, srcDir, outDir, base, ssgOptions.extension, ssgOptions.siteUrl);
|
|
11346
10622
|
pageResults.push({
|
|
11347
10623
|
inputPath,
|
|
10624
|
+
routePaths,
|
|
11348
10625
|
transformedHtml,
|
|
11349
10626
|
title,
|
|
11350
10627
|
description,
|
|
@@ -11353,7 +10630,6 @@ async function buildSsg(options, root) {
|
|
|
11353
10630
|
toc: result.toc
|
|
11354
10631
|
});
|
|
11355
10632
|
if (shouldGenerateOgImages) {
|
|
11356
|
-
const ogImageOutputPath = getOgImagePath(inputPath, srcDir, outDir);
|
|
11357
10633
|
const { layout: _layout, ...frontmatterRest } = result.frontmatter;
|
|
11358
10634
|
ogImageEntries.push({
|
|
11359
10635
|
props: {
|
|
@@ -11362,10 +10638,10 @@ async function buildSsg(options, root) {
|
|
|
11362
10638
|
description,
|
|
11363
10639
|
siteName
|
|
11364
10640
|
},
|
|
11365
|
-
outputPath:
|
|
10641
|
+
outputPath: routePaths.ogImagePath
|
|
11366
10642
|
});
|
|
11367
10643
|
ogImageInputPaths.push(inputPath);
|
|
11368
|
-
ogImageUrlMap.set(inputPath,
|
|
10644
|
+
ogImageUrlMap.set(inputPath, routePaths.ogImageUrl);
|
|
11369
10645
|
}
|
|
11370
10646
|
} catch (err) {
|
|
11371
10647
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -11394,7 +10670,7 @@ async function buildSsg(options, root) {
|
|
|
11394
10670
|
ogImageUrlMap.clear();
|
|
11395
10671
|
}
|
|
11396
10672
|
for (const pageResult of pageResults) try {
|
|
11397
|
-
const { inputPath, transformedHtml, title, description, lastUpdated, frontmatter, toc } = pageResult;
|
|
10673
|
+
const { inputPath, routePaths, transformedHtml, title, description, lastUpdated, frontmatter, toc } = pageResult;
|
|
11398
10674
|
let pageOgImage = ssgOptions.ogImage;
|
|
11399
10675
|
if (shouldGenerateOgImages && ogImageUrlMap.has(inputPath)) pageOgImage = ogImageUrlMap.get(inputPath);
|
|
11400
10676
|
let entryPage;
|
|
@@ -11412,16 +10688,15 @@ async function buildSsg(options, root) {
|
|
|
11412
10688
|
toc,
|
|
11413
10689
|
lastUpdated,
|
|
11414
10690
|
frontmatter,
|
|
11415
|
-
path:
|
|
11416
|
-
href:
|
|
10691
|
+
path: routePaths.urlPath,
|
|
10692
|
+
href: routePaths.href,
|
|
11417
10693
|
entryPage
|
|
11418
10694
|
};
|
|
11419
10695
|
html = await generateHtmlPage(pageData, navItems, siteName, base, pageOgImage, ssgOptions.theme, getPageLocale(pageData.path, options.i18n), options.i18n ? options.i18n.locales : void 0);
|
|
11420
10696
|
}
|
|
11421
|
-
const outputPath = getOutputPath(inputPath, srcDir, outDir, ssgOptions.extension);
|
|
11422
10697
|
generatedPages.push({
|
|
11423
10698
|
inputPath,
|
|
11424
|
-
outputPath,
|
|
10699
|
+
outputPath: routePaths.outputPath,
|
|
11425
10700
|
html
|
|
11426
10701
|
});
|
|
11427
10702
|
} catch (err) {
|
|
@@ -11549,234 +10824,7 @@ async function writeSearchIndex(indexJson, outDir) {
|
|
|
11549
10824
|
* This is injected into the bundle as a virtual module.
|
|
11550
10825
|
*/
|
|
11551
10826
|
function generateSearchModule(options, indexPath) {
|
|
11552
|
-
return
|
|
11553
|
-
// Search module generated by ox-content
|
|
11554
|
-
const searchOptions = ${JSON.stringify(options)};
|
|
11555
|
-
|
|
11556
|
-
let searchIndex = null;
|
|
11557
|
-
let indexPromise = null;
|
|
11558
|
-
|
|
11559
|
-
function parseScopedQuery(query) {
|
|
11560
|
-
const scopes = [];
|
|
11561
|
-
const terms = [];
|
|
11562
|
-
|
|
11563
|
-
for (const part of query.trim().split(/\\s+/).filter(Boolean)) {
|
|
11564
|
-
if (part.startsWith('@') && part.length > 1) {
|
|
11565
|
-
scopes.push(part.slice(1).toLowerCase());
|
|
11566
|
-
} else {
|
|
11567
|
-
terms.push(part);
|
|
11568
|
-
}
|
|
11569
|
-
}
|
|
11570
|
-
|
|
11571
|
-
return {
|
|
11572
|
-
text: terms.join(' ').trim(),
|
|
11573
|
-
scopes: [...new Set(scopes)],
|
|
11574
|
-
};
|
|
11575
|
-
}
|
|
11576
|
-
|
|
11577
|
-
function getScopesForDoc(doc) {
|
|
11578
|
-
const source = (doc.id || doc.url || '').replace(/^\\/+/, '').toLowerCase();
|
|
11579
|
-
const segments = source.split('/').filter(Boolean);
|
|
11580
|
-
|
|
11581
|
-
if (segments.length <= 1) {
|
|
11582
|
-
return [];
|
|
11583
|
-
}
|
|
11584
|
-
|
|
11585
|
-
const scopes = [];
|
|
11586
|
-
let current = '';
|
|
11587
|
-
for (const segment of segments.slice(0, -1)) {
|
|
11588
|
-
current = current ? current + '/' + segment : segment;
|
|
11589
|
-
scopes.push(current);
|
|
11590
|
-
}
|
|
11591
|
-
|
|
11592
|
-
return scopes;
|
|
11593
|
-
}
|
|
11594
|
-
|
|
11595
|
-
function matchesScopes(doc, scopes) {
|
|
11596
|
-
if (!scopes.length) {
|
|
11597
|
-
return true;
|
|
11598
|
-
}
|
|
11599
|
-
|
|
11600
|
-
const docScopes = new Set(getScopesForDoc(doc));
|
|
11601
|
-
return scopes.some(scope => docScopes.has(scope));
|
|
11602
|
-
}
|
|
11603
|
-
|
|
11604
|
-
// Tokenizer for queries
|
|
11605
|
-
function tokenizeQuery(text) {
|
|
11606
|
-
const tokens = [];
|
|
11607
|
-
let current = '';
|
|
11608
|
-
|
|
11609
|
-
for (const char of text) {
|
|
11610
|
-
const isCjk = /[\\u4E00-\\u9FFF\\u3400-\\u4DBF\\u3040-\\u309F\\u30A0-\\u30FF\\uAC00-\\uD7AF]/.test(char);
|
|
11611
|
-
|
|
11612
|
-
if (isCjk) {
|
|
11613
|
-
if (current) {
|
|
11614
|
-
tokens.push(current.toLowerCase());
|
|
11615
|
-
current = '';
|
|
11616
|
-
}
|
|
11617
|
-
tokens.push(char);
|
|
11618
|
-
} else if (/[a-zA-Z0-9_]/.test(char)) {
|
|
11619
|
-
current += char;
|
|
11620
|
-
} else if (current) {
|
|
11621
|
-
tokens.push(current.toLowerCase());
|
|
11622
|
-
current = '';
|
|
11623
|
-
}
|
|
11624
|
-
}
|
|
11625
|
-
|
|
11626
|
-
if (current) {
|
|
11627
|
-
tokens.push(current.toLowerCase());
|
|
11628
|
-
}
|
|
11629
|
-
|
|
11630
|
-
return tokens;
|
|
11631
|
-
}
|
|
11632
|
-
|
|
11633
|
-
// BM25 scoring
|
|
11634
|
-
function computeIdf(df, docCount) {
|
|
11635
|
-
return Math.log((docCount - df + 0.5) / (df + 0.5) + 1.0);
|
|
11636
|
-
}
|
|
11637
|
-
|
|
11638
|
-
function getFieldBoost(field) {
|
|
11639
|
-
switch (field) {
|
|
11640
|
-
case 'Title': return 10.0;
|
|
11641
|
-
case 'Heading': return 5.0;
|
|
11642
|
-
case 'Body': return 1.0;
|
|
11643
|
-
case 'Code': return 0.5;
|
|
11644
|
-
default: return 1.0;
|
|
11645
|
-
}
|
|
11646
|
-
}
|
|
11647
|
-
|
|
11648
|
-
// Load the index
|
|
11649
|
-
async function loadIndex() {
|
|
11650
|
-
if (searchIndex) return searchIndex;
|
|
11651
|
-
if (indexPromise) return indexPromise;
|
|
11652
|
-
|
|
11653
|
-
indexPromise = fetch('${indexPath}')
|
|
11654
|
-
.then(res => res.json())
|
|
11655
|
-
.then(data => {
|
|
11656
|
-
searchIndex = data;
|
|
11657
|
-
return data;
|
|
11658
|
-
})
|
|
11659
|
-
.catch(err => {
|
|
11660
|
-
console.error('[ox-content] Failed to load search index:', err);
|
|
11661
|
-
return null;
|
|
11662
|
-
});
|
|
11663
|
-
|
|
11664
|
-
return indexPromise;
|
|
11665
|
-
}
|
|
11666
|
-
|
|
11667
|
-
// Search function
|
|
11668
|
-
export async function search(query, options = {}) {
|
|
11669
|
-
const index = await loadIndex();
|
|
11670
|
-
|
|
11671
|
-
if (!index) {
|
|
11672
|
-
return [];
|
|
11673
|
-
}
|
|
11674
|
-
|
|
11675
|
-
const parsedQuery = parseScopedQuery(query);
|
|
11676
|
-
|
|
11677
|
-
if (!parsedQuery.text && parsedQuery.scopes.length === 0) {
|
|
11678
|
-
return [];
|
|
11679
|
-
}
|
|
11680
|
-
|
|
11681
|
-
const limit = options.limit ?? searchOptions.limit;
|
|
11682
|
-
const prefix = options.prefix ?? searchOptions.prefix;
|
|
11683
|
-
const tokens = tokenizeQuery(parsedQuery.text);
|
|
11684
|
-
|
|
11685
|
-
const k1 = 1.2;
|
|
11686
|
-
const b = 0.75;
|
|
11687
|
-
const docScores = new Map();
|
|
11688
|
-
|
|
11689
|
-
if (tokens.length === 0) {
|
|
11690
|
-
index.documents.forEach((doc, docIdx) => {
|
|
11691
|
-
if (matchesScopes(doc, parsedQuery.scopes)) {
|
|
11692
|
-
docScores.set(docIdx, { score: 0, matches: new Set() });
|
|
11693
|
-
}
|
|
11694
|
-
});
|
|
11695
|
-
}
|
|
11696
|
-
|
|
11697
|
-
for (let i = 0; i < tokens.length; i++) {
|
|
11698
|
-
const token = tokens[i];
|
|
11699
|
-
const isLast = i === tokens.length - 1;
|
|
11700
|
-
|
|
11701
|
-
// Find matching terms
|
|
11702
|
-
let matchingTerms = [];
|
|
11703
|
-
if (prefix && isLast && token.length >= 2) {
|
|
11704
|
-
matchingTerms = Object.keys(index.index).filter(term => term.startsWith(token));
|
|
11705
|
-
} else if (index.index[token]) {
|
|
11706
|
-
matchingTerms = [token];
|
|
11707
|
-
}
|
|
11708
|
-
|
|
11709
|
-
for (const term of matchingTerms) {
|
|
11710
|
-
const postings = index.index[term] || [];
|
|
11711
|
-
const df = index.df[term] || 1;
|
|
11712
|
-
const idf = computeIdf(df, index.doc_count);
|
|
11713
|
-
|
|
11714
|
-
for (const posting of postings) {
|
|
11715
|
-
const doc = index.documents[posting.doc_idx];
|
|
11716
|
-
if (!doc) continue;
|
|
11717
|
-
if (!matchesScopes(doc, parsedQuery.scopes)) continue;
|
|
11718
|
-
|
|
11719
|
-
const docLen = doc.body.length;
|
|
11720
|
-
const tf = posting.tf;
|
|
11721
|
-
const boost = getFieldBoost(posting.field);
|
|
11722
|
-
|
|
11723
|
-
const score = idf * ((tf * (k1 + 1.0)) / (tf + k1 * (1.0 - b + b * docLen / index.avg_dl))) * boost;
|
|
11724
|
-
|
|
11725
|
-
if (!docScores.has(posting.doc_idx)) {
|
|
11726
|
-
docScores.set(posting.doc_idx, { score: 0, matches: new Set() });
|
|
11727
|
-
}
|
|
11728
|
-
const entry = docScores.get(posting.doc_idx);
|
|
11729
|
-
entry.score += score;
|
|
11730
|
-
entry.matches.add(term);
|
|
11731
|
-
}
|
|
11732
|
-
}
|
|
11733
|
-
}
|
|
11734
|
-
|
|
11735
|
-
// Convert to results
|
|
11736
|
-
const results = Array.from(docScores.entries())
|
|
11737
|
-
.map(([docIdx, data]) => {
|
|
11738
|
-
const doc = index.documents[docIdx];
|
|
11739
|
-
const matches = Array.from(data.matches);
|
|
11740
|
-
const scopes = getScopesForDoc(doc);
|
|
11741
|
-
|
|
11742
|
-
// Generate snippet
|
|
11743
|
-
let snippet = '';
|
|
11744
|
-
if (doc.body) {
|
|
11745
|
-
const bodyLower = doc.body.toLowerCase();
|
|
11746
|
-
let firstPos = -1;
|
|
11747
|
-
for (const match of matches) {
|
|
11748
|
-
const pos = bodyLower.indexOf(match);
|
|
11749
|
-
if (pos !== -1 && (firstPos === -1 || pos < firstPos)) {
|
|
11750
|
-
firstPos = pos;
|
|
11751
|
-
}
|
|
11752
|
-
}
|
|
11753
|
-
|
|
11754
|
-
const start = firstPos === -1 ? 0 : Math.max(0, firstPos - 50);
|
|
11755
|
-
const end = Math.min(doc.body.length, start + 150);
|
|
11756
|
-
snippet = doc.body.slice(start, end);
|
|
11757
|
-
if (start > 0) snippet = '...' + snippet;
|
|
11758
|
-
if (end < doc.body.length) snippet = snippet + '...';
|
|
11759
|
-
}
|
|
11760
|
-
|
|
11761
|
-
return {
|
|
11762
|
-
id: doc.id,
|
|
11763
|
-
title: doc.title,
|
|
11764
|
-
url: doc.url,
|
|
11765
|
-
score: data.score,
|
|
11766
|
-
matches,
|
|
11767
|
-
snippet,
|
|
11768
|
-
scopes,
|
|
11769
|
-
};
|
|
11770
|
-
})
|
|
11771
|
-
.sort((a, b) => b.score - a.score || a.title.localeCompare(b.title))
|
|
11772
|
-
.slice(0, limit);
|
|
11773
|
-
|
|
11774
|
-
return results;
|
|
11775
|
-
}
|
|
11776
|
-
|
|
11777
|
-
export { searchOptions };
|
|
11778
|
-
export default { search, searchOptions, loadIndex };
|
|
11779
|
-
`;
|
|
10827
|
+
return require_mermaid.importNapiModuleSync().generateSearchModule(JSON.stringify(options), indexPath);
|
|
11780
10828
|
}
|
|
11781
10829
|
//#endregion
|
|
11782
10830
|
//#region src/dev-server.ts
|
|
@@ -12432,193 +11480,18 @@ function createI18nPlugin(resolvedOptions) {
|
|
|
12432
11480
|
*/
|
|
12433
11481
|
function generateI18nModule(options, root) {
|
|
12434
11482
|
const dictDir = path.resolve(root, options.dir);
|
|
12435
|
-
const
|
|
12436
|
-
|
|
12437
|
-
|
|
11483
|
+
const config = {
|
|
11484
|
+
defaultLocale: options.defaultLocale,
|
|
11485
|
+
locales: options.locales,
|
|
11486
|
+
hideDefaultLocale: options.hideDefaultLocale
|
|
11487
|
+
};
|
|
12438
11488
|
try {
|
|
12439
11489
|
const napi = require("@ox-content/napi");
|
|
12440
|
-
if (napi.
|
|
12441
|
-
|
|
12442
|
-
|
|
12443
|
-
} else dictionariesCode = JSON.stringify(loadDictionariesFallback(options, dictDir));
|
|
12444
|
-
} catch {
|
|
12445
|
-
try {
|
|
12446
|
-
dictionariesCode = JSON.stringify(loadDictionariesFallback(options, dictDir));
|
|
12447
|
-
} catch {}
|
|
12448
|
-
}
|
|
12449
|
-
return `
|
|
12450
|
-
export const i18nConfig = {
|
|
12451
|
-
enabled: true,
|
|
12452
|
-
defaultLocale: ${defaultLocale},
|
|
12453
|
-
locales: ${localesJson},
|
|
12454
|
-
hideDefaultLocale: ${JSON.stringify(options.hideDefaultLocale)},
|
|
12455
|
-
};
|
|
12456
|
-
|
|
12457
|
-
export const dictionaries = ${dictionariesCode};
|
|
12458
|
-
|
|
12459
|
-
export function t(key, params, locale) {
|
|
12460
|
-
const dict = dictionaries[locale || i18nConfig.defaultLocale] || {};
|
|
12461
|
-
let message = dict[key];
|
|
12462
|
-
if (!message) {
|
|
12463
|
-
const fallback = dictionaries[i18nConfig.defaultLocale] || {};
|
|
12464
|
-
message = fallback[key] || key;
|
|
12465
|
-
}
|
|
12466
|
-
if (params) {
|
|
12467
|
-
for (const [k, v] of Object.entries(params)) {
|
|
12468
|
-
message = message.split('{$' + k + '}').join(String(v));
|
|
12469
|
-
}
|
|
12470
|
-
}
|
|
12471
|
-
return message;
|
|
12472
|
-
}
|
|
12473
|
-
|
|
12474
|
-
export function getLocaleFromPath(pathname) {
|
|
12475
|
-
const match = pathname.match(new RegExp('^/([A-Za-z]{2,3}(?:-[A-Za-z0-9]+)*)(/|$)'));
|
|
12476
|
-
if (match) {
|
|
12477
|
-
const code = match[1];
|
|
12478
|
-
if (i18nConfig.locales.some(l => l.code === code)) {
|
|
12479
|
-
return code;
|
|
12480
|
-
}
|
|
12481
|
-
}
|
|
12482
|
-
return i18nConfig.defaultLocale;
|
|
12483
|
-
}
|
|
12484
|
-
|
|
12485
|
-
export function localePath(pathname, locale) {
|
|
12486
|
-
const current = getLocaleFromPath(pathname);
|
|
12487
|
-
let clean = pathname;
|
|
12488
|
-
if (current !== i18nConfig.defaultLocale || !i18nConfig.hideDefaultLocale) {
|
|
12489
|
-
const prefix = '/' + current;
|
|
12490
|
-
if (clean === prefix) clean = '/';
|
|
12491
|
-
else if (clean.startsWith(prefix + '/')) clean = clean.slice(prefix.length);
|
|
12492
|
-
}
|
|
12493
|
-
if (locale === i18nConfig.defaultLocale && i18nConfig.hideDefaultLocale) {
|
|
12494
|
-
return clean || '/';
|
|
12495
|
-
}
|
|
12496
|
-
return '/' + locale + (clean.startsWith('/') ? clean : '/' + clean);
|
|
12497
|
-
}
|
|
12498
|
-
|
|
12499
|
-
const formatterCache = new Map();
|
|
12500
|
-
|
|
12501
|
-
function getFormatter(kind, locale, options) {
|
|
12502
|
-
const key = kind + ':' + locale + ':' + JSON.stringify(options || {});
|
|
12503
|
-
if (!formatterCache.has(key)) {
|
|
12504
|
-
formatterCache.set(key, new Intl[kind](locale, options));
|
|
12505
|
-
}
|
|
12506
|
-
return formatterCache.get(key);
|
|
12507
|
-
}
|
|
12508
|
-
|
|
12509
|
-
export function getLocaleMeta(locale) {
|
|
12510
|
-
const code = locale || i18nConfig.defaultLocale;
|
|
12511
|
-
return i18nConfig.locales.find(l => l.code === code) || { code, name: code, dir: 'ltr' };
|
|
12512
|
-
}
|
|
12513
|
-
|
|
12514
|
-
export function formatDate(value, options, locale) {
|
|
12515
|
-
return getFormatter('DateTimeFormat', locale || i18nConfig.defaultLocale, options).format(
|
|
12516
|
-
value instanceof Date ? value : new Date(value),
|
|
12517
|
-
);
|
|
12518
|
-
}
|
|
12519
|
-
|
|
12520
|
-
export function formatDateParts(value, options, locale) {
|
|
12521
|
-
return getFormatter('DateTimeFormat', locale || i18nConfig.defaultLocale, options).formatToParts(
|
|
12522
|
-
value instanceof Date ? value : new Date(value),
|
|
12523
|
-
);
|
|
12524
|
-
}
|
|
12525
|
-
|
|
12526
|
-
export function formatNumber(value, options, locale) {
|
|
12527
|
-
return getFormatter('NumberFormat', locale || i18nConfig.defaultLocale, options).format(value);
|
|
12528
|
-
}
|
|
12529
|
-
|
|
12530
|
-
export function formatNumberParts(value, options, locale) {
|
|
12531
|
-
return getFormatter('NumberFormat', locale || i18nConfig.defaultLocale, options).formatToParts(value);
|
|
12532
|
-
}
|
|
12533
|
-
|
|
12534
|
-
export function formatRelativeTime(value, unit, options, locale) {
|
|
12535
|
-
return getFormatter('RelativeTimeFormat', locale || i18nConfig.defaultLocale, options).format(value, unit);
|
|
12536
|
-
}
|
|
12537
|
-
|
|
12538
|
-
export function formatList(values, options, locale) {
|
|
12539
|
-
return getFormatter('ListFormat', locale || i18nConfig.defaultLocale, options).format(values);
|
|
12540
|
-
}
|
|
12541
|
-
|
|
12542
|
-
export function formatListParts(values, options, locale) {
|
|
12543
|
-
return getFormatter('ListFormat', locale || i18nConfig.defaultLocale, options).formatToParts(values);
|
|
12544
|
-
}
|
|
12545
|
-
|
|
12546
|
-
export function formatDisplayName(value, type, options, locale) {
|
|
12547
|
-
if (!Intl.DisplayNames) return String(value);
|
|
12548
|
-
const displayType = type || 'language';
|
|
12549
|
-
return getFormatter('DisplayNames', locale || i18nConfig.defaultLocale, { type: displayType, ...options }).of(value) || String(value);
|
|
12550
|
-
}
|
|
12551
|
-
|
|
12552
|
-
export function createIntl(locale, defaults = {}) {
|
|
12553
|
-
const meta = getLocaleMeta(locale);
|
|
12554
|
-
const code = meta.code;
|
|
12555
|
-
return {
|
|
12556
|
-
locale: code,
|
|
12557
|
-
meta,
|
|
12558
|
-
dir: meta.dir || 'ltr',
|
|
12559
|
-
date: (value, options) => formatDate(value, { ...defaults.date, ...options }, code),
|
|
12560
|
-
dateParts: (value, options) => formatDateParts(value, { ...defaults.date, ...options }, code),
|
|
12561
|
-
number: (value, options) => formatNumber(value, { ...defaults.number, ...options }, code),
|
|
12562
|
-
numberParts: (value, options) => formatNumberParts(value, { ...defaults.number, ...options }, code),
|
|
12563
|
-
relativeTime: (value, unit, options) => formatRelativeTime(value, unit, { ...defaults.relativeTime, ...options }, code),
|
|
12564
|
-
list: (values, options) => formatList(values, { ...defaults.list, ...options }, code),
|
|
12565
|
-
listParts: (values, options) => formatListParts(values, { ...defaults.list, ...options }, code),
|
|
12566
|
-
displayName: (value, type, options) => formatDisplayName(value, type, { ...defaults.displayName, ...options }, code),
|
|
12567
|
-
};
|
|
12568
|
-
}
|
|
12569
|
-
|
|
12570
|
-
export default {
|
|
12571
|
-
i18nConfig,
|
|
12572
|
-
dictionaries,
|
|
12573
|
-
t,
|
|
12574
|
-
getLocaleFromPath,
|
|
12575
|
-
localePath,
|
|
12576
|
-
getLocaleMeta,
|
|
12577
|
-
createIntl,
|
|
12578
|
-
formatDate,
|
|
12579
|
-
formatDateParts,
|
|
12580
|
-
formatNumber,
|
|
12581
|
-
formatNumberParts,
|
|
12582
|
-
formatRelativeTime,
|
|
12583
|
-
formatList,
|
|
12584
|
-
formatListParts,
|
|
12585
|
-
formatDisplayName,
|
|
12586
|
-
};
|
|
12587
|
-
`;
|
|
12588
|
-
}
|
|
12589
|
-
/**
|
|
12590
|
-
* Flattens a nested object into dot-separated keys.
|
|
12591
|
-
*/
|
|
12592
|
-
function flattenObject(obj, prefix, result) {
|
|
12593
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
12594
|
-
const fullKey = `${prefix}.${key}`;
|
|
12595
|
-
if (typeof value === "string") result[fullKey] = value;
|
|
12596
|
-
else if (typeof value === "object" && value !== null && !Array.isArray(value)) flattenObject(value, fullKey, result);
|
|
12597
|
-
else result[fullKey] = String(value);
|
|
12598
|
-
}
|
|
12599
|
-
}
|
|
12600
|
-
/**
|
|
12601
|
-
* Fallback dictionary loading using TS-based JSON file reading.
|
|
12602
|
-
*/
|
|
12603
|
-
function loadDictionariesFallback(options, dictDir) {
|
|
12604
|
-
const dictData = {};
|
|
12605
|
-
for (const locale of options.locales) {
|
|
12606
|
-
const localeDir = path.join(dictDir, locale.code);
|
|
12607
|
-
if (!fs.existsSync(localeDir)) continue;
|
|
12608
|
-
const files = fs.readdirSync(localeDir);
|
|
12609
|
-
const localeDict = {};
|
|
12610
|
-
for (const file of files) {
|
|
12611
|
-
if (!file.endsWith(".json")) continue;
|
|
12612
|
-
const filePath = path.join(localeDir, file);
|
|
12613
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
12614
|
-
const namespace = path.basename(file, ".json");
|
|
12615
|
-
try {
|
|
12616
|
-
flattenObject(JSON.parse(content), namespace, localeDict);
|
|
12617
|
-
} catch {}
|
|
12618
|
-
}
|
|
12619
|
-
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)}`);
|
|
12620
11493
|
}
|
|
12621
|
-
|
|
11494
|
+
throw new Error("[ox-content:i18n] @ox-content/napi does not expose generateI18nModule. Please rebuild the NAPI package.");
|
|
12622
11495
|
}
|
|
12623
11496
|
/**
|
|
12624
11497
|
* Collects translation keys from source files using NAPI extractTranslationKeys.
|
|
@@ -13814,9 +12687,9 @@ function resolveCodeAnnotationsOptions(options) {
|
|
|
13814
12687
|
/**
|
|
13815
12688
|
* Generates virtual module content.
|
|
13816
12689
|
*/
|
|
13817
|
-
function generateVirtualModule(path$
|
|
13818
|
-
if (path$
|
|
13819
|
-
if (path$
|
|
12690
|
+
function generateVirtualModule(path$1, options) {
|
|
12691
|
+
if (path$1 === "config") return `export default ${JSON.stringify(options)};`;
|
|
12692
|
+
if (path$1 === "runtime") {
|
|
13820
12693
|
const base = normalizeRuntimeBase(options.base);
|
|
13821
12694
|
return `
|
|
13822
12695
|
export const base = ${JSON.stringify(base)};
|