feat: GIF search (Klipy), stickers, emoji picker, and bug fixes

- Add GIF search powered by Klipy API with correct response mapping
  (file.sm/hd tiers, not flat files structure)
- Add sticker system: packs, upload with auto-downscale, send in messages
- Add tabbed InputPopover with emoji, GIF, and sticker pickers
- Fix GIF API key migration race condition (column-add loop vs rename)
- Fix masked API key corruption on settings save (server + client guards)
- Fix sticker packs 403 (reversed isMember parameter order)
- Fix emoji picker not filling popover width (perLine 8→9, CSS 100%)
- Add error logging for Klipy API failures
This commit is contained in:
Jannis Braun
2026-03-15 02:04:37 +01:00
parent 7113f47b17
commit 3de6e4a668
26 changed files with 2160 additions and 50 deletions
@@ -0,0 +1,39 @@
import React, { useRef, useEffect } from 'react';
import Picker from '@emoji-mart/react';
import data from '@emoji-mart/data';
interface EmojiPickerProps {
onEmojiSelect: (emoji: { native: string }) => void;
}
export function EmojiPicker({ onEmojiSelect }: EmojiPickerProps) {
const containerRef = useRef<HTMLDivElement>(null);
// Prevent keyboard events from bubbling out (e.g. Enter submitting the chat input)
useEffect(() => {
const el = containerRef.current;
if (!el) return;
const stop = (e: KeyboardEvent) => e.stopPropagation();
el.addEventListener('keydown', stop);
return () => el.removeEventListener('keydown', stop);
}, []);
return (
<div ref={containerRef} className="emoji-picker-wrapper">
<Picker
data={data}
onEmojiSelect={onEmojiSelect}
theme="dark"
set="native"
skinTonePosition="search"
previewPosition="none"
navPosition="bottom"
perLine={9}
maxFrequentRows={2}
emojiSize={24}
emojiButtonSize={32}
categories={['frequent', 'people', 'nature', 'foods', 'activity', 'places', 'objects', 'symbols', 'flags']}
/>
</div>
);
}