import { useState, useRef } from "react"; import { NDKEvent } from "@nostr-dev-kit/ndk"; import { publishReply } from "../../lib/nostr"; import { uploadImage, uploadBytes } from "../../lib/upload"; import { useAutoResize } from "../../hooks/useAutoResize"; import { useReplyCount } from "../../hooks/useReplyCount"; import { EmojiPicker } from "../shared/EmojiPicker"; import { open } from "@tauri-apps/plugin-dialog"; import { readFile } from "@tauri-apps/plugin-fs"; interface InlineReplyBoxProps { event: NDKEvent; name: string; rootEvent?: { id: string; pubkey: string }; } export function InlineReplyBox({ event, name, rootEvent }: InlineReplyBoxProps) { const [replyText, setReplyText] = useState(""); const [attachments, setAttachments] = useState([]); const [replying, setReplying] = useState(false); const [replyError, setReplyError] = useState(null); const [replySent, setReplySent] = useState(false); const [showReplyEmoji, setShowReplyEmoji] = useState(false); const [uploading, setUploading] = useState(false); const [uploadError, setUploadError] = useState(null); const autoResize = useAutoResize(2, 8); const replyRef = useRef(null); const [, adjustReplyCount] = useReplyCount(event.id); const insertAtCursor = (str: string) => { const ta = replyRef.current; if (ta) { const start = ta.selectionStart ?? replyText.length; const end = ta.selectionEnd ?? replyText.length; setReplyText(replyText.slice(0, start) + str + replyText.slice(end)); setTimeout(() => { ta.selectionStart = ta.selectionEnd = start + str.length; ta.focus(); }, 0); } else { setReplyText((t) => t + str); } }; const addAttachment = (url: string) => { setAttachments((prev) => [...prev, url]); }; const removeAttachment = (index: number) => { setAttachments((prev) => prev.filter((_, i) => i !== index)); }; const handleImageUpload = async (file: File) => { setUploading(true); setUploadError(null); try { const url = await uploadImage(file); addAttachment(url); } catch (err) { setUploadError(`Upload failed: ${err}`); } finally { setUploading(false); } }; const handlePaste = async (e: React.ClipboardEvent) => { const fileFromFiles = Array.from(e.clipboardData.files).find((f) => f.type.startsWith("image/")); if (fileFromFiles) { e.preventDefault(); handleImageUpload(fileFromFiles); return; } const items = Array.from(e.clipboardData.items ?? []); const imageItem = items.find((item) => item.type.startsWith("image/")); if (imageItem) { const file = imageItem.getAsFile(); if (file) { e.preventDefault(); handleImageUpload(file); return; } } const pastedText = e.clipboardData.getData("text/plain"); if (pastedText && /\.(jpg|jpeg|png|gif|webp|svg|mp4|webm|mov)$/i.test(pastedText.trim()) && /^(\/|[A-Z]:\\)/.test(pastedText.trim())) { e.preventDefault(); setUploading(true); setUploadError(null); try { const bytes = await readFile(pastedText.trim()); const fileName = pastedText.trim().split(/[\\/]/).pop() || "file"; const ext = fileName.split(".").pop()?.toLowerCase() || ""; const mimeMap: Record = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", gif: "image/gif", webp: "image/webp" }; const url = await uploadBytes(new Uint8Array(bytes), fileName, mimeMap[ext] || "application/octet-stream"); addAttachment(url); } catch (err) { setUploadError(`Upload failed: ${err}`); } finally { setUploading(false); } } }; const handleFilePicker = async () => { try { const selected = await open({ multiple: false, filters: [{ name: "Media", extensions: ["jpg", "jpeg", "png", "gif", "webp", "svg", "mp4", "webm", "mov"] }], }); if (!selected) return; setUploading(true); setUploadError(null); const bytes = await readFile(selected); const fileName = selected.split(/[\\/]/).pop() || "file"; const ext = fileName.split(".").pop()?.toLowerCase() || ""; const mimeMap: Record = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", gif: "image/gif", webp: "image/webp", mp4: "video/mp4", webm: "video/webm", mov: "video/quicktime" }; const url = await uploadBytes(new Uint8Array(bytes), fileName, mimeMap[ext] || "application/octet-stream"); addAttachment(url); } catch (err) { setUploadError(`Upload failed: ${err}`); } finally { setUploading(false); } }; const handleReplySubmit = async () => { if ((!replyText.trim() && attachments.length === 0) || replying) return; setReplying(true); setReplyError(null); try { // Build final content: text + attachment URLs on separate lines const parts = [replyText.trim(), ...attachments].filter(Boolean); const content = parts.join("\n"); await publishReply(content, { id: event.id, pubkey: event.pubkey }, rootEvent); setReplyText(""); setAttachments([]); setReplySent(true); adjustReplyCount(1); setTimeout(() => { setReplySent(false); }, 1500); } catch (err) { setReplyError(`Failed: ${err}`); } finally { setReplying(false); } }; const handleReplyKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) handleReplySubmit(); if (e.key === "Escape") { // Parent controls visibility — just blur replyRef.current?.blur(); } }; return (