fix(webui): restore code block copy fallback

This commit is contained in:
chengyongru
2026-06-25 16:10:23 +08:00
committed by Xubin Ren
parent 943191f0c0
commit 123384975e
2 changed files with 33 additions and 3 deletions
+3 -2
View File
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
import { useThemeValue } from "@/hooks/useTheme";
import { hasAnsi, parseAnsiSegments, stripAnsi } from "@/lib/ansi";
import { copyTextToClipboard } from "@/lib/clipboard";
import { cn } from "@/lib/utils";
interface CodeBlockProps {
@@ -192,8 +193,8 @@ export function CodeBlock({
const renderAnsi = shouldRenderAnsi(language, code);
const onCopy = useCallback(() => {
if (!navigator.clipboard) return;
navigator.clipboard.writeText(renderAnsi ? stripAnsi(code) : code).then(() => {
void copyTextToClipboard(renderAnsi ? stripAnsi(code) : code).then((ok) => {
if (!ok) return;
setCopied(true);
setTimeout(() => setCopied(false), 1_500);
});
+30 -1
View File
@@ -1,4 +1,4 @@
import { act, render, screen } from "@testing-library/react";
import { act, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
@@ -146,6 +146,35 @@ describe("CodeBlock", () => {
}
});
it("copies with the textarea fallback when Clipboard API is unavailable", async () => {
const user = userEvent.setup();
Object.defineProperty(navigator, "clipboard", {
configurable: true,
value: undefined,
});
const execCommand = vi.fn().mockReturnValue(true);
Object.defineProperty(document, "execCommand", {
configurable: true,
value: execCommand,
});
try {
render(
<ThemeProvider theme="dark">
<CodeBlock language="ts" code="const value = 1;" highlight={false} />
</ThemeProvider>,
);
await user.click(screen.getByRole("button", { name: /copy/i }));
await waitFor(() => expect(execCommand).toHaveBeenCalledWith("copy"));
expect(screen.getByText("Copied")).toBeInTheDocument();
} finally {
Reflect.deleteProperty(navigator, "clipboard");
Reflect.deleteProperty(document, "execCommand");
}
});
it("reads theme from context without creating per-block observers", async () => {
const originalMutationObserver = globalThis.MutationObserver;
const observer = vi.fn();