@farming-labs/theme 0.1.60 → 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 +248 -29
- package/dist/client-analytics.mjs +23 -0
- package/dist/docs-ai-features.d.mts +3 -1
- package/dist/docs-ai-features.mjs +47 -12
- 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 +63 -11
- 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 +27 -4
- 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,5 +1,6 @@
|
|
|
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";
|
|
@@ -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 [];
|
|
@@ -494,7 +546,7 @@ function DocsCommandSearch({ api = "/api/docs", locale }) {
|
|
|
494
546
|
const allItems = [...recentItems, ...displayItems];
|
|
495
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,5 +1,6 @@
|
|
|
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";
|
|
@@ -162,7 +163,7 @@ function TitleDecorations({ description, belowTitle }) {
|
|
|
162
163
|
if (!description && !belowTitle) return null;
|
|
163
164
|
return /* @__PURE__ */ jsx(Fragment$1, { children: Children.toArray([description, belowTitle].filter(Boolean)) });
|
|
164
165
|
}
|
|
165
|
-
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 }) {
|
|
166
167
|
const fdTocStyle = tocStyle === "directional" ? "clerk" : void 0;
|
|
167
168
|
const [toc, setToc] = useState([]);
|
|
168
169
|
const [titlePortalHost, setTitlePortalHost] = useState(null);
|
|
@@ -174,6 +175,25 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
174
175
|
const normalizedPath = (browserPath ?? pathname).replace(/\/$/, "") || "/";
|
|
175
176
|
const isChangelogRoute = !!(changelogBasePath && (normalizedPath === changelogBasePath || normalizedPath.startsWith(`${changelogBasePath}/`)));
|
|
176
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
|
+
]);
|
|
177
197
|
const resolvedReadingTime = !isChangelogRoute ? readingTimeProp !== void 0 ? readingTimeProp : readingTimeEnabled ? matchedReadingTime : void 0 : void 0;
|
|
178
198
|
const effectiveTocEnabled = isChangelogRoute ? false : tocEnabled;
|
|
179
199
|
const effectiveBreadcrumbEnabled = isChangelogRoute ? false : breadcrumbEnabled;
|
|
@@ -285,7 +305,8 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
285
305
|
openDocs,
|
|
286
306
|
providers: openDocsProviders,
|
|
287
307
|
alignment: pageActionsAlignment,
|
|
288
|
-
githubFileUrl
|
|
308
|
+
githubFileUrl,
|
|
309
|
+
analytics
|
|
289
310
|
})
|
|
290
311
|
}),
|
|
291
312
|
showReadingTimeBelowTitle && readingTimeBlock
|
|
@@ -349,7 +370,8 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
349
370
|
openDocs,
|
|
350
371
|
providers: openDocsProviders,
|
|
351
372
|
alignment: pageActionsAlignment,
|
|
352
|
-
githubFileUrl
|
|
373
|
+
githubFileUrl,
|
|
374
|
+
analytics
|
|
353
375
|
})
|
|
354
376
|
}), readingTimeBlock]
|
|
355
377
|
}),
|
|
@@ -374,7 +396,8 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
374
396
|
positiveLabel: feedbackPositiveLabel,
|
|
375
397
|
negativeLabel: feedbackNegativeLabel,
|
|
376
398
|
submitLabel: feedbackSubmitLabel,
|
|
377
|
-
onFeedback: feedbackOnFeedback
|
|
399
|
+
onFeedback: feedbackOnFeedback,
|
|
400
|
+
analytics
|
|
378
401
|
}),
|
|
379
402
|
showFooter && /* @__PURE__ */ jsxs("div", {
|
|
380
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) {
|