refactor: unify clipboard copy flow across desktop/web/vscode runtimes (#458)
* fix: prevent copying incomplete diagnostics report * refactoring: clipboard writes to unified cross-runtime fallback helper
This commit is contained in:
committed by
GitHub
parent
881e8bdf4f
commit
47cecfc356
@@ -27,6 +27,7 @@ import {
|
||||
RiFileCopyLine,
|
||||
} from '@remixicon/react';
|
||||
import { toast } from '@/components/ui';
|
||||
import { copyTextToClipboard } from '@/lib/clipboard';
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -447,8 +448,13 @@ const FileRow: React.FC<FileRowProps> = ({
|
||||
)}
|
||||
<DropdownMenuItem onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
void navigator.clipboard.writeText(node.path);
|
||||
toast.success('Path copied');
|
||||
void copyTextToClipboard(node.path).then((result) => {
|
||||
if (result.ok) {
|
||||
toast.success('Path copied');
|
||||
return;
|
||||
}
|
||||
toast.error('Copy failed');
|
||||
});
|
||||
}}>
|
||||
<RiFileCopyLine className="mr-2 h-4 w-4" /> Copy Path
|
||||
</DropdownMenuItem>
|
||||
@@ -2049,8 +2055,8 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(fileContent);
|
||||
const result = await copyTextToClipboard(fileContent);
|
||||
if (result.ok) {
|
||||
setCopiedContent(true);
|
||||
if (copiedContentTimeoutRef.current !== null) {
|
||||
window.clearTimeout(copiedContentTimeoutRef.current);
|
||||
@@ -2058,7 +2064,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
copiedContentTimeoutRef.current = window.setTimeout(() => {
|
||||
setCopiedContent(false);
|
||||
}, 1200);
|
||||
} catch {
|
||||
} else {
|
||||
toast.error('Copy failed');
|
||||
}
|
||||
}}
|
||||
@@ -2079,8 +2085,8 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(displaySelectedPath);
|
||||
const result = await copyTextToClipboard(displaySelectedPath);
|
||||
if (result.ok) {
|
||||
setCopiedPath(true);
|
||||
if (copiedPathTimeoutRef.current !== null) {
|
||||
window.clearTimeout(copiedPathTimeoutRef.current);
|
||||
@@ -2088,7 +2094,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
copiedPathTimeoutRef.current = window.setTimeout(() => {
|
||||
setCopiedPath(false);
|
||||
}, 1200);
|
||||
} catch {
|
||||
} else {
|
||||
toast.error('Copy failed');
|
||||
}
|
||||
}}
|
||||
@@ -2437,8 +2443,8 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(fileContent);
|
||||
const result = await copyTextToClipboard(fileContent);
|
||||
if (result.ok) {
|
||||
setCopiedContent(true);
|
||||
if (copiedContentTimeoutRef.current !== null) {
|
||||
window.clearTimeout(copiedContentTimeoutRef.current);
|
||||
@@ -2446,7 +2452,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
copiedContentTimeoutRef.current = window.setTimeout(() => {
|
||||
setCopiedContent(false);
|
||||
}, 1200);
|
||||
} catch {
|
||||
} else {
|
||||
toast.error('Copy failed');
|
||||
}
|
||||
}}
|
||||
@@ -2467,8 +2473,8 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(displaySelectedPath);
|
||||
const result = await copyTextToClipboard(displaySelectedPath);
|
||||
if (result.ok) {
|
||||
setCopiedPath(true);
|
||||
if (copiedPathTimeoutRef.current !== null) {
|
||||
window.clearTimeout(copiedPathTimeoutRef.current);
|
||||
@@ -2476,7 +2482,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
copiedPathTimeoutRef.current = window.setTimeout(() => {
|
||||
setCopiedPath(false);
|
||||
}, 1200);
|
||||
} catch {
|
||||
} else {
|
||||
toast.error('Copy failed');
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useFireworksCelebration } from '@/contexts/FireworksContext';
|
||||
import type { GitIdentityProfile, CommitFileEntry } from '@/lib/api/types';
|
||||
import { useGitIdentitiesStore } from '@/stores/useGitIdentitiesStore';
|
||||
import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory';
|
||||
import { copyTextToClipboard } from '@/lib/clipboard';
|
||||
import {
|
||||
useGitStore,
|
||||
useGitStatus,
|
||||
@@ -473,14 +474,13 @@ export const GitView: React.FC<GitViewProps> = ({ mode = 'full' }) => {
|
||||
const [stashDialogBranch, setStashDialogBranch] = React.useState('');
|
||||
|
||||
const handleCopyCommitHash = React.useCallback((hash: string) => {
|
||||
navigator.clipboard
|
||||
.writeText(hash)
|
||||
.then(() => {
|
||||
void copyTextToClipboard(hash).then((result) => {
|
||||
if (result.ok) {
|
||||
toast.success('Commit hash copied');
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error('Failed to copy');
|
||||
});
|
||||
return;
|
||||
}
|
||||
toast.error('Failed to copy');
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleToggleCommit = React.useCallback((hash: string) => {
|
||||
|
||||
@@ -21,6 +21,7 @@ import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { useInlineCommentDraftStore } from '@/stores/useInlineCommentDraftStore';
|
||||
import { toast } from '@/components/ui';
|
||||
import { copyTextToClipboard } from '@/lib/clipboard';
|
||||
|
||||
const normalize = (value: string): string => {
|
||||
if (!value) return '';
|
||||
@@ -428,8 +429,8 @@ export const PlanView: React.FC = () => {
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(content);
|
||||
const result = await copyTextToClipboard(content);
|
||||
if (result.ok) {
|
||||
setCopiedContent(true);
|
||||
if (copiedContentTimeoutRef.current !== null) {
|
||||
window.clearTimeout(copiedContentTimeoutRef.current);
|
||||
@@ -437,7 +438,7 @@ export const PlanView: React.FC = () => {
|
||||
copiedContentTimeoutRef.current = window.setTimeout(() => {
|
||||
setCopiedContent(false);
|
||||
}, 1200);
|
||||
} catch {
|
||||
} else {
|
||||
// ignored
|
||||
}
|
||||
}}
|
||||
@@ -455,8 +456,8 @@ export const PlanView: React.FC = () => {
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(displayPath ?? resolvedPath);
|
||||
const result = await copyTextToClipboard(displayPath ?? resolvedPath);
|
||||
if (result.ok) {
|
||||
setCopiedPath(true);
|
||||
if (copiedTimeoutRef.current !== null) {
|
||||
window.clearTimeout(copiedTimeoutRef.current);
|
||||
@@ -464,7 +465,7 @@ export const PlanView: React.FC = () => {
|
||||
copiedTimeoutRef.current = window.setTimeout(() => {
|
||||
setCopiedPath(false);
|
||||
}, 1200);
|
||||
} catch {
|
||||
} else {
|
||||
// ignored
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '@remixicon/react';
|
||||
import { toast } from '@/components/ui';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { copyTextToClipboard } from '@/lib/clipboard';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { ProviderLogo } from '@/components/ui/ProviderLogo';
|
||||
import { useAgentGroupsStore, type AgentGroup, type AgentGroupSession } from '@/stores/useAgentGroupsStore';
|
||||
@@ -88,14 +89,13 @@ export const AgentGroupDetail: React.FC<AgentGroupDetailProps> = ({
|
||||
toast.error('No worktree path available');
|
||||
return;
|
||||
}
|
||||
navigator.clipboard
|
||||
.writeText(selectedSession.path)
|
||||
.then(() => {
|
||||
void copyTextToClipboard(selectedSession.path).then((result) => {
|
||||
if (result.ok) {
|
||||
toast.success('Worktree path copied');
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error('Failed to copy path');
|
||||
});
|
||||
return;
|
||||
}
|
||||
toast.error('Failed to copy path');
|
||||
});
|
||||
}, [selectedSession?.path]);
|
||||
|
||||
const handleRemoveSelectedWorktree = React.useCallback(async () => {
|
||||
|
||||
Reference in New Issue
Block a user