From cd79b77e6fe839ea9beb8a4f1d11e211e8596716 Mon Sep 17 00:00:00 2001 From: byGalax Date: Sat, 16 May 2026 18:07:40 +0200 Subject: [PATCH] feat(desktop): GIF button + picker wired into composer --- apps/desktop/src/pages/ConversationPage.tsx | 36 +++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/apps/desktop/src/pages/ConversationPage.tsx b/apps/desktop/src/pages/ConversationPage.tsx index 61dbbd2..3666c3c 100644 --- a/apps/desktop/src/pages/ConversationPage.tsx +++ b/apps/desktop/src/pages/ConversationPage.tsx @@ -8,6 +8,7 @@ import { ConversationHeader } from '../components/ConversationHeader'; import { EmojiPicker } from '../components/EmojiPicker'; import { EmptyState } from '../components/EmptyState'; import { ForwardDialog } from '../components/ForwardDialog'; +import { GifPicker } from '../components/GifPicker'; import { GroupInfoPanel } from '../components/GroupInfoPanel'; import { AlertIcon, @@ -43,6 +44,7 @@ import { compressImages } from '../lib/imageCompress'; import { ensureInstallId } from '../lib/installId'; import { searchCachedMessages } from '../lib/messageCache'; import { supabase } from '../lib/supabase'; +import { type GifResult } from '../lib/tenor'; import type { OutboxItem } from '../lib/messageOutbox'; import { useConversationMessages } from '../lib/useConversationMessages'; import { useMessageReactions } from '../lib/useMessageReactions'; @@ -180,6 +182,7 @@ export function ConversationPage() { } | null>(null); const [mentionState, setMentionState] = useState<{ query: string; start: number } | null>(null); const [emojiOpen, setEmojiOpen] = useState(false); + const [gifPickerOpen, setGifPickerOpen] = useState(false); const pins = usePinnedMessages(id); const [pinnedPanelOpen, setPinnedPanelOpen] = useState(false); const pinnedIds = useMemo(() => new Set(pins.map((p) => p.messageId)), [pins]); @@ -199,6 +202,23 @@ export function ConversationPage() { }, [id, myId, pinnedIds], ); + + const handleGifPick = useCallback( + async (gif: GifResult) => { + try { + // Download the GIF bytes once and feed them into the existing + // image-attachment pipeline so the result is end-to-end-encrypted + // like any normal image. + const res = await fetch(gif.url); + const blob = await res.blob(); + const file = new File([blob], `tenor-${gif.id}.gif`, { type: 'image/gif' }); + await send('', [file], null); + } catch (err) { + console.warn('GIF send failed', err); + } + }, + [send], + ); const scrollRef = useRef(null); const loadMoreSentinelRef = useRef(null); const fileInputRef = useRef(null); @@ -976,6 +996,22 @@ export function ConversationPage() { onClose={() => setEmojiOpen(false)} /> +
+ + setGifPickerOpen(false)} + onPick={(gif) => void handleGifPick(gif)} + /> +
{