fix(docs): replace broken page copy buttons with working single button

The three page-level buttons (Copy MD, View MD, Copy MDX) were broken
because the fetch URL missed the /ktx basePath. Replace with a single
"Copy as Markdown" button that strips frontmatter from the MDX source
already available client-side — no fetch needed. Drop the .md link
since agents discover markdown URLs through llms.txt and content
negotiation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Luca Martial 2026-05-18 15:58:07 +02:00
parent e64da5a85d
commit 63e2773e0a
2 changed files with 25 additions and 100 deletions

View file

@ -51,10 +51,7 @@ export default async function Page(props: {
<>
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between sm:gap-4">
<DocsTitle>{page.data.title}</DocsTitle>
<DocsPageActions
markdownUrl={`${page.url}.md`}
mdxSource={mdxSource}
/>
<DocsPageActions mdxSource={mdxSource} />
</div>
<DocsDescription className="wrap-anywhere">
{page.data.description}

View file

@ -2,109 +2,37 @@
import { useState } from "react";
type CopyState = "idle" | "copied" | "error";
type Props = {
markdownUrl: string;
mdxSource: string;
};
export function DocsPageActions({ markdownUrl, mdxSource }: Props) {
function stripFrontmatter(source: string) {
return source.trim().replace(/^---\n[\s\S]*?\n---\n?/, "").trim();
}
export function DocsPageActions({ mdxSource }: Props) {
const [copied, setCopied] = useState(false);
const onCopy = async () => {
try {
await navigator.clipboard.writeText(stripFrontmatter(mdxSource));
setCopied(true);
setTimeout(() => setCopied(false), 1500);
} catch {
// Clipboard denied — fail silently
}
};
return (
<div className="not-prose flex flex-wrap items-center gap-2 text-xs">
<CopyMarkdownButton markdownUrl={markdownUrl} />
<a
href={markdownUrl}
className="inline-flex h-8 items-center rounded-md border border-fd-border bg-fd-background px-3 font-medium text-fd-muted-foreground transition-colors hover:border-fd-primary/40 hover:text-fd-foreground"
<button
type="button"
onClick={onCopy}
className="inline-flex h-8 items-center rounded-md border border-fd-border bg-fd-background px-3 font-medium text-fd-muted-foreground transition-colors hover:border-fd-primary/40 hover:text-fd-foreground data-[state=copied]:border-emerald-500/40 data-[state=copied]:text-emerald-600"
data-state={copied ? "copied" : "idle"}
>
View MD
</a>
<CopyTextButton label="Copy MDX" text={mdxSource} />
{copied ? "Copied" : "Copy as Markdown"}
</button>
</div>
);
}
function CopyMarkdownButton({ markdownUrl }: { markdownUrl: string }) {
const [state, setState] = useState<CopyState>("idle");
const onClick = async () => {
try {
const response = await fetch(markdownUrl, {
headers: { Accept: "text/markdown" },
});
if (!response.ok) {
throw new Error(`Failed to fetch ${markdownUrl}`);
}
await navigator.clipboard.writeText(await response.text());
flash(setState, "copied");
} catch {
flash(setState, "error");
}
};
return (
<ActionButton
label={labelForState(state, "Copy MD")}
onClick={onClick}
state={state}
/>
);
}
function CopyTextButton({ label, text }: { label: string; text: string }) {
const [state, setState] = useState<CopyState>("idle");
const onClick = async () => {
try {
await navigator.clipboard.writeText(text);
flash(setState, "copied");
} catch {
flash(setState, "error");
}
};
return (
<ActionButton
label={labelForState(state, label)}
onClick={onClick}
state={state}
/>
);
}
function ActionButton({
label,
onClick,
state,
}: {
label: string;
onClick: () => void;
state: CopyState;
}) {
return (
<button
type="button"
onClick={onClick}
className="inline-flex h-8 items-center rounded-md border border-fd-border bg-fd-background px-3 font-medium text-fd-muted-foreground transition-colors hover:border-fd-primary/40 hover:text-fd-foreground data-[state=copied]:border-emerald-500/40 data-[state=copied]:text-emerald-600 data-[state=error]:border-red-500/40 data-[state=error]:text-red-600"
data-state={state}
>
{label}
</button>
);
}
function labelForState(state: CopyState, label: string) {
if (state === "copied") return "Copied";
if (state === "error") return "Copy failed";
return label;
}
function flash(
setState: (state: CopyState) => void,
state: Exclude<CopyState, "idle">,
) {
setState(state);
window.setTimeout(() => setState("idle"), 1500);
}