From a9c8208fa8d2e7a82c9775596fdeed10b92ec2a9 Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Wed, 18 Feb 2026 23:26:03 -0600 Subject: [PATCH] Add clipboard paste support for images in ComposeBox Users can now paste images directly from clipboard into the compose box. Images are automatically uploaded to Blossom and the URL is inserted into the content. Co-authored-by: shakespeare.diy --- src/components/ComposeBox.tsx | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/components/ComposeBox.tsx b/src/components/ComposeBox.tsx index b86781c6..019a6556 100644 --- a/src/components/ComposeBox.tsx +++ b/src/components/ComposeBox.tsx @@ -257,7 +257,7 @@ export function ComposeBox({ onSuccess, placeholder = "What's on your mind?", co expand(); }, [content, expand]); - const handleFileUpload = async (file: File) => { + const handleFileUpload = useCallback(async (file: File) => { try { const tags = await uploadFile(file); const [[, url]] = tags; @@ -269,7 +269,25 @@ export function ComposeBox({ onSuccess, placeholder = "What's on your mind?", co } catch { toast({ title: 'Upload failed', description: 'Could not upload file.', variant: 'destructive' }); } - }; + }, [uploadFile, expand, toast]); + + const handlePaste = useCallback(async (e: React.ClipboardEvent) => { + const items = e.clipboardData?.items; + if (!items) return; + + // Check for image files in clipboard + for (let i = 0; i < items.length; i++) { + const item = items[i]; + if (item.type.startsWith('image/')) { + e.preventDefault(); // Prevent default paste behavior for images + const file = item.getAsFile(); + if (file) { + await handleFileUpload(file); + } + break; + } + } + }, [handleFileUpload]); const handleSubmit = async () => { if (!content.trim() || !user || charCount > MAX_CHARS) return; @@ -420,6 +438,7 @@ export function ComposeBox({ onSuccess, placeholder = "What's on your mind?", co value={content} onChange={(e) => setContent(e.target.value)} onFocus={expand} + onPaste={handlePaste} placeholder={placeholder} className={cn( 'w-full bg-transparent text-foreground placeholder:text-muted-foreground resize-none outline-none text-lg pt-2.5 pb-2 opacity-85',