@farming-labs/theme 0.1.13 → 0.1.17
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/docs-api.d.mts +3 -1
- package/dist/docs-api.mjs +155 -9
- package/dist/docs-layout.mjs +96 -7
- package/dist/docs-page-client.d.mts +2 -0
- package/dist/docs-page-client.mjs +30 -11
- package/dist/index.d.mts +2 -2
- package/package.json +2 -2
- package/styles/base.css +666 -0
- package/styles/colorful.css +37 -19
package/dist/docs-api.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DocsI18nConfig, DocsMcpConfig, DocsSearchConfig, OrderingItem } from "@farming-labs/docs";
|
|
1
|
+
import { ChangelogConfig, DocsI18nConfig, DocsMcpConfig, DocsSearchConfig, OrderingItem } from "@farming-labs/docs";
|
|
2
2
|
|
|
3
3
|
//#region src/docs-api.d.ts
|
|
4
4
|
interface AIProviderConfig {
|
|
@@ -31,6 +31,8 @@ interface DocsAPIOptions {
|
|
|
31
31
|
entry?: string;
|
|
32
32
|
/** Override the docs content directory when it does not live in app/<entry>. */
|
|
33
33
|
contentDir?: string;
|
|
34
|
+
/** Changelog configuration. */
|
|
35
|
+
changelog?: boolean | ChangelogConfig;
|
|
34
36
|
/** Search language (default: "english") */
|
|
35
37
|
language?: string;
|
|
36
38
|
/** AI chat configuration */
|
package/dist/docs-api.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { getNextAppDir } from "./get-app-dir.mjs";
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import matter from "gray-matter";
|
|
6
|
-
import { performDocsSearch, resolveDocsI18n, resolveDocsLocale, resolveSearchRequestConfig } from "@farming-labs/docs";
|
|
6
|
+
import { performDocsSearch, resolveChangelogConfig, resolveDocsI18n, resolveDocsLocale, resolveSearchRequestConfig } from "@farming-labs/docs";
|
|
7
7
|
import { createDocsMcpHttpHandler, createFilesystemDocsMcpSource } from "@farming-labs/docs/server";
|
|
8
8
|
|
|
9
9
|
//#region src/docs-api.ts
|
|
@@ -278,10 +278,18 @@ function stripMdx(raw) {
|
|
|
278
278
|
const { content } = matter(raw);
|
|
279
279
|
return content.replace(/^(import|export)\s.*$/gm, "").replace(/<[^>]+\/>/g, "").replace(/<\/?[A-Z][^>]*>/g, "").replace(/<\/?[a-z][^>]*>/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1").replace(/^#{1,6}\s+/gm, "").replace(/(\*{1,3}|_{1,3})(.*?)\1/g, "$2").replace(/```[\s\S]*?```/g, "").replace(/`([^`]+)`/g, "$1").replace(/^>\s+/gm, "").replace(/^[-*_]{3,}\s*$/gm, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
280
280
|
}
|
|
281
|
-
function scanDocsDir(docsDir, entry, locale) {
|
|
281
|
+
function scanDocsDir(docsDir, entry, locale, excludedDirs = []) {
|
|
282
282
|
const indexes = [];
|
|
283
|
+
function isExcluded(dir) {
|
|
284
|
+
const resolved = path.resolve(dir);
|
|
285
|
+
return excludedDirs.some((excluded) => {
|
|
286
|
+
const relative = path.relative(excluded, resolved);
|
|
287
|
+
return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
283
290
|
function scan(dir, slugParts) {
|
|
284
291
|
if (!fs.existsSync(dir)) return;
|
|
292
|
+
if (isExcluded(dir)) return;
|
|
285
293
|
const pagePath = path.join(dir, "page.mdx");
|
|
286
294
|
if (fs.existsSync(pagePath)) try {
|
|
287
295
|
const raw = fs.readFileSync(pagePath, "utf-8");
|
|
@@ -316,6 +324,82 @@ function scanDocsDir(docsDir, entry, locale) {
|
|
|
316
324
|
scan(docsDir, []);
|
|
317
325
|
return indexes;
|
|
318
326
|
}
|
|
327
|
+
function scanChangelogDir(changelogDir, entryPath, changelogPath, locale) {
|
|
328
|
+
if (!fs.existsSync(changelogDir)) return [];
|
|
329
|
+
const indexes = [];
|
|
330
|
+
let entries;
|
|
331
|
+
try {
|
|
332
|
+
entries = fs.readdirSync(changelogDir).sort().reverse();
|
|
333
|
+
} catch {
|
|
334
|
+
return indexes;
|
|
335
|
+
}
|
|
336
|
+
for (const name of entries) {
|
|
337
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(name)) continue;
|
|
338
|
+
const entryDir = path.join(changelogDir, name);
|
|
339
|
+
let isDirectory = false;
|
|
340
|
+
try {
|
|
341
|
+
isDirectory = fs.existsSync(entryDir) && fs.statSync(entryDir).isDirectory();
|
|
342
|
+
} catch {
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
if (!isDirectory) continue;
|
|
346
|
+
const pagePath = path.join(entryDir, "page.mdx");
|
|
347
|
+
if (!fs.existsSync(pagePath)) continue;
|
|
348
|
+
try {
|
|
349
|
+
const raw = fs.readFileSync(pagePath, "utf-8");
|
|
350
|
+
const { data, content: rawContent } = matter(raw);
|
|
351
|
+
if (data.draft === true) continue;
|
|
352
|
+
const title = data.title || name.replace(/-/g, " ");
|
|
353
|
+
const description = data.description;
|
|
354
|
+
const content = stripMdx(raw);
|
|
355
|
+
const url = withLangInUrl(`/${entryPath}/${changelogPath}/${name}`, locale);
|
|
356
|
+
const tags = Array.isArray(data.tags) ? data.tags.filter((value) => typeof value === "string") : void 0;
|
|
357
|
+
indexes.push({
|
|
358
|
+
title,
|
|
359
|
+
description,
|
|
360
|
+
content,
|
|
361
|
+
rawContent,
|
|
362
|
+
url,
|
|
363
|
+
locale,
|
|
364
|
+
type: "changelog",
|
|
365
|
+
version: typeof data.version === "string" ? data.version : void 0,
|
|
366
|
+
tags
|
|
367
|
+
});
|
|
368
|
+
} catch {}
|
|
369
|
+
}
|
|
370
|
+
return indexes;
|
|
371
|
+
}
|
|
372
|
+
function normalizePathSegment(value) {
|
|
373
|
+
return value.replace(/^\/+|\/+$/g, "");
|
|
374
|
+
}
|
|
375
|
+
function normalizeUrlPath(value) {
|
|
376
|
+
const normalized = value.replace(/\/+/g, "/");
|
|
377
|
+
if (normalized === "/") return normalized;
|
|
378
|
+
return normalized.replace(/\/+$/, "");
|
|
379
|
+
}
|
|
380
|
+
function normalizeRequestedMarkdownPath(entry, requestedPath) {
|
|
381
|
+
const trimmed = requestedPath.trim().replace(/\.md$/i, "");
|
|
382
|
+
if (!trimmed) return `/${entry}`;
|
|
383
|
+
const normalized = normalizeUrlPath(trimmed.startsWith("/") ? trimmed : `/${trimmed}`);
|
|
384
|
+
const normalizedEntry = `/${normalizePathSegment(entry)}`;
|
|
385
|
+
if (normalized === normalizedEntry || normalized.startsWith(`${normalizedEntry}/`)) return normalized;
|
|
386
|
+
const slug = normalizePathSegment(trimmed);
|
|
387
|
+
return slug ? normalizeUrlPath(`${normalizedEntry}/${slug}`) : normalizedEntry;
|
|
388
|
+
}
|
|
389
|
+
function findDocsMcpPage(entry, pages, requestedPath) {
|
|
390
|
+
const normalizedRequest = normalizeRequestedMarkdownPath(entry, requestedPath);
|
|
391
|
+
for (const page of pages) if (normalizeUrlPath(page.url) === normalizedRequest) return page;
|
|
392
|
+
const normalizedSlug = normalizePathSegment(requestedPath.replace(/^\//, "").replace(/\.md$/i, ""));
|
|
393
|
+
for (const page of pages) if (normalizePathSegment(page.slug) === normalizedSlug) return page;
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
function renderMarkdownDocument(page) {
|
|
397
|
+
if ("agentRawContent" in page && page.agentRawContent !== void 0) return page.agentRawContent;
|
|
398
|
+
const lines = [`# ${page.title}`, `URL: ${page.url}`];
|
|
399
|
+
if (page.description) lines.push(`Description: ${page.description}`);
|
|
400
|
+
lines.push("", page.rawContent ?? page.content);
|
|
401
|
+
return lines.join("\n");
|
|
402
|
+
}
|
|
319
403
|
const DEFAULT_SYSTEM_PROMPT = `You are a helpful documentation assistant. Answer questions based on the provided documentation context. Be concise and accurate. If the answer is not in the context, say so honestly. Use markdown formatting for code examples and links.`;
|
|
320
404
|
function resolveModelAndProvider(aiConfig, requestedModelId) {
|
|
321
405
|
const raw = aiConfig.model;
|
|
@@ -463,6 +547,7 @@ function createDocsAPI(options) {
|
|
|
463
547
|
const entry = options?.entry ?? readEntry(root);
|
|
464
548
|
const appDir = getNextAppDir(root);
|
|
465
549
|
const contentDir = options?.contentDir ?? path.join(appDir, entry);
|
|
550
|
+
const changelogConfig = resolveChangelogConfig(options?.changelog);
|
|
466
551
|
const i18n = resolveDocsI18n(options?.i18n ?? readI18nConfig(root));
|
|
467
552
|
const aiConfig = options?.ai ?? readAIConfig(root);
|
|
468
553
|
const searchConfig = options?.search;
|
|
@@ -498,32 +583,78 @@ function createDocsAPI(options) {
|
|
|
498
583
|
} catch {}
|
|
499
584
|
return i18n.defaultLocale;
|
|
500
585
|
}
|
|
586
|
+
function resolveChangelogDirCandidates(docsDirs) {
|
|
587
|
+
if (!changelogConfig.enabled) return [];
|
|
588
|
+
if (path.isAbsolute(changelogConfig.contentDir)) return [changelogConfig.contentDir];
|
|
589
|
+
return docsDirs.map((docsDir) => path.join(docsDir, changelogConfig.contentDir));
|
|
590
|
+
}
|
|
591
|
+
function isWithinDir(candidate, target) {
|
|
592
|
+
const relative = path.relative(target, candidate);
|
|
593
|
+
return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
594
|
+
}
|
|
501
595
|
function resolveContextFromRequest(request) {
|
|
502
|
-
if (!i18n)
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
596
|
+
if (!i18n) {
|
|
597
|
+
const docsDirs = resolveDocsDirCandidates();
|
|
598
|
+
return {
|
|
599
|
+
entryPath: entry,
|
|
600
|
+
docsDirs,
|
|
601
|
+
changelogDirs: resolveChangelogDirCandidates(docsDirs)
|
|
602
|
+
};
|
|
603
|
+
}
|
|
506
604
|
const locale = resolveLocaleFromRequest(request) ?? i18n.defaultLocale;
|
|
605
|
+
const docsDirs = resolveDocsDirCandidates(locale);
|
|
507
606
|
return {
|
|
508
607
|
entryPath: entry,
|
|
509
608
|
locale,
|
|
510
|
-
docsDirs
|
|
609
|
+
docsDirs,
|
|
610
|
+
changelogDirs: resolveChangelogDirCandidates(docsDirs)
|
|
511
611
|
};
|
|
512
612
|
}
|
|
513
613
|
const indexesByLocale = /* @__PURE__ */ new Map();
|
|
514
614
|
const llmsCacheByLocale = /* @__PURE__ */ new Map();
|
|
615
|
+
const markdownSourcesByLocale = /* @__PURE__ */ new Map();
|
|
515
616
|
function getIndexes(ctx) {
|
|
516
617
|
const key = ctx.locale ?? "__default__";
|
|
517
618
|
const cached = indexesByLocale.get(key);
|
|
518
619
|
if (cached) return cached;
|
|
519
620
|
let next = [];
|
|
520
621
|
for (const docsDir of ctx.docsDirs) {
|
|
521
|
-
|
|
522
|
-
|
|
622
|
+
const excludedDirs = ctx.changelogDirs.filter((dir) => isWithinDir(dir, docsDir));
|
|
623
|
+
const docsPages = scanDocsDir(docsDir, ctx.entryPath, ctx.locale, excludedDirs);
|
|
624
|
+
if (docsPages.length === 0) continue;
|
|
625
|
+
next = docsPages;
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
if (changelogConfig.enabled) {
|
|
629
|
+
const changelogPages = ctx.changelogDirs.flatMap((dir) => scanChangelogDir(dir, ctx.entryPath, changelogConfig.path, ctx.locale));
|
|
630
|
+
next = [...next, ...changelogPages];
|
|
523
631
|
}
|
|
524
632
|
indexesByLocale.set(key, next);
|
|
525
633
|
return next;
|
|
526
634
|
}
|
|
635
|
+
function getMarkdownSources(ctx) {
|
|
636
|
+
const key = ctx.locale ?? "__default__";
|
|
637
|
+
const cached = markdownSourcesByLocale.get(key);
|
|
638
|
+
if (cached) return cached;
|
|
639
|
+
const sources = ctx.docsDirs.map((docsDir) => createFilesystemDocsMcpSource({
|
|
640
|
+
rootDir: root,
|
|
641
|
+
entry: ctx.entryPath,
|
|
642
|
+
contentDir: docsDir
|
|
643
|
+
}));
|
|
644
|
+
markdownSourcesByLocale.set(key, sources);
|
|
645
|
+
return sources;
|
|
646
|
+
}
|
|
647
|
+
async function getMarkdownDocument(ctx, requestedPath) {
|
|
648
|
+
for (const source of getMarkdownSources(ctx)) {
|
|
649
|
+
const page = findDocsMcpPage(ctx.entryPath, await source.getPages(), requestedPath);
|
|
650
|
+
if (page) return renderMarkdownDocument(page);
|
|
651
|
+
}
|
|
652
|
+
const normalizedRequest = normalizeRequestedMarkdownPath(ctx.entryPath, requestedPath);
|
|
653
|
+
const fallbackPage = getIndexes(ctx).find((page) => normalizeUrlPath(page.url) === normalizedRequest);
|
|
654
|
+
if (fallbackPage) return renderMarkdownDocument(fallbackPage);
|
|
655
|
+
for (const page of getIndexes(ctx)) if (normalizePathSegment(page.url.replace(/^\/+/, "").replace(`${ctx.entryPath}/`, "")) === normalizePathSegment(requestedPath.replace(/^\/+/, "").replace(/\.md$/i, ""))) return renderMarkdownDocument(page);
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
527
658
|
function getLlmsContent(ctx) {
|
|
528
659
|
const key = ctx.locale ?? "__default__";
|
|
529
660
|
const cached = llmsCacheByLocale.get(key);
|
|
@@ -541,6 +672,21 @@ function createDocsAPI(options) {
|
|
|
541
672
|
const ctx = resolveContextFromRequest(request);
|
|
542
673
|
const url = new URL(request.url);
|
|
543
674
|
const format = url.searchParams.get("format");
|
|
675
|
+
if (format === "markdown") {
|
|
676
|
+
const document = await getMarkdownDocument(ctx, url.searchParams.get("path")?.trim() ?? "");
|
|
677
|
+
if (!document) return new Response("Not Found", {
|
|
678
|
+
status: 404,
|
|
679
|
+
headers: {
|
|
680
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
681
|
+
"X-Robots-Tag": "noindex"
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
return new Response(document, { headers: {
|
|
685
|
+
"Content-Type": "text/markdown; charset=utf-8",
|
|
686
|
+
"Cache-Control": "public, max-age=0, s-maxage=3600",
|
|
687
|
+
"X-Robots-Tag": "noindex"
|
|
688
|
+
} });
|
|
689
|
+
}
|
|
544
690
|
if (format === "llms") return new Response(getLlmsContent(ctx).llmsTxt, { headers: {
|
|
545
691
|
"Content-Type": "text/plain; charset=utf-8",
|
|
546
692
|
"Cache-Control": "public, max-age=3600"
|
package/dist/docs-layout.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import path from "node:path";
|
|
|
10
10
|
import matter from "gray-matter";
|
|
11
11
|
import { DocsLayout } from "fumadocs-ui/layouts/docs";
|
|
12
12
|
import { Suspense } from "react";
|
|
13
|
-
import { buildPageOpenGraph, buildPageTwitter } from "@farming-labs/docs";
|
|
13
|
+
import { buildPageOpenGraph, buildPageTwitter, resolveChangelogConfig } from "@farming-labs/docs";
|
|
14
14
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
15
15
|
|
|
16
16
|
//#region src/docs-layout.tsx
|
|
@@ -33,10 +33,19 @@ function readFrontmatter(filePath) {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
/** Check if a directory has any subdirectories that contain page.mdx. */
|
|
36
|
-
function
|
|
36
|
+
function isWithinDir(candidate, target) {
|
|
37
|
+
const relative = path.relative(target, candidate);
|
|
38
|
+
return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
39
|
+
}
|
|
40
|
+
function isExcludedDir(dir, excludedDirs) {
|
|
41
|
+
const resolved = path.resolve(dir);
|
|
42
|
+
return excludedDirs.some((excluded) => isWithinDir(resolved, excluded));
|
|
43
|
+
}
|
|
44
|
+
function hasChildPages(dir, excludedDirs) {
|
|
37
45
|
if (!fs.existsSync(dir)) return false;
|
|
38
46
|
for (const name of fs.readdirSync(dir)) {
|
|
39
47
|
const full = path.join(dir, name);
|
|
48
|
+
if (isExcludedDir(full, excludedDirs)) continue;
|
|
40
49
|
if (fs.statSync(full).isDirectory() && fs.existsSync(path.join(full, "page.mdx"))) return true;
|
|
41
50
|
}
|
|
42
51
|
return false;
|
|
@@ -76,11 +85,70 @@ function resolveDocsLocaleContext(config, locale) {
|
|
|
76
85
|
docsDir: resolveContentDir(resolvedLocale)
|
|
77
86
|
};
|
|
78
87
|
}
|
|
88
|
+
function getExcludedDocsDirs(config, ctx) {
|
|
89
|
+
const changelog = resolveChangelogConfig(config.changelog);
|
|
90
|
+
if (!changelog.enabled) return [];
|
|
91
|
+
const dir = path.isAbsolute(changelog.contentDir) ? changelog.contentDir : path.join(ctx.docsDir, changelog.contentDir);
|
|
92
|
+
return [path.resolve(dir)];
|
|
93
|
+
}
|
|
94
|
+
function readChangelogTreeEntries(config, ctx) {
|
|
95
|
+
const changelog = resolveChangelogConfig(config.changelog);
|
|
96
|
+
if (!changelog.enabled) return [];
|
|
97
|
+
const changelogDir = path.isAbsolute(changelog.contentDir) ? changelog.contentDir : path.join(ctx.docsDir, changelog.contentDir);
|
|
98
|
+
if (!fs.existsSync(changelogDir)) return [];
|
|
99
|
+
const entries = [];
|
|
100
|
+
for (const name of fs.readdirSync(changelogDir)) {
|
|
101
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(name)) continue;
|
|
102
|
+
const entryDir = path.join(changelogDir, name);
|
|
103
|
+
if (!fs.existsSync(entryDir) || !fs.statSync(entryDir).isDirectory()) continue;
|
|
104
|
+
const pagePath = path.join(entryDir, "page.mdx");
|
|
105
|
+
if (!fs.existsSync(pagePath)) continue;
|
|
106
|
+
const data = readFrontmatter(pagePath);
|
|
107
|
+
if (data.draft === true) continue;
|
|
108
|
+
entries.push({
|
|
109
|
+
slug: name,
|
|
110
|
+
date: name,
|
|
111
|
+
title: data.title ?? name.replace(/-/g, " "),
|
|
112
|
+
pinned: data.pinned === true
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return entries.sort((left, right) => {
|
|
116
|
+
if (left.pinned !== right.pinned) return left.pinned ? -1 : 1;
|
|
117
|
+
return right.date.localeCompare(left.date);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function buildChangelogTree(config, ctx, flat = false) {
|
|
121
|
+
const changelog = resolveChangelogConfig(config.changelog);
|
|
122
|
+
if (!changelog.enabled) return null;
|
|
123
|
+
const entries = readChangelogTreeEntries(config, ctx);
|
|
124
|
+
if (entries.length === 0) return null;
|
|
125
|
+
const url = `/${ctx.entryPath}/${changelog.path}`;
|
|
126
|
+
const children = entries.map((entry) => ({
|
|
127
|
+
type: "page",
|
|
128
|
+
name: entry.title,
|
|
129
|
+
url: `${url}/${entry.slug}`
|
|
130
|
+
}));
|
|
131
|
+
return {
|
|
132
|
+
type: "folder",
|
|
133
|
+
name: changelog.title,
|
|
134
|
+
index: {
|
|
135
|
+
type: "page",
|
|
136
|
+
name: changelog.title,
|
|
137
|
+
url
|
|
138
|
+
},
|
|
139
|
+
children,
|
|
140
|
+
...flat ? {
|
|
141
|
+
collapsible: false,
|
|
142
|
+
defaultOpen: true
|
|
143
|
+
} : {}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
79
146
|
function buildTree(config, ctx, flat = false) {
|
|
80
147
|
const docsDir = ctx.docsDir;
|
|
81
148
|
const icons = config.icons;
|
|
82
149
|
const ordering = config.ordering;
|
|
83
150
|
const rootChildren = [];
|
|
151
|
+
const excludedDirs = getExcludedDocsDirs(config, ctx);
|
|
84
152
|
if (fs.existsSync(path.join(docsDir, "page.mdx"))) {
|
|
85
153
|
const data = readFrontmatter(path.join(docsDir, "page.mdx"));
|
|
86
154
|
rootChildren.push({
|
|
@@ -92,6 +160,7 @@ function buildTree(config, ctx, flat = false) {
|
|
|
92
160
|
}
|
|
93
161
|
function buildNode(dir, name, baseSlug, slugOrder) {
|
|
94
162
|
const full = path.join(dir, name);
|
|
163
|
+
if (isExcludedDir(full, excludedDirs)) return null;
|
|
95
164
|
if (!fs.statSync(full).isDirectory()) return null;
|
|
96
165
|
const pagePath = path.join(full, "page.mdx");
|
|
97
166
|
if (!fs.existsSync(pagePath)) return null;
|
|
@@ -100,7 +169,7 @@ function buildTree(config, ctx, flat = false) {
|
|
|
100
169
|
const url = `/${ctx.entryPath}/${slug.join("/")}`;
|
|
101
170
|
const icon = resolveIcon(data.icon, icons);
|
|
102
171
|
const displayName = data.title ?? name.replace(/-/g, " ");
|
|
103
|
-
if (hasChildPages(full)) {
|
|
172
|
+
if (hasChildPages(full, excludedDirs)) {
|
|
104
173
|
const folderChildren = scanDir(full, slug, slugOrder);
|
|
105
174
|
return {
|
|
106
175
|
type: "folder",
|
|
@@ -135,10 +204,12 @@ function buildTree(config, ctx, flat = false) {
|
|
|
135
204
|
for (const item of slugOrder) slugMap.set(item.slug, item);
|
|
136
205
|
for (const item of slugOrder) {
|
|
137
206
|
if (!entries.includes(item.slug)) continue;
|
|
207
|
+
if (isExcludedDir(path.join(dir, item.slug), excludedDirs)) continue;
|
|
138
208
|
const node = buildNode(dir, item.slug, baseSlug, item.children);
|
|
139
209
|
if (node) nodes.push(node);
|
|
140
210
|
}
|
|
141
211
|
for (const name of entries) {
|
|
212
|
+
if (isExcludedDir(path.join(dir, name), excludedDirs)) continue;
|
|
142
213
|
if (slugMap.has(name)) continue;
|
|
143
214
|
const node = buildNode(dir, name, baseSlug);
|
|
144
215
|
if (node) nodes.push(node);
|
|
@@ -149,6 +220,7 @@ function buildTree(config, ctx, flat = false) {
|
|
|
149
220
|
const nodes = [];
|
|
150
221
|
for (const name of entries) {
|
|
151
222
|
const full = path.join(dir, name);
|
|
223
|
+
if (isExcludedDir(full, excludedDirs)) continue;
|
|
152
224
|
if (!fs.statSync(full).isDirectory()) continue;
|
|
153
225
|
const pagePath = path.join(full, "page.mdx");
|
|
154
226
|
if (!fs.existsSync(pagePath)) continue;
|
|
@@ -168,6 +240,7 @@ function buildTree(config, ctx, flat = false) {
|
|
|
168
240
|
}
|
|
169
241
|
const nodes = [];
|
|
170
242
|
for (const name of entries) {
|
|
243
|
+
if (isExcludedDir(path.join(dir, name), excludedDirs)) continue;
|
|
171
244
|
const node = buildNode(dir, name, baseSlug);
|
|
172
245
|
if (node) nodes.push(node);
|
|
173
246
|
}
|
|
@@ -175,6 +248,14 @@ function buildTree(config, ctx, flat = false) {
|
|
|
175
248
|
}
|
|
176
249
|
const rootSlugOrder = Array.isArray(ordering) ? ordering : void 0;
|
|
177
250
|
rootChildren.push(...scanDir(docsDir, [], rootSlugOrder));
|
|
251
|
+
const changelogTree = buildChangelogTree(config, ctx, flat);
|
|
252
|
+
if (changelogTree) {
|
|
253
|
+
if (rootChildren.length > 0) rootChildren.push({
|
|
254
|
+
type: "separator",
|
|
255
|
+
name: "Updates"
|
|
256
|
+
});
|
|
257
|
+
rootChildren.push(changelogTree);
|
|
258
|
+
}
|
|
178
259
|
return {
|
|
179
260
|
name: "Docs",
|
|
180
261
|
children: rootChildren
|
|
@@ -186,6 +267,7 @@ function localizeTreeUrls(tree, locale) {
|
|
|
186
267
|
...node,
|
|
187
268
|
url: withLangInUrl(node.url, locale)
|
|
188
269
|
};
|
|
270
|
+
if (node.type === "separator") return node;
|
|
189
271
|
return {
|
|
190
272
|
...node,
|
|
191
273
|
index: node.index ? {
|
|
@@ -204,9 +286,10 @@ function localizeTreeUrls(tree, locale) {
|
|
|
204
286
|
* Scan all page.mdx files under the docs entry directory and build
|
|
205
287
|
* a map of URL pathname → formatted last-modified date string.
|
|
206
288
|
*/
|
|
207
|
-
function buildLastModifiedMap(ctx) {
|
|
289
|
+
function buildLastModifiedMap(config, ctx) {
|
|
208
290
|
const docsDir = ctx.docsDir;
|
|
209
291
|
const map = {};
|
|
292
|
+
const excludedDirs = getExcludedDocsDirs(config, ctx);
|
|
210
293
|
function formatDate(date) {
|
|
211
294
|
return date.toLocaleDateString("en-US", {
|
|
212
295
|
year: "numeric",
|
|
@@ -216,6 +299,7 @@ function buildLastModifiedMap(ctx) {
|
|
|
216
299
|
}
|
|
217
300
|
function scan(dir, slugParts) {
|
|
218
301
|
if (!fs.existsSync(dir)) return;
|
|
302
|
+
if (isExcludedDir(dir, excludedDirs)) return;
|
|
219
303
|
const pagePath = path.join(dir, "page.mdx");
|
|
220
304
|
if (fs.existsSync(pagePath)) {
|
|
221
305
|
const url = slugParts.length === 0 ? `/${ctx.entryPath}` : `/${ctx.entryPath}/${slugParts.join("/")}`;
|
|
@@ -233,11 +317,13 @@ function buildLastModifiedMap(ctx) {
|
|
|
233
317
|
* Scan all page.mdx files and build a map of URL pathname → description
|
|
234
318
|
* from the frontmatter `description` field.
|
|
235
319
|
*/
|
|
236
|
-
function buildDescriptionMap(ctx) {
|
|
320
|
+
function buildDescriptionMap(config, ctx) {
|
|
237
321
|
const docsDir = ctx.docsDir;
|
|
238
322
|
const map = {};
|
|
323
|
+
const excludedDirs = getExcludedDocsDirs(config, ctx);
|
|
239
324
|
function scan(dir, slugParts) {
|
|
240
325
|
if (!fs.existsSync(dir)) return;
|
|
326
|
+
if (isExcludedDir(dir, excludedDirs)) return;
|
|
241
327
|
const pagePath = path.join(dir, "page.mdx");
|
|
242
328
|
if (fs.existsSync(pagePath)) {
|
|
243
329
|
const desc = readFrontmatter(pagePath).description;
|
|
@@ -446,6 +532,8 @@ function createDocsLayout(config, options) {
|
|
|
446
532
|
const i18n = resolveDocsI18nConfig(getDocsI18n(config));
|
|
447
533
|
const activeLocale = localeContext.locale ?? i18n?.defaultLocale;
|
|
448
534
|
const docsApiUrl = withLangInUrl("/api/docs", activeLocale);
|
|
535
|
+
const changelogConfig = resolveChangelogConfig(config.changelog);
|
|
536
|
+
const changelogBasePath = changelogConfig.enabled ? `/${localeContext.entryPath}/${changelogConfig.path}` : void 0;
|
|
449
537
|
const navTitle = config.nav?.title ?? "Docs";
|
|
450
538
|
const navUrl = withLangInUrl(config.nav?.url ?? `/${localeContext.entryPath}`, activeLocale);
|
|
451
539
|
const themeSwitch = resolveThemeSwitch(config.themeToggle);
|
|
@@ -498,8 +586,8 @@ function createDocsLayout(config, options) {
|
|
|
498
586
|
aiModels = rawModelConfig.models ?? aiModels;
|
|
499
587
|
aiDefaultModelId = rawModelConfig.defaultModel ?? rawModelConfig.models?.[0]?.id ?? aiDefaultModelId;
|
|
500
588
|
}
|
|
501
|
-
const lastModifiedMap = buildLastModifiedMap(localeContext);
|
|
502
|
-
const descriptionMap = buildDescriptionMap(localeContext);
|
|
589
|
+
const lastModifiedMap = buildLastModifiedMap(config, localeContext);
|
|
590
|
+
const descriptionMap = buildDescriptionMap(config, localeContext);
|
|
503
591
|
return function DocsLayoutWrapper({ children }) {
|
|
504
592
|
const tree = buildTree(config, localeContext, !!sidebarFlat);
|
|
505
593
|
const localizedTree = i18n ? localizeTreeUrls(tree, activeLocale) : tree;
|
|
@@ -577,6 +665,7 @@ function createDocsLayout(config, options) {
|
|
|
577
665
|
tocEnabled,
|
|
578
666
|
tocStyle,
|
|
579
667
|
breadcrumbEnabled,
|
|
668
|
+
changelogBasePath,
|
|
580
669
|
entry: localeContext.entryPath,
|
|
581
670
|
locale: activeLocale,
|
|
582
671
|
copyMarkdown: copyMarkdownEnabled,
|
|
@@ -13,6 +13,7 @@ interface DocsPageClientProps {
|
|
|
13
13
|
tocEnabled: boolean;
|
|
14
14
|
tocStyle?: "default" | "directional";
|
|
15
15
|
breadcrumbEnabled?: boolean;
|
|
16
|
+
changelogBasePath?: string;
|
|
16
17
|
/** The docs entry folder name (e.g. "docs") — used to strip from breadcrumb */
|
|
17
18
|
entry?: string;
|
|
18
19
|
/** Active locale (used for llms.txt links) */
|
|
@@ -62,6 +63,7 @@ declare function DocsPageClient({
|
|
|
62
63
|
tocEnabled,
|
|
63
64
|
tocStyle,
|
|
64
65
|
breadcrumbEnabled,
|
|
66
|
+
changelogBasePath,
|
|
65
67
|
entry,
|
|
66
68
|
locale,
|
|
67
69
|
copyMarkdown,
|
|
@@ -158,16 +158,21 @@ function TitleDecorations({ description, belowTitle }) {
|
|
|
158
158
|
if (!description && !belowTitle) return null;
|
|
159
159
|
return /* @__PURE__ */ jsxs(Fragment, { children: [description, belowTitle] });
|
|
160
160
|
}
|
|
161
|
-
function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled = true, entry = "docs", locale, copyMarkdown = false, openDocs = false, openDocsProviders, pageActionsPosition = "below-title", pageActionsAlignment = "left", githubUrl, contentDir, githubBranch = "main", githubDirectory, editOnGithubUrl, lastModifiedMap, lastModified: lastModifiedProp, lastUpdatedEnabled = true, lastUpdatedPosition = "footer", llmsTxtEnabled = false, descriptionMap, description, feedbackEnabled = false, feedbackQuestion, feedbackPlaceholder, feedbackPositiveLabel, feedbackNegativeLabel, feedbackSubmitLabel, feedbackOnFeedback, children }) {
|
|
161
|
+
function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled = true, changelogBasePath, entry = "docs", locale, copyMarkdown = false, openDocs = false, openDocsProviders, pageActionsPosition = "below-title", pageActionsAlignment = "left", githubUrl, contentDir, githubBranch = "main", githubDirectory, editOnGithubUrl, lastModifiedMap, lastModified: lastModifiedProp, lastUpdatedEnabled = true, lastUpdatedPosition = "footer", llmsTxtEnabled = false, descriptionMap, description, feedbackEnabled = false, feedbackQuestion, feedbackPlaceholder, feedbackPositiveLabel, feedbackNegativeLabel, feedbackSubmitLabel, feedbackOnFeedback, children }) {
|
|
162
162
|
const fdTocStyle = tocStyle === "directional" ? "clerk" : void 0;
|
|
163
163
|
const [toc, setToc] = useState([]);
|
|
164
164
|
const [titlePortalHost, setTitlePortalHost] = useState(null);
|
|
165
|
+
const [browserPath, setBrowserPath] = useState(null);
|
|
165
166
|
const pathname = usePathname();
|
|
166
167
|
const activeLocale = resolveClientLocale(useWindowSearchParams(), locale);
|
|
167
168
|
const llmsLangParam = activeLocale ? `&lang=${encodeURIComponent(activeLocale)}` : "";
|
|
168
169
|
const pageDescription = description ?? descriptionMap?.[pathname.replace(/\/$/, "") || "/"];
|
|
170
|
+
const normalizedPath = (browserPath ?? pathname).replace(/\/$/, "") || "/";
|
|
171
|
+
const isChangelogRoute = !!(changelogBasePath && (normalizedPath === changelogBasePath || normalizedPath.startsWith(`${changelogBasePath}/`)));
|
|
172
|
+
const effectiveTocEnabled = isChangelogRoute ? false : tocEnabled;
|
|
173
|
+
const effectiveBreadcrumbEnabled = isChangelogRoute ? false : breadcrumbEnabled;
|
|
169
174
|
useEffect(() => {
|
|
170
|
-
if (!
|
|
175
|
+
if (!effectiveTocEnabled) return;
|
|
171
176
|
const timer = requestAnimationFrame(() => {
|
|
172
177
|
const container = document.getElementById("nd-page");
|
|
173
178
|
if (!container) return;
|
|
@@ -179,7 +184,7 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
179
184
|
})));
|
|
180
185
|
});
|
|
181
186
|
return () => cancelAnimationFrame(timer);
|
|
182
|
-
}, [
|
|
187
|
+
}, [effectiveTocEnabled, pathname]);
|
|
183
188
|
useEffect(() => {
|
|
184
189
|
if (!activeLocale) return;
|
|
185
190
|
const timer = requestAnimationFrame(() => {
|
|
@@ -193,6 +198,19 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
193
198
|
children,
|
|
194
199
|
pathname
|
|
195
200
|
]);
|
|
201
|
+
useEffect(() => {
|
|
202
|
+
setBrowserPath(window.location.pathname);
|
|
203
|
+
}, [pathname]);
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
const root = document.documentElement;
|
|
206
|
+
if (isChangelogRoute) {
|
|
207
|
+
root.dataset.fdRouteKind = "changelog";
|
|
208
|
+
return () => {
|
|
209
|
+
if (root.dataset.fdRouteKind === "changelog") delete root.dataset.fdRouteKind;
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
if (root.dataset.fdRouteKind === "changelog") delete root.dataset.fdRouteKind;
|
|
213
|
+
}, [isChangelogRoute]);
|
|
196
214
|
useEffect(() => {
|
|
197
215
|
let frame = 0;
|
|
198
216
|
let timeout = 0;
|
|
@@ -220,15 +238,14 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
220
238
|
clearTimeout(timeout);
|
|
221
239
|
};
|
|
222
240
|
}, [pathname, children]);
|
|
223
|
-
const showActions = copyMarkdown || openDocs;
|
|
241
|
+
const showActions = !isChangelogRoute && (copyMarkdown || openDocs);
|
|
224
242
|
const showActionsBelowTitle = showActions && pageActionsPosition === "below-title";
|
|
225
243
|
const showActionsAboveTitle = showActions && pageActionsPosition === "above-title";
|
|
226
244
|
const githubFileUrl = editOnGithubUrl ?? (githubUrl ? buildGithubFileUrl(githubUrl, githubBranch, pathname, entry, activeLocale, githubDirectory, contentDir) : void 0);
|
|
227
|
-
const
|
|
228
|
-
const lastModified = lastUpdatedEnabled ? lastModifiedProp ?? lastModifiedMap?.[normalizedPath] : void 0;
|
|
245
|
+
const lastModified = !isChangelogRoute && lastUpdatedEnabled ? lastModifiedProp ?? lastModifiedMap?.[normalizedPath] : void 0;
|
|
229
246
|
const showLastUpdatedBelowTitle = !!lastModified && lastUpdatedPosition === "below-title";
|
|
230
247
|
const showLastUpdatedInFooter = !!lastModified && lastUpdatedPosition === "footer";
|
|
231
|
-
const showFooter = !!githubFileUrl || showLastUpdatedInFooter || llmsTxtEnabled;
|
|
248
|
+
const showFooter = !isChangelogRoute && (!!githubFileUrl || showLastUpdatedInFooter || llmsTxtEnabled);
|
|
232
249
|
const titleDescription = pageDescription ? /* @__PURE__ */ jsx("p", {
|
|
233
250
|
className: "fd-page-description",
|
|
234
251
|
children: pageDescription
|
|
@@ -283,18 +300,20 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
283
300
|
belowTitle: belowTitleBlock
|
|
284
301
|
}), titlePortalHost) : null;
|
|
285
302
|
return /* @__PURE__ */ jsxs(DocsPage, {
|
|
303
|
+
full: false,
|
|
286
304
|
toc,
|
|
287
305
|
tableOfContent: {
|
|
288
|
-
enabled:
|
|
306
|
+
enabled: effectiveTocEnabled,
|
|
289
307
|
style: fdTocStyle
|
|
290
308
|
},
|
|
291
309
|
tableOfContentPopover: {
|
|
292
|
-
enabled:
|
|
310
|
+
enabled: effectiveTocEnabled,
|
|
293
311
|
style: fdTocStyle
|
|
294
312
|
},
|
|
295
313
|
breadcrumb: { enabled: false },
|
|
314
|
+
footer: { enabled: !isChangelogRoute },
|
|
296
315
|
children: [
|
|
297
|
-
|
|
316
|
+
effectiveBreadcrumbEnabled && /* @__PURE__ */ jsx(PathBreadcrumb, {
|
|
298
317
|
pathname,
|
|
299
318
|
entry,
|
|
300
319
|
locale: activeLocale
|
|
@@ -324,7 +343,7 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
324
343
|
children: decoratedChildren
|
|
325
344
|
}),
|
|
326
345
|
titleDecorationsPortal,
|
|
327
|
-
feedbackEnabled && /* @__PURE__ */ jsx(DocsFeedback, {
|
|
346
|
+
!isChangelogRoute && feedbackEnabled && /* @__PURE__ */ jsx(DocsFeedback, {
|
|
328
347
|
pathname: normalizedPath,
|
|
329
348
|
entry,
|
|
330
349
|
locale: activeLocale,
|
package/dist/index.d.mts
CHANGED
|
@@ -12,8 +12,8 @@ import { PageActions } from "./page-actions.mjs";
|
|
|
12
12
|
import { withLangInUrl } from "./i18n.mjs";
|
|
13
13
|
import { HoverLink, HoverLinkProps } from "./hover-link.mjs";
|
|
14
14
|
import { DocsLayout } from "fumadocs-ui/layouts/docs";
|
|
15
|
-
import { AIConfig, BreadcrumbConfig, CopyMarkdownConfig, DocsConfig, DocsFeedbackData, DocsFeedbackValue, DocsMetadata, DocsNav, DocsTheme, FeedbackConfig, FontStyle, OGConfig, OpenDocsConfig, OpenDocsProvider, PageActionsConfig, PageFrontmatter, SidebarConfig, ThemeToggleConfig, TypographyConfig, UIConfig, createTheme, deepMerge, defineDocs, extendTheme } from "@farming-labs/docs";
|
|
15
|
+
import { AIConfig, BreadcrumbConfig, ChangelogConfig, ChangelogFrontmatter, CopyMarkdownConfig, DocsConfig, DocsFeedbackData, DocsFeedbackValue, DocsMetadata, DocsNav, DocsTheme, FeedbackConfig, FontStyle, OGConfig, OpenDocsConfig, OpenDocsProvider, PageActionsConfig, PageFrontmatter, SidebarConfig, ThemeToggleConfig, TypographyConfig, UIConfig, createTheme, deepMerge, defineDocs, extendTheme } from "@farming-labs/docs";
|
|
16
16
|
import { DocsBody, DocsPage } from "fumadocs-ui/layouts/docs/page";
|
|
17
17
|
import { Tab, Tabs } from "fumadocs-ui/components/tabs";
|
|
18
18
|
import { CodeBlock, CodeBlockTab, CodeBlockTabs, CodeBlockTabsList, CodeBlockTabsTrigger, Pre } from "fumadocs-ui/components/codeblock";
|
|
19
|
-
export { type AIConfig, type BreadcrumbConfig, CodeBlock, CodeBlockTab, CodeBlockTabs, CodeBlockTabsList, CodeBlockTabsTrigger, CommandGridUIDefaults, ConcreteUIDefaults, type CopyMarkdownConfig, DocsBody, DocsClientHooks, DocsCommandSearch, type DocsConfig, DocsFeedback, type DocsFeedbackData, type DocsFeedbackProps, type DocsFeedbackValue, DocsLayout, type DocsMetadata, type DocsNav, DocsPage, DocsPageClient, type DocsTheme, type FeedbackConfig, type FontStyle, DefaultUIDefaults as FumadocsUIDefaults, HardlineUIDefaults, HoverLink, type HoverLinkProps, type OGConfig, type OpenDocsConfig, type OpenDocsProvider, PageActions, type PageActionsConfig, type PageFrontmatter, Pre, RootProvider, type SidebarConfig, Tab, Tabs, type ThemeToggleConfig, type TypographyConfig, type UIConfig, commandGrid, concrete, createDocsLayout, createDocsMetadata, createPageMetadata, createTheme, deepMerge, defineDocs, extendTheme, fumadocs, hardline, withLangInUrl };
|
|
19
|
+
export { type AIConfig, type BreadcrumbConfig, type ChangelogConfig, type ChangelogFrontmatter, CodeBlock, CodeBlockTab, CodeBlockTabs, CodeBlockTabsList, CodeBlockTabsTrigger, CommandGridUIDefaults, ConcreteUIDefaults, type CopyMarkdownConfig, DocsBody, DocsClientHooks, DocsCommandSearch, type DocsConfig, DocsFeedback, type DocsFeedbackData, type DocsFeedbackProps, type DocsFeedbackValue, DocsLayout, type DocsMetadata, type DocsNav, DocsPage, DocsPageClient, type DocsTheme, type FeedbackConfig, type FontStyle, DefaultUIDefaults as FumadocsUIDefaults, HardlineUIDefaults, HoverLink, type HoverLinkProps, type OGConfig, type OpenDocsConfig, type OpenDocsProvider, PageActions, type PageActionsConfig, type PageFrontmatter, Pre, RootProvider, type SidebarConfig, Tab, Tabs, type ThemeToggleConfig, type TypographyConfig, type UIConfig, commandGrid, concrete, createDocsLayout, createDocsMetadata, createPageMetadata, createTheme, deepMerge, defineDocs, extendTheme, fumadocs, hardline, withLangInUrl };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farming-labs/theme",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"description": "Theme package for @farming-labs/docs — layout, provider, MDX components, and styles",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"docs",
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
"tsdown": "^0.20.3",
|
|
134
134
|
"typescript": "^5.9.3",
|
|
135
135
|
"vitest": "^3.2.4",
|
|
136
|
-
"@farming-labs/docs": "0.1.
|
|
136
|
+
"@farming-labs/docs": "0.1.17"
|
|
137
137
|
},
|
|
138
138
|
"peerDependencies": {
|
|
139
139
|
"@farming-labs/docs": ">=0.0.1",
|