|
120 | 120 | appState.pastedImages.push({ dataUrl, timecode, path }); |
121 | 121 | } |
122 | 122 |
|
| 123 | + let isPasting = false; |
| 124 | +
|
123 | 125 | async function handlePaste(event: ClipboardEvent) { |
124 | 126 | if (appState.phase !== "recording") return; |
125 | | - const timecode = appState.elapsedSecs; |
126 | | -
|
127 | | - // Primary: modern async Clipboard API — more reliable in WKWebView for |
128 | | - // images copied from native apps (screenshots, etc.) |
| 127 | + if (isPasting) return; |
| 128 | + isPasting = true; |
129 | 129 | try { |
130 | | - const clipboardItems = await navigator.clipboard.read(); |
131 | | - for (const clipItem of clipboardItems) { |
132 | | - const imageType = clipItem.types.find((t) => t.startsWith("image/")); |
133 | | - if (imageType) { |
134 | | - event.preventDefault(); |
135 | | - const blob = await clipItem.getType(imageType); |
136 | | - await saveImageBlob(blob, timecode); |
137 | | - return; |
| 130 | + const timecode = appState.elapsedSecs; |
| 131 | +
|
| 132 | + // Primary: modern async Clipboard API — more reliable in WKWebView for |
| 133 | + // images copied from native apps (screenshots, etc.) |
| 134 | + try { |
| 135 | + const clipboardItems = await navigator.clipboard.read(); |
| 136 | + for (const clipItem of clipboardItems) { |
| 137 | + const imageType = clipItem.types.find((t) => t.startsWith("image/")); |
| 138 | + if (imageType) { |
| 139 | + event.preventDefault(); |
| 140 | + const blob = await clipItem.getType(imageType); |
| 141 | + await saveImageBlob(blob, timecode); |
| 142 | + return; |
| 143 | + } |
138 | 144 | } |
| 145 | + } catch { |
| 146 | + // Clipboard API unavailable or no permission — fall through to legacy path |
139 | 147 | } |
140 | | - } catch { |
141 | | - // Clipboard API unavailable or no permission — fall through to legacy path |
142 | | - } |
143 | 148 |
|
144 | | - // Fallback: event.clipboardData.items (works for paste from web content) |
145 | | - const items = event.clipboardData?.items; |
146 | | - if (!items) return; |
147 | | - for (const item of Array.from(items)) { |
148 | | - if (item.type.startsWith("image/")) { |
149 | | - event.preventDefault(); |
150 | | - const file = item.getAsFile(); |
151 | | - if (!file) continue; |
152 | | - try { |
153 | | - await saveImageBlob(file, timecode); |
154 | | - } catch (e) { |
155 | | - console.error("Failed to save pasted image:", e); |
| 149 | + // Fallback: event.clipboardData.items (works for paste from web content) |
| 150 | + const items = event.clipboardData?.items; |
| 151 | + if (!items) return; |
| 152 | + for (const item of Array.from(items)) { |
| 153 | + if (item.type.startsWith("image/")) { |
| 154 | + event.preventDefault(); |
| 155 | + const file = item.getAsFile(); |
| 156 | + if (!file) continue; |
| 157 | + try { |
| 158 | + await saveImageBlob(file, timecode); |
| 159 | + } catch (e) { |
| 160 | + console.error("Failed to save pasted image:", e); |
| 161 | + } |
| 162 | + break; |
156 | 163 | } |
157 | | - break; |
158 | 164 | } |
| 165 | + } finally { |
| 166 | + isPasting = false; |
159 | 167 | } |
160 | 168 | } |
161 | 169 |
|
|
0 commit comments