fix: route clipboard image pastes to the attachment strip #295

Merged
talksik merged 5 commits from claude/markdown-image-upload-fix-t9ggfa into main 2026-06-21 16:15:21 +00:00
Showing only changes of commit b3084fd515 - Show all commits
+24 -5
View File
@@ -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);