3 Commits
2 changed files with 49 additions and 10 deletions
@@ -187,12 +187,31 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
// Refs and logic for tracking session's first unread message to show the divider exactly once per load // Refs and logic for tracking session's first unread message to show the divider exactly once per load
const sessionUnreadRef = useRef<{ chatId: string, msgId: string | null }>({ chatId: '', msgId: null }); const sessionUnreadRef = useRef<{ chatId: string, msgId: string | null }>({ chatId: '', msgId: null });
if (activeChat && activeChat !== sessionUnreadRef.current.chatId && !isLoadingMessages) { // Update sessionUnreadRef when chat changes OR when messages are marked as read
useEffect(() => {
if (!activeChat || isLoadingMessages) return;
// Reset on chat change
if (activeChat !== sessionUnreadRef.current.chatId) {
const firstUnreadMsg = chatMessages.find( const firstUnreadMsg = chatMessages.find(
(m) => m.senderId !== user?.id && !m.readBy?.some((r) => r.userId === user?.id) (m) => m.senderId !== user?.id && !m.readBy?.some((r) => r.userId === user?.id)
); );
sessionUnreadRef.current = { chatId: activeChat, msgId: firstUnreadMsg ? firstUnreadMsg.id : null }; sessionUnreadRef.current = { chatId: activeChat, msgId: firstUnreadMsg ? firstUnreadMsg.id : null };
} else {
// Update if the first unread message was read (msgId no longer exists in unread list)
const firstUnreadMsg = chatMessages.find(
(m) => m.senderId !== user?.id && !m.readBy?.some((r) => r.userId === user?.id)
);
if (sessionUnreadRef.current.msgId && !firstUnreadMsg) {
// All messages are now read
sessionUnreadRef.current.msgId = null;
} else if (firstUnreadMsg && sessionUnreadRef.current.msgId !== firstUnreadMsg.id) {
// First unread changed (some messages were read)
sessionUnreadRef.current.msgId = firstUnreadMsg.id;
} }
}
}, [activeChat, chatMessages, user?.id, isLoadingMessages]);
const firstUnreadId = activeChat === sessionUnreadRef.current.chatId ? sessionUnreadRef.current.msgId : null; const firstUnreadId = activeChat === sessionUnreadRef.current.chatId ? sessionUnreadRef.current.msgId : null;
const initialScrollChatId = useRef<string | null>(null); const initialScrollChatId = useRef<string | null>(null);
@@ -573,6 +592,7 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
{ {
root: scrollContainerRef.current, root: scrollContainerRef.current,
threshold: 0.1, threshold: 0.1,
rootMargin: '0px',
} }
); );
@@ -584,8 +604,27 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
unreadElements.forEach((el: Element) => { unreadElements.forEach((el: Element) => {
const id = el.getAttribute('data-message-id'); const id = el.getAttribute('data-message-id');
if (id && !sentReadIdsRef.current.has(id)) { if (id && !sentReadIdsRef.current.has(id)) {
// Check if element is already visible
const rect = el.getBoundingClientRect();
const containerRect = scrollContainerRef.current!.getBoundingClientRect();
const isVisible = rect.top >= containerRect.top && rect.bottom <= containerRect.bottom;
if (isVisible) {
// Mark as read immediately without waiting for intersection
const seqId = parseInt(el.getAttribute('data-sequence-id') || '0', 10);
if (seqId > 0) {
socket.emit('read_messages', {
chatId: activeChat,
lastReadMessageId: id,
lastReadSequenceId: seqId,
});
useChatStore.getState().markRead(activeChat, user.id, seqId);
sentReadIdsRef.current.add(id);
}
} else {
observer.observe(el); observer.observe(el);
} }
}
}); });
}; };
@@ -452,7 +452,7 @@ function MessageBubble({
</div> </div>
)} )}
{!isMine && ( {!isMine && activeChat?.type !== 'personal' && activeChat?.type !== 'favorites' && (
<div className="w-8 flex-shrink-0 mr-2 self-end"> <div className="w-8 flex-shrink-0 mr-2 self-end">
{showAvatar ? ( {showAvatar ? (
<button onClick={() => onViewProfile?.(message.senderId)}> <button onClick={() => onViewProfile?.(message.senderId)}>
@@ -469,7 +469,7 @@ function MessageBubble({
)} )}
<div className={`max-[500px]:max-w-[85%] max-w-[75%] lg:max-w-[65%] min-w-0 ${isMine ? 'items-end' : 'items-start'} flex flex-col`}> <div className={`max-[500px]:max-w-[85%] max-w-[75%] lg:max-w-[65%] min-w-0 ${isMine ? 'items-end' : 'items-start'} flex flex-col`}>
{!isMine && showAvatar && ( {!isMine && showAvatar && activeChat?.type !== 'personal' && activeChat?.type !== 'favorites' && (
<button <button
className="text-xs font-medium text-knot-400 ml-3 mb-0.5 hover:underline" className="text-xs font-medium text-knot-400 ml-3 mb-0.5 hover:underline"
onClick={() => onViewProfile?.(message.senderId)} onClick={() => onViewProfile?.(message.senderId)}
@@ -1085,7 +1085,7 @@ function MessageBubble({
)} )}
</div> </div>
{isMine && ( {isMine && activeChat?.type !== 'personal' && activeChat?.type !== 'favorites' && (
<div className="w-8 flex-shrink-0 ml-2 self-end"> <div className="w-8 flex-shrink-0 ml-2 self-end">
{showAvatar ? ( {showAvatar ? (
<button onClick={() => onViewProfile?.(message.senderId)}> <button onClick={() => onViewProfile?.(message.senderId)}>