Архитектура
This commit is contained in:
@@ -262,27 +262,36 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
let highestSequenceId = -1;
|
||||
let highestMsgId = '';
|
||||
const newlyReadIds: string[] = [];
|
||||
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
const msgId = entry.target.getAttribute('data-message-id');
|
||||
if (msgId && !sentReadIdsRef.current.has(msgId)) {
|
||||
const seqIdAttr = entry.target.getAttribute('data-sequence-id');
|
||||
if (msgId && seqIdAttr && !sentReadIdsRef.current.has(msgId)) {
|
||||
newlyReadIds.push(msgId);
|
||||
sentReadIdsRef.current.add(msgId);
|
||||
// Stop observing once read
|
||||
observer.unobserve(entry.target);
|
||||
|
||||
const seqId = parseInt(seqIdAttr, 10);
|
||||
if (seqId > highestSequenceId) {
|
||||
highestSequenceId = seqId;
|
||||
highestMsgId = msgId;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (newlyReadIds.length > 0) {
|
||||
console.log('[IntersectionObserver] Marking as read:', newlyReadIds);
|
||||
if (newlyReadIds.length > 0 && highestMsgId) {
|
||||
console.log('[IntersectionObserver] Marking as read up to:', highestSequenceId);
|
||||
socket.emit('read_messages', {
|
||||
chatId: activeChat,
|
||||
messageIds: newlyReadIds,
|
||||
lastReadMessageId: highestMsgId,
|
||||
lastReadSequenceId: highestSequenceId,
|
||||
});
|
||||
// Update local store immediately for current user
|
||||
useChatStore.getState().markRead(activeChat, user.id, newlyReadIds);
|
||||
useChatStore.getState().markRead(activeChat, user.id, highestSequenceId);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -904,6 +913,7 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
<div
|
||||
key={msg.id}
|
||||
data-message-id={msg.id}
|
||||
data-sequence-id={msg.sequenceId}
|
||||
className={`transition-colors duration-500 ${msg.senderId !== user?.id && !msg.readBy?.some(r => r.userId === user?.id) ? 'unread-detector' : ''}`}
|
||||
>
|
||||
{isFirstUnread && (
|
||||
@@ -986,45 +996,70 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
{/* Ввод сообщения */}
|
||||
{activeChat && <MessageInput chatId={activeChat} />}
|
||||
|
||||
{/* Профиль пользователя */}
|
||||
<AnimatePresence>
|
||||
{profileUserId && (
|
||||
<UserProfile
|
||||
userId={profileUserId}
|
||||
chatId={activeChat || undefined}
|
||||
onClose={() => setProfileUserId(null)}
|
||||
onGoToMessage={(msgId) => {
|
||||
const el = document.getElementById(`msg-${msgId}`);
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
el.classList.add('highlight-message');
|
||||
setTimeout(() => el.classList.remove('highlight-message'), 5000);
|
||||
setProfileUserId(null);
|
||||
}
|
||||
}}
|
||||
isSelf={profileUserId === user?.id}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
{(() => {
|
||||
const handleJumpToMessage = async (msgId: string, cleanup?: () => void) => {
|
||||
cleanup?.();
|
||||
const tryScroll = () => {
|
||||
const el = document.getElementById(`msg-${msgId}`);
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
el.classList.add('highlight-message');
|
||||
setTimeout(() => el.classList.remove('highlight-message'), 5000);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
{/* Настройки группы */}
|
||||
<AnimatePresence>
|
||||
{showGroupSettings && chat && chat.type === 'group' && (
|
||||
<GroupSettings
|
||||
chat={chat}
|
||||
onClose={() => setShowGroupSettings(false)}
|
||||
onGoToMessage={(msgId) => {
|
||||
const el = document.getElementById(`msg-${msgId}`);
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
el.classList.add('highlight-message');
|
||||
setTimeout(() => el.classList.remove('highlight-message'), 5000);
|
||||
setShowGroupSettings(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
if (tryScroll()) return;
|
||||
if (!activeChat) return;
|
||||
|
||||
const NotificationStore = await import('../../../../core/application/stores/notificationStore');
|
||||
NotificationStore.useNotificationStore.getState().addNotification('info', 'Поиск сообщения в истории...');
|
||||
|
||||
const chatStore = useChatStore.getState();
|
||||
let found = false;
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (chatStore.hasMoreMessages[activeChat] === false) break;
|
||||
await chatStore.loadMessages(activeChat, false, true);
|
||||
await new Promise(resolve => setTimeout(resolve, 150));
|
||||
if (tryScroll()) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
NotificationStore.useNotificationStore.getState().addNotification('warning', 'Сообщение слишком старое');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Профиль пользователя */}
|
||||
<AnimatePresence>
|
||||
{profileUserId && (
|
||||
<UserProfile
|
||||
userId={profileUserId}
|
||||
chatId={activeChat || undefined}
|
||||
onClose={() => setProfileUserId(null)}
|
||||
onGoToMessage={(msgId) => handleJumpToMessage(msgId, () => setProfileUserId(null))}
|
||||
isSelf={profileUserId === user?.id}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Настройки группы */}
|
||||
<AnimatePresence>
|
||||
{showGroupSettings && chat && chat.type === 'group' && (
|
||||
<GroupSettings
|
||||
chat={chat}
|
||||
onClose={() => setShowGroupSettings(false)}
|
||||
onGoToMessage={(msgId) => handleJumpToMessage(msgId, () => setShowGroupSettings(false))}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
|
||||
<AnimatePresence>
|
||||
{showForwardModal && (
|
||||
|
||||
Reference in New Issue
Block a user