fix(frontend): handle clipboard fallback and markdown word export
This commit is contained in:
1 parent
195ef3ee40
commit
db2f21191c
6 files changed
+66
-17
No files matched your search
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { copyTextToClipboard } from "@/core/clipboard";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { CheckIcon, CopyIcon } from "lucide-react";
|
||||
import {
|
||||
@@ -147,13 +148,8 @@ export const CodeBlockCopyButton = ({
|
||||
const { code } = useContext(CodeBlockContext);
|
||||
|
||||
const copyToClipboard = async () => {
|
||||
if (typeof window === "undefined" || !navigator?.clipboard?.writeText) {
|
||||
onError?.(new Error("Clipboard API not available"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(code);
|
||||
await copyTextToClipboard(code);
|
||||
setIsCopied(true);
|
||||
onCopy?.();
|
||||
setTimeout(() => setIsCopied(false), timeout);
|
||||
|
||||
@@ -40,6 +40,7 @@ import { CodeEditor } from "@/components/workspace/code-editor";
|
||||
import { publishArtifactToKnowledge } from "@/core/agents/api";
|
||||
import { useAgent } from "@/core/agents/hooks";
|
||||
import { apiFetch } from "@/core/api";
|
||||
import { copyTextToClipboard } from "@/core/clipboard";
|
||||
import {
|
||||
downloadArtifactFile,
|
||||
} from "@/core/artifacts/download";
|
||||
@@ -682,7 +683,7 @@ export function ArtifactFileDetail({
|
||||
disabled={!content}
|
||||
onClick={async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(displayContent ?? "");
|
||||
await copyTextToClipboard(displayContent ?? "");
|
||||
toast.success(t.clipboard.copiedToClipboard);
|
||||
} catch (error) {
|
||||
toast.error("Failed to copy to clipboard");
|
||||
@@ -692,7 +693,7 @@ export function ArtifactFileDetail({
|
||||
tooltip={t.clipboard.copyToClipboard}
|
||||
/>
|
||||
)}
|
||||
{!useWriteFileStreaming && language === "markdown" && (
|
||||
{language === "markdown" && (
|
||||
<ArtifactAction
|
||||
icon={isExportingWord ? LoaderIcon : FileType2Icon}
|
||||
label="另存为 Word"
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { CheckIcon, CopyIcon } from "lucide-react";
|
||||
import { useCallback, useState, type ComponentProps } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { copyTextToClipboard } from "@/core/clipboard";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
|
||||
import { Tooltip } from "./tooltip";
|
||||
@@ -14,11 +16,15 @@ export function CopyButton({
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const [copied, setCopied] = useState(false);
|
||||
const handleCopy = useCallback(() => {
|
||||
void navigator.clipboard.writeText(clipboardData);
|
||||
const handleCopy = useCallback(async () => {
|
||||
try {
|
||||
await copyTextToClipboard(clipboardData);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
}, [clipboardData]);
|
||||
} catch {
|
||||
toast.error(t.clipboard.failedToCopyToClipboard);
|
||||
}
|
||||
}, [clipboardData, t.clipboard.failedToCopyToClipboard]);
|
||||
return (
|
||||
<Tooltip content={t.clipboard.copyToClipboard}>
|
||||
<Button
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
export async function copyTextToClipboard(text: string): Promise<void> {
|
||||
let clipboardError: unknown;
|
||||
|
||||
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
return;
|
||||
} catch (error) {
|
||||
clipboardError = error;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof document === "undefined" || !document.body) {
|
||||
throw clipboardError instanceof Error
|
||||
? clipboardError
|
||||
: new Error("Clipboard is not available");
|
||||
}
|
||||
|
||||
const textarea = document.createElement("textarea");
|
||||
textarea.value = text;
|
||||
textarea.setAttribute("readonly", "");
|
||||
textarea.style.position = "fixed";
|
||||
textarea.style.top = "0";
|
||||
textarea.style.left = "0";
|
||||
textarea.style.width = "1px";
|
||||
textarea.style.height = "1px";
|
||||
textarea.style.opacity = "0";
|
||||
|
||||
document.body.appendChild(textarea);
|
||||
textarea.focus();
|
||||
textarea.select();
|
||||
textarea.setSelectionRange(0, textarea.value.length);
|
||||
|
||||
try {
|
||||
const copied = document.execCommand("copy");
|
||||
if (!copied) {
|
||||
throw clipboardError instanceof Error
|
||||
? clipboardError
|
||||
: new Error("Copy command failed");
|
||||
}
|
||||
} finally {
|
||||
textarea.remove();
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import sql from 'react-syntax-highlighter/dist/esm/languages/prism/sql';
|
||||
import tsx from 'react-syntax-highlighter/dist/esm/languages/prism/tsx';
|
||||
import typescript from 'react-syntax-highlighter/dist/esm/languages/prism/typescript';
|
||||
import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||
import { copyTextToClipboard } from '@/core/clipboard';
|
||||
|
||||
export interface WikiLinkTarget {
|
||||
target: string;
|
||||
@@ -101,11 +102,11 @@ const CodeBlock: React.FC<{ children: React.ReactNode; className?: string }> = (
|
||||
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(codeText);
|
||||
await copyTextToClipboard(codeText);
|
||||
setCopied(true);
|
||||
window.setTimeout(() => setCopied(false), 1500);
|
||||
} catch (error) {
|
||||
console.error('复制代码失败:', error);
|
||||
} catch {
|
||||
// The shared clipboard helper already tries the browser fallback.
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
} from './GeneralQA.mock';
|
||||
import { requestCrudService } from '@/service/request-axios-service';
|
||||
import { authStorageGet } from '@/core/auth/scoped-storage';
|
||||
import { copyTextToClipboard } from '@/core/clipboard';
|
||||
|
||||
/**
|
||||
* 通用问答组件
|
||||
@@ -1763,9 +1764,9 @@ const GeneralQA: React.FC<{
|
||||
const handleCopyAssistantMessage = async (message: Message) => {
|
||||
const answerText = getAssistantAnswerText(message);
|
||||
try {
|
||||
await navigator.clipboard.writeText(answerText);
|
||||
} catch (copyError) {
|
||||
console.error('复制模型回答失败:', copyError);
|
||||
await copyTextToClipboard(answerText);
|
||||
} catch {
|
||||
// Keep the chat surface quiet when the browser blocks clipboard access.
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user