@farming-labs/theme 0.1.112 → 0.1.114
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-layout.mjs +9 -5
- package/dist/docs-page-client.d.mts +6 -0
- package/dist/docs-page-client.mjs +5 -1
- package/dist/open-docs-providers.mjs +79 -0
- package/dist/page-actions.d.mts +6 -0
- package/dist/page-actions.mjs +63 -24
- package/dist/prompt.d.mts +8 -4
- package/dist/prompt.mjs +61 -21
- package/dist/safe-icon-html.mjs +45 -0
- package/dist/tanstack-layout.mjs +8 -4
- package/package.json +2 -2
package/dist/docs-layout.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { withLangInUrl } from "./i18n.mjs";
|
|
|
3
3
|
import { DocsPageClient } from "./docs-page-client.mjs";
|
|
4
4
|
import { DocsAIFeatures } from "./docs-ai-features.mjs";
|
|
5
5
|
import { DocsCommandSearch } from "./docs-command-search.mjs";
|
|
6
|
+
import { resolveOpenDocsProviders } from "./open-docs-providers.mjs";
|
|
6
7
|
import { resolvePageReadingTime, resolveReadingTimeOptions } from "./reading-time.mjs";
|
|
7
8
|
import { SidebarSearchWithAI } from "./sidebar-search-ai.mjs";
|
|
8
9
|
import { LocaleThemeControl } from "./locale-theme-control.mjs";
|
|
@@ -660,11 +661,12 @@ function createDocsLayout(config, options) {
|
|
|
660
661
|
const readingTimeWordsPerMinute = readingTimeOptions.wordsPerMinute ?? 220;
|
|
661
662
|
const llmsTxtEnabled = resolveEnabledByDefault(config.llmsTxt);
|
|
662
663
|
const feedbackConfig = resolveFeedbackConfig(config.feedback);
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
664
|
+
const openDocsConfig = pageActions?.openDocs && typeof pageActions.openDocs === "object" ? pageActions.openDocs : void 0;
|
|
665
|
+
const openDocsProviders = resolveOpenDocsProviders(openDocsConfig?.providers, {
|
|
666
|
+
target: openDocsConfig?.target,
|
|
667
|
+
prompt: openDocsConfig?.prompt,
|
|
668
|
+
serializeIcon
|
|
669
|
+
});
|
|
668
670
|
const githubRaw = config.github;
|
|
669
671
|
const githubUrl = typeof githubRaw === "string" ? githubRaw.replace(/\/$/, "") : githubRaw?.url.replace(/\/$/, "");
|
|
670
672
|
const githubBranch = typeof githubRaw === "object" ? githubRaw.branch ?? "main" : "main";
|
|
@@ -784,6 +786,8 @@ function createDocsLayout(config, options) {
|
|
|
784
786
|
copyMarkdown: copyMarkdownEnabled,
|
|
785
787
|
openDocs: openDocsEnabled,
|
|
786
788
|
openDocsProviders,
|
|
789
|
+
openDocsTarget: openDocsConfig?.target,
|
|
790
|
+
openDocsPrompt: openDocsConfig?.prompt,
|
|
787
791
|
pageActionsPosition,
|
|
788
792
|
pageActionsAlignment,
|
|
789
793
|
githubUrl,
|
|
@@ -8,6 +8,8 @@ interface SerializedProvider {
|
|
|
8
8
|
name: string;
|
|
9
9
|
iconHtml?: string;
|
|
10
10
|
urlTemplate: string;
|
|
11
|
+
target?: "markdown" | "page" | "source" | "github";
|
|
12
|
+
prompt?: string;
|
|
11
13
|
}
|
|
12
14
|
interface DocsPageClientProps {
|
|
13
15
|
tocEnabled: boolean;
|
|
@@ -23,6 +25,8 @@ interface DocsPageClientProps {
|
|
|
23
25
|
copyMarkdown?: boolean;
|
|
24
26
|
openDocs?: boolean;
|
|
25
27
|
openDocsProviders?: SerializedProvider[];
|
|
28
|
+
openDocsTarget?: "markdown" | "page" | "source" | "github";
|
|
29
|
+
openDocsPrompt?: string;
|
|
26
30
|
/** Where to render page actions relative to the title */
|
|
27
31
|
pageActionsPosition?: "above-title" | "below-title";
|
|
28
32
|
/** Horizontal alignment of page action buttons */
|
|
@@ -86,6 +90,8 @@ declare function DocsPageClient({
|
|
|
86
90
|
copyMarkdown,
|
|
87
91
|
openDocs,
|
|
88
92
|
openDocsProviders,
|
|
93
|
+
openDocsTarget,
|
|
94
|
+
openDocsPrompt,
|
|
89
95
|
pageActionsPosition,
|
|
90
96
|
pageActionsAlignment,
|
|
91
97
|
githubUrl,
|
|
@@ -263,7 +263,7 @@ function TitleDecorations({ description, belowTitle }) {
|
|
|
263
263
|
if (!description && !belowTitle) return null;
|
|
264
264
|
return /* @__PURE__ */ jsx(Fragment$1, { children: Children.toArray([description, belowTitle].filter(Boolean)) });
|
|
265
265
|
}
|
|
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 }) {
|
|
266
|
+
function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled = true, changelogBasePath, entry = "docs", publicPath, locale, copyMarkdown = false, openDocs = false, openDocsProviders, openDocsTarget, openDocsPrompt, 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 }) {
|
|
267
267
|
const fdTocStyle = tocStyle === "directional" ? "clerk" : void 0;
|
|
268
268
|
const [toc, setToc] = useState([]);
|
|
269
269
|
const [titlePortalHost, setTitlePortalHost] = useState(null);
|
|
@@ -414,6 +414,8 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
414
414
|
copyMarkdown,
|
|
415
415
|
openDocs,
|
|
416
416
|
providers: openDocsProviders,
|
|
417
|
+
openDocsTarget,
|
|
418
|
+
openDocsPrompt,
|
|
417
419
|
alignment: pageActionsAlignment,
|
|
418
420
|
githubFileUrl,
|
|
419
421
|
analytics
|
|
@@ -492,6 +494,8 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
492
494
|
copyMarkdown,
|
|
493
495
|
openDocs,
|
|
494
496
|
providers: openDocsProviders,
|
|
497
|
+
openDocsTarget,
|
|
498
|
+
openDocsPrompt,
|
|
495
499
|
alignment: pageActionsAlignment,
|
|
496
500
|
githubFileUrl,
|
|
497
501
|
analytics
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
//#region src/open-docs-providers.ts
|
|
2
|
+
const PROMPT_PROVIDER_TEMPLATES = {
|
|
3
|
+
chatgpt: "https://chatgpt.com/?q={prompt}",
|
|
4
|
+
claude: "https://claude.ai/new?q={prompt}",
|
|
5
|
+
cursor: "https://cursor.com/link/prompt?text={prompt}",
|
|
6
|
+
gemini: "https://gemini.google.com/app?q={prompt}",
|
|
7
|
+
copilot: "https://github.com/copilot?prompt={prompt}"
|
|
8
|
+
};
|
|
9
|
+
const OPEN_DOCS_PROVIDER_PRESETS = {
|
|
10
|
+
chatgpt: {
|
|
11
|
+
name: "ChatGPT",
|
|
12
|
+
urlTemplate: "https://chatgpt.com/?q={prompt}",
|
|
13
|
+
promptUrlTemplate: PROMPT_PROVIDER_TEMPLATES.chatgpt
|
|
14
|
+
},
|
|
15
|
+
claude: {
|
|
16
|
+
name: "Claude",
|
|
17
|
+
urlTemplate: "https://claude.ai/new?q={prompt}",
|
|
18
|
+
promptUrlTemplate: PROMPT_PROVIDER_TEMPLATES.claude
|
|
19
|
+
},
|
|
20
|
+
cursor: {
|
|
21
|
+
name: "Cursor",
|
|
22
|
+
urlTemplate: "https://cursor.com/link/prompt?text={prompt}",
|
|
23
|
+
promptUrlTemplate: PROMPT_PROVIDER_TEMPLATES.cursor
|
|
24
|
+
},
|
|
25
|
+
gemini: {
|
|
26
|
+
name: "Gemini",
|
|
27
|
+
urlTemplate: "https://gemini.google.com/app?q={prompt}",
|
|
28
|
+
promptUrlTemplate: PROMPT_PROVIDER_TEMPLATES.gemini
|
|
29
|
+
},
|
|
30
|
+
copilot: {
|
|
31
|
+
name: "Copilot",
|
|
32
|
+
urlTemplate: "https://github.com/copilot?prompt={prompt}",
|
|
33
|
+
promptUrlTemplate: PROMPT_PROVIDER_TEMPLATES.copilot
|
|
34
|
+
},
|
|
35
|
+
github: {
|
|
36
|
+
name: "GitHub",
|
|
37
|
+
urlTemplate: "{githubUrl}",
|
|
38
|
+
promptUrlTemplate: "{githubUrl}",
|
|
39
|
+
target: "github"
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
function normalizeProviderName(name) {
|
|
43
|
+
return name.trim().toLowerCase();
|
|
44
|
+
}
|
|
45
|
+
function resolveOpenDocsProviders(providers, options = {}) {
|
|
46
|
+
if (!providers || providers.length === 0) return void 0;
|
|
47
|
+
const serialized = providers.map((provider) => resolveOpenDocsProvider(provider, options)).filter((provider) => provider !== void 0);
|
|
48
|
+
return serialized.length > 0 ? serialized : void 0;
|
|
49
|
+
}
|
|
50
|
+
function resolveOpenDocsProvider(provider, options = {}) {
|
|
51
|
+
const normalizedId = typeof provider === "string" ? normalizeProviderName(provider) : typeof provider.id === "string" ? normalizeProviderName(provider.id) : typeof provider.name === "string" ? normalizeProviderName(provider.name) : typeof provider.label === "string" ? normalizeProviderName(provider.label) : void 0;
|
|
52
|
+
const preset = normalizedId ? OPEN_DOCS_PROVIDER_PRESETS[normalizedId] : void 0;
|
|
53
|
+
if (typeof provider === "string") {
|
|
54
|
+
if (!preset) return void 0;
|
|
55
|
+
return {
|
|
56
|
+
name: preset.name,
|
|
57
|
+
urlTemplate: preset.urlTemplate,
|
|
58
|
+
promptUrlTemplate: preset.promptUrlTemplate,
|
|
59
|
+
target: preset.target ?? options.target,
|
|
60
|
+
prompt: options.prompt
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const cursorAppTemplate = normalizedId === "cursor" && provider.mode === "app" ? "cursor://anysphere.cursor-deeplink/prompt?text={prompt}" : void 0;
|
|
64
|
+
const name = provider.name ?? provider.label ?? preset?.name;
|
|
65
|
+
const urlTemplate = provider.urlTemplate ?? cursorAppTemplate ?? preset?.urlTemplate;
|
|
66
|
+
const hasCustomUrlTemplate = typeof provider.urlTemplate === "string";
|
|
67
|
+
if (!name || !urlTemplate) return void 0;
|
|
68
|
+
return {
|
|
69
|
+
name,
|
|
70
|
+
urlTemplate,
|
|
71
|
+
promptUrlTemplate: provider.promptUrlTemplate ?? cursorAppTemplate ?? preset?.promptUrlTemplate,
|
|
72
|
+
iconHtml: options.serializeIcon?.(provider.icon) ?? (typeof provider.icon === "string" ? provider.icon : void 0),
|
|
73
|
+
target: provider.target ?? preset?.target ?? options.target ?? (hasCustomUrlTemplate ? "page" : void 0),
|
|
74
|
+
prompt: provider.prompt ?? options.prompt
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
//#endregion
|
|
79
|
+
export { resolveOpenDocsProviders };
|
package/dist/page-actions.d.mts
CHANGED
|
@@ -6,11 +6,15 @@ interface SerializedProvider {
|
|
|
6
6
|
name: string;
|
|
7
7
|
iconHtml?: string;
|
|
8
8
|
urlTemplate: string;
|
|
9
|
+
target?: "markdown" | "page" | "source" | "github";
|
|
10
|
+
prompt?: string;
|
|
9
11
|
}
|
|
10
12
|
interface PageActionsProps {
|
|
11
13
|
copyMarkdown?: boolean;
|
|
12
14
|
openDocs?: boolean;
|
|
13
15
|
providers?: SerializedProvider[];
|
|
16
|
+
openDocsTarget?: "markdown" | "page" | "source" | "github";
|
|
17
|
+
openDocsPrompt?: string;
|
|
14
18
|
alignment?: "left" | "right";
|
|
15
19
|
/** GitHub file URL (edit view) for the current page. Used when urlTemplate contains {githubUrl}. */
|
|
16
20
|
githubFileUrl?: string | null;
|
|
@@ -20,6 +24,8 @@ declare function PageActions({
|
|
|
20
24
|
copyMarkdown,
|
|
21
25
|
openDocs,
|
|
22
26
|
providers,
|
|
27
|
+
openDocsTarget,
|
|
28
|
+
openDocsPrompt,
|
|
23
29
|
alignment,
|
|
24
30
|
githubFileUrl,
|
|
25
31
|
analytics
|
package/dist/page-actions.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { emitClientAnalyticsEvent } from "./client-analytics.mjs";
|
|
4
|
+
import { sanitizeIconHtml } from "./safe-icon-html.mjs";
|
|
4
5
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
5
6
|
import { usePathname } from "fumadocs-core/framework";
|
|
6
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -68,12 +69,30 @@ const ExternalLinkIcon = () => /* @__PURE__ */ jsxs("svg", {
|
|
|
68
69
|
});
|
|
69
70
|
const DEFAULT_PROVIDERS = [{
|
|
70
71
|
name: "ChatGPT",
|
|
71
|
-
urlTemplate: "https://chatgpt.com/?
|
|
72
|
+
urlTemplate: "https://chatgpt.com/?q={prompt}"
|
|
72
73
|
}, {
|
|
73
74
|
name: "Claude",
|
|
74
|
-
urlTemplate: "https://claude.ai/new?q=
|
|
75
|
+
urlTemplate: "https://claude.ai/new?q={prompt}"
|
|
75
76
|
}];
|
|
76
|
-
|
|
77
|
+
const DEFAULT_OPEN_DOCS_TARGET = "markdown";
|
|
78
|
+
const DEFAULT_OPEN_DOCS_PROMPT = "Read this documentation: {url}";
|
|
79
|
+
function pageUrlToMarkdownUrl(pageUrl) {
|
|
80
|
+
try {
|
|
81
|
+
const url = new URL(pageUrl);
|
|
82
|
+
const pathname = url.pathname.replace(/\/+$/, "") || url.pathname;
|
|
83
|
+
url.pathname = pathname.endsWith(".md") ? pathname : `${pathname}.md`;
|
|
84
|
+
url.search = "";
|
|
85
|
+
url.hash = "";
|
|
86
|
+
return url.toString();
|
|
87
|
+
} catch {
|
|
88
|
+
const clean = pageUrl.replace(/[?#].*$/, "").replace(/\/+$/, "") || pageUrl;
|
|
89
|
+
return clean.endsWith(".md") ? clean : `${clean}.md`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function fillPromptTemplate(template, values) {
|
|
93
|
+
return template.replace(/\{pageUrl\}/g, values.pageUrl).replace(/\{markdownUrl\}/g, values.markdownUrl).replace(/\{sourceUrl\}/g, values.sourceUrl).replace(/\{mdxUrl\}/g, values.sourceUrl).replace(/\{githubUrl\}/g, values.githubUrl).replace(/\{url\}/g, values.url);
|
|
94
|
+
}
|
|
95
|
+
function PageActions({ copyMarkdown, openDocs, providers, openDocsTarget = DEFAULT_OPEN_DOCS_TARGET, openDocsPrompt = DEFAULT_OPEN_DOCS_PROMPT, alignment = "left", githubFileUrl, analytics = false }) {
|
|
77
96
|
const [copied, setCopied] = useState(false);
|
|
78
97
|
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
79
98
|
const dropdownRef = useRef(null);
|
|
@@ -99,13 +118,28 @@ function PageActions({ copyMarkdown, openDocs, providers, alignment = "left", gi
|
|
|
99
118
|
}, [analytics, pathname]);
|
|
100
119
|
const handleOpen = useCallback((provider) => {
|
|
101
120
|
const template = provider.urlTemplate;
|
|
102
|
-
|
|
121
|
+
const githubUrl = githubFileUrl ?? "";
|
|
122
|
+
if (/\{githubUrl\}/.test(template) && !githubUrl) {
|
|
103
123
|
setDropdownOpen(false);
|
|
104
124
|
return;
|
|
105
125
|
}
|
|
106
126
|
const pageUrl = window.location.href;
|
|
107
|
-
const
|
|
108
|
-
|
|
127
|
+
const sourceUrl = `${window.location.origin}${pathname}.mdx`;
|
|
128
|
+
const markdownUrl = pageUrlToMarkdownUrl(pageUrl);
|
|
129
|
+
const target = provider.target ?? openDocsTarget;
|
|
130
|
+
if (target === "github" && !githubUrl) {
|
|
131
|
+
setDropdownOpen(false);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const targetUrl = target === "markdown" ? markdownUrl : target === "source" ? sourceUrl : target === "github" ? githubUrl : pageUrl;
|
|
135
|
+
const prompt = fillPromptTemplate(provider.prompt ?? openDocsPrompt, {
|
|
136
|
+
url: targetUrl,
|
|
137
|
+
pageUrl,
|
|
138
|
+
markdownUrl,
|
|
139
|
+
sourceUrl,
|
|
140
|
+
githubUrl
|
|
141
|
+
});
|
|
142
|
+
let url = template.replace(/\{prompt\}/g, encodeURIComponent(prompt)).replace(/\{url\}/g, encodeURIComponent(targetUrl)).replace(/\{pageUrl\}/g, encodeURIComponent(pageUrl)).replace(/\{markdownUrl\}/g, encodeURIComponent(markdownUrl)).replace(/\{sourceUrl\}/g, encodeURIComponent(sourceUrl)).replace(/\{mdxUrl\}/g, encodeURIComponent(sourceUrl)).replace(/\{githubUrl\}/g, githubUrl);
|
|
109
143
|
if (analytics) emitClientAnalyticsEvent({
|
|
110
144
|
type: "page_action_open_docs",
|
|
111
145
|
properties: {
|
|
@@ -119,7 +153,9 @@ function PageActions({ copyMarkdown, openDocs, providers, alignment = "left", gi
|
|
|
119
153
|
}, [
|
|
120
154
|
analytics,
|
|
121
155
|
pathname,
|
|
122
|
-
githubFileUrl
|
|
156
|
+
githubFileUrl,
|
|
157
|
+
openDocsPrompt,
|
|
158
|
+
openDocsTarget
|
|
123
159
|
]);
|
|
124
160
|
useEffect(() => {
|
|
125
161
|
if (!dropdownOpen) return;
|
|
@@ -166,23 +202,26 @@ function PageActions({ copyMarkdown, openDocs, providers, alignment = "left", gi
|
|
|
166
202
|
}), dropdownOpen && /* @__PURE__ */ jsx("div", {
|
|
167
203
|
className: "fd-page-action-menu",
|
|
168
204
|
role: "menu",
|
|
169
|
-
children: resolvedProviders.map((provider) =>
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
205
|
+
children: resolvedProviders.map((provider) => {
|
|
206
|
+
const iconHtml = sanitizeIconHtml(provider.iconHtml);
|
|
207
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
208
|
+
type: "button",
|
|
209
|
+
role: "menuitem",
|
|
210
|
+
className: "fd-page-action-menu-item",
|
|
211
|
+
onClick: () => handleOpen(provider),
|
|
212
|
+
children: [
|
|
213
|
+
iconHtml && /* @__PURE__ */ jsx("span", {
|
|
214
|
+
className: "fd-page-action-menu-icon",
|
|
215
|
+
dangerouslySetInnerHTML: { __html: iconHtml }
|
|
216
|
+
}),
|
|
217
|
+
/* @__PURE__ */ jsxs("span", {
|
|
218
|
+
className: "fd-page-action-menu-label",
|
|
219
|
+
children: ["Open in ", provider.name]
|
|
220
|
+
}),
|
|
221
|
+
/* @__PURE__ */ jsx(ExternalLinkIcon, {})
|
|
222
|
+
]
|
|
223
|
+
}, provider.name);
|
|
224
|
+
})
|
|
186
225
|
})]
|
|
187
226
|
})]
|
|
188
227
|
});
|
package/dist/prompt.d.mts
CHANGED
|
@@ -4,12 +4,16 @@ import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
|
4
4
|
//#region src/prompt.d.ts
|
|
5
5
|
type PromptAction = "copy" | "open";
|
|
6
6
|
type PromptIconValue = React.ReactNode | string;
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
type PromptOpenDocsProvider = string | {
|
|
8
|
+
id?: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
label?: string;
|
|
9
11
|
icon?: PromptIconValue;
|
|
10
|
-
|
|
12
|
+
iconHtml?: string;
|
|
13
|
+
urlTemplate?: string;
|
|
11
14
|
promptUrlTemplate?: string;
|
|
12
|
-
|
|
15
|
+
mode?: "web" | "app";
|
|
16
|
+
};
|
|
13
17
|
interface PromptProps {
|
|
14
18
|
title?: string;
|
|
15
19
|
description?: string;
|
package/dist/prompt.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { sanitizeIconHtml } from "./safe-icon-html.mjs";
|
|
3
4
|
import { extractPromptText } from "./prompt-text.mjs";
|
|
4
5
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
5
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -74,6 +75,13 @@ const defaultPromptProviderTemplates = {
|
|
|
74
75
|
gemini: "https://gemini.google.com/app?q={prompt}",
|
|
75
76
|
copilot: "https://github.com/copilot?prompt={prompt}"
|
|
76
77
|
};
|
|
78
|
+
const defaultPromptProviderLabels = {
|
|
79
|
+
chatgpt: "ChatGPT",
|
|
80
|
+
claude: "Claude",
|
|
81
|
+
cursor: "Cursor",
|
|
82
|
+
gemini: "Gemini",
|
|
83
|
+
copilot: "Copilot"
|
|
84
|
+
};
|
|
77
85
|
function normalizeProviderName(name) {
|
|
78
86
|
return name.trim().toLowerCase();
|
|
79
87
|
}
|
|
@@ -93,8 +101,32 @@ function parseStringArray(value) {
|
|
|
93
101
|
return normalized.length > 0 ? normalized : void 0;
|
|
94
102
|
}
|
|
95
103
|
function resolveProviderChoices(availableProviders, preferredNames) {
|
|
96
|
-
const
|
|
97
|
-
|
|
104
|
+
const configuredProviders = (availableProviders ?? []).map((provider) => {
|
|
105
|
+
if (typeof provider === "string") {
|
|
106
|
+
const normalized = normalizeProviderName(provider);
|
|
107
|
+
return {
|
|
108
|
+
normalized,
|
|
109
|
+
name: defaultPromptProviderLabels[normalized] ?? provider,
|
|
110
|
+
provider: {
|
|
111
|
+
id: provider,
|
|
112
|
+
name: defaultPromptProviderLabels[normalized] ?? provider
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const rawName = provider.name ?? provider.label ?? provider.id;
|
|
117
|
+
if (!rawName) return null;
|
|
118
|
+
const normalized = normalizeProviderName(rawName);
|
|
119
|
+
return {
|
|
120
|
+
normalized,
|
|
121
|
+
name: provider.name ?? provider.label ?? defaultPromptProviderLabels[normalized] ?? rawName,
|
|
122
|
+
provider: {
|
|
123
|
+
...provider,
|
|
124
|
+
name: provider.name ?? provider.label ?? defaultPromptProviderLabels[normalized] ?? rawName
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}).filter((entry) => entry !== null);
|
|
128
|
+
const configuredByName = new Map(configuredProviders.map((entry) => [entry.normalized, entry.provider]));
|
|
129
|
+
const names = preferredNames && preferredNames.length > 0 ? preferredNames : configuredProviders.map((entry) => entry.name);
|
|
98
130
|
const seen = /* @__PURE__ */ new Set();
|
|
99
131
|
const resolved = [];
|
|
100
132
|
for (const rawName of names) {
|
|
@@ -104,11 +136,13 @@ function resolveProviderChoices(availableProviders, preferredNames) {
|
|
|
104
136
|
if (seen.has(normalized)) continue;
|
|
105
137
|
seen.add(normalized);
|
|
106
138
|
const configured = configuredByName.get(normalized);
|
|
107
|
-
const
|
|
139
|
+
const cursorAppTemplate = normalized === "cursor" && configured?.mode === "app" ? "cursor://anysphere.cursor-deeplink/prompt?text={prompt}" : void 0;
|
|
140
|
+
const template = configured?.promptUrlTemplate ?? cursorAppTemplate ?? configured?.urlTemplate ?? defaultPromptProviderTemplates[normalized];
|
|
108
141
|
if (!template) continue;
|
|
109
142
|
resolved.push({
|
|
110
|
-
name: configured?.name ?? name,
|
|
143
|
+
name: configured?.name ?? defaultPromptProviderLabels[normalized] ?? name,
|
|
111
144
|
icon: configured?.icon,
|
|
145
|
+
iconHtml: configured?.iconHtml,
|
|
112
146
|
urlTemplate: template
|
|
113
147
|
});
|
|
114
148
|
}
|
|
@@ -252,23 +286,29 @@ function Prompt({ title, description, prompt, icon, showTitle = true, showDescri
|
|
|
252
286
|
}), menuOpen && /* @__PURE__ */ jsx("div", {
|
|
253
287
|
className: "fd-prompt-menu",
|
|
254
288
|
role: "menu",
|
|
255
|
-
children: resolvedProviders.map((provider) =>
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
children: provider.icon
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
289
|
+
children: resolvedProviders.map((provider) => {
|
|
290
|
+
const iconHtml = sanitizeIconHtml(provider.iconHtml);
|
|
291
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
292
|
+
type: "button",
|
|
293
|
+
role: "menuitem",
|
|
294
|
+
className: "fd-prompt-menu-item",
|
|
295
|
+
onClick: () => handleOpen(provider),
|
|
296
|
+
children: [provider.icon && typeof provider.icon !== "string" ? /* @__PURE__ */ jsx("span", {
|
|
297
|
+
className: "fd-prompt-menu-icon",
|
|
298
|
+
children: provider.icon
|
|
299
|
+
}) : iconHtml ? /* @__PURE__ */ jsx("span", {
|
|
300
|
+
className: "fd-prompt-menu-icon",
|
|
301
|
+
dangerouslySetInnerHTML: { __html: iconHtml }
|
|
302
|
+
}) : null, /* @__PURE__ */ jsxs("span", {
|
|
303
|
+
className: "fd-prompt-menu-label",
|
|
304
|
+
children: [
|
|
305
|
+
openLabel,
|
|
306
|
+
" ",
|
|
307
|
+
provider.name
|
|
308
|
+
]
|
|
309
|
+
})]
|
|
310
|
+
}, provider.name);
|
|
311
|
+
})
|
|
272
312
|
})]
|
|
273
313
|
}) : null
|
|
274
314
|
]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//#region src/safe-icon-html.ts
|
|
2
|
+
const SAFE_ICON_TAGS = new Set([
|
|
3
|
+
"circle",
|
|
4
|
+
"clippath",
|
|
5
|
+
"defs",
|
|
6
|
+
"desc",
|
|
7
|
+
"ellipse",
|
|
8
|
+
"g",
|
|
9
|
+
"line",
|
|
10
|
+
"lineargradient",
|
|
11
|
+
"mask",
|
|
12
|
+
"path",
|
|
13
|
+
"polygon",
|
|
14
|
+
"polyline",
|
|
15
|
+
"radialgradient",
|
|
16
|
+
"rect",
|
|
17
|
+
"span",
|
|
18
|
+
"stop",
|
|
19
|
+
"svg",
|
|
20
|
+
"symbol",
|
|
21
|
+
"title",
|
|
22
|
+
"use"
|
|
23
|
+
]);
|
|
24
|
+
const TAG_PATTERN = /<\/?\s*([a-zA-Z][\w:-]*)\b/g;
|
|
25
|
+
const BLOCKED_MARKUP_PATTERN = /<\s*!/;
|
|
26
|
+
const BLOCKED_ATTRIBUTE_PATTERN = /(?:^|[\s/])(?:on[a-z][\w:-]*|srcdoc|style)\s*=/i;
|
|
27
|
+
const URL_ATTRIBUTE_PATTERN = /(?:^|[\s/])(?:href|xlink:href|src)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi;
|
|
28
|
+
function sanitizeIconHtml(html) {
|
|
29
|
+
const trimmed = html?.trim();
|
|
30
|
+
if (!trimmed || trimmed.length > 1e4) return void 0;
|
|
31
|
+
if (BLOCKED_MARKUP_PATTERN.test(trimmed) || BLOCKED_ATTRIBUTE_PATTERN.test(trimmed)) return;
|
|
32
|
+
let tagMatch;
|
|
33
|
+
TAG_PATTERN.lastIndex = 0;
|
|
34
|
+
while ((tagMatch = TAG_PATTERN.exec(trimmed)) !== null) if (!SAFE_ICON_TAGS.has(tagMatch[1].toLowerCase())) return void 0;
|
|
35
|
+
let urlMatch;
|
|
36
|
+
URL_ATTRIBUTE_PATTERN.lastIndex = 0;
|
|
37
|
+
while ((urlMatch = URL_ATTRIBUTE_PATTERN.exec(trimmed)) !== null) {
|
|
38
|
+
const value = (urlMatch[1] ?? urlMatch[2] ?? urlMatch[3] ?? "").trim();
|
|
39
|
+
if (value && !value.startsWith("#")) return void 0;
|
|
40
|
+
}
|
|
41
|
+
return trimmed;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//#endregion
|
|
45
|
+
export { sanitizeIconHtml };
|
package/dist/tanstack-layout.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { escapeJsonLdForScript } from "./json-ld.mjs";
|
|
|
3
3
|
import { DocsPageClient } from "./docs-page-client.mjs";
|
|
4
4
|
import { DocsAIFeatures } from "./docs-ai-features.mjs";
|
|
5
5
|
import { DocsCommandSearch } from "./docs-command-search.mjs";
|
|
6
|
+
import { resolveOpenDocsProviders } from "./open-docs-providers.mjs";
|
|
6
7
|
import { resolveReadingTimeOptions } from "./reading-time.mjs";
|
|
7
8
|
import { SidebarSearchWithAI } from "./sidebar-search-ai.mjs";
|
|
8
9
|
import { LocaleThemeControl } from "./locale-theme-control.mjs";
|
|
@@ -245,10 +246,11 @@ function TanstackDocsLayout({ config, tree, locale, description, readingTime, la
|
|
|
245
246
|
const llmsTxtEnabled = resolveEnabledByDefault(config.llmsTxt);
|
|
246
247
|
const feedbackConfig = resolveFeedbackConfig(config.feedback);
|
|
247
248
|
const staticExport = !!config.staticExport;
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
249
|
+
const openDocsConfig = pageActions?.openDocs && typeof pageActions.openDocs === "object" ? pageActions.openDocs : void 0;
|
|
250
|
+
const openDocsProviders = resolveOpenDocsProviders(openDocsConfig?.providers, {
|
|
251
|
+
target: openDocsConfig?.target,
|
|
252
|
+
prompt: openDocsConfig?.prompt
|
|
253
|
+
});
|
|
252
254
|
const aiConfig = config.ai;
|
|
253
255
|
const aiEnabled = !staticExport && !!aiConfig?.enabled;
|
|
254
256
|
const aiMode = aiConfig?.mode ?? "search";
|
|
@@ -348,6 +350,8 @@ function TanstackDocsLayout({ config, tree, locale, description, readingTime, la
|
|
|
348
350
|
copyMarkdown: copyMarkdownEnabled,
|
|
349
351
|
openDocs: openDocsEnabled,
|
|
350
352
|
openDocsProviders,
|
|
353
|
+
openDocsTarget: openDocsConfig?.target,
|
|
354
|
+
openDocsPrompt: openDocsConfig?.prompt,
|
|
351
355
|
pageActionsPosition,
|
|
352
356
|
pageActionsAlignment,
|
|
353
357
|
editOnGithubUrl,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farming-labs/theme",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.114",
|
|
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.114"
|
|
143
143
|
},
|
|
144
144
|
"peerDependencies": {
|
|
145
145
|
"@farming-labs/docs": ">=0.0.1",
|