@farming-labs/theme 0.1.59 → 0.1.62
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 +9 -3
- package/dist/ai-search-dialog.mjs +253 -34
- package/dist/client-analytics.mjs +23 -0
- package/dist/docs-ai-features.d.mts +3 -1
- package/dist/docs-ai-features.mjs +49 -14
- package/dist/docs-api.d.mts +4 -1
- package/dist/docs-api.mjs +352 -59
- package/dist/docs-client-hooks.d.mts +4 -2
- package/dist/docs-client-hooks.mjs +52 -1
- package/dist/docs-command-search.d.mts +3 -1
- package/dist/docs-command-search.mjs +67 -15
- package/dist/docs-feedback.d.mts +3 -1
- package/dist/docs-feedback.mjs +35 -2
- package/dist/docs-layout.mjs +7 -3
- package/dist/docs-page-client.d.mts +2 -0
- package/dist/docs-page-client.mjs +34 -9
- package/dist/page-actions.d.mts +3 -1
- package/dist/page-actions.mjs +39 -7
- package/dist/tanstack-layout.mjs +7 -3
- package/package.json +2 -2
- package/styles/base.css +25 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { emitClientAnalyticsEvent } from "./client-analytics.mjs";
|
|
3
4
|
import { useWindowSearchParams } from "./client-location.mjs";
|
|
4
5
|
import { resolveClientLocale, withLangInUrl } from "./i18n.mjs";
|
|
5
6
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
6
7
|
import { createPortal } from "react-dom";
|
|
7
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
8
9
|
|
|
9
10
|
//#region src/docs-command-search.tsx
|
|
10
11
|
function cn(...classes) {
|
|
@@ -56,7 +57,7 @@ function fuzzyScore(query, text) {
|
|
|
56
57
|
};
|
|
57
58
|
}
|
|
58
59
|
function HighlightedLabel({ label, indices }) {
|
|
59
|
-
if (!indices.length) return /* @__PURE__ */ jsx(Fragment, { children: label });
|
|
60
|
+
if (!indices.length) return /* @__PURE__ */ jsx(Fragment$1, { children: label });
|
|
60
61
|
const out = [];
|
|
61
62
|
for (let pos = 0; pos < label.length; pos++) if (indices.includes(pos)) {
|
|
62
63
|
let run = label[pos];
|
|
@@ -71,7 +72,7 @@ function HighlightedLabel({ label, indices }) {
|
|
|
71
72
|
}, `m-${pos}`));
|
|
72
73
|
pos = p - 1;
|
|
73
74
|
} else out.push(/* @__PURE__ */ jsx("span", { children: label[pos] }, `t-${pos}`));
|
|
74
|
-
return /* @__PURE__ */ jsx(Fragment, { children: out });
|
|
75
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children: out });
|
|
75
76
|
}
|
|
76
77
|
function SearchIcon() {
|
|
77
78
|
return /* @__PURE__ */ jsxs("svg", {
|
|
@@ -302,7 +303,7 @@ function labelForType(type) {
|
|
|
302
303
|
* fuzzy-search experience. Styled entirely via omni-* CSS classes
|
|
303
304
|
* so each theme provides its own visual variant.
|
|
304
305
|
*/
|
|
305
|
-
function DocsCommandSearch({ api = "/api/docs", locale }) {
|
|
306
|
+
function DocsCommandSearch({ api = "/api/docs", locale, analytics = false }) {
|
|
306
307
|
const [open, setOpen] = useState(false);
|
|
307
308
|
const [query, setQuery] = useState("");
|
|
308
309
|
const [debouncedQuery, setDebouncedQuery] = useState("");
|
|
@@ -315,6 +316,18 @@ function DocsCommandSearch({ api = "/api/docs", locale }) {
|
|
|
315
316
|
const searchApi = useMemo(() => withLangInUrl(api, activeLocale), [activeLocale, api]);
|
|
316
317
|
const inputRef = useRef(null);
|
|
317
318
|
const listRef = useRef(null);
|
|
319
|
+
const setOpenWithAnalytics = useCallback((nextOpen, trigger) => {
|
|
320
|
+
setOpen(nextOpen);
|
|
321
|
+
if (!analytics) return;
|
|
322
|
+
emitClientAnalyticsEvent({
|
|
323
|
+
type: nextOpen ? "search_open" : "search_close",
|
|
324
|
+
locale: activeLocale,
|
|
325
|
+
properties: {
|
|
326
|
+
trigger,
|
|
327
|
+
mode: "command"
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
}, [activeLocale, analytics]);
|
|
318
331
|
useEffect(() => {
|
|
319
332
|
setMounted(true);
|
|
320
333
|
try {
|
|
@@ -332,12 +345,12 @@ function DocsCommandSearch({ api = "/api/docs", locale }) {
|
|
|
332
345
|
e.preventDefault();
|
|
333
346
|
e.stopPropagation();
|
|
334
347
|
e.stopImmediatePropagation();
|
|
335
|
-
|
|
348
|
+
setOpenWithAnalytics(true, "keyboard");
|
|
336
349
|
}
|
|
337
350
|
}
|
|
338
351
|
document.addEventListener("keydown", handler, true);
|
|
339
352
|
return () => document.removeEventListener("keydown", handler, true);
|
|
340
|
-
}, []);
|
|
353
|
+
}, [setOpenWithAnalytics]);
|
|
341
354
|
useEffect(() => {
|
|
342
355
|
function handler(e) {
|
|
343
356
|
const button = e.target.closest("button");
|
|
@@ -348,12 +361,12 @@ function DocsCommandSearch({ api = "/api/docs", locale }) {
|
|
|
348
361
|
e.preventDefault();
|
|
349
362
|
e.stopPropagation();
|
|
350
363
|
e.stopImmediatePropagation();
|
|
351
|
-
|
|
364
|
+
setOpenWithAnalytics(true, "button");
|
|
352
365
|
}
|
|
353
366
|
}
|
|
354
367
|
document.addEventListener("click", handler, true);
|
|
355
368
|
return () => document.removeEventListener("click", handler, true);
|
|
356
|
-
}, []);
|
|
369
|
+
}, [setOpenWithAnalytics]);
|
|
357
370
|
useEffect(() => {
|
|
358
371
|
if (!debouncedQuery.trim()) {
|
|
359
372
|
setResults([]);
|
|
@@ -363,6 +376,7 @@ function DocsCommandSearch({ api = "/api/docs", locale }) {
|
|
|
363
376
|
let cancelled = false;
|
|
364
377
|
setLoading(true);
|
|
365
378
|
(async () => {
|
|
379
|
+
const startedAt = Date.now();
|
|
366
380
|
try {
|
|
367
381
|
const requestUrl = new URL(searchApi, window.location.origin);
|
|
368
382
|
requestUrl.searchParams.set("query", debouncedQuery);
|
|
@@ -385,8 +399,28 @@ function DocsCommandSearch({ api = "/api/docs", locale }) {
|
|
|
385
399
|
if (!cancelled) {
|
|
386
400
|
setResults(items);
|
|
387
401
|
setActiveIndex(0);
|
|
402
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
403
|
+
type: "search_query",
|
|
404
|
+
locale: activeLocale,
|
|
405
|
+
properties: {
|
|
406
|
+
mode: "command",
|
|
407
|
+
queryLength: debouncedQuery.length,
|
|
408
|
+
resultCount: items.length,
|
|
409
|
+
durationMs: Math.max(0, Date.now() - startedAt)
|
|
410
|
+
}
|
|
411
|
+
});
|
|
388
412
|
}
|
|
389
|
-
} catch {
|
|
413
|
+
} catch {
|
|
414
|
+
if (!cancelled && analytics) emitClientAnalyticsEvent({
|
|
415
|
+
type: "search_error",
|
|
416
|
+
locale: activeLocale,
|
|
417
|
+
properties: {
|
|
418
|
+
mode: "command",
|
|
419
|
+
queryLength: debouncedQuery.length,
|
|
420
|
+
durationMs: Math.max(0, Date.now() - startedAt)
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
}
|
|
390
424
|
if (!cancelled) setLoading(false);
|
|
391
425
|
})();
|
|
392
426
|
return () => {
|
|
@@ -394,6 +428,7 @@ function DocsCommandSearch({ api = "/api/docs", locale }) {
|
|
|
394
428
|
};
|
|
395
429
|
}, [
|
|
396
430
|
activeLocale,
|
|
431
|
+
analytics,
|
|
397
432
|
debouncedQuery,
|
|
398
433
|
searchApi
|
|
399
434
|
]);
|
|
@@ -424,11 +459,11 @@ function DocsCommandSearch({ api = "/api/docs", locale }) {
|
|
|
424
459
|
useEffect(() => {
|
|
425
460
|
if (!open) return;
|
|
426
461
|
function handler(e) {
|
|
427
|
-
if (e.key === "Escape")
|
|
462
|
+
if (e.key === "Escape") setOpenWithAnalytics(false, "escape");
|
|
428
463
|
}
|
|
429
464
|
document.addEventListener("keydown", handler);
|
|
430
465
|
return () => document.removeEventListener("keydown", handler);
|
|
431
|
-
}, [open]);
|
|
466
|
+
}, [open, setOpenWithAnalytics]);
|
|
432
467
|
const saveRecent = useCallback((item) => {
|
|
433
468
|
try {
|
|
434
469
|
const entry = {
|
|
@@ -442,11 +477,28 @@ function DocsCommandSearch({ api = "/api/docs", locale }) {
|
|
|
442
477
|
} catch {}
|
|
443
478
|
}, [recents]);
|
|
444
479
|
const execute = useCallback((item) => {
|
|
480
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
481
|
+
type: "search_result_click",
|
|
482
|
+
locale: activeLocale,
|
|
483
|
+
path: item.url,
|
|
484
|
+
properties: {
|
|
485
|
+
mode: "command",
|
|
486
|
+
resultId: item.id,
|
|
487
|
+
resultUrl: item.url,
|
|
488
|
+
labelLength: item.label.length,
|
|
489
|
+
queryLength: query.length
|
|
490
|
+
}
|
|
491
|
+
});
|
|
445
492
|
saveRecent(item);
|
|
446
493
|
setOpen(false);
|
|
447
494
|
if (item.url.startsWith("http")) window.open(item.url, "_blank", "noopener");
|
|
448
495
|
else window.location.href = item.url;
|
|
449
|
-
}, [
|
|
496
|
+
}, [
|
|
497
|
+
activeLocale,
|
|
498
|
+
analytics,
|
|
499
|
+
query.length,
|
|
500
|
+
saveRecent
|
|
501
|
+
]);
|
|
450
502
|
const displayItems = useMemo(() => {
|
|
451
503
|
if (results.length > 0) return results;
|
|
452
504
|
return [];
|
|
@@ -492,9 +544,9 @@ function DocsCommandSearch({ api = "/api/docs", locale }) {
|
|
|
492
544
|
}
|
|
493
545
|
if (!mounted || !open) return null;
|
|
494
546
|
const allItems = [...recentItems, ...displayItems];
|
|
495
|
-
return createPortal(/* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
547
|
+
return createPortal(/* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
|
|
496
548
|
className: "omni-overlay",
|
|
497
|
-
onClick: () =>
|
|
549
|
+
onClick: () => setOpenWithAnalytics(false, "overlay")
|
|
498
550
|
}), /* @__PURE__ */ jsxs("div", {
|
|
499
551
|
className: "omni-content",
|
|
500
552
|
role: "dialog",
|
|
@@ -528,7 +580,7 @@ function DocsCommandSearch({ api = "/api/docs", locale }) {
|
|
|
528
580
|
/* @__PURE__ */ jsx("button", {
|
|
529
581
|
"aria-label": "Close",
|
|
530
582
|
className: "omni-close-btn",
|
|
531
|
-
onClick: () =>
|
|
583
|
+
onClick: () => setOpenWithAnalytics(false, "button"),
|
|
532
584
|
children: /* @__PURE__ */ jsx(CloseIcon, {})
|
|
533
585
|
})
|
|
534
586
|
]
|
package/dist/docs-feedback.d.mts
CHANGED
|
@@ -12,6 +12,7 @@ interface DocsFeedbackProps {
|
|
|
12
12
|
negativeLabel?: string;
|
|
13
13
|
submitLabel?: string;
|
|
14
14
|
onFeedback?: (data: DocsFeedbackData) => void | Promise<void>;
|
|
15
|
+
analytics?: boolean;
|
|
15
16
|
}
|
|
16
17
|
declare function DocsFeedback({
|
|
17
18
|
pathname,
|
|
@@ -22,7 +23,8 @@ declare function DocsFeedback({
|
|
|
22
23
|
positiveLabel,
|
|
23
24
|
negativeLabel,
|
|
24
25
|
submitLabel,
|
|
25
|
-
onFeedback
|
|
26
|
+
onFeedback,
|
|
27
|
+
analytics
|
|
26
28
|
}: DocsFeedbackProps): react_jsx_runtime0.JSX.Element;
|
|
27
29
|
//#endregion
|
|
28
30
|
export { DocsFeedback, DocsFeedbackProps };
|
package/dist/docs-feedback.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { emitClientAnalyticsEvent } from "./client-analytics.mjs";
|
|
3
4
|
import { useEffect, useMemo, useState } from "react";
|
|
4
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
6
|
|
|
@@ -99,7 +100,7 @@ function CheckIcon() {
|
|
|
99
100
|
})
|
|
100
101
|
});
|
|
101
102
|
}
|
|
102
|
-
function DocsFeedback({ pathname, entry, locale, question = "How is this guide?", placeholder = "Leave your feedback...", positiveLabel = "Good", negativeLabel = "Bad", submitLabel = "Submit", onFeedback }) {
|
|
103
|
+
function DocsFeedback({ pathname, entry, locale, question = "How is this guide?", placeholder = "Leave your feedback...", positiveLabel = "Good", negativeLabel = "Bad", submitLabel = "Submit", onFeedback, analytics = false }) {
|
|
103
104
|
const [selected, setSelected] = useState(null);
|
|
104
105
|
const [comment, setComment] = useState("");
|
|
105
106
|
const [status, setStatus] = useState("idle");
|
|
@@ -114,14 +115,46 @@ function DocsFeedback({ pathname, entry, locale, question = "How is this guide?"
|
|
|
114
115
|
function handleSelect(value) {
|
|
115
116
|
setSelected(value);
|
|
116
117
|
if (status !== "idle") setStatus("idle");
|
|
118
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
119
|
+
type: "feedback_select",
|
|
120
|
+
locale,
|
|
121
|
+
path: normalizedPathname,
|
|
122
|
+
properties: {
|
|
123
|
+
value,
|
|
124
|
+
slug: resolveSlug(entry, normalizedPathname)
|
|
125
|
+
}
|
|
126
|
+
});
|
|
117
127
|
}
|
|
118
128
|
async function handleSubmit() {
|
|
119
129
|
if (!selected || status === "submitting") return;
|
|
120
130
|
setStatus("submitting");
|
|
121
131
|
try {
|
|
122
|
-
|
|
132
|
+
const payload = buildFeedbackPayload(selected, normalizedPathname, entry, comment, locale);
|
|
133
|
+
await emitFeedback(payload, onFeedback);
|
|
134
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
135
|
+
type: "feedback_submit",
|
|
136
|
+
locale,
|
|
137
|
+
path: normalizedPathname,
|
|
138
|
+
properties: {
|
|
139
|
+
value: payload.value,
|
|
140
|
+
slug: payload.slug,
|
|
141
|
+
hasComment: Boolean(payload.comment),
|
|
142
|
+
commentLength: payload.comment?.length ?? 0
|
|
143
|
+
}
|
|
144
|
+
});
|
|
123
145
|
setStatus("submitted");
|
|
124
146
|
} catch {
|
|
147
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
148
|
+
type: "feedback_error",
|
|
149
|
+
locale,
|
|
150
|
+
path: normalizedPathname,
|
|
151
|
+
properties: {
|
|
152
|
+
value: selected,
|
|
153
|
+
slug: resolveSlug(entry, normalizedPathname),
|
|
154
|
+
hasComment: Boolean(comment.trim()),
|
|
155
|
+
commentLength: comment.trim().length
|
|
156
|
+
}
|
|
157
|
+
});
|
|
125
158
|
setStatus("error");
|
|
126
159
|
}
|
|
127
160
|
}
|
package/dist/docs-layout.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import path from "node:path";
|
|
|
11
11
|
import matter from "gray-matter";
|
|
12
12
|
import { DocsLayout } from "fumadocs-ui/layouts/docs";
|
|
13
13
|
import { Suspense } from "react";
|
|
14
|
-
import { applySidebarFolderIndexBehavior, buildPageOpenGraph, buildPageTwitter, resolveChangelogConfig, resolveDocsAgentMdxContent, resolvePageSidebarFolderIndexBehavior } from "@farming-labs/docs";
|
|
14
|
+
import { applySidebarFolderIndexBehavior, buildPageOpenGraph, buildPageTwitter, resolveChangelogConfig, resolveDocsAgentMdxContent, resolveDocsAnalyticsConfig, resolvePageSidebarFolderIndexBehavior } from "@farming-labs/docs";
|
|
15
15
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
16
16
|
|
|
17
17
|
//#region src/docs-layout.tsx
|
|
@@ -554,6 +554,7 @@ function createDocsLayout(config, options) {
|
|
|
554
554
|
const tocConfig = config.theme?.ui?.layout?.toc;
|
|
555
555
|
const tocEnabled = tocConfig?.enabled !== false;
|
|
556
556
|
const tocStyle = tocConfig?.style;
|
|
557
|
+
const analyticsEnabled = resolveDocsAnalyticsConfig(config.analytics).enabled;
|
|
557
558
|
const localeContext = resolveDocsLocaleContext(config, options?.locale);
|
|
558
559
|
const i18n = resolveDocsI18nConfig(getDocsI18n(config));
|
|
559
560
|
const activeLocale = localeContext.locale ?? i18n?.defaultLocale;
|
|
@@ -673,7 +674,8 @@ function createDocsLayout(config, options) {
|
|
|
673
674
|
fallback: null,
|
|
674
675
|
children: /* @__PURE__ */ jsx(DocsCommandSearch, {
|
|
675
676
|
api: docsApiUrl,
|
|
676
|
-
locale: activeLocale
|
|
677
|
+
locale: activeLocale,
|
|
678
|
+
analytics: analyticsEnabled
|
|
677
679
|
})
|
|
678
680
|
}),
|
|
679
681
|
aiEnabled && /* @__PURE__ */ jsx(Suspense, {
|
|
@@ -690,7 +692,8 @@ function createDocsLayout(config, options) {
|
|
|
690
692
|
loaderVariant: aiLoaderVariant,
|
|
691
693
|
loadingComponentHtml: aiLoadingComponentHtml,
|
|
692
694
|
models: aiModels,
|
|
693
|
-
defaultModelId: aiDefaultModelId
|
|
695
|
+
defaultModelId: aiDefaultModelId,
|
|
696
|
+
analytics: analyticsEnabled
|
|
694
697
|
})
|
|
695
698
|
}),
|
|
696
699
|
/* @__PURE__ */ jsx(Suspense, {
|
|
@@ -724,6 +727,7 @@ function createDocsLayout(config, options) {
|
|
|
724
727
|
feedbackPositiveLabel: feedbackConfig.positiveLabel,
|
|
725
728
|
feedbackNegativeLabel: feedbackConfig.negativeLabel,
|
|
726
729
|
feedbackSubmitLabel: feedbackConfig.submitLabel,
|
|
730
|
+
analytics: analyticsEnabled,
|
|
727
731
|
children
|
|
728
732
|
})
|
|
729
733
|
})
|
|
@@ -66,6 +66,7 @@ interface DocsPageClientProps {
|
|
|
66
66
|
feedbackNegativeLabel?: string;
|
|
67
67
|
feedbackSubmitLabel?: string;
|
|
68
68
|
feedbackOnFeedback?: (data: DocsFeedbackData) => void | Promise<void>;
|
|
69
|
+
analytics?: boolean;
|
|
69
70
|
children: ReactNode;
|
|
70
71
|
}
|
|
71
72
|
declare function DocsPageClient({
|
|
@@ -102,6 +103,7 @@ declare function DocsPageClient({
|
|
|
102
103
|
feedbackNegativeLabel,
|
|
103
104
|
feedbackSubmitLabel,
|
|
104
105
|
feedbackOnFeedback,
|
|
106
|
+
analytics,
|
|
105
107
|
children
|
|
106
108
|
}: DocsPageClientProps): react_jsx_runtime0.JSX.Element;
|
|
107
109
|
//#endregion
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { emitClientAnalyticsEvent } from "./client-analytics.mjs";
|
|
3
4
|
import { PageActions } from "./page-actions.mjs";
|
|
4
5
|
import { useWindowSearchParams } from "./client-location.mjs";
|
|
5
6
|
import { DocsFeedback } from "./docs-feedback.mjs";
|
|
6
7
|
import { resolveClientLocale, withLangInUrl } from "./i18n.mjs";
|
|
7
|
-
import { Children, cloneElement, isValidElement, useEffect, useState } from "react";
|
|
8
|
+
import { Children, Fragment, cloneElement, isValidElement, useEffect, useState } from "react";
|
|
8
9
|
import { DocsBody, DocsPage, EditOnGitHub } from "fumadocs-ui/layouts/docs/page";
|
|
9
10
|
import { createPortal } from "react-dom";
|
|
10
11
|
import { usePathname, useRouter } from "fumadocs-core/framework";
|
|
11
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
12
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
12
13
|
|
|
13
14
|
//#region src/docs-page-client.tsx
|
|
14
15
|
/**
|
|
@@ -124,6 +125,7 @@ function injectTitleDecorations(node, { description, belowTitle }) {
|
|
|
124
125
|
node,
|
|
125
126
|
inserted: false
|
|
126
127
|
};
|
|
128
|
+
const insertedExtras = extras.map((extra, index) => /* @__PURE__ */ jsx(Fragment, { children: extra }, `fd-title-decoration-${index}`));
|
|
127
129
|
function visit(current) {
|
|
128
130
|
if (current == null || typeof current === "boolean") return current;
|
|
129
131
|
if (inserted) return current;
|
|
@@ -134,7 +136,7 @@ function injectTitleDecorations(node, { description, belowTitle }) {
|
|
|
134
136
|
if (!isValidElement(current)) return current;
|
|
135
137
|
if (typeof current.type === "string" && current.type === "h1") {
|
|
136
138
|
inserted = true;
|
|
137
|
-
return
|
|
139
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [current, insertedExtras] }, "fd-title-decoration-block");
|
|
138
140
|
}
|
|
139
141
|
const childProps = current.props ?? null;
|
|
140
142
|
if (childProps?.children === void 0) return current;
|
|
@@ -159,9 +161,9 @@ function injectTitleDecorations(node, { description, belowTitle }) {
|
|
|
159
161
|
}
|
|
160
162
|
function TitleDecorations({ description, belowTitle }) {
|
|
161
163
|
if (!description && !belowTitle) return null;
|
|
162
|
-
return /* @__PURE__ */ jsx(Fragment, { children: Children.toArray([description, belowTitle].filter(Boolean)) });
|
|
164
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children: Children.toArray([description, belowTitle].filter(Boolean)) });
|
|
163
165
|
}
|
|
164
|
-
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, readingTimeEnabled = false, lastUpdatedEnabled = true, lastUpdatedPosition = "footer", llmsTxtEnabled = false, descriptionMap, description, feedbackEnabled = false, feedbackQuestion, feedbackPlaceholder, feedbackPositiveLabel, feedbackNegativeLabel, feedbackSubmitLabel, feedbackOnFeedback, children }) {
|
|
166
|
+
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, readingTimeEnabled = false, lastUpdatedEnabled = true, lastUpdatedPosition = "footer", llmsTxtEnabled = false, descriptionMap, description, feedbackEnabled = false, feedbackQuestion, feedbackPlaceholder, feedbackPositiveLabel, feedbackNegativeLabel, feedbackSubmitLabel, feedbackOnFeedback, analytics = false, children }) {
|
|
165
167
|
const fdTocStyle = tocStyle === "directional" ? "clerk" : void 0;
|
|
166
168
|
const [toc, setToc] = useState([]);
|
|
167
169
|
const [titlePortalHost, setTitlePortalHost] = useState(null);
|
|
@@ -173,6 +175,25 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
173
175
|
const normalizedPath = (browserPath ?? pathname).replace(/\/$/, "") || "/";
|
|
174
176
|
const isChangelogRoute = !!(changelogBasePath && (normalizedPath === changelogBasePath || normalizedPath.startsWith(`${changelogBasePath}/`)));
|
|
175
177
|
const matchedReadingTime = readingTimeMap?.[normalizedPath];
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
if (!analytics) return;
|
|
180
|
+
emitClientAnalyticsEvent({
|
|
181
|
+
type: "page_view",
|
|
182
|
+
locale: activeLocale,
|
|
183
|
+
path: normalizedPath,
|
|
184
|
+
properties: {
|
|
185
|
+
entry,
|
|
186
|
+
pathname: normalizedPath,
|
|
187
|
+
isChangelogRoute
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}, [
|
|
191
|
+
analytics,
|
|
192
|
+
activeLocale,
|
|
193
|
+
entry,
|
|
194
|
+
isChangelogRoute,
|
|
195
|
+
normalizedPath
|
|
196
|
+
]);
|
|
176
197
|
const resolvedReadingTime = !isChangelogRoute ? readingTimeProp !== void 0 ? readingTimeProp : readingTimeEnabled ? matchedReadingTime : void 0 : void 0;
|
|
177
198
|
const effectiveTocEnabled = isChangelogRoute ? false : tocEnabled;
|
|
178
199
|
const effectiveBreadcrumbEnabled = isChangelogRoute ? false : breadcrumbEnabled;
|
|
@@ -284,7 +305,8 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
284
305
|
openDocs,
|
|
285
306
|
providers: openDocsProviders,
|
|
286
307
|
alignment: pageActionsAlignment,
|
|
287
|
-
githubFileUrl
|
|
308
|
+
githubFileUrl,
|
|
309
|
+
analytics
|
|
288
310
|
})
|
|
289
311
|
}),
|
|
290
312
|
showReadingTimeBelowTitle && readingTimeBlock
|
|
@@ -318,6 +340,7 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
318
340
|
description: titleDescription,
|
|
319
341
|
belowTitle: belowTitleBlock
|
|
320
342
|
}), titlePortalHost) : null;
|
|
343
|
+
const renderedChildren = Children.toArray(decoratedChildren);
|
|
321
344
|
return /* @__PURE__ */ jsxs(DocsPage, {
|
|
322
345
|
full: false,
|
|
323
346
|
toc,
|
|
@@ -347,7 +370,8 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
347
370
|
openDocs,
|
|
348
371
|
providers: openDocsProviders,
|
|
349
372
|
alignment: pageActionsAlignment,
|
|
350
|
-
githubFileUrl
|
|
373
|
+
githubFileUrl,
|
|
374
|
+
analytics
|
|
351
375
|
})
|
|
352
376
|
}), readingTimeBlock]
|
|
353
377
|
}),
|
|
@@ -360,7 +384,7 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
360
384
|
children: [
|
|
361
385
|
/* @__PURE__ */ jsx("div", {
|
|
362
386
|
style: { flex: 1 },
|
|
363
|
-
children:
|
|
387
|
+
children: renderedChildren
|
|
364
388
|
}),
|
|
365
389
|
titleDecorationsPortal,
|
|
366
390
|
!isChangelogRoute && feedbackEnabled && /* @__PURE__ */ jsx(DocsFeedback, {
|
|
@@ -372,7 +396,8 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
372
396
|
positiveLabel: feedbackPositiveLabel,
|
|
373
397
|
negativeLabel: feedbackNegativeLabel,
|
|
374
398
|
submitLabel: feedbackSubmitLabel,
|
|
375
|
-
onFeedback: feedbackOnFeedback
|
|
399
|
+
onFeedback: feedbackOnFeedback,
|
|
400
|
+
analytics
|
|
376
401
|
}),
|
|
377
402
|
showFooter && /* @__PURE__ */ jsxs("div", {
|
|
378
403
|
className: "not-prose fd-page-footer",
|
package/dist/page-actions.d.mts
CHANGED
|
@@ -14,13 +14,15 @@ interface PageActionsProps {
|
|
|
14
14
|
alignment?: "left" | "right";
|
|
15
15
|
/** GitHub file URL (edit view) for the current page. Used when urlTemplate contains {githubUrl}. */
|
|
16
16
|
githubFileUrl?: string | null;
|
|
17
|
+
analytics?: boolean;
|
|
17
18
|
}
|
|
18
19
|
declare function PageActions({
|
|
19
20
|
copyMarkdown,
|
|
20
21
|
openDocs,
|
|
21
22
|
providers,
|
|
22
23
|
alignment,
|
|
23
|
-
githubFileUrl
|
|
24
|
+
githubFileUrl,
|
|
25
|
+
analytics
|
|
24
26
|
}: PageActionsProps): react_jsx_runtime0.JSX.Element | null;
|
|
25
27
|
//#endregion
|
|
26
28
|
export { PageActions };
|
package/dist/page-actions.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { emitClientAnalyticsEvent } from "./client-analytics.mjs";
|
|
3
4
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
5
|
import { usePathname } from "fumadocs-core/framework";
|
|
5
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -72,7 +73,7 @@ const DEFAULT_PROVIDERS = [{
|
|
|
72
73
|
name: "Claude",
|
|
73
74
|
urlTemplate: "https://claude.ai/new?q=Read+{mdxUrl},+I+want+to+ask+questions+about+it."
|
|
74
75
|
}];
|
|
75
|
-
function PageActions({ copyMarkdown, openDocs, providers, alignment = "left", githubFileUrl }) {
|
|
76
|
+
function PageActions({ copyMarkdown, openDocs, providers, alignment = "left", githubFileUrl, analytics = false }) {
|
|
76
77
|
const [copied, setCopied] = useState(false);
|
|
77
78
|
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
78
79
|
const dropdownRef = useRef(null);
|
|
@@ -82,13 +83,22 @@ function PageActions({ copyMarkdown, openDocs, providers, alignment = "left", gi
|
|
|
82
83
|
try {
|
|
83
84
|
const article = document.querySelector("article");
|
|
84
85
|
if (article) {
|
|
85
|
-
|
|
86
|
+
const content = article.innerText || "";
|
|
87
|
+
await navigator.clipboard.writeText(content);
|
|
88
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
89
|
+
type: "page_action_copy_markdown",
|
|
90
|
+
properties: {
|
|
91
|
+
contentLength: content.length,
|
|
92
|
+
pathname
|
|
93
|
+
}
|
|
94
|
+
});
|
|
86
95
|
setCopied(true);
|
|
87
96
|
setTimeout(() => setCopied(false), 2e3);
|
|
88
97
|
}
|
|
89
98
|
} catch {}
|
|
90
|
-
}, []);
|
|
91
|
-
const handleOpen = useCallback((
|
|
99
|
+
}, [analytics, pathname]);
|
|
100
|
+
const handleOpen = useCallback((provider) => {
|
|
101
|
+
const template = provider.urlTemplate;
|
|
92
102
|
if (/\{githubUrl\}/.test(template) && !githubFileUrl) {
|
|
93
103
|
setDropdownOpen(false);
|
|
94
104
|
return;
|
|
@@ -96,9 +106,21 @@ function PageActions({ copyMarkdown, openDocs, providers, alignment = "left", gi
|
|
|
96
106
|
const pageUrl = window.location.href;
|
|
97
107
|
const mdxUrl = `${window.location.origin}${pathname}.mdx`;
|
|
98
108
|
let url = template.replace(/\{url\}/g, encodeURIComponent(pageUrl)).replace(/\{mdxUrl\}/g, encodeURIComponent(mdxUrl)).replace(/\{githubUrl\}/g, githubFileUrl ?? "");
|
|
109
|
+
if (analytics) emitClientAnalyticsEvent({
|
|
110
|
+
type: "page_action_open_docs",
|
|
111
|
+
properties: {
|
|
112
|
+
provider: provider.name,
|
|
113
|
+
pathname,
|
|
114
|
+
usesGithubUrl: template.includes("{githubUrl}")
|
|
115
|
+
}
|
|
116
|
+
});
|
|
99
117
|
window.open(url, "_blank", "noopener,noreferrer");
|
|
100
118
|
setDropdownOpen(false);
|
|
101
|
-
}, [
|
|
119
|
+
}, [
|
|
120
|
+
analytics,
|
|
121
|
+
pathname,
|
|
122
|
+
githubFileUrl
|
|
123
|
+
]);
|
|
102
124
|
useEffect(() => {
|
|
103
125
|
if (!dropdownOpen) return;
|
|
104
126
|
function handleClick(e) {
|
|
@@ -127,7 +149,17 @@ function PageActions({ copyMarkdown, openDocs, providers, alignment = "left", gi
|
|
|
127
149
|
className: "fd-page-action-dropdown",
|
|
128
150
|
children: [/* @__PURE__ */ jsxs("button", {
|
|
129
151
|
type: "button",
|
|
130
|
-
onClick: () =>
|
|
152
|
+
onClick: () => {
|
|
153
|
+
const next = !dropdownOpen;
|
|
154
|
+
setDropdownOpen(next);
|
|
155
|
+
if (analytics && next) emitClientAnalyticsEvent({
|
|
156
|
+
type: "page_action_open_docs_menu",
|
|
157
|
+
properties: {
|
|
158
|
+
providerCount: resolvedProviders.length,
|
|
159
|
+
pathname
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
},
|
|
131
163
|
className: "fd-page-action-btn",
|
|
132
164
|
"aria-expanded": dropdownOpen,
|
|
133
165
|
children: [/* @__PURE__ */ jsx("span", { children: "Open in" }), /* @__PURE__ */ jsx(ChevronDownIcon, {})]
|
|
@@ -138,7 +170,7 @@ function PageActions({ copyMarkdown, openDocs, providers, alignment = "left", gi
|
|
|
138
170
|
type: "button",
|
|
139
171
|
role: "menuitem",
|
|
140
172
|
className: "fd-page-action-menu-item",
|
|
141
|
-
onClick: () => handleOpen(provider
|
|
173
|
+
onClick: () => handleOpen(provider),
|
|
142
174
|
children: [
|
|
143
175
|
provider.iconHtml && /* @__PURE__ */ jsx("span", {
|
|
144
176
|
className: "fd-page-action-menu-icon",
|
package/dist/tanstack-layout.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { SidebarSearchWithAI } from "./sidebar-search-ai.mjs";
|
|
|
7
7
|
import { LocaleThemeControl } from "./locale-theme-control.mjs";
|
|
8
8
|
import { DocsLayout } from "fumadocs-ui/layouts/docs";
|
|
9
9
|
import { Suspense } from "react";
|
|
10
|
-
import { applySidebarFolderIndexBehavior } from "@farming-labs/docs";
|
|
10
|
+
import { applySidebarFolderIndexBehavior, resolveDocsAnalyticsConfig } from "@farming-labs/docs";
|
|
11
11
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
12
12
|
|
|
13
13
|
//#region src/tanstack-layout.tsx
|
|
@@ -211,6 +211,7 @@ function TanstackDocsLayout({ config, tree, locale, description, readingTime, la
|
|
|
211
211
|
const tocConfig = config.theme?.ui?.layout?.toc;
|
|
212
212
|
const tocEnabled = tocConfig?.enabled !== false;
|
|
213
213
|
const tocStyle = tocConfig?.style;
|
|
214
|
+
const analyticsEnabled = resolveDocsAnalyticsConfig(config.analytics).enabled;
|
|
214
215
|
const docsApiUrl = withLangInUrl("/api/docs", locale);
|
|
215
216
|
const navTitle = config.nav?.title ?? "Docs";
|
|
216
217
|
const navUrl = withLangInUrl(config.nav?.url ?? `/${config.entry ?? "docs"}`, locale);
|
|
@@ -310,7 +311,8 @@ function TanstackDocsLayout({ config, tree, locale, description, readingTime, la
|
|
|
310
311
|
fallback: null,
|
|
311
312
|
children: /* @__PURE__ */ jsx(DocsCommandSearch, {
|
|
312
313
|
api: docsApiUrl,
|
|
313
|
-
locale
|
|
314
|
+
locale,
|
|
315
|
+
analytics: analyticsEnabled
|
|
314
316
|
})
|
|
315
317
|
}),
|
|
316
318
|
aiEnabled && /* @__PURE__ */ jsx(Suspense, {
|
|
@@ -325,7 +327,8 @@ function TanstackDocsLayout({ config, tree, locale, description, readingTime, la
|
|
|
325
327
|
aiLabel,
|
|
326
328
|
loaderVariant: aiLoaderVariant,
|
|
327
329
|
models: aiModels,
|
|
328
|
-
defaultModelId: aiDefaultModelId
|
|
330
|
+
defaultModelId: aiDefaultModelId,
|
|
331
|
+
analytics: analyticsEnabled
|
|
329
332
|
})
|
|
330
333
|
}),
|
|
331
334
|
/* @__PURE__ */ jsx(Suspense, {
|
|
@@ -355,6 +358,7 @@ function TanstackDocsLayout({ config, tree, locale, description, readingTime, la
|
|
|
355
358
|
feedbackPositiveLabel: feedbackConfig.positiveLabel,
|
|
356
359
|
feedbackNegativeLabel: feedbackConfig.negativeLabel,
|
|
357
360
|
feedbackSubmitLabel: feedbackConfig.submitLabel,
|
|
361
|
+
analytics: analyticsEnabled,
|
|
358
362
|
children
|
|
359
363
|
})
|
|
360
364
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farming-labs/theme",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.62",
|
|
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.62"
|
|
137
137
|
},
|
|
138
138
|
"peerDependencies": {
|
|
139
139
|
"@farming-labs/docs": ">=0.0.1",
|
package/styles/base.css
CHANGED
|
@@ -1399,8 +1399,10 @@ html[data-fd-route-kind="changelog"] .fd-ai-fm-input-bar {
|
|
|
1399
1399
|
.fd-page-nav-card {
|
|
1400
1400
|
display: flex;
|
|
1401
1401
|
min-width: 0;
|
|
1402
|
+
min-height: 8.75rem;
|
|
1402
1403
|
flex-direction: column;
|
|
1403
|
-
|
|
1404
|
+
justify-content: flex-start;
|
|
1405
|
+
gap: 0.375rem;
|
|
1404
1406
|
padding: 1rem;
|
|
1405
1407
|
border: 1px solid var(--color-fd-border, hsl(0 0% 80% / 50%));
|
|
1406
1408
|
border-radius: 0.5rem;
|
|
@@ -1434,10 +1436,32 @@ html[data-fd-route-kind="changelog"] .fd-ai-fm-input-bar {
|
|
|
1434
1436
|
}
|
|
1435
1437
|
|
|
1436
1438
|
.fd-page-nav-title {
|
|
1439
|
+
display: -webkit-box;
|
|
1440
|
+
overflow: hidden;
|
|
1441
|
+
min-height: calc(1.4em * 2);
|
|
1437
1442
|
font-size: 0.875rem;
|
|
1438
1443
|
font-weight: 600;
|
|
1439
1444
|
line-height: 1.4;
|
|
1440
1445
|
color: var(--color-fd-foreground);
|
|
1446
|
+
overflow-wrap: anywhere;
|
|
1447
|
+
-webkit-box-orient: vertical;
|
|
1448
|
+
-webkit-line-clamp: 2;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
.fd-page-nav-description {
|
|
1452
|
+
display: -webkit-box;
|
|
1453
|
+
overflow: hidden;
|
|
1454
|
+
min-height: calc(1.5em * 2);
|
|
1455
|
+
-webkit-box-orient: vertical;
|
|
1456
|
+
-webkit-line-clamp: 2;
|
|
1457
|
+
font-size: 0.875rem;
|
|
1458
|
+
line-height: 1.5;
|
|
1459
|
+
color: var(--color-fd-muted-foreground, hsl(0 0% 45%));
|
|
1460
|
+
overflow-wrap: anywhere;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
.fd-page-nav-description-empty {
|
|
1464
|
+
visibility: hidden;
|
|
1441
1465
|
}
|
|
1442
1466
|
|
|
1443
1467
|
@media (max-width: 640px) {
|