-
Notifications
You must be signed in to change notification settings - Fork 519
Expand file tree
/
Copy pathuse-clipboard.ts
More file actions
100 lines (86 loc) · 2.85 KB
/
use-clipboard.ts
File metadata and controls
100 lines (86 loc) · 2.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { useRenderer } from '@opentui/react'
import { useCallback, useEffect, useRef, useState } from 'react'
import {
copyTextToClipboard,
subscribeClipboardMessages,
} from '../utils/clipboard'
function formatDefaultClipboardMessage(text: string): string | null {
const preview = text.replace(/\s+/g, ' ').trim()
if (!preview) {
return null
}
const truncated = preview.length > 40 ? `${preview.slice(0, 37)}…` : preview
return `Copied: "${truncated}"`
}
export const useClipboard = () => {
const renderer = useRenderer()
const [clipboardMessage, setClipboardMessage] = useState<string | null>(null)
const pendingCopyTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(
null,
)
const copyDelayRef = useRef<number>(2000)
const pendingSelectionRef = useRef<string | null>(null)
const lastCopiedRef = useRef<string | null>(null)
useEffect(() => {
return subscribeClipboardMessages(setClipboardMessage)
}, [])
useEffect(() => {
const handleSelection = (selectionEvent: any) => {
const rendererAny = renderer as any
const selectionObj = selectionEvent ?? (rendererAny?.getSelection ? rendererAny.getSelection() : undefined)
const rawText: string | null = selectionObj?.getSelectedText
? selectionObj.getSelectedText()
: typeof selectionObj === 'string'
? selectionObj
: null
if (!rawText || rawText.trim().length === 0) {
pendingSelectionRef.current = null
if (pendingCopyTimeoutRef.current) {
clearTimeout(pendingCopyTimeoutRef.current)
pendingCopyTimeoutRef.current = null
}
return
}
if (rawText === pendingSelectionRef.current) {
return
}
pendingSelectionRef.current = rawText
if (pendingCopyTimeoutRef.current) {
clearTimeout(pendingCopyTimeoutRef.current)
}
pendingCopyTimeoutRef.current = setTimeout(() => {
pendingCopyTimeoutRef.current = null
const pending = pendingSelectionRef.current
if (!pending || pending === lastCopiedRef.current) {
return
}
lastCopiedRef.current = pending
const successMessage = formatDefaultClipboardMessage(pending)
void copyTextToClipboard(pending, {
successMessage,
durationMs: 3000,
}).catch(() => {
// Errors are logged within copyTextToClipboard
})
}, copyDelayRef.current)
}
if (renderer?.on) {
renderer.on('selection', handleSelection)
return () => {
renderer.off?.('selection', handleSelection)
}
}
return undefined
}, [renderer])
useEffect(() => {
return () => {
if (pendingCopyTimeoutRef.current) {
clearTimeout(pendingCopyTimeoutRef.current)
pendingCopyTimeoutRef.current = null
}
}
}, [])
return {
clipboardMessage,
}
}