Skip to content

Commit d40ab64

Browse files
authored
fix: jump to first unread message when the last read and first unread message id is unknown (#2315)
1 parent e098ef1 commit d40ab64

12 files changed

+677
-244
lines changed

src/components/Channel/Channel.tsx

+100-33
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,15 @@ import { TypingProvider } from '../../context/TypingContext';
6666

6767
import {
6868
DEFAULT_INITIAL_CHANNEL_PAGE_SIZE,
69+
DEFAULT_JUMP_TO_PAGE_SIZE,
6970
DEFAULT_NEXT_CHANNEL_PAGE_SIZE,
7071
DEFAULT_THREAD_PAGE_SIZE,
7172
} from '../../constants/limits';
7273

7374
import type { UnreadMessagesNotificationProps } from '../MessageList';
7475
import { hasMoreMessagesProbably, UnreadMessagesSeparator } from '../MessageList';
7576
import { useChannelContainerClasses } from './hooks/useChannelContainerClasses';
76-
import { makeAddNotifications } from './utils';
77+
import { findInMsgSetByDate, findInMsgSetById, makeAddNotifications } from './utils';
7778
import { getChannel } from '../../utils';
7879

7980
import type { MessageProps } from '../Message/types';
@@ -554,6 +555,10 @@ const ChannelInner = <
554555
};
555556
});
556557

558+
if (event.type === 'channel.truncated' && event.cid === channel.cid) {
559+
_setChannelUnreadUiState(undefined);
560+
}
561+
557562
throttledCopyStateFromChannel();
558563
};
559564

@@ -709,7 +714,7 @@ const ChannelInner = <
709714
return queryResponse.messages.length;
710715
};
711716

712-
const loadMoreNewer = async (limit = 100) => {
717+
const loadMoreNewer = async (limit = DEFAULT_NEXT_CHANNEL_PAGE_SIZE) => {
713718
if (!online.current || !window.navigator.onLine || !state.hasMoreNewer) return 0;
714719

715720
const newestMessage = state?.messages?.[state?.messages?.length - 1];
@@ -744,7 +749,7 @@ const ChannelInner = <
744749

745750
const clearHighlightedMessageTimeoutId = useRef<ReturnType<typeof setTimeout> | null>(null);
746751

747-
const jumpToMessage = async (messageId: string, messageLimit = 100) => {
752+
const jumpToMessage = async (messageId: string, messageLimit = DEFAULT_JUMP_TO_PAGE_SIZE) => {
748753
dispatch({ loadingMore: true, type: 'setLoadingMore' });
749754
await channel.state.loadMessageIntoState(messageId, undefined, messageLimit);
750755

@@ -783,56 +788,118 @@ const ChannelInner = <
783788
};
784789

785790
const jumpToFirstUnreadMessage = useCallback(
786-
async (queryMessageLimit = 100) => {
787-
if (!(client.user && channelUnreadUiState?.unread_messages)) return;
788-
if (!channelUnreadUiState?.last_read_message_id) {
789-
addNotification(t('Failed to jump to the first unread message'), 'error');
790-
return;
791-
}
792-
793-
let indexOfLastReadMessage;
791+
async (queryMessageLimit = DEFAULT_JUMP_TO_PAGE_SIZE) => {
792+
if (!channelUnreadUiState?.unread_messages) return;
793+
let lastReadMessageId = channelUnreadUiState?.last_read_message_id;
794+
let firstUnreadMessageId = channelUnreadUiState?.first_unread_message_id;
795+
let isInCurrentMessageSet = false;
796+
let hasMoreMessages = true;
797+
798+
if (firstUnreadMessageId) {
799+
const result = findInMsgSetById(firstUnreadMessageId, channel.state.messages);
800+
isInCurrentMessageSet = result.index !== -1;
801+
} else if (lastReadMessageId) {
802+
const result = findInMsgSetById(lastReadMessageId, channel.state.messages);
803+
isInCurrentMessageSet = !!result.target;
804+
firstUnreadMessageId =
805+
result.index > -1 ? channel.state.messages[result.index + 1]?.id : undefined;
806+
} else {
807+
const lastReadTimestamp = channelUnreadUiState.last_read.getTime();
808+
const { index: lastReadMessageIndex, target: lastReadMessage } = findInMsgSetByDate(
809+
channelUnreadUiState.last_read,
810+
channel.state.messages,
811+
true,
812+
);
813+
814+
if (lastReadMessage) {
815+
firstUnreadMessageId = channel.state.messages[lastReadMessageIndex + 1]?.id;
816+
isInCurrentMessageSet = !!firstUnreadMessageId;
817+
lastReadMessageId = lastReadMessage.id;
818+
} else {
819+
dispatch({ loadingMore: true, type: 'setLoadingMore' });
820+
let messages;
821+
try {
822+
messages = (
823+
await channel.query(
824+
{
825+
messages: {
826+
created_at_around: channelUnreadUiState.last_read.toISOString(),
827+
limit: queryMessageLimit,
828+
},
829+
},
830+
'new',
831+
)
832+
).messages;
833+
} catch (e) {
834+
addNotification(t('Failed to jump to the first unread message'), 'error');
835+
loadMoreFinished(hasMoreMessages, channel.state.messages);
836+
return;
837+
}
794838

795-
const currentMessageSet = channel.state.messages;
796-
for (let i = currentMessageSet.length - 1; i >= 0; i--) {
797-
const { id } = currentMessageSet[i];
798-
if (id === channelUnreadUiState.last_read_message_id) {
799-
indexOfLastReadMessage = i;
800-
break;
839+
const firstMessageWithCreationDate = messages.find((msg) => msg.created_at);
840+
if (!firstMessageWithCreationDate) {
841+
addNotification(t('Failed to jump to the first unread message'), 'error');
842+
loadMoreFinished(hasMoreMessages, channel.state.messages);
843+
return;
844+
}
845+
const firstMessageTimestamp = new Date(
846+
firstMessageWithCreationDate.created_at as string,
847+
).getTime();
848+
if (lastReadTimestamp < firstMessageTimestamp) {
849+
// whole channel is unread
850+
firstUnreadMessageId = firstMessageWithCreationDate.id;
851+
hasMoreMessages = false;
852+
} else {
853+
const result = findInMsgSetByDate(channelUnreadUiState.last_read, messages);
854+
lastReadMessageId = result.target?.id;
855+
hasMoreMessages = result.index >= Math.floor(queryMessageLimit / 2);
856+
}
857+
loadMoreFinished(hasMoreMessages, channel.state.messages);
801858
}
802859
}
803860

804-
if (typeof indexOfLastReadMessage === 'undefined') {
861+
if (!firstUnreadMessageId && !lastReadMessageId) {
862+
addNotification(t('Failed to jump to the first unread message'), 'error');
863+
return;
864+
}
865+
866+
if (!isInCurrentMessageSet) {
805867
dispatch({ loadingMore: true, type: 'setLoadingMore' });
806-
let hasMoreMessages = true;
807868
try {
808-
await channel.state.loadMessageIntoState(
809-
channelUnreadUiState.last_read_message_id,
810-
undefined,
811-
queryMessageLimit,
812-
);
869+
const targetId = (firstUnreadMessageId ?? lastReadMessageId) as string;
870+
await channel.state.loadMessageIntoState(targetId, undefined, queryMessageLimit);
813871
/**
814872
* if the index of the last read message on the page is beyond the half of the page,
815873
* we have arrived to the oldest page of the channel
816874
*/
817-
indexOfLastReadMessage = channel.state.messages.findIndex(
818-
(message) => message.id === channelUnreadUiState.last_read_message_id,
875+
const indexOfTarget = channel.state.messages.findIndex(
876+
(message) => message.id === targetId,
819877
) as number;
820-
hasMoreMessages = indexOfLastReadMessage >= Math.floor(queryMessageLimit / 2);
878+
hasMoreMessages = indexOfTarget >= Math.floor(queryMessageLimit / 2);
879+
loadMoreFinished(hasMoreMessages, channel.state.messages);
880+
firstUnreadMessageId =
881+
firstUnreadMessageId ?? channel.state.messages[indexOfTarget + 1]?.id;
821882
} catch (e) {
822883
addNotification(t('Failed to jump to the first unread message'), 'error');
823884
loadMoreFinished(hasMoreMessages, channel.state.messages);
824885
return;
825886
}
826-
827-
loadMoreFinished(hasMoreMessages, channel.state.messages);
828887
}
829888

830-
const firstUnreadMessage = channel.state.messages[indexOfLastReadMessage + 1];
831-
const jumpToMessageId = firstUnreadMessage?.id ?? channelUnreadUiState.last_read_message_id;
889+
if (!firstUnreadMessageId) {
890+
addNotification(t('Failed to jump to the first unread message'), 'error');
891+
return;
892+
}
893+
if (!channelUnreadUiState.first_unread_message_id)
894+
_setChannelUnreadUiState({
895+
...channelUnreadUiState,
896+
first_unread_message_id: firstUnreadMessageId,
897+
last_read_message_id: lastReadMessageId,
898+
});
832899

833900
dispatch({
834901
hasMoreNewer: channel.state.messages !== channel.state.latestMessages,
835-
highlightedMessageId: jumpToMessageId,
902+
highlightedMessageId: firstUnreadMessageId,
836903
type: 'jumpToMessageFinished',
837904
});
838905

@@ -845,7 +912,7 @@ const ChannelInner = <
845912
dispatch({ type: 'clearHighlightedMessage' });
846913
}, 500);
847914
},
848-
[addNotification, channel, client, loadMoreFinished, t, channelUnreadUiState],
915+
[addNotification, channel, loadMoreFinished, t, channelUnreadUiState],
849916
);
850917

851918
const deleteMessage = useCallback(

0 commit comments

Comments
 (0)