-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
copy-code.ts
93 lines (74 loc) · 2.22 KB
/
copy-code.ts
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
import { nextTick, watch } from 'vue'
import { inBrowser, useData } from 'vitepress'
export function useCopyCode() {
const { page } = useData()
if (inBrowser)
watch(
() => page.value.relativePath,
() => {
nextTick(() => {
document
.querySelectorAll<HTMLSpanElement>(
'.vp-doc div[class*="language-"]>span.copy'
)
.forEach(handleElement)
})
},
{ immediate: true, flush: 'post' }
)
}
async function copyToClipboard(text: string) {
try {
return navigator.clipboard.writeText(text)
} catch {
const element = document.createElement('textarea')
const previouslyFocusedElement = document.activeElement
element.value = text
// Prevent keyboard from showing on mobile
element.setAttribute('readonly', '')
element.style.contain = 'strict'
element.style.position = 'absolute'
element.style.left = '-9999px'
element.style.fontSize = '12pt' // Prevent zooming on iOS
const selection = document.getSelection()
const originalRange = selection
? selection.rangeCount > 0 && selection.getRangeAt(0)
: null
document.body.append(element)
element.select()
// Explicit selection workaround for iOS
element.selectionStart = 0
element.selectionEnd = text.length
document.execCommand('copy')
element.remove()
if (originalRange) {
selection!.removeAllRanges() // originalRange can't be truthy when selection is falsy
selection!.addRange(originalRange)
}
// Get the focus back on the previously focused element, if any
if (previouslyFocusedElement) {
;(previouslyFocusedElement as HTMLElement).focus()
}
}
}
function handleElement(el: HTMLElement) {
el.onclick = () => {
const parent = el.parentElement
if (!parent) {
return
}
const isShell =
parent.classList.contains('language-sh') ||
parent.classList.contains('language-bash')
let { innerText: text = '' } = parent
if (isShell) {
text = text.replace(/^ *\$ /gm, '')
}
copyToClipboard(text).then(() => {
el.classList.add('copied')
setTimeout(() => {
el.classList.remove('copied')
}, 3000)
})
}
}