Merge branch 'main' into fix/chatbox-scroll-menu-cd4e390d
This commit is contained in:
commit
0fd8c4645b
10 changed files with 26 additions and 53 deletions
|
|
@ -272,8 +272,8 @@ function MermaidCopyButton({ source }: { source: string }) {
|
|||
type="button"
|
||||
className="absolute top-3.5 right-20 z-20 cursor-pointer text-muted-foreground transition-all hover:text-foreground"
|
||||
title="Copy Mermaid source"
|
||||
onClick={() => {
|
||||
if (!copyToClipboard(source)) {
|
||||
onClick={async () => {
|
||||
if (!(await copyToClipboard(source))) {
|
||||
return;
|
||||
}
|
||||
showCopied();
|
||||
|
|
@ -306,8 +306,8 @@ function CodeBlockActions({
|
|||
className={ACTION_BUTTON_CLASS}
|
||||
title="Copy code"
|
||||
disabled={disabled}
|
||||
onClick={() => {
|
||||
if (!copyToClipboard(source)) {
|
||||
onClick={async () => {
|
||||
if (!(await copyToClipboard(source))) {
|
||||
return;
|
||||
}
|
||||
showCopied();
|
||||
|
|
|
|||
|
|
@ -322,8 +322,8 @@ function ReasoningCopyButton({ startIndex, endIndex }: { startIndex: number; end
|
|||
.join("\n");
|
||||
});
|
||||
|
||||
const handleCopy = useCallback(() => {
|
||||
if (copyToClipboard(reasoningText)) {
|
||||
const handleCopy = useCallback(async () => {
|
||||
if (await copyToClipboard(reasoningText)) {
|
||||
setCopied(true);
|
||||
if (resetRef.current) clearTimeout(resetRef.current);
|
||||
resetRef.current = setTimeout(() => setCopied(false), COPY_RESET_MS);
|
||||
|
|
|
|||
|
|
@ -701,9 +701,9 @@ const CopyButton: FC = () => {
|
|||
const [copied, setCopied] = useState(false);
|
||||
const resetTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const handleCopy = () => {
|
||||
const handleCopy = async () => {
|
||||
const text = aui.message().getCopyText();
|
||||
if (copyToClipboard(text)) {
|
||||
if (await copyToClipboard(text)) {
|
||||
setCopied(true);
|
||||
if (resetTimeoutRef.current) clearTimeout(resetTimeoutRef.current);
|
||||
resetTimeoutRef.current = setTimeout(() => {
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ function CopyBtn({ text }: { text: string }) {
|
|||
};
|
||||
}, []);
|
||||
|
||||
const copy = useCallback(() => {
|
||||
if (copyToClipboard(text)) {
|
||||
const copy = useCallback(async () => {
|
||||
if (await copyToClipboard(text)) {
|
||||
setCopied(true);
|
||||
if (timer.current) {
|
||||
clearTimeout(timer.current);
|
||||
|
|
|
|||
|
|
@ -34,8 +34,8 @@ function CopyBtn({ text }: { text: string }) {
|
|||
};
|
||||
}, []);
|
||||
|
||||
const copy = useCallback(() => {
|
||||
if (copyToClipboard(text)) {
|
||||
const copy = useCallback(async () => {
|
||||
if (await copyToClipboard(text)) {
|
||||
setCopied(true);
|
||||
if (timer.current) {
|
||||
clearTimeout(timer.current);
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ export function ApiKeyRow({
|
|||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => copyToClipboard(prefix)}>
|
||||
<DropdownMenuItem onClick={async () => { await copyToClipboard(prefix); }}>
|
||||
<HugeiconsIcon icon={Copy01Icon} className="size-3.5 mr-2" />
|
||||
Copy prefix
|
||||
</DropdownMenuItem>
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ export function KeyRevealCard({
|
|||
}) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = () => {
|
||||
if (copyToClipboard(rawKey)) {
|
||||
const handleCopy = async () => {
|
||||
if (await copyToClipboard(rawKey)) {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 1800);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,8 +39,8 @@ function CopyableCommand({
|
|||
};
|
||||
}, []);
|
||||
|
||||
const handleCopy = () => {
|
||||
if (!copyToClipboard(command)) {
|
||||
const handleCopy = async () => {
|
||||
if (!(await copyToClipboard(command))) {
|
||||
return;
|
||||
}
|
||||
setCopied(true);
|
||||
|
|
|
|||
|
|
@ -67,8 +67,8 @@ export function UsageExamples() {
|
|||
[],
|
||||
);
|
||||
|
||||
const handleCopy = () => {
|
||||
if (copyToClipboard(snippets[lang])) {
|
||||
const handleCopy = async () => {
|
||||
if (await copyToClipboard(snippets[lang])) {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 1800);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,50 +32,23 @@ function copyWithExecCommand(text: string): boolean {
|
|||
}
|
||||
}
|
||||
|
||||
export function copyToClipboard(text: string): boolean {
|
||||
export async function copyToClipboard(text: string): Promise<boolean> {
|
||||
if (typeof text !== "string" || text.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof document !== "undefined" && document.queryCommandSupported?.("copy") !== false) {
|
||||
if (copyWithExecCommand(text)) return true;
|
||||
}
|
||||
|
||||
// Async fallback for environments where execCommand is entirely unsupported
|
||||
// but the Clipboard API is available (rare; kept for original contract parity).
|
||||
if (typeof navigator?.clipboard?.writeText === "function") {
|
||||
navigator.clipboard.writeText(text).then(
|
||||
() => {},
|
||||
() => {},
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function copyToClipboardAsync(text: string): Promise<boolean> {
|
||||
if (typeof text !== "string" || text.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prefer the async Clipboard API: avoids focus disruption in Radix
|
||||
// focus-trapped dialogs where execCommand always fails.
|
||||
// Primary: async Clipboard API
|
||||
if (typeof navigator?.clipboard?.writeText === "function") {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
return true;
|
||||
} catch {
|
||||
// Clipboard API rejected (e.g. NotAllowedError, permission policy).
|
||||
// User activation is still valid through promise chains per spec, so
|
||||
// execCommand can succeed for callers outside focus-trapped dialogs.
|
||||
// Inside a Radix modal the focus trap will block textarea.focus() and
|
||||
// execCommand returns false harmlessly.
|
||||
return copyWithExecCommand(text);
|
||||
} catch (error) {
|
||||
console.warn("Async clipboard API failed, falling back to execCommand", error);
|
||||
// Clipboard API rejected (NotAllowedError, insecure context, etc.)
|
||||
// Fall through to execCommand fallback.
|
||||
}
|
||||
}
|
||||
|
||||
// No Clipboard API (older browser / non-secure context): still in the
|
||||
// original user-gesture frame, so execCommand can work.
|
||||
// Fallback: execCommand (works in Safari when called during user gesture)
|
||||
return copyWithExecCommand(text);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue