diff --git a/js/desktop/src/hooks/use-file-input.ts b/js/desktop/src/hooks/use-file-input.ts index 749c987..737b98c 100644 --- a/js/desktop/src/hooks/use-file-input.ts +++ b/js/desktop/src/hooks/use-file-input.ts @@ -5,6 +5,19 @@ interface UseFileInputOptions { enabled: boolean; } +/** + * Files carried by a paste. A pasted image is usually exposed only through + * `items` (`getAsFile`), with `files` left empty, so read `items` first and + * fall back to `files` for the rare clipboard that populates only the latter. + */ +function clipboardFiles(data: DataTransfer): File[] { + const fromItems = Array.from(data.items) + .filter((item) => item.kind === 'file') + .map((item) => item.getAsFile()) + .filter((file): file is File => file !== null); + return fromItems.length > 0 ? fromItems : Array.from(data.files); +} + export function useFileInput({ onFilesSelected, enabled, @@ -49,14 +62,20 @@ export function useFileInput({ if (!enabled) return; // Capture phase so file pastes always become attachments — ProseMirror - // would otherwise inline pasted images as ephemeral blob: URLs. Mixed - // clipboards (e.g. Excel/Word ship an image rendition alongside the text) - // must still paste as text, so only file-only pastes are intercepted. + // (the markdown editor) would otherwise inline a pasted image as an + // ephemeral blob: URL and get stuck on its "uploading" placeholder. const handlePaste = (e: ClipboardEvent) => { const data = e.clipboardData; if (!data) return; - const files = Array.from(data.files); - if (files.length === 0 || data.types.includes('text/plain')) return; + + // Actual text means "paste as text", even when the clipboard also ships + // an image rendition (Excel/Word, or "copy image" from a page with alt + // text). Test the payload, not the advertised types: image pastes often + // list an *empty* text/plain entry that must not block the attachment. + if (data.getData('text/plain').trim().length > 0) return; + + const files = clipboardFiles(data); + if (files.length === 0) return; e.preventDefault(); e.stopPropagation(); onFilesRef.current(files);