radiant-docs 0.1.66 → 0.1.68
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/package.json +1 -1
- package/template/astro.config.mjs +24 -2
- package/template/package-lock.json +8 -8
- package/template/package.json +2 -2
- package/template/src/components/MdxPage.astro +12 -2
- package/template/src/components/OpenApiPage.astro +13 -3
- package/template/src/components/PageAiActions.astro +715 -0
- package/template/src/components/endpoint/PlaygroundButton.astro +1 -1
- package/template/src/components/endpoint/PlaygroundForm.astro +8 -0
- package/template/src/layouts/Layout.astro +6 -1
- package/template/src/lib/ai-artifacts.ts +295 -13
- package/template/src/pages/{[...slug]/index.md.ts → [...slug].md.ts} +1 -1
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
3
|
+
import Icon from "./ui/Icon.astro";
|
|
4
|
+
import { prependBasePath } from "../lib/base-path";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
title: string;
|
|
8
|
+
routeSlug: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type MenuItem = {
|
|
13
|
+
type: "button" | "link";
|
|
14
|
+
icon: string;
|
|
15
|
+
title: string;
|
|
16
|
+
description: string;
|
|
17
|
+
href?: string;
|
|
18
|
+
target?: string;
|
|
19
|
+
rel?: string;
|
|
20
|
+
external?: boolean;
|
|
21
|
+
copyMarkdown?: boolean;
|
|
22
|
+
copyMcp?: string;
|
|
23
|
+
hasActionLabel?: boolean;
|
|
24
|
+
openApp?: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function getMarkdownPathForSlug(routeSlug: string): string {
|
|
28
|
+
const normalizedSlug = routeSlug.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
29
|
+
return normalizedSlug ? `/${normalizedSlug}.md` : "/index.md";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getPagePathForSlug(routeSlug: string): string {
|
|
33
|
+
const normalizedSlug = routeSlug.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
34
|
+
return normalizedSlug ? `/${normalizedSlug}` : "/";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getInstallNameFromUrl(url: string): string {
|
|
38
|
+
try {
|
|
39
|
+
const hostname = new URL(url).hostname;
|
|
40
|
+
const normalized = hostname
|
|
41
|
+
.toLowerCase()
|
|
42
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
43
|
+
.replace(/^-+|-+$/g, "")
|
|
44
|
+
.slice(0, 48);
|
|
45
|
+
|
|
46
|
+
return normalized || "docs";
|
|
47
|
+
} catch {
|
|
48
|
+
return "docs";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getCursorInstallNameFromUrl(url: string): string {
|
|
53
|
+
try {
|
|
54
|
+
return new URL(url).hostname.toLowerCase() || "docs";
|
|
55
|
+
} catch {
|
|
56
|
+
return "docs";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function toBase64(value: string): string {
|
|
61
|
+
return Buffer.from(value, "utf8").toString("base64");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function buildAssistantPrompt(pageUrl: string): string {
|
|
65
|
+
return `Read from ${pageUrl} so I can ask questions about it.`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const { routeSlug } = Astro.props;
|
|
69
|
+
const parsedOrgTier = Number.parseInt(
|
|
70
|
+
(import.meta.env.ORG_TIER ?? "1").toString(),
|
|
71
|
+
10,
|
|
72
|
+
);
|
|
73
|
+
const orgTier =
|
|
74
|
+
Number.isFinite(parsedOrgTier) && parsedOrgTier > 0 ? parsedOrgTier : 1;
|
|
75
|
+
const isDev = import.meta.env.DEV;
|
|
76
|
+
const mcpEnabled = isDev || orgTier >= 3;
|
|
77
|
+
const markdownPath = prependBasePath(getMarkdownPathForSlug(routeSlug));
|
|
78
|
+
const pagePath = prependBasePath(getPagePathForSlug(routeSlug));
|
|
79
|
+
const pageUrl = new URL(pagePath, Astro.site ?? Astro.url).toString();
|
|
80
|
+
const mcpPath = prependBasePath("/_mcp/server");
|
|
81
|
+
const mcpUrl = new URL(mcpPath, Astro.site ?? Astro.url).toString();
|
|
82
|
+
const installName = getInstallNameFromUrl(mcpUrl);
|
|
83
|
+
const cursorInstallName = getCursorInstallNameFromUrl(mcpUrl);
|
|
84
|
+
const cursorServerConfig = {
|
|
85
|
+
name: cursorInstallName,
|
|
86
|
+
url: mcpUrl,
|
|
87
|
+
};
|
|
88
|
+
const cursorInstallUrl = `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent(cursorInstallName)}&config=${encodeURIComponent(toBase64(JSON.stringify(cursorServerConfig)))}`;
|
|
89
|
+
const prompt = buildAssistantPrompt(pageUrl);
|
|
90
|
+
const chatGptUrl = `https://chatgpt.com/?q=${encodeURIComponent(prompt)}`;
|
|
91
|
+
const claudeUrl = `https://claude.ai/new?q=${encodeURIComponent(prompt)}`;
|
|
92
|
+
|
|
93
|
+
const menuItemClass =
|
|
94
|
+
"group cursor-pointer flex w-full items-center gap-2.5 rounded-lg p-1.5 text-left transition-colors hover:bg-neutral-100/80 focus-visible:outline focus-visible:outline-2 focus-visible:outline-neutral-900 dark:hover:bg-neutral-900/80 dark:focus-visible:outline-white";
|
|
95
|
+
const menuIconClass =
|
|
96
|
+
"inline-flex size-[32px] shrink-0 items-center justify-center rounded-sm border border-neutral-900/10 text-neutral-500 dark:border-white/6 dark:text-neutral-400";
|
|
97
|
+
const menuTitleClass =
|
|
98
|
+
"block text-sm font-medium leading-5 text-neutral-900 dark:text-white";
|
|
99
|
+
const menuDescriptionClass =
|
|
100
|
+
"block text-xs leading-4 text-neutral-500 dark:text-neutral-400";
|
|
101
|
+
|
|
102
|
+
const menuItems: MenuItem[] = [
|
|
103
|
+
{
|
|
104
|
+
type: "button",
|
|
105
|
+
icon: "lucide:copy",
|
|
106
|
+
title: "Copy page",
|
|
107
|
+
description: "Copy page as Markdown for LLMs",
|
|
108
|
+
copyMarkdown: true,
|
|
109
|
+
hasActionLabel: true,
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
type: "link",
|
|
113
|
+
icon: "simple-icons:markdown",
|
|
114
|
+
title: "View as Markdown",
|
|
115
|
+
description: "Open the Markdown version of this page",
|
|
116
|
+
href: markdownPath,
|
|
117
|
+
target: "_blank",
|
|
118
|
+
rel: "noopener noreferrer",
|
|
119
|
+
external: true,
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
type: "link",
|
|
123
|
+
icon: "simple-icons:openai",
|
|
124
|
+
title: "Open in ChatGPT",
|
|
125
|
+
description: "Ask questions about this page",
|
|
126
|
+
href: chatGptUrl,
|
|
127
|
+
target: "_blank",
|
|
128
|
+
rel: "noopener noreferrer",
|
|
129
|
+
external: true,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
type: "link",
|
|
133
|
+
icon: "simple-icons:claude",
|
|
134
|
+
title: "Open in Claude",
|
|
135
|
+
description: "Ask questions about this page",
|
|
136
|
+
href: claudeUrl,
|
|
137
|
+
target: "_blank",
|
|
138
|
+
rel: "noopener noreferrer",
|
|
139
|
+
external: true,
|
|
140
|
+
},
|
|
141
|
+
...(mcpEnabled
|
|
142
|
+
? [
|
|
143
|
+
{
|
|
144
|
+
type: "button",
|
|
145
|
+
icon: "lucide:terminal",
|
|
146
|
+
title: "Copy MCP install command",
|
|
147
|
+
description: "Copy command to install MCP server",
|
|
148
|
+
copyMcp: "install",
|
|
149
|
+
hasActionLabel: true,
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
type: "link",
|
|
153
|
+
icon: "simple-icons:cursor",
|
|
154
|
+
title: "Connect to Cursor",
|
|
155
|
+
description: "Install MCP Server on Cursor",
|
|
156
|
+
href: cursorInstallUrl,
|
|
157
|
+
openApp: "cursor",
|
|
158
|
+
external: true,
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
type: "button",
|
|
162
|
+
icon: "simple-icons:claudecode",
|
|
163
|
+
title: "Connect to Claude Code",
|
|
164
|
+
description: "Copy command for Claude Code",
|
|
165
|
+
copyMcp: "claudeCode",
|
|
166
|
+
hasActionLabel: true,
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
type: "button",
|
|
170
|
+
icon: "custom:codex",
|
|
171
|
+
title: "Connect to Codex",
|
|
172
|
+
description: "Copy command for Codex",
|
|
173
|
+
copyMcp: "codexCli",
|
|
174
|
+
hasActionLabel: true,
|
|
175
|
+
},
|
|
176
|
+
]
|
|
177
|
+
: []),
|
|
178
|
+
];
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
<nav
|
|
182
|
+
class="relative flex w-max shrink-0 justify-end"
|
|
183
|
+
data-rd-page-actions
|
|
184
|
+
data-markdown-url={markdownPath}
|
|
185
|
+
data-mcp-url={mcpUrl}
|
|
186
|
+
data-install-name={installName}
|
|
187
|
+
aria-label="Page AI actions"
|
|
188
|
+
>
|
|
189
|
+
<div class="relative inline-flex w-max shrink-0 flex-col items-end">
|
|
190
|
+
<div
|
|
191
|
+
class="inline-flex w-max shrink-0 overflow-hidden rounded-lg border-[0.5px] border-neutral-900/12 bg-neutral-50/50 text-sm text-neutral-700 shadow-[0_.5px_1px_rgba(0,0,0,0.15),0_5px_12px_-6px_rgba(0,0,0,0.08)] dark:border-white/6 dark:bg-(--rd-code-surface) dark:text-neutral-200 dark:shadow-[0_-.5px_1px_rgba(255,255,255,0.15),0_5px_12px_-6px_rgba(0,0,0,0.2)]"
|
|
192
|
+
>
|
|
193
|
+
<button
|
|
194
|
+
type="button"
|
|
195
|
+
class="cursor-pointer inline-flex h-8 shrink-0 items-center gap-1.5 whitespace-nowrap pl-2.5 pr-2 transition-colors hover:bg-white/80 hover:text-neutral-950 focus-visible:z-10 focus-visible:outline focus-visible:outline-2 focus-visible:outline-neutral-900 dark:hover:bg-white/6 dark:hover:text-white dark:focus-visible:outline-white"
|
|
196
|
+
data-rd-copy-markdown
|
|
197
|
+
>
|
|
198
|
+
<span
|
|
199
|
+
class="relative inline-flex size-3.5 shrink-0 items-center justify-center"
|
|
200
|
+
>
|
|
201
|
+
<Icon
|
|
202
|
+
name="lucide:copy"
|
|
203
|
+
class="size-3.5 origin-center scale-100 rotate-0 opacity-100 transition-all duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none"
|
|
204
|
+
data-rd-copy-icon
|
|
205
|
+
aria-hidden="true"
|
|
206
|
+
/>
|
|
207
|
+
<Icon
|
|
208
|
+
name="lucide:check"
|
|
209
|
+
class="absolute size-3.5 stroke-3 origin-center scale-50 rotate-6 opacity-0 text-green-700/80 transition-all duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none dark:text-green-400/90"
|
|
210
|
+
data-rd-copy-check
|
|
211
|
+
aria-hidden="true"
|
|
212
|
+
/>
|
|
213
|
+
<Icon
|
|
214
|
+
name="lucide:x"
|
|
215
|
+
class="absolute size-3.5 stroke-3 origin-center -rotate-6 scale-50 opacity-0 text-red-600/80 transition-all duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none dark:text-red-400/90"
|
|
216
|
+
data-rd-copy-error
|
|
217
|
+
aria-hidden="true"
|
|
218
|
+
/>
|
|
219
|
+
</span>
|
|
220
|
+
<span data-rd-action-label>Copy page</span>
|
|
221
|
+
</button>
|
|
222
|
+
|
|
223
|
+
<button
|
|
224
|
+
type="button"
|
|
225
|
+
class="cursor-pointer inline-flex h-8 w-8 pr-0.5 items-center justify-center border-l-[0.5px] border-neutral-900/12 transition-colors hover:bg-white/80 hover:text-neutral-950 focus-visible:z-10 focus-visible:outline focus-visible:outline-2 focus-visible:outline-neutral-900 dark:border-white/6 dark:hover:bg-white/6 dark:hover:text-white dark:focus-visible:outline-white"
|
|
226
|
+
data-rd-page-actions-trigger
|
|
227
|
+
aria-expanded="false"
|
|
228
|
+
aria-haspopup="true"
|
|
229
|
+
aria-label="More page actions"
|
|
230
|
+
>
|
|
231
|
+
<Icon
|
|
232
|
+
name="lucide:chevron-down"
|
|
233
|
+
class="size-3.5 transition-transform"
|
|
234
|
+
aria-hidden="true"
|
|
235
|
+
data-rd-page-actions-chevron
|
|
236
|
+
/>
|
|
237
|
+
</button>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<div
|
|
241
|
+
class="rd-page-actions-dropdown absolute left-0 right-auto top-full z-30 mt-1 w-max max-w-[calc(100vw-2rem)] rounded-xl border-[0.5px] border-neutral-200 bg-white p-1 text-neutral-700 shadow-2xl shadow-neutral-900/12 sm:left-auto sm:right-0 dark:border-neutral-800 dark:bg-(--rd-code-surface) dark:text-neutral-200 dark:shadow-black/40"
|
|
242
|
+
data-origin="responsive"
|
|
243
|
+
data-rd-page-actions-menu
|
|
244
|
+
hidden
|
|
245
|
+
>
|
|
246
|
+
{
|
|
247
|
+
menuItems.map((item) =>
|
|
248
|
+
item.type === "button" ? (
|
|
249
|
+
<button
|
|
250
|
+
type="button"
|
|
251
|
+
class={menuItemClass}
|
|
252
|
+
data-rd-copy-markdown={item.copyMarkdown ? true : undefined}
|
|
253
|
+
data-rd-copy-mcp={item.copyMcp}
|
|
254
|
+
>
|
|
255
|
+
<span class={menuIconClass}>
|
|
256
|
+
{item.copyMarkdown || item.copyMcp ? (
|
|
257
|
+
<span class="relative inline-flex size-4 items-center justify-center">
|
|
258
|
+
{item.icon === "custom:codex" ? (
|
|
259
|
+
<svg
|
|
260
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
261
|
+
viewBox="0 0 24 24"
|
|
262
|
+
fill="currentColor"
|
|
263
|
+
fill-rule="evenodd"
|
|
264
|
+
class="size-4 origin-center scale-100 rotate-0 opacity-100 transition-all duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none"
|
|
265
|
+
data-rd-copy-icon
|
|
266
|
+
aria-hidden="true"
|
|
267
|
+
>
|
|
268
|
+
<path
|
|
269
|
+
clip-rule="evenodd"
|
|
270
|
+
d="M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z"
|
|
271
|
+
/>
|
|
272
|
+
</svg>
|
|
273
|
+
) : (
|
|
274
|
+
<Icon
|
|
275
|
+
name={item.copyMarkdown ? "lucide:copy" : item.icon}
|
|
276
|
+
class="size-4 origin-center scale-100 rotate-0 opacity-100 transition-all duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none"
|
|
277
|
+
data-rd-copy-icon
|
|
278
|
+
aria-hidden="true"
|
|
279
|
+
/>
|
|
280
|
+
)}
|
|
281
|
+
<Icon
|
|
282
|
+
name="lucide:check"
|
|
283
|
+
class="absolute size-4 stroke-3 origin-center scale-50 rotate-6 opacity-0 text-green-700/80 transition-all duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none dark:text-green-400/90"
|
|
284
|
+
data-rd-copy-check
|
|
285
|
+
aria-hidden="true"
|
|
286
|
+
/>
|
|
287
|
+
<Icon
|
|
288
|
+
name="lucide:x"
|
|
289
|
+
class="absolute size-4 stroke-3 origin-center -rotate-6 scale-50 opacity-0 text-red-600/80 transition-all duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none dark:text-red-400/90"
|
|
290
|
+
data-rd-copy-error
|
|
291
|
+
aria-hidden="true"
|
|
292
|
+
/>
|
|
293
|
+
</span>
|
|
294
|
+
) : item.icon === "custom:codex" ? (
|
|
295
|
+
<svg
|
|
296
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
297
|
+
viewBox="0 0 24 24"
|
|
298
|
+
fill="currentColor"
|
|
299
|
+
fill-rule="evenodd"
|
|
300
|
+
class="size-4"
|
|
301
|
+
aria-hidden="true"
|
|
302
|
+
>
|
|
303
|
+
<path
|
|
304
|
+
clip-rule="evenodd"
|
|
305
|
+
d="M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z"
|
|
306
|
+
/>
|
|
307
|
+
</svg>
|
|
308
|
+
) : (
|
|
309
|
+
<Icon name={item.icon} class="size-4" aria-hidden="true" />
|
|
310
|
+
)}
|
|
311
|
+
</span>
|
|
312
|
+
<span class="min-w-0 pr-3">
|
|
313
|
+
<span
|
|
314
|
+
class={menuTitleClass}
|
|
315
|
+
data-rd-action-label={item.hasActionLabel ? true : undefined}
|
|
316
|
+
>
|
|
317
|
+
{item.title}
|
|
318
|
+
</span>
|
|
319
|
+
<span class={menuDescriptionClass}>{item.description}</span>
|
|
320
|
+
</span>
|
|
321
|
+
</button>
|
|
322
|
+
) : (
|
|
323
|
+
<a
|
|
324
|
+
href={item.href}
|
|
325
|
+
target={item.target}
|
|
326
|
+
rel={item.rel}
|
|
327
|
+
class={menuItemClass}
|
|
328
|
+
data-rd-open-app={item.openApp}
|
|
329
|
+
>
|
|
330
|
+
<span class={menuIconClass}>
|
|
331
|
+
{item.icon === "custom:codex" ? (
|
|
332
|
+
<svg
|
|
333
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
334
|
+
viewBox="0 0 24 24"
|
|
335
|
+
fill="currentColor"
|
|
336
|
+
fill-rule="evenodd"
|
|
337
|
+
class="size-4"
|
|
338
|
+
aria-hidden="true"
|
|
339
|
+
>
|
|
340
|
+
<path
|
|
341
|
+
clip-rule="evenodd"
|
|
342
|
+
d="M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z"
|
|
343
|
+
/>
|
|
344
|
+
</svg>
|
|
345
|
+
) : (
|
|
346
|
+
<Icon name={item.icon} class="size-4" aria-hidden="true" />
|
|
347
|
+
)}
|
|
348
|
+
</span>
|
|
349
|
+
<span class="min-w-0 pr-3">
|
|
350
|
+
<span class={menuTitleClass}>
|
|
351
|
+
{item.title}
|
|
352
|
+
{item.external && (
|
|
353
|
+
<Icon
|
|
354
|
+
name="lucide:arrow-up-right"
|
|
355
|
+
class="ml-[3px] mb-[3px] inline size-3 self-start"
|
|
356
|
+
aria-hidden="true"
|
|
357
|
+
/>
|
|
358
|
+
)}
|
|
359
|
+
</span>
|
|
360
|
+
<span class={menuDescriptionClass}>{item.description}</span>
|
|
361
|
+
</span>
|
|
362
|
+
</a>
|
|
363
|
+
),
|
|
364
|
+
)
|
|
365
|
+
}
|
|
366
|
+
</div>
|
|
367
|
+
</div>
|
|
368
|
+
</nav>
|
|
369
|
+
|
|
370
|
+
<style>
|
|
371
|
+
[data-rd-page-actions] {
|
|
372
|
+
--dropdown-open-dur: 250ms;
|
|
373
|
+
--dropdown-close-dur: 150ms;
|
|
374
|
+
--dropdown-pre-scale: 0.97;
|
|
375
|
+
--dropdown-closing-scale: 0.99;
|
|
376
|
+
--dropdown-ease: cubic-bezier(0.22, 1, 0.36, 1);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.rd-page-actions-dropdown {
|
|
380
|
+
transform-origin: top right;
|
|
381
|
+
transform: scale(var(--dropdown-pre-scale));
|
|
382
|
+
opacity: 0;
|
|
383
|
+
pointer-events: none;
|
|
384
|
+
transition:
|
|
385
|
+
transform var(--dropdown-open-dur) var(--dropdown-ease),
|
|
386
|
+
opacity var(--dropdown-open-dur) var(--dropdown-ease);
|
|
387
|
+
will-change: transform, opacity;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.rd-page-actions-dropdown[data-origin="top-left"] {
|
|
391
|
+
transform-origin: top left;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.rd-page-actions-dropdown[data-origin="top-center"] {
|
|
395
|
+
transform-origin: top center;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.rd-page-actions-dropdown[data-origin="top-right"] {
|
|
399
|
+
transform-origin: top right;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.rd-page-actions-dropdown[data-origin="responsive"] {
|
|
403
|
+
transform-origin: top left;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
@media (min-width: 640px) {
|
|
407
|
+
.rd-page-actions-dropdown[data-origin="responsive"] {
|
|
408
|
+
transform-origin: top right;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.rd-page-actions-dropdown.is-open {
|
|
413
|
+
transform: scale(1);
|
|
414
|
+
opacity: 1;
|
|
415
|
+
pointer-events: auto;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.rd-page-actions-dropdown.is-closing {
|
|
419
|
+
transform: scale(var(--dropdown-closing-scale));
|
|
420
|
+
opacity: 0;
|
|
421
|
+
pointer-events: none;
|
|
422
|
+
transition:
|
|
423
|
+
transform var(--dropdown-close-dur) var(--dropdown-ease),
|
|
424
|
+
opacity var(--dropdown-close-dur) var(--dropdown-ease);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
@media (prefers-reduced-motion: reduce) {
|
|
428
|
+
.rd-page-actions-dropdown {
|
|
429
|
+
transition: none !important;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
</style>
|
|
433
|
+
|
|
434
|
+
<script is:inline data-astro-rerun>
|
|
435
|
+
(() => {
|
|
436
|
+
const script = document.currentScript;
|
|
437
|
+
let root = script?.previousElementSibling;
|
|
438
|
+
while (
|
|
439
|
+
root &&
|
|
440
|
+
(!(root instanceof HTMLElement) ||
|
|
441
|
+
!root.hasAttribute("data-rd-page-actions"))
|
|
442
|
+
) {
|
|
443
|
+
root = root.previousElementSibling;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (
|
|
447
|
+
!(root instanceof HTMLElement) ||
|
|
448
|
+
root.dataset.pageActionsBound === "true"
|
|
449
|
+
) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
root.dataset.pageActionsBound = "true";
|
|
453
|
+
|
|
454
|
+
const trigger = root.querySelector("[data-rd-page-actions-trigger]");
|
|
455
|
+
const menu = root.querySelector("[data-rd-page-actions-menu]");
|
|
456
|
+
const chevron = root.querySelector("[data-rd-page-actions-chevron]");
|
|
457
|
+
const markdownUrl = root.dataset.markdownUrl || "/index.md";
|
|
458
|
+
const mcpUrl = root.dataset.mcpUrl || "/_mcp/server";
|
|
459
|
+
const installName = root.dataset.installName || "docs";
|
|
460
|
+
const closeDurationMs = 150;
|
|
461
|
+
const copyFeedbackDurationMs = 1400;
|
|
462
|
+
let closeTimeout;
|
|
463
|
+
let mcpMetadataPromise;
|
|
464
|
+
const copyFeedbackTimeouts = new WeakMap();
|
|
465
|
+
|
|
466
|
+
function fallbackCopy(text) {
|
|
467
|
+
const textarea = document.createElement("textarea");
|
|
468
|
+
textarea.value = text;
|
|
469
|
+
textarea.setAttribute("readonly", "");
|
|
470
|
+
textarea.style.position = "fixed";
|
|
471
|
+
textarea.style.opacity = "0";
|
|
472
|
+
document.body.appendChild(textarea);
|
|
473
|
+
textarea.select();
|
|
474
|
+
const copied = document.execCommand("copy");
|
|
475
|
+
document.body.removeChild(textarea);
|
|
476
|
+
return copied;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async function copyToClipboard(text) {
|
|
480
|
+
try {
|
|
481
|
+
if (navigator.clipboard?.writeText) {
|
|
482
|
+
await navigator.clipboard.writeText(text);
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
} catch {
|
|
486
|
+
// Fallback below when clipboard API is unavailable or blocked.
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return fallbackCopy(text);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function setExpanded(nextExpanded) {
|
|
493
|
+
if (!(trigger instanceof HTMLElement)) return;
|
|
494
|
+
trigger.setAttribute("aria-expanded", String(nextExpanded));
|
|
495
|
+
if (chevron instanceof HTMLElement) {
|
|
496
|
+
chevron.classList.toggle("rotate-180", nextExpanded);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function openMenu() {
|
|
501
|
+
if (!(menu instanceof HTMLElement)) return;
|
|
502
|
+
window.clearTimeout(closeTimeout);
|
|
503
|
+
menu.hidden = false;
|
|
504
|
+
menu.classList.remove("is-closing");
|
|
505
|
+
menu.classList.add("is-open");
|
|
506
|
+
setExpanded(true);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function closeMenu() {
|
|
510
|
+
if (!(menu instanceof HTMLElement) || menu.hidden) return;
|
|
511
|
+
window.clearTimeout(closeTimeout);
|
|
512
|
+
menu.classList.remove("is-open");
|
|
513
|
+
menu.classList.add("is-closing");
|
|
514
|
+
setExpanded(false);
|
|
515
|
+
closeTimeout = window.setTimeout(() => {
|
|
516
|
+
menu.classList.remove("is-closing");
|
|
517
|
+
menu.hidden = true;
|
|
518
|
+
}, closeDurationMs);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function toggleMenu() {
|
|
522
|
+
if (!(menu instanceof HTMLElement)) return;
|
|
523
|
+
if (menu.hidden || !menu.classList.contains("is-open")) {
|
|
524
|
+
openMenu();
|
|
525
|
+
} else {
|
|
526
|
+
closeMenu();
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function setLabelFeedback(button, copiedText = "Copied") {
|
|
531
|
+
const label = button.querySelector("[data-rd-action-label]");
|
|
532
|
+
if (!(label instanceof HTMLElement)) return;
|
|
533
|
+
|
|
534
|
+
const defaultText = label.dataset.defaultLabel || label.textContent || "";
|
|
535
|
+
label.dataset.defaultLabel = defaultText;
|
|
536
|
+
label.textContent = copiedText;
|
|
537
|
+
window.setTimeout(() => {
|
|
538
|
+
label.textContent = label.dataset.defaultLabel || defaultText;
|
|
539
|
+
}, copyFeedbackDurationMs);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function setCopyIconState(button, state) {
|
|
543
|
+
const copyIcon = button.querySelector("[data-rd-copy-icon]");
|
|
544
|
+
const checkIcon = button.querySelector("[data-rd-copy-check]");
|
|
545
|
+
const errorIcon = button.querySelector("[data-rd-copy-error]");
|
|
546
|
+
|
|
547
|
+
if (!copyIcon || !checkIcon || !errorIcon) return false;
|
|
548
|
+
|
|
549
|
+
const showCopy = () => {
|
|
550
|
+
copyIcon.classList.remove("scale-50", "opacity-0", "-rotate-6");
|
|
551
|
+
copyIcon.classList.add("scale-100", "opacity-100", "rotate-0");
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
const hideCopy = () => {
|
|
555
|
+
copyIcon.classList.add("scale-50", "opacity-0", "-rotate-6");
|
|
556
|
+
copyIcon.classList.remove("scale-100", "opacity-100", "rotate-0");
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
const showStatusIcon = (icon) => {
|
|
560
|
+
icon.classList.remove("scale-50", "opacity-0", "rotate-6", "-rotate-6");
|
|
561
|
+
icon.classList.add("scale-110", "opacity-100", "rotate-0");
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
const hideStatusIcon = (icon, rotationClass) => {
|
|
565
|
+
icon.classList.remove("scale-110", "opacity-100", "rotate-0");
|
|
566
|
+
icon.classList.add("scale-50", "opacity-0", rotationClass);
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
if (state === "success") {
|
|
570
|
+
hideCopy();
|
|
571
|
+
showStatusIcon(checkIcon);
|
|
572
|
+
hideStatusIcon(errorIcon, "-rotate-6");
|
|
573
|
+
return true;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (state === "error") {
|
|
577
|
+
hideCopy();
|
|
578
|
+
hideStatusIcon(checkIcon, "rotate-6");
|
|
579
|
+
showStatusIcon(errorIcon);
|
|
580
|
+
return true;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
showCopy();
|
|
584
|
+
hideStatusIcon(checkIcon, "rotate-6");
|
|
585
|
+
hideStatusIcon(errorIcon, "-rotate-6");
|
|
586
|
+
return true;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function setCopyFeedback(button, state, fallbackText) {
|
|
590
|
+
if (setCopyIconState(button, state)) {
|
|
591
|
+
const existingTimeout = copyFeedbackTimeouts.get(button);
|
|
592
|
+
if (existingTimeout) window.clearTimeout(existingTimeout);
|
|
593
|
+
|
|
594
|
+
const timeout = window.setTimeout(() => {
|
|
595
|
+
setCopyIconState(button, "idle");
|
|
596
|
+
copyFeedbackTimeouts.delete(button);
|
|
597
|
+
}, copyFeedbackDurationMs);
|
|
598
|
+
copyFeedbackTimeouts.set(button, timeout);
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
setLabelFeedback(
|
|
603
|
+
button,
|
|
604
|
+
fallbackText || (state === "error" ? "Failed" : "Copied"),
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function getTomlServerKey() {
|
|
609
|
+
return (
|
|
610
|
+
installName.replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") ||
|
|
611
|
+
"docs"
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function buildFallbackMcpMetadata() {
|
|
616
|
+
return {
|
|
617
|
+
usage: {
|
|
618
|
+
install: `npx add-mcp ${mcpUrl}`,
|
|
619
|
+
claudeCode: `claude mcp add --transport http ${installName} ${mcpUrl}`,
|
|
620
|
+
codexCli: `codex mcp add ${installName} --url ${mcpUrl}`,
|
|
621
|
+
cursor: `Add "${mcpUrl}" to your MCP server configuration`,
|
|
622
|
+
codex: `[mcp_servers.${getTomlServerKey()}]\nurl = "${mcpUrl}"`,
|
|
623
|
+
},
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
async function getMcpMetadata() {
|
|
628
|
+
if (!mcpMetadataPromise) {
|
|
629
|
+
mcpMetadataPromise = fetch(mcpUrl, {
|
|
630
|
+
headers: { Accept: "application/json" },
|
|
631
|
+
})
|
|
632
|
+
.then((response) => {
|
|
633
|
+
if (!response.ok) throw new Error("MCP metadata request failed");
|
|
634
|
+
return response.json();
|
|
635
|
+
})
|
|
636
|
+
.catch(() => buildFallbackMcpMetadata());
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
return mcpMetadataPromise;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
async function copyMarkdown(button) {
|
|
643
|
+
const response = await fetch(markdownUrl, {
|
|
644
|
+
headers: { Accept: "text/markdown,text/plain;q=0.9,*/*;q=0.1" },
|
|
645
|
+
});
|
|
646
|
+
if (!response.ok) {
|
|
647
|
+
throw new Error("Markdown request failed");
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const text = await response.text();
|
|
651
|
+
const copied = await copyToClipboard(text);
|
|
652
|
+
if (!copied) throw new Error("Clipboard write failed");
|
|
653
|
+
setCopyFeedback(button, "success");
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
async function copyMcpUsage(button) {
|
|
657
|
+
const key = button.getAttribute("data-rd-copy-mcp");
|
|
658
|
+
const metadata = await getMcpMetadata();
|
|
659
|
+
const usage = metadata?.usage || buildFallbackMcpMetadata().usage;
|
|
660
|
+
const fallbackUsage = buildFallbackMcpMetadata().usage;
|
|
661
|
+
const usageKey = key && key in fallbackUsage ? key : "cursor";
|
|
662
|
+
const value = usage[usageKey] || fallbackUsage[usageKey] || mcpUrl;
|
|
663
|
+
|
|
664
|
+
const copied = await copyToClipboard(value || mcpUrl);
|
|
665
|
+
if (!copied) throw new Error("Clipboard write failed");
|
|
666
|
+
setCopyFeedback(button, "success");
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
trigger?.addEventListener("click", (event) => {
|
|
670
|
+
event.stopPropagation();
|
|
671
|
+
toggleMenu();
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
menu?.addEventListener("click", (event) => {
|
|
675
|
+
event.stopPropagation();
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
document.addEventListener("click", (event) => {
|
|
679
|
+
if (!root.contains(event.target)) closeMenu();
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
document.addEventListener("keydown", (event) => {
|
|
683
|
+
if (event.key === "Escape") closeMenu();
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
root.querySelectorAll("[data-rd-copy-markdown]").forEach((button) => {
|
|
687
|
+
if (!(button instanceof HTMLButtonElement)) return;
|
|
688
|
+
button.addEventListener("click", async () => {
|
|
689
|
+
try {
|
|
690
|
+
await copyMarkdown(button);
|
|
691
|
+
} catch {
|
|
692
|
+
setCopyFeedback(button, "error", "Failed");
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
root.querySelectorAll("[data-rd-copy-mcp]").forEach((button) => {
|
|
698
|
+
if (!(button instanceof HTMLButtonElement)) return;
|
|
699
|
+
button.addEventListener("click", async () => {
|
|
700
|
+
try {
|
|
701
|
+
await copyMcpUsage(button);
|
|
702
|
+
} catch {
|
|
703
|
+
setCopyFeedback(button, "error", "Failed");
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
root.querySelectorAll("[data-rd-open-app]").forEach((link) => {
|
|
709
|
+
if (!(link instanceof HTMLAnchorElement)) return;
|
|
710
|
+
link.addEventListener("click", () => {
|
|
711
|
+
closeMenu();
|
|
712
|
+
});
|
|
713
|
+
});
|
|
714
|
+
})();
|
|
715
|
+
</script>
|