forked from
quillmatiq.com/augment
Fork of Chiri for Astro for my blog
1---
2import { themeConfig } from '@/config'
3---
4
5<script define:vars={{ copyCode: themeConfig.post.copyCode }}>
6 function initCopyCode() {
7 const copyIcon = `
8 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="14" height="14" fill="currentColor">
9 <path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path>
10 <path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path>
11 </svg>
12 `
13
14 const copiedIcon = `
15 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
16 <path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path>
17 </svg>
18 `
19
20 document.body.setAttribute('data-copy-code', copyCode ? 'enabled' : 'disabled')
21
22 if (!copyCode) {
23 return
24 }
25
26 const copyButtons = document.querySelectorAll('.copy-button')
27
28 copyButtons.forEach((button) => {
29 // Avoid initializing multiple times (important for SPA navigation)
30 if (button.dataset.copyInit) return
31 button.dataset.copyInit = '1'
32
33 // Find the closest pre element with the .copy-code-block class
34 let preElement = button.closest('.copy-code-block')
35 if (!preElement) {
36 const parent = button.parentElement
37 if (parent) {
38 preElement = parent.querySelector('.copy-code-block')
39 }
40 }
41 if (!preElement) return
42
43 const codeElement = preElement.querySelector('code')
44 if (!codeElement) return
45
46 if (!button.querySelector('svg')) {
47 button.innerHTML = copyIcon
48 }
49
50 button.style.opacity = '0'
51 button.style.pointerEvents = 'none'
52
53 // Determine the container to attach hover events
54 let container = preElement
55 const parent = button.parentElement
56 if (parent && parent.classList.contains('copy-code-wrapper')) {
57 container = parent
58 }
59
60 container.addEventListener('mouseenter', () => {
61 button.style.opacity = '1'
62 button.style.pointerEvents = 'auto'
63 })
64
65 container.addEventListener('mouseleave', () => {
66 if (!button.hasAttribute('data-copying')) {
67 button.style.opacity = '0'
68 button.style.pointerEvents = 'none'
69 }
70 })
71
72 button.addEventListener('click', async () => {
73 const codeText = codeElement.textContent || ''
74
75 try {
76 // Primary method: use modern clipboard API
77 await navigator.clipboard.writeText(codeText)
78 button.setAttribute('data-copying', 'true')
79 button.innerHTML = copiedIcon
80
81 setTimeout(() => {
82 if (!container.matches(':hover')) {
83 button.style.opacity = '0'
84 button.style.pointerEvents = 'none'
85 }
86 button.removeAttribute('data-copying')
87
88 setTimeout(() => {
89 button.innerHTML = copyIcon
90 }, 500)
91 }, 1500)
92 } catch (err) {
93 console.error('Failed to copy code:', err)
94
95 // Fallback method: create temporary textarea for older browsers
96 try {
97 const textArea = document.createElement('textarea')
98 textArea.value = codeText
99 textArea.style.position = 'fixed'
100 textArea.style.opacity = '0'
101 document.body.appendChild(textArea)
102 textArea.focus()
103 textArea.select()
104
105 navigator.clipboard
106 .writeText(codeText)
107 .then(() => {
108 document.body.removeChild(textArea)
109 })
110 .catch(() => {
111 console.error('Both clipboard methods failed')
112 document.body.removeChild(textArea)
113 })
114 } catch (fallbackErr) {
115 console.error('All clipboard methods failed:', fallbackErr)
116 }
117
118 button.setAttribute('data-copying', 'true')
119 button.innerHTML = copiedIcon
120
121 setTimeout(() => {
122 if (!container.matches(':hover')) {
123 button.style.opacity = '0'
124 button.style.pointerEvents = 'none'
125 }
126 button.removeAttribute('data-copying')
127
128 setTimeout(() => {
129 button.innerHTML = copyIcon
130 }, 500)
131 }, 1500)
132 }
133 })
134 })
135 }
136
137 // Use only astro:page-load as it fires on initial load and navigation
138 document.addEventListener('astro:page-load', initCopyCode)
139</script>
140
141<style is:inline>
142 /* Ensure the positioned ancestor is correct */
143 .copy-code-wrapper {
144 position: relative !important;
145 }
146
147 /* Keep for backward compatibility */
148 .copy-code-block {
149 position: relative !important;
150 }
151
152 .copy-button {
153 position: absolute;
154 top: 0.5rem;
155 right: 0.5rem;
156 width: 2rem;
157 height: 2rem;
158 z-index: 10;
159 background: var(--bg);
160 border-radius: 0.375rem;
161 font-size: 0.75rem;
162 color: var(--text-secondary);
163 cursor: pointer;
164 transition: all 0.15s ease-out;
165 display: flex;
166 align-items: center;
167 justify-content: center;
168 border: 1px solid var(--border);
169 backdrop-filter: blur(48px);
170 opacity: 0;
171 pointer-events: none;
172 }
173
174 body[data-copy-code='disabled'] .copy-button {
175 display: none !important;
176 }
177
178 .copy-button::before {
179 content: '';
180 position: absolute;
181 top: 0;
182 left: 0;
183 right: 0;
184 bottom: 0;
185 background: var(--code-bg);
186 border-radius: 0.325rem;
187 opacity: 0;
188 transition: opacity 0.15s ease-out;
189 pointer-events: none;
190 }
191
192 .copy-button:hover::before {
193 opacity: 1;
194 }
195
196 .copy-button:hover {
197 color: var(--text-primary);
198 }
199
200 .copy-button svg {
201 flex-shrink: 0;
202 position: relative;
203 z-index: 1;
204 }
205</style>