@farming-labs/theme 0.1.1 → 0.1.3
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 +6 -3
- package/dist/docs-api.mjs +25 -24
- package/dist/docs-command-search.mjs +5 -2
- package/dist/docs-page-client.mjs +48 -0
- package/dist/search.d.mts +4 -4
- package/dist/search.mjs +3 -3
- package/package.json +2 -2
package/dist/docs-api.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DocsI18nConfig, DocsMcpConfig, OrderingItem } from "@farming-labs/docs";
|
|
1
|
+
import { DocsI18nConfig, DocsMcpConfig, DocsSearchConfig, OrderingItem } from "@farming-labs/docs";
|
|
2
2
|
|
|
3
3
|
//#region src/docs-api.d.ts
|
|
4
4
|
interface AIProviderConfig {
|
|
@@ -34,6 +34,8 @@ interface DocsAPIOptions {
|
|
|
34
34
|
ai?: AIOptions;
|
|
35
35
|
/** i18n config (optional) */
|
|
36
36
|
i18n?: DocsI18nConfig;
|
|
37
|
+
/** Search configuration */
|
|
38
|
+
search?: boolean | DocsSearchConfig;
|
|
37
39
|
}
|
|
38
40
|
interface DocsMCPAPIOptions {
|
|
39
41
|
rootDir?: string;
|
|
@@ -44,6 +46,7 @@ interface DocsMCPAPIOptions {
|
|
|
44
46
|
};
|
|
45
47
|
ordering?: "alphabetical" | "numeric" | OrderingItem[];
|
|
46
48
|
mcp?: boolean | DocsMcpConfig;
|
|
49
|
+
search?: boolean | DocsSearchConfig;
|
|
47
50
|
}
|
|
48
51
|
/**
|
|
49
52
|
* Create a unified docs API route handler.
|
|
@@ -57,7 +60,7 @@ interface DocsMCPAPIOptions {
|
|
|
57
60
|
* @example
|
|
58
61
|
* ```ts
|
|
59
62
|
* // app/api/docs/route.ts (auto-generated by withDocs)
|
|
60
|
-
* import { createDocsAPI } from "@farming-labs/
|
|
63
|
+
* import { createDocsAPI } from "@farming-labs/next/api";
|
|
61
64
|
* export const { GET, POST } = createDocsAPI();
|
|
62
65
|
* export const revalidate = false;
|
|
63
66
|
* ```
|
|
@@ -68,7 +71,7 @@ declare function createDocsAPI(options?: DocsAPIOptions): {
|
|
|
68
71
|
/**
|
|
69
72
|
* GET handler — search, llms.txt, or llms-full.txt depending on query params.
|
|
70
73
|
*/
|
|
71
|
-
GET(request: Request):
|
|
74
|
+
GET(request: Request): Promise<Response>;
|
|
72
75
|
/**
|
|
73
76
|
* POST handler — AI chat with RAG.
|
|
74
77
|
* Body: `{ messages: [{ role: "user", content: "How do I …?" }] }`
|
package/dist/docs-api.mjs
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { withLangInUrl } from "./i18n.mjs";
|
|
2
2
|
import { getNextAppDir } from "./get-app-dir.mjs";
|
|
3
|
-
import { resolveDocsI18n, resolveDocsLocale } from "@farming-labs/docs";
|
|
3
|
+
import { performDocsSearch, resolveDocsI18n, resolveDocsLocale, resolveSearchRequestConfig } from "@farming-labs/docs";
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import matter from "gray-matter";
|
|
7
|
-
import { createSearchAPI } from "fumadocs-core/search/server";
|
|
8
7
|
import { createDocsMcpHttpHandler, createFilesystemDocsMcpSource } from "@farming-labs/docs/server";
|
|
9
8
|
|
|
10
9
|
//#region src/docs-api.ts
|
|
@@ -22,7 +21,7 @@ import { createDocsMcpHttpHandler, createFilesystemDocsMcpSource } from "@farmin
|
|
|
22
21
|
* @example
|
|
23
22
|
* ```ts
|
|
24
23
|
* // app/api/docs/route.ts (auto-generated by withDocs)
|
|
25
|
-
* import { createDocsAPI } from "@farming-labs/
|
|
24
|
+
* import { createDocsAPI } from "@farming-labs/next/api";
|
|
26
25
|
* export const { GET, POST } = createDocsAPI();
|
|
27
26
|
* export const revalidate = false;
|
|
28
27
|
* ```
|
|
@@ -289,13 +288,16 @@ function scanDocsDir(docsDir, entry, locale) {
|
|
|
289
288
|
const { data } = matter(raw);
|
|
290
289
|
const title = data.title || slugParts[slugParts.length - 1]?.replace(/-/g, " ") || "Documentation";
|
|
291
290
|
const description = data.description;
|
|
291
|
+
const { content: rawContent } = matter(raw);
|
|
292
292
|
const content = stripMdx(raw);
|
|
293
293
|
const url = withLangInUrl(slugParts.length === 0 ? `/${entry}` : `/${entry}/${slugParts.join("/")}`, locale);
|
|
294
294
|
indexes.push({
|
|
295
295
|
title,
|
|
296
296
|
description,
|
|
297
297
|
content,
|
|
298
|
-
|
|
298
|
+
rawContent,
|
|
299
|
+
url,
|
|
300
|
+
locale
|
|
299
301
|
});
|
|
300
302
|
} catch {}
|
|
301
303
|
let entries;
|
|
@@ -334,7 +336,7 @@ function resolveModelAndProvider(aiConfig, requestedModelId) {
|
|
|
334
336
|
apiKey
|
|
335
337
|
};
|
|
336
338
|
}
|
|
337
|
-
async function handleAskAI(request, indexes,
|
|
339
|
+
async function handleAskAI(request, indexes, aiConfig) {
|
|
338
340
|
let body;
|
|
339
341
|
try {
|
|
340
342
|
body = await request.json();
|
|
@@ -445,7 +447,7 @@ function generateLlmsTxt(indexes, options) {
|
|
|
445
447
|
* @example
|
|
446
448
|
* ```ts
|
|
447
449
|
* // app/api/docs/route.ts (auto-generated by withDocs)
|
|
448
|
-
* import { createDocsAPI } from "@farming-labs/
|
|
450
|
+
* import { createDocsAPI } from "@farming-labs/next/api";
|
|
449
451
|
* export const { GET, POST } = createDocsAPI();
|
|
450
452
|
* export const revalidate = false;
|
|
451
453
|
* ```
|
|
@@ -456,9 +458,9 @@ function createDocsAPI(options) {
|
|
|
456
458
|
const root = process.cwd();
|
|
457
459
|
const entry = options?.entry ?? readEntry(root);
|
|
458
460
|
const appDir = getNextAppDir(root);
|
|
459
|
-
const language = options?.language ?? "english";
|
|
460
461
|
const i18n = resolveDocsI18n(options?.i18n ?? readI18nConfig(root));
|
|
461
462
|
const aiConfig = options?.ai ?? readAIConfig(root);
|
|
463
|
+
const searchConfig = options?.search;
|
|
462
464
|
const llmsConfig = readLlmsTxtConfig(root);
|
|
463
465
|
function resolveLocaleFromRequest(request) {
|
|
464
466
|
if (!i18n) return void 0;
|
|
@@ -484,7 +486,6 @@ function createDocsAPI(options) {
|
|
|
484
486
|
};
|
|
485
487
|
}
|
|
486
488
|
const indexesByLocale = /* @__PURE__ */ new Map();
|
|
487
|
-
const searchApiByLocale = /* @__PURE__ */ new Map();
|
|
488
489
|
const llmsCacheByLocale = /* @__PURE__ */ new Map();
|
|
489
490
|
function getIndexes(ctx) {
|
|
490
491
|
const key = ctx.locale ?? "__default__";
|
|
@@ -494,17 +495,6 @@ function createDocsAPI(options) {
|
|
|
494
495
|
indexesByLocale.set(key, next);
|
|
495
496
|
return next;
|
|
496
497
|
}
|
|
497
|
-
function getSearchAPI(ctx) {
|
|
498
|
-
const key = ctx.locale ?? "__default__";
|
|
499
|
-
const cached = searchApiByLocale.get(key);
|
|
500
|
-
if (cached) return cached;
|
|
501
|
-
const api = createSearchAPI("simple", {
|
|
502
|
-
language,
|
|
503
|
-
indexes: getIndexes(ctx)
|
|
504
|
-
});
|
|
505
|
-
searchApiByLocale.set(key, api);
|
|
506
|
-
return api;
|
|
507
|
-
}
|
|
508
498
|
function getLlmsContent(ctx) {
|
|
509
499
|
const key = ctx.locale ?? "__default__";
|
|
510
500
|
const cached = llmsCacheByLocale.get(key);
|
|
@@ -518,9 +508,10 @@ function createDocsAPI(options) {
|
|
|
518
508
|
return next;
|
|
519
509
|
}
|
|
520
510
|
return {
|
|
521
|
-
GET(request) {
|
|
511
|
+
async GET(request) {
|
|
522
512
|
const ctx = resolveContextFromRequest(request);
|
|
523
|
-
const
|
|
513
|
+
const url = new URL(request.url);
|
|
514
|
+
const format = url.searchParams.get("format");
|
|
524
515
|
if (format === "llms") return new Response(getLlmsContent(ctx).llmsTxt, { headers: {
|
|
525
516
|
"Content-Type": "text/plain; charset=utf-8",
|
|
526
517
|
"Cache-Control": "public, max-age=3600"
|
|
@@ -529,12 +520,21 @@ function createDocsAPI(options) {
|
|
|
529
520
|
"Content-Type": "text/plain; charset=utf-8",
|
|
530
521
|
"Cache-Control": "public, max-age=3600"
|
|
531
522
|
} });
|
|
532
|
-
|
|
523
|
+
const query = url.searchParams.get("query")?.trim();
|
|
524
|
+
if (!query) return new Response(JSON.stringify([]), { headers: { "Content-Type": "application/json" } });
|
|
525
|
+
const results = await performDocsSearch({
|
|
526
|
+
pages: getIndexes(ctx),
|
|
527
|
+
query,
|
|
528
|
+
search: resolveSearchRequestConfig(searchConfig, request.url),
|
|
529
|
+
locale: ctx.locale,
|
|
530
|
+
pathname: url.searchParams.get("pathname") ?? void 0,
|
|
531
|
+
siteTitle: llmsConfig.siteTitle ?? "Documentation"
|
|
532
|
+
});
|
|
533
|
+
return new Response(JSON.stringify(results), { headers: { "Content-Type": "application/json" } });
|
|
533
534
|
},
|
|
534
535
|
async POST(request) {
|
|
535
536
|
if (!aiConfig.enabled) return Response.json({ error: "AI is not enabled. Set `ai: { enabled: true }` in your docs.config to enable it." }, { status: 404 });
|
|
536
|
-
|
|
537
|
-
return handleAskAI(request, getIndexes(ctx), getSearchAPI(ctx), aiConfig);
|
|
537
|
+
return handleAskAI(request, getIndexes(resolveContextFromRequest(request)), aiConfig);
|
|
538
538
|
}
|
|
539
539
|
};
|
|
540
540
|
}
|
|
@@ -558,6 +558,7 @@ function createDocsMCPAPI(options = {}) {
|
|
|
558
558
|
ordering: options.ordering
|
|
559
559
|
}),
|
|
560
560
|
mcp: options.mcp ?? readMcpConfig(rootDir),
|
|
561
|
+
search: options.search,
|
|
561
562
|
defaultName: navTitle
|
|
562
563
|
});
|
|
563
564
|
return {
|
|
@@ -18,6 +18,9 @@ function stripHtml(html) {
|
|
|
18
18
|
}
|
|
19
19
|
return html.replace(/<[^>]+>/g, "");
|
|
20
20
|
}
|
|
21
|
+
function stripSearchPreview(text) {
|
|
22
|
+
return text.replace(/```[\s\S]*?```/g, "").replace(/~~~[\s\S]*?~~~/g, "").replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/^#{1,6}\s+/gm, "").replace(/^\|?[\s:-]+(\|[\s:-]+)+\|?\s*$/gm, "").replace(/\|/g, " ").replace(/^[-*+]\s+/gm, "").replace(/(\*{1,3}|_{1,3})(.*?)\1/g, "$2").replace(/`([^`]+)`/g, "$1").replace(/`+/g, "").replace(/\s{2,}/g, " ").trim();
|
|
23
|
+
}
|
|
21
24
|
function fuzzyScore(query, text) {
|
|
22
25
|
const q = query.trim().toLowerCase();
|
|
23
26
|
const t = text.toLowerCase();
|
|
@@ -366,12 +369,12 @@ function DocsCommandSearch({ api = "/api/docs", locale }) {
|
|
|
366
369
|
const res = await fetch(requestUrl.toString());
|
|
367
370
|
if (!res.ok || cancelled) return;
|
|
368
371
|
const items = (await res.json()).map((r) => {
|
|
369
|
-
const label = stripHtml(r.content);
|
|
372
|
+
const label = stripSearchPreview(stripHtml(r.content));
|
|
370
373
|
const { score, indices } = fuzzyScore(debouncedQuery, label);
|
|
371
374
|
return {
|
|
372
375
|
id: r.id,
|
|
373
376
|
label,
|
|
374
|
-
subtitle: labelForType(r.type),
|
|
377
|
+
subtitle: r.description ? stripSearchPreview(stripHtml(r.description)) : labelForType(r.type),
|
|
375
378
|
url: withLangInUrl(r.url, activeLocale),
|
|
376
379
|
icon: iconForType(r.type),
|
|
377
380
|
score,
|
|
@@ -89,6 +89,27 @@ function localizeInternalLinks(root, locale) {
|
|
|
89
89
|
} catch {}
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
+
function decodeHashTarget(hash) {
|
|
93
|
+
const value = hash.startsWith("#") ? hash.slice(1) : hash;
|
|
94
|
+
try {
|
|
95
|
+
return decodeURIComponent(value);
|
|
96
|
+
} catch {
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function escapeIdSelector(value) {
|
|
101
|
+
if (typeof CSS !== "undefined" && typeof CSS.escape === "function") return CSS.escape(value);
|
|
102
|
+
return value.replace(/["\\.#:[\]>+~(){}^$|*?=!'`\s]/g, "\\$&");
|
|
103
|
+
}
|
|
104
|
+
function scrollToHashTarget(hash) {
|
|
105
|
+
if (!hash || hash === "#") return false;
|
|
106
|
+
const targetId = decodeHashTarget(hash);
|
|
107
|
+
if (!targetId) return false;
|
|
108
|
+
const target = document.getElementById(targetId) ?? document.querySelector(`#${escapeIdSelector(targetId)}`);
|
|
109
|
+
if (!target) return false;
|
|
110
|
+
target.scrollIntoView({ block: "start" });
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
92
113
|
function injectTitleDecorations(node, { description, belowTitle }) {
|
|
93
114
|
if (!description && !belowTitle) return {
|
|
94
115
|
node,
|
|
@@ -172,6 +193,33 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
172
193
|
children,
|
|
173
194
|
pathname
|
|
174
195
|
]);
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
let frame = 0;
|
|
198
|
+
let timeout = 0;
|
|
199
|
+
let cancelled = false;
|
|
200
|
+
const scheduleScroll = (attempt = 0) => {
|
|
201
|
+
if (cancelled) return;
|
|
202
|
+
const hash = window.location.hash;
|
|
203
|
+
if (!hash) return;
|
|
204
|
+
if (scrollToHashTarget(hash) || attempt >= 20) return;
|
|
205
|
+
timeout = window.setTimeout(() => {
|
|
206
|
+
frame = requestAnimationFrame(() => scheduleScroll(attempt + 1));
|
|
207
|
+
}, 100);
|
|
208
|
+
};
|
|
209
|
+
frame = requestAnimationFrame(() => scheduleScroll());
|
|
210
|
+
const onHashChange = () => {
|
|
211
|
+
cancelAnimationFrame(frame);
|
|
212
|
+
clearTimeout(timeout);
|
|
213
|
+
frame = requestAnimationFrame(() => scheduleScroll());
|
|
214
|
+
};
|
|
215
|
+
window.addEventListener("hashchange", onHashChange);
|
|
216
|
+
return () => {
|
|
217
|
+
cancelled = true;
|
|
218
|
+
window.removeEventListener("hashchange", onHashChange);
|
|
219
|
+
cancelAnimationFrame(frame);
|
|
220
|
+
clearTimeout(timeout);
|
|
221
|
+
};
|
|
222
|
+
}, [pathname, children]);
|
|
175
223
|
const showActions = copyMarkdown || openDocs;
|
|
176
224
|
const showActionsBelowTitle = showActions && pageActionsPosition === "below-title";
|
|
177
225
|
const showActionsAboveTitle = showActions && pageActionsPosition === "above-title";
|
package/dist/search.d.mts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Search API — backward-compatible re-export.
|
|
4
4
|
*
|
|
5
|
-
* New projects should use `createDocsAPI` from `@farming-labs/
|
|
5
|
+
* New projects should use `createDocsAPI` from `@farming-labs/next/api`
|
|
6
6
|
* which provides a unified handler for both search (GET) and AI chat (POST).
|
|
7
7
|
*
|
|
8
8
|
* This module is kept for backward compatibility with existing projects
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* @example
|
|
12
12
|
* ```ts
|
|
13
13
|
* // Recommended (new): app/api/docs/route.ts
|
|
14
|
-
* import { createDocsAPI } from "@farming-labs/
|
|
14
|
+
* import { createDocsAPI } from "@farming-labs/next/api";
|
|
15
15
|
* export const { GET, POST } = createDocsAPI();
|
|
16
16
|
*
|
|
17
17
|
* // Legacy: app/api/search/route.ts
|
|
@@ -20,14 +20,14 @@
|
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
22
22
|
/**
|
|
23
|
-
* @deprecated Use `createDocsAPI` from `@farming-labs/
|
|
23
|
+
* @deprecated Use `createDocsAPI` from `@farming-labs/next/api` instead.
|
|
24
24
|
* This function is kept for backward compatibility.
|
|
25
25
|
*/
|
|
26
26
|
declare function createDocsSearchAPI(options?: {
|
|
27
27
|
entry?: string;
|
|
28
28
|
language?: string;
|
|
29
29
|
}): {
|
|
30
|
-
GET(request: Request):
|
|
30
|
+
GET(request: Request): Promise<Response>;
|
|
31
31
|
POST(request: Request): Promise<Response>;
|
|
32
32
|
};
|
|
33
33
|
//#endregion
|
package/dist/search.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { createDocsAPI } from "./docs-api.mjs";
|
|
|
4
4
|
/**
|
|
5
5
|
* Search API — backward-compatible re-export.
|
|
6
6
|
*
|
|
7
|
-
* New projects should use `createDocsAPI` from `@farming-labs/
|
|
7
|
+
* New projects should use `createDocsAPI` from `@farming-labs/next/api`
|
|
8
8
|
* which provides a unified handler for both search (GET) and AI chat (POST).
|
|
9
9
|
*
|
|
10
10
|
* This module is kept for backward compatibility with existing projects
|
|
@@ -13,7 +13,7 @@ import { createDocsAPI } from "./docs-api.mjs";
|
|
|
13
13
|
* @example
|
|
14
14
|
* ```ts
|
|
15
15
|
* // Recommended (new): app/api/docs/route.ts
|
|
16
|
-
* import { createDocsAPI } from "@farming-labs/
|
|
16
|
+
* import { createDocsAPI } from "@farming-labs/next/api";
|
|
17
17
|
* export const { GET, POST } = createDocsAPI();
|
|
18
18
|
*
|
|
19
19
|
* // Legacy: app/api/search/route.ts
|
|
@@ -22,7 +22,7 @@ import { createDocsAPI } from "./docs-api.mjs";
|
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
24
|
/**
|
|
25
|
-
* @deprecated Use `createDocsAPI` from `@farming-labs/
|
|
25
|
+
* @deprecated Use `createDocsAPI` from `@farming-labs/next/api` instead.
|
|
26
26
|
* This function is kept for backward compatibility.
|
|
27
27
|
*/
|
|
28
28
|
function createDocsSearchAPI(options) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farming-labs/theme",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Theme package for @farming-labs/docs — layout, provider, MDX components, and styles",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"docs",
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
"tsdown": "^0.20.3",
|
|
128
128
|
"typescript": "^5.9.3",
|
|
129
129
|
"vitest": "^3.2.4",
|
|
130
|
-
"@farming-labs/docs": "0.1.
|
|
130
|
+
"@farming-labs/docs": "0.1.3"
|
|
131
131
|
},
|
|
132
132
|
"peerDependencies": {
|
|
133
133
|
"@farming-labs/docs": ">=0.0.1",
|