Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/app/components/sidebar/SidebarUnreadBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ type SidebarUnreadBadgeProps = {
highlight?: boolean;
count: number;
dm?: boolean;
estimated?: boolean;
mode?: UnreadBadgeMode;
};

export function SidebarUnreadBadge({
highlight,
count,
dm,
estimated,
mode,
}: Readonly<SidebarUnreadBadgeProps>) {
const [showUnreadCounts] = useSetting(settingsAtom, 'showUnreadCounts');
Expand All @@ -26,6 +28,7 @@ export function SidebarUnreadBadge({
highlight,
count,
dm,
estimated,
showUnreadCounts,
badgeCountDMsOnly,
showPingCounts,
Expand Down
12 changes: 12 additions & 0 deletions src/app/components/unread-badge/UnreadBadge.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ describe('resolveUnreadBadgeMode', () => {
).toBe('count');
});

it('returns dot for an estimated count even when unread counts are enabled', () => {
expect(
resolveUnreadBadgeMode({
count: 1,
estimated: true,
showUnreadCounts: true,
badgeCountDMsOnly: false,
showPingCounts: false,
})
).toBe('dot');
});

it('returns dot for a room unread when unread counts are disabled', () => {
expect(
resolveUnreadBadgeMode({
Expand Down
7 changes: 6 additions & 1 deletion src/app/components/unread-badge/UnreadBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type UnreadBadgeProps = {
count: number;
/** Whether this badge belongs to a DM room. Used with the badgeCountDMsOnly setting. */
dm?: boolean;
estimated?: boolean;
mode?: UnreadBadgeMode;
};

Expand All @@ -28,18 +29,21 @@ export type UnreadBadgeMode = 'dot' | 'count';
* @param options.showUnreadCounts Whether regular room unread badges should show counts.
* @param options.badgeCountDMsOnly Whether direct message unread badges should show counts.
* @param options.showPingCounts Whether highlight badges should show counts.
* @param options.estimated Whether the count is a placeholder awaiting backfill.
* @returns `'count'` when the current badge context is allowed to show a number, otherwise `'dot'`.
*/
export function resolveUnreadBadgeMode({
highlight,
count,
dm,
estimated,
showUnreadCounts,
badgeCountDMsOnly,
showPingCounts,
}: ResolveUnreadBadgeModeOptions): UnreadBadgeMode {
const showNumber =
count > 0 &&
!estimated &&
((dm && badgeCountDMsOnly) || (!dm && showUnreadCounts) || (highlight && showPingCounts));

return showNumber ? 'count' : 'dot';
Expand Down Expand Up @@ -68,7 +72,7 @@ export function UnreadBadgeCenter({ children }: { children: ReactNode }) {
);
}

export function UnreadBadge({ highlight, count, dm, mode }: UnreadBadgeProps) {
export function UnreadBadge({ highlight, count, dm, estimated, mode }: UnreadBadgeProps) {
const [showUnreadCounts] = useSetting(settingsAtom, 'showUnreadCounts');
const [badgeCountDMsOnly] = useSetting(settingsAtom, 'badgeCountDMsOnly');
const [showPingCounts] = useSetting(settingsAtom, 'showPingCounts');
Expand All @@ -79,6 +83,7 @@ export function UnreadBadge({ highlight, count, dm, mode }: UnreadBadgeProps) {
highlight,
count,
dm,
estimated,
showUnreadCounts,
badgeCountDMsOnly,
showPingCounts,
Expand Down
2 changes: 2 additions & 0 deletions src/app/features/room-nav/RoomNavItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,7 @@ export function RoomNavItem({
<SidebarUnreadBadge
highlight={unread.highlight > 0}
count={unread.highlight > 0 ? unread.highlight : unread.total}
estimated={unread.estimated}
/>
)}

Expand Down Expand Up @@ -601,6 +602,7 @@ export function RoomNavItem({
highlight={!!unread && unread.highlight > 0}
count={unreadCount}
dm={direct}
estimated={unread?.estimated}
/>
</UnreadBadgeCenter>
)}
Expand Down
28 changes: 28 additions & 0 deletions src/app/features/room/RoomTimeline.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1117,6 +1117,34 @@ describe('unread read marker (normal sync)', () => {
await act(() => new Promise((resolve) => requestAnimationFrame(resolve)));
expect(markAsReadMock).not.toHaveBeenCalled();
});

it('does not mark the room read before the initial scroll settles', async () => {
getRoomUnreadInfoMock.mockReturnValue(undefined);
windowFocused.current = true;

renderTimeline();
await act(() => new Promise((resolve) => requestAnimationFrame(resolve)));

expect(markAsReadMock).not.toHaveBeenCalled();
});

it('resolves the read marker when the boundary loads after mount', async () => {
getRoomUnreadInfoMock.mockReturnValue(undefined);
const { rerender } = renderTimeline();
await settleInitialScroll();

expect(processedTimelineOptions.current?.readUptoEventId).toBeUndefined();

getRoomUnreadInfoMock.mockReturnValue({
readUptoEventId: '$read:example.org',
inLiveTimeline: true,
scrollTo: false,
});
timelineSync.eventsLength = 2;
rerender(<RoomTimeline room={room} editor={{} as Editor} />);

expect(processedTimelineOptions.current?.readUptoEventId).toBe('$read:example.org');
});
});

describe('unread read marker (sliding sync)', () => {
Expand Down
13 changes: 12 additions & 1 deletion src/app/features/room/RoomTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,7 @@ export function RoomTimeline({

const readUptoEventIdRef = useRef<string | undefined>(undefined);
if (unreadInfo) readUptoEventIdRef.current = unreadInfo.readUptoEventId;
const unreadResolvedRef = useRef(unreadInfo !== undefined);
const hideReadsRef = useRef(hideReads);
hideReadsRef.current = hideReads;

Expand Down Expand Up @@ -895,6 +896,15 @@ export function RoomTimeline({
setAtBottom(true);
}, [eventId, focusLiveTimeline, setAtBottom]);

useEffect(() => {
if (unreadResolvedRef.current) return;
const resolved = getRoomUnreadInfo(room, !isReady);
if (!resolved) return;
unreadResolvedRef.current = true;
readUptoEventIdRef.current = resolved.readUptoEventId;
setUnreadInfo(resolved);
}, [room, isReady, timelineSync.eventsLength]);

useEffect(() => {
if (eventId) return;
if (isReady) return;
Expand Down Expand Up @@ -1048,6 +1058,7 @@ export function RoomTimeline({

const tryAutoMarkAsRead = useCallback(() => {
if (isInactivePanel) return; // Don't clear unread while room is behind the list
if (!isReady) return;
if (!atBottomRef.current) return;
if (!readUptoEventIdRef.current) {
requestAnimationFrame(() => markAsRead(mx, room.roomId, hideReads));
Expand All @@ -1058,7 +1069,7 @@ export function RoomTimeline({
if (latestTimeline === room.getLiveTimeline()) {
requestAnimationFrame(() => markAsRead(mx, room.roomId, hideReads));
}
}, [mx, room, hideReads, isInactivePanel]);
}, [mx, room, hideReads, isInactivePanel, isReady]);

useDocumentFocusChange(
useCallback(
Expand Down
Loading
Loading