diff --git a/apps/meteor/client/views/room/MessageList/MessageList.tsx b/apps/meteor/client/views/room/MessageList/MessageList.tsx index bc226f87d7345..3b1e88d809cdc 100644 --- a/apps/meteor/client/views/room/MessageList/MessageList.tsx +++ b/apps/meteor/client/views/room/MessageList/MessageList.tsx @@ -42,7 +42,7 @@ type MessageListProps = { setUnreadCount: Dispatch>; setLastMessageDate: Dispatch>; debouncedClearNewMessagesOnScroll: () => void; - handleDateScroll: (topMessage: IMessage | undefined) => void; + handleDateScroll: (topMessage: IMessage | undefined, offset: number) => void; setShouldJumpToBottom: Dispatch>; debouncedMessageRead: () => void; }; @@ -253,7 +253,7 @@ export const MessageList = function MessageList({ const handle = virtualizerRef.current; const topMessage = handle ? messages[handle.findItemIndex(handle.scrollOffset) - (canPreview ? 1 : 0)] : undefined; handleTopVisibleMessage(topMessage); - handleDateScroll(topMessage); + handleDateScroll(topMessage, offset); debouncedMessageRead(); }} > diff --git a/apps/meteor/client/views/room/contextualBar/Threads/components/ThreadMessageList.tsx b/apps/meteor/client/views/room/contextualBar/Threads/components/ThreadMessageList.tsx index a3161fdae4e9d..073d25d31a770 100644 --- a/apps/meteor/client/views/room/contextualBar/Threads/components/ThreadMessageList.tsx +++ b/apps/meteor/client/views/room/contextualBar/Threads/components/ThreadMessageList.tsx @@ -195,7 +195,7 @@ const ThreadMessageList = ({ mainMessage, shouldJumpToBottom, setShouldJumpToBot isAtBottom.current = offset - handle.scrollSize + handle.viewportSize >= -20; const topMessage = items[handle.findItemIndex(handle.scrollOffset)]; - handleDateScroll(topMessage); + handleDateScroll(topMessage, offset); }} > {loading ? ( diff --git a/apps/meteor/client/views/room/hooks/useDateScroll.ts b/apps/meteor/client/views/room/hooks/useDateScroll.ts index 7c278b079f5f0..e070a6dad6be7 100644 --- a/apps/meteor/client/views/room/hooks/useDateScroll.ts +++ b/apps/meteor/client/views/room/hooks/useDateScroll.ts @@ -7,7 +7,7 @@ import { useRef, useState } from 'react'; import { useDateListController } from '../providers/DateListProvider'; type useDateScrollReturn = { - handleDateScroll: (topMessage: IMessage | undefined) => void; + handleDateScroll: (topMessage: IMessage | undefined, offset: number) => void; bubbleRef: MutableRefObject; listStyle?: ReturnType; } & BubbleDateProps; @@ -19,6 +19,10 @@ export type BubbleDateProps = { bubbleDateStyle?: CSSProperties; }; +// The threshold in pixels to consider a date divider as "visible" when scrolling. +// The divider being a few pixels above the top of the viewport is safe, as it is always contained inside a message +const DATE_DIVIDER_VISIBILITY_THRESHOLD = 100; + export const useDateScroll = (margin = 8): useDateScrollReturn => { const [bubbleDate, setBubbleDate] = useSafely( useState<{ @@ -43,7 +47,7 @@ export const useDateScroll = (margin = 8): useDateScrollReturn => { const hideBubbleTimeoutRef = useRef | null>(null); const handleDateScroll = useDebouncedCallback( - (topMessage: IMessage | undefined) => { + (topMessage: IMessage | undefined, offset: number) => { if (hideBubbleTimeoutRef.current) { clearTimeout(hideBubbleTimeoutRef.current); hideBubbleTimeoutRef.current = null; @@ -54,12 +58,18 @@ export const useDateScroll = (margin = 8): useDateScrollReturn => { type Matched = [string, HTMLElement | undefined, { [key: string]: string | number }?] | []; // Gets the first non visible message date and sets the bubble date to it let matched: Matched = [...list].reduce((ret, divider) => { + const { top, height } = divider.getBoundingClientRect(); + // Some dividers might be kept in the DOM if the "new day" message has a file attached + // So we check if they are actually visible to avoid showing old dates in the bubble + // We also need the parent since it has the actual offset inside the scroll container + const parentOffsetTop = divider.parentElement?.offsetTop; + const parentSafeOffset = parentOffsetTop !== undefined ? parentOffsetTop + DATE_DIVIDER_VISIBILITY_THRESHOLD : 0; + // Sanitize elements - if (!divider.dataset.id) { + if (!divider.dataset.id || parentSafeOffset < offset) { return ret; } - const { top, height } = divider.getBoundingClientRect(); const { id } = divider.dataset; // if the bubble if between the divider and the top, position it at the top of the divider