Skip to content

Commit b9eb846

Browse files
authored
fix: show unread msg banner above unread msg only (#2596)
1 parent 8c65bbf commit b9eb846

File tree

5 files changed

+69
-102
lines changed

5 files changed

+69
-102
lines changed

src/components/MessageList/VirtualizedMessageListComponents.tsx

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { LoadingIndicator as DefaultLoadingIndicator } from '../Loading';
88
import { isMessageEdited, Message } from '../Message';
99

1010
import { StreamMessage, useComponentContext } from '../../context';
11-
import { isDateSeparatorMessage } from './utils';
11+
import { getIsFirstUnreadMessage, isDateSeparatorMessage } from './utils';
1212

1313
import type { GroupStyle } from './utils';
1414
import type { VirtuosoContext } from './VirtualizedMessageList';
@@ -185,29 +185,19 @@ export const messageRenderer = <
185185
shouldGroupByUser &&
186186
(message.user?.id !== maybeNextMessage?.user?.id || isMessageEdited(message));
187187

188-
const createdAtTimestamp = message.created_at && new Date(message.created_at).getTime();
189-
const lastReadTimestamp = lastReadDate?.getTime();
190-
const isFirstMessage = streamMessageIndex === 0;
191-
const isNewestMessage = lastReadMessageId === lastReceivedMessageId;
192-
const isLastReadMessage =
193-
message.id === lastReadMessageId ||
194-
(!unreadMessageCount && createdAtTimestamp === lastReadTimestamp);
195-
const isFirstUnreadMessage =
196-
firstUnreadMessageId === message.id ||
197-
(!!unreadMessageCount &&
198-
createdAtTimestamp &&
199-
lastReadTimestamp &&
200-
createdAtTimestamp > lastReadTimestamp &&
201-
isFirstMessage);
202-
203-
const showUnreadSeparatorAbove = !lastReadMessageId && isFirstUnreadMessage;
204-
205-
const showUnreadSeparatorBelow =
206-
isLastReadMessage && !isNewestMessage && (firstUnreadMessageId || !!unreadMessageCount);
188+
const isFirstUnreadMessage = getIsFirstUnreadMessage({
189+
firstUnreadMessageId,
190+
isFirstMessage: streamMessageIndex === 0,
191+
lastReadDate,
192+
lastReadMessageId,
193+
message,
194+
previousMessage: streamMessageIndex ? messageList[streamMessageIndex - 1] : undefined,
195+
unreadMessageCount,
196+
});
207197

208198
return (
209199
<>
210-
{showUnreadSeparatorAbove && (
200+
{isFirstUnreadMessage && (
211201
<div className='str-chat__unread-messages-separator-wrapper'>
212202
<UnreadMessagesSeparator unreadCount={unreadMessageCount} />
213203
</div>
@@ -233,11 +223,6 @@ export const messageRenderer = <
233223
sortReactions={sortReactions}
234224
threadList={threadList}
235225
/>
236-
{showUnreadSeparatorBelow && (
237-
<div className='str-chat__unread-messages-separator-wrapper'>
238-
<UnreadMessagesSeparator unreadCount={unreadMessageCount} />
239-
</div>
240-
)}
241226
</>
242227
);
243228
};

src/components/MessageList/__tests__/MessageList.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ describe('MessageList', () => {
527527
},
528528
});
529529
});
530-
expect(screen.queryByTestId('unread-messages-separator')).not.toBeInTheDocument();
530+
expect(screen.queryByTestId('unread-messages-separator')).toBeInTheDocument();
531531
});
532532

533533
it('should display custom unread messages separator when channel is marked unread', async () => {
@@ -583,7 +583,7 @@ describe('MessageList', () => {
583583
},
584584
});
585585
});
586-
expect(screen.queryByText(customUnreadMessagesSeparatorText)).not.toBeInTheDocument();
586+
expect(screen.queryByText(customUnreadMessagesSeparatorText)).toBeInTheDocument();
587587
});
588588

589589
describe('notification', () => {

src/components/MessageList/__tests__/VirtualizedMessageListComponents.test.js

Lines changed: 11 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -435,55 +435,22 @@ describe('VirtualizedMessageComponents', () => {
435435
);
436436
};
437437

438-
it('should be rendered below the last read message if unread count is non-zero', async () => {
438+
it('should be rendered above the first unread message if unread count is non-zero', async () => {
439439
const { container } = await renderMarkUnread({
440440
virtuosoContext: {
441+
lastReadDate: new Date(messages[0].created_at),
441442
lastReadMessageId: messages[0].id,
442443
lastReceivedMessageId: messages[1].id,
443444
Message,
444445
messageGroupStyles: {},
445-
numItemsPrepended,
446+
numItemsPrepended: 1,
446447
ownMessagesReadByOthers: {},
447448
processedMessages: messages,
448449
unreadMessageCount: 1,
449450
UnreadMessagesSeparator,
450451
virtuosoRef: { current: {} },
451452
},
452453
});
453-
expect(container).toMatchInlineSnapshot(`
454-
<div>
455-
<div
456-
class="message-component"
457-
/>
458-
<div
459-
class="str-chat__unread-messages-separator-wrapper"
460-
>
461-
<div
462-
class="str-chat__unread-messages-separator"
463-
data-testid="unread-messages-separator"
464-
>
465-
Unread messages
466-
</div>
467-
</div>
468-
</div>
469-
`);
470-
});
471-
472-
it('should be rendered above the last first unread message', async () => {
473-
const { container } = await renderMarkUnread({
474-
virtuosoContext: {
475-
lastReadDate: new Date(1),
476-
lastReceivedMessageId: messages[1].id,
477-
Message,
478-
messageGroupStyles: {},
479-
numItemsPrepended,
480-
ownMessagesReadByOthers: {},
481-
processedMessages: messages,
482-
unreadMessageCount: messages.length,
483-
UnreadMessagesSeparator,
484-
virtuosoRef: { current: {} },
485-
},
486-
});
487454
expect(container).toMatchInlineSnapshot(`
488455
<div>
489456
<div
@@ -506,6 +473,7 @@ describe('VirtualizedMessageComponents', () => {
506473
it('should not be rendered below the last read message if the message is the newest in the channel', async () => {
507474
const { container } = await renderMarkUnread({
508475
virtuosoContext: {
476+
lastReadDate: new Date(messages[1].created_at),
509477
lastReadMessageId: messages[1].id,
510478
lastReceivedMessageId: messages[1].id,
511479
Message,
@@ -527,15 +495,15 @@ describe('VirtualizedMessageComponents', () => {
527495
`);
528496
});
529497

530-
it('should be rendered if unread count is falsy and first unread message is known', async () => {
498+
it('should be rendered if unread count is falsy and the first unread message is known', async () => {
531499
const { container } = await renderMarkUnread({
532500
virtuosoContext: {
533501
firstUnreadMessageId: messages[1].id,
534502
lastReadMessageId: messages[0].id,
535503
lastReceivedMessageId: messages[1].id,
536504
Message,
537505
messageGroupStyles: {},
538-
numItemsPrepended,
506+
numItemsPrepended: 1,
539507
ownMessagesReadByOthers: {},
540508
processedMessages: messages,
541509
unreadMessageCount: 0,
@@ -545,9 +513,6 @@ describe('VirtualizedMessageComponents', () => {
545513
});
546514
expect(container).toMatchInlineSnapshot(`
547515
<div>
548-
<div
549-
class="message-component"
550-
/>
551516
<div
552517
class="str-chat__unread-messages-separator-wrapper"
553518
>
@@ -558,6 +523,9 @@ describe('VirtualizedMessageComponents', () => {
558523
Unread messages
559524
</div>
560525
</div>
526+
<div
527+
class="message-component"
528+
/>
561529
</div>
562530
`);
563531
});
@@ -569,7 +537,7 @@ describe('VirtualizedMessageComponents', () => {
569537
lastReceivedMessageId: messages[1].id,
570538
Message,
571539
messageGroupStyles: {},
572-
numItemsPrepended,
540+
numItemsPrepended: 1,
573541
ownMessagesReadByOthers: {},
574542
processedMessages: messages,
575543
unreadMessageCount: 0,
@@ -593,7 +561,7 @@ describe('VirtualizedMessageComponents', () => {
593561
lastReceivedMessageId: messages[1].id,
594562
Message,
595563
messageGroupStyles: {},
596-
numItemsPrepended: 1,
564+
numItemsPrepended: 0,
597565
ownMessagesReadByOthers: {},
598566
processedMessages: messages,
599567
unreadMessageCount: 1,

src/components/MessageList/renderMessages.tsx

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { Fragment, ReactNode } from 'react';
22

33
import type { UserResponse } from 'stream-chat';
44

5-
import { GroupStyle, isDateSeparatorMessage } from './utils';
5+
import { getIsFirstUnreadMessage, GroupStyle, isDateSeparatorMessage } from './utils';
66
import { Message } from '../Message';
77
import { DateSeparator as DefaultDateSeparator } from '../DateSeparator';
88
import { EventComponent as DefaultMessageSystem } from '../EventComponent';
@@ -75,6 +75,7 @@ export function defaultRenderMessages<
7575

7676
const renderedMessages = [];
7777
let firstMessage;
78+
let previousMessage = undefined;
7879
for (let index = 0; index < messages.length; index++) {
7980
const message = messages[index];
8081
if (isDateSeparatorMessage(message)) {
@@ -106,34 +107,19 @@ export function defaultRenderMessages<
106107
const groupStyles: GroupStyle = messageGroupStyles[message.id] || '';
107108
const messageClass = customClasses?.message || `str-chat__li str-chat__li--${groupStyles}`;
108109

109-
const createdAtTimestamp = message.created_at && new Date(message.created_at).getTime();
110-
const lastReadTimestamp = channelUnreadUiState?.last_read.getTime();
111-
const isFirstMessage = firstMessage?.id && firstMessage.id === message.id;
112-
const isNewestMessage = index === messages.length - 1;
113-
114-
const isLastReadMessage =
115-
channelUnreadUiState?.last_read_message_id === message.id ||
116-
(!channelUnreadUiState?.unread_messages && createdAtTimestamp === lastReadTimestamp);
117-
118-
const isFirstUnreadMessage =
119-
channelUnreadUiState?.first_unread_message_id === message.id ||
120-
(!!channelUnreadUiState?.unread_messages &&
121-
!!createdAtTimestamp &&
122-
!!lastReadTimestamp &&
123-
createdAtTimestamp > lastReadTimestamp &&
124-
isFirstMessage);
125-
126-
const showUnreadSeparatorAbove =
127-
!channelUnreadUiState?.last_read_message_id && isFirstUnreadMessage;
128-
129-
const showUnreadSeparatorBelow =
130-
isLastReadMessage &&
131-
!isNewestMessage &&
132-
(channelUnreadUiState?.first_unread_message_id || !!channelUnreadUiState?.unread_messages); // this part has to be here as we do not mark channel read when sending a message
110+
const isFirstUnreadMessage = getIsFirstUnreadMessage({
111+
firstUnreadMessageId: channelUnreadUiState?.first_unread_message_id,
112+
isFirstMessage: !!firstMessage?.id && firstMessage.id === message.id,
113+
lastReadDate: channelUnreadUiState?.last_read,
114+
lastReadMessageId: channelUnreadUiState?.last_read_message_id,
115+
message,
116+
previousMessage,
117+
unreadMessageCount: channelUnreadUiState?.unread_messages,
118+
});
133119

134120
renderedMessages.push(
135121
<Fragment key={message.id || (message.created_at as string)}>
136-
{showUnreadSeparatorAbove && UnreadMessagesSeparator && (
122+
{isFirstUnreadMessage && UnreadMessagesSeparator && (
137123
<li className='str-chat__li str-chat__unread-messages-separator-wrapper'>
138124
<UnreadMessagesSeparator unreadCount={channelUnreadUiState?.unread_messages} />
139125
</li>
@@ -147,13 +133,9 @@ export function defaultRenderMessages<
147133
{...messageProps}
148134
/>
149135
</li>
150-
{showUnreadSeparatorBelow && UnreadMessagesSeparator && (
151-
<li className='str-chat__li str-chat__unread-messages-separator-wrapper'>
152-
<UnreadMessagesSeparator unreadCount={channelUnreadUiState?.unread_messages} />
153-
</li>
154-
)}
155136
</Fragment>,
156137
);
138+
previousMessage = message;
157139
}
158140
}
159141
return renderedMessages;

src/components/MessageList/utils.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,3 +373,35 @@ export function isDateSeparatorMessage<
373373
>(message: StreamMessage<StreamChatGenerics>): message is DateSeparatorMessage {
374374
return message.customType === CUSTOM_MESSAGE_TYPE.date && !!message.date && isDate(message.date);
375375
}
376+
377+
export const getIsFirstUnreadMessage = ({
378+
firstUnreadMessageId,
379+
isFirstMessage,
380+
lastReadDate,
381+
lastReadMessageId,
382+
message,
383+
previousMessage,
384+
unreadMessageCount = 0,
385+
}: {
386+
isFirstMessage: boolean;
387+
message: StreamMessage;
388+
firstUnreadMessageId?: string;
389+
lastReadDate?: Date;
390+
lastReadMessageId?: string;
391+
previousMessage?: StreamMessage;
392+
unreadMessageCount?: number;
393+
}) => {
394+
const createdAtTimestamp = message.created_at && new Date(message.created_at).getTime();
395+
const lastReadTimestamp = lastReadDate?.getTime();
396+
397+
const messageIsUnread =
398+
!!createdAtTimestamp && !!lastReadTimestamp && createdAtTimestamp > lastReadTimestamp;
399+
400+
const previousMessageIsLastRead =
401+
!!lastReadMessageId && lastReadMessageId === previousMessage?.id;
402+
403+
return (
404+
firstUnreadMessageId === message.id ||
405+
(!!unreadMessageCount && messageIsUnread && (isFirstMessage || previousMessageIsLastRead))
406+
);
407+
};

0 commit comments

Comments
 (0)