Skip to content

Commit ef6ef05

Browse files
committed
Debounce selection copy and show preview
1 parent b3552ea commit ef6ef05

File tree

1 file changed

+52
-8
lines changed

1 file changed

+52
-8
lines changed

cli/src/hooks/use-clipboard.ts

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ export const useClipboard = () => {
99
const clipboardTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(
1010
null,
1111
)
12+
const pendingCopyTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(
13+
null,
14+
)
15+
const copyDelayRef = useRef<number>(2000)
16+
const pendingSelectionRef = useRef<string | null>(null)
17+
const lastCopiedRef = useRef<string | null>(null)
1218

1319
const copyToClipboard = useCallback(async (text: string) => {
1420
if (!text || text.trim().length === 0) return
@@ -37,7 +43,9 @@ export const useClipboard = () => {
3743
clearTimeout(clipboardTimeoutRef.current)
3844
}
3945

40-
setClipboardMessage('Copied to clipboard')
46+
const preview = text.replace(/\s+/g, ' ').trim()
47+
const truncated = preview.length > 40 ? `${preview.slice(0, 37)}…` : preview
48+
setClipboardMessage(`Copied: "${truncated}"`)
4149
clipboardTimeoutRef.current = setTimeout(() => {
4250
setClipboardMessage(null)
4351
clipboardTimeoutRef.current = null
@@ -48,17 +56,49 @@ export const useClipboard = () => {
4856
}, [])
4957

5058
useEffect(() => {
51-
const handleSelection = () => {
52-
const selection = (renderer as any)?.getSelection?.()
53-
if (selection && selection.length > 0) {
54-
void copyToClipboard(selection)
59+
const handleSelection = (selectionEvent: any) => {
60+
const selectionObj = selectionEvent ?? (renderer as any)?.getSelection?.()
61+
const rawText: string | null = selectionObj?.getSelectedText
62+
? selectionObj.getSelectedText()
63+
: typeof selectionObj === 'string'
64+
? selectionObj
65+
: null
66+
67+
if (!rawText || rawText.trim().length === 0) {
68+
pendingSelectionRef.current = null
69+
if (pendingCopyTimeoutRef.current) {
70+
clearTimeout(pendingCopyTimeoutRef.current)
71+
pendingCopyTimeoutRef.current = null
72+
}
73+
return
74+
}
75+
76+
if (rawText === pendingSelectionRef.current) {
77+
return
5578
}
79+
80+
pendingSelectionRef.current = rawText
81+
82+
if (pendingCopyTimeoutRef.current) {
83+
clearTimeout(pendingCopyTimeoutRef.current)
84+
}
85+
86+
pendingCopyTimeoutRef.current = setTimeout(() => {
87+
pendingCopyTimeoutRef.current = null
88+
const pending = pendingSelectionRef.current
89+
if (!pending || pending === lastCopiedRef.current) {
90+
return
91+
}
92+
93+
lastCopiedRef.current = pending
94+
void copyToClipboard(pending)
95+
}, copyDelayRef.current)
5696
}
5797

58-
if (renderer) {
59-
renderer.on?.('selectionchange', handleSelection)
98+
if (renderer?.on) {
99+
renderer.on('selection', handleSelection)
60100
return () => {
61-
renderer.off?.('selectionchange', handleSelection)
101+
renderer.off?.('selection', handleSelection)
62102
}
63103
}
64104
return undefined
@@ -69,6 +109,10 @@ export const useClipboard = () => {
69109
if (clipboardTimeoutRef.current) {
70110
clearTimeout(clipboardTimeoutRef.current)
71111
}
112+
if (pendingCopyTimeoutRef.current) {
113+
clearTimeout(pendingCopyTimeoutRef.current)
114+
pendingCopyTimeoutRef.current = null
115+
}
72116
}
73117
}, [])
74118

0 commit comments

Comments
 (0)