fix: route clipboard image pastes to the attachment strip #295
@@ -5,6 +5,19 @@ interface UseFileInputOptions {
|
|||||||
enabled: boolean;
|
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({
|
export function useFileInput({
|
||||||
onFilesSelected,
|
onFilesSelected,
|
||||||
enabled,
|
enabled,
|
||||||
@@ -49,14 +62,20 @@ export function useFileInput({
|
|||||||
if (!enabled) return;
|
if (!enabled) return;
|
||||||
|
|
||||||
// Capture phase so file pastes always become attachments — ProseMirror
|
// Capture phase so file pastes always become attachments — ProseMirror
|
||||||
// would otherwise inline pasted images as ephemeral blob: URLs. Mixed
|
// (the markdown editor) would otherwise inline a pasted image as an
|
||||||
// clipboards (e.g. Excel/Word ship an image rendition alongside the text)
|
// ephemeral blob: URL and get stuck on its "uploading" placeholder.
|
||||||
// must still paste as text, so only file-only pastes are intercepted.
|
|
||||||
const handlePaste = (e: ClipboardEvent) => {
|
const handlePaste = (e: ClipboardEvent) => {
|
||||||
const data = e.clipboardData;
|
const data = e.clipboardData;
|
||||||
if (!data) return;
|
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.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onFilesRef.current(files);
|
onFilesRef.current(files);
|
||||||
|
|||||||
Reference in New Issue
Block a user