@docubook/flame 2.0.0-beta.0 → 2.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.docu/components/ScrollTo.tsx +17 -11
- package/.docu/components/Toc.tsx +32 -7
- package/.docu/lib/build.deno.js +6 -9
- package/.docu/lib/build.deno.js.map +1 -0
- package/.docu/lib/build.impl-BJsi28mD.js +377 -0
- package/.docu/lib/build.impl-BJsi28mD.js.map +1 -0
- package/.docu/lib/build.impl-yVLaa1eQ.js +2 -0
- package/.docu/lib/build.node.js +6 -9
- package/.docu/lib/build.node.js.map +1 -0
- package/.docu/lib/clean.js +25 -25
- package/.docu/lib/clean.js.map +1 -0
- package/.docu/lib/deno-DmEaKggj.js +20 -0
- package/.docu/lib/deno-DmEaKggj.js.map +1 -0
- package/.docu/lib/deploy.deno.js +9 -9
- package/.docu/lib/deploy.deno.js.map +1 -0
- package/.docu/lib/deploy.node.js +8 -8
- package/.docu/lib/deploy.node.js.map +1 -0
- package/.docu/lib/deploy.shared-D3UWafa6.js +295 -0
- package/.docu/lib/deploy.shared-D3UWafa6.js.map +1 -0
- package/.docu/lib/html.shared-DSn-7_6t.js +2467 -0
- package/.docu/lib/html.shared-DSn-7_6t.js.map +1 -0
- package/.docu/lib/logger-CycvLCrQ.js +312 -0
- package/.docu/lib/logger-CycvLCrQ.js.map +1 -0
- package/.docu/lib/node-DPTySdwG.js +80 -0
- package/.docu/lib/node-DPTySdwG.js.map +1 -0
- package/.docu/lib/paths-BtOIPBQ9.js +52 -0
- package/.docu/lib/paths-BtOIPBQ9.js.map +1 -0
- package/.docu/lib/preview.deno.js +7 -11
- package/.docu/lib/preview.deno.js.map +1 -0
- package/.docu/lib/preview.impl-CXJS2tQS.js +77 -0
- package/.docu/lib/preview.impl-CXJS2tQS.js.map +1 -0
- package/.docu/lib/preview.node.js +7 -11
- package/.docu/lib/preview.node.js.map +1 -0
- package/.docu/lib/server.deno.js +7 -12
- package/.docu/lib/server.deno.js.map +1 -0
- package/.docu/lib/server.impl-CJuWimfN.js +358 -0
- package/.docu/lib/server.impl-CJuWimfN.js.map +1 -0
- package/.docu/lib/server.node.js +7 -12
- package/.docu/lib/server.node.js.map +1 -0
- package/.docu/lib/utils-DA17MyQG.js +167 -0
- package/.docu/lib/utils-DA17MyQG.js.map +1 -0
- package/.docu/node/hydrate.node.ts +116 -166
- package/bin/compile-lib.mjs +30 -28
- package/package.json +6 -6
- package/.docu/lib/build.impl-S4DS5XPH.js +0 -12
- package/.docu/lib/chunk-4IQXHHPF.js +0 -62
- package/.docu/lib/chunk-C3RCUTUE.js +0 -470
- package/.docu/lib/chunk-D3QTSBR4.js +0 -434
- package/.docu/lib/chunk-EOK6KATZ.js +0 -191
- package/.docu/lib/chunk-IVM5UM44.js +0 -301
- package/.docu/lib/chunk-JMQI3FKV.js +0 -92
- package/.docu/lib/chunk-LXJLSXQI.js +0 -2691
- package/.docu/lib/chunk-O326MYJE.js +0 -330
- package/.docu/lib/chunk-UISOJ4RW.js +0 -114
|
@@ -0,0 +1,2467 @@
|
|
|
1
|
+
import { a as DOCS_DIR, c as PROJECT_ROOT, d as loadDocuConfig, l as STYLES_DIR, o as FRAMEWORK_ROOT, s as LIB_DIR, t as ASSETS_DIR, u as cleanOldBundles } from "./paths-BtOIPBQ9.js";
|
|
2
|
+
import { a as isExternalUrl, h as normalizeImporterPath, n as docsHtmlHref, o as scanMdxFiles, r as formatDate2, t as cn } from "./utils-DA17MyQG.js";
|
|
3
|
+
import { builtinModules, createRequire } from "node:module";
|
|
4
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
5
|
+
import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
|
|
6
|
+
import { mkdir, readFile, stat, unlink, writeFile } from "node:fs/promises";
|
|
7
|
+
import { execFile } from "node:child_process";
|
|
8
|
+
import { promisify } from "node:util";
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
import { build } from "vite";
|
|
11
|
+
import { generateThemeCss, presetRegistry, resolveTheme } from "@docubook/themes-colors";
|
|
12
|
+
import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
import { MDXRemote, createDefaultRehypePlugins, createDefaultRemarkPlugins, extractFrontmatter, extractFrontmatterWithContent, extractTocsFromRawMdx, serialize } from "@docubook/core";
|
|
15
|
+
import { createMdxComponents } from "@docubook/markdown";
|
|
16
|
+
import * as LucideIcons from "lucide-react";
|
|
17
|
+
import { ArrowUpIcon, ArrowUpRight, ChevronDown, ChevronLeft, ChevronRight, ListIcon, SquarePen } from "lucide-react";
|
|
18
|
+
import { Breadcrumb, BreadcrumbItem, BreadcrumbList, BreadcrumbPage } from "@docubook/ui-react/breadcrumbs";
|
|
19
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
20
|
+
import { PaginationDocs } from "@docubook/ui-react/pagination";
|
|
21
|
+
//#region .docu/node/plugin-loader.ts
|
|
22
|
+
var NPM_PACKAGE_RE = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
23
|
+
/**
|
|
24
|
+
* Resolve a plugin specifier to an absolute path or npm package name.
|
|
25
|
+
*
|
|
26
|
+
* Resolution rules:
|
|
27
|
+
* 1. Relative path (starts with `.`) → resolve from project root, guard traversal
|
|
28
|
+
* 2. Absolute path (starts with `/`) → guard traversal
|
|
29
|
+
* 3. Anything else → validate as npm package name, then pass to Bun's import
|
|
30
|
+
*
|
|
31
|
+
* Path traversal protection:
|
|
32
|
+
* - All file-system paths (relative & absolute) must resolve within PROJECT_ROOT.
|
|
33
|
+
* - This prevents `../../sensitive-file` or `/etc/passwd` from being imported.
|
|
34
|
+
*/
|
|
35
|
+
/** @internal Exported for testing only. */
|
|
36
|
+
function resolveSpecifier(specifier) {
|
|
37
|
+
let resolved;
|
|
38
|
+
if (specifier.startsWith(".")) resolved = resolve(PROJECT_ROOT, specifier);
|
|
39
|
+
else if (specifier.startsWith("/")) resolved = specifier;
|
|
40
|
+
else {
|
|
41
|
+
if (!NPM_PACKAGE_RE.test(specifier)) throw new Error(`[plugin-loader] Invalid plugin specifier "${specifier}": must be a valid npm package name, relative path, or absolute path`);
|
|
42
|
+
return specifier;
|
|
43
|
+
}
|
|
44
|
+
const root = PROJECT_ROOT.endsWith("/") ? PROJECT_ROOT : PROJECT_ROOT + "/";
|
|
45
|
+
if (!resolved.startsWith(root)) throw new Error(`[plugin-loader] Path traversal blocked: "${specifier}" resolves outside project root`);
|
|
46
|
+
return resolved;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Load and instantiate all plugins from a config entries array.
|
|
50
|
+
*
|
|
51
|
+
* @param entries - Array of plugin entries from `docu.json`.
|
|
52
|
+
* Each entry is either:
|
|
53
|
+
* - a `string` (plugin specifier, no options)
|
|
54
|
+
* - a `[string, object]` tuple (plugin specifier + factory options)
|
|
55
|
+
* @returns Array of resolved `DocuBookPlugin` instances.
|
|
56
|
+
*
|
|
57
|
+
* @throws If a plugin specifier cannot be imported.
|
|
58
|
+
* @throws If a plugin's default export lacks a `name` property.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* const plugins = await loadPlugins([
|
|
62
|
+
* "@docubook/plugin-sitemap",
|
|
63
|
+
* ["@docubook/plugin-analytics", { id: "G-XXXXXXX" }],
|
|
64
|
+
* "./plugins/local-reading-time",
|
|
65
|
+
* ]);
|
|
66
|
+
*/
|
|
67
|
+
async function loadPlugins(entries = []) {
|
|
68
|
+
const plugins = [];
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
const [specifier, options] = Array.isArray(entry) ? entry : [entry, void 0];
|
|
71
|
+
const resolved = resolveSpecifier(specifier);
|
|
72
|
+
let mod;
|
|
73
|
+
try {
|
|
74
|
+
mod = await import(resolved);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
77
|
+
throw new Error(`[plugin-loader] Failed to import plugin "${specifier}": ${message}`, { cause: err });
|
|
78
|
+
}
|
|
79
|
+
const exported = mod.default;
|
|
80
|
+
let plugin;
|
|
81
|
+
if (typeof exported === "function") try {
|
|
82
|
+
plugin = exported(options);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
85
|
+
throw new Error(`[plugin-loader] Plugin factory "${specifier}" threw during initialization: ${message}`, { cause: err });
|
|
86
|
+
}
|
|
87
|
+
else if (exported && typeof exported === "object") plugin = exported;
|
|
88
|
+
else throw new Error(`[plugin-loader] Plugin "${specifier}" must export a default function or object. Got: ${typeof exported}`);
|
|
89
|
+
if (!plugin.name || typeof plugin.name !== "string") throw new Error(`[plugin-loader] Plugin "${specifier}" must have a valid 'name' property (string). Got: ${typeof plugin.name}`);
|
|
90
|
+
if (typeof plugin.setup !== "function") throw new Error(`[plugin-loader] Plugin "${specifier}" (name: "${plugin.name}") must have a 'setup(build)' function.`);
|
|
91
|
+
plugins.push(plugin);
|
|
92
|
+
}
|
|
93
|
+
return plugins;
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region .docu/node/plugin-builder.ts
|
|
97
|
+
var BuildPluginBuilder = class {
|
|
98
|
+
config;
|
|
99
|
+
_handleRequest = [];
|
|
100
|
+
_injectBody = [];
|
|
101
|
+
_injectHead = [];
|
|
102
|
+
_onEnd = [];
|
|
103
|
+
_onLoad = [];
|
|
104
|
+
_onStart = [];
|
|
105
|
+
_rehypePlugins = [];
|
|
106
|
+
_remarkPlugins = [];
|
|
107
|
+
_transformFrontmatter = [];
|
|
108
|
+
_transformHtml = [];
|
|
109
|
+
constructor(config) {
|
|
110
|
+
this.config = config;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Collect and deduplicate all `<body>` injection snippets from registered plugins.
|
|
114
|
+
* Each callback is executed in registration order; plugin errors are wrapped
|
|
115
|
+
* with a descriptive message.
|
|
116
|
+
*
|
|
117
|
+
* @param context - Current page context passed to each injectBody callback.
|
|
118
|
+
* @returns Deduplicated array of HTML strings to inject before `</body>`.
|
|
119
|
+
* @throws Error if any injectBody callback throws — wraps original error as cause.
|
|
120
|
+
*/
|
|
121
|
+
collectBody(context) {
|
|
122
|
+
const items = [];
|
|
123
|
+
for (const cb of this._injectBody) try {
|
|
124
|
+
const result = cb(context);
|
|
125
|
+
if (result) this.collectItems(items, result, "injectBody");
|
|
126
|
+
} catch (err) {
|
|
127
|
+
throw new Error(`[plugin] injectBody callback failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
128
|
+
}
|
|
129
|
+
return [...new Set(items)];
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Collect and deduplicate all `<head>` injection snippets from registered plugins.
|
|
133
|
+
* Each callback is executed in registration order; plugin errors are wrapped
|
|
134
|
+
* with a descriptive message.
|
|
135
|
+
*
|
|
136
|
+
* @param context - Current page context passed to each injectHead callback.
|
|
137
|
+
* @returns Deduplicated array of HTML strings to inject before `</head>`.
|
|
138
|
+
* @throws Error if any injectHead callback throws — wraps original error as cause.
|
|
139
|
+
*/
|
|
140
|
+
collectHead(context) {
|
|
141
|
+
const items = [];
|
|
142
|
+
for (const cb of this._injectHead) try {
|
|
143
|
+
const result = cb(context);
|
|
144
|
+
if (result) this.collectItems(items, result, "injectHead");
|
|
145
|
+
} catch (err) {
|
|
146
|
+
throw new Error(`[plugin] injectHead callback failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
147
|
+
}
|
|
148
|
+
return [...new Set(items)];
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Collect all rehype plugin arrays from registered rehypePlugins callbacks.
|
|
152
|
+
* Results from all plugins are flattened into a single array.
|
|
153
|
+
*
|
|
154
|
+
* @returns Flattened array of rehype plugin instances applied after default set.
|
|
155
|
+
* @throws Error if any rehypePlugins callback throws — wraps original error as cause.
|
|
156
|
+
*/
|
|
157
|
+
collectRehypePlugins() {
|
|
158
|
+
const plugins = [];
|
|
159
|
+
for (const cb of this._rehypePlugins) try {
|
|
160
|
+
plugins.push(...cb());
|
|
161
|
+
} catch (err) {
|
|
162
|
+
throw new Error(`[plugin] rehypePlugins callback failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
163
|
+
}
|
|
164
|
+
return plugins;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Collect all remark plugin arrays from registered remarkPlugins callbacks.
|
|
168
|
+
* Results from all plugins are flattened into a single array.
|
|
169
|
+
*
|
|
170
|
+
* @returns Flattened array of remark plugin instances applied after default set.
|
|
171
|
+
* @throws Error if any remarkPlugins callback throws — wraps original error as cause.
|
|
172
|
+
*/
|
|
173
|
+
collectRemarkPlugins() {
|
|
174
|
+
const plugins = [];
|
|
175
|
+
for (const cb of this._remarkPlugins) try {
|
|
176
|
+
plugins.push(...cb());
|
|
177
|
+
} catch (err) {
|
|
178
|
+
throw new Error(`[plugin] remarkPlugins callback failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
179
|
+
}
|
|
180
|
+
return plugins;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Register a callback to intercept incoming requests during development.
|
|
184
|
+
* The **first** callback to return a `Response` short-circuits all subsequent handlers.
|
|
185
|
+
* Errors inside callbacks are caught and logged — execution continues to next handler.
|
|
186
|
+
*
|
|
187
|
+
* @param callback - Receives the Request and dev server context. Return Response or void.
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* build.handleRequest((req, ctx) => {
|
|
191
|
+
* if (new URL(req.url).pathname === "/api/status") {
|
|
192
|
+
* return new Response(JSON.stringify({ ok: true }), {
|
|
193
|
+
* headers: { "Content-Type": "application/json" },
|
|
194
|
+
* });
|
|
195
|
+
* }
|
|
196
|
+
* });
|
|
197
|
+
*/
|
|
198
|
+
handleRequest(callback) {
|
|
199
|
+
this._handleRequest.push(callback);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Register a callback that returns HTML strings to inject before `</body>`.
|
|
203
|
+
* Results from all plugins are merged, deduplicated, and served via `collectBody()`.
|
|
204
|
+
*
|
|
205
|
+
* @param callback - Returns a single HTML string or an array. Called once per page.
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* build.injectBody(() => `<div id="chat-widget"></div>`);
|
|
209
|
+
*/
|
|
210
|
+
injectBody(callback) {
|
|
211
|
+
this._injectBody.push(callback);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Register a callback that returns HTML strings to inject inside `<head>`.
|
|
215
|
+
* Results from all plugins are merged, deduplicated, and served via `collectHead()`.
|
|
216
|
+
*
|
|
217
|
+
* @param callback - Returns a single HTML string or an array. Called once per page.
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* build.injectHead(() => `<script async src="https://cdn.example.com/analytics.js"><\/script>`);
|
|
221
|
+
*/
|
|
222
|
+
injectHead(callback) {
|
|
223
|
+
this._injectHead.push(callback);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Register a callback to run once after all pages are built.
|
|
227
|
+
* Receives the resolved config and aggregated page metadata.
|
|
228
|
+
* Errors thrown by the callback propagate to the caller via `runOnEnd()`.
|
|
229
|
+
*
|
|
230
|
+
* @param callback - Receives config and page metadata array. May return a Promise.
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* build.onEnd(async (config, pages) => {
|
|
234
|
+
* const xml = generateSitemap(pages, config.meta.baseURL);
|
|
235
|
+
* const out = ".docu/dist/sitemap.xml";
|
|
236
|
+
* // Bun.write on Bun for speed, writeFile on Node/Deno
|
|
237
|
+
* await (typeof Bun !== "undefined"
|
|
238
|
+
* ? Bun.write(out, xml)
|
|
239
|
+
* : writeFile(out, xml));
|
|
240
|
+
* });
|
|
241
|
+
*/
|
|
242
|
+
onEnd(callback) {
|
|
243
|
+
this._onEnd.push(callback);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Register a callback to transform raw file content before MDX compilation.
|
|
247
|
+
* Filtered by regex against the file's relative path — only the **first** matching
|
|
248
|
+
* handler's result is used.
|
|
249
|
+
* Errors thrown by the callback propagate to the caller via `runOnLoad()`.
|
|
250
|
+
*
|
|
251
|
+
* @param args.filter - RegExp matched against the file's relative path.
|
|
252
|
+
* @param args.namespace - Optional namespace prefix (reserved for future use).
|
|
253
|
+
* @param callback - Receives file path and raw content. Return new contents or void.
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* build.onLoad({ filter: /\.md$/ }, ({ path, content }) => {
|
|
257
|
+
* return { contents: `<!-- preprocessed -->\n${content}`, loader: "mdx" };
|
|
258
|
+
* });
|
|
259
|
+
*/
|
|
260
|
+
onLoad(args, callback) {
|
|
261
|
+
this._onLoad.push({
|
|
262
|
+
...args,
|
|
263
|
+
fn: callback
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Register a callback to run once before the build starts.
|
|
268
|
+
* Receives the resolved DocuConfig for validation or resource initialization.
|
|
269
|
+
* Errors thrown by the callback propagate to the caller via `runOnStart()`.
|
|
270
|
+
*
|
|
271
|
+
* @param callback - Receives the resolved config. May return a Promise.
|
|
272
|
+
*
|
|
273
|
+
* @example
|
|
274
|
+
* build.onStart((config) => {
|
|
275
|
+
* if (!config.meta.baseURL) throw new Error("baseURL required");
|
|
276
|
+
* });
|
|
277
|
+
*/
|
|
278
|
+
onStart(callback) {
|
|
279
|
+
this._onStart.push(callback);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Register additional rehype (HTML) plugins for the MDX compilation pipeline.
|
|
283
|
+
* Results from all plugins are merged and applied **after** the default set.
|
|
284
|
+
*
|
|
285
|
+
* @param callback - Returns an array of rehype plugins.
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* build.rehypePlugins(() => [require("rehype-autolink-headings")]);
|
|
289
|
+
*/
|
|
290
|
+
rehypePlugins(callback) {
|
|
291
|
+
this._rehypePlugins.push(callback);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Register additional remark (Markdown) plugins for the MDX compilation pipeline.
|
|
295
|
+
* Results from all plugins are merged and applied **after** the default set.
|
|
296
|
+
*
|
|
297
|
+
* @param callback - Returns an array of remark plugins.
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* build.remarkPlugins(() => [require("remark-custom-heading-id")]);
|
|
301
|
+
*/
|
|
302
|
+
remarkPlugins(callback) {
|
|
303
|
+
this._remarkPlugins.push(callback);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Execute all registered handleRequest callbacks sequentially.
|
|
307
|
+
* Stops and returns the **first** `Response` returned by any callback.
|
|
308
|
+
* Errors inside individual callbacks are caught and logged — execution
|
|
309
|
+
* continues to the next callback without throwing.
|
|
310
|
+
*
|
|
311
|
+
* @param req - The incoming HTTP Request.
|
|
312
|
+
* @param context - Dev server context (port, hostname).
|
|
313
|
+
* @returns A Response if a callback intercepted the request, or null if none did.
|
|
314
|
+
*/
|
|
315
|
+
async runHandleRequest(req, context) {
|
|
316
|
+
for (let i = 0; i < this._handleRequest.length; i++) try {
|
|
317
|
+
const result = await this._handleRequest[i](req, context);
|
|
318
|
+
if (result instanceof Response) return result;
|
|
319
|
+
} catch (err) {
|
|
320
|
+
console.error(`[plugin] handleRequest callback #${i + 1} error: ${err instanceof Error ? err.message : String(err)}`);
|
|
321
|
+
}
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Execute all registered onEnd callbacks sequentially with the resolved
|
|
326
|
+
* config and aggregated page metadata.
|
|
327
|
+
* Errors inside individual callbacks are caught and logged — execution
|
|
328
|
+
* continues to the next callback without throwing.
|
|
329
|
+
*
|
|
330
|
+
* @param pages - Array of metadata for every built page.
|
|
331
|
+
*/
|
|
332
|
+
async runOnEnd(pages) {
|
|
333
|
+
for (let i = 0; i < this._onEnd.length; i++) try {
|
|
334
|
+
await this._onEnd[i](this.config, pages);
|
|
335
|
+
} catch (err) {
|
|
336
|
+
console.error(`[plugin] onEnd callback #${i + 1} error: ${err instanceof Error ? err.message : String(err)}`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Execute registered onLoad handlers in registration order against a file.
|
|
341
|
+
* Only the **first** handler whose `filter` regex matches the path and returns
|
|
342
|
+
* a result is applied. If a matching handler throws, the error is logged and
|
|
343
|
+
* subsequent handlers are tried.
|
|
344
|
+
*
|
|
345
|
+
* @param path - Relative path of the file being loaded.
|
|
346
|
+
* @param content - Raw file content.
|
|
347
|
+
* @returns Transformed content if a matching handler returned it, or null.
|
|
348
|
+
*/
|
|
349
|
+
async runOnLoad(path, content) {
|
|
350
|
+
for (const handler of this._onLoad) if (handler.filter.test(path)) try {
|
|
351
|
+
const result = await handler.fn({
|
|
352
|
+
path,
|
|
353
|
+
content
|
|
354
|
+
});
|
|
355
|
+
if (result) return result;
|
|
356
|
+
} catch (err) {
|
|
357
|
+
console.error(`[plugin] onLoad handler for filter ${handler.filter} error: ${err instanceof Error ? err.message : String(err)}`);
|
|
358
|
+
}
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Execute all registered onStart callbacks sequentially.
|
|
363
|
+
* Each callback receives the resolved DocuConfig.
|
|
364
|
+
* Errors inside individual callbacks are caught and logged — execution
|
|
365
|
+
* continues to the next callback without throwing.
|
|
366
|
+
*/
|
|
367
|
+
async runOnStart() {
|
|
368
|
+
for (let i = 0; i < this._onStart.length; i++) try {
|
|
369
|
+
await this._onStart[i](this.config);
|
|
370
|
+
} catch (err) {
|
|
371
|
+
console.error(`[plugin] onStart callback #${i + 1} error: ${err instanceof Error ? err.message : String(err)}`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Execute the transformFrontmatter chain in waterfall pattern.
|
|
376
|
+
* Each callback receives the **previous** callback's return value (or the
|
|
377
|
+
* original frontmatter for the first). Callbacks that return `undefined` or
|
|
378
|
+
* `null` pass the current value through unchanged.
|
|
379
|
+
* Callbacks that return a non-object (string, number, array) are skipped
|
|
380
|
+
* with a console warning — only plain objects are accepted.
|
|
381
|
+
* Errors inside individual callbacks are caught and logged — the current
|
|
382
|
+
* frontmatter passes through unchanged for that step.
|
|
383
|
+
*
|
|
384
|
+
* @param frontmatter - Initial frontmatter object parsed from MDX.
|
|
385
|
+
* @param context - Page context with slug, filePath, and raw content.
|
|
386
|
+
* @returns The final transformed frontmatter object.
|
|
387
|
+
*/
|
|
388
|
+
async runTransformFrontmatterChain(frontmatter, context) {
|
|
389
|
+
let result = frontmatter;
|
|
390
|
+
for (let i = 0; i < this._transformFrontmatter.length; i++) try {
|
|
391
|
+
const next = await this._transformFrontmatter[i](result, context);
|
|
392
|
+
if (next !== void 0 && next !== null) {
|
|
393
|
+
if (typeof next === "object" && !Array.isArray(next)) result = next;
|
|
394
|
+
else console.warn(`[plugin] transformFrontmatter callback #${i + 1} returned invalid type (expected a plain object), skipping`);
|
|
395
|
+
}
|
|
396
|
+
} catch (err) {
|
|
397
|
+
console.error(`[plugin] transformFrontmatter callback #${i + 1} error: ${err instanceof Error ? err.message : String(err)}`);
|
|
398
|
+
}
|
|
399
|
+
return result;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Execute the transformHtml chain in pipeline pattern.
|
|
403
|
+
* Each callback receives the **previous** callback's return value (or the
|
|
404
|
+
* original HTML for the first). Every callback **must** return a string.
|
|
405
|
+
* Errors inside individual callbacks are caught and logged — the current
|
|
406
|
+
* HTML passes through unchanged for that step.
|
|
407
|
+
*
|
|
408
|
+
* @param html - The initial HTML string.
|
|
409
|
+
* @param context - Full page context (slug, filePath, frontmatter, content, config).
|
|
410
|
+
* @returns The final transformed HTML string.
|
|
411
|
+
*/
|
|
412
|
+
async runTransformHtmlChain(html, context) {
|
|
413
|
+
let result = html;
|
|
414
|
+
for (let i = 0; i < this._transformHtml.length; i++) try {
|
|
415
|
+
result = await this._transformHtml[i](result, context);
|
|
416
|
+
} catch (err) {
|
|
417
|
+
console.error(`[plugin] transformHtml callback #${i + 1} error: ${err instanceof Error ? err.message : String(err)}`);
|
|
418
|
+
}
|
|
419
|
+
return result;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Register a callback to mutate frontmatter before MDX compilation.
|
|
423
|
+
* Callbacks are chained in a waterfall: the return value of one is passed
|
|
424
|
+
* as input to the next. Return `undefined` to pass through unchanged.
|
|
425
|
+
*
|
|
426
|
+
* **Note:** Only plain objects are accepted as return values. Returning
|
|
427
|
+
* a string, number, or array will be silently skipped with a warning.
|
|
428
|
+
* Plugin authors should validate their return values before returning.
|
|
429
|
+
*
|
|
430
|
+
* @param callback - Receives frontmatter object and page context.
|
|
431
|
+
*
|
|
432
|
+
* @example
|
|
433
|
+
* build.transformFrontmatter((fm, ctx) => {
|
|
434
|
+
* const wordCount = ctx.content!.split(/\s+/).length;
|
|
435
|
+
* return { ...fm, readingTime: `${Math.ceil(wordCount / 200)} min read` };
|
|
436
|
+
* });
|
|
437
|
+
*/
|
|
438
|
+
transformFrontmatter(callback) {
|
|
439
|
+
this._transformFrontmatter.push(callback);
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Register a callback to transform the final HTML string per page.
|
|
443
|
+
* This is the **last** hook before the HTML is written to disk.
|
|
444
|
+
* Callbacks are chained in a pipeline: each receives the previous callback's output.
|
|
445
|
+
*
|
|
446
|
+
* @param callback - Receives HTML string and full page context. Must return HTML.
|
|
447
|
+
*
|
|
448
|
+
* @example
|
|
449
|
+
* build.transformHtml((html, ctx) => {
|
|
450
|
+
* return html.replace(/https?:\/\/old-domain\.com\//g, "/");
|
|
451
|
+
* });
|
|
452
|
+
*/
|
|
453
|
+
transformHtml(callback) {
|
|
454
|
+
this._transformHtml.push(callback);
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Collect items from a callback result, filtering only valid strings.
|
|
458
|
+
* Non-string items and unexpected types are logged as warnings.
|
|
459
|
+
*/
|
|
460
|
+
collectItems(items, result, hookName) {
|
|
461
|
+
if (Array.isArray(result)) for (const item of result) if (typeof item === "string") items.push(item);
|
|
462
|
+
else console.warn(`[plugin] ${hookName} callback returned non-string item (got ${typeof item}), skipping`);
|
|
463
|
+
else if (typeof result === "string") items.push(result);
|
|
464
|
+
else console.warn(`[plugin] ${hookName} callback returned unexpected type (got ${typeof result}), expected string or string[], skipping`);
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
//#endregion
|
|
468
|
+
//#region .docu/node/fs-scanner.ts
|
|
469
|
+
function toTitleCase$1(str) {
|
|
470
|
+
return str.replace(/-/g, " ").replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
|
|
471
|
+
}
|
|
472
|
+
function isDocFile(filename) {
|
|
473
|
+
const ext = extname(filename).toLowerCase();
|
|
474
|
+
return [".mdx", ".md"].includes(ext);
|
|
475
|
+
}
|
|
476
|
+
function normalizePath(path) {
|
|
477
|
+
return path.split(sep).join("/");
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Recursively scan a directory and return file tree
|
|
481
|
+
*
|
|
482
|
+
* @param dirPath - Absolute path to scan
|
|
483
|
+
* @param docsRoot - Root docs folder for relative path calculation
|
|
484
|
+
*/
|
|
485
|
+
function scanDir(dirPath, docsRoot) {
|
|
486
|
+
const nodes = [];
|
|
487
|
+
let entries;
|
|
488
|
+
try {
|
|
489
|
+
entries = readdirSync(dirPath).sort();
|
|
490
|
+
} catch (err) {
|
|
491
|
+
if (err.code !== "ENOENT") throw err;
|
|
492
|
+
return nodes;
|
|
493
|
+
}
|
|
494
|
+
for (const entry of entries) {
|
|
495
|
+
if (entry.startsWith(".") || entry === "assets") continue;
|
|
496
|
+
const absPath = join(dirPath, entry);
|
|
497
|
+
try {
|
|
498
|
+
const stat = statSync(absPath);
|
|
499
|
+
if (stat.isDirectory()) {
|
|
500
|
+
const children = scanDir(absPath, docsRoot);
|
|
501
|
+
if (children.length > 0) nodes.push({
|
|
502
|
+
name: entry,
|
|
503
|
+
relPath: normalizePath(relative(docsRoot, absPath)),
|
|
504
|
+
absPath,
|
|
505
|
+
isDirectory: true,
|
|
506
|
+
children
|
|
507
|
+
});
|
|
508
|
+
} else if (stat.isFile() && isDocFile(entry)) nodes.push({
|
|
509
|
+
name: entry,
|
|
510
|
+
relPath: normalizePath(relative(docsRoot, absPath).replace(/\.(mdx|md)$/, "")),
|
|
511
|
+
absPath,
|
|
512
|
+
isDirectory: false
|
|
513
|
+
});
|
|
514
|
+
} catch (err) {
|
|
515
|
+
if (err.code !== "ENOENT") throw err;
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return nodes;
|
|
520
|
+
}
|
|
521
|
+
function fileNodesToRoutes(nodes, parentHref = "") {
|
|
522
|
+
const routes = [];
|
|
523
|
+
for (const node of nodes) if (!node.isDirectory) {
|
|
524
|
+
const baseName = node.name.replace(/\.(mdx|md)$/, "");
|
|
525
|
+
if (/^(index|readme)$/i.test(baseName)) continue;
|
|
526
|
+
const href = `/${node.relPath.split("/").pop()}`;
|
|
527
|
+
routes.push({
|
|
528
|
+
title: toTitleCase$1(baseName),
|
|
529
|
+
href
|
|
530
|
+
});
|
|
531
|
+
} else {
|
|
532
|
+
const dirTitle = toTitleCase$1(node.name);
|
|
533
|
+
const dirHref = `/${node.relPath.split("/").pop()}`;
|
|
534
|
+
const children = fileNodesToRoutes(node.children || [], dirHref);
|
|
535
|
+
if (children.length === 0) continue;
|
|
536
|
+
if ((node.children || []).some((c) => !c.isDirectory && /^(index|readme)\.(mdx|md)$/i.test(c.name))) routes.push({
|
|
537
|
+
title: dirTitle,
|
|
538
|
+
href: dirHref,
|
|
539
|
+
...parentHref === "" && { context: {
|
|
540
|
+
title: dirTitle,
|
|
541
|
+
icon: "CircleHelp",
|
|
542
|
+
description: dirTitle
|
|
543
|
+
} },
|
|
544
|
+
items: children
|
|
545
|
+
});
|
|
546
|
+
else routes.push({
|
|
547
|
+
title: dirTitle,
|
|
548
|
+
href: dirHref,
|
|
549
|
+
noLink: true,
|
|
550
|
+
...parentHref === "" && { context: {
|
|
551
|
+
title: dirTitle,
|
|
552
|
+
icon: "CircleHelp",
|
|
553
|
+
description: dirTitle
|
|
554
|
+
} },
|
|
555
|
+
items: children
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
return routes;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Scan docs folder and convert to DocuRoute[]
|
|
562
|
+
*
|
|
563
|
+
* @param docsPath - Path to docs folder (default: "./docs")
|
|
564
|
+
*/
|
|
565
|
+
function scanDocsFolder(docsPath = "./docs") {
|
|
566
|
+
const absDocsPath = join(process.cwd(), docsPath);
|
|
567
|
+
return fileNodesToRoutes(scanDir(absDocsPath, absDocsPath));
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Resolve routes:
|
|
571
|
+
* 1. If docu.json has routes, use them (manual priority)
|
|
572
|
+
* 2. Else, scan docs folder (auto-detect)
|
|
573
|
+
*
|
|
574
|
+
* @param docuJsonRoutes - Routes from docu.json (optional)
|
|
575
|
+
*/
|
|
576
|
+
function resolveRoutes(docuJsonRoutes) {
|
|
577
|
+
if (docuJsonRoutes && docuJsonRoutes.length > 0) return docuJsonRoutes;
|
|
578
|
+
return scanDocsFolder();
|
|
579
|
+
}
|
|
580
|
+
//#endregion
|
|
581
|
+
//#region .docu/node/hydrate.ts
|
|
582
|
+
var themeRegistry = presetRegistry;
|
|
583
|
+
/**
|
|
584
|
+
* Read the effective theme config with this priority:
|
|
585
|
+
* 1. FLAME_THEME env var (CLI --theme flag)
|
|
586
|
+
* 2. docu.json theme.colors field
|
|
587
|
+
*/
|
|
588
|
+
function getThemeConfig() {
|
|
589
|
+
if (process.env.FLAME_THEME) return process.env.FLAME_THEME;
|
|
590
|
+
return loadDocuConfig().themes?.colors;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Append theme CSS to compiled Tailwind output based on theme config.
|
|
594
|
+
*/
|
|
595
|
+
function buildThemeCss(baseCss, themeConfig) {
|
|
596
|
+
try {
|
|
597
|
+
const resolved = resolveTheme(themeConfig, themeRegistry);
|
|
598
|
+
return baseCss + "\n" + generateThemeCss(resolved);
|
|
599
|
+
} catch (err) {
|
|
600
|
+
console.warn(`[flame] Failed to resolve theme CSS: ${err instanceof Error ? err.message : String(err)}`);
|
|
601
|
+
return baseCss;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Compute inline theme CSS for FOUC prevention.
|
|
606
|
+
* Returns undefined if no theme is configured or on error.
|
|
607
|
+
*/
|
|
608
|
+
function computeInlineThemeCss() {
|
|
609
|
+
try {
|
|
610
|
+
const themeColors = getThemeConfig();
|
|
611
|
+
if (themeColors) {
|
|
612
|
+
const resolved = resolveTheme(themeColors, themeRegistry);
|
|
613
|
+
return generateThemeCss(resolved);
|
|
614
|
+
}
|
|
615
|
+
} catch (err) {
|
|
616
|
+
console.warn(`[flame] Failed to compute inline theme CSS: ${err instanceof Error ? err.message : String(err)}`);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
//#endregion
|
|
620
|
+
//#region .docu/node/hydrate.node.ts
|
|
621
|
+
/**
|
|
622
|
+
* Client bundle builder for Node/Deno runtimes.
|
|
623
|
+
*
|
|
624
|
+
* Uses Vite with Rolldown plugins to produce a browser-ready client
|
|
625
|
+
* bundle (JS + CSS) from the same components Bun.build handles natively.
|
|
626
|
+
* Theme helpers (getThemeConfig, buildThemeCss, computeInlineThemeCss)
|
|
627
|
+
* are re-exported from `hydrate.ts` — that module's `buildClientBundle`
|
|
628
|
+
* is Bun-only and unused here.
|
|
629
|
+
*/
|
|
630
|
+
/** Extract Lucide icon names from user docu.json configuration. */
|
|
631
|
+
function extractConfigIcons(config) {
|
|
632
|
+
const icons = [];
|
|
633
|
+
const pushIf = (s) => {
|
|
634
|
+
if (s) icons.push(s);
|
|
635
|
+
};
|
|
636
|
+
config.home?.hero?.actions?.forEach((a) => pushIf(a.icon));
|
|
637
|
+
config.home?.features?.forEach((f) => pushIf(f.icon));
|
|
638
|
+
(function walk(routes) {
|
|
639
|
+
for (const r of routes) {
|
|
640
|
+
pushIf(r.context?.icon);
|
|
641
|
+
if (r.items) walk(r.items);
|
|
642
|
+
}
|
|
643
|
+
})(config.routes ?? []);
|
|
644
|
+
return [...new Set(icons.filter((n) => /^[A-Z]/.test(n)))];
|
|
645
|
+
}
|
|
646
|
+
var execFileAsync = promisify(execFile);
|
|
647
|
+
/** Resolve the @tailwindcss/cli binary path from the installed package. */
|
|
648
|
+
function resolveTailwindBin() {
|
|
649
|
+
const require = createRequire(import.meta.url);
|
|
650
|
+
const pkgPath = require.resolve("@tailwindcss/cli/package.json");
|
|
651
|
+
const pkg = require(pkgPath);
|
|
652
|
+
const binRel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin.tailwindcss;
|
|
653
|
+
return join(dirname(pkgPath), binRel);
|
|
654
|
+
}
|
|
655
|
+
/** Compute a cache key from globals.css + theme config content. */
|
|
656
|
+
function tailwindCacheKey() {
|
|
657
|
+
const globalsPath = join(STYLES_DIR, "globals.css");
|
|
658
|
+
const globalsContent = existsSync(globalsPath) ? readFileSync(globalsPath, "utf-8") : "";
|
|
659
|
+
let themeSuffix = "";
|
|
660
|
+
try {
|
|
661
|
+
const themeColors = getThemeConfig();
|
|
662
|
+
if (themeColors) themeSuffix = JSON.stringify(themeColors);
|
|
663
|
+
} catch {}
|
|
664
|
+
return createHash("sha256").update(globalsContent + themeSuffix).digest("hex").slice(0, 16);
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Build Tailwind CSS with content-based caching.
|
|
668
|
+
* If a CSS file for the current input hash already exists, skip the subprocess.
|
|
669
|
+
* Returns the filename (e.g. "client-abc123.css") and CSS content.
|
|
670
|
+
*/
|
|
671
|
+
async function buildTailwindCss(key) {
|
|
672
|
+
const cachedFile = `client-${key}.css`;
|
|
673
|
+
const cachedPath = join(ASSETS_DIR, cachedFile);
|
|
674
|
+
if (existsSync(cachedPath)) return {
|
|
675
|
+
file: cachedFile,
|
|
676
|
+
content: readFileSync(cachedPath, "utf-8")
|
|
677
|
+
};
|
|
678
|
+
const tmpCss = join(ASSETS_DIR, `_tmp-${key}.css`);
|
|
679
|
+
const bin = resolveTailwindBin();
|
|
680
|
+
const twArgs = [
|
|
681
|
+
"-i",
|
|
682
|
+
join(STYLES_DIR, "globals.css"),
|
|
683
|
+
"-o",
|
|
684
|
+
tmpCss,
|
|
685
|
+
"--minify"
|
|
686
|
+
];
|
|
687
|
+
const args = "Deno" in globalThis ? [
|
|
688
|
+
"run",
|
|
689
|
+
"-A",
|
|
690
|
+
bin,
|
|
691
|
+
...twArgs
|
|
692
|
+
] : [bin, ...twArgs];
|
|
693
|
+
try {
|
|
694
|
+
await execFileAsync(process.execPath, args, { maxBuffer: 16777216 });
|
|
695
|
+
} catch (err) {
|
|
696
|
+
const stderr = err.stderr ?? String(err);
|
|
697
|
+
throw new Error(`Tailwind CSS build failed:\n${stderr}`, { cause: err });
|
|
698
|
+
}
|
|
699
|
+
let cssContent = await readFile(tmpCss, "utf-8");
|
|
700
|
+
await unlink(tmpCss);
|
|
701
|
+
try {
|
|
702
|
+
const themeColors = getThemeConfig();
|
|
703
|
+
if (themeColors) cssContent = buildThemeCss(cssContent, themeColors);
|
|
704
|
+
} catch (err) {
|
|
705
|
+
console.warn(`[flame] Failed to resolve theme config, falling back to globals.css only: ${err instanceof Error ? err.message : String(err)}`);
|
|
706
|
+
}
|
|
707
|
+
const cssFile = `client-${key}.css`;
|
|
708
|
+
const outPath = join(ASSETS_DIR, cssFile);
|
|
709
|
+
if (!existsSync(outPath)) await writeFile(outPath, cssContent);
|
|
710
|
+
return {
|
|
711
|
+
file: cssFile,
|
|
712
|
+
content: cssContent
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
var NODE_BUILTINS_RE = new RegExp(`^(node:.*|${builtinModules.map((m) => m.replace(/\//g, "\\/")).join("|")})$`);
|
|
716
|
+
var lucideRealEntry;
|
|
717
|
+
/** Resolve the real lucide-react entry path once (cached). */
|
|
718
|
+
function getLucideRealEntry() {
|
|
719
|
+
if (!lucideRealEntry) lucideRealEntry = createRequire(import.meta.url).resolve("lucide-react");
|
|
720
|
+
return lucideRealEntry;
|
|
721
|
+
}
|
|
722
|
+
var LUCIDE_IMPORT_RE = /import\s*\{([^}]+)\}\s*from\s*["']lucide-react["']/g;
|
|
723
|
+
var LUCIDE_ICON_RE = /^[A-Z]/;
|
|
724
|
+
/** Walk a directory scanning JS/TS/TSX files for `lucide-react` named imports. */
|
|
725
|
+
function scanDirLucideIcons(dir, set) {
|
|
726
|
+
if (!existsSync(dir)) return;
|
|
727
|
+
try {
|
|
728
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
729
|
+
for (const e of entries) {
|
|
730
|
+
const full = join(dir, e.name);
|
|
731
|
+
if (e.isDirectory()) {
|
|
732
|
+
if (e.name !== "node_modules") scanDirLucideIcons(full, set);
|
|
733
|
+
} else if (/\.(js|ts|tsx)$/.test(e.name)) {
|
|
734
|
+
const content = readFileSync(full, "utf-8");
|
|
735
|
+
for (const m of content.matchAll(LUCIDE_IMPORT_RE)) for (const s of m[1].split(",")) {
|
|
736
|
+
const name = s.trim().split(/\s+as\s+/)[0].trim();
|
|
737
|
+
if (LUCIDE_ICON_RE.test(name)) set.add(name);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
} catch (err) {
|
|
742
|
+
console.warn(`[flame] Failed to scan lucide icons: ${err instanceof Error ? err.message : String(err)}`);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
/** Collect every lucide icon name imported across flame sources and deps. */
|
|
746
|
+
function collectAllLucideIcons() {
|
|
747
|
+
const icons = /* @__PURE__ */ new Set();
|
|
748
|
+
scanDirLucideIcons(join(FRAMEWORK_ROOT, ".docu/components"), icons);
|
|
749
|
+
scanDirLucideIcons(join(FRAMEWORK_ROOT, ".docu/pages"), icons);
|
|
750
|
+
const depDirs = [
|
|
751
|
+
join(FRAMEWORK_ROOT, "..", "mdx-content", "dist"),
|
|
752
|
+
join(FRAMEWORK_ROOT, "..", "ui-react", "dist"),
|
|
753
|
+
join(FRAMEWORK_ROOT, "..", "core", "dist"),
|
|
754
|
+
join(FRAMEWORK_ROOT, "..", "themes-colors", "dist")
|
|
755
|
+
];
|
|
756
|
+
for (const d of depDirs) scanDirLucideIcons(resolve(d), icons);
|
|
757
|
+
return [...icons];
|
|
758
|
+
}
|
|
759
|
+
/** Build the client JS bundle and Tailwind CSS. */
|
|
760
|
+
async function buildClientBundle(mdxSources = {}) {
|
|
761
|
+
await mkdir(ASSETS_DIR, { recursive: true });
|
|
762
|
+
const twKey = tailwindCacheKey();
|
|
763
|
+
await cleanOldBundles(/* @__PURE__ */ new Set([`client-${twKey}.css`]));
|
|
764
|
+
const nodeEnv = process.env.NODE_ENV || "development";
|
|
765
|
+
const entryPath = join(LIB_DIR, "client.ts");
|
|
766
|
+
const bundle = await build({
|
|
767
|
+
configFile: false,
|
|
768
|
+
publicDir: false,
|
|
769
|
+
define: { "process.env.NODE_ENV": JSON.stringify(nodeEnv) },
|
|
770
|
+
plugins: [
|
|
771
|
+
{
|
|
772
|
+
name: "node-builtin-stub",
|
|
773
|
+
resolveId(id) {
|
|
774
|
+
if (NODE_BUILTINS_RE.test(id)) return `\0node-stub:${id}`;
|
|
775
|
+
return null;
|
|
776
|
+
},
|
|
777
|
+
load(id) {
|
|
778
|
+
if (!id.startsWith("\0node-stub:")) return null;
|
|
779
|
+
return "export default {}; export {};";
|
|
780
|
+
}
|
|
781
|
+
},
|
|
782
|
+
{
|
|
783
|
+
name: "lucide-optimize",
|
|
784
|
+
resolveId(id, importer) {
|
|
785
|
+
if (id !== "lucide-react") return null;
|
|
786
|
+
if (importer) {
|
|
787
|
+
if (normalizeImporterPath(importer).includes("/markdown/dist/")) return null;
|
|
788
|
+
}
|
|
789
|
+
return "\0lucide-virt";
|
|
790
|
+
},
|
|
791
|
+
load(id) {
|
|
792
|
+
if (id !== "\0lucide-virt") return null;
|
|
793
|
+
const scanned = collectAllLucideIcons();
|
|
794
|
+
const configured = extractConfigIcons(loadDocuConfig());
|
|
795
|
+
return `export { ${[.../* @__PURE__ */ new Set([...scanned, ...configured])].join(", ")} } from ${JSON.stringify(getLucideRealEntry())};`;
|
|
796
|
+
}
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
name: "docu-config",
|
|
800
|
+
resolveId(id) {
|
|
801
|
+
if (!/client-routes$/.test(id)) return null;
|
|
802
|
+
return "\0client-routes";
|
|
803
|
+
},
|
|
804
|
+
load(id) {
|
|
805
|
+
if (id !== "\0client-routes") return null;
|
|
806
|
+
const config = loadDocuConfig();
|
|
807
|
+
const resolved = {
|
|
808
|
+
...config,
|
|
809
|
+
routes: resolveRoutes(config.routes)
|
|
810
|
+
};
|
|
811
|
+
return [
|
|
812
|
+
`const docuConfig = ${JSON.stringify(resolved)};`,
|
|
813
|
+
`export const routes = docuConfig.routes || [];`,
|
|
814
|
+
`export const config = docuConfig;`
|
|
815
|
+
].join("\n");
|
|
816
|
+
}
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
name: "mdx-hydrate",
|
|
820
|
+
resolveId(id) {
|
|
821
|
+
if (/mdx-manifest$/.test(id)) return "\0mdx-manifest";
|
|
822
|
+
if (id.startsWith("mdx-module:")) return `\0${id}`;
|
|
823
|
+
return null;
|
|
824
|
+
},
|
|
825
|
+
load(id) {
|
|
826
|
+
if (id === "\0mdx-manifest") {
|
|
827
|
+
const slugs = Object.keys(mdxSources).sort();
|
|
828
|
+
return `${slugs.map((slug, i) => {
|
|
829
|
+
const key = slug.replace(/["\\]/g, "");
|
|
830
|
+
return `import * as _mdx${i} from ${JSON.stringify(`mdx-module:${key}`)};`;
|
|
831
|
+
}).join("\n")}\nexport const mdxModules = { ${slugs.map((slug, i) => `${JSON.stringify(slug)}: _mdx${i}`).join(", ")} };\n`;
|
|
832
|
+
}
|
|
833
|
+
if (!id.startsWith("\0mdx-module:")) return null;
|
|
834
|
+
const slug = id.slice(12);
|
|
835
|
+
const contents = mdxSources[slug];
|
|
836
|
+
if (contents == null) throw new Error(`unknown mdx module: ${slug}`);
|
|
837
|
+
return contents;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
],
|
|
841
|
+
build: {
|
|
842
|
+
outDir: ASSETS_DIR,
|
|
843
|
+
emptyOutDir: false,
|
|
844
|
+
sourcemap: true,
|
|
845
|
+
minify: nodeEnv === "production",
|
|
846
|
+
target: "es2020",
|
|
847
|
+
rollupOptions: {
|
|
848
|
+
input: entryPath,
|
|
849
|
+
output: {
|
|
850
|
+
format: "es",
|
|
851
|
+
entryFileNames: "client-[hash].js",
|
|
852
|
+
chunkFileNames: "chunks/[name]-[hash].js",
|
|
853
|
+
assetFileNames: "assets/[name]-[hash][extname]"
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
const outputs = Array.isArray(bundle) ? bundle : [bundle];
|
|
859
|
+
let jsFile;
|
|
860
|
+
for (const item of outputs) {
|
|
861
|
+
if (!("output" in item)) continue;
|
|
862
|
+
for (const output of item.output) if (output.type === "chunk" && output.isEntry && output.facadeModuleId && resolve(output.facadeModuleId) === entryPath) {
|
|
863
|
+
jsFile = basename(output.fileName);
|
|
864
|
+
break;
|
|
865
|
+
}
|
|
866
|
+
if (jsFile) break;
|
|
867
|
+
}
|
|
868
|
+
if (!jsFile) throw new Error("Client bundle produced no output files");
|
|
869
|
+
const { file: cssFile } = await buildTailwindCss(twKey);
|
|
870
|
+
await writeFile(join(ASSETS_DIR, "manifest.json"), JSON.stringify({
|
|
871
|
+
js: jsFile,
|
|
872
|
+
css: cssFile
|
|
873
|
+
}));
|
|
874
|
+
return {
|
|
875
|
+
js: jsFile,
|
|
876
|
+
css: cssFile
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
//#endregion
|
|
880
|
+
//#region .docu/node/git.ts
|
|
881
|
+
/**
|
|
882
|
+
* Runtime-neutral git helpers — same behavior as the git functions in
|
|
883
|
+
* `utils.ts` (Bun-only, protected) but spawning via `node:child_process`,
|
|
884
|
+
* which works on Bun, Node.js, and Deno. `mdx.ts` imports from here.
|
|
885
|
+
*/
|
|
886
|
+
function runGit(args) {
|
|
887
|
+
return new Promise((resolve, reject) => {
|
|
888
|
+
execFile("git", args, { maxBuffer: 16777216 }, (err, stdout) => {
|
|
889
|
+
if (err) reject(err);
|
|
890
|
+
else resolve(stdout);
|
|
891
|
+
});
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
function sanitizePath(filePath) {
|
|
895
|
+
const cleanPath = filePath.replace(/^\//, "");
|
|
896
|
+
if (!cleanPath || !/^[a-zA-Z0-9\-_/.\s]+$/.test(cleanPath) || /(^|\/)\.\.($|\/)/.test(cleanPath)) return null;
|
|
897
|
+
return cleanPath;
|
|
898
|
+
}
|
|
899
|
+
/** Get git last modified date for a file */
|
|
900
|
+
async function getGitLastModified(filePath) {
|
|
901
|
+
const cleanPath = sanitizePath(filePath);
|
|
902
|
+
if (!cleanPath) return null;
|
|
903
|
+
try {
|
|
904
|
+
return (await runGit([
|
|
905
|
+
"log",
|
|
906
|
+
"-1",
|
|
907
|
+
"--format=%cI",
|
|
908
|
+
"--",
|
|
909
|
+
cleanPath
|
|
910
|
+
])).trim() || null;
|
|
911
|
+
} catch {
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Fallback to filesystem mtime when git is unavailable (shallow clone, no .git, etc.).
|
|
917
|
+
* Returns ISO 8601 date string or null.
|
|
918
|
+
*/
|
|
919
|
+
async function getFilesystemMtime(filePath) {
|
|
920
|
+
try {
|
|
921
|
+
return (await stat(filePath)).mtime.toISOString();
|
|
922
|
+
} catch {
|
|
923
|
+
return null;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
/** Batch git last modified dates for multiple files in a single spawn */
|
|
927
|
+
async function getGitLastModifiedBatch(filePaths) {
|
|
928
|
+
const result = /* @__PURE__ */ new Map();
|
|
929
|
+
if (filePaths.length === 0) return result;
|
|
930
|
+
const safePaths = [];
|
|
931
|
+
for (const fp of filePaths) {
|
|
932
|
+
const cleanPath = sanitizePath(fp);
|
|
933
|
+
if (!cleanPath) {
|
|
934
|
+
console.warn(`[git] getGitLastModifiedBatch: skipping invalid path "${fp}"`);
|
|
935
|
+
continue;
|
|
936
|
+
}
|
|
937
|
+
safePaths.push(cleanPath);
|
|
938
|
+
}
|
|
939
|
+
if (safePaths.length === 0) return result;
|
|
940
|
+
try {
|
|
941
|
+
const text = await runGit([
|
|
942
|
+
"log",
|
|
943
|
+
"--format=%cI",
|
|
944
|
+
"--name-only",
|
|
945
|
+
"--diff-filter=ACMR",
|
|
946
|
+
...safePaths
|
|
947
|
+
]);
|
|
948
|
+
let currentDate = "";
|
|
949
|
+
for (const line of text.split("\n")) {
|
|
950
|
+
const trimmed = line.trim();
|
|
951
|
+
if (!trimmed) continue;
|
|
952
|
+
if (/^\d{4}-\d{2}-\d{2}T/.test(trimmed)) currentDate = trimmed;
|
|
953
|
+
else if (currentDate && !result.has(trimmed)) result.set(trimmed, currentDate);
|
|
954
|
+
}
|
|
955
|
+
} catch (err) {
|
|
956
|
+
console.error("Failed to get git last modified batch for", filePaths, err);
|
|
957
|
+
}
|
|
958
|
+
return result;
|
|
959
|
+
}
|
|
960
|
+
//#endregion
|
|
961
|
+
//#region .docu/node/mdx.ts
|
|
962
|
+
/**
|
|
963
|
+
* Return the value with `.html` appended, or null if the value should be left
|
|
964
|
+
* unchanged. Rules:
|
|
965
|
+
* - Must be a string
|
|
966
|
+
* - Must start with /docs/ (the /docs root index needs no suffix)
|
|
967
|
+
* - Must not be an external URL, contain a fragment, or already end in .html
|
|
968
|
+
*/
|
|
969
|
+
function appendHtml(value) {
|
|
970
|
+
if (typeof value !== "string") return null;
|
|
971
|
+
if (/^https?:\/\//.test(value)) return null;
|
|
972
|
+
if (!value.startsWith("/docs/")) return null;
|
|
973
|
+
if (value.includes("#")) return null;
|
|
974
|
+
if (value.endsWith(".html")) return null;
|
|
975
|
+
return `${value}.html`;
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Rehype plugin: append `.html` to internal `/docs/` hrefs on HTML `<a>` nodes.
|
|
979
|
+
*
|
|
980
|
+
* This covers standard markdown links: `[text](/docs/page)` → `<a href="…">`.
|
|
981
|
+
* It runs in the HAST (HTML AST) phase, where `<a>` elements are real nodes.
|
|
982
|
+
*
|
|
983
|
+
* Skips: external URLs, anchor-only links, paths that already end in `.html`,
|
|
984
|
+
* and the `/docs` root index (no trailing slash segment).
|
|
985
|
+
*/
|
|
986
|
+
function rehypeDocsHtmlLinks() {
|
|
987
|
+
return (tree) => {
|
|
988
|
+
function walk(node) {
|
|
989
|
+
if (node.type === "element" && node.tagName === "a") {
|
|
990
|
+
const fixed = appendHtml(node.properties?.href);
|
|
991
|
+
if (fixed) node.properties.href = fixed;
|
|
992
|
+
}
|
|
993
|
+
if (node.children) for (const child of node.children) walk(child);
|
|
994
|
+
}
|
|
995
|
+
walk(tree);
|
|
996
|
+
return tree;
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Remark plugin: append `.html` to internal `/docs/` hrefs on MDX JSX nodes.
|
|
1001
|
+
*
|
|
1002
|
+
* MDX JSX elements (`<Card href="…">`, `<LinkCard href="…">`, etc.) live in
|
|
1003
|
+
* the MDAST as `mdxJsxFlowElement` / `mdxJsxTextElement` nodes. They are
|
|
1004
|
+
* compiled directly to JavaScript by the MDX compiler *before* rehype runs,
|
|
1005
|
+
* so a rehype plugin can never see them as `<a>` elements. This remark plugin
|
|
1006
|
+
* intercepts them at the MDAST phase where their `attributes` array is still
|
|
1007
|
+
* accessible and mutable.
|
|
1008
|
+
*
|
|
1009
|
+
* Skips: same rules as `appendHtml` (external URLs, anchors, already `.html`).
|
|
1010
|
+
*/
|
|
1011
|
+
function remarkMdxJsxDocsHtmlLinks() {
|
|
1012
|
+
return (tree) => {
|
|
1013
|
+
function walk(node) {
|
|
1014
|
+
if ((node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement") && node.attributes) {
|
|
1015
|
+
for (const attr of node.attributes) if (attr.type === "mdxJsxAttribute" && attr.name === "href") {
|
|
1016
|
+
const fixed = appendHtml(attr.value);
|
|
1017
|
+
if (fixed) attr.value = fixed;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
if (node.children) for (const child of node.children) walk(child);
|
|
1021
|
+
}
|
|
1022
|
+
walk(tree);
|
|
1023
|
+
return tree;
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
z.object({
|
|
1027
|
+
title: z.coerce.string().optional(),
|
|
1028
|
+
description: z.coerce.string().optional(),
|
|
1029
|
+
image: z.coerce.string().optional(),
|
|
1030
|
+
date: z.coerce.string().optional()
|
|
1031
|
+
}).passthrough();
|
|
1032
|
+
/**
|
|
1033
|
+
* Read a string field from frontmatter after the plugin transform chain
|
|
1034
|
+
* (which widens the type to `Record<string, unknown>`). Returns "" when
|
|
1035
|
+
* missing or not a string.
|
|
1036
|
+
*/
|
|
1037
|
+
function frontmatterField(frontmatter, key) {
|
|
1038
|
+
return typeof frontmatter[key] === "string" ? frontmatter[key] : "";
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Compile MDX/MD content into a React element and compiled source.
|
|
1042
|
+
*
|
|
1043
|
+
* @param rawMdx - Raw MDX/MD file content
|
|
1044
|
+
* @param filePath - Relative file path for git date lookup
|
|
1045
|
+
* @param gitDates - Optional pre-fetched git last-modified map
|
|
1046
|
+
* @param remarkPlugins - Additional remark plugins (merged after defaults, optional)
|
|
1047
|
+
* @param rehypePlugins - Additional rehype plugins (merged after defaults, optional)
|
|
1048
|
+
* @param frontmatterSchema - Custom schema overriding the default contract
|
|
1049
|
+
*/
|
|
1050
|
+
/**
|
|
1051
|
+
* Shared compile core used by `compileMdx` (SSR) and `compileMdxModule`
|
|
1052
|
+
* (static hydration). Strips frontmatter, merges the doc plugin chain
|
|
1053
|
+
* (defaults + .html link fixes + user plugins) and runs `serialize()`.
|
|
1054
|
+
*/
|
|
1055
|
+
async function serializeWithDocPlugins(rawMdx, opts = {}, pre) {
|
|
1056
|
+
const { strippedContent, frontmatter } = pre ?? extractFrontmatterWithContent(rawMdx, opts.frontmatterSchema);
|
|
1057
|
+
const defaultRemark = createDefaultRemarkPlugins();
|
|
1058
|
+
const defaultRehype = createDefaultRehypePlugins();
|
|
1059
|
+
const finalRemark = [
|
|
1060
|
+
...defaultRemark,
|
|
1061
|
+
remarkMdxJsxDocsHtmlLinks,
|
|
1062
|
+
...opts.remarkPlugins ?? []
|
|
1063
|
+
];
|
|
1064
|
+
const finalRehype = [
|
|
1065
|
+
...defaultRehype,
|
|
1066
|
+
rehypeDocsHtmlLinks,
|
|
1067
|
+
...opts.rehypePlugins ?? []
|
|
1068
|
+
];
|
|
1069
|
+
return serialize(strippedContent, {
|
|
1070
|
+
outputFormat: opts.outputFormat,
|
|
1071
|
+
format: "md",
|
|
1072
|
+
mdxOptions: {
|
|
1073
|
+
rehypePlugins: finalRehype,
|
|
1074
|
+
remarkPlugins: finalRemark
|
|
1075
|
+
}
|
|
1076
|
+
}).then((serialized) => ({
|
|
1077
|
+
...serialized,
|
|
1078
|
+
frontmatter,
|
|
1079
|
+
strippedContent
|
|
1080
|
+
}));
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Compile MDX/MD content into a React element and compiled source.
|
|
1084
|
+
*
|
|
1085
|
+
* @param rawMdx - Raw MDX/MD file content
|
|
1086
|
+
* @param filePath - Relative file path for git date lookup
|
|
1087
|
+
* @param gitDates - Optional pre-fetched git last-modified map
|
|
1088
|
+
* @param remarkPlugins - Additional remark plugins (merged after defaults, optional)
|
|
1089
|
+
* @param rehypePlugins - Additional rehype plugins (merged after defaults, optional)
|
|
1090
|
+
* @param frontmatterSchema - Custom schema overriding the default contract
|
|
1091
|
+
*/
|
|
1092
|
+
async function compileMdx(rawMdx, filePath, gitDates, remarkPlugins, rehypePlugins, frontmatterSchema, pre) {
|
|
1093
|
+
const tocs = extractTocsFromRawMdx(rawMdx);
|
|
1094
|
+
const frontmatter = pre?.frontmatter ?? extractFrontmatterWithContent(rawMdx, frontmatterSchema).frontmatter;
|
|
1095
|
+
const serialized = await serializeWithDocPlugins(rawMdx, {
|
|
1096
|
+
remarkPlugins,
|
|
1097
|
+
rehypePlugins,
|
|
1098
|
+
frontmatterSchema
|
|
1099
|
+
}, pre);
|
|
1100
|
+
const components = createMdxComponents();
|
|
1101
|
+
const content = React.createElement(MDXRemote, {
|
|
1102
|
+
compiledSource: serialized.compiledSource,
|
|
1103
|
+
scope: {},
|
|
1104
|
+
frontmatter: {},
|
|
1105
|
+
components
|
|
1106
|
+
});
|
|
1107
|
+
const date = frontmatter.date || gitDates?.get(filePath) || await getGitLastModified(filePath) || await getFilesystemMtime(filePath) || void 0;
|
|
1108
|
+
return {
|
|
1109
|
+
content,
|
|
1110
|
+
compiledSource: serialized.compiledSource,
|
|
1111
|
+
frontmatter: {
|
|
1112
|
+
...frontmatter,
|
|
1113
|
+
date
|
|
1114
|
+
},
|
|
1115
|
+
tocs
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1119
|
+
* Compile MDX to a real ESM module source (program format) for static
|
|
1120
|
+
* client-side hydration — the browser imports and executes it via the bundler
|
|
1121
|
+
* instead of `new Function(compiledSource)`. Uses the same plugin chain as
|
|
1122
|
+
* `compileMdx` so the hydrated tree matches the SSR output.
|
|
1123
|
+
*/
|
|
1124
|
+
/**
|
|
1125
|
+
* Frontmatter records collected once during compilation — pagination title /
|
|
1126
|
+
* description and other metadata consumers read from here instead of
|
|
1127
|
+
* re-reading + re-parsing files (parse-once contract: the frontmatter is
|
|
1128
|
+
* already parsed by `serializeWithDocPlugins`).
|
|
1129
|
+
*/
|
|
1130
|
+
var pageFrontmatter = /* @__PURE__ */ new Map();
|
|
1131
|
+
/** Register a page's frontmatter, keyed by its href. */
|
|
1132
|
+
function registerPageFrontmatter(href, frontmatter) {
|
|
1133
|
+
pageFrontmatter.set(href, frontmatter);
|
|
1134
|
+
}
|
|
1135
|
+
/** Look up a page's frontmatter (undefined when not yet compiled). */
|
|
1136
|
+
function getPageFrontmatter(href) {
|
|
1137
|
+
return pageFrontmatter.get(href);
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Frontmatter-stripped content from the prePass — lets the SSR compile
|
|
1141
|
+
* phase skip its own `extractFrontmatterWithContent` (parse-once across
|
|
1142
|
+
* both compile phases).
|
|
1143
|
+
*/
|
|
1144
|
+
var pageStripped = /* @__PURE__ */ new Map();
|
|
1145
|
+
function registerPageStripped(href, stripped) {
|
|
1146
|
+
pageStripped.set(href, stripped);
|
|
1147
|
+
}
|
|
1148
|
+
function getPageStripped(href) {
|
|
1149
|
+
return pageStripped.get(href);
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Original (pre-transform) file content, cached during the prePass so the
|
|
1153
|
+
* page loop does not re-read the file from disk — one read per file.
|
|
1154
|
+
*/
|
|
1155
|
+
var pageContent = /* @__PURE__ */ new Map();
|
|
1156
|
+
function registerPageContent(href, raw) {
|
|
1157
|
+
pageContent.set(href, raw);
|
|
1158
|
+
}
|
|
1159
|
+
function getPageContent(href) {
|
|
1160
|
+
return pageContent.get(href);
|
|
1161
|
+
}
|
|
1162
|
+
async function compileMdxModule(rawMdx, remarkPlugins, rehypePlugins, href) {
|
|
1163
|
+
const serialized = await serializeWithDocPlugins(rawMdx, {
|
|
1164
|
+
outputFormat: "program",
|
|
1165
|
+
remarkPlugins,
|
|
1166
|
+
rehypePlugins
|
|
1167
|
+
});
|
|
1168
|
+
if (href) {
|
|
1169
|
+
registerPageFrontmatter(href, serialized.frontmatter);
|
|
1170
|
+
registerPageStripped(href, serialized.strippedContent);
|
|
1171
|
+
}
|
|
1172
|
+
return serialized.compiledSource;
|
|
1173
|
+
}
|
|
1174
|
+
//#endregion
|
|
1175
|
+
//#region .docu/node/search-indexer.ts
|
|
1176
|
+
/**
|
|
1177
|
+
* Search Index Generator
|
|
1178
|
+
*
|
|
1179
|
+
* Scans MDX files at build-time and produces a search-index.json
|
|
1180
|
+
* with hierarchy-based records (similar to Algolia DocSearch crawler).
|
|
1181
|
+
*
|
|
1182
|
+
* Hierarchy levels:
|
|
1183
|
+
* lvl0 = section (parent route title)
|
|
1184
|
+
* lvl1 = page title (frontmatter title or h1)
|
|
1185
|
+
* lvl2-lvl6 = headings h2-h6
|
|
1186
|
+
* content = first paragraph/list items after each heading
|
|
1187
|
+
*/
|
|
1188
|
+
var docuConfig$5 = loadDocuConfig();
|
|
1189
|
+
function getSectionTitle(filePath) {
|
|
1190
|
+
const parts = filePath.split("/");
|
|
1191
|
+
if (parts.length > 1) {
|
|
1192
|
+
const section = docuConfig$5.routes?.find((r) => r.href === `/${parts[0]}` || r.href === parts[0]);
|
|
1193
|
+
if (section) return section.title;
|
|
1194
|
+
}
|
|
1195
|
+
return docuConfig$5.meta?.title || "Docs";
|
|
1196
|
+
}
|
|
1197
|
+
function slugify(text) {
|
|
1198
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1199
|
+
}
|
|
1200
|
+
function stripJsx(content) {
|
|
1201
|
+
let result = content;
|
|
1202
|
+
let prev = "";
|
|
1203
|
+
while (result !== prev) {
|
|
1204
|
+
prev = result;
|
|
1205
|
+
result = result.replace(/<[A-Z][\w.]*[\s\S]*?\/>/g, "").replace(/<[A-Z][\w.]*[\s\S]*?>([\s\S]*?)<\/[A-Z][\w.]*>/g, "$1");
|
|
1206
|
+
}
|
|
1207
|
+
return result;
|
|
1208
|
+
}
|
|
1209
|
+
function extractRecords(filePath, raw) {
|
|
1210
|
+
const { frontmatter, strippedContent: content } = extractFrontmatterWithContent(raw);
|
|
1211
|
+
const records = [];
|
|
1212
|
+
const url = docsHtmlHref(`/docs/${filePath}`);
|
|
1213
|
+
const lvl0 = getSectionTitle(filePath);
|
|
1214
|
+
const lvl1 = frontmatterField(frontmatter, "title") || null;
|
|
1215
|
+
const hierarchy = {
|
|
1216
|
+
lvl0,
|
|
1217
|
+
lvl1,
|
|
1218
|
+
lvl2: null,
|
|
1219
|
+
lvl3: null,
|
|
1220
|
+
lvl4: null,
|
|
1221
|
+
lvl5: null,
|
|
1222
|
+
lvl6: null
|
|
1223
|
+
};
|
|
1224
|
+
if (lvl1) records.push({
|
|
1225
|
+
url,
|
|
1226
|
+
hierarchy: { ...hierarchy },
|
|
1227
|
+
content: frontmatterField(frontmatter, "description") || null,
|
|
1228
|
+
type: "lvl1"
|
|
1229
|
+
});
|
|
1230
|
+
const lines = stripJsx(content).split("\n");
|
|
1231
|
+
let currentParagraph = [];
|
|
1232
|
+
let inCodeBlock = false;
|
|
1233
|
+
const flushParagraph = () => {
|
|
1234
|
+
if (currentParagraph.length > 0) {
|
|
1235
|
+
const text = currentParagraph.join(" ").trim();
|
|
1236
|
+
if (text) records.push({
|
|
1237
|
+
url: buildAnchorUrl(),
|
|
1238
|
+
hierarchy: { ...hierarchy },
|
|
1239
|
+
content: text,
|
|
1240
|
+
type: "content"
|
|
1241
|
+
});
|
|
1242
|
+
currentParagraph = [];
|
|
1243
|
+
}
|
|
1244
|
+
};
|
|
1245
|
+
const buildAnchorUrl = () => {
|
|
1246
|
+
for (let i = 6; i >= 2; i--) {
|
|
1247
|
+
const key = `lvl${i}`;
|
|
1248
|
+
if (hierarchy[key]) return `${url}#${slugify(hierarchy[key])}`;
|
|
1249
|
+
}
|
|
1250
|
+
return url;
|
|
1251
|
+
};
|
|
1252
|
+
for (const line of lines) {
|
|
1253
|
+
const trimmed = line.trim();
|
|
1254
|
+
if (trimmed.startsWith("```")) {
|
|
1255
|
+
inCodeBlock = !inCodeBlock;
|
|
1256
|
+
continue;
|
|
1257
|
+
}
|
|
1258
|
+
if (inCodeBlock) continue;
|
|
1259
|
+
if (/^import\s+[\w{*]/.test(trimmed) || /^export\s+[\w{*]/.test(trimmed)) continue;
|
|
1260
|
+
const headingMatch = trimmed.match(/^(#{1,6})\s+(.+)$/);
|
|
1261
|
+
if (headingMatch) {
|
|
1262
|
+
flushParagraph();
|
|
1263
|
+
const level = headingMatch[1].length;
|
|
1264
|
+
const title = headingMatch[2].replace(/[*`[\]]/g, "").trim();
|
|
1265
|
+
for (let i = level; i <= 6; i++) hierarchy[`lvl${i}`] = null;
|
|
1266
|
+
hierarchy[`lvl${level}`] = title;
|
|
1267
|
+
if (level === 1 && !hierarchy.lvl1) hierarchy.lvl1 = title;
|
|
1268
|
+
if (level >= 2) records.push({
|
|
1269
|
+
url: `${url}#${slugify(title)}`,
|
|
1270
|
+
hierarchy: { ...hierarchy },
|
|
1271
|
+
content: null,
|
|
1272
|
+
type: `lvl${level}`
|
|
1273
|
+
});
|
|
1274
|
+
continue;
|
|
1275
|
+
}
|
|
1276
|
+
if (trimmed === "" || trimmed === "---") {
|
|
1277
|
+
flushParagraph();
|
|
1278
|
+
continue;
|
|
1279
|
+
}
|
|
1280
|
+
if (/^\|.+\|/.test(trimmed)) continue;
|
|
1281
|
+
const cleaned = trimmed.replace(/^[-*+]\s+/, "").replace(/^\d+\.\s+/, "").replace(/^>\s+/, "").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").trim();
|
|
1282
|
+
if (cleaned) currentParagraph.push(cleaned);
|
|
1283
|
+
}
|
|
1284
|
+
flushParagraph();
|
|
1285
|
+
return records;
|
|
1286
|
+
}
|
|
1287
|
+
async function generateSearchIndex(docsDir, outputDir) {
|
|
1288
|
+
const docs = resolve(docsDir || DOCS_DIR);
|
|
1289
|
+
const dist = resolve(outputDir || ASSETS_DIR);
|
|
1290
|
+
await mkdir(dist, { recursive: true });
|
|
1291
|
+
const mdxFiles = await scanMdxFiles(docs);
|
|
1292
|
+
const allRecords = (await Promise.all(mdxFiles.map(async (file) => {
|
|
1293
|
+
const raw = getPageContent(`/${file.path}`) ?? await readFile(file.absPath, "utf-8");
|
|
1294
|
+
return extractRecords(file.path, raw);
|
|
1295
|
+
}))).flat();
|
|
1296
|
+
await writeFile(join(dist, "search-index.json"), JSON.stringify(allRecords));
|
|
1297
|
+
return allRecords.length;
|
|
1298
|
+
}
|
|
1299
|
+
//#endregion
|
|
1300
|
+
//#region .docu/node/sentry.ts
|
|
1301
|
+
/**
|
|
1302
|
+
* Optional Sentry error tracking integration.
|
|
1303
|
+
* Active only when SENTRY_DSN environment variable is set.
|
|
1304
|
+
* Requires @sentry/bun as an optional peer dependency.
|
|
1305
|
+
*/
|
|
1306
|
+
var sentry = null;
|
|
1307
|
+
var initialized = false;
|
|
1308
|
+
async function initSentry() {
|
|
1309
|
+
const dsn = process.env.SENTRY_DSN;
|
|
1310
|
+
if (!dsn) return;
|
|
1311
|
+
try {
|
|
1312
|
+
sentry = await import("@sentry/bun");
|
|
1313
|
+
sentry.init({
|
|
1314
|
+
dsn,
|
|
1315
|
+
environment: process.env.NODE_ENV || "development",
|
|
1316
|
+
release: process.env.SENTRY_RELEASE || void 0
|
|
1317
|
+
});
|
|
1318
|
+
initialized = true;
|
|
1319
|
+
} catch {
|
|
1320
|
+
sentry = null;
|
|
1321
|
+
initialized = false;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
function captureException(err, context) {
|
|
1325
|
+
if (!initialized || !sentry) return;
|
|
1326
|
+
sentry.captureException(err, context ? { extra: context } : void 0);
|
|
1327
|
+
}
|
|
1328
|
+
//#endregion
|
|
1329
|
+
//#region .docu/components/Breadcrumb.tsx
|
|
1330
|
+
function toTitleCase(input) {
|
|
1331
|
+
return input.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
1332
|
+
}
|
|
1333
|
+
function DocsBreadcrumb({ paths }) {
|
|
1334
|
+
return /* @__PURE__ */ jsx(Breadcrumb, {
|
|
1335
|
+
className: "py-4",
|
|
1336
|
+
children: /* @__PURE__ */ jsxs(BreadcrumbList, { children: [/* @__PURE__ */ jsx(BreadcrumbItem, { children: /* @__PURE__ */ jsx("span", {
|
|
1337
|
+
className: "text-muted-foreground",
|
|
1338
|
+
children: "Docs"
|
|
1339
|
+
}) }), paths.map((path, index) => /* @__PURE__ */ jsx(BreadcrumbItem, { children: index < paths.length - 1 ? /* @__PURE__ */ jsx("span", {
|
|
1340
|
+
className: "text-muted-foreground",
|
|
1341
|
+
children: toTitleCase(path)
|
|
1342
|
+
}) : /* @__PURE__ */ jsx(BreadcrumbPage, {
|
|
1343
|
+
className: "text-base-content",
|
|
1344
|
+
children: toTitleCase(path)
|
|
1345
|
+
}) }, `${path}-${index}`))] })
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
var routes$1 = resolveRoutes(loadDocuConfig().routes);
|
|
1349
|
+
function flattenRoutes() {
|
|
1350
|
+
const paths = [];
|
|
1351
|
+
function traverse(route, section = "") {
|
|
1352
|
+
const fullPath = route.href.startsWith(section) ? route.href : `${section}${route.href}`.replace(/\/+/g, "/");
|
|
1353
|
+
if (route.href && !route.noLink) paths.push(fullPath);
|
|
1354
|
+
if (route.items) route.items.forEach((item) => traverse(item, fullPath));
|
|
1355
|
+
}
|
|
1356
|
+
routes$1.forEach((route) => traverse(route));
|
|
1357
|
+
return paths;
|
|
1358
|
+
}
|
|
1359
|
+
function getRouteMap() {
|
|
1360
|
+
const map = /* @__PURE__ */ new Map();
|
|
1361
|
+
function traverse(route, section = "") {
|
|
1362
|
+
const fullPath = route.href.startsWith(section) ? route.href : `${section}${route.href}`.replace(/\/+/g, "/");
|
|
1363
|
+
map.set(fullPath, route.title);
|
|
1364
|
+
if (route.items) route.items.forEach((item) => traverse(item, fullPath));
|
|
1365
|
+
}
|
|
1366
|
+
routes$1.forEach((route) => traverse(route));
|
|
1367
|
+
return map;
|
|
1368
|
+
}
|
|
1369
|
+
/**
|
|
1370
|
+
* Frontmatter for a page — read from the parse-once registry (populated
|
|
1371
|
+
* during compilation) instead of re-reading + re-parsing the file. Falls
|
|
1372
|
+
* back to a direct read only for pages the dev server has not compiled yet,
|
|
1373
|
+
* and caches the result back into the registry.
|
|
1374
|
+
*/
|
|
1375
|
+
function readPageFrontmatter(href) {
|
|
1376
|
+
const registered = getPageFrontmatter(href);
|
|
1377
|
+
if (registered) return registered;
|
|
1378
|
+
let fm = {};
|
|
1379
|
+
const rel = href.replace(/^\/|$/g, "");
|
|
1380
|
+
for (const ext of [".mdx", ".md"]) {
|
|
1381
|
+
for (const file of [join(DOCS_DIR, `${rel}${ext}`), join(DOCS_DIR, `${rel}/index${ext}`)]) try {
|
|
1382
|
+
fm = extractFrontmatter(readFileSync(file, "utf-8"));
|
|
1383
|
+
break;
|
|
1384
|
+
} catch {}
|
|
1385
|
+
if (Object.keys(fm).length) break;
|
|
1386
|
+
}
|
|
1387
|
+
registerPageFrontmatter(href, fm);
|
|
1388
|
+
return fm;
|
|
1389
|
+
}
|
|
1390
|
+
function getPreviousNext(pathname) {
|
|
1391
|
+
const normalizedPath = pathname.replace(/^\/|$/g, "");
|
|
1392
|
+
if (normalizedPath === "docs" || normalizedPath === "") {
|
|
1393
|
+
const paths = flattenRoutes();
|
|
1394
|
+
const routeMap = getRouteMap();
|
|
1395
|
+
const first = paths[0];
|
|
1396
|
+
if (!first) return {
|
|
1397
|
+
prev: null,
|
|
1398
|
+
next: null
|
|
1399
|
+
};
|
|
1400
|
+
const fm = readPageFrontmatter(first);
|
|
1401
|
+
return {
|
|
1402
|
+
prev: null,
|
|
1403
|
+
next: {
|
|
1404
|
+
href: first,
|
|
1405
|
+
title: fm.title || routeMap.get(first) || "",
|
|
1406
|
+
description: fm.description || ""
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
const paths = flattenRoutes();
|
|
1411
|
+
const index = paths.findIndex((href) => href === `/${normalizedPath}` || href === normalizedPath);
|
|
1412
|
+
if (index === -1) return {
|
|
1413
|
+
prev: null,
|
|
1414
|
+
next: null
|
|
1415
|
+
};
|
|
1416
|
+
const routeMap = getRouteMap();
|
|
1417
|
+
const prevHref = index > 0 ? paths[index - 1] : null;
|
|
1418
|
+
const nextHref = index < paths.length - 1 ? paths[index + 1] : null;
|
|
1419
|
+
const prevFm = prevHref ? readPageFrontmatter(prevHref) : null;
|
|
1420
|
+
const nextFm = nextHref ? readPageFrontmatter(nextHref) : null;
|
|
1421
|
+
return {
|
|
1422
|
+
prev: prevHref ? {
|
|
1423
|
+
href: prevHref,
|
|
1424
|
+
title: prevFm?.title || routeMap.get(prevHref) || ""
|
|
1425
|
+
} : null,
|
|
1426
|
+
next: nextHref ? {
|
|
1427
|
+
href: nextHref,
|
|
1428
|
+
title: nextFm?.title || routeMap.get(nextHref) || "",
|
|
1429
|
+
description: nextFm?.description || ""
|
|
1430
|
+
} : null
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
//#endregion
|
|
1434
|
+
//#region .docu/components/Pagination.tsx
|
|
1435
|
+
function Pagination({ pathname, className, prevIcon, nextIcon, linkClassName }) {
|
|
1436
|
+
const { prev, next } = getPreviousNext(pathname);
|
|
1437
|
+
if (!prev && !next) return null;
|
|
1438
|
+
return /* @__PURE__ */ jsx(PaginationDocs, {
|
|
1439
|
+
prev: prev ? {
|
|
1440
|
+
href: docsHtmlHref(`/docs${prev.href}`),
|
|
1441
|
+
title: prev.title
|
|
1442
|
+
} : void 0,
|
|
1443
|
+
next: next ? {
|
|
1444
|
+
href: docsHtmlHref(`/docs${next.href}`),
|
|
1445
|
+
title: next.title,
|
|
1446
|
+
description: next.description
|
|
1447
|
+
} : void 0,
|
|
1448
|
+
className,
|
|
1449
|
+
prevIcon,
|
|
1450
|
+
nextIcon,
|
|
1451
|
+
linkClassName
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
//#endregion
|
|
1455
|
+
//#region .docu/components/Typography.tsx
|
|
1456
|
+
function Typography({ children }) {
|
|
1457
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1458
|
+
className: "prose prose-zinc dark:prose-invert prose-code:font-code dark:prose-code:bg-stone-900/25 prose-code:bg-stone-50 prose-pre:bg-background max-lg:prose-headings:scroll-mt-16 prose-headings:scroll-mt-4 prose-code:text-sm prose-code:leading-6 dark:prose-code:text-white prose-code:text-stone-800 prose-code:p-1 prose-code:rounded-md prose-code:border prose-img:rounded-md prose-img:border prose-code:before:content-none prose-code:after:content-none prose-code:px-1.5 prose-code:overflow-x-auto prose-img:my-3 prose-h2:my-4 prose-h2:mt-8 w-full max-w-full! pt-2",
|
|
1459
|
+
children
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
//#endregion
|
|
1463
|
+
//#region .docu/node/helpers.ts
|
|
1464
|
+
var docuConfig$3 = loadDocuConfig();
|
|
1465
|
+
function getEditLink(url, filePath) {
|
|
1466
|
+
const configPath = docuConfig$3?.repo?.path || detectPlatformPath(url);
|
|
1467
|
+
const encodedPath = filePath.replace(/^\//, "").split("/").map(encodeURIComponent).join("/");
|
|
1468
|
+
return `${url}/${configPath}`.replace("{filePath}", encodedPath);
|
|
1469
|
+
}
|
|
1470
|
+
/**
|
|
1471
|
+
* Detect the default edit path template from the repo URL hostname.
|
|
1472
|
+
* Supports GitHub, GitLab, Bitbucket, Gitea (cloud + self-hosted),
|
|
1473
|
+
* Gogs, Forgejo, and Codeberg.
|
|
1474
|
+
* Falls back to Gitea-style for unknown hosts — override via repo.path.
|
|
1475
|
+
*/
|
|
1476
|
+
function detectPlatformPath(url) {
|
|
1477
|
+
try {
|
|
1478
|
+
const host = new URL(url).hostname;
|
|
1479
|
+
if (host === "github.com") return "blob/main/{filePath}";
|
|
1480
|
+
if (host === "gitlab.com") return "-/blob/main/{filePath}";
|
|
1481
|
+
if (host === "bitbucket.org") return "src/main/{filePath}";
|
|
1482
|
+
if (host === "gitea.com") return "src/branch/main/{filePath}";
|
|
1483
|
+
if (host === "codeberg.org") return "src/branch/main/{filePath}";
|
|
1484
|
+
return "src/branch/main/{filePath}";
|
|
1485
|
+
} catch {
|
|
1486
|
+
return "blob/main/{filePath}";
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
function isEditEnabled() {
|
|
1490
|
+
return docuConfig$3?.repo?.edit ?? false;
|
|
1491
|
+
}
|
|
1492
|
+
function getRepoUrl() {
|
|
1493
|
+
return docuConfig$3?.repo?.url || "";
|
|
1494
|
+
}
|
|
1495
|
+
function getSocialLinks() {
|
|
1496
|
+
return docuConfig$3?.footer?.social || [];
|
|
1497
|
+
}
|
|
1498
|
+
//#endregion
|
|
1499
|
+
//#region .docu/components/EditWith.tsx
|
|
1500
|
+
function EditWith({ filePath, text = "Edit this page", className = "" }) {
|
|
1501
|
+
if (!isEditEnabled()) return null;
|
|
1502
|
+
const repoUrl = getRepoUrl();
|
|
1503
|
+
if (!repoUrl) return null;
|
|
1504
|
+
const editUrl = getEditLink(repoUrl, filePath);
|
|
1505
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1506
|
+
className: `text-right text-sm ${className}`,
|
|
1507
|
+
children: /* @__PURE__ */ jsxs("a", {
|
|
1508
|
+
href: editUrl,
|
|
1509
|
+
target: "_blank",
|
|
1510
|
+
rel: "noopener noreferrer",
|
|
1511
|
+
"aria-label": "Edit this page",
|
|
1512
|
+
className: "flex items-center gap-1 no-underline",
|
|
1513
|
+
children: [/* @__PURE__ */ jsx(SquarePen, { className: "h-4 w-4" }), text]
|
|
1514
|
+
})
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
//#endregion
|
|
1518
|
+
//#region .docu/components/Social.tsx
|
|
1519
|
+
function createIcon(pathData) {
|
|
1520
|
+
return function Icon({ className = "", size = 20 }) {
|
|
1521
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
1522
|
+
className,
|
|
1523
|
+
width: size,
|
|
1524
|
+
height: size,
|
|
1525
|
+
viewBox: "0 0 24 24",
|
|
1526
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1527
|
+
fill: "currentColor",
|
|
1528
|
+
children: /* @__PURE__ */ jsx("path", { d: pathData })
|
|
1529
|
+
});
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
var GithubIcon = createIcon("M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12");
|
|
1533
|
+
var BitbucketIcon = createIcon("M.778 1.213a.768.768 0 00-.768.892l3.263 19.81c.084.5.515.868 1.022.873H19.95a.772.772 0 00.77-.646l3.27-20.03a.768.768 0 00-.768-.891zM14.52 15.53H9.522L8.17 8.466h7.561z");
|
|
1534
|
+
var GitlabIcon = createIcon("m23.6004 9.5927-.0337-.0862L20.3.9814a.851.851 0 0 0-.3362-.405.8748.8748 0 0 0-.9997.0539.8748.8748 0 0 0-.29.4399l-2.2055 6.748H7.5375l-2.2057-6.748a.8573.8573 0 0 0-.29-.4412.8748.8748 0 0 0-.9997-.0537.8585.8585 0 0 0-.3362.4049L.4332 9.5015l-.0325.0862a6.0657 6.0657 0 0 0 2.0119 7.0105l.0113.0087.03.0213 4.976 3.7264 2.462 1.8633 1.4995 1.1321a1.0085 1.0085 0 0 0 1.2197 0l1.4995-1.1321 2.4619-1.8633 5.006-3.7489.0125-.01a6.0682 6.0682 0 0 0 2.0094-7.003z");
|
|
1535
|
+
var NpmIcon = createIcon("M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z");
|
|
1536
|
+
var YoutubeIcon = createIcon("M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z");
|
|
1537
|
+
var TwitterIcon = createIcon("M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z");
|
|
1538
|
+
var iconMap = {
|
|
1539
|
+
github: GithubIcon,
|
|
1540
|
+
gitlab: GitlabIcon,
|
|
1541
|
+
bitbucket: BitbucketIcon,
|
|
1542
|
+
npm: NpmIcon,
|
|
1543
|
+
youtube: YoutubeIcon,
|
|
1544
|
+
twitter: TwitterIcon,
|
|
1545
|
+
x: TwitterIcon,
|
|
1546
|
+
instagram: createIcon("M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077"),
|
|
1547
|
+
linkedin: createIcon("M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.528zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.226.792 24 1.771 24h20.451C23.2 24 24 23.226 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"),
|
|
1548
|
+
facebook: createIcon("M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z"),
|
|
1549
|
+
telegram: createIcon("M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"),
|
|
1550
|
+
discord: createIcon("M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"),
|
|
1551
|
+
threads: createIcon("M12.186 24h-.007c-3.581-.024-6.334-1.205-8.184-3.509C2.35 18.44 1.5 15.586 1.472 12.01v-.017c.03-3.579.879-6.43 2.525-8.482C5.845 1.205 8.6.024 12.18 0h.014c2.746.02 5.043.725 6.826 2.098 1.677 1.29 2.858 3.13 3.509 5.467l-2.04.569c-1.104-3.96-3.898-5.984-8.304-6.015-2.91.022-5.11.936-6.54 2.717C4.307 6.504 3.616 8.914 3.589 12c.027 3.086.718 5.496 2.057 7.164 1.43 1.783 3.631 2.698 6.54 2.717 2.623-.02 4.358-.631 5.8-2.045 1.647-1.613 1.618-3.593 1.09-4.798-.31-.71-.873-1.3-1.634-1.75-.192 1.352-.622 2.446-1.284 3.272-.886 1.102-2.14 1.704-3.73 1.79-1.202.065-2.361-.218-3.259-.801-1.063-.689-1.685-1.74-1.752-2.964-.065-1.19.408-2.285 1.33-3.082.88-.76 2.119-1.207 3.583-1.291a13.853 13.853 0 0 1 3.02.142c-.126-.742-.375-1.332-.75-1.757-.513-.586-1.308-.883-2.359-.89h-.029c-.844 0-1.992.232-2.721 1.32L7.734 7.847c.98-1.454 2.568-2.256 4.478-2.256h.044c3.194.02 5.097 1.975 5.287 5.388.108.046.216.094.321.142 1.49.7 2.58 1.761 3.154 3.07.797 1.82.871 4.79-1.548 7.158-1.85 1.81-4.094 2.628-7.277 2.65Zm1.003-11.69c-.242 0-.487.007-.739.021-1.836.103-2.98.946-2.916 2.143.067 1.256 1.452 1.839 2.784 1.767 1.224-.065 2.818-.543 3.086-3.71a10.5 10.5 0 0 0-2.215-.221z"),
|
|
1552
|
+
mastodon: createIcon("M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z")
|
|
1553
|
+
};
|
|
1554
|
+
function getSocialIcon(name) {
|
|
1555
|
+
return iconMap[name.toLowerCase().replace(/\s+/g, "")] || null;
|
|
1556
|
+
}
|
|
1557
|
+
function Social({ className = "", size = 16 }) {
|
|
1558
|
+
const socialLinks = getSocialLinks();
|
|
1559
|
+
if (!socialLinks?.length) return null;
|
|
1560
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1561
|
+
className: `flex gap-1 ${className}`,
|
|
1562
|
+
children: socialLinks.map((link) => {
|
|
1563
|
+
const Icon = getSocialIcon(link.name);
|
|
1564
|
+
return /* @__PURE__ */ jsx("a", {
|
|
1565
|
+
href: link.url,
|
|
1566
|
+
target: "_blank",
|
|
1567
|
+
rel: "noopener noreferrer",
|
|
1568
|
+
"aria-label": link.name,
|
|
1569
|
+
className: "btn-xs btn-circle btn-ghost text-muted-foreground",
|
|
1570
|
+
children: Icon && /* @__PURE__ */ jsx(Icon, { size })
|
|
1571
|
+
}, link.name);
|
|
1572
|
+
})
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
//#endregion
|
|
1576
|
+
//#region .docu/components/Footer.tsx
|
|
1577
|
+
function Footer() {
|
|
1578
|
+
return /* @__PURE__ */ jsxs("footer", {
|
|
1579
|
+
className: "text-muted-foreground mt-auto flex w-full flex-col items-start gap-4 py-6 sm:flex-row sm:items-center sm:justify-between",
|
|
1580
|
+
children: [/* @__PURE__ */ jsx(Social, {}), /* @__PURE__ */ jsx("aside", {
|
|
1581
|
+
className: "sm:ml-auto",
|
|
1582
|
+
children: /* @__PURE__ */ jsxs("p", {
|
|
1583
|
+
className: "text-xs",
|
|
1584
|
+
children: [
|
|
1585
|
+
"Made with",
|
|
1586
|
+
" ",
|
|
1587
|
+
/* @__PURE__ */ jsx("a", {
|
|
1588
|
+
href: "https://docubook.pro",
|
|
1589
|
+
target: "_blank",
|
|
1590
|
+
rel: "noopener noreferrer",
|
|
1591
|
+
className: "link link-hover text-muted-foreground font-medium",
|
|
1592
|
+
children: "DocuBook"
|
|
1593
|
+
})
|
|
1594
|
+
]
|
|
1595
|
+
})
|
|
1596
|
+
})]
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
//#endregion
|
|
1600
|
+
//#region .docu/components/ScrollTo.tsx
|
|
1601
|
+
function ScrollTo({ className, showIcon = true, onScrollToTop }) {
|
|
1602
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
1603
|
+
const checkScroll = useCallback(() => {
|
|
1604
|
+
const container = document.getElementById("scroll-container");
|
|
1605
|
+
const shouldShow = (container ? container.scrollTop : window.scrollY) > (container ? container.scrollHeight : document.documentElement.scrollHeight) * .3;
|
|
1606
|
+
if (shouldShow !== isVisible) setIsVisible(shouldShow);
|
|
1607
|
+
}, [isVisible]);
|
|
1608
|
+
useEffect(() => {
|
|
1609
|
+
let timeoutId;
|
|
1610
|
+
const handleScroll = () => {
|
|
1611
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1612
|
+
timeoutId = setTimeout(checkScroll, 100);
|
|
1613
|
+
};
|
|
1614
|
+
const container = document.getElementById("scroll-container") || window;
|
|
1615
|
+
container.addEventListener("scroll", handleScroll, { passive: true });
|
|
1616
|
+
return () => {
|
|
1617
|
+
container.removeEventListener("scroll", handleScroll);
|
|
1618
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1619
|
+
};
|
|
1620
|
+
}, [checkScroll]);
|
|
1621
|
+
const scrollToTop = useCallback((e) => {
|
|
1622
|
+
e.preventDefault();
|
|
1623
|
+
onScrollToTop?.();
|
|
1624
|
+
history.replaceState(null, "", "#top");
|
|
1625
|
+
const container = document.getElementById("scroll-container");
|
|
1626
|
+
if (container) container.scrollTo({
|
|
1627
|
+
top: 0,
|
|
1628
|
+
behavior: "smooth"
|
|
1629
|
+
});
|
|
1630
|
+
else window.scrollTo({
|
|
1631
|
+
top: 0,
|
|
1632
|
+
behavior: "smooth"
|
|
1633
|
+
});
|
|
1634
|
+
}, [onScrollToTop]);
|
|
1635
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1636
|
+
className: cn("border-base-300 mt-4 border-t pt-4", "transition-opacity duration-300", isVisible ? "opacity-100" : "pointer-events-none opacity-0", className),
|
|
1637
|
+
children: /* @__PURE__ */ jsxs("a", {
|
|
1638
|
+
href: "#top",
|
|
1639
|
+
onClick: scrollToTop,
|
|
1640
|
+
className: cn("inline-flex items-center text-sm", "link link-hover text-base-content/60 hover:text-base-content", "transition-all duration-200 hover:translate-y-px"),
|
|
1641
|
+
"aria-label": "Scroll to top",
|
|
1642
|
+
children: [showIcon && /* @__PURE__ */ jsx(ArrowUpIcon, { className: "mr-1 h-3.5 w-3.5 shrink-0" }), /* @__PURE__ */ jsx("span", { children: "Scroll to Top" })]
|
|
1643
|
+
})
|
|
1644
|
+
});
|
|
1645
|
+
}
|
|
1646
|
+
//#endregion
|
|
1647
|
+
//#region .docu/components/Toc.tsx
|
|
1648
|
+
function Toc({ tocs }) {
|
|
1649
|
+
const [activeId, setActiveId] = useState(null);
|
|
1650
|
+
const clickedIdRef = useRef(null);
|
|
1651
|
+
const clickTimerRef = useRef(null);
|
|
1652
|
+
const activeIdRef = useRef(null);
|
|
1653
|
+
useEffect(() => {
|
|
1654
|
+
activeIdRef.current = activeId;
|
|
1655
|
+
}, [activeId]);
|
|
1656
|
+
const getAnchorOffset = useCallback(() => {
|
|
1657
|
+
if (typeof window === "undefined") return 100;
|
|
1658
|
+
const first = tocs[0] ? document.getElementById(tocs[0].href.slice(1)) : null;
|
|
1659
|
+
return (first ? parseFloat(getComputedStyle(first).scrollMarginTop) : 0) || 100;
|
|
1660
|
+
}, [tocs]);
|
|
1661
|
+
useEffect(() => {
|
|
1662
|
+
if (typeof window === "undefined" || window.innerWidth < 1024) return;
|
|
1663
|
+
if (!tocs.length) return;
|
|
1664
|
+
const container = window.innerWidth >= 1024 ? document.getElementById("scroll-container") : null;
|
|
1665
|
+
const scrollTarget = container || window;
|
|
1666
|
+
const offset = getAnchorOffset();
|
|
1667
|
+
const handleScroll = () => {
|
|
1668
|
+
if (clickedIdRef.current) return;
|
|
1669
|
+
let currentId = null;
|
|
1670
|
+
for (const toc of tocs) {
|
|
1671
|
+
const id = toc.href.slice(1);
|
|
1672
|
+
const el = document.getElementById(id);
|
|
1673
|
+
if (!el) continue;
|
|
1674
|
+
if ((container ? el.offsetTop - container.scrollTop : el.getBoundingClientRect().top) <= offset) currentId = id;
|
|
1675
|
+
else break;
|
|
1676
|
+
}
|
|
1677
|
+
if (currentId !== activeIdRef.current) {
|
|
1678
|
+
setActiveId(currentId);
|
|
1679
|
+
history.replaceState(null, "", currentId ? `#${currentId}` : "#top");
|
|
1680
|
+
}
|
|
1681
|
+
};
|
|
1682
|
+
handleScroll();
|
|
1683
|
+
let throttleTimer = null;
|
|
1684
|
+
const listener = tocs.length > 30 ? () => {
|
|
1685
|
+
if (throttleTimer) return;
|
|
1686
|
+
throttleTimer = setTimeout(() => {
|
|
1687
|
+
throttleTimer = null;
|
|
1688
|
+
handleScroll();
|
|
1689
|
+
}, 50);
|
|
1690
|
+
} : handleScroll;
|
|
1691
|
+
scrollTarget.addEventListener("scroll", listener, { passive: true });
|
|
1692
|
+
return () => {
|
|
1693
|
+
scrollTarget.removeEventListener("scroll", listener);
|
|
1694
|
+
if (throttleTimer) clearTimeout(throttleTimer);
|
|
1695
|
+
};
|
|
1696
|
+
}, [tocs, getAnchorOffset]);
|
|
1697
|
+
const handleLinkClick = useCallback((id) => {
|
|
1698
|
+
clickedIdRef.current = id;
|
|
1699
|
+
setActiveId(id);
|
|
1700
|
+
history.replaceState(null, "", `#${id}`);
|
|
1701
|
+
if (clickTimerRef.current) clearTimeout(clickTimerRef.current);
|
|
1702
|
+
clickTimerRef.current = setTimeout(() => {
|
|
1703
|
+
clickedIdRef.current = null;
|
|
1704
|
+
}, 1e3);
|
|
1705
|
+
}, []);
|
|
1706
|
+
const handleScrollToTop = useCallback(() => {
|
|
1707
|
+
clickedIdRef.current = "__top__";
|
|
1708
|
+
setActiveId(null);
|
|
1709
|
+
history.replaceState(null, "", "#top");
|
|
1710
|
+
if (clickTimerRef.current) clearTimeout(clickTimerRef.current);
|
|
1711
|
+
clickTimerRef.current = setTimeout(() => {
|
|
1712
|
+
clickedIdRef.current = null;
|
|
1713
|
+
}, 1e3);
|
|
1714
|
+
}, []);
|
|
1715
|
+
useEffect(() => {
|
|
1716
|
+
return () => {
|
|
1717
|
+
if (clickTimerRef.current) clearTimeout(clickTimerRef.current);
|
|
1718
|
+
};
|
|
1719
|
+
}, []);
|
|
1720
|
+
const activeItemRef = useRef(null);
|
|
1721
|
+
useEffect(() => {
|
|
1722
|
+
if (activeId && activeItemRef.current && !clickedIdRef.current) activeItemRef.current.scrollIntoView({
|
|
1723
|
+
block: "nearest",
|
|
1724
|
+
behavior: "smooth"
|
|
1725
|
+
});
|
|
1726
|
+
}, [activeId]);
|
|
1727
|
+
if (!tocs.length) return null;
|
|
1728
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1729
|
+
className: "flex h-full min-h-0 w-full flex-col gap-2",
|
|
1730
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1731
|
+
className: "flex shrink-0 items-center gap-2",
|
|
1732
|
+
children: [/* @__PURE__ */ jsx(ListIcon, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("h3", {
|
|
1733
|
+
className: "text-sm font-medium",
|
|
1734
|
+
children: "On this page"
|
|
1735
|
+
})]
|
|
1736
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1737
|
+
className: "relative min-h-0 flex-1 overflow-y-auto overscroll-contain pr-1",
|
|
1738
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1739
|
+
className: "relative text-sm",
|
|
1740
|
+
children: [/* @__PURE__ */ jsx("div", { className: "bg-base-300 absolute top-0 left-0 h-full w-px" }), /* @__PURE__ */ jsx("div", {
|
|
1741
|
+
className: "flex flex-col",
|
|
1742
|
+
children: tocs.map(({ href, level, text }) => {
|
|
1743
|
+
const id = href.slice(1);
|
|
1744
|
+
const isActive = activeId === id;
|
|
1745
|
+
const levelPadding = (level - 2) * 16;
|
|
1746
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1747
|
+
ref: isActive ? activeItemRef : void 0,
|
|
1748
|
+
className: cn("relative flex items-center transition-all duration-200", isActive && "bg-primary/5"),
|
|
1749
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1750
|
+
className: cn("flex shrink-0 items-center px-1 py-2 transition-all duration-200", isActive && "border-primary -ml-px border-l-[3px]"),
|
|
1751
|
+
children: [/* @__PURE__ */ jsx("div", { className: cn("h-px transition-colors duration-200", isActive ? "bg-primary w-3" : "bg-base-300 w-2") }), /* @__PURE__ */ jsx("div", { className: cn("h-1.5 w-1.5 shrink-0 rounded-full transition-colors duration-300", isActive ? "bg-primary" : "bg-base-300") })]
|
|
1752
|
+
}), /* @__PURE__ */ jsx("a", {
|
|
1753
|
+
href,
|
|
1754
|
+
onClick: (e) => {
|
|
1755
|
+
e.preventDefault();
|
|
1756
|
+
handleLinkClick(id);
|
|
1757
|
+
const el = document.getElementById(id);
|
|
1758
|
+
if (el) {
|
|
1759
|
+
const offset = getAnchorOffset();
|
|
1760
|
+
const scroller = window.innerWidth >= 1024 ? document.getElementById("scroll-container") : null;
|
|
1761
|
+
const elTop = el.getBoundingClientRect().top;
|
|
1762
|
+
if (scroller) scroller.scrollTo({
|
|
1763
|
+
top: elTop - scroller.getBoundingClientRect().top + scroller.scrollTop - offset,
|
|
1764
|
+
behavior: "smooth"
|
|
1765
|
+
});
|
|
1766
|
+
else window.scrollTo({
|
|
1767
|
+
top: elTop + window.scrollY - offset,
|
|
1768
|
+
behavior: "smooth"
|
|
1769
|
+
});
|
|
1770
|
+
}
|
|
1771
|
+
},
|
|
1772
|
+
className: cn("flex flex-1 items-center py-2 transition-all duration-200", isActive ? "text-primary font-medium" : "text-base-content/60 hover:text-base-content"),
|
|
1773
|
+
style: { paddingLeft: `${levelPadding + 6}px` },
|
|
1774
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
1775
|
+
className: "line-clamp-2 text-sm break-words",
|
|
1776
|
+
children: text
|
|
1777
|
+
})
|
|
1778
|
+
})]
|
|
1779
|
+
}, href);
|
|
1780
|
+
})
|
|
1781
|
+
})]
|
|
1782
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1783
|
+
className: "mt-2 grid grid-cols-[28px_minmax(0,1fr)] items-start px-4 text-sm",
|
|
1784
|
+
children: [/* @__PURE__ */ jsx("div", { "aria-hidden": "true" }), /* @__PURE__ */ jsx(ScrollTo, {
|
|
1785
|
+
className: "mt-0",
|
|
1786
|
+
onScrollToTop: handleScrollToTop
|
|
1787
|
+
})]
|
|
1788
|
+
})]
|
|
1789
|
+
})]
|
|
1790
|
+
});
|
|
1791
|
+
}
|
|
1792
|
+
//#endregion
|
|
1793
|
+
//#region .docu/pages/docs/[[...slug]].tsx
|
|
1794
|
+
function DocsPage({ slug, title, description, date, content, tocs, filePath, repoUrl, mdxSlug, compiledSource }) {
|
|
1795
|
+
const pathname = slug.join("/");
|
|
1796
|
+
const tocsJson = JSON.stringify(tocs);
|
|
1797
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1798
|
+
className: "flex w-full flex-1 px-0 pb-4 lg:h-[calc(100vh-4rem)] lg:px-8 lg:pb-8",
|
|
1799
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1800
|
+
id: "scroll-container",
|
|
1801
|
+
className: "bg-base-100 border-base-300 relative flex w-full flex-col items-start rounded-b-3xl border shadow-md max-lg:scroll-p-54 lg:h-full lg:flex-row lg:overflow-y-auto lg:rounded-xl",
|
|
1802
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
1803
|
+
id: "mobile-bar-island",
|
|
1804
|
+
className: "sticky top-0 z-50 w-full lg:hidden",
|
|
1805
|
+
"data-tocs": tocsJson,
|
|
1806
|
+
"data-title": title,
|
|
1807
|
+
"data-repo": repoUrl || ""
|
|
1808
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1809
|
+
className: "flex w-full flex-col lg:flex-row 2xl:mx-auto 2xl:max-w-[1300px]",
|
|
1810
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1811
|
+
className: `w-full min-w-0 ${tocs.length > 0 ? "flex-[7]" : "mx-auto max-w-[820px]"} px-4 py-6 lg:px-12 lg:py-10`,
|
|
1812
|
+
children: [/* @__PURE__ */ jsx(DocsBreadcrumb, { paths: slug }), /* @__PURE__ */ jsxs(Typography, { children: [
|
|
1813
|
+
/* @__PURE__ */ jsx("h1", {
|
|
1814
|
+
className: "-mt-0.5 text-3xl",
|
|
1815
|
+
children: title
|
|
1816
|
+
}),
|
|
1817
|
+
description && /* @__PURE__ */ jsx("p", {
|
|
1818
|
+
className: "text-muted-foreground -mt-4 text-[16.5px]",
|
|
1819
|
+
children: description
|
|
1820
|
+
}),
|
|
1821
|
+
/* @__PURE__ */ jsx("div", {
|
|
1822
|
+
id: "mdx-content-island",
|
|
1823
|
+
"data-mdx-slug": mdxSlug,
|
|
1824
|
+
dangerouslySetInnerHTML: { __html: content }
|
|
1825
|
+
}),
|
|
1826
|
+
compiledSource && /* @__PURE__ */ jsx("script", {
|
|
1827
|
+
id: "mdx-compiled-source",
|
|
1828
|
+
type: "application/json",
|
|
1829
|
+
dangerouslySetInnerHTML: { __html: JSON.stringify(compiledSource).replace(/<\//g, "\\u003C/") }
|
|
1830
|
+
}),
|
|
1831
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1832
|
+
className: "border-base-300 my-8 flex items-center border-b-2 border-dashed",
|
|
1833
|
+
children: [/* @__PURE__ */ jsx(EditWith, {
|
|
1834
|
+
className: "text-muted-foreground",
|
|
1835
|
+
filePath
|
|
1836
|
+
}), date && /* @__PURE__ */ jsxs("p", {
|
|
1837
|
+
className: "text-muted-foreground ml-auto text-[13px]",
|
|
1838
|
+
children: ["Last updated ", formatDate2(date)]
|
|
1839
|
+
})]
|
|
1840
|
+
}),
|
|
1841
|
+
/* @__PURE__ */ jsx(Pagination, {
|
|
1842
|
+
pathname,
|
|
1843
|
+
prevIcon: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-3 w-3" }),
|
|
1844
|
+
nextIcon: /* @__PURE__ */ jsx(ChevronRight, { className: "h-3 w-3" })
|
|
1845
|
+
}),
|
|
1846
|
+
/* @__PURE__ */ jsx(Footer, {})
|
|
1847
|
+
] })]
|
|
1848
|
+
}), tocs.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
1849
|
+
id: "toc-island",
|
|
1850
|
+
"data-tocs": tocsJson,
|
|
1851
|
+
className: "sticky top-4 hidden h-[calc(100vh-8rem)] min-w-[240px] flex-[3] self-start lg:flex lg:px-4 lg:py-6",
|
|
1852
|
+
children: /* @__PURE__ */ jsx(Toc, { tocs })
|
|
1853
|
+
})]
|
|
1854
|
+
})]
|
|
1855
|
+
})
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1858
|
+
//#endregion
|
|
1859
|
+
//#region .docu/pages/404.tsx
|
|
1860
|
+
function NotFoundPage() {
|
|
1861
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1862
|
+
className: "flex w-full flex-1 px-4 py-8 lg:h-[calc(100vh-4rem)] lg:px-8 lg:py-4",
|
|
1863
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1864
|
+
className: "bg-base-100 border-base-300 flex min-h-[50vh] w-full flex-col items-center justify-center rounded-xl border shadow-md lg:min-h-0 lg:flex-1",
|
|
1865
|
+
children: [
|
|
1866
|
+
/* @__PURE__ */ jsx("h1", {
|
|
1867
|
+
className: "text-6xl font-bold",
|
|
1868
|
+
children: "404"
|
|
1869
|
+
}),
|
|
1870
|
+
/* @__PURE__ */ jsx("p", {
|
|
1871
|
+
className: "text-base-content/60 py-4 text-xl",
|
|
1872
|
+
children: "Page not found"
|
|
1873
|
+
}),
|
|
1874
|
+
/* @__PURE__ */ jsx("a", {
|
|
1875
|
+
href: "/docs/",
|
|
1876
|
+
className: "btn btn-primary mt-2",
|
|
1877
|
+
children: "Go to Docs"
|
|
1878
|
+
})
|
|
1879
|
+
]
|
|
1880
|
+
})
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
//#endregion
|
|
1884
|
+
//#region .docu/components/Lucide.tsx
|
|
1885
|
+
/**
|
|
1886
|
+
* Get Lucide icon component by name
|
|
1887
|
+
* Icon names must match Lucide's export names exactly (e.g., "Zap", "BookOpen", "Search")
|
|
1888
|
+
*/
|
|
1889
|
+
function getLucideIcon(name) {
|
|
1890
|
+
if (!name) return null;
|
|
1891
|
+
return LucideIcons[name] || null;
|
|
1892
|
+
}
|
|
1893
|
+
/**
|
|
1894
|
+
* Render Lucide icon component
|
|
1895
|
+
*/
|
|
1896
|
+
function renderLucideIcon(name, className) {
|
|
1897
|
+
const Icon = getLucideIcon(name);
|
|
1898
|
+
return Icon ? /* @__PURE__ */ jsx(Icon, { className }) : null;
|
|
1899
|
+
}
|
|
1900
|
+
//#endregion
|
|
1901
|
+
//#region .docu/components/home/Hero.tsx
|
|
1902
|
+
function isExternalLink(link) {
|
|
1903
|
+
return /^https?:\/\//.test(link);
|
|
1904
|
+
}
|
|
1905
|
+
function renderActionButtonIcon(iconName, className) {
|
|
1906
|
+
if (!iconName) return null;
|
|
1907
|
+
const lucideIcon = renderLucideIcon(iconName, className);
|
|
1908
|
+
if (lucideIcon) return lucideIcon;
|
|
1909
|
+
const SocialIcon = getSocialIcon(iconName);
|
|
1910
|
+
if (SocialIcon) return /* @__PURE__ */ jsx(SocialIcon, { className });
|
|
1911
|
+
return null;
|
|
1912
|
+
}
|
|
1913
|
+
function ActionButton({ action }) {
|
|
1914
|
+
const themeClasses = {
|
|
1915
|
+
primary: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
1916
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/90",
|
|
1917
|
+
ghost: "bg-transparent text-muted-foreground border border-base-300 hover:bg-base-200"
|
|
1918
|
+
};
|
|
1919
|
+
const isExternal = isExternalLink(action.link);
|
|
1920
|
+
return /* @__PURE__ */ jsxs("a", {
|
|
1921
|
+
href: action.link,
|
|
1922
|
+
target: isExternal ? "_blank" : void 0,
|
|
1923
|
+
rel: isExternal ? "noopener noreferrer" : void 0,
|
|
1924
|
+
className: cn("inline-flex items-center gap-2 rounded-lg px-6 py-3 text-sm font-medium transition-colors", themeClasses[action.theme || "primary"]),
|
|
1925
|
+
children: [renderActionButtonIcon(action.icon, "h-4 w-4"), action.text]
|
|
1926
|
+
});
|
|
1927
|
+
}
|
|
1928
|
+
function Hero({ hero, className }) {
|
|
1929
|
+
const { tagline, headline, description, actions } = hero;
|
|
1930
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1931
|
+
className: cn("mx-auto max-w-4xl px-6 py-32 sm:py-44", className),
|
|
1932
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1933
|
+
className: "text-center",
|
|
1934
|
+
children: [
|
|
1935
|
+
tagline && /* @__PURE__ */ jsx("p", {
|
|
1936
|
+
className: "text-primary mb-4 text-lg font-semibold",
|
|
1937
|
+
children: tagline
|
|
1938
|
+
}),
|
|
1939
|
+
/* @__PURE__ */ jsx("h1", {
|
|
1940
|
+
className: "text-5xl font-semibold tracking-tight text-balance sm:text-7xl",
|
|
1941
|
+
children: headline
|
|
1942
|
+
}),
|
|
1943
|
+
description && /* @__PURE__ */ jsx("p", {
|
|
1944
|
+
className: "text-muted-foreground mt-8 text-lg text-pretty sm:text-xl",
|
|
1945
|
+
children: description
|
|
1946
|
+
}),
|
|
1947
|
+
actions && actions.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
1948
|
+
className: "mt-10 flex flex-wrap items-center justify-center gap-4",
|
|
1949
|
+
children: actions.map((action, index) => /* @__PURE__ */ jsx(ActionButton, { action }, index))
|
|
1950
|
+
})
|
|
1951
|
+
]
|
|
1952
|
+
})
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
//#endregion
|
|
1956
|
+
//#region .docu/components/home/Features.tsx
|
|
1957
|
+
function FeatureCard({ feature, index }) {
|
|
1958
|
+
const Wrapper = feature.link ? "a" : "div";
|
|
1959
|
+
const wrapperProps = feature.link ? { href: feature.link } : {};
|
|
1960
|
+
const patternId = `grid-${index}`;
|
|
1961
|
+
return /* @__PURE__ */ jsxs(Wrapper, {
|
|
1962
|
+
...wrapperProps,
|
|
1963
|
+
className: cn("border-base-200 bg-base-100 hover:border-primary/40 group relative overflow-hidden rounded-2xl border p-6 transition-all hover:shadow-lg", feature.link && "cursor-pointer"),
|
|
1964
|
+
children: [/* @__PURE__ */ jsxs("svg", {
|
|
1965
|
+
className: "absolute inset-0 h-full w-full",
|
|
1966
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1967
|
+
style: { color: "var(--color-primary)" },
|
|
1968
|
+
"aria-hidden": "true",
|
|
1969
|
+
children: [/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("pattern", {
|
|
1970
|
+
id: patternId,
|
|
1971
|
+
width: "40",
|
|
1972
|
+
height: "40",
|
|
1973
|
+
patternUnits: "userSpaceOnUse",
|
|
1974
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
1975
|
+
d: "M 40 0 L 0 0 0 40",
|
|
1976
|
+
fill: "none",
|
|
1977
|
+
stroke: "currentColor",
|
|
1978
|
+
strokeWidth: "0.5",
|
|
1979
|
+
opacity: "0.15"
|
|
1980
|
+
})
|
|
1981
|
+
}) }), /* @__PURE__ */ jsx("rect", {
|
|
1982
|
+
width: "100%",
|
|
1983
|
+
height: "100%",
|
|
1984
|
+
fill: `url(#${patternId})`
|
|
1985
|
+
})]
|
|
1986
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1987
|
+
className: "relative z-10",
|
|
1988
|
+
children: [
|
|
1989
|
+
feature.icon && /* @__PURE__ */ jsx("div", {
|
|
1990
|
+
className: "bg-primary/10 mb-4 inline-flex h-12 w-12 items-center justify-center rounded-lg",
|
|
1991
|
+
children: renderLucideIcon(feature.icon, "h-6 w-6 text-primary")
|
|
1992
|
+
}),
|
|
1993
|
+
/* @__PURE__ */ jsx("h3", {
|
|
1994
|
+
className: "mb-2 text-lg font-semibold",
|
|
1995
|
+
children: feature.title
|
|
1996
|
+
}),
|
|
1997
|
+
/* @__PURE__ */ jsx("p", {
|
|
1998
|
+
className: "text-muted-foreground text-sm",
|
|
1999
|
+
children: feature.description
|
|
2000
|
+
})
|
|
2001
|
+
]
|
|
2002
|
+
})]
|
|
2003
|
+
});
|
|
2004
|
+
}
|
|
2005
|
+
function Features({ features, className }) {
|
|
2006
|
+
if (!features || features.length === 0) return null;
|
|
2007
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2008
|
+
className: cn("mx-auto max-w-5xl px-6 pb-24", className),
|
|
2009
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
2010
|
+
className: "grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3",
|
|
2011
|
+
children: features.map((feature, index) => /* @__PURE__ */ jsx(FeatureCard, {
|
|
2012
|
+
feature,
|
|
2013
|
+
index
|
|
2014
|
+
}, index))
|
|
2015
|
+
})
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
//#endregion
|
|
2019
|
+
//#region .docu/pages/index.tsx
|
|
2020
|
+
var docuConfig$2 = loadDocuConfig();
|
|
2021
|
+
function IndexPage() {
|
|
2022
|
+
const { meta, home } = docuConfig$2;
|
|
2023
|
+
const routes = docuConfig$2.routes || [];
|
|
2024
|
+
const linkWithHtml = (link) => {
|
|
2025
|
+
if (isExternalUrl(link)) return link;
|
|
2026
|
+
if (link.startsWith("/docs/")) return `${link}.html`;
|
|
2027
|
+
return link;
|
|
2028
|
+
};
|
|
2029
|
+
const features = home?.features?.map((f) => ({
|
|
2030
|
+
...f,
|
|
2031
|
+
link: f.link ? linkWithHtml(f.link) : void 0
|
|
2032
|
+
})) || routes.filter((r) => r.context).map((route) => ({
|
|
2033
|
+
icon: route.context?.icon,
|
|
2034
|
+
title: route.context?.title || route.title,
|
|
2035
|
+
description: route.context?.description || "",
|
|
2036
|
+
link: docsHtmlHref(`/docs${route.href}${route.items?.[0]?.href || ""}`)
|
|
2037
|
+
}));
|
|
2038
|
+
const hero = home?.hero ? {
|
|
2039
|
+
...home.hero,
|
|
2040
|
+
actions: home.hero.actions?.map((a) => ({
|
|
2041
|
+
...a,
|
|
2042
|
+
link: linkWithHtml(a.link)
|
|
2043
|
+
}))
|
|
2044
|
+
} : {
|
|
2045
|
+
headline: meta.title,
|
|
2046
|
+
description: meta.description
|
|
2047
|
+
};
|
|
2048
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2049
|
+
className: "bg-base-100 relative isolate min-h-screen overflow-hidden",
|
|
2050
|
+
children: [
|
|
2051
|
+
/* @__PURE__ */ jsx("div", {
|
|
2052
|
+
className: "absolute top-4 right-4 z-10",
|
|
2053
|
+
id: "theme-island"
|
|
2054
|
+
}),
|
|
2055
|
+
/* @__PURE__ */ jsx("div", {
|
|
2056
|
+
"aria-hidden": "true",
|
|
2057
|
+
className: "pointer-events-none absolute -top-40 left-1/2 -z-10 -translate-x-1/2 blur-3xl sm:-top-80",
|
|
2058
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
2059
|
+
style: { clipPath: "polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)" },
|
|
2060
|
+
className: "from-primary to-accent h-[40rem] w-[80rem] bg-gradient-to-tr opacity-20"
|
|
2061
|
+
})
|
|
2062
|
+
}),
|
|
2063
|
+
/* @__PURE__ */ jsx(Hero, { hero }),
|
|
2064
|
+
/* @__PURE__ */ jsx(Features, { features }),
|
|
2065
|
+
/* @__PURE__ */ jsx("div", {
|
|
2066
|
+
"aria-hidden": "true",
|
|
2067
|
+
className: "pointer-events-none absolute bottom-0 left-1/2 -z-10 translate-x-1/4 blur-3xl",
|
|
2068
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
2069
|
+
style: { clipPath: "polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)" },
|
|
2070
|
+
className: "from-accent to-primary h-[30rem] w-[70rem] bg-gradient-to-tr opacity-20"
|
|
2071
|
+
})
|
|
2072
|
+
})
|
|
2073
|
+
]
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2076
|
+
//#endregion
|
|
2077
|
+
//#region .docu/components/Anchor.tsx
|
|
2078
|
+
function Anchor({ href = "", className = "", activeClassName = "", activeWhen, disabled = false, children, ...props }) {
|
|
2079
|
+
const isActive = (() => {
|
|
2080
|
+
if (!activeWhen || typeof window === "undefined") return false;
|
|
2081
|
+
const pathname = window.location.pathname;
|
|
2082
|
+
if (typeof activeWhen === "string") return pathname === activeWhen || pathname.endsWith(activeWhen);
|
|
2083
|
+
if (activeWhen instanceof RegExp) return activeWhen.test(pathname);
|
|
2084
|
+
if (typeof activeWhen === "function") return activeWhen(pathname);
|
|
2085
|
+
return false;
|
|
2086
|
+
})();
|
|
2087
|
+
const isExternal = isExternalUrl(href);
|
|
2088
|
+
const baseClasses = cn("hover:underline transition-colors", className, isActive ? activeClassName : "", disabled && "cursor-not-allowed opacity-50");
|
|
2089
|
+
if (disabled) return /* @__PURE__ */ jsx("span", {
|
|
2090
|
+
className: baseClasses,
|
|
2091
|
+
children
|
|
2092
|
+
});
|
|
2093
|
+
if (isExternal) return /* @__PURE__ */ jsxs("a", {
|
|
2094
|
+
href,
|
|
2095
|
+
className: baseClasses,
|
|
2096
|
+
target: "_blank",
|
|
2097
|
+
rel: "noopener noreferrer",
|
|
2098
|
+
...props,
|
|
2099
|
+
children: [children, /* @__PURE__ */ jsx(ArrowUpRight, { className: "ml-0.5 inline-block h-3.5 w-3.5" })]
|
|
2100
|
+
});
|
|
2101
|
+
return /* @__PURE__ */ jsx("a", {
|
|
2102
|
+
href,
|
|
2103
|
+
className: baseClasses,
|
|
2104
|
+
...props,
|
|
2105
|
+
children
|
|
2106
|
+
});
|
|
2107
|
+
}
|
|
2108
|
+
//#endregion
|
|
2109
|
+
//#region .docu/node/client-routes.ts
|
|
2110
|
+
var docuConfig$1 = loadDocuConfig();
|
|
2111
|
+
resolveRoutes(docuConfig$1.routes || []);
|
|
2112
|
+
var config = docuConfig$1;
|
|
2113
|
+
//#endregion
|
|
2114
|
+
//#region .docu/components/Sublink.tsx
|
|
2115
|
+
/** Exclusive accordion for level >= 2 sidebar groups — opening one group
|
|
2116
|
+
* closes the previously open one. All level >= 2 groups default to closed
|
|
2117
|
+
* and expand only when the header is clicked. `open` is used to auto-expand
|
|
2118
|
+
* the group containing the active page. */
|
|
2119
|
+
var GroupAccordionContext = createContext({
|
|
2120
|
+
openId: null,
|
|
2121
|
+
open: () => {},
|
|
2122
|
+
toggle: () => {}
|
|
2123
|
+
});
|
|
2124
|
+
function GroupAccordionProvider({ children }) {
|
|
2125
|
+
const [openId, setOpenId] = useState(null);
|
|
2126
|
+
const open = useCallback((id) => setOpenId(id), []);
|
|
2127
|
+
const toggle = useCallback((id) => setOpenId((prev) => prev === id ? null : id), []);
|
|
2128
|
+
return /* @__PURE__ */ jsx(GroupAccordionContext.Provider, {
|
|
2129
|
+
value: {
|
|
2130
|
+
openId,
|
|
2131
|
+
open,
|
|
2132
|
+
toggle
|
|
2133
|
+
},
|
|
2134
|
+
children
|
|
2135
|
+
});
|
|
2136
|
+
}
|
|
2137
|
+
function Sublink({ title, href, items, noLink, level, onNavigate, parentHref = "", pathname: pathnameProp }) {
|
|
2138
|
+
const fullHref = parentHref ? `${parentHref}${href}` : `/docs${href}`;
|
|
2139
|
+
const currentPathname = pathnameProp || (typeof window !== "undefined" ? window.location.pathname : "/docs");
|
|
2140
|
+
const isSeparator = config.sidebar?.context === "separator";
|
|
2141
|
+
const { openId, open, toggle } = useContext(GroupAccordionContext);
|
|
2142
|
+
const isAccordionGroup = Boolean(items) && (isSeparator || level >= 1);
|
|
2143
|
+
const treeLevel = level + (isSeparator ? 2 : 1);
|
|
2144
|
+
const [isOpen, setIsOpen] = useState(() => {
|
|
2145
|
+
if (isAccordionGroup) return false;
|
|
2146
|
+
if (level === 0) return true;
|
|
2147
|
+
return false;
|
|
2148
|
+
});
|
|
2149
|
+
const effectiveOpen = isAccordionGroup ? openId === fullHref : isOpen;
|
|
2150
|
+
const handleToggle = () => {
|
|
2151
|
+
if (isAccordionGroup) toggle(fullHref);
|
|
2152
|
+
else setIsOpen((o) => !o);
|
|
2153
|
+
};
|
|
2154
|
+
const isInsideActive = currentPathname.startsWith(fullHref) && currentPathname !== fullHref;
|
|
2155
|
+
useEffect(() => {
|
|
2156
|
+
if (isAccordionGroup && isInsideActive) open(fullHref);
|
|
2157
|
+
}, [
|
|
2158
|
+
isAccordionGroup,
|
|
2159
|
+
isInsideActive,
|
|
2160
|
+
fullHref,
|
|
2161
|
+
open
|
|
2162
|
+
]);
|
|
2163
|
+
const levelPadding = cn(level === 1 && "pl-4", level === 2 && "pl-8", level >= 3 && "pl-12");
|
|
2164
|
+
const isActive = currentPathname === fullHref || currentPathname === `${fullHref}.html`;
|
|
2165
|
+
const activeRef = useRef(null);
|
|
2166
|
+
useEffect(() => {
|
|
2167
|
+
if (isActive && activeRef.current) activeRef.current.scrollIntoView({ block: "nearest" });
|
|
2168
|
+
}, [isActive]);
|
|
2169
|
+
if (!items) {
|
|
2170
|
+
const link = /* @__PURE__ */ jsx(Anchor, {
|
|
2171
|
+
href: docsHtmlHref(fullHref),
|
|
2172
|
+
className: "text-foreground hover:text-foreground/80 text-sm transition-colors",
|
|
2173
|
+
activeClassName: "text-primary font-medium",
|
|
2174
|
+
activeWhen: (path) => path === fullHref || path === `${fullHref}.html`,
|
|
2175
|
+
onClick: onNavigate,
|
|
2176
|
+
children: title
|
|
2177
|
+
});
|
|
2178
|
+
if (level >= 1) {
|
|
2179
|
+
if (config.sidebar?.context === "separator") {
|
|
2180
|
+
const sectionOffsets = {
|
|
2181
|
+
1: 16,
|
|
2182
|
+
2: 32
|
|
2183
|
+
};
|
|
2184
|
+
let overlap = 14;
|
|
2185
|
+
for (let l = 1; l < level; l++) overlap += sectionOffsets[l] ?? 48;
|
|
2186
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2187
|
+
ref: activeRef,
|
|
2188
|
+
className: cn("border-l-2", isActive ? "border-primary" : "border-transparent"),
|
|
2189
|
+
style: { marginLeft: `-${overlap}px` },
|
|
2190
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
2191
|
+
className: cn("py-1 pl-3", levelPadding),
|
|
2192
|
+
children: link
|
|
2193
|
+
})
|
|
2194
|
+
});
|
|
2195
|
+
}
|
|
2196
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2197
|
+
ref: activeRef,
|
|
2198
|
+
className: cn("py-1", levelPadding, "border-l-2", isActive ? "border-primary" : "border-base-300"),
|
|
2199
|
+
children: link
|
|
2200
|
+
});
|
|
2201
|
+
}
|
|
2202
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2203
|
+
ref: activeRef,
|
|
2204
|
+
className: cn("py-1", levelPadding),
|
|
2205
|
+
children: link
|
|
2206
|
+
});
|
|
2207
|
+
}
|
|
2208
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2209
|
+
ref: isActive ? activeRef : void 0,
|
|
2210
|
+
className: cn("flex flex-col", levelPadding),
|
|
2211
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
2212
|
+
type: "button",
|
|
2213
|
+
onClick: handleToggle,
|
|
2214
|
+
className: cn("flex w-full cursor-pointer items-center justify-between py-1 text-left text-sm transition-colors", noLink && treeLevel === 1 ? "text-base-content font-semibold" : "text-foreground hover:text-foreground/80"),
|
|
2215
|
+
children: [noLink ? /* @__PURE__ */ jsx("span", { children: title }) : /* @__PURE__ */ jsx(Anchor, {
|
|
2216
|
+
href: docsHtmlHref(fullHref),
|
|
2217
|
+
className: "text-foreground hover:text-foreground/80 transition-colors",
|
|
2218
|
+
activeClassName: "text-primary",
|
|
2219
|
+
activeWhen: (path) => path === fullHref || path === `${fullHref}.html`,
|
|
2220
|
+
onClick: onNavigate,
|
|
2221
|
+
children: title
|
|
2222
|
+
}), /* @__PURE__ */ jsx(ChevronDown, { className: cn("text-base-content/40 h-4 w-4 shrink-0 transition-transform duration-200", effectiveOpen ? "rotate-0" : "-rotate-90") })]
|
|
2223
|
+
}), effectiveOpen && /* @__PURE__ */ jsx("div", {
|
|
2224
|
+
className: "flex flex-col py-1",
|
|
2225
|
+
children: items.map((item) => /* @__PURE__ */ jsx(Sublink, {
|
|
2226
|
+
...item,
|
|
2227
|
+
href: item.href,
|
|
2228
|
+
level: level + 1,
|
|
2229
|
+
onNavigate,
|
|
2230
|
+
parentHref: fullHref,
|
|
2231
|
+
pathname: pathnameProp
|
|
2232
|
+
}, `${fullHref}${item.href}`))
|
|
2233
|
+
})]
|
|
2234
|
+
});
|
|
2235
|
+
}
|
|
2236
|
+
//#endregion
|
|
2237
|
+
//#region .docu/components/SidebarGroupHeader.tsx
|
|
2238
|
+
function SidebarGroupHeader({ icon, title }) {
|
|
2239
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2240
|
+
className: "sidebar-group-header mb-1.5 flex items-center gap-2.5 font-medium text-gray-900 dark:text-gray-200",
|
|
2241
|
+
children: [icon && /* @__PURE__ */ jsx("span", {
|
|
2242
|
+
className: "flex h-4 w-4 shrink-0 items-center justify-center",
|
|
2243
|
+
children: renderLucideIcon(icon, "h-3.5 w-3.5")
|
|
2244
|
+
}), /* @__PURE__ */ jsx("h3", {
|
|
2245
|
+
className: "sidebar-title font-[inherit] text-[length:inherit] leading-[inherit]",
|
|
2246
|
+
children: /* @__PURE__ */ jsx("span", { children: title })
|
|
2247
|
+
})]
|
|
2248
|
+
});
|
|
2249
|
+
}
|
|
2250
|
+
//#endregion
|
|
2251
|
+
//#region .docu/components/Menu.tsx
|
|
2252
|
+
function getCurrentContext(path) {
|
|
2253
|
+
if (!path.startsWith("/docs")) return void 0;
|
|
2254
|
+
const match = path.match(/^\/docs\/([^/]+)/);
|
|
2255
|
+
return match ? match[1] : void 0;
|
|
2256
|
+
}
|
|
2257
|
+
function getContextRoute(contextPath, routeList) {
|
|
2258
|
+
return routeList.find((route) => {
|
|
2259
|
+
return route.href.replace(/^\/+|\/+$/, "") === contextPath;
|
|
2260
|
+
});
|
|
2261
|
+
}
|
|
2262
|
+
function Menu({ onNavigate, className = "", pathname, routes = [] }) {
|
|
2263
|
+
const menuRoutes = routes;
|
|
2264
|
+
const [currentPath] = useState(() => pathname || (typeof window !== "undefined" ? window.location.pathname : "/docs"));
|
|
2265
|
+
if (!currentPath.startsWith("/docs")) return null;
|
|
2266
|
+
const mode = config.sidebar?.context || "dropdown";
|
|
2267
|
+
const navProps = {
|
|
2268
|
+
"aria-label": "Documentation navigation",
|
|
2269
|
+
className: cn("transition-all duration-200", className)
|
|
2270
|
+
};
|
|
2271
|
+
const renderBorderItem = (item, parentRouteHref, key) => {
|
|
2272
|
+
const fullHref = `/docs${parentRouteHref}${item.href}`;
|
|
2273
|
+
const isActive = currentPath === fullHref || currentPath === `${fullHref}.html`;
|
|
2274
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("div", {
|
|
2275
|
+
className: cn("-ml-[14px] border-l-2", isActive ? "border-primary" : "border-transparent"),
|
|
2276
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
2277
|
+
className: "pl-3",
|
|
2278
|
+
children: /* @__PURE__ */ jsx(Sublink, {
|
|
2279
|
+
...item,
|
|
2280
|
+
href: item.href,
|
|
2281
|
+
level: 0,
|
|
2282
|
+
onNavigate,
|
|
2283
|
+
parentHref: `/docs${parentRouteHref}`
|
|
2284
|
+
})
|
|
2285
|
+
})
|
|
2286
|
+
}) }, key);
|
|
2287
|
+
};
|
|
2288
|
+
const sharedUlClasses = "border-base-300 flex flex-col gap-0.5 border-l-2 pb-0.5 pl-3 pt-0.5";
|
|
2289
|
+
if (mode === "separator") {
|
|
2290
|
+
const contextRoutes = menuRoutes.filter((r) => r.context);
|
|
2291
|
+
if (contextRoutes.length === 0) return /* @__PURE__ */ jsx(GroupAccordionProvider, { children: /* @__PURE__ */ jsx("nav", {
|
|
2292
|
+
...navProps,
|
|
2293
|
+
children: /* @__PURE__ */ jsx("ul", {
|
|
2294
|
+
className: sharedUlClasses,
|
|
2295
|
+
children: menuRoutes.map((route) => renderBorderItem(route, "", route.href))
|
|
2296
|
+
})
|
|
2297
|
+
}) });
|
|
2298
|
+
return /* @__PURE__ */ jsx(GroupAccordionProvider, { children: /* @__PURE__ */ jsx("nav", {
|
|
2299
|
+
...navProps,
|
|
2300
|
+
children: contextRoutes.map((route, i) => /* @__PURE__ */ jsxs("div", {
|
|
2301
|
+
className: i > 0 ? "mt-6 lg:mt-8" : "",
|
|
2302
|
+
children: [/* @__PURE__ */ jsx(SidebarGroupHeader, {
|
|
2303
|
+
icon: route.context?.icon,
|
|
2304
|
+
title: route.context?.title || route.title
|
|
2305
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
2306
|
+
className: sharedUlClasses,
|
|
2307
|
+
children: route.items?.map((item) => renderBorderItem(item, route.href, item.href))
|
|
2308
|
+
})]
|
|
2309
|
+
}, route.href))
|
|
2310
|
+
}) });
|
|
2311
|
+
}
|
|
2312
|
+
const isDocsRoot = currentPath === "/docs" || currentPath === "/docs/";
|
|
2313
|
+
const currentContext = isDocsRoot ? menuRoutes[0]?.href.replace(/^\/+|\/+$/, "") : getCurrentContext(currentPath);
|
|
2314
|
+
const contextRoute = isDocsRoot && menuRoutes[0] ? currentContext ? getContextRoute(currentContext, menuRoutes) : menuRoutes[0] : currentContext ? getContextRoute(currentContext, menuRoutes) : void 0;
|
|
2315
|
+
if (!contextRoute) return null;
|
|
2316
|
+
return /* @__PURE__ */ jsx(GroupAccordionProvider, { children: /* @__PURE__ */ jsx("nav", {
|
|
2317
|
+
...navProps,
|
|
2318
|
+
children: /* @__PURE__ */ jsx("ul", {
|
|
2319
|
+
className: "flex flex-col gap-0.5 py-4",
|
|
2320
|
+
children: /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Sublink, {
|
|
2321
|
+
...contextRoute,
|
|
2322
|
+
href: contextRoute.href,
|
|
2323
|
+
level: 0,
|
|
2324
|
+
onNavigate,
|
|
2325
|
+
parentHref: "/docs"
|
|
2326
|
+
}) }, contextRoute.title)
|
|
2327
|
+
})
|
|
2328
|
+
}) });
|
|
2329
|
+
}
|
|
2330
|
+
//#endregion
|
|
2331
|
+
//#region .docu/components/DocsLayout.tsx
|
|
2332
|
+
var docuConfig = loadDocuConfig();
|
|
2333
|
+
function DocsLayout({ children, repoUrl, pathname = "/docs" }) {
|
|
2334
|
+
return React.createElement("div", { className: "docs-layout flex flex-col min-h-screen w-full" }, React.createElement("div", { className: "flex flex-1 items-start w-full" }, React.createElement("aside", {
|
|
2335
|
+
id: "sidebar-island",
|
|
2336
|
+
className: "sticky top-0 hidden h-screen w-[280px] shrink-0 flex-col lg:flex border-r border-base-200 bg-base-100",
|
|
2337
|
+
"data-tocs": "[]",
|
|
2338
|
+
"data-title": "",
|
|
2339
|
+
"data-repo": repoUrl || ""
|
|
2340
|
+
}, React.createElement("div", { className: "flex h-full flex-col overflow-y-auto px-4" }, React.createElement(Menu, {
|
|
2341
|
+
pathname,
|
|
2342
|
+
routes: docuConfig.routes || []
|
|
2343
|
+
}))), React.createElement("main", { className: "flex-1 min-w-0 min-h-screen flex flex-col" }, React.createElement("div", { className: "hidden lg:flex items-center justify-end gap-6 h-14 px-8" }, React.createElement("nav", { className: "flex items-center gap-6 text-sm font-medium text-base-content/80" }, ...(docuConfig.navbar?.menu || []).map((item) => {
|
|
2344
|
+
const isExternal = /^https?:\/\//.test(item.href);
|
|
2345
|
+
const isDocsActive = item.href === "/docs";
|
|
2346
|
+
return React.createElement("a", {
|
|
2347
|
+
key: item.title,
|
|
2348
|
+
href: item.href,
|
|
2349
|
+
className: `flex items-center gap-1 hover:text-base-content transition-colors${isDocsActive ? " text-primary font-semibold" : ""}`,
|
|
2350
|
+
...isExternal ? {
|
|
2351
|
+
target: "_blank",
|
|
2352
|
+
rel: "noopener noreferrer"
|
|
2353
|
+
} : {}
|
|
2354
|
+
}, item.title, isExternal ? React.createElement("svg", {
|
|
2355
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
2356
|
+
width: "14",
|
|
2357
|
+
height: "14",
|
|
2358
|
+
viewBox: "0 0 24 24",
|
|
2359
|
+
fill: "none",
|
|
2360
|
+
stroke: "currentColor",
|
|
2361
|
+
strokeWidth: "2",
|
|
2362
|
+
strokeLinecap: "round",
|
|
2363
|
+
strokeLinejoin: "round"
|
|
2364
|
+
}, React.createElement("path", { d: "M7 7h10v10" }), React.createElement("path", { d: "M7 17 17 7" })) : null);
|
|
2365
|
+
}))), React.createElement("div", { className: "flex-1 w-full" }, children))));
|
|
2366
|
+
}
|
|
2367
|
+
//#endregion
|
|
2368
|
+
//#region .docu/node/escapeHtml.ts
|
|
2369
|
+
var ESCAPE_RE = /[&<>"']/g;
|
|
2370
|
+
var ESCAPE_MAP = {
|
|
2371
|
+
"&": "&",
|
|
2372
|
+
"<": "<",
|
|
2373
|
+
">": ">",
|
|
2374
|
+
"\"": """,
|
|
2375
|
+
"'": "'"
|
|
2376
|
+
};
|
|
2377
|
+
/**
|
|
2378
|
+
* Runtime-neutral HTML escaping — same character set as `Bun.escapeHTML`
|
|
2379
|
+
* (& < > " '). Used by the shared HTML shell so Node/Deno entries never
|
|
2380
|
+
* touch the Bun global.
|
|
2381
|
+
*/
|
|
2382
|
+
function escapeHtml(input) {
|
|
2383
|
+
return input.replace(ESCAPE_RE, (ch) => ESCAPE_MAP[ch]);
|
|
2384
|
+
}
|
|
2385
|
+
//#endregion
|
|
2386
|
+
//#region .docu/node/html.shared.ts
|
|
2387
|
+
/**
|
|
2388
|
+
* Runtime-neutral HTML shell — identical templates to `html.ts` (Bun-only,
|
|
2389
|
+
* protected) but escaping via the pure `escapeHtml()` so it runs on
|
|
2390
|
+
* Node.js and Deno as well as Bun. Shared modules (`server-routes.ts`) and
|
|
2391
|
+
* the non-Bun entries import from here; `build.ts`/`server.ts` keep using
|
|
2392
|
+
* `html.ts` untouched.
|
|
2393
|
+
*/
|
|
2394
|
+
function htmlShell(opts) {
|
|
2395
|
+
const { title, description, body, favicon, css, js, nonce, csp, extraScripts, themeCss, depth = 0, headExtra, bodyExtra, absoluteAssets = false } = opts;
|
|
2396
|
+
const nonceAttr = nonce ? ` nonce="${escapeHtml(nonce)}"` : "";
|
|
2397
|
+
const themeStyle = themeCss ? `\n <style${nonceAttr}>${escapeHtml(themeCss)}</style>` : "";
|
|
2398
|
+
const headInjection = headExtra?.length ? `\n ${headExtra.join("\n ")}` : "";
|
|
2399
|
+
const bodyInjection = bodyExtra?.length ? `\n ${bodyExtra.join("\n ")}` : "";
|
|
2400
|
+
const depthPrefix = depth === 0 ? "" : "../".repeat(depth);
|
|
2401
|
+
const assetPrefix = absoluteAssets ? "/assets/" : depthPrefix + "assets/";
|
|
2402
|
+
const resolvePath = (path) => absoluteAssets ? path : path.startsWith("/") ? depthPrefix + path.slice(1) : path;
|
|
2403
|
+
let seoTags = "";
|
|
2404
|
+
if (opts.seo) {
|
|
2405
|
+
const s = opts.seo;
|
|
2406
|
+
const e = escapeHtml;
|
|
2407
|
+
seoTags = `\n <meta property="og:title" content="${e(title)}" />\n <meta property="og:description" content="${e(description)}" />\n <meta property="og:url" content="${e(s.url)}" />\n <meta property="og:type" content="website" />\n <meta property="og:site_name" content="${e(s.siteName)}" />\n <meta name="twitter:card" content="summary_large_image" />\n <link rel="canonical" href="${e(s.url)}" />`;
|
|
2408
|
+
if (s.image) seoTags += `\n <meta property="og:image" content="${e(s.image)}" />`;
|
|
2409
|
+
}
|
|
2410
|
+
return `<!DOCTYPE html>
|
|
2411
|
+
<html lang="en">
|
|
2412
|
+
<head>
|
|
2413
|
+
<meta charset="UTF-8">
|
|
2414
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2415
|
+
<title>${escapeHtml(title)}</title>
|
|
2416
|
+
<meta name="description" content="${escapeHtml(description)}">
|
|
2417
|
+
${favicon ? `<link rel="icon" type="image/x-icon" href="${escapeHtml(resolvePath(favicon))}">` : ""}${themeStyle}
|
|
2418
|
+
<link rel="preload" href="${escapeHtml(assetPrefix + css)}" as="style">
|
|
2419
|
+
<link rel="stylesheet" href="${escapeHtml(assetPrefix + css)}">
|
|
2420
|
+
${csp ? `<meta http-equiv="Content-Security-Policy" content="${escapeHtml(csp)}">` : ""}
|
|
2421
|
+
${seoTags}
|
|
2422
|
+
<script${nonceAttr}>try{if(localStorage.getItem("theme")==="dark")document.documentElement.classList.add("dark")}catch(e){}<\/script>${headInjection}
|
|
2423
|
+
</head>
|
|
2424
|
+
<body>
|
|
2425
|
+
<div id="root">${body}</div>
|
|
2426
|
+
<link rel="modulepreload" href="${escapeHtml(assetPrefix + js)}">
|
|
2427
|
+
<script type="module"${nonceAttr} src="${escapeHtml(assetPrefix + js)}"><\/script>${extraScripts ? `\n ${extraScripts}` : ""}${bodyInjection}
|
|
2428
|
+
</body>
|
|
2429
|
+
</html>`;
|
|
2430
|
+
}
|
|
2431
|
+
function errorHtml(message, stack) {
|
|
2432
|
+
const msg = escapeHtml(message || "Unknown error");
|
|
2433
|
+
const st = escapeHtml(stack || "");
|
|
2434
|
+
return `<!DOCTYPE html>
|
|
2435
|
+
<html lang="en">
|
|
2436
|
+
<head>
|
|
2437
|
+
<meta charset="utf-8">
|
|
2438
|
+
<title>Server Error</title>
|
|
2439
|
+
<style>
|
|
2440
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
2441
|
+
body{padding:2rem;font-family:ui-monospace,monospace;background:#1a1a2e;color:#e0e0e0}
|
|
2442
|
+
h1{color:#ff6b6b;font-size:1.5rem;margin-bottom:1rem}
|
|
2443
|
+
pre{background:#0d0d1a;border:1px solid #333;border-radius:8px;padding:1.5rem;overflow-x:auto;font-size:14px;line-height:1.6;white-space:pre-wrap;word-break:break-word}
|
|
2444
|
+
.msg{color:#ff6b6b;font-weight:bold}
|
|
2445
|
+
</style>
|
|
2446
|
+
</head>
|
|
2447
|
+
<body>
|
|
2448
|
+
<h1>🔥 Server Error</h1>
|
|
2449
|
+
<pre><span class="msg">${msg}</span>${st ? `\n\n${st}` : ""}</pre>
|
|
2450
|
+
</body>
|
|
2451
|
+
</html>`;
|
|
2452
|
+
}
|
|
2453
|
+
function hmrScript(nonce) {
|
|
2454
|
+
return `<script nonce="${escapeHtml(nonce)}">
|
|
2455
|
+
(function(){
|
|
2456
|
+
const es = new EventSource("/__hmr");
|
|
2457
|
+
es.onmessage = function(e) {
|
|
2458
|
+
if (e.data === "reload") window.location.reload();
|
|
2459
|
+
};
|
|
2460
|
+
es.onerror = function() { es.close(); setTimeout(() => { window.location.reload(); }, 2000); };
|
|
2461
|
+
})();
|
|
2462
|
+
<\/script>`;
|
|
2463
|
+
}
|
|
2464
|
+
//#endregion
|
|
2465
|
+
export { loadPlugins as S, registerPageContent as _, IndexPage as a, computeInlineThemeCss as b, captureException as c, compileMdx as d, compileMdxModule as f, getPageStripped as g, getPageFrontmatter as h, DocsLayout as i, initSentry as l, getPageContent as m, hmrScript as n, NotFoundPage as o, frontmatterField as p, htmlShell as r, DocsPage as s, errorHtml as t, generateSearchIndex as u, getGitLastModifiedBatch as v, BuildPluginBuilder as x, buildClientBundle as y };
|
|
2466
|
+
|
|
2467
|
+
//# sourceMappingURL=html.shared-DSn-7_6t.js.map
|