Кэш для GIF, подсветка цитат и ответов

This commit is contained in:
Халимов Рустам
2026-03-16 15:53:57 +03:00
parent 239b08b566
commit 33007b3c72
8 changed files with 159 additions and 84 deletions
+47 -81
View File
@@ -5,6 +5,7 @@ import data from '@emoji-mart/data';
import { Search, TrendingUp, Loader2 } from 'lucide-react';
import { useLang } from '../lib/i18n';
import { useAuthStore } from '../stores/authStore';
import { api } from '../lib/api';
interface KlipyGif {
id: string;
@@ -22,18 +23,9 @@ interface EmojiPickerProps {
onClose: () => void;
}
const getKlipyKey = (config: any) => {
return config?.klipyApiKey || import.meta.env.VITE_KLIPY_API_KEY || '';
};
const getCustomerId = (config: any, user: any) => {
return config?.klipyCustomerId || user?.id || 'anonymous';
};
export default function EmojiPicker({ onSelect, onSelectGif, onClose }: EmojiPickerProps) {
const { lang, t } = useLang();
const { config, user } = useAuthStore();
const klipyApiKey = getKlipyKey(config);
const klipyCustomerId = getCustomerId(config, user);
const { config } = useAuthStore();
const [tab, setTab] = useState<'emoji' | 'gif'>('emoji');
const [gifQuery, setGifQuery] = useState('');
const [gifs, setGifs] = useState<KlipyGif[]>([]);
@@ -57,17 +49,10 @@ export default function EmojiPicker({ onSelect, onSelectGif, onClose }: EmojiPic
// Load trending GIFs (Klipy)
useEffect(() => {
if (tab === 'gif' && klipyApiKey && !initialFetchDone.current) {
if (tab === 'gif' && config?.enableKlipy && !initialFetchDone.current) {
initialFetchDone.current = true;
setGifLoading(true);
fetch(`https://api.klipy.com/api/v1/${klipyApiKey}/gifs/trending?page=1&per_page=30&customer_id=${klipyCustomerId}`)
.then(async (r) => {
if (!r.ok) {
const text = await r.text();
throw new Error(`Klipy error: ${r.status} ${text.substring(0, 100)}`);
}
return r.json();
})
api.getTrendingGifs()
.then(d => {
setTrendingGifs(extractGifs(d));
setGifLoading(false);
@@ -78,23 +63,16 @@ export default function EmojiPicker({ onSelect, onSelectGif, onClose }: EmojiPic
setGifLoading(false);
});
}
}, [tab, klipyApiKey, klipyCustomerId]);
}, [tab, config?.enableKlipy]);
const searchGifs = useCallback((q: string) => {
if (!klipyApiKey || !q.trim()) {
if (!config?.enableKlipy || !q.trim()) {
setGifs([]);
setGifLoading(false);
return;
}
setGifLoading(true);
fetch(`https://api.klipy.com/api/v1/${klipyApiKey}/gifs/search?page=1&per_page=30&q=${encodeURIComponent(q)}&customer_id=${klipyCustomerId}`)
.then(async (r) => {
if (!r.ok) {
const text = await r.text();
throw new Error(`Klipy error: ${r.status} ${text.substring(0, 100)}`);
}
return r.json();
})
api.searchKlipyGifs(q)
.then(d => {
setGifs(extractGifs(d));
setGifLoading(false);
@@ -104,7 +82,7 @@ export default function EmojiPicker({ onSelect, onSelectGif, onClose }: EmojiPic
setGifs([]);
setGifLoading(false);
});
}, [klipyApiKey, klipyCustomerId]);
}, [config?.enableKlipy]);
const handleGifSearch = (q: string) => {
setGifQuery(q);
@@ -173,56 +151,46 @@ export default function EmojiPicker({ onSelect, onSelectGif, onClose }: EmojiPic
visibility: pos ? 'visible' : 'hidden',
}}
>
{/* Tabs */}
<div className="flex border-b border-white/10">
<button
onClick={() => setTab('emoji')}
className={`flex-1 py-2.5 text-xs font-semibold tracking-wide transition-colors ${tab === 'emoji' ? 'text-white border-b-2 border-accent' : 'text-zinc-500 hover:text-zinc-300'}`}
>
EMOJI
</button>
{config?.enableKlipy && (klipyApiKey || onSelectGif) && (
<button
onClick={() => { setTab('gif'); setTimeout(() => gifSearchRef.current?.focus(), 100); }}
className={`flex-1 py-2.5 text-xs font-semibold tracking-wide transition-colors ${tab === 'gif' ? 'text-white border-b-2 border-accent' : 'text-zinc-500 hover:text-zinc-300'}`}
>
GIF
</button>
)}
</div>
{/* Tabs */}
<div className="flex border-b border-white/10">
<button
onClick={() => setTab('emoji')}
className={`flex-1 py-2.5 text-xs font-semibold tracking-wide transition-colors ${tab === 'emoji' ? 'text-white border-b-2 border-accent' : 'text-zinc-500 hover:text-zinc-300'}`}
>
EMOJI
</button>
{config?.enableKlipy && onSelectGif && (
<button
onClick={() => { setTab('gif'); setTimeout(() => gifSearchRef.current?.focus(), 100); }}
className={`flex-1 py-2.5 text-xs font-semibold tracking-wide transition-colors ${tab === 'gif' ? 'text-white border-b-2 border-accent' : 'text-zinc-500 hover:text-zinc-300'}`}
>
GIF
</button>
)}
</div>
{/* Emoji tab */}
{tab === 'emoji' && (
<Picker
data={data}
onEmojiSelect={(e: { native: string }) => onSelect(e.native)}
theme="dark"
locale={lang === 'ru' ? 'ru' : 'en'}
set="native"
previewPosition="none"
skinTonePosition="search"
perLine={9}
emojiSize={28}
emojiButtonSize={36}
maxFrequentRows={2}
navPosition="bottom"
dynamicWidth={false}
/>
)}
{/* Emoji tab */}
{tab === 'emoji' && (
<Picker
data={data}
onEmojiSelect={(e: { native: string }) => onSelect(e.native)}
theme="dark"
locale={lang === 'ru' ? 'ru' : 'en'}
set="native"
previewPosition="none"
skinTonePosition="search"
perLine={9}
emojiSize={28}
emojiButtonSize={36}
maxFrequentRows={2}
navPosition="bottom"
dynamicWidth={false}
/>
)}
{/* GIF tab */}
{config?.enableKlipy && tab === 'gif' && (
<div className="flex flex-col h-[calc(100%-41px)]">
{!klipyApiKey ? (
<div className="flex-1 flex flex-col items-center justify-center p-6 text-center">
<p className="text-sm text-zinc-400 mb-2">Klipy API Key required</p>
<p className="text-xs text-zinc-500 mb-3">{t('openConsoleRun')}</p>
<code className="text-xs bg-black/30 px-3 py-1.5 rounded-lg text-knot-400">
VITE_KLIPY_API_KEY in .env
</code>
</div>
) : (
<>
{/* GIF tab */}
{config?.enableKlipy && tab === 'gif' && (
<div className="flex flex-col h-[calc(100%-41px)]">
<div className="p-2">
<div className="relative">
<Search size={14} className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-500" />
@@ -267,11 +235,9 @@ export default function EmojiPicker({ onSelect, onSelectGif, onClose }: EmojiPic
</div>
)}
</div>
</>
</div>
)}
</div>
)}
</div>
</>,
document.body
)}