@farming-labs/theme 0.0.2-beta.9 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-search-dialog.d.mts +23 -1
- package/dist/ai-search-dialog.mjs +247 -37
- package/dist/colorful/index.d.mts +78 -0
- package/dist/colorful/index.mjs +82 -0
- package/dist/darkbold/index.d.mts +80 -0
- package/dist/darkbold/index.mjs +84 -0
- package/dist/docs-ai-features.d.mts +3 -1
- package/dist/docs-ai-features.mjs +63 -10
- package/dist/docs-api.d.mts +7 -6
- package/dist/docs-api.mjs +73 -4
- package/dist/docs-command-search.d.mts +10 -0
- package/dist/docs-command-search.mjs +654 -0
- package/dist/docs-layout.d.mts +20 -11
- package/dist/docs-layout.mjs +230 -50
- package/dist/docs-page-client.d.mts +20 -0
- package/dist/docs-page-client.mjs +119 -25
- package/dist/greentree/index.d.mts +80 -0
- package/dist/greentree/index.mjs +84 -0
- package/dist/index.d.mts +3 -2
- package/dist/index.mjs +3 -2
- package/dist/page-actions.d.mts +4 -1
- package/dist/page-actions.mjs +8 -4
- package/dist/search.d.mts +1 -1
- package/dist/shiny/index.d.mts +79 -0
- package/dist/shiny/index.mjs +83 -0
- package/dist/sidebar-search-ai.d.mts +11 -0
- package/dist/sidebar-search-ai.mjs +128 -0
- package/package.json +38 -14
- package/styles/ai.css +408 -81
- package/styles/base.css +211 -14
- package/styles/colorful.css +266 -0
- package/styles/darkbold.css +575 -0
- package/styles/darksharp.css +27 -11
- package/styles/default.css +22 -1
- package/styles/greentree.css +719 -0
- package/styles/omni.css +359 -0
- package/styles/pixel-border.css +115 -52
- package/styles/shiny.css +505 -0
package/dist/docs-layout.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { DocsAIFeatures } from "./docs-ai-features.mjs";
|
|
2
|
+
import { DocsCommandSearch } from "./docs-command-search.mjs";
|
|
2
3
|
import { serializeIcon } from "./serialize-icon.mjs";
|
|
3
4
|
import { DocsPageClient } from "./docs-page-client.mjs";
|
|
5
|
+
import { SidebarSearchWithAI } from "./sidebar-search-ai.mjs";
|
|
4
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
7
|
import fs from "node:fs";
|
|
6
8
|
import path from "node:path";
|
|
@@ -31,9 +33,10 @@ function hasChildPages(dir) {
|
|
|
31
33
|
}
|
|
32
34
|
return false;
|
|
33
35
|
}
|
|
34
|
-
function buildTree(config) {
|
|
36
|
+
function buildTree(config, flat = false) {
|
|
35
37
|
const docsDir = path.join(process.cwd(), "app", config.entry);
|
|
36
38
|
const icons = config.icons;
|
|
39
|
+
const ordering = config.ordering;
|
|
37
40
|
const rootChildren = [];
|
|
38
41
|
if (fs.existsSync(path.join(docsDir, "page.mdx"))) {
|
|
39
42
|
const data = readFrontmatter(path.join(docsDir, "page.mdx"));
|
|
@@ -44,52 +47,91 @@ function buildTree(config) {
|
|
|
44
47
|
icon: resolveIcon(data.icon, icons)
|
|
45
48
|
});
|
|
46
49
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
function buildNode(dir, name, baseSlug, slugOrder) {
|
|
51
|
+
const full = path.join(dir, name);
|
|
52
|
+
if (!fs.statSync(full).isDirectory()) return null;
|
|
53
|
+
const pagePath = path.join(full, "page.mdx");
|
|
54
|
+
if (!fs.existsSync(pagePath)) return null;
|
|
55
|
+
const data = readFrontmatter(pagePath);
|
|
56
|
+
const slug = [...baseSlug, name];
|
|
57
|
+
const url = `/${config.entry}/${slug.join("/")}`;
|
|
58
|
+
const icon = resolveIcon(data.icon, icons);
|
|
59
|
+
const displayName = data.title ?? name.replace(/-/g, " ");
|
|
60
|
+
if (hasChildPages(full)) {
|
|
61
|
+
const folderChildren = scanDir(full, slug, slugOrder);
|
|
62
|
+
return {
|
|
63
|
+
type: "folder",
|
|
64
|
+
name: displayName,
|
|
65
|
+
icon,
|
|
66
|
+
index: {
|
|
67
|
+
type: "page",
|
|
68
|
+
name: displayName,
|
|
69
|
+
url,
|
|
70
|
+
icon
|
|
71
|
+
},
|
|
72
|
+
children: folderChildren,
|
|
73
|
+
...flat ? {
|
|
74
|
+
collapsible: false,
|
|
75
|
+
defaultOpen: true
|
|
76
|
+
} : {}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
type: "page",
|
|
81
|
+
name: displayName,
|
|
82
|
+
url,
|
|
83
|
+
icon
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function scanDir(dir, baseSlug, slugOrder) {
|
|
56
87
|
if (!fs.existsSync(dir)) return [];
|
|
57
|
-
const nodes = [];
|
|
58
88
|
const entries = fs.readdirSync(dir).sort();
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
nodes.push(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
89
|
+
if (slugOrder) {
|
|
90
|
+
const nodes = [];
|
|
91
|
+
const slugMap = /* @__PURE__ */ new Map();
|
|
92
|
+
for (const item of slugOrder) slugMap.set(item.slug, item);
|
|
93
|
+
for (const item of slugOrder) {
|
|
94
|
+
if (!entries.includes(item.slug)) continue;
|
|
95
|
+
const node = buildNode(dir, item.slug, baseSlug, item.children);
|
|
96
|
+
if (node) nodes.push(node);
|
|
97
|
+
}
|
|
98
|
+
for (const name of entries) {
|
|
99
|
+
if (slugMap.has(name)) continue;
|
|
100
|
+
const node = buildNode(dir, name, baseSlug);
|
|
101
|
+
if (node) nodes.push(node);
|
|
102
|
+
}
|
|
103
|
+
return nodes;
|
|
104
|
+
}
|
|
105
|
+
if (ordering === "numeric") {
|
|
106
|
+
const nodes = [];
|
|
107
|
+
for (const name of entries) {
|
|
108
|
+
const full = path.join(dir, name);
|
|
109
|
+
if (!fs.statSync(full).isDirectory()) continue;
|
|
110
|
+
const pagePath = path.join(full, "page.mdx");
|
|
111
|
+
if (!fs.existsSync(pagePath)) continue;
|
|
112
|
+
const data = readFrontmatter(pagePath);
|
|
113
|
+
const order = typeof data.order === "number" ? data.order : Infinity;
|
|
114
|
+
const node = buildNode(dir, name, baseSlug);
|
|
115
|
+
if (node) nodes.push({
|
|
116
|
+
order,
|
|
117
|
+
node
|
|
82
118
|
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
icon
|
|
119
|
+
}
|
|
120
|
+
nodes.sort((a, b) => {
|
|
121
|
+
if (a.order === b.order) return 0;
|
|
122
|
+
return a.order - b.order;
|
|
88
123
|
});
|
|
124
|
+
return nodes.map((n) => n.node);
|
|
125
|
+
}
|
|
126
|
+
const nodes = [];
|
|
127
|
+
for (const name of entries) {
|
|
128
|
+
const node = buildNode(dir, name, baseSlug);
|
|
129
|
+
if (node) nodes.push(node);
|
|
89
130
|
}
|
|
90
131
|
return nodes;
|
|
91
132
|
}
|
|
92
|
-
|
|
133
|
+
const rootSlugOrder = Array.isArray(ordering) ? ordering : void 0;
|
|
134
|
+
rootChildren.push(...scanDir(docsDir, [], rootSlugOrder));
|
|
93
135
|
return {
|
|
94
136
|
name: "Docs",
|
|
95
137
|
children: rootChildren
|
|
@@ -125,6 +167,31 @@ function buildLastModifiedMap(entry) {
|
|
|
125
167
|
return map;
|
|
126
168
|
}
|
|
127
169
|
/**
|
|
170
|
+
* Scan all page.mdx files and build a map of URL pathname → description
|
|
171
|
+
* from the frontmatter `description` field.
|
|
172
|
+
*/
|
|
173
|
+
function buildDescriptionMap(entry) {
|
|
174
|
+
const docsDir = path.join(process.cwd(), "app", entry);
|
|
175
|
+
const map = {};
|
|
176
|
+
function scan(dir, slugParts) {
|
|
177
|
+
if (!fs.existsSync(dir)) return;
|
|
178
|
+
const pagePath = path.join(dir, "page.mdx");
|
|
179
|
+
if (fs.existsSync(pagePath)) {
|
|
180
|
+
const desc = readFrontmatter(pagePath).description;
|
|
181
|
+
if (desc) {
|
|
182
|
+
const url = slugParts.length === 0 ? `/${entry}` : `/${entry}/${slugParts.join("/")}`;
|
|
183
|
+
map[url] = desc;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
for (const name of fs.readdirSync(dir)) {
|
|
187
|
+
const full = path.join(dir, name);
|
|
188
|
+
if (fs.statSync(full).isDirectory()) scan(full, [...slugParts, name]);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
scan(docsDir, []);
|
|
192
|
+
return map;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
128
195
|
* Build a Next.js Metadata object from the docs config.
|
|
129
196
|
*
|
|
130
197
|
* Returns layout-level metadata including `title.template` so each page's
|
|
@@ -137,15 +204,71 @@ function buildLastModifiedMap(entry) {
|
|
|
137
204
|
*/
|
|
138
205
|
function createDocsMetadata(config) {
|
|
139
206
|
const meta = config.metadata;
|
|
207
|
+
const og = config.og;
|
|
140
208
|
const template = meta?.titleTemplate ?? "%s";
|
|
141
|
-
|
|
209
|
+
const defaultTitle = template.replace("%s", "").replace(/^[\s–—-]+/, "").trim() || "Docs";
|
|
210
|
+
const result = {
|
|
142
211
|
title: {
|
|
143
212
|
template,
|
|
144
|
-
default:
|
|
213
|
+
default: defaultTitle
|
|
145
214
|
},
|
|
146
215
|
...meta?.description ? { description: meta.description } : {},
|
|
147
216
|
...meta?.twitterCard ? { twitter: { card: meta.twitterCard } } : {}
|
|
148
217
|
};
|
|
218
|
+
if (og?.enabled !== false && og?.endpoint) {
|
|
219
|
+
const ogUrl = `${og.endpoint}?title=${encodeURIComponent(defaultTitle)}${meta?.description ? `&description=${encodeURIComponent(meta.description)}` : ""}`;
|
|
220
|
+
result.openGraph = { images: [{
|
|
221
|
+
url: ogUrl,
|
|
222
|
+
width: 1200,
|
|
223
|
+
height: 630
|
|
224
|
+
}] };
|
|
225
|
+
result.twitter = {
|
|
226
|
+
...result.twitter,
|
|
227
|
+
card: meta?.twitterCard ?? "summary_large_image",
|
|
228
|
+
images: [ogUrl]
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Generate page-level metadata with dynamic OG images.
|
|
235
|
+
*
|
|
236
|
+
* Usage in a docs page or [[...slug]] route:
|
|
237
|
+
* ```ts
|
|
238
|
+
* export function generateMetadata({ params }) {
|
|
239
|
+
* const page = getPage(params.slug);
|
|
240
|
+
* return createPageMetadata(docsConfig, {
|
|
241
|
+
* title: page.data.title,
|
|
242
|
+
* description: page.data.description,
|
|
243
|
+
* });
|
|
244
|
+
* }
|
|
245
|
+
* ```
|
|
246
|
+
*/
|
|
247
|
+
function createPageMetadata(config, page) {
|
|
248
|
+
const og = config.og;
|
|
249
|
+
const result = {
|
|
250
|
+
title: page.title,
|
|
251
|
+
...page.description ? { description: page.description } : {}
|
|
252
|
+
};
|
|
253
|
+
if (og?.enabled !== false && og?.endpoint) {
|
|
254
|
+
const ogUrl = `${og.endpoint}?title=${encodeURIComponent(page.title)}${page.description ? `&description=${encodeURIComponent(page.description)}` : ""}`;
|
|
255
|
+
result.openGraph = {
|
|
256
|
+
title: page.title,
|
|
257
|
+
description: page.description,
|
|
258
|
+
images: [{
|
|
259
|
+
url: ogUrl,
|
|
260
|
+
width: 1200,
|
|
261
|
+
height: 630
|
|
262
|
+
}]
|
|
263
|
+
};
|
|
264
|
+
result.twitter = {
|
|
265
|
+
card: "summary_large_image",
|
|
266
|
+
title: page.title,
|
|
267
|
+
description: page.description,
|
|
268
|
+
images: [ogUrl]
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
return result;
|
|
149
272
|
}
|
|
150
273
|
/** Resolve the themeToggle config into fumadocs-ui's `themeSwitch` prop. */
|
|
151
274
|
function resolveThemeSwitch(toggle) {
|
|
@@ -162,10 +285,11 @@ function resolveSidebar(sidebar) {
|
|
|
162
285
|
if (sidebar === false) return { enabled: false };
|
|
163
286
|
return {
|
|
164
287
|
enabled: sidebar.enabled !== false,
|
|
165
|
-
|
|
288
|
+
componentFn: typeof sidebar.component === "function" ? sidebar.component : void 0,
|
|
166
289
|
footer: sidebar.footer,
|
|
167
290
|
banner: sidebar.banner,
|
|
168
|
-
collapsible: sidebar.collapsible
|
|
291
|
+
collapsible: sidebar.collapsible,
|
|
292
|
+
flat: sidebar.flat
|
|
169
293
|
};
|
|
170
294
|
}
|
|
171
295
|
const COLOR_MAP = {
|
|
@@ -194,7 +318,7 @@ function buildColorsCSS(colors) {
|
|
|
194
318
|
vars.push(`${COLOR_MAP[key]}: ${value};`);
|
|
195
319
|
}
|
|
196
320
|
if (vars.length === 0) return "";
|
|
197
|
-
return
|
|
321
|
+
return `.dark {\n ${vars.join("\n ")}\n}`;
|
|
198
322
|
}
|
|
199
323
|
function ColorStyle({ colors }) {
|
|
200
324
|
const css = buildColorsCSS(colors);
|
|
@@ -238,22 +362,59 @@ function TypographyStyle({ typography }) {
|
|
|
238
362
|
if (!css) return null;
|
|
239
363
|
return /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: css } });
|
|
240
364
|
}
|
|
365
|
+
function LayoutStyle({ layout }) {
|
|
366
|
+
if (!layout) return null;
|
|
367
|
+
const rootVars = [];
|
|
368
|
+
const desktopRootVars = [];
|
|
369
|
+
const desktopGridVars = [];
|
|
370
|
+
if (layout.sidebarWidth) {
|
|
371
|
+
const v = `--fd-sidebar-width: ${layout.sidebarWidth}px`;
|
|
372
|
+
desktopRootVars.push(`${v};`);
|
|
373
|
+
desktopGridVars.push(`${v} !important;`);
|
|
374
|
+
}
|
|
375
|
+
if (layout.contentWidth) rootVars.push(`--fd-content-width: ${layout.contentWidth}px;`);
|
|
376
|
+
if (layout.tocWidth) {
|
|
377
|
+
const v = `--fd-toc-width: ${layout.tocWidth}px`;
|
|
378
|
+
desktopRootVars.push(`${v};`);
|
|
379
|
+
desktopGridVars.push(`${v} !important;`);
|
|
380
|
+
}
|
|
381
|
+
if (rootVars.length === 0 && desktopRootVars.length === 0) return null;
|
|
382
|
+
const parts = [];
|
|
383
|
+
if (rootVars.length > 0) parts.push(`:root {\n ${rootVars.join("\n ")}\n}`);
|
|
384
|
+
if (desktopRootVars.length > 0) {
|
|
385
|
+
const inner = [`:root {\n ${desktopRootVars.join("\n ")}\n }`];
|
|
386
|
+
if (desktopGridVars.length > 0) inner.push(`[style*="fd-sidebar-col"] {\n ${desktopGridVars.join("\n ")}\n }`);
|
|
387
|
+
parts.push(`@media (min-width: 1024px) {\n ${inner.join("\n ")}\n}`);
|
|
388
|
+
}
|
|
389
|
+
return /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: parts.join("\n") } });
|
|
390
|
+
}
|
|
241
391
|
function createDocsLayout(config) {
|
|
242
|
-
const
|
|
392
|
+
const tocConfig = config.theme?.ui?.layout?.toc;
|
|
393
|
+
const tocEnabled = tocConfig?.enabled !== false;
|
|
394
|
+
const tocStyle = tocConfig?.style;
|
|
243
395
|
const navTitle = config.nav?.title ?? "Docs";
|
|
244
396
|
const navUrl = config.nav?.url ?? `/${config.entry}`;
|
|
245
397
|
const themeSwitch = resolveThemeSwitch(config.themeToggle);
|
|
246
398
|
const toggleConfig = typeof config.themeToggle === "object" ? config.themeToggle : void 0;
|
|
247
399
|
const forcedTheme = themeSwitch.enabled === false && toggleConfig?.default && toggleConfig.default !== "system" ? toggleConfig.default : void 0;
|
|
248
|
-
const
|
|
400
|
+
const resolvedSidebar = resolveSidebar(config.sidebar);
|
|
401
|
+
const sidebarFlat = resolvedSidebar.flat;
|
|
402
|
+
const sidebarComponentFn = resolvedSidebar.componentFn;
|
|
403
|
+
const { flat: _sidebarFlat, componentFn: _componentFn, ...sidebarProps } = resolvedSidebar;
|
|
249
404
|
const breadcrumbConfig = config.breadcrumb;
|
|
250
405
|
const breadcrumbEnabled = breadcrumbConfig === void 0 || breadcrumbConfig === true || typeof breadcrumbConfig === "object" && breadcrumbConfig.enabled !== false;
|
|
251
406
|
const colors = config.theme?._userColorOverrides;
|
|
252
407
|
const typography = config.theme?.ui?.typography;
|
|
408
|
+
const layoutDimensions = config.theme?.ui?.layout;
|
|
253
409
|
const pageActions = config.pageActions;
|
|
254
410
|
const copyMarkdownEnabled = resolveBool(pageActions?.copyMarkdown);
|
|
255
411
|
const openDocsEnabled = resolveBool(pageActions?.openDocs);
|
|
256
412
|
const pageActionsPosition = pageActions?.position ?? "below-title";
|
|
413
|
+
const pageActionsAlignment = pageActions?.alignment ?? "left";
|
|
414
|
+
const lastUpdatedRaw = config.lastUpdated;
|
|
415
|
+
const lastUpdatedEnabled = lastUpdatedRaw !== false && (typeof lastUpdatedRaw !== "object" || lastUpdatedRaw.enabled !== false);
|
|
416
|
+
const lastUpdatedPosition = typeof lastUpdatedRaw === "object" ? lastUpdatedRaw.position ?? "footer" : "footer";
|
|
417
|
+
const llmsTxtEnabled = resolveBool(config.llmsTxt);
|
|
257
418
|
const openDocsProviders = (typeof pageActions?.openDocs === "object" && pageActions.openDocs.providers ? pageActions.openDocs.providers : void 0)?.map((p) => ({
|
|
258
419
|
name: p.name,
|
|
259
420
|
urlTemplate: p.urlTemplate,
|
|
@@ -271,21 +432,33 @@ function createDocsLayout(config) {
|
|
|
271
432
|
const aiTriggerComponentHtml = aiConfig?.triggerComponent ? serializeIcon(aiConfig.triggerComponent) : void 0;
|
|
272
433
|
const aiSuggestedQuestions = aiConfig?.suggestedQuestions;
|
|
273
434
|
const aiLabel = aiConfig?.aiLabel;
|
|
435
|
+
const aiLoaderVariant = aiConfig?.loader;
|
|
274
436
|
const aiLoadingComponentHtml = typeof aiConfig?.loadingComponent === "function" ? serializeIcon(aiConfig.loadingComponent({ name: aiLabel || "AI" })) : void 0;
|
|
275
437
|
const lastModifiedMap = buildLastModifiedMap(config.entry);
|
|
438
|
+
const descriptionMap = buildDescriptionMap(config.entry);
|
|
276
439
|
return function DocsLayoutWrapper({ children }) {
|
|
440
|
+
const tree = buildTree(config, !!sidebarFlat);
|
|
441
|
+
const finalSidebarProps = { ...sidebarProps };
|
|
442
|
+
if (sidebarComponentFn) finalSidebarProps.component = sidebarComponentFn({
|
|
443
|
+
tree,
|
|
444
|
+
collapsible: sidebarProps.collapsible !== false,
|
|
445
|
+
flat: !!sidebarFlat
|
|
446
|
+
});
|
|
277
447
|
return /* @__PURE__ */ jsxs(DocsLayout, {
|
|
278
|
-
tree
|
|
448
|
+
tree,
|
|
279
449
|
nav: {
|
|
280
450
|
title: navTitle,
|
|
281
451
|
url: navUrl
|
|
282
452
|
},
|
|
283
453
|
themeSwitch,
|
|
284
|
-
sidebar:
|
|
454
|
+
sidebar: finalSidebarProps,
|
|
455
|
+
...aiMode === "sidebar-icon" && aiEnabled ? { searchToggle: { components: { lg: /* @__PURE__ */ jsx(SidebarSearchWithAI, {}) } } } : {},
|
|
285
456
|
children: [
|
|
286
457
|
/* @__PURE__ */ jsx(ColorStyle, { colors }),
|
|
287
458
|
/* @__PURE__ */ jsx(TypographyStyle, { typography }),
|
|
459
|
+
/* @__PURE__ */ jsx(LayoutStyle, { layout: layoutDimensions }),
|
|
288
460
|
forcedTheme && /* @__PURE__ */ jsx(ForcedThemeScript, { theme: forcedTheme }),
|
|
461
|
+
/* @__PURE__ */ jsx(DocsCommandSearch, {}),
|
|
289
462
|
aiEnabled && /* @__PURE__ */ jsx(DocsAIFeatures, {
|
|
290
463
|
mode: aiMode,
|
|
291
464
|
position: aiPosition,
|
|
@@ -293,20 +466,27 @@ function createDocsLayout(config) {
|
|
|
293
466
|
triggerComponentHtml: aiTriggerComponentHtml,
|
|
294
467
|
suggestedQuestions: aiSuggestedQuestions,
|
|
295
468
|
aiLabel,
|
|
469
|
+
loaderVariant: aiLoaderVariant,
|
|
296
470
|
loadingComponentHtml: aiLoadingComponentHtml
|
|
297
471
|
}),
|
|
298
472
|
/* @__PURE__ */ jsx(DocsPageClient, {
|
|
299
473
|
tocEnabled,
|
|
474
|
+
tocStyle,
|
|
300
475
|
breadcrumbEnabled,
|
|
301
476
|
entry: config.entry,
|
|
302
477
|
copyMarkdown: copyMarkdownEnabled,
|
|
303
478
|
openDocs: openDocsEnabled,
|
|
304
479
|
openDocsProviders,
|
|
305
480
|
pageActionsPosition,
|
|
481
|
+
pageActionsAlignment,
|
|
306
482
|
githubUrl,
|
|
307
483
|
githubBranch,
|
|
308
484
|
githubDirectory,
|
|
309
485
|
lastModifiedMap,
|
|
486
|
+
lastUpdatedEnabled,
|
|
487
|
+
lastUpdatedPosition,
|
|
488
|
+
llmsTxtEnabled,
|
|
489
|
+
descriptionMap,
|
|
310
490
|
children
|
|
311
491
|
})
|
|
312
492
|
]
|
|
@@ -328,4 +508,4 @@ function ForcedThemeScript({ theme }) {
|
|
|
328
508
|
}
|
|
329
509
|
|
|
330
510
|
//#endregion
|
|
331
|
-
export { createDocsLayout, createDocsMetadata };
|
|
511
|
+
export { createDocsLayout, createDocsMetadata, createPageMetadata };
|
|
@@ -10,6 +10,7 @@ interface SerializedProvider {
|
|
|
10
10
|
}
|
|
11
11
|
interface DocsPageClientProps {
|
|
12
12
|
tocEnabled: boolean;
|
|
13
|
+
tocStyle?: "default" | "directional";
|
|
13
14
|
breadcrumbEnabled?: boolean;
|
|
14
15
|
/** The docs entry folder name (e.g. "docs") — used to strip from breadcrumb */
|
|
15
16
|
entry?: string;
|
|
@@ -18,6 +19,8 @@ interface DocsPageClientProps {
|
|
|
18
19
|
openDocsProviders?: SerializedProvider[];
|
|
19
20
|
/** Where to render page actions relative to the title */
|
|
20
21
|
pageActionsPosition?: "above-title" | "below-title";
|
|
22
|
+
/** Horizontal alignment of page action buttons */
|
|
23
|
+
pageActionsAlignment?: "left" | "right";
|
|
21
24
|
/** GitHub repository URL (e.g. "https://github.com/user/repo") */
|
|
22
25
|
githubUrl?: string;
|
|
23
26
|
/** GitHub branch name @default "main" */
|
|
@@ -26,20 +29,37 @@ interface DocsPageClientProps {
|
|
|
26
29
|
githubDirectory?: string;
|
|
27
30
|
/** Map of pathname → formatted last-modified date string */
|
|
28
31
|
lastModifiedMap?: Record<string, string>;
|
|
32
|
+
/** Whether to show "Last updated" at all */
|
|
33
|
+
lastUpdatedEnabled?: boolean;
|
|
34
|
+
/** Where to show the "Last updated" date: "footer" (next to Edit on GitHub) or "below-title" */
|
|
35
|
+
lastUpdatedPosition?: "footer" | "below-title";
|
|
36
|
+
/** Whether llms.txt is enabled — shows links in footer */
|
|
37
|
+
llmsTxtEnabled?: boolean;
|
|
38
|
+
/** Map of pathname → frontmatter description */
|
|
39
|
+
descriptionMap?: Record<string, string>;
|
|
40
|
+
/** Frontmatter description to display below the page title (overrides descriptionMap) */
|
|
41
|
+
description?: string;
|
|
29
42
|
children: ReactNode;
|
|
30
43
|
}
|
|
31
44
|
declare function DocsPageClient({
|
|
32
45
|
tocEnabled,
|
|
46
|
+
tocStyle,
|
|
33
47
|
breadcrumbEnabled,
|
|
34
48
|
entry,
|
|
35
49
|
copyMarkdown,
|
|
36
50
|
openDocs,
|
|
37
51
|
openDocsProviders,
|
|
38
52
|
pageActionsPosition,
|
|
53
|
+
pageActionsAlignment,
|
|
39
54
|
githubUrl,
|
|
40
55
|
githubBranch,
|
|
41
56
|
githubDirectory,
|
|
42
57
|
lastModifiedMap,
|
|
58
|
+
lastUpdatedEnabled,
|
|
59
|
+
lastUpdatedPosition,
|
|
60
|
+
llmsTxtEnabled,
|
|
61
|
+
descriptionMap,
|
|
62
|
+
description,
|
|
43
63
|
children
|
|
44
64
|
}: DocsPageClientProps): react_jsx_runtime0.JSX.Element;
|
|
45
65
|
//#endregion
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { PageActions } from "./page-actions.mjs";
|
|
4
4
|
import { useEffect, useState } from "react";
|
|
5
|
+
import { createPortal } from "react-dom";
|
|
5
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
7
|
import { DocsBody, DocsPage, EditOnGitHub } from "fumadocs-ui/layouts/docs/page";
|
|
7
8
|
import { usePathname, useRouter } from "next/navigation";
|
|
@@ -13,15 +14,13 @@ import { usePathname, useRouter } from "next/navigation";
|
|
|
13
14
|
*/
|
|
14
15
|
function PathBreadcrumb({ pathname, entry }) {
|
|
15
16
|
const router = useRouter();
|
|
16
|
-
const
|
|
17
|
-
const segments = allSegments.filter((s) => s.toLowerCase() !== entry.toLowerCase());
|
|
17
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
18
18
|
if (segments.length < 2) return null;
|
|
19
19
|
const parentSegment = segments[segments.length - 2];
|
|
20
20
|
const currentSegment = segments[segments.length - 1];
|
|
21
21
|
const parentLabel = parentSegment.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
22
22
|
const currentLabel = currentSegment.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
23
|
-
const
|
|
24
|
-
const parentUrl = "/" + allSegments.slice(0, parentIndex + 1).join("/");
|
|
23
|
+
const parentUrl = "/" + segments.slice(0, segments.length - 1).join("/");
|
|
25
24
|
return /* @__PURE__ */ jsxs("nav", {
|
|
26
25
|
className: "fd-breadcrumb",
|
|
27
26
|
"aria-label": "Breadcrumb",
|
|
@@ -54,19 +53,22 @@ function PathBreadcrumb({ pathname, entry }) {
|
|
|
54
53
|
* (Copy Markdown, Open in LLM). Re-scans when the route changes.
|
|
55
54
|
*/
|
|
56
55
|
/**
|
|
57
|
-
* Build the GitHub URL for the current page's source file.
|
|
56
|
+
* Build the GitHub URL for the current page's source file (edit view).
|
|
58
57
|
*
|
|
59
58
|
* Examples:
|
|
60
|
-
* No directory: https://github.com/user/repo/
|
|
61
|
-
* With directory: https://github.com/farming-labs/docs/
|
|
59
|
+
* No directory: https://github.com/user/repo/edit/main/app/docs/cli/page.mdx
|
|
60
|
+
* With directory: https://github.com/farming-labs/docs/edit/main/website/app/docs/cli/page.mdx
|
|
62
61
|
*/
|
|
63
62
|
function buildGithubFileUrl(githubUrl, branch, pathname, directory) {
|
|
64
63
|
const segments = pathname.replace(/^\//, "").replace(/\/$/, "");
|
|
65
|
-
return `${githubUrl}/
|
|
64
|
+
return `${githubUrl}/edit/${branch}/${`${directory ? `${directory}/` : ""}app/${segments}/page.mdx`}`;
|
|
66
65
|
}
|
|
67
|
-
function DocsPageClient({ tocEnabled, breadcrumbEnabled = true, entry = "docs", copyMarkdown = false, openDocs = false, openDocsProviders, pageActionsPosition = "below-title", githubUrl, githubBranch = "main", githubDirectory, lastModifiedMap, children }) {
|
|
66
|
+
function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled = true, entry = "docs", copyMarkdown = false, openDocs = false, openDocsProviders, pageActionsPosition = "below-title", pageActionsAlignment = "left", githubUrl, githubBranch = "main", githubDirectory, lastModifiedMap, lastUpdatedEnabled = true, lastUpdatedPosition = "footer", llmsTxtEnabled = false, descriptionMap, description, children }) {
|
|
67
|
+
const fdTocStyle = tocStyle === "directional" ? "clerk" : void 0;
|
|
68
68
|
const [toc, setToc] = useState([]);
|
|
69
69
|
const pathname = usePathname();
|
|
70
|
+
const [actionsPortalTarget, setActionsPortalTarget] = useState(null);
|
|
71
|
+
const pageDescription = description ?? descriptionMap?.[pathname.replace(/\/$/, "") || "/"];
|
|
70
72
|
useEffect(() => {
|
|
71
73
|
if (!tocEnabled) return;
|
|
72
74
|
const timer = requestAnimationFrame(() => {
|
|
@@ -81,29 +83,102 @@ function DocsPageClient({ tocEnabled, breadcrumbEnabled = true, entry = "docs",
|
|
|
81
83
|
});
|
|
82
84
|
return () => cancelAnimationFrame(timer);
|
|
83
85
|
}, [tocEnabled, pathname]);
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (!pageDescription) return;
|
|
88
|
+
const timer = requestAnimationFrame(() => {
|
|
89
|
+
const container = document.getElementById("nd-page");
|
|
90
|
+
if (!container) return;
|
|
91
|
+
const existingDesc = container.querySelector(".fd-page-description");
|
|
92
|
+
if (existingDesc) existingDesc.remove();
|
|
93
|
+
const h1 = container.querySelector("h1");
|
|
94
|
+
if (!h1) return;
|
|
95
|
+
const descEl = document.createElement("p");
|
|
96
|
+
descEl.className = "fd-page-description";
|
|
97
|
+
descEl.textContent = pageDescription;
|
|
98
|
+
h1.insertAdjacentElement("afterend", descEl);
|
|
99
|
+
});
|
|
100
|
+
return () => {
|
|
101
|
+
cancelAnimationFrame(timer);
|
|
102
|
+
const desc = document.querySelector("#nd-page .fd-page-description");
|
|
103
|
+
if (desc) desc.remove();
|
|
104
|
+
};
|
|
105
|
+
}, [pageDescription, pathname]);
|
|
84
106
|
const showActions = copyMarkdown || openDocs;
|
|
85
107
|
const githubFileUrl = githubUrl ? buildGithubFileUrl(githubUrl, githubBranch, pathname, githubDirectory) : void 0;
|
|
86
108
|
const normalizedPath = pathname.replace(/\/$/, "") || "/";
|
|
87
|
-
const lastModified = lastModifiedMap?.[normalizedPath];
|
|
88
|
-
const
|
|
109
|
+
const lastModified = lastUpdatedEnabled ? lastModifiedMap?.[normalizedPath] : void 0;
|
|
110
|
+
const showLastUpdatedBelowTitle = !!lastModified && lastUpdatedPosition === "below-title";
|
|
111
|
+
const showLastUpdatedInFooter = !!lastModified && lastUpdatedPosition === "footer";
|
|
112
|
+
const showFooter = !!githubFileUrl || showLastUpdatedInFooter || llmsTxtEnabled;
|
|
113
|
+
const needsBelowTitleBlock = showLastUpdatedBelowTitle || showActions;
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
if (!needsBelowTitleBlock) return;
|
|
116
|
+
const timer = requestAnimationFrame(() => {
|
|
117
|
+
const container = document.getElementById("nd-page");
|
|
118
|
+
if (!container) return;
|
|
119
|
+
container.querySelectorAll(".fd-below-title-block").forEach((el) => el.remove());
|
|
120
|
+
const h1 = container.querySelector("h1");
|
|
121
|
+
if (!h1) return;
|
|
122
|
+
let insertAfter = h1;
|
|
123
|
+
const desc = container.querySelector(".fd-page-description");
|
|
124
|
+
if (desc) insertAfter = desc;
|
|
125
|
+
const wrapper = document.createElement("div");
|
|
126
|
+
wrapper.className = "fd-below-title-block not-prose";
|
|
127
|
+
if (showLastUpdatedBelowTitle) {
|
|
128
|
+
const lastUpdatedEl = document.createElement("p");
|
|
129
|
+
lastUpdatedEl.className = "fd-last-updated-inline";
|
|
130
|
+
lastUpdatedEl.textContent = `Last updated ${lastModified}`;
|
|
131
|
+
wrapper.appendChild(lastUpdatedEl);
|
|
132
|
+
}
|
|
133
|
+
if (showLastUpdatedBelowTitle || showActions) {
|
|
134
|
+
const hr = document.createElement("hr");
|
|
135
|
+
hr.className = "fd-title-separator";
|
|
136
|
+
wrapper.appendChild(hr);
|
|
137
|
+
}
|
|
138
|
+
if (showActions) {
|
|
139
|
+
const portalEl = document.createElement("div");
|
|
140
|
+
portalEl.className = "fd-actions-portal";
|
|
141
|
+
portalEl.setAttribute("data-actions-alignment", pageActionsAlignment);
|
|
142
|
+
wrapper.appendChild(portalEl);
|
|
143
|
+
setActionsPortalTarget(portalEl);
|
|
144
|
+
}
|
|
145
|
+
insertAfter.insertAdjacentElement("afterend", wrapper);
|
|
146
|
+
});
|
|
147
|
+
return () => {
|
|
148
|
+
cancelAnimationFrame(timer);
|
|
149
|
+
setActionsPortalTarget(null);
|
|
150
|
+
document.querySelectorAll("#nd-page .fd-below-title-block").forEach((el) => el.remove());
|
|
151
|
+
};
|
|
152
|
+
}, [
|
|
153
|
+
lastModified,
|
|
154
|
+
needsBelowTitleBlock,
|
|
155
|
+
showLastUpdatedBelowTitle,
|
|
156
|
+
showActions,
|
|
157
|
+
pageActionsAlignment,
|
|
158
|
+
pathname
|
|
159
|
+
]);
|
|
89
160
|
return /* @__PURE__ */ jsxs(DocsPage, {
|
|
90
161
|
toc,
|
|
91
|
-
tableOfContent: {
|
|
92
|
-
|
|
162
|
+
tableOfContent: {
|
|
163
|
+
enabled: tocEnabled,
|
|
164
|
+
style: fdTocStyle
|
|
165
|
+
},
|
|
166
|
+
tableOfContentPopover: {
|
|
167
|
+
enabled: tocEnabled,
|
|
168
|
+
style: fdTocStyle
|
|
169
|
+
},
|
|
93
170
|
breadcrumb: { enabled: false },
|
|
94
171
|
children: [
|
|
95
172
|
breadcrumbEnabled && /* @__PURE__ */ jsx(PathBreadcrumb, {
|
|
96
173
|
pathname,
|
|
97
174
|
entry
|
|
98
175
|
}),
|
|
99
|
-
showActions && /* @__PURE__ */ jsx(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
})
|
|
106
|
-
}),
|
|
176
|
+
showActions && actionsPortalTarget && createPortal(/* @__PURE__ */ jsx(PageActions, {
|
|
177
|
+
copyMarkdown,
|
|
178
|
+
openDocs,
|
|
179
|
+
providers: openDocsProviders,
|
|
180
|
+
githubFileUrl
|
|
181
|
+
}), actionsPortalTarget),
|
|
107
182
|
/* @__PURE__ */ jsxs(DocsBody, {
|
|
108
183
|
style: {
|
|
109
184
|
display: "flex",
|
|
@@ -114,10 +189,29 @@ function DocsPageClient({ tocEnabled, breadcrumbEnabled = true, entry = "docs",
|
|
|
114
189
|
children
|
|
115
190
|
}), showFooter && /* @__PURE__ */ jsxs("div", {
|
|
116
191
|
className: "not-prose fd-page-footer",
|
|
117
|
-
children: [
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
192
|
+
children: [
|
|
193
|
+
githubFileUrl && /* @__PURE__ */ jsx(EditOnGitHub, { href: githubFileUrl }),
|
|
194
|
+
llmsTxtEnabled && /* @__PURE__ */ jsxs("span", {
|
|
195
|
+
className: "fd-llms-txt-links",
|
|
196
|
+
children: [/* @__PURE__ */ jsx("a", {
|
|
197
|
+
href: "/api/docs?format=llms",
|
|
198
|
+
target: "_blank",
|
|
199
|
+
rel: "noopener noreferrer",
|
|
200
|
+
className: "fd-llms-txt-link",
|
|
201
|
+
children: "llms.txt"
|
|
202
|
+
}), /* @__PURE__ */ jsx("a", {
|
|
203
|
+
href: "/api/docs?format=llms-full",
|
|
204
|
+
target: "_blank",
|
|
205
|
+
rel: "noopener noreferrer",
|
|
206
|
+
className: "fd-llms-txt-link",
|
|
207
|
+
children: "llms-full.txt"
|
|
208
|
+
})]
|
|
209
|
+
}),
|
|
210
|
+
showLastUpdatedInFooter && lastModified && /* @__PURE__ */ jsxs("span", {
|
|
211
|
+
className: "fd-last-updated-footer",
|
|
212
|
+
children: ["Last updated ", lastModified]
|
|
213
|
+
})
|
|
214
|
+
]
|
|
121
215
|
})]
|
|
122
216
|
})
|
|
123
217
|
]
|