From 7bac4927e8895719a91011da9a2b997579238145 Mon Sep 17 00:00:00 2001
From: polwex <polwex@sortug.com>
Date: Thu, 18 Sep 2025 08:26:30 +0700
Subject: damn my trill codebase was really something

---
 front/src/components/composer/Composer.tsx | 198 ++++++++++++++++++++++-------
 1 file changed, 153 insertions(+), 45 deletions(-)

(limited to 'front/src/components/composer/Composer.tsx')

diff --git a/front/src/components/composer/Composer.tsx b/front/src/components/composer/Composer.tsx
index 43d38cd..81d0358 100644
--- a/front/src/components/composer/Composer.tsx
+++ b/front/src/components/composer/Composer.tsx
@@ -1,28 +1,44 @@
 import useLocalState from "@/state/state";
 import type { Poast } from "@/types/trill";
 import Sigil from "@/components/Sigil";
-import { useState, type FormEvent } from "react";
-import type { ComposerData } from "@/types/ui";
+import { useState, useEffect, useRef, type FormEvent } from "react";
 import Snippets, { ReplySnippet } from "./Snippets";
 import toast from "react-hot-toast";
-import { useLocation } from "wouter";
+import Icon from "@/components/Icon";
+import { wait } from "@/logic/utils";
 
-function Composer({
-  isAnon,
-  replying,
-}: {
-  isAnon?: boolean;
-  replying?: Poast;
-}) {
-  const [loc, navigate] = useLocation();
-  const { api, composerData, addNotification, setComposerData } = useLocalState((s) => ({
-    api: s.api,
-    composerData: s.composerData,
-    addNotification: s.addNotification,
-    setComposerData: s.setComposerData,
-  }));
+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 our = api!.airlock.our!;
-  const [input, setInput] = useState(replying ? `${replying}: ` : "");
+  const [input, setInput] = useState("");
+  const [isExpanded, setIsExpanded] = useState(false);
+  const [isLoading, setLoading] = useState(false);
+  const inputRef = useRef<HTMLInputElement>(null);
+
+  useEffect(() => {
+    if (composerData) {
+      setIsExpanded(true);
+      if (
+        composerData.type === "reply" &&
+        composerData.post &&
+        "trill" in composerData.post
+      ) {
+        const author = composerData.post.trill.author;
+        setInput(`${author} `);
+      }
+      // Auto-focus input when composer opens
+      setTimeout(() => {
+        inputRef.current?.focus();
+      }, 100); // Small delay to ensure the composer is rendered
+    }
+  }, [composerData]);
   async function poast(e: FormEvent<HTMLFormElement>) {
     e.preventDefault();
     // TODO
@@ -39,13 +55,32 @@ function Composer({
     //   tags: input.match(HASHTAGS_REGEX) || [],
     // };
     // TODO make it user choosable
-    const res = await api!.addPost(input);
-    if (res) {
-      // Check for mentions in the post (ship names starting with ~)
+    setLoading(true);
+
+    const res =
+      composerData?.type === "reply" && "trill" in composerData.post
+        ? api!.addReply(
+            input,
+            composerData.post.trill.host,
+            composerData.post.trill.id,
+            composerData.post.trill.thread || composerData.post.trill.id,
+          )
+        : composerData?.type === "quote" && "trill" in composerData.post
+          ? api!.addQuote(input, {
+              ship: composerData.post.trill.host,
+              id: composerData.post.trill.id,
+            })
+          : !composerData
+            ? api!.addPost(input)
+            : wait(500);
+    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
+        mentions.forEach((mention) => {
+          if (mention !== our) {
+            // Don't notify self-mentions
             addNotification({
               type: "mention",
               from: our,
@@ -56,40 +91,113 @@ function Composer({
       }
 
       // If this is a reply, add notification
-      if (composerData?.type === "reply" && 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,
-        });
+      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");
-      navigate(`/feed/${our}`);
+      setIsExpanded(false);
     }
   }
-  const placeHolder = isAnon ? "> be me" : "What's going on in Urbit";
+  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 (
-    <form id="composer" onSubmit={poast}>
+    <form
+      id="composer"
+      className={`${isExpanded ? "expanded" : ""} ${composerData ? "has-context" : ""}`}
+      onSubmit={poast}
+    >
       <div className="sigil avatar">
         <Sigil patp={our} size={46} />
       </div>
 
-      {composerData && composerData.type === "reply" && (
-        <ReplySnippet post={composerData?.post} />
-      )}
-      <input
-        value={input}
-        onInput={(e) => setInput(e.currentTarget.value)}
-        placeholder={placeHolder}
-      />
-      {composerData && composerData.type === "quote" && (
-        <Snippets post={composerData?.post} />
-      )}
-      <button type="submit">Post</button>
+      <div className="composer-content">
+        {/* Reply snippets appear above input */}
+        {composerData && composerData.type === "reply" && (
+          <div className="composer-context reply-context">
+            <div className="context-header">
+              <span className="context-type">
+                <Icon name="reply" size={14} /> Replying to
+              </span>
+              <button
+                className="clear-context"
+                onClick={clearComposer}
+                title="Clear"
+                type="button"
+              >
+                ×
+              </button>
+            </div>
+            <ReplySnippet post={composerData.post} />
+          </div>
+        )}
+
+        {/* Quote context header above input (without snippet) */}
+        {composerData && composerData.type === "quote" && (
+          <div className="quote-header">
+            <div className="context-header">
+              <span className="context-type">
+                <Icon name="quote" size={14} /> Quote posting
+              </span>
+              <button
+                className="clear-context"
+                onClick={clearComposer}
+                title="Clear"
+                type="button"
+              >
+                ×
+              </button>
+            </div>
+          </div>
+        )}
+
+        <div className="composer-input-row">
+          <input
+            ref={inputRef}
+            value={input}
+            onInput={(e) => setInput(e.currentTarget.value)}
+            onFocus={() => setIsExpanded(true)}
+            placeholder={placeHolder}
+          />
+          <button type="submit" disabled={!input.trim()} className="post-btn">
+            Post
+          </button>
+        </div>
+
+        {/* Quote snippets appear below input */}
+        {composerData && composerData.type === "quote" && (
+          <div className="composer-context quote-context">
+            <Snippets post={composerData.post} />
+          </div>
+        )}
+      </div>
     </form>
   );
 }
-- 
cgit v1.2.3

