Починка реакций

This commit is contained in:
Халимов Рустам
2026-03-13 12:46:01 +03:00
parent 9a32442b5c
commit ca1d88191c
438 changed files with 374 additions and 20961 deletions

View File

@@ -152,8 +152,8 @@ function MessageBubble({
const chatForDelete = chats.find(c => c.id === message.chatId);
const otherMemberName = chatForDelete?.type === 'personal'
? chatForDelete.members.find(m => m.user.id !== user?.id)?.user.displayName
|| chatForDelete.members.find(m => m.user.id !== user?.id)?.user.username
|| ''
|| chatForDelete.members.find(m => m.user.id !== user?.id)?.user.username
|| ''
: '';
const isPinned = pinnedMessages[message.chatId]?.id === message.id;
@@ -176,11 +176,22 @@ function MessageBubble({
const existingReaction = message.reactions?.find(
(r) => r.userId === user?.id && r.emoji === emoji
);
console.log('[Reaction] handleReaction:', {
emoji,
messageId: message.id,
chatId: message.chatId,
existingReaction: !!existingReaction,
userId: user?.id
});
if (existingReaction) {
console.log('[Reaction] Emitting remove_reaction');
socket.emit('remove_reaction', { messageId: message.id, chatId: message.chatId, emoji });
} else {
console.log('[Reaction] Emitting add_reaction');
socket.emit('add_reaction', { messageId: message.id, chatId: message.chatId, emoji });
}
} else {
console.warn('[Reaction] Socket not available');
}
setShowContext(false);
};
@@ -393,7 +404,7 @@ function MessageBubble({
{/* Reply */}
{message.replyTo && (
<div
<div
className="mx-3 mb-1 px-3 py-1.5 rounded-lg border-l-2 border-vortex-500 bg-vortex-500/10 max-w-full cursor-pointer hover:bg-vortex-500/20 transition-colors"
onClick={() => {
const el = document.getElementById(`msg-${message.replyToId}`);
@@ -430,17 +441,16 @@ function MessageBubble({
onContextMenu={handleContextMenu}
onDoubleClick={handleReply}
title={t('reply') ? `${t('reply')} (Double Click)` : 'Double click to reply'}
className={`cursor-pointer rounded-[1.25rem] overflow-hidden transition-all duration-300 ${
hasImage && !message.content
className={`cursor-pointer rounded-[1.25rem] overflow-hidden transition-all duration-300 ${hasImage && !message.content
? 'p-0 shadow-none border-none'
: isMine
? 'bubble-sent text-white shadow-sm px-4 py-2.5 hover:shadow-md hover:brightness-105'
: 'bubble-received text-zinc-100 shadow-sm px-4 py-2.5 hover:shadow-md hover:brightness-105'
}`}
}`}
>
{/* Рендер пересланного сообщения */}
{message.forwardedFrom && (
<div
<div
className="mb-2 text-[13px] opacity-90 border-l-[2px] border-white/40 pl-3 py-0.5 cursor-pointer hover:bg-white/5 transition-colors -mx-1 px-1 rounded-sm"
onClick={() => onViewProfile?.(message.forwardedFromId!)}
>
@@ -456,13 +466,12 @@ function MessageBubble({
{/* Изображения и Видео (Галерея) */}
{(hasImage || hasVideo) && (
<div className={`${message.content ? 'mb-2 -mx-4 -mt-2.5' : ''} bg-black/20 overflow-hidden`}>
<div className={`grid gap-[2px] ${
media.filter(m => m.type === 'image' || m.type === 'video').length >= 3
<div className={`grid gap-[2px] ${media.filter(m => m.type === 'image' || m.type === 'video').length >= 3
? 'grid-cols-3'
: media.filter(m => m.type === 'image' || m.type === 'video').length === 2
? 'grid-cols-2'
: 'grid-cols-1'
}`}>
}`}>
{media
.filter((m) => m.type === 'image' || m.type === 'video')
.map((m, idx) => (
@@ -471,17 +480,15 @@ function MessageBubble({
key={m.id}
src={m.url}
alt=""
className={`w-full h-full object-cover cursor-pointer hover:brightness-90 transition-all ${
media.filter(i => i.type === 'image' || i.type === 'video').length > 1 ? 'aspect-square' : 'max-h-[500px]'
}`}
className={`w-full h-full object-cover cursor-pointer hover:brightness-90 transition-all ${media.filter(i => i.type === 'image' || i.type === 'video').length > 1 ? 'aspect-square' : 'max-h-[500px]'
}`}
onClick={() => setLightboxData({ index: idx })}
/>
) : (
<div
key={m.id}
className={`relative cursor-pointer group/video ${
media.filter(i => i.type === 'image' || i.type === 'video').length > 1 ? 'aspect-square' : ''
}`}
<div
key={m.id}
className={`relative cursor-pointer group/video ${media.filter(i => i.type === 'image' || i.type === 'video').length > 1 ? 'aspect-square' : ''
}`}
onClick={() => setLightboxData({ index: idx })}
>
<video
@@ -489,7 +496,7 @@ function MessageBubble({
className="w-full h-full object-cover"
/>
<div className="absolute inset-0 flex items-center justify-center bg-black/20 group-hover/video:bg-black/40 transition-colors">
<Play size={media.filter(i => i.type === 'image' || i.type === 'video').length > 1 ? 24 : 48} className="text-white opacity-80" />
<Play size={media.filter(i => i.type === 'image' || i.type === 'video').length > 1 ? 24 : 48} className="text-white opacity-80" />
</div>
</div>
)
@@ -766,84 +773,84 @@ function MessageBubble({
</>
) : (
<>
<div className="flex items-center gap-1 px-3 py-2 border-b border-border">
{['👍', '❤️', '😂', '😮', '😢', '🔥'].map((emoji) => (
<div className="flex items-center gap-1 px-3 py-2 border-b border-border">
{['👍', '❤️', '😂', '😮', '😢', '🔥'].map((emoji) => (
<button
key={emoji}
onClick={() => handleReaction(emoji)}
className="w-8 h-8 flex items-center justify-center rounded-lg hover:bg-surface-hover transition-colors text-lg"
>
{emoji}
</button>
))}
</div>
<button
key={emoji}
onClick={() => handleReaction(emoji)}
className="w-8 h-8 flex items-center justify-center rounded-lg hover:bg-surface-hover transition-colors text-lg"
onClick={handleReply}
className="flex items-center gap-3 w-full px-4 py-2.5 text-sm text-zinc-300 hover:bg-surface-hover hover:text-white transition-colors"
>
{emoji}
<Reply size={16} />
{quotedText ? t('replyWithQuote') : t('reply')}
</button>
))}
</div>
<button
onClick={handleReply}
className="flex items-center gap-3 w-full px-4 py-2.5 text-sm text-zinc-300 hover:bg-surface-hover hover:text-white transition-colors"
>
<Reply size={16} />
{quotedText ? t('replyWithQuote') : t('reply')}
</button>
<button
onClick={() => {
setShowContext(false);
onStartSelectionMode?.(message.id);
}}
className="flex items-center gap-3 w-full px-4 py-2.5 text-sm text-zinc-300 hover:bg-surface-hover hover:text-white transition-colors"
>
<CheckCheck size={16} />
{t('select')}
</button>
<button
onClick={() => {
setShowContext(false);
onStartSelectionMode?.(message.id);
}}
className="flex items-center gap-3 w-full px-4 py-2.5 text-sm text-zinc-300 hover:bg-surface-hover hover:text-white transition-colors"
>
<CheckCheck size={16} />
{t('select')}
</button>
<button
onClick={() => {
setShowContext(false);
onForward?.(message.id);
}}
className="flex items-center gap-3 w-full px-4 py-2.5 text-sm text-zinc-300 hover:bg-surface-hover hover:text-white transition-colors"
>
<Forward size={16} />
{t('forward')}
</button>
<button
onClick={() => {
setShowContext(false);
onForward?.(message.id);
}}
className="flex items-center gap-3 w-full px-4 py-2.5 text-sm text-zinc-300 hover:bg-surface-hover hover:text-white transition-colors"
>
<Forward size={16} />
{t('forward')}
</button>
<button
onClick={handlePin}
className="flex items-center gap-3 w-full px-4 py-2.5 text-sm text-zinc-300 hover:bg-surface-hover hover:text-white transition-colors"
>
<Pin size={16} />
{isPinned ? t('unpinMessage') : t('pinMessage')}
</button>
<button
onClick={handlePin}
className="flex items-center gap-3 w-full px-4 py-2.5 text-sm text-zinc-300 hover:bg-surface-hover hover:text-white transition-colors"
>
<Pin size={16} />
{isPinned ? t('unpinMessage') : t('pinMessage')}
</button>
{message.content && (
<button
onClick={handleCopy}
className="flex items-center gap-3 w-full px-4 py-2.5 text-sm text-zinc-300 hover:bg-surface-hover hover:text-white transition-colors"
>
<Copy size={16} />
{t('copy')}
</button>
)}
{message.content && (
<button
onClick={handleCopy}
className="flex items-center gap-3 w-full px-4 py-2.5 text-sm text-zinc-300 hover:bg-surface-hover hover:text-white transition-colors"
>
<Copy size={16} />
{t('copy')}
</button>
)}
{isMine && message.content && (
<button
onClick={handleEdit}
className="flex items-center gap-3 w-full px-4 py-2.5 text-sm text-zinc-300 hover:bg-surface-hover hover:text-white transition-colors"
>
<Pencil size={16} />
{t('edit')}
</button>
)}
{isMine && message.content && (
<button
onClick={handleEdit}
className="flex items-center gap-3 w-full px-4 py-2.5 text-sm text-zinc-300 hover:bg-surface-hover hover:text-white transition-colors"
>
<Pencil size={16} />
{t('edit')}
</button>
)}
<div className="border-t border-border my-1" />
<button
onClick={() => setDeleteMenuMode(true)}
className="flex items-center gap-3 w-full px-4 py-2.5 text-sm text-red-400 hover:bg-red-500/10 transition-colors"
>
<Trash2 size={16} />
{t('delete')}
</button>
<div className="border-t border-border my-1" />
<button
onClick={() => setDeleteMenuMode(true)}
className="flex items-center gap-3 w-full px-4 py-2.5 text-sm text-red-400 hover:bg-red-500/10 transition-colors"
>
<Trash2 size={16} />
{t('delete')}
</button>
</>
)}
</motion.div>
@@ -854,10 +861,10 @@ function MessageBubble({
<AnimatePresence>
{lightboxData && (
<ImageLightbox
images={media.filter(m => m.type === 'image' || m.type === 'video').map(m => ({ url: m.url, type: m.type }))}
<ImageLightbox
images={media.filter(m => m.type === 'image' || m.type === 'video').map(m => ({ url: m.url, type: m.type }))}
initialIndex={lightboxData.index}
onClose={() => setLightboxData(null)}
onClose={() => setLightboxData(null)}
/>
)}
</AnimatePresence>