import "@/styles/Composer.css"; import useLocalState from "@/state/state"; import spinner from "@/assets/triangles.svg"; import Sigil from "@/components/Sigil"; import { useState, useEffect, useRef, type FormEvent } from "react"; import Snippets, { ReplySnippet } from "./Snippets"; import toast from "react-hot-toast"; import Icon from "@/components/Icon"; import { wait } from "@/logic/utils"; import type { UserType } from "@/types/nostrill"; function Composer({ isAnon }: { isAnon?: boolean }) { const { api, composerData, addNotification, setComposerData } = useLocalState( (s) => ({ api: s.api, composerData: s.composerData, addNotification: s.addNotification, setComposerData: s.setComposerData, }), ); const [input, setInput] = useState(""); const [isExpanded, setIsExpanded] = useState(false); const [isLoading, setLoading] = useState(false); const inputRef = useRef(null); console.log({ composerData }); useEffect(() => { if (composerData) { setIsExpanded(true); if ( composerData.type === "reply" && composerData.post && "trill" in composerData.post ) { } // Auto-focus input when composer opens setTimeout(() => { inputRef.current?.focus(); }, 100); // Small delay to ensure the composer is rendered } }, [composerData]); async function addSimple() { if (!api) return; // TODOhandle error return await api.addPost(input); } async function addComplex() { if (!api) return; // TODOhandle error if (!composerData) return; const host: UserType = "trill" in composerData.post ? { urbit: composerData.post.trill.author } : "nostr" in composerData.post ? { nostr: composerData.post.nostr.pubkey } : { urbit: api.airlock.our! }; const id = "trill" in composerData.post ? composerData.post.trill.id : "nostr" in composerData.post ? composerData.post.nostr.eventId : ""; const thread = "trill" in composerData.post ? composerData.post.trill.thread || composerData.post.trill.id : "nostr" in composerData.post ? composerData.post.nostr.eventId : ""; const res = composerData.type === "reply" ? api.addReply(input, host, id, thread) : composerData?.type === "quote" ? api.addQuote(input, host, id) : wait(500); return await res; } async function poast(e: FormEvent) { e.preventDefault(); if (!api) return; // TODOhandle error const our = api.airlock.our!; setLoading(true); const res = !composerData ? addSimple() : addComplex(); const ares = await res; if (ares) { // // Check for mentions in the post (ship names starting with ~) const mentions = input.match(/~[a-z-]+/g); if (mentions) { mentions.forEach((mention) => { if (mention !== our) { // Don't notify self-mentions addNotification({ type: "mention", from: our, message: `You mentioned ${mention} in a post`, }); } }); } // If this is a reply, add notification if ( composerData?.type === "reply" && composerData.post && "trill" in composerData.post ) { if (composerData.post.trill.author !== our) { addNotification({ type: "reply", from: our, message: `You replied to ${composerData.post.trill.author}'s post`, postId: composerData.post.trill.id, }); } } setInput(""); setComposerData(null); // Clear composer data after successful post toast.success("post sent"); setLoading(false); setIsExpanded(false); } } const placeHolder = composerData?.type === "reply" ? "Write your reply..." : composerData?.type === "quote" ? "Add your thoughts..." : isAnon ? "> be me" : "What's going on in Urbit"; const clearComposer = (e: React.MouseEvent) => { e.preventDefault(); setComposerData(null); setInput(""); setIsExpanded(false); }; return (
{/* Reply snippets appear above input */} {composerData && composerData.type === "reply" && (
Replying to
)} {/* Quote context header above input (without snippet) */} {composerData && composerData.type === "quote" && (
Quote posting
)}
setInput(e.currentTarget.value)} onFocus={() => setIsExpanded(true)} placeholder={placeHolder} /> {isLoading ? (
) : ( )}
{/* Quote snippets appear below input */} {composerData && composerData.type === "quote" && (
)}
); } export default Composer;