-
-
Notifications
You must be signed in to change notification settings - Fork 339
Expand file tree
/
Copy pathCopyMarkdownButton.tsx
More file actions
127 lines (116 loc) · 3.57 KB
/
CopyMarkdownButton.tsx
File metadata and controls
127 lines (116 loc) · 3.57 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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
'use client'
import { useState, useTransition } from 'react'
import { type MouseEventHandler, useEffect, useRef } from 'react'
import { useToast } from '~/components/ToastProvider'
import { Check, Copy } from 'lucide-react'
import { Button } from '~/ui'
export function useCopyButton(
onCopy: () => void | Promise<void>,
): [checked: boolean, onClick: MouseEventHandler] {
const [checked, setChecked] = useState(false)
const timeoutRef = useRef<number | null>(null)
const onClick: MouseEventHandler = async () => {
if (timeoutRef.current) window.clearTimeout(timeoutRef.current)
const res = Promise.resolve(onCopy())
void res.then(() => {
setChecked(true)
timeoutRef.current = window.setTimeout(() => {
setChecked(false)
}, 1500)
})
}
// avoid updates after being unmounted
useEffect(() => {
return () => {
if (timeoutRef.current) window.clearTimeout(timeoutRef.current)
}
}, [])
return [checked, onClick]
}
const cache = new Map<string, string>()
interface CopyMarkdownButtonProps {
repo: string
branch: string
filePath: string
}
export function CopyMarkdownButton({
repo,
branch,
filePath,
}: CopyMarkdownButtonProps) {
const [isLoading, startTransition] = useTransition()
const { notify } = useToast()
const [checked, onClick] = useCopyButton(async () => {
startTransition(() => {
const url = `https://raw.githubusercontent.com/${repo}/${branch}/${filePath}`
const cached = cache.get(url)
if (cached) {
navigator.clipboard.writeText(cached).then(() => {
notify(
<div>
<div className="font-medium">Copied markdown</div>
<div className="text-gray-500 dark:text-gray-400 text-xs">
Source content copied from cache
</div>
</div>,
)
})
} else {
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error('Fetch failed')
}
return response.text()
})
.then((content) => {
cache.set(url, content)
return navigator.clipboard.writeText(content).then(() => {
notify(
<div>
<div className="font-medium">Copied markdown</div>
<div className="text-gray-500 dark:text-gray-400 text-xs">
Source content copied from GitHub
</div>
</div>,
)
})
})
.catch(() => {
// fallback: try to copy current page content if available
const pageContent =
document.querySelector('.styled-markdown-content')?.textContent ||
''
navigator.clipboard.writeText(pageContent).then(() => {
notify(
<div>
<div className="font-medium">Copied markdown</div>
<div className="text-gray-500 dark:text-gray-400 text-xs">
Fallback: copied rendered page content
</div>
</div>,
)
})
})
}
})
})
return (
<Button
disabled={isLoading}
className="bg-white/70 text-black dark:bg-gray-500/40 dark:text-white shadow-md backdrop-blur-sm"
onClick={onClick}
title="Copy markdown source"
>
{checked ? (
<>
<Check className="w-3 h-3" /> Copied to Clipboard
</>
) : (
<>
<Copy className="w-3 h-3" /> Copy Markdown
</>
)}
</Button>
)
}