feat(desktop): GIF button + picker wired into composer
This commit is contained in:
@@ -8,6 +8,7 @@ import { ConversationHeader } from '../components/ConversationHeader';
|
|||||||
import { EmojiPicker } from '../components/EmojiPicker';
|
import { EmojiPicker } from '../components/EmojiPicker';
|
||||||
import { EmptyState } from '../components/EmptyState';
|
import { EmptyState } from '../components/EmptyState';
|
||||||
import { ForwardDialog } from '../components/ForwardDialog';
|
import { ForwardDialog } from '../components/ForwardDialog';
|
||||||
|
import { GifPicker } from '../components/GifPicker';
|
||||||
import { GroupInfoPanel } from '../components/GroupInfoPanel';
|
import { GroupInfoPanel } from '../components/GroupInfoPanel';
|
||||||
import {
|
import {
|
||||||
AlertIcon,
|
AlertIcon,
|
||||||
@@ -43,6 +44,7 @@ import { compressImages } from '../lib/imageCompress';
|
|||||||
import { ensureInstallId } from '../lib/installId';
|
import { ensureInstallId } from '../lib/installId';
|
||||||
import { searchCachedMessages } from '../lib/messageCache';
|
import { searchCachedMessages } from '../lib/messageCache';
|
||||||
import { supabase } from '../lib/supabase';
|
import { supabase } from '../lib/supabase';
|
||||||
|
import { type GifResult } from '../lib/tenor';
|
||||||
import type { OutboxItem } from '../lib/messageOutbox';
|
import type { OutboxItem } from '../lib/messageOutbox';
|
||||||
import { useConversationMessages } from '../lib/useConversationMessages';
|
import { useConversationMessages } from '../lib/useConversationMessages';
|
||||||
import { useMessageReactions } from '../lib/useMessageReactions';
|
import { useMessageReactions } from '../lib/useMessageReactions';
|
||||||
@@ -180,6 +182,7 @@ export function ConversationPage() {
|
|||||||
} | null>(null);
|
} | null>(null);
|
||||||
const [mentionState, setMentionState] = useState<{ query: string; start: number } | null>(null);
|
const [mentionState, setMentionState] = useState<{ query: string; start: number } | null>(null);
|
||||||
const [emojiOpen, setEmojiOpen] = useState(false);
|
const [emojiOpen, setEmojiOpen] = useState(false);
|
||||||
|
const [gifPickerOpen, setGifPickerOpen] = useState(false);
|
||||||
const pins = usePinnedMessages(id);
|
const pins = usePinnedMessages(id);
|
||||||
const [pinnedPanelOpen, setPinnedPanelOpen] = useState(false);
|
const [pinnedPanelOpen, setPinnedPanelOpen] = useState(false);
|
||||||
const pinnedIds = useMemo(() => new Set(pins.map((p) => p.messageId)), [pins]);
|
const pinnedIds = useMemo(() => new Set(pins.map((p) => p.messageId)), [pins]);
|
||||||
@@ -199,6 +202,23 @@ export function ConversationPage() {
|
|||||||
},
|
},
|
||||||
[id, myId, pinnedIds],
|
[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<HTMLDivElement>(null);
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
const loadMoreSentinelRef = useRef<HTMLDivElement>(null);
|
const loadMoreSentinelRef = useRef<HTMLDivElement>(null);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
@@ -976,6 +996,22 @@ export function ConversationPage() {
|
|||||||
onClose={() => setEmojiOpen(false)}
|
onClose={() => setEmojiOpen(false)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="relative">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setGifPickerOpen((v) => !v)}
|
||||||
|
className="flex h-9 w-9 cursor-pointer items-center justify-center rounded-md text-fg-muted hover:bg-surface-3 hover:text-fg"
|
||||||
|
aria-label="GIF"
|
||||||
|
title="GIF einfügen"
|
||||||
|
>
|
||||||
|
<span className="text-xs font-bold">GIF</span>
|
||||||
|
</button>
|
||||||
|
<GifPicker
|
||||||
|
open={gifPickerOpen}
|
||||||
|
onClose={() => setGifPickerOpen(false)}
|
||||||
|
onPick={(gif) => void handleGifPick(gif)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<VoiceRecorder
|
<VoiceRecorder
|
||||||
disabled={sending}
|
disabled={sending}
|
||||||
onComplete={async (file) => {
|
onComplete={async (file) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user