@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.
Files changed (54) hide show
  1. package/.docu/components/ScrollTo.tsx +17 -11
  2. package/.docu/components/Toc.tsx +32 -7
  3. package/.docu/lib/build.deno.js +6 -9
  4. package/.docu/lib/build.deno.js.map +1 -0
  5. package/.docu/lib/build.impl-BJsi28mD.js +377 -0
  6. package/.docu/lib/build.impl-BJsi28mD.js.map +1 -0
  7. package/.docu/lib/build.impl-yVLaa1eQ.js +2 -0
  8. package/.docu/lib/build.node.js +6 -9
  9. package/.docu/lib/build.node.js.map +1 -0
  10. package/.docu/lib/clean.js +25 -25
  11. package/.docu/lib/clean.js.map +1 -0
  12. package/.docu/lib/deno-DmEaKggj.js +20 -0
  13. package/.docu/lib/deno-DmEaKggj.js.map +1 -0
  14. package/.docu/lib/deploy.deno.js +9 -9
  15. package/.docu/lib/deploy.deno.js.map +1 -0
  16. package/.docu/lib/deploy.node.js +8 -8
  17. package/.docu/lib/deploy.node.js.map +1 -0
  18. package/.docu/lib/deploy.shared-D3UWafa6.js +295 -0
  19. package/.docu/lib/deploy.shared-D3UWafa6.js.map +1 -0
  20. package/.docu/lib/html.shared-DSn-7_6t.js +2467 -0
  21. package/.docu/lib/html.shared-DSn-7_6t.js.map +1 -0
  22. package/.docu/lib/logger-CycvLCrQ.js +312 -0
  23. package/.docu/lib/logger-CycvLCrQ.js.map +1 -0
  24. package/.docu/lib/node-DPTySdwG.js +80 -0
  25. package/.docu/lib/node-DPTySdwG.js.map +1 -0
  26. package/.docu/lib/paths-BtOIPBQ9.js +52 -0
  27. package/.docu/lib/paths-BtOIPBQ9.js.map +1 -0
  28. package/.docu/lib/preview.deno.js +7 -11
  29. package/.docu/lib/preview.deno.js.map +1 -0
  30. package/.docu/lib/preview.impl-CXJS2tQS.js +77 -0
  31. package/.docu/lib/preview.impl-CXJS2tQS.js.map +1 -0
  32. package/.docu/lib/preview.node.js +7 -11
  33. package/.docu/lib/preview.node.js.map +1 -0
  34. package/.docu/lib/server.deno.js +7 -12
  35. package/.docu/lib/server.deno.js.map +1 -0
  36. package/.docu/lib/server.impl-CJuWimfN.js +358 -0
  37. package/.docu/lib/server.impl-CJuWimfN.js.map +1 -0
  38. package/.docu/lib/server.node.js +7 -12
  39. package/.docu/lib/server.node.js.map +1 -0
  40. package/.docu/lib/utils-DA17MyQG.js +167 -0
  41. package/.docu/lib/utils-DA17MyQG.js.map +1 -0
  42. package/.docu/node/hydrate.node.ts +116 -166
  43. package/bin/compile-lib.mjs +30 -28
  44. package/package.json +6 -6
  45. package/.docu/lib/build.impl-S4DS5XPH.js +0 -12
  46. package/.docu/lib/chunk-4IQXHHPF.js +0 -62
  47. package/.docu/lib/chunk-C3RCUTUE.js +0 -470
  48. package/.docu/lib/chunk-D3QTSBR4.js +0 -434
  49. package/.docu/lib/chunk-EOK6KATZ.js +0 -191
  50. package/.docu/lib/chunk-IVM5UM44.js +0 -301
  51. package/.docu/lib/chunk-JMQI3FKV.js +0 -92
  52. package/.docu/lib/chunk-LXJLSXQI.js +0 -2691
  53. package/.docu/lib/chunk-O326MYJE.js +0 -330
  54. 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
+ "&": "&amp;",
2372
+ "<": "&lt;",
2373
+ ">": "&gt;",
2374
+ "\"": "&quot;",
2375
+ "'": "&#x27;"
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