@docubook/flame 2.0.0-alpha.1 → 2.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.docu/components/Menu.tsx +38 -32
- package/.docu/components/Sublink.tsx +72 -9
- package/.docu/lib/build.deno.js +3 -3
- package/.docu/lib/{build.impl-VXB4KL4D.js → build.impl-S4DS5XPH.js} +3 -3
- package/.docu/lib/build.node.js +3 -3
- package/.docu/lib/{chunk-MQWWCO6O.js → chunk-C3RCUTUE.js} +37 -13
- package/.docu/lib/{chunk-A6FIEG3H.js → chunk-D3QTSBR4.js} +2 -2
- package/.docu/lib/{chunk-JRERMREW.js → chunk-IVM5UM44.js} +6 -4
- package/.docu/lib/{chunk-LZDEWK25.js → chunk-JMQI3FKV.js} +1 -1
- package/.docu/lib/{chunk-TRT6WQZG.js → chunk-LXJLSXQI.js} +105 -44
- package/.docu/lib/{chunk-7PRQ3RQB.js → chunk-O326MYJE.js} +1 -1
- package/.docu/lib/clean.js +1 -1
- package/.docu/lib/deploy.deno.js +1 -1
- package/.docu/lib/deploy.node.js +1 -1
- package/.docu/lib/preview.deno.js +2 -2
- package/.docu/lib/preview.node.js +2 -2
- package/.docu/lib/server.deno.js +3 -3
- package/.docu/lib/server.node.js +3 -3
- package/.docu/node/build.impl.ts +50 -11
- package/.docu/node/build.ts +50 -11
- package/.docu/node/logger.ts +7 -3
- package/.docu/node/mdx.ts +87 -21
- package/.docu/node/route.ts +27 -19
- package/.docu/node/search-indexer.ts +4 -1
- package/.docu/styles/globals.css +1 -1
- package/package.json +5 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState } from "react";
|
|
4
|
-
import Sublink from "./Sublink";
|
|
4
|
+
import Sublink, { GroupAccordionProvider } from "./Sublink";
|
|
5
5
|
import SidebarGroupHeader from "./SidebarGroupHeader";
|
|
6
6
|
import type { DocuRoute } from "../node/types";
|
|
7
7
|
import { cn } from "../node/utils";
|
|
@@ -76,28 +76,32 @@ export default function Menu({ onNavigate, className = "", pathname, routes = []
|
|
|
76
76
|
// No context routes defined — fall back to flat list of all routes
|
|
77
77
|
if (contextRoutes.length === 0) {
|
|
78
78
|
return (
|
|
79
|
-
<
|
|
80
|
-
<
|
|
81
|
-
{
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
<GroupAccordionProvider>
|
|
80
|
+
<nav {...navProps}>
|
|
81
|
+
<ul className={sharedUlClasses}>
|
|
82
|
+
{menuRoutes.map((route) => renderBorderItem(route, "", route.href))}
|
|
83
|
+
</ul>
|
|
84
|
+
</nav>
|
|
85
|
+
</GroupAccordionProvider>
|
|
84
86
|
);
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
return (
|
|
88
|
-
<
|
|
89
|
-
{
|
|
90
|
-
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
{
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
90
|
+
<GroupAccordionProvider>
|
|
91
|
+
<nav {...navProps}>
|
|
92
|
+
{contextRoutes.map((route, i) => (
|
|
93
|
+
<div key={route.href} className={i > 0 ? "mt-6 lg:mt-8" : ""}>
|
|
94
|
+
<SidebarGroupHeader
|
|
95
|
+
icon={route.context?.icon}
|
|
96
|
+
title={route.context?.title || route.title}
|
|
97
|
+
/>
|
|
98
|
+
<ul className={sharedUlClasses}>
|
|
99
|
+
{route.items?.map((item) => renderBorderItem(item, route.href, item.href))}
|
|
100
|
+
</ul>
|
|
101
|
+
</div>
|
|
102
|
+
))}
|
|
103
|
+
</nav>
|
|
104
|
+
</GroupAccordionProvider>
|
|
101
105
|
);
|
|
102
106
|
}
|
|
103
107
|
|
|
@@ -119,18 +123,20 @@ export default function Menu({ onNavigate, className = "", pathname, routes = []
|
|
|
119
123
|
if (!contextRoute) return null;
|
|
120
124
|
|
|
121
125
|
return (
|
|
122
|
-
<
|
|
123
|
-
<
|
|
124
|
-
<
|
|
125
|
-
<
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
126
|
+
<GroupAccordionProvider>
|
|
127
|
+
<nav {...navProps}>
|
|
128
|
+
<ul className="flex flex-col gap-0.5 py-4">
|
|
129
|
+
<li key={contextRoute.title}>
|
|
130
|
+
<Sublink
|
|
131
|
+
{...contextRoute}
|
|
132
|
+
href={contextRoute.href}
|
|
133
|
+
level={0}
|
|
134
|
+
onNavigate={onNavigate}
|
|
135
|
+
parentHref="/docs"
|
|
136
|
+
/>
|
|
137
|
+
</li>
|
|
138
|
+
</ul>
|
|
139
|
+
</nav>
|
|
140
|
+
</GroupAccordionProvider>
|
|
135
141
|
);
|
|
136
142
|
}
|
|
@@ -1,12 +1,43 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useContext,
|
|
6
|
+
useState,
|
|
7
|
+
useRef,
|
|
8
|
+
useEffect,
|
|
9
|
+
useCallback,
|
|
10
|
+
type ReactNode,
|
|
11
|
+
} from "react";
|
|
4
12
|
import { ChevronDown } from "lucide-react";
|
|
5
13
|
import Anchor from "./Anchor";
|
|
6
14
|
import type { DocuRoute } from "../node/types";
|
|
7
15
|
import { cn, docsHtmlHref } from "../node/utils";
|
|
8
16
|
import { config as docuConfig } from "../node/client-routes";
|
|
9
17
|
|
|
18
|
+
/** Exclusive accordion for level >= 2 sidebar groups — opening one group
|
|
19
|
+
* closes the previously open one. All level >= 2 groups default to closed
|
|
20
|
+
* and expand only when the header is clicked. `open` is used to auto-expand
|
|
21
|
+
* the group containing the active page. */
|
|
22
|
+
export const GroupAccordionContext = createContext<{
|
|
23
|
+
openId: string | null;
|
|
24
|
+
open: (id: string) => void;
|
|
25
|
+
toggle: (id: string) => void;
|
|
26
|
+
}>({ openId: null, open: () => {}, toggle: () => {} });
|
|
27
|
+
|
|
28
|
+
export function GroupAccordionProvider({ children }: { children: ReactNode }) {
|
|
29
|
+
const [openId, setOpenId] = useState<string | null>(null);
|
|
30
|
+
// Exclusive: expanding B closes A — only one group stays open at a time;
|
|
31
|
+
// clicking the open group again collapses it.
|
|
32
|
+
const open = useCallback((id: string) => setOpenId(id), []);
|
|
33
|
+
const toggle = useCallback((id: string) => setOpenId((prev) => (prev === id ? null : id)), []);
|
|
34
|
+
return (
|
|
35
|
+
<GroupAccordionContext.Provider value={{ openId, open, toggle }}>
|
|
36
|
+
{children}
|
|
37
|
+
</GroupAccordionContext.Provider>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
10
41
|
interface SublinkProps extends DocuRoute {
|
|
11
42
|
level: number;
|
|
12
43
|
onNavigate?: () => void;
|
|
@@ -28,12 +59,39 @@ export default function Sublink({
|
|
|
28
59
|
const currentPathname =
|
|
29
60
|
pathnameProp || (typeof window !== "undefined" ? window.location.pathname : "/docs");
|
|
30
61
|
|
|
62
|
+
// Groups with children are exclusive accordions (default closed, expand on
|
|
63
|
+
// click). In separator mode every nav item renders at level 0 (sections are
|
|
64
|
+
// SidebarGroupHeader, not Sublinks), so depth can't tell them apart — the
|
|
65
|
+
// mode does. In dropdown mode the top section is level 0 (stays open) and
|
|
66
|
+
// everything deeper is an accordion.
|
|
67
|
+
const isSeparator = docuConfig.sidebar?.context === "separator";
|
|
68
|
+
const { openId, open, toggle } = useContext(GroupAccordionContext);
|
|
69
|
+
const isAccordionGroup = Boolean(items) && (isSeparator || level >= 1);
|
|
70
|
+
// Routes-tree level: separator mode renders every item at level 0 (sections
|
|
71
|
+
// are SidebarGroupHeader), dropdown starts the context section at level 0.
|
|
72
|
+
const treeLevel = level + (isSeparator ? 2 : 1);
|
|
73
|
+
|
|
31
74
|
const [isOpen, setIsOpen] = useState(() => {
|
|
32
|
-
if (
|
|
33
|
-
if (
|
|
34
|
-
return
|
|
75
|
+
if (isAccordionGroup) return false; // default closed — context controls it
|
|
76
|
+
if (level === 0) return true; // top-level section stays open
|
|
77
|
+
return false; // leaves
|
|
35
78
|
});
|
|
36
79
|
|
|
80
|
+
const effectiveOpen = isAccordionGroup ? openId === fullHref : isOpen;
|
|
81
|
+
const handleToggle = () => {
|
|
82
|
+
if (isAccordionGroup) toggle(fullHref);
|
|
83
|
+
else setIsOpen((o) => !o);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Auto-expand the accordion group that contains the active page — on mount
|
|
87
|
+
// and whenever the current path changes. `open` is stable (useCallback), so
|
|
88
|
+
// this only fires on real path changes; a manual collapse by the user is
|
|
89
|
+
// respected until the path changes again.
|
|
90
|
+
const isInsideActive = currentPathname.startsWith(fullHref) && currentPathname !== fullHref;
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (isAccordionGroup && isInsideActive) open(fullHref);
|
|
93
|
+
}, [isAccordionGroup, isInsideActive, fullHref, open]);
|
|
94
|
+
|
|
37
95
|
// Shared padding based on nesting level
|
|
38
96
|
const levelPadding = cn(level === 1 && "pl-4", level === 2 && "pl-8", level >= 3 && "pl-12");
|
|
39
97
|
const isActive = currentPathname === fullHref || currentPathname === `${fullHref}.html`;
|
|
@@ -113,12 +171,15 @@ export default function Sublink({
|
|
|
113
171
|
{/* Section header */}
|
|
114
172
|
<button
|
|
115
173
|
type="button"
|
|
116
|
-
onClick={
|
|
174
|
+
onClick={handleToggle}
|
|
117
175
|
className={cn(
|
|
118
176
|
"flex w-full cursor-pointer items-center justify-between py-1 text-left text-sm transition-colors",
|
|
119
|
-
|
|
177
|
+
// Only the top-level section label (routes-tree level 1) is bold.
|
|
178
|
+
// Deeper groups (e.g. Search at level 2) are children that happen to
|
|
179
|
+
// have items — style them like links, no header weight.
|
|
180
|
+
noLink && treeLevel === 1
|
|
120
181
|
? "text-base-content font-semibold"
|
|
121
|
-
: "text-
|
|
182
|
+
: "text-foreground hover:text-foreground/80"
|
|
122
183
|
)}
|
|
123
184
|
>
|
|
124
185
|
{noLink ? (
|
|
@@ -137,13 +198,15 @@ export default function Sublink({
|
|
|
137
198
|
<ChevronDown
|
|
138
199
|
className={cn(
|
|
139
200
|
"text-base-content/40 h-4 w-4 shrink-0 transition-transform duration-200",
|
|
140
|
-
|
|
201
|
+
// Tree convention: closed = chevron pointing right (expandable),
|
|
202
|
+
// open = pointing down — a 90° turn instead of the 180° flip.
|
|
203
|
+
effectiveOpen ? "rotate-0" : "-rotate-90"
|
|
141
204
|
)}
|
|
142
205
|
/>
|
|
143
206
|
</button>
|
|
144
207
|
|
|
145
208
|
{/* Children */}
|
|
146
|
-
{
|
|
209
|
+
{effectiveOpen && (
|
|
147
210
|
<div className="flex flex-col py-1">
|
|
148
211
|
{items.map((item) => (
|
|
149
212
|
<Sublink
|
package/.docu/lib/build.deno.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runBuildCli
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-C3RCUTUE.js";
|
|
4
|
+
import "./chunk-LXJLSXQI.js";
|
|
5
5
|
import "./chunk-EOK6KATZ.js";
|
|
6
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-IVM5UM44.js";
|
|
7
7
|
import "./chunk-4IQXHHPF.js";
|
|
8
8
|
|
|
9
9
|
// .docu/node/build.deno.ts
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runBuild,
|
|
3
3
|
runBuildCli
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-C3RCUTUE.js";
|
|
5
|
+
import "./chunk-LXJLSXQI.js";
|
|
6
6
|
import "./chunk-EOK6KATZ.js";
|
|
7
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-IVM5UM44.js";
|
|
8
8
|
import "./chunk-4IQXHHPF.js";
|
|
9
9
|
export {
|
|
10
10
|
runBuild,
|
package/.docu/lib/build.node.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runBuildCli
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-C3RCUTUE.js";
|
|
4
|
+
import "./chunk-LXJLSXQI.js";
|
|
5
5
|
import "./chunk-EOK6KATZ.js";
|
|
6
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-IVM5UM44.js";
|
|
7
7
|
import "./chunk-4IQXHHPF.js";
|
|
8
8
|
|
|
9
9
|
// .docu/node/build.node.ts
|
|
@@ -12,10 +12,14 @@ import {
|
|
|
12
12
|
frontmatterField,
|
|
13
13
|
generateSearchIndex,
|
|
14
14
|
getGitLastModifiedBatch,
|
|
15
|
+
getPageContent,
|
|
16
|
+
getPageFrontmatter,
|
|
17
|
+
getPageStripped,
|
|
15
18
|
htmlShell,
|
|
16
19
|
initSentry,
|
|
17
|
-
loadPlugins
|
|
18
|
-
|
|
20
|
+
loadPlugins,
|
|
21
|
+
registerPageContent
|
|
22
|
+
} from "./chunk-LXJLSXQI.js";
|
|
19
23
|
import {
|
|
20
24
|
cspHeader,
|
|
21
25
|
generateNonce,
|
|
@@ -23,7 +27,7 @@ import {
|
|
|
23
27
|
} from "./chunk-EOK6KATZ.js";
|
|
24
28
|
import {
|
|
25
29
|
logger
|
|
26
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-IVM5UM44.js";
|
|
27
31
|
import {
|
|
28
32
|
ASSETS_DIR,
|
|
29
33
|
CACHE_FILE,
|
|
@@ -109,7 +113,18 @@ async function renderDocsPage(docuConfig, slug, rawMdx, filePath, gitDates, buil
|
|
|
109
113
|
try {
|
|
110
114
|
const remarkPlugins = builder?.collectRemarkPlugins();
|
|
111
115
|
const rehypePlugins = builder?.collectRehypePlugins();
|
|
112
|
-
|
|
116
|
+
const preFm = getPageFrontmatter(`/${slug}`);
|
|
117
|
+
const preStripped = getPageStripped(`/${slug}`);
|
|
118
|
+
const pre = preFm !== void 0 && preStripped !== void 0 ? { frontmatter: preFm, strippedContent: preStripped } : void 0;
|
|
119
|
+
result = await compileMdx(
|
|
120
|
+
content,
|
|
121
|
+
filePath,
|
|
122
|
+
gitDates,
|
|
123
|
+
remarkPlugins,
|
|
124
|
+
rehypePlugins,
|
|
125
|
+
void 0,
|
|
126
|
+
pre
|
|
127
|
+
);
|
|
113
128
|
} catch (err) {
|
|
114
129
|
const msg = err instanceof Error ? err.message : "Unknown MDX error";
|
|
115
130
|
throw new Error(`MDX Error in: docs/${slug}.mdx
|
|
@@ -222,6 +237,7 @@ async function runBuild() {
|
|
|
222
237
|
} catch {
|
|
223
238
|
return;
|
|
224
239
|
}
|
|
240
|
+
registerPageContent(`/${file.path}`, raw);
|
|
225
241
|
let content = raw;
|
|
226
242
|
if (builder) {
|
|
227
243
|
const relPath = file.absPath.replace(PROJECT_ROOT + "/", "");
|
|
@@ -230,7 +246,12 @@ async function runBuild() {
|
|
|
230
246
|
}
|
|
231
247
|
const remarkPlugins = builder?.collectRemarkPlugins();
|
|
232
248
|
const rehypePlugins = builder?.collectRehypePlugins();
|
|
233
|
-
mdxSources[file.path] = await compileMdxModule(
|
|
249
|
+
mdxSources[file.path] = await compileMdxModule(
|
|
250
|
+
content,
|
|
251
|
+
remarkPlugins,
|
|
252
|
+
rehypePlugins,
|
|
253
|
+
`/${file.path}`
|
|
254
|
+
);
|
|
234
255
|
});
|
|
235
256
|
await Promise.all(prePassTasks);
|
|
236
257
|
const indexMdxPath = join(DOCS_DIR, "index.mdx");
|
|
@@ -285,12 +306,14 @@ async function runBuild() {
|
|
|
285
306
|
continue;
|
|
286
307
|
}
|
|
287
308
|
}
|
|
288
|
-
let rawMdx;
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
309
|
+
let rawMdx = getPageContent(`/${file.path}`);
|
|
310
|
+
if (rawMdx === void 0) {
|
|
311
|
+
try {
|
|
312
|
+
rawMdx = await readFile(file.absPath, "utf-8");
|
|
313
|
+
} catch (err) {
|
|
314
|
+
if (err.code !== "ENOENT") throw err;
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
294
317
|
}
|
|
295
318
|
if (rebuildDecision === "hash_check") {
|
|
296
319
|
const contentHash = hashContent(rawMdx);
|
|
@@ -418,8 +441,9 @@ async function runBuild() {
|
|
|
418
441
|
}
|
|
419
442
|
logger.indexStart();
|
|
420
443
|
t = performance.now();
|
|
421
|
-
const
|
|
422
|
-
|
|
444
|
+
const indexSkipped = built === 0 && existsSync(join(ASSETS_DIR, "search-index.json"));
|
|
445
|
+
const indexCount = indexSkipped ? 0 : await generateSearchIndex();
|
|
446
|
+
logger.indexDone(indexCount, Math.round(performance.now() - t), indexSkipped);
|
|
423
447
|
logger.routes();
|
|
424
448
|
await writeCache(cache);
|
|
425
449
|
if (errors.length > 0) {
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
htmlShell,
|
|
16
16
|
initSentry,
|
|
17
17
|
loadPlugins
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-LXJLSXQI.js";
|
|
19
19
|
import {
|
|
20
20
|
SECURITY_HEADERS,
|
|
21
21
|
generateNonce,
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
} from "./chunk-EOK6KATZ.js";
|
|
29
29
|
import {
|
|
30
30
|
logger
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-IVM5UM44.js";
|
|
32
32
|
import {
|
|
33
33
|
DIST_DIR,
|
|
34
34
|
DOCS_DIR,
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
// package.json
|
|
6
6
|
var package_default = {
|
|
7
7
|
name: "@docubook/flame",
|
|
8
|
-
version: "2.0.0-
|
|
8
|
+
version: "2.0.0-beta.0",
|
|
9
9
|
description: "A blazing-fast React + MDX framework powered by Bun, built for modern documentation experiences.",
|
|
10
10
|
type: "module",
|
|
11
11
|
bin: {
|
|
@@ -239,9 +239,11 @@ ${c.bold}${c.cyan} \u{1F525} DocuBook Flame${c.reset} ${c.dim}v${package_defaul
|
|
|
239
239
|
if (guard("info", "index_start")) return;
|
|
240
240
|
this.spinner.start("Generating search index...");
|
|
241
241
|
},
|
|
242
|
-
indexDone(records, ms) {
|
|
243
|
-
if (guard("info", "index_done", { records, duration_ms: ms })) return;
|
|
244
|
-
this.spinner.stop(
|
|
242
|
+
indexDone(records, ms, skipped = false) {
|
|
243
|
+
if (guard("info", "index_done", { records, duration_ms: ms, skipped })) return;
|
|
244
|
+
this.spinner.stop(
|
|
245
|
+
skipped ? `Search index cached ${c.dim}(${ms}ms)${c.reset}` : `Search index generated ${c.dim}(${records} records, ${ms}ms)${c.reset}`
|
|
246
|
+
);
|
|
245
247
|
},
|
|
246
248
|
routes() {
|
|
247
249
|
if (guard("debug", "routes")) return;
|