@farming-labs/theme 0.1.108 → 0.1.109
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 +2 -0
- package/dist/docs-api.mjs +144 -16
- package/dist/docs-layout.mjs +32 -9
- package/dist/docs-page-client.d.mts +3 -0
- package/dist/docs-page-client.mjs +108 -11
- package/package.json +2 -2
package/dist/docs-api.d.mts
CHANGED
|
@@ -34,6 +34,8 @@ interface DocsAPIOptions {
|
|
|
34
34
|
rootDir?: string;
|
|
35
35
|
/** Docs entry folder (default: read from docs.config) */
|
|
36
36
|
entry?: string;
|
|
37
|
+
/** Public docs route prefix. Defaults to the docs entry path. */
|
|
38
|
+
docsPath?: string;
|
|
37
39
|
/** Override the docs content directory when it does not live in app/<entry>. */
|
|
38
40
|
contentDir?: string;
|
|
39
41
|
/** Changelog 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 { buildDocsAskAIContext, createDocsAgentTraceContext, createDocsAgentTraceId, createDocsRobotsResponse, createDocsSitemapResponse, emitDocsAgentTraceEvent, emitDocsAnalyticsEvent, formatDocsAskAIPackageHints, getDocsLlmsTxtMaxCharsIssue,
|
|
6
|
+
import { buildDocsAskAIContext, createDocsAgentTraceContext, createDocsAgentTraceId, createDocsRobotsResponse, createDocsSitemapResponse, emitDocsAgentTraceEvent, emitDocsAnalyticsEvent, formatDocsAskAIPackageHints, getDocsLlmsTxtMaxCharsIssue, getDocsMarkdownVaryHeader, hasDocsMarkdownSignatureAgent, normalizeDocsRelated, performDocsSearch, renderDocsLlmsTxt, renderDocsMarkdownNotFound, renderDocsRelatedMarkdownLines, resolveAskAISearchRequestConfig, resolveChangelogConfig, resolveDocsI18n, resolveDocsLlmsTxtRequest, resolveDocsLlmsTxtSections, resolveDocsLocale, resolveDocsSitemapConfig, resolvePageSidebarFolderIndexBehavior, resolveSearchRequestConfig, selectDocsLlmsTxtContent } from "@farming-labs/docs";
|
|
7
7
|
import { buildApiReferenceOpenApiDocumentAsync, createDocsMcpHttpHandler, createFilesystemDocsMcpSource, readDocsSitemapManifest, resolveApiReferenceConfig, resolveDocsMcpConfig } from "@farming-labs/docs/server";
|
|
8
8
|
|
|
9
9
|
//#region src/docs-api.ts
|
|
@@ -807,7 +807,7 @@ function isHiddenFolderIndexPageDir(dir) {
|
|
|
807
807
|
return false;
|
|
808
808
|
}
|
|
809
809
|
}
|
|
810
|
-
function scanDocsDir(docsDir, entry, locale, excludedDirs = []) {
|
|
810
|
+
function scanDocsDir(docsDir, entry, locale, excludedDirs = [], publicPath = `/${normalizePathSegment(entry) || "docs"}`) {
|
|
811
811
|
const indexes = [];
|
|
812
812
|
function isExcluded(dir) {
|
|
813
813
|
const resolved = path.resolve(dir);
|
|
@@ -830,7 +830,7 @@ function scanDocsDir(docsDir, entry, locale, excludedDirs = []) {
|
|
|
830
830
|
const rawContent = resolveAgentMdxContent(fileContent, "human");
|
|
831
831
|
const agentRawContent = resolveAgentMdxContent(fileContent, "agent");
|
|
832
832
|
const content = stripMdx(rawContent);
|
|
833
|
-
const url = withLangInUrl(
|
|
833
|
+
const url = withLangInUrl(publicDocsRoute(publicPath, slugParts), locale);
|
|
834
834
|
indexes.push({
|
|
835
835
|
title,
|
|
836
836
|
description,
|
|
@@ -861,7 +861,7 @@ function scanDocsDir(docsDir, entry, locale, excludedDirs = []) {
|
|
|
861
861
|
scan(docsDir, []);
|
|
862
862
|
return resolveRelatedForSearchPages(indexes);
|
|
863
863
|
}
|
|
864
|
-
function scanChangelogDir(changelogDir, entryPath, changelogPath, locale) {
|
|
864
|
+
function scanChangelogDir(changelogDir, entryPath, changelogPath, locale, publicPath = `/${normalizePathSegment(entryPath) || "docs"}`) {
|
|
865
865
|
if (!fs.existsSync(changelogDir)) return [];
|
|
866
866
|
const indexes = [];
|
|
867
867
|
let entries;
|
|
@@ -890,7 +890,7 @@ function scanChangelogDir(changelogDir, entryPath, changelogPath, locale) {
|
|
|
890
890
|
const rawContent = resolveAgentMdxContent(fileContent, "human");
|
|
891
891
|
const agentRawContent = resolveAgentMdxContent(fileContent, "agent");
|
|
892
892
|
const content = stripMdx(rawContent);
|
|
893
|
-
const url = withLangInUrl(
|
|
893
|
+
const url = withLangInUrl(publicDocsRoute(publicPath, [changelogPath, name]), locale);
|
|
894
894
|
const tags = Array.isArray(data.tags) ? data.tags.filter((value) => typeof value === "string") : void 0;
|
|
895
895
|
indexes.push({
|
|
896
896
|
title,
|
|
@@ -935,6 +935,38 @@ function normalizeRequestedMarkdownPath(entry, requestedPath) {
|
|
|
935
935
|
const slug = normalizePathSegment(trimmed);
|
|
936
936
|
return slug ? normalizeUrlPath(`${normalizedEntry}/${slug}`) : normalizedEntry;
|
|
937
937
|
}
|
|
938
|
+
function normalizePublicRequestedMarkdownPath(ctx, requestedPath) {
|
|
939
|
+
const trimmed = requestedPath.trim().replace(/\.md$/i, "");
|
|
940
|
+
if (!trimmed) return ctx.publicPath || "/";
|
|
941
|
+
const normalized = normalizeUrlPath(trimmed.startsWith("/") ? trimmed : `/${trimmed}`);
|
|
942
|
+
const normalizedEntry = `/${normalizePathSegment(ctx.entryPath)}`;
|
|
943
|
+
if (normalized === normalizedEntry) return ctx.publicPath || "/";
|
|
944
|
+
if (normalized.startsWith(`${normalizedEntry}/`)) {
|
|
945
|
+
const suffix = normalized.slice(normalizedEntry.length + 1);
|
|
946
|
+
return publicDocsRoute(ctx.publicPath, suffix.split("/").filter(Boolean));
|
|
947
|
+
}
|
|
948
|
+
if (ctx.publicPath) {
|
|
949
|
+
if (normalized === ctx.publicPath || normalized.startsWith(`${ctx.publicPath}/`)) return normalized;
|
|
950
|
+
} else if (normalized === "/") return "/";
|
|
951
|
+
const slug = normalizePathSegment(trimmed);
|
|
952
|
+
return publicDocsRoute(ctx.publicPath, slug ? slug.split("/").filter(Boolean) : []);
|
|
953
|
+
}
|
|
954
|
+
function normalizePublicDocsSlug(ctx, value) {
|
|
955
|
+
const pathname = normalizeUrlPath(value);
|
|
956
|
+
const normalizedEntry = `/${normalizePathSegment(ctx.entryPath)}`;
|
|
957
|
+
if (ctx.publicPath) {
|
|
958
|
+
if (pathname === ctx.publicPath) return "";
|
|
959
|
+
if (pathname.startsWith(`${ctx.publicPath}/`)) return normalizePathSegment(pathname.slice(ctx.publicPath.length + 1));
|
|
960
|
+
} else if (pathname === "/") return "";
|
|
961
|
+
if (pathname === normalizedEntry) return "";
|
|
962
|
+
if (pathname.startsWith(`${normalizedEntry}/`)) return normalizePathSegment(pathname.slice(normalizedEntry.length + 1));
|
|
963
|
+
return normalizePathSegment(pathname);
|
|
964
|
+
}
|
|
965
|
+
function getPublicMarkdownCanonicalLinkHeader({ origin, ctx, requestedPath }) {
|
|
966
|
+
const canonicalUrl = new URL(normalizePublicRequestedMarkdownPath(ctx, requestedPath), origin);
|
|
967
|
+
if (ctx.locale) canonicalUrl.searchParams.set("lang", ctx.locale);
|
|
968
|
+
return `<${canonicalUrl.toString()}>; rel="canonical"`;
|
|
969
|
+
}
|
|
938
970
|
function findDocsMcpPage(entry, pages, requestedPath) {
|
|
939
971
|
const normalizedRequest = normalizeRequestedMarkdownPath(entry, requestedPath);
|
|
940
972
|
for (const page of pages) if (normalizeUrlPath(page.url) === normalizedRequest) return page;
|
|
@@ -985,6 +1017,95 @@ function resolveMarkdownRequest(entry, url, request) {
|
|
|
985
1017
|
}
|
|
986
1018
|
return null;
|
|
987
1019
|
}
|
|
1020
|
+
function normalizeDocsPublicPath(value, entry) {
|
|
1021
|
+
if (typeof value !== "string") return `/${normalizePathSegment(entry)}`;
|
|
1022
|
+
const cleaned = value.trim();
|
|
1023
|
+
if (cleaned === "" || cleaned === "/") return "";
|
|
1024
|
+
return `/${cleaned.replace(/^\/+|\/+$/g, "")}`;
|
|
1025
|
+
}
|
|
1026
|
+
function publicDocsRoute(publicPath, slugParts = []) {
|
|
1027
|
+
const slug = slugParts.join("/");
|
|
1028
|
+
if (!slug) return publicPath || "/";
|
|
1029
|
+
return publicPath ? `${publicPath}/${slug}` : `/${slug}`;
|
|
1030
|
+
}
|
|
1031
|
+
function toPublicDocsUrl(value, entry, publicPath) {
|
|
1032
|
+
const normalizedEntry = `/${normalizePathSegment(entry)}`;
|
|
1033
|
+
const hashIndex = value.indexOf("#");
|
|
1034
|
+
const hash = hashIndex >= 0 ? value.slice(hashIndex) : "";
|
|
1035
|
+
const withoutHash = hashIndex >= 0 ? value.slice(0, hashIndex) : value;
|
|
1036
|
+
const queryIndex = withoutHash.indexOf("?");
|
|
1037
|
+
const query = queryIndex >= 0 ? withoutHash.slice(queryIndex) : "";
|
|
1038
|
+
const pathname = normalizeUrlPath(queryIndex >= 0 ? withoutHash.slice(0, queryIndex) : withoutHash);
|
|
1039
|
+
if (publicPath === normalizedEntry) return value;
|
|
1040
|
+
if (pathname === normalizedEntry) return `${publicPath || "/"}${query}${hash}`;
|
|
1041
|
+
if (pathname.startsWith(`${normalizedEntry}/`)) {
|
|
1042
|
+
const suffix = pathname.slice(normalizedEntry.length + 1);
|
|
1043
|
+
return `${publicPath ? `${publicPath}/${suffix}` : `/${suffix}`}${query}${hash}`;
|
|
1044
|
+
}
|
|
1045
|
+
return value;
|
|
1046
|
+
}
|
|
1047
|
+
function withPublicDocsUrl(page, ctx) {
|
|
1048
|
+
const url = toPublicDocsUrl(page.url, ctx.entryPath, ctx.publicPath);
|
|
1049
|
+
return url === page.url ? page : {
|
|
1050
|
+
...page,
|
|
1051
|
+
url
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
function readDocsPath(root) {
|
|
1055
|
+
for (const ext of FILE_EXTS) {
|
|
1056
|
+
const configPath = path.join(root, `docs.config.${ext}`);
|
|
1057
|
+
if (!fs.existsSync(configPath)) continue;
|
|
1058
|
+
try {
|
|
1059
|
+
const match = fs.readFileSync(configPath, "utf-8").match(/docsPath\s*:\s*["']([^"']*)["']/);
|
|
1060
|
+
if (match) return match[1];
|
|
1061
|
+
} catch {}
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
function resolvePublicMarkdownRequest(entry, docsPath, url, request) {
|
|
1065
|
+
if (docsPath === `/${normalizePathSegment(entry)}`) return null;
|
|
1066
|
+
const pathname = normalizeUrlPath(url.pathname);
|
|
1067
|
+
if (docsPath === "") {
|
|
1068
|
+
if (pathname === `/${normalizePathSegment(entry)}.md`) return {
|
|
1069
|
+
requestedPath: "",
|
|
1070
|
+
delivery: "md_route"
|
|
1071
|
+
};
|
|
1072
|
+
if (pathname !== "/docs.md" && pathname.endsWith(".md")) return {
|
|
1073
|
+
requestedPath: pathname.slice(1, -3),
|
|
1074
|
+
delivery: "md_route"
|
|
1075
|
+
};
|
|
1076
|
+
const hasSignatureAgent = hasDocsMarkdownSignatureAgent(request);
|
|
1077
|
+
if (acceptsMarkdown(request) || hasSignatureAgent) {
|
|
1078
|
+
const delivery = hasSignatureAgent ? "signature_agent" : "accept_header";
|
|
1079
|
+
return {
|
|
1080
|
+
requestedPath: pathname === "/" ? "" : pathname.slice(1),
|
|
1081
|
+
delivery
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
return null;
|
|
1085
|
+
}
|
|
1086
|
+
if (pathname === `${docsPath}.md`) return {
|
|
1087
|
+
requestedPath: "",
|
|
1088
|
+
delivery: "md_route"
|
|
1089
|
+
};
|
|
1090
|
+
const slugPrefix = `${docsPath}/`;
|
|
1091
|
+
if (pathname.startsWith(slugPrefix) && pathname.endsWith(".md")) return {
|
|
1092
|
+
requestedPath: pathname.slice(slugPrefix.length, -3),
|
|
1093
|
+
delivery: "md_route"
|
|
1094
|
+
};
|
|
1095
|
+
const hasSignatureAgent = hasDocsMarkdownSignatureAgent(request);
|
|
1096
|
+
if (acceptsMarkdown(request) || hasSignatureAgent) {
|
|
1097
|
+
const delivery = hasSignatureAgent ? "signature_agent" : "accept_header";
|
|
1098
|
+
if (pathname === docsPath) return {
|
|
1099
|
+
requestedPath: "",
|
|
1100
|
+
delivery
|
|
1101
|
+
};
|
|
1102
|
+
if (pathname.startsWith(slugPrefix)) return {
|
|
1103
|
+
requestedPath: pathname.slice(slugPrefix.length),
|
|
1104
|
+
delivery
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
return null;
|
|
1108
|
+
}
|
|
988
1109
|
function renderMarkdownDocument(page, options = {}) {
|
|
989
1110
|
if ("agentRawContent" in page && page.agentRawContent !== void 0) return page.agentRawContent;
|
|
990
1111
|
const relatedLines = renderDocsRelatedMarkdownLines(page.related);
|
|
@@ -1771,6 +1892,7 @@ function generateLlmsTxt(indexes, options) {
|
|
|
1771
1892
|
function createDocsAPI(options) {
|
|
1772
1893
|
const root = options?.rootDir ?? process.cwd();
|
|
1773
1894
|
const entry = options?.entry ?? readEntry(root);
|
|
1895
|
+
const docsPath = normalizeDocsPublicPath(options?.docsPath ?? readDocsPath(root), entry);
|
|
1774
1896
|
const analytics = options?.analytics;
|
|
1775
1897
|
const observability = options?.observability;
|
|
1776
1898
|
const appDir = getNextAppDir(root);
|
|
@@ -1836,6 +1958,7 @@ function createDocsAPI(options) {
|
|
|
1836
1958
|
const docsDirs = resolveDocsDirCandidates();
|
|
1837
1959
|
return {
|
|
1838
1960
|
entryPath: entry,
|
|
1961
|
+
publicPath: docsPath,
|
|
1839
1962
|
docsDirs,
|
|
1840
1963
|
changelogDirs: resolveChangelogDirCandidates(docsDirs)
|
|
1841
1964
|
};
|
|
@@ -1844,6 +1967,7 @@ function createDocsAPI(options) {
|
|
|
1844
1967
|
const docsDirs = resolveDocsDirCandidates(locale);
|
|
1845
1968
|
return {
|
|
1846
1969
|
entryPath: entry,
|
|
1970
|
+
publicPath: docsPath,
|
|
1847
1971
|
locale,
|
|
1848
1972
|
docsDirs,
|
|
1849
1973
|
changelogDirs: resolveChangelogDirCandidates(docsDirs)
|
|
@@ -1859,13 +1983,13 @@ function createDocsAPI(options) {
|
|
|
1859
1983
|
let next = [];
|
|
1860
1984
|
for (const docsDir of ctx.docsDirs) {
|
|
1861
1985
|
const excludedDirs = ctx.changelogDirs.filter((dir) => isWithinDir(dir, docsDir));
|
|
1862
|
-
const docsPages = scanDocsDir(docsDir, ctx.entryPath, ctx.locale, excludedDirs);
|
|
1986
|
+
const docsPages = scanDocsDir(docsDir, ctx.entryPath, ctx.locale, excludedDirs, ctx.publicPath);
|
|
1863
1987
|
if (docsPages.length === 0) continue;
|
|
1864
1988
|
next = docsPages;
|
|
1865
1989
|
break;
|
|
1866
1990
|
}
|
|
1867
1991
|
if (changelogConfig.enabled) {
|
|
1868
|
-
const changelogPages = ctx.changelogDirs.flatMap((dir) => scanChangelogDir(dir, ctx.entryPath, changelogConfig.path, ctx.locale));
|
|
1992
|
+
const changelogPages = ctx.changelogDirs.flatMap((dir) => scanChangelogDir(dir, ctx.entryPath, changelogConfig.path, ctx.locale, ctx.publicPath));
|
|
1869
1993
|
next = [...next, ...changelogPages];
|
|
1870
1994
|
}
|
|
1871
1995
|
indexesByLocale.set(key, next);
|
|
@@ -1885,16 +2009,21 @@ function createDocsAPI(options) {
|
|
|
1885
2009
|
}
|
|
1886
2010
|
async function getMarkdownDocument(ctx, requestedPath) {
|
|
1887
2011
|
const normalizedRequest = normalizeRequestedMarkdownPath(ctx.entryPath, requestedPath);
|
|
2012
|
+
const normalizedPublicRequest = normalizePublicRequestedMarkdownPath(ctx, requestedPath);
|
|
1888
2013
|
const normalizedEntry = `/${normalizePathSegment(ctx.entryPath)}`;
|
|
1889
2014
|
const relativeSlug = normalizedRequest === normalizedEntry ? "" : normalizedRequest.slice(normalizedEntry.length).replace(/^\/+/, "");
|
|
1890
2015
|
for (const docsDir of ctx.docsDirs) if (isHiddenFolderIndexPageDir(relativeSlug ? path.join(docsDir, ...relativeSlug.split("/")) : docsDir)) return null;
|
|
1891
2016
|
for (const source of getMarkdownSources(ctx)) {
|
|
1892
2017
|
const page = findDocsMcpPage(ctx.entryPath, await source.getPages(), requestedPath);
|
|
1893
|
-
if (page) return renderMarkdownDocument(page, { llmsEnabled: llmsConfig.enabled });
|
|
2018
|
+
if (page) return renderMarkdownDocument(withPublicDocsUrl(page, ctx), { llmsEnabled: llmsConfig.enabled });
|
|
1894
2019
|
}
|
|
1895
|
-
const fallbackPage = getIndexes(ctx).find((page) =>
|
|
1896
|
-
|
|
1897
|
-
|
|
2020
|
+
const fallbackPage = getIndexes(ctx).find((page) => {
|
|
2021
|
+
const pageUrl = normalizeUrlPath(page.url);
|
|
2022
|
+
return pageUrl === normalizedRequest || pageUrl === normalizedPublicRequest;
|
|
2023
|
+
});
|
|
2024
|
+
if (fallbackPage) return renderMarkdownDocument(withPublicDocsUrl(fallbackPage, ctx), { llmsEnabled: llmsConfig.enabled });
|
|
2025
|
+
const requestedSlug = normalizePublicDocsSlug(ctx, normalizedPublicRequest);
|
|
2026
|
+
for (const page of getIndexes(ctx)) if (normalizePublicDocsSlug(ctx, page.url) === requestedSlug) return renderMarkdownDocument(withPublicDocsUrl(page, ctx), { llmsEnabled: llmsConfig.enabled });
|
|
1898
2027
|
return null;
|
|
1899
2028
|
}
|
|
1900
2029
|
function getLlmsContent(ctx) {
|
|
@@ -2049,15 +2178,14 @@ function createDocsAPI(options) {
|
|
|
2049
2178
|
manifest: readDocsSitemapManifest(root, sitemapConfig)
|
|
2050
2179
|
});
|
|
2051
2180
|
if (sitemapResponse) return sitemapResponse;
|
|
2052
|
-
const markdownRequest = resolveMarkdownRequest(entry, url, request);
|
|
2181
|
+
const markdownRequest = resolveMarkdownRequest(entry, url, request) ?? resolvePublicMarkdownRequest(entry, docsPath, url, request);
|
|
2053
2182
|
if (markdownRequest) {
|
|
2054
2183
|
const document = await getMarkdownDocument(ctx, markdownRequest.requestedPath);
|
|
2055
2184
|
const varyHeader = getDocsMarkdownVaryHeader(request);
|
|
2056
|
-
const canonicalLinkHeader =
|
|
2185
|
+
const canonicalLinkHeader = getPublicMarkdownCanonicalLinkHeader({
|
|
2057
2186
|
origin: url.origin,
|
|
2058
|
-
|
|
2059
|
-
requestedPath: markdownRequest.requestedPath
|
|
2060
|
-
locale: ctx.locale
|
|
2187
|
+
ctx,
|
|
2188
|
+
requestedPath: markdownRequest.requestedPath
|
|
2061
2189
|
});
|
|
2062
2190
|
if (!document) {
|
|
2063
2191
|
await emitDocsAnalyticsEvent(analytics, {
|
package/dist/docs-layout.mjs
CHANGED
|
@@ -65,6 +65,7 @@ function resolveDocsI18nConfig(i18n) {
|
|
|
65
65
|
}
|
|
66
66
|
function resolveDocsLocaleContext(config, locale) {
|
|
67
67
|
const entryBase = config.entry ?? "docs";
|
|
68
|
+
const publicPath = normalizeDocsPublicPath(config.docsPath, entryBase);
|
|
68
69
|
const i18n = resolveDocsI18nConfig(getDocsI18n(config));
|
|
69
70
|
const contentDir = config.contentDir;
|
|
70
71
|
function resolveContentDir(localeValue) {
|
|
@@ -77,15 +78,36 @@ function resolveDocsLocaleContext(config, locale) {
|
|
|
77
78
|
}
|
|
78
79
|
if (!i18n) return {
|
|
79
80
|
entryPath: entryBase,
|
|
81
|
+
publicPath,
|
|
80
82
|
docsDir: resolveContentDir()
|
|
81
83
|
};
|
|
82
84
|
const resolvedLocale = locale && i18n.locales.includes(locale) ? locale : i18n.defaultLocale;
|
|
83
85
|
return {
|
|
84
86
|
entryPath: entryBase,
|
|
87
|
+
publicPath,
|
|
85
88
|
locale: resolvedLocale,
|
|
86
89
|
docsDir: resolveContentDir(resolvedLocale)
|
|
87
90
|
};
|
|
88
91
|
}
|
|
92
|
+
function normalizeDocsPublicPath(value, entry) {
|
|
93
|
+
if (typeof value !== "string") return `/${entry.replace(/^\/+|\/+$/g, "") || "docs"}`;
|
|
94
|
+
const cleaned = value.trim();
|
|
95
|
+
if (cleaned === "" || cleaned === "/") return "";
|
|
96
|
+
return `/${cleaned.replace(/^\/+|\/+$/g, "")}`;
|
|
97
|
+
}
|
|
98
|
+
function publicDocsRoute(ctx, slugParts = []) {
|
|
99
|
+
const slug = slugParts.join("/");
|
|
100
|
+
if (!slug) return ctx.publicPath || "/";
|
|
101
|
+
return ctx.publicPath ? `${ctx.publicPath}/${slug}` : `/${slug}`;
|
|
102
|
+
}
|
|
103
|
+
function toPublicDocsPath(pathname, ctx) {
|
|
104
|
+
const normalizedEntry = `/${ctx.entryPath.replace(/^\/+|\/+$/g, "")}`;
|
|
105
|
+
const normalizedPath = pathname.replace(/\/+$/, "") || "/";
|
|
106
|
+
if (ctx.publicPath === normalizedEntry) return normalizedPath;
|
|
107
|
+
if (normalizedPath === normalizedEntry) return ctx.publicPath || "/";
|
|
108
|
+
if (normalizedPath.startsWith(`${normalizedEntry}/`)) return publicDocsRoute(ctx, normalizedPath.slice(normalizedEntry.length + 1).split("/").filter(Boolean));
|
|
109
|
+
return normalizedPath;
|
|
110
|
+
}
|
|
89
111
|
function getExcludedDocsDirs(config, ctx) {
|
|
90
112
|
const changelog = resolveChangelogConfig(config.changelog);
|
|
91
113
|
if (!changelog.enabled) return [];
|
|
@@ -123,7 +145,7 @@ function buildChangelogTree(config, ctx, flat = false) {
|
|
|
123
145
|
if (!changelog.enabled) return null;
|
|
124
146
|
const entries = readChangelogTreeEntries(config, ctx);
|
|
125
147
|
if (entries.length === 0) return null;
|
|
126
|
-
const url =
|
|
148
|
+
const url = publicDocsRoute(ctx, [changelog.path]);
|
|
127
149
|
const children = entries.map((entry) => ({
|
|
128
150
|
type: "page",
|
|
129
151
|
name: entry.title,
|
|
@@ -155,7 +177,7 @@ function buildTree(config, ctx, flat = false) {
|
|
|
155
177
|
if (data.hidden !== true) rootChildren.push({
|
|
156
178
|
type: "page",
|
|
157
179
|
name: data.title ?? "Documentation",
|
|
158
|
-
url:
|
|
180
|
+
url: publicDocsRoute(ctx),
|
|
159
181
|
icon: resolveIcon(data.icon, icons)
|
|
160
182
|
});
|
|
161
183
|
}
|
|
@@ -167,7 +189,7 @@ function buildTree(config, ctx, flat = false) {
|
|
|
167
189
|
if (!fs.existsSync(pagePath)) return null;
|
|
168
190
|
const data = readFrontmatter(pagePath);
|
|
169
191
|
const slug = [...baseSlug, name];
|
|
170
|
-
const url =
|
|
192
|
+
const url = publicDocsRoute(ctx, slug);
|
|
171
193
|
const icon = resolveIcon(data.icon, icons);
|
|
172
194
|
const displayName = data.title ?? name.replace(/-/g, " ");
|
|
173
195
|
const hasChildren = hasChildPages(full, excludedDirs);
|
|
@@ -318,7 +340,7 @@ function buildLastModifiedMap(config, ctx) {
|
|
|
318
340
|
if (isExcludedDir(dir, excludedDirs)) return;
|
|
319
341
|
const pagePath = path.join(dir, "page.mdx");
|
|
320
342
|
if (fs.existsSync(pagePath)) {
|
|
321
|
-
const url =
|
|
343
|
+
const url = publicDocsRoute(ctx, slugParts);
|
|
322
344
|
map[url] = formatDate(fs.statSync(pagePath).mtime);
|
|
323
345
|
}
|
|
324
346
|
for (const name of fs.readdirSync(dir)) {
|
|
@@ -344,7 +366,7 @@ function buildDescriptionMap(config, ctx) {
|
|
|
344
366
|
if (fs.existsSync(pagePath)) {
|
|
345
367
|
const desc = readFrontmatter(pagePath).description;
|
|
346
368
|
if (desc) {
|
|
347
|
-
const url =
|
|
369
|
+
const url = publicDocsRoute(ctx, slugParts);
|
|
348
370
|
map[url] = desc;
|
|
349
371
|
}
|
|
350
372
|
}
|
|
@@ -368,7 +390,7 @@ function buildReadingTimeMap(config, ctx, options) {
|
|
|
368
390
|
const { data, content } = matter(fs.readFileSync(pagePath, "utf-8"));
|
|
369
391
|
const minutes = resolvePageReadingTime(data, resolveDocsAgentMdxContent(content, "human"), options);
|
|
370
392
|
if (typeof minutes === "number") {
|
|
371
|
-
const url =
|
|
393
|
+
const url = publicDocsRoute(ctx, slugParts);
|
|
372
394
|
map[url] = minutes;
|
|
373
395
|
}
|
|
374
396
|
}
|
|
@@ -394,7 +416,7 @@ function buildStructuredDataMap(config, ctx) {
|
|
|
394
416
|
const pagePath = findDocsPageFile(dir);
|
|
395
417
|
if (pagePath) {
|
|
396
418
|
const { data } = matter(fs.readFileSync(pagePath, "utf-8"));
|
|
397
|
-
const route =
|
|
419
|
+
const route = publicDocsRoute(ctx, slugParts);
|
|
398
420
|
const title = typeof data.title === "string" ? data.title : slugParts.at(-1)?.replace(/-/g, " ") || "Documentation";
|
|
399
421
|
const description = typeof data.description === "string" ? data.description : void 0;
|
|
400
422
|
const stat = fs.statSync(pagePath);
|
|
@@ -610,9 +632,9 @@ function createDocsLayout(config, options) {
|
|
|
610
632
|
const activeLocale = localeContext.locale ?? i18n?.defaultLocale;
|
|
611
633
|
const docsApiUrl = withLangInUrl("/api/docs", activeLocale);
|
|
612
634
|
const changelogConfig = resolveChangelogConfig(config.changelog);
|
|
613
|
-
const changelogBasePath = changelogConfig.enabled ?
|
|
635
|
+
const changelogBasePath = changelogConfig.enabled ? publicDocsRoute(localeContext, [changelogConfig.path]) : void 0;
|
|
614
636
|
const navTitle = config.nav?.title ?? "Docs";
|
|
615
|
-
const navUrl = withLangInUrl(config.nav?.url
|
|
637
|
+
const navUrl = withLangInUrl(config.nav?.url ? toPublicDocsPath(config.nav.url, localeContext) : publicDocsRoute(localeContext), activeLocale);
|
|
616
638
|
const themeSwitch = resolveThemeSwitch(config.themeToggle);
|
|
617
639
|
const toggleConfig = typeof config.themeToggle === "object" ? config.themeToggle : void 0;
|
|
618
640
|
const forcedTheme = themeSwitch.enabled === false && toggleConfig?.default && toggleConfig.default !== "system" ? toggleConfig.default : void 0;
|
|
@@ -757,6 +779,7 @@ function createDocsLayout(config, options) {
|
|
|
757
779
|
breadcrumbEnabled,
|
|
758
780
|
changelogBasePath,
|
|
759
781
|
entry: localeContext.entryPath,
|
|
782
|
+
publicPath: localeContext.publicPath,
|
|
760
783
|
locale: activeLocale,
|
|
761
784
|
copyMarkdown: copyMarkdownEnabled,
|
|
762
785
|
openDocs: openDocsEnabled,
|
|
@@ -16,6 +16,8 @@ interface DocsPageClientProps {
|
|
|
16
16
|
changelogBasePath?: string;
|
|
17
17
|
/** The docs entry folder name (e.g. "docs") — used to strip from breadcrumb */
|
|
18
18
|
entry?: string;
|
|
19
|
+
/** Public docs route prefix. Empty string means docs render from the site root. */
|
|
20
|
+
publicPath?: string;
|
|
19
21
|
/** Active locale (used for llms.txt links) */
|
|
20
22
|
locale?: string;
|
|
21
23
|
copyMarkdown?: boolean;
|
|
@@ -79,6 +81,7 @@ declare function DocsPageClient({
|
|
|
79
81
|
breadcrumbEnabled,
|
|
80
82
|
changelogBasePath,
|
|
81
83
|
entry,
|
|
84
|
+
publicPath,
|
|
82
85
|
locale,
|
|
83
86
|
copyMarkdown,
|
|
84
87
|
openDocs,
|
|
@@ -28,17 +28,17 @@ const agentLlmsDirectiveStyle = {
|
|
|
28
28
|
* Path-based breadcrumb that shows only parent / current folder.
|
|
29
29
|
* Skips the entry segment (e.g. "docs"). Parent is clickable.
|
|
30
30
|
*/
|
|
31
|
-
function PathBreadcrumb({ pathname,
|
|
31
|
+
function PathBreadcrumb({ pathname, publicPath, locale }) {
|
|
32
32
|
const router = useRouter();
|
|
33
33
|
const segments = pathname.split("/").filter(Boolean);
|
|
34
|
-
const
|
|
35
|
-
const contentSegments = segments.slice(
|
|
34
|
+
const publicParts = publicPath.split("/").filter(Boolean);
|
|
35
|
+
const contentSegments = publicParts.length > 0 && segments.slice(0, publicParts.length).join("/") === publicParts.join("/") ? segments.slice(publicParts.length) : segments;
|
|
36
36
|
if (contentSegments.length < 2) return null;
|
|
37
37
|
const parentSegment = contentSegments[contentSegments.length - 2];
|
|
38
38
|
const currentSegment = contentSegments[contentSegments.length - 1];
|
|
39
39
|
const parentLabel = parentSegment.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
40
40
|
const currentLabel = currentSegment.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
41
|
-
const localizedParentUrl = withLangInUrl("/" + [...
|
|
41
|
+
const localizedParentUrl = withLangInUrl("/" + [...publicParts, ...contentSegments.slice(0, -1)].filter(Boolean).join("/"), locale);
|
|
42
42
|
return /* @__PURE__ */ jsxs("nav", {
|
|
43
43
|
className: "fd-breadcrumb",
|
|
44
44
|
"aria-label": "Breadcrumb",
|
|
@@ -102,6 +102,94 @@ function localizeInternalLinks(root, locale) {
|
|
|
102
102
|
} catch {}
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
+
function normalizePublicDocsPath(value, entry) {
|
|
106
|
+
if (typeof value !== "string") return `/${entry.replace(/^\/+|\/+$/g, "") || "docs"}`;
|
|
107
|
+
const cleaned = value.trim();
|
|
108
|
+
if (cleaned === "" || cleaned === "/") return "";
|
|
109
|
+
return `/${cleaned.replace(/^\/+|\/+$/g, "")}`;
|
|
110
|
+
}
|
|
111
|
+
function toPublicDocsPath(pathname, entry, publicPath) {
|
|
112
|
+
const normalizedEntry = `/${entry.replace(/^\/+|\/+$/g, "") || "docs"}`;
|
|
113
|
+
const normalizedPath = pathname.replace(/\/+$/, "") || "/";
|
|
114
|
+
if (publicPath === normalizedEntry) return normalizedPath;
|
|
115
|
+
if (normalizedPath === normalizedEntry) return publicPath || "/";
|
|
116
|
+
if (normalizedPath.startsWith(`${normalizedEntry}/`)) {
|
|
117
|
+
const suffix = normalizedPath.slice(normalizedEntry.length + 1);
|
|
118
|
+
return publicPath ? `${publicPath}/${suffix}` : `/${suffix}`;
|
|
119
|
+
}
|
|
120
|
+
return normalizedPath;
|
|
121
|
+
}
|
|
122
|
+
function rewriteDocsPathLinks(root, entry, publicPath) {
|
|
123
|
+
const anchors = root.querySelectorAll("a[href]:not([data-fd-docspath=\"true\"])");
|
|
124
|
+
for (const anchor of anchors) {
|
|
125
|
+
const href = anchor.getAttribute("href");
|
|
126
|
+
if (!href || href.startsWith("#")) continue;
|
|
127
|
+
if (/^(mailto:|tel:|javascript:)/i.test(href)) continue;
|
|
128
|
+
try {
|
|
129
|
+
const url = new URL(href, window.location.origin);
|
|
130
|
+
if (url.origin !== window.location.origin) continue;
|
|
131
|
+
const nextPath = toPublicDocsPath(url.pathname, entry, publicPath);
|
|
132
|
+
if (nextPath === url.pathname) continue;
|
|
133
|
+
anchor.href = `${nextPath}${url.search}${url.hash}`;
|
|
134
|
+
anchor.dataset.fdDocspath = "true";
|
|
135
|
+
} catch {}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function normalizeCurrentPath(pathname) {
|
|
139
|
+
return pathname.replace(/\/+$/, "") || "/";
|
|
140
|
+
}
|
|
141
|
+
function syncDocsPathActiveLinks(root, entry, publicPath) {
|
|
142
|
+
const currentPath = normalizeCurrentPath(window.location.pathname);
|
|
143
|
+
const anchors = root.querySelectorAll("a[data-active][href]");
|
|
144
|
+
for (const anchor of anchors) {
|
|
145
|
+
const href = anchor.getAttribute("href");
|
|
146
|
+
if (!href || href.startsWith("#")) continue;
|
|
147
|
+
try {
|
|
148
|
+
const url = new URL(href, window.location.origin);
|
|
149
|
+
if (url.origin !== window.location.origin) continue;
|
|
150
|
+
if (!isDocsNavigationPath(url.pathname, entry, publicPath)) continue;
|
|
151
|
+
const publicHrefPath = normalizeCurrentPath(toPublicDocsPath(url.pathname, entry, publicPath));
|
|
152
|
+
anchor.dataset.active = publicHrefPath === currentPath ? "true" : "false";
|
|
153
|
+
} catch {}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function isFrameworkPath(pathname) {
|
|
157
|
+
return pathname.startsWith("/_next") || pathname.startsWith("/api") || pathname.startsWith("/.well-known") || pathname === "/mcp" || pathname === "/llms.txt" || pathname === "/llms-full.txt" || pathname === "/robots.txt" || pathname === "/sitemap.xml" || pathname === "/sitemap.md" || pathname === "/AGENTS.md" || pathname === "/AGENT.md" || pathname === "/skill.md" || /\/[^/]+\.[^/]+$/.test(pathname);
|
|
158
|
+
}
|
|
159
|
+
function isDocsNavigationPath(pathname, entry, publicPath) {
|
|
160
|
+
if (isFrameworkPath(pathname)) return false;
|
|
161
|
+
const normalizedEntry = `/${entry.replace(/^\/+|\/+$/g, "") || "docs"}`;
|
|
162
|
+
if (pathname === normalizedEntry || pathname.startsWith(`${normalizedEntry}/`)) return true;
|
|
163
|
+
if (publicPath === "") return pathname.startsWith("/");
|
|
164
|
+
return pathname === publicPath || pathname.startsWith(`${publicPath}/`);
|
|
165
|
+
}
|
|
166
|
+
function installDocsPathNavigationGuard(entry, publicPath) {
|
|
167
|
+
if (publicPath === `/${entry.replace(/^\/+|\/+$/g, "") || "docs"}`) return void 0;
|
|
168
|
+
function onClick(event) {
|
|
169
|
+
if (event.defaultPrevented) return;
|
|
170
|
+
if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
171
|
+
const anchor = (event.target instanceof Element ? event.target : null)?.closest("a[href]");
|
|
172
|
+
if (!anchor) return;
|
|
173
|
+
if (anchor.target && anchor.target !== "_self") return;
|
|
174
|
+
if (anchor.hasAttribute("download")) return;
|
|
175
|
+
const href = anchor.getAttribute("href");
|
|
176
|
+
if (!href || href.startsWith("#")) return;
|
|
177
|
+
if (/^(mailto:|tel:|javascript:)/i.test(href)) return;
|
|
178
|
+
try {
|
|
179
|
+
const url = new URL(href, window.location.origin);
|
|
180
|
+
if (url.origin !== window.location.origin) return;
|
|
181
|
+
if (!isDocsNavigationPath(url.pathname, entry, publicPath)) return;
|
|
182
|
+
const nextPath = toPublicDocsPath(url.pathname, entry, publicPath);
|
|
183
|
+
if (nextPath === url.pathname) return;
|
|
184
|
+
const nextHref = `${nextPath}${url.search}${url.hash}`;
|
|
185
|
+
if (nextHref === `${window.location.pathname}${window.location.search}${window.location.hash}`) return;
|
|
186
|
+
event.preventDefault();
|
|
187
|
+
window.location.assign(nextHref);
|
|
188
|
+
} catch {}
|
|
189
|
+
}
|
|
190
|
+
document.addEventListener("click", onClick, true);
|
|
191
|
+
return () => document.removeEventListener("click", onClick, true);
|
|
192
|
+
}
|
|
105
193
|
function decodeHashTarget(hash) {
|
|
106
194
|
const value = hash.startsWith("#") ? hash.slice(1) : hash;
|
|
107
195
|
try {
|
|
@@ -175,13 +263,14 @@ function TitleDecorations({ description, belowTitle }) {
|
|
|
175
263
|
if (!description && !belowTitle) return null;
|
|
176
264
|
return /* @__PURE__ */ jsx(Fragment$1, { children: Children.toArray([description, belowTitle].filter(Boolean)) });
|
|
177
265
|
}
|
|
178
|
-
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, readingTimeMap, readingTime: readingTimeProp, structuredDataMap, structuredData: structuredDataProp, readingTimeEnabled = false, lastUpdatedEnabled = true, lastUpdatedPosition = "footer", llmsTxtEnabled = false, descriptionMap, description, feedbackEnabled = false, feedbackQuestion, feedbackPlaceholder, feedbackPositiveLabel, feedbackNegativeLabel, feedbackSubmitLabel, feedbackOnFeedback, analytics = false, children }) {
|
|
266
|
+
function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled = true, changelogBasePath, entry = "docs", publicPath, locale, copyMarkdown = false, openDocs = false, openDocsProviders, pageActionsPosition = "below-title", pageActionsAlignment = "left", githubUrl, contentDir, githubBranch = "main", githubDirectory, editOnGithubUrl, lastModifiedMap, lastModified: lastModifiedProp, readingTimeMap, readingTime: readingTimeProp, structuredDataMap, structuredData: structuredDataProp, readingTimeEnabled = false, lastUpdatedEnabled = true, lastUpdatedPosition = "footer", llmsTxtEnabled = false, descriptionMap, description, feedbackEnabled = false, feedbackQuestion, feedbackPlaceholder, feedbackPositiveLabel, feedbackNegativeLabel, feedbackSubmitLabel, feedbackOnFeedback, analytics = false, children }) {
|
|
179
267
|
const fdTocStyle = tocStyle === "directional" ? "clerk" : void 0;
|
|
180
268
|
const [toc, setToc] = useState([]);
|
|
181
269
|
const [titlePortalHost, setTitlePortalHost] = useState(null);
|
|
182
270
|
const [browserPath, setBrowserPath] = useState(null);
|
|
183
271
|
const pathname = usePathname();
|
|
184
272
|
const activeLocale = resolveClientLocale(useWindowSearchParams(), locale);
|
|
273
|
+
const resolvedPublicPath = normalizePublicDocsPath(publicPath, entry);
|
|
185
274
|
const llmsLangQuery = activeLocale ? `?lang=${encodeURIComponent(activeLocale)}` : "";
|
|
186
275
|
const pageDescription = description ?? descriptionMap?.[pathname.replace(/\/$/, "") || "/"];
|
|
187
276
|
const normalizedPath = (browserPath ?? pathname).replace(/\/$/, "") || "/";
|
|
@@ -207,6 +296,9 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
207
296
|
isChangelogRoute,
|
|
208
297
|
normalizedPath
|
|
209
298
|
]);
|
|
299
|
+
useEffect(() => {
|
|
300
|
+
return installDocsPathNavigationGuard(entry, resolvedPublicPath);
|
|
301
|
+
}, [entry, resolvedPublicPath]);
|
|
210
302
|
const resolvedReadingTime = !isChangelogRoute ? readingTimeProp !== void 0 ? readingTimeProp : readingTimeEnabled ? matchedReadingTime : void 0 : void 0;
|
|
211
303
|
const effectiveTocEnabled = isChangelogRoute ? false : tocEnabled;
|
|
212
304
|
const effectiveBreadcrumbEnabled = isChangelogRoute ? false : breadcrumbEnabled;
|
|
@@ -225,17 +317,22 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
225
317
|
return () => cancelAnimationFrame(timer);
|
|
226
318
|
}, [effectiveTocEnabled, pathname]);
|
|
227
319
|
useEffect(() => {
|
|
228
|
-
if (!activeLocale) return;
|
|
229
320
|
const timer = requestAnimationFrame(() => {
|
|
230
|
-
const
|
|
231
|
-
if (!
|
|
232
|
-
|
|
321
|
+
const root = document.body;
|
|
322
|
+
if (!root) return;
|
|
323
|
+
rewriteDocsPathLinks(root, entry, resolvedPublicPath);
|
|
324
|
+
syncDocsPathActiveLinks(root, entry, resolvedPublicPath);
|
|
325
|
+
if (!activeLocale) return;
|
|
326
|
+
localizeInternalLinks(root, activeLocale);
|
|
233
327
|
});
|
|
234
328
|
return () => cancelAnimationFrame(timer);
|
|
235
329
|
}, [
|
|
236
330
|
activeLocale,
|
|
331
|
+
browserPath,
|
|
237
332
|
children,
|
|
238
|
-
|
|
333
|
+
entry,
|
|
334
|
+
pathname,
|
|
335
|
+
resolvedPublicPath
|
|
239
336
|
]);
|
|
240
337
|
useEffect(() => {
|
|
241
338
|
setBrowserPath(window.location.pathname);
|
|
@@ -383,7 +480,7 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
383
480
|
children: [
|
|
384
481
|
effectiveBreadcrumbEnabled && /* @__PURE__ */ jsx(PathBreadcrumb, {
|
|
385
482
|
pathname,
|
|
386
|
-
|
|
483
|
+
publicPath: resolvedPublicPath,
|
|
387
484
|
locale: activeLocale
|
|
388
485
|
}),
|
|
389
486
|
showActionsAboveTitle && /* @__PURE__ */ jsxs("div", {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farming-labs/theme",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.109",
|
|
4
4
|
"description": "Theme package for @farming-labs/docs — layout, provider, MDX components, and styles",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"docs",
|
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
"tsdown": "^0.20.3",
|
|
140
140
|
"typescript": "^5.9.3",
|
|
141
141
|
"vitest": "^3.2.4",
|
|
142
|
-
"@farming-labs/docs": "0.1.
|
|
142
|
+
"@farming-labs/docs": "0.1.109"
|
|
143
143
|
},
|
|
144
144
|
"peerDependencies": {
|
|
145
145
|
"@farming-labs/docs": ">=0.0.1",
|