diff --git a/web/src/components/ChatArea.tsx b/web/src/components/ChatArea.tsx index 64151bf..517fe64 100644 --- a/web/src/components/ChatArea.tsx +++ b/web/src/components/ChatArea.tsx @@ -437,6 +437,58 @@ export function ChatArea() { return () => clearInterval(t); }, [slowmodeRemaining]); + const handlePaste = async (e: React.ClipboardEvent) => { + const items = e.clipboardData.items; + let imageFile: File | null = null; + + for (let i = 0; i < items.length; i++) { + if (items[i].type.startsWith('image/')) { + imageFile = items[i].getAsFile(); + break; + } + } + + if (imageFile) { + e.preventDefault(); + + const formData = new FormData(); + formData.append('file', imageFile); + + try { + const res = await fetch('/api/v1/upload', { + method: 'POST', + body: formData, + credentials: 'include' + }); + + if (!res.ok) throw new Error('Upload failed'); + + const data = await res.json(); + const imageUrl = data.url; + + // Insert into input + const imgMarkdown = `![image](${imageUrl})`; + + const inputEl = inputRef.current; + if (inputEl) { + const start = inputEl.selectionStart || 0; + const end = inputEl.selectionEnd || 0; + const newValue = input.slice(0, start) + imgMarkdown + input.slice(end); + setInput(newValue); + + setTimeout(() => { + inputEl.focus(); + inputEl.setSelectionRange(start + imgMarkdown.length, start + imgMarkdown.length); + }, 0); + } else { + setInput(prev => prev + (prev.length > 0 && !prev.endsWith(' ') ? ' ' : '') + imgMarkdown); + } + } catch (err) { + console.error('Failed to upload image:', err); + } + } + }; + const handleInputChange = (e: React.ChangeEvent) => { const value = e.target.value; const cursor = e.target.selectionStart ?? value.length; @@ -851,6 +903,7 @@ export function ChatArea() { type="text" value={input} onChange={handleInputChange} + onPaste={handlePaste} placeholder="type a message..." className="terminal-input w-full" disabled={!activeChannelId || slowmodeRemaining > 0}