From f61b33ff81abc5d26666380fd6d802caea64c76e Mon Sep 17 00:00:00 2001 From: hobokenchicken Date: Thu, 2 Jul 2026 15:50:28 -0400 Subject: [PATCH] feat: GIF picker and kaomoji in DMs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added GiphyPicker to DM input area (toggle with [GIF] button) - Added EmojiPicker/kaomoji to DMs (toggle with [☺] or Ctrl+E) - GiphyPicker shows above input when toggled - Kaomoji appends to input text --- web/src/components/DMChat.tsx | 58 ++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/web/src/components/DMChat.tsx b/web/src/components/DMChat.tsx index ec791fd..33de698 100644 --- a/web/src/components/DMChat.tsx +++ b/web/src/components/DMChat.tsx @@ -4,6 +4,8 @@ import { useConversationStore, type ConversationMessage } from "../stores/conver import { useAuthStore } from "../stores/auth.ts"; import { useTypingStore } from "../stores/typing.ts"; import { useLayoutStore } from "../stores/layout.ts"; +import { GiphyPicker, type Gif } from "./GiphyPicker.tsx"; +import { EmojiPicker } from "./EmojiPicker.tsx"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; @@ -71,6 +73,8 @@ export function DMChat() { const sendTypingStart = useTypingStore((s) => s.sendTypingStart); const [input, setInput] = useState(""); const [error, setError] = useState(null); + const [showGifPicker, setShowGifPicker] = useState(false); + const [showKaomoji, setShowKaomoji] = useState(false); const bottomRef = useRef(null); const scrollContainerRef = useRef(null); const lastTypingRef = useRef(0); @@ -108,6 +112,29 @@ export function DMChat() { bottomRef.current?.scrollIntoView({ behavior: "auto" }); }, [messages]); + // Ctrl+E kaomoji shortcut + useEffect(() => { + const handler = (e: KeyboardEvent) => { + if (e.ctrlKey && e.key === 'e') { + e.preventDefault(); + setShowKaomoji((prev) => !prev); + } + }; + window.addEventListener('keydown', handler); + return () => window.removeEventListener('keydown', handler); + }, []); + + const handleGifSelect = async (gif: Gif) => { + if (!id) return; + const content = `![${gif.title || "GIF"}](${gif.images.fixed_height.url})`; + try { + await sendMessage(id, content); + setShowGifPicker(false); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to send"); + } + }; + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!id || !input.trim()) return; @@ -152,6 +179,11 @@ export function DMChat() { ))}
+ {showGifPicker && ( +
+ setShowGifPicker(false)} /> +
+ )}
{(() => { const convTyping = id ? (typingUsers[id] || []).filter(u => u.userId !== currentUser?.id) : []; @@ -165,7 +197,7 @@ export function DMChat() {

ERR: {error}

)} -
+ {">"} + + + {showKaomoji && ( + setInput((prev) => prev + emoji)} + onClose={() => setShowKaomoji(false)} + /> + )} );