diff --git a/app/sql.js b/app/sql.js index 6cd7915c7..556f8192e 100644 --- a/app/sql.js +++ b/app/sql.js @@ -71,6 +71,7 @@ module.exports = { getOutgoingWithoutExpiresAt, getNextExpiringMessage, getMessagesByConversation, + getLastMessagesByConversation, getFirstUnreadMessageIdInConversation, hasConversationOutgoingMessage, trimMessages, @@ -2230,27 +2231,83 @@ function getUnreadCountByConversation(conversationId) { // Note: Sorting here is necessary for getting the last message (with limit 1) // be sure to update the sorting order to sort messages on redux too (sortMessages) +const orderByClause = + 'ORDER BY serverTimestamp DESC, serverId DESC, sent_at DESC, received_at DESC'; +function getMessagesByConversation(conversationId, { messageId = null } = {}) { + const absLimit = 30; + // If messageId is given it means we are opening the conversation to that specific messageId, + // or that we just scrolled to it by a quote click and needs to load around it. + // If messageId is null, it means we are just opening the convo to the last unread message, or at the bottom + const firstUnread = getFirstUnreadMessageIdInConversation(conversationId); + + if (messageId || firstUnread) { + const messageFound = getMessageById(messageId || firstUnread); + + if (messageFound && messageFound.conversationId === conversationId) { + console.warn('firstUnread', messageId, firstUnread, messageFound); + + const rows = globalInstance + .prepare( + `WITH cte AS ( + SELECT id, json, row_number() OVER (${orderByClause}) as row_number + FROM messages + ), current AS ( + SELECT row_number + FROM cte + WHERE id = $messageId + ) + SELECT cte.* + FROM cte, current + WHERE ABS(cte.row_number - current.row_number) <= $limit + ORDER BY cte.row_number; + ` + ) + .all({ + conversationId, + messageId: messageId || firstUnread, + limit: absLimit, + }); + console.warn('rows', rows); + return map(rows, row => jsonToObject(row.json)); + } + console.warn( + `getMessagesByConversation: Could not find messageId ${messageId} in db with conversationId: ${conversationId}. Just fetching the convo as usual.` + ); + } -function getMessagesByConversation( - conversationId, - { limit = 100, receivedAt = Number.MAX_VALUE, type = '%' } = {} -) { const rows = globalInstance .prepare( ` SELECT json FROM ${MESSAGES_TABLE} WHERE - conversationId = $conversationId AND - received_at < $received_at AND - type LIKE $type - ORDER BY serverTimestamp DESC, serverId DESC, sent_at DESC, received_at DESC + conversationId = $conversationId + ${orderByClause} + LIMIT $limit; + ` + ) + .all({ + conversationId, + limit: 2 * absLimit, + }); + return map(rows, row => jsonToObject(row.json)); +} + +function getLastMessagesByConversation(conversationId, limit) { + if (!isNumber(limit)) { + throw new Error('limit must be a number'); + } + + const rows = globalInstance + .prepare( + ` + SELECT json FROM ${MESSAGES_TABLE} WHERE + conversationId = $conversationId + ${orderByClause} LIMIT $limit; ` ) .all({ conversationId, - received_at: receivedAt, limit, - type, }); return map(rows, row => jsonToObject(row.json)); } diff --git a/ts/components/conversation/SessionConversation.tsx b/ts/components/conversation/SessionConversation.tsx index 5e3f2ae8e..e2405bd50 100644 --- a/ts/components/conversation/SessionConversation.tsx +++ b/ts/components/conversation/SessionConversation.tsx @@ -87,7 +87,7 @@ export class SessionConversation extends React.Component { }; this.messageContainerRef = React.createRef(); this.dragCounter = 0; - this.updateMemberList = _.debounce(this.updateMemberListBouncy.bind(this), 1000); + this.updateMemberList = _.debounce(this.updateMemberListBouncy.bind(this), 10000); autoBind(this); } diff --git a/ts/components/conversation/SessionMessagesList.tsx b/ts/components/conversation/SessionMessagesList.tsx index cf5d7bcc8..cf40fc2b9 100644 --- a/ts/components/conversation/SessionMessagesList.tsx +++ b/ts/components/conversation/SessionMessagesList.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useLayoutEffect } from 'react'; import { useSelector } from 'react-redux'; // tslint:disable-next-line: no-submodule-imports import useKey from 'react-use/lib/useKey'; @@ -9,7 +9,10 @@ import { PropsForGroupInvitation, PropsForGroupUpdate, } from '../../state/ducks/conversations'; -import { getSortedMessagesTypesOfSelectedConversation } from '../../state/selectors/conversations'; +import { + getOldTopMessageId, + getSortedMessagesTypesOfSelectedConversation, +} from '../../state/selectors/conversations'; import { GroupUpdateMessage } from './message/message-item/GroupUpdateMessage'; import { DataExtractionNotification } from './message/message-item/DataExtractionNotification'; import { MessageDateBreak } from './message/message-item/DateBreak'; @@ -26,12 +29,32 @@ function isNotTextboxEvent(e: KeyboardEvent) { export const SessionMessagesList = (props: { scrollToQuoteMessage: (options: QuoteClickOptions) => Promise; + scrollAfterLoadMore: ( + messageIdToScrollTo: string, + block: ScrollLogicalPosition | undefined + ) => void; onPageUpPressed: () => void; onPageDownPressed: () => void; onHomePressed: () => void; onEndPressed: () => void; }) => { const messagesProps = useSelector(getSortedMessagesTypesOfSelectedConversation); + const oldTopMessageId = useSelector(getOldTopMessageId); + + useLayoutEffect(() => { + const newTopMessageId = messagesProps.length + ? messagesProps[messagesProps.length - 1].message.props.messageId + : undefined; + console.warn('useLayoutEffect ', { + oldTopMessageId, + newTopMessageId, + length: messagesProps.length, + }); + + if (oldTopMessageId !== newTopMessageId && oldTopMessageId && newTopMessageId) { + props.scrollAfterLoadMore(oldTopMessageId, 'center'); + } + }); useKey('PageUp', () => { props.onPageUpPressed(); diff --git a/ts/components/conversation/SessionMessagesListContainer.tsx b/ts/components/conversation/SessionMessagesListContainer.tsx index cf0b48fa0..ead2e6c66 100644 --- a/ts/components/conversation/SessionMessagesListContainer.tsx +++ b/ts/components/conversation/SessionMessagesListContainer.tsx @@ -95,56 +95,59 @@ class SessionMessagesListContainerInner extends React.Component { public componentDidUpdate( prevProps: Props, - _prevState: any, - snapShot: { fakeScrollTop: number; realScrollTop: number; scrollHeight: number } + _prevState: any + // snapShot: { + // fakeScrollTop: number; + // realScrollTop: number; + // scrollHeight: number; + // oldTopMessageId?: string; + // } ) { - // this was hard to write, it should be hard to read - // just make sure you don't remove that as a bug in chrome makes the column-reverse do bad things - // https://bugs.chromium.org/p/chromium/issues/detail?id=1189195&q=column-reverse&can=2#makechanges - const currentRef = this.props.messageContainerRef.current; + // const { oldTopMessageId } = snapShot; + // console.warn('didupdate with oldTopMessageId', oldTopMessageId); + // // If you want to mess with this, be my guest. + // // just make sure you don't remove that as a bug in chrome makes the column-reverse do bad things + // // https://bugs.chromium.org/p/chromium/issues/detail?id=1189195&q=column-reverse&can=2#makechanges const isSameConvo = prevProps.conversationKey === this.props.conversationKey; - const prevMsgLength = prevProps.messagesProps.length; - const newMsgLength = this.props.messagesProps.length; - - const prevFirstMesssageId = prevProps.messagesProps[0]?.propsForMessage.id; - const newFirstMesssageId = this.props.messagesProps[0]?.propsForMessage.id; - const messageAddedWasMoreRecentOne = prevFirstMesssageId !== newFirstMesssageId; - - if (isSameConvo && snapShot?.realScrollTop && prevMsgLength !== newMsgLength && currentRef) { - if (messageAddedWasMoreRecentOne) { - if (snapShot.scrollHeight - snapShot.realScrollTop < 50) { - // consider that we were scrolled to bottom - currentRef.scrollTop = 0; - } else { - currentRef.scrollTop = -(currentRef.scrollHeight - snapShot.realScrollTop); - } - } else { - currentRef.scrollTop = snapShot.fakeScrollTop; - } - } - if (!isSameConvo || (prevMsgLength === 0 && newMsgLength !== 0)) { + // if (isSameConvo && oldTopMessageId) { + // this.scrollToMessage(oldTopMessageId, 'center'); + // // if (messageAddedWasMoreRecentOne) { + // // if (snapShot.scrollHeight - snapShot.realScrollTop < 50) { + // // // consider that we were scrolled to bottom + // // currentRef.scrollTop = 0; + // // } else { + // // currentRef.scrollTop = -(currentRef.scrollHeight - snapShot.realScrollTop); + // // } + // // } else { + // // currentRef.scrollTop = snapShot.fakeScrollTop; + // // } + // } + if (!isSameConvo) { + console.info('Not same convo, resetting scrolling posiiton'); this.setupTimeoutResetQuotedHighlightedMessage(this.props.animateQuotedMessageId); - // displayed conversation changed. We have a bit of cleaning to do here this.initialMessageLoadingPosition(); } } public getSnapshotBeforeUpdate() { - const messageContainer = this.props.messageContainerRef.current; - - const scrollTop = messageContainer?.scrollTop || undefined; - const scrollHeight = messageContainer?.scrollHeight || undefined; - - // as we use column-reverse for displaying message list - // the top is < 0 - // tslint:disable-next-line: restrict-plus-operands - const realScrollTop = scrollHeight && scrollTop ? scrollHeight + scrollTop : undefined; - return { - realScrollTop, - fakeScrollTop: scrollTop, - scrollHeight: scrollHeight, - }; + // const messagePropsBeforeUpdate = this.props.messagesProps; + // const oldTopMessageId = messagePropsBeforeUpdate.length + // ? messagePropsBeforeUpdate[messagePropsBeforeUpdate.length - 1].propsForMessage.id + // : undefined; + // console.warn('oldTopMessageId', oldTopMessageId); + // const messageContainer = this.props.messageContainerRef.current; + // const scrollTop = messageContainer?.scrollTop || undefined; + // const scrollHeight = messageContainer?.scrollHeight || undefined; + // // as we use column-reverse for displaying message list + // // the top is < 0 + // const realScrollTop = scrollHeight && scrollTop ? scrollHeight + scrollTop : undefined; + // return { + // realScrollTop, + // fakeScrollTop: scrollTop, + // scrollHeight: scrollHeight, + // oldTopMessageId, + // }; } public render() { @@ -180,6 +183,7 @@ class SessionMessagesListContainerInner extends React.Component { { return; } + console.warn('firstUnreadOnOpen', firstUnreadOnOpen); + if ( (conversation.unreadCount && conversation.unreadCount <= 0) || firstUnreadOnOpen === undefined @@ -218,6 +224,7 @@ class SessionMessagesListContainerInner extends React.Component { const firstUnreadIndex = messagesProps.findIndex( m => m.propsForMessage.id === firstUnreadOnOpen ); + console.warn('firstUnreadIndex', firstUnreadIndex); if (firstUnreadIndex === -1) { // the first unread message is not in the 30 most recent messages @@ -260,8 +267,10 @@ class SessionMessagesListContainerInner extends React.Component { } } - private scrollToMessage(messageId: string, block: 'center' | 'end' | 'nearest' | 'start') { + private scrollToMessage(messageId: string, block: ScrollLogicalPosition | undefined) { const messageElementDom = document.getElementById(`msg-${messageId}`); + console.warn('scrollToMessage', messageElementDom); + debugger; messageElementDom?.scrollIntoView({ behavior: 'auto', block, diff --git a/ts/components/conversation/SessionRightPanel.tsx b/ts/components/conversation/SessionRightPanel.tsx index 0393b777a..d9a116182 100644 --- a/ts/components/conversation/SessionRightPanel.tsx +++ b/ts/components/conversation/SessionRightPanel.tsx @@ -37,6 +37,7 @@ async function getMediaGalleryProps( documents: Array; media: Array; }> { + console.warn('getMediaGalleryProps'); // We fetch more documents than media as they don’t require to be loaded // into memory right away. Revisit this once we have infinite scrolling: const rawMedia = await getMessagesWithVisualMediaAttachments(conversationId, { diff --git a/ts/components/conversation/message/message-content/ClickToTrustSender.tsx b/ts/components/conversation/message/message-content/ClickToTrustSender.tsx index 8b97c4f3d..3787b3c02 100644 --- a/ts/components/conversation/message/message-content/ClickToTrustSender.tsx +++ b/ts/components/conversation/message/message-content/ClickToTrustSender.tsx @@ -1,6 +1,6 @@ import React from 'react'; import styled from 'styled-components'; -import { getMessageById, getMessagesByConversation } from '../../../../data/data'; +import { getLastMessagesByConversation, getMessageById } from '../../../../data/data'; import { getConversationController } from '../../../../session/conversations'; import { AttachmentDownloads } from '../../../../session/utils'; import { updateConfirmModal } from '../../../../state/ducks/modalDialog'; @@ -42,9 +42,7 @@ export const ClickToTrustSender = (props: { messageId: string }) => { onClickOk: async () => { convo.set({ isTrustedForAttachmentDownload: true }); await convo.commit(); - const messagesInConvo = await getMessagesByConversation(convo.id, { - limit: 100, - }); + const messagesInConvo = await getLastMessagesByConversation(convo.id, 100, false); await Promise.all( messagesInConvo.map(async message => { diff --git a/ts/components/conversation/message/message-item/GenericReadableMessage.tsx b/ts/components/conversation/message/message-item/GenericReadableMessage.tsx index df4136e95..1cb527a64 100644 --- a/ts/components/conversation/message/message-item/GenericReadableMessage.tsx +++ b/ts/components/conversation/message/message-item/GenericReadableMessage.tsx @@ -12,7 +12,6 @@ import { messageExpired } from '../../../../state/ducks/conversations'; import { getGenericReadableMessageSelectorProps, getIsMessageSelected, - getQuotedMessageToAnimate, isMessageSelectionMode, } from '../../../../state/selectors/conversations'; import { getIncrement } from '../../../../util/timer'; diff --git a/ts/components/conversation/message/message-item/ReadableMessage.tsx b/ts/components/conversation/message/message-item/ReadableMessage.tsx index 2b515a774..6fde6ea8f 100644 --- a/ts/components/conversation/message/message-item/ReadableMessage.tsx +++ b/ts/components/conversation/message/message-item/ReadableMessage.tsx @@ -3,15 +3,14 @@ import React, { useCallback } from 'react'; import { InView } from 'react-intersection-observer'; import { useDispatch, useSelector } from 'react-redux'; import { getMessageById } from '../../../../data/data'; -import { Constants } from '../../../../session'; import { getConversationController } from '../../../../session/conversations'; import { - fetchMessagesForConversation, + fetchTopMessagesForConversation, markConversationFullyRead, showScrollToBottomButton, } from '../../../../state/ducks/conversations'; import { - areMoreMessagesBeingFetched, + areMoreTopMessagesBeingFetched, getHaveDoneFirstScroll, getLoadedMessagesLength, getMostRecentMessageId, @@ -29,13 +28,12 @@ type ReadableMessageProps = { onContextMenu?: (e: React.MouseEvent) => void; }; -const debouncedTriggerLoadMore = _.debounce( - (loadedMessagesLength: number, selectedConversationKey: string | undefined) => { - const numMessages = loadedMessagesLength + Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT; +const debouncedTriggerLoadMoreTop = _.debounce( + (selectedConversationKey: string, oldestMessageId: string) => { (window.inboxStore?.dispatch as any)( - fetchMessagesForConversation({ - conversationKey: selectedConversationKey as string, - count: numMessages, + fetchTopMessagesForConversation({ + conversationKey: selectedConversationKey, + oldTopMessageId: oldestMessageId, }) ); }, @@ -53,7 +51,7 @@ export const ReadableMessage = (props: ReadableMessageProps) => { const haveDoneFirstScroll = useSelector(getHaveDoneFirstScroll); const mostRecentMessageId = useSelector(getMostRecentMessageId); const oldestMessageId = useSelector(getOldestMessageId); - const fetchingMore = useSelector(areMoreMessagesBeingFetched); + const fetchingMore = useSelector(areMoreTopMessagesBeingFetched); const shouldMarkReadWhenVisible = isUnread; const onVisible = useCallback( @@ -83,8 +81,14 @@ export const ReadableMessage = (props: ReadableMessageProps) => { } } - if (inView === true && isAppFocused && oldestMessageId === messageId && !fetchingMore) { - debouncedTriggerLoadMore(loadedMessagesLength, selectedConversationKey); + if ( + inView === true && + isAppFocused && + oldestMessageId === messageId && + !fetchingMore && + selectedConversationKey + ) { + debouncedTriggerLoadMoreTop(selectedConversationKey, oldestMessageId); } // this part is just handling the marking of the message as read if needed @@ -115,7 +119,6 @@ export const ReadableMessage = (props: ReadableMessageProps) => { receivedAt, shouldMarkReadWhenVisible, messageId, - debouncedTriggerLoadMore, ] ); diff --git a/ts/components/settings/section/CategoryAppearance.tsx b/ts/components/settings/section/CategoryAppearance.tsx index 200b6d5ab..8c2ce46e7 100644 --- a/ts/components/settings/section/CategoryAppearance.tsx +++ b/ts/components/settings/section/CategoryAppearance.tsx @@ -3,11 +3,7 @@ import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; // tslint:disable-next-line: no-submodule-imports import useUpdate from 'react-use/lib/useUpdate'; -import { - createOrUpdateItem, - fillWithTestData, - hasLinkPreviewPopupBeenDisplayed, -} from '../../../data/data'; +import { createOrUpdateItem, hasLinkPreviewPopupBeenDisplayed } from '../../../data/data'; import { ToastUtils } from '../../../session/utils'; import { updateConfirmModal } from '../../../state/ducks/modalDialog'; import { toggleAudioAutoplay } from '../../../state/ducks/userConfig'; diff --git a/ts/data/data.ts b/ts/data/data.ts index 5e40939d6..244e936e3 100644 --- a/ts/data/data.ts +++ b/ts/data/data.ts @@ -125,6 +125,7 @@ const channelsToMake = { getOutgoingWithoutExpiresAt, getNextExpiringMessage, getMessagesByConversation, + getLastMessagesByConversation, getFirstUnreadMessageIdInConversation, hasConversationOutgoingMessage, getSeenMessagesByHashList, @@ -753,18 +754,45 @@ export async function getUnreadCountByConversation(conversationId: string): Prom export async function getMessagesByConversation( conversationId: string, - { limit = 100, receivedAt = Number.MAX_VALUE, type = '%', skipTimerInit = false } + { + skipTimerInit = false, + messageId = null, + }: { limit?: number; skipTimerInit?: false; messageId: string | null } ): Promise { const messages = await channels.getMessagesByConversation(conversationId, { - limit, - receivedAt, - type, + messageId, }); if (skipTimerInit) { for (const message of messages) { message.skipTimerInit = skipTimerInit; } } + console.warn('messages length got: ', messages.length); + return new MessageCollection(messages); +} + +/** + * This function should only be used when you don't want to render the messages. + * It just grabs the last messages of a conversation. + * + * To be used when you want for instance to remove messages from a conversations, in order. + * Or to trigger downloads of a attachments from a just approved contact (clicktotrustSender) + * @param conversationId the conversationId to fetch messages from + * @param limit the maximum number of messages to return + * @param skipTimerInit see MessageModel.skipTimerInit + * @returns the fetched messageModels + */ +export async function getLastMessagesByConversation( + conversationId: string, + limit: number, + skipTimerInit: boolean +): Promise { + const messages = await channels.getLastMessagesByConversation(conversationId, limit); + if (skipTimerInit) { + for (const message of messages) { + message.skipTimerInit = skipTimerInit; + } + } return new MessageCollection(messages); } @@ -795,12 +823,10 @@ export async function getSeenMessagesByHashList(hashes: Array): Promise< export async function removeAllMessagesInConversation(conversationId: string): Promise { let messages; do { - // Yes, we really want the await in the loop. We're deleting 100 at a + // Yes, we really want the await in the loop. We're deleting 500 at a // time so we don't use too much memory. // eslint-disable-next-line no-await-in-loop - messages = await getMessagesByConversation(conversationId, { - limit: 500, - }); + messages = await getLastMessagesByConversation(conversationId, 500, false); if (!messages.length) { return; } diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index f7903e665..c14e5a27c 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -12,7 +12,7 @@ import { MessageModel } from './message'; import { MessageAttributesOptionals, MessageModelType } from './messageType'; import autoBind from 'auto-bind'; import { - getMessagesByConversation, + getLastMessagesByConversation, getUnreadByConversation, getUnreadCountByConversation, removeMessage as dataRemoveMessage, @@ -787,10 +787,7 @@ export class ConversationModel extends Backbone.Model { if (!this.get('active_at')) { return; } - const messages = await getMessagesByConversation(this.id, { - limit: 1, - skipTimerInit: true, - }); + const messages = await getLastMessagesByConversation(this.id, 1, true); const lastMessageModel = messages.at(0); const lastMessageJSON = lastMessageModel ? lastMessageModel.toJSON() : null; const lastMessageStatusModel = lastMessageModel diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index 7fdac36da..a4ce99502 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -1,4 +1,3 @@ -import { Constants } from '../../session'; import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; import { getConversationController } from '../../session/conversations'; import { getFirstUnreadMessageIdInConversation, getMessagesByConversation } from '../../data/data'; @@ -273,7 +272,8 @@ export type ConversationsStateType = { selectedMessageIds: Array; lightBox?: LightBoxOptions; quotedMessage?: ReplyingToMessageProps; - areMoreMessagesBeingFetched: boolean; + areMoreTopMessagesBeingFetched: boolean; + oldTopMessageId: string | null; haveDoneFirstScroll: boolean; showScrollButton: boolean; @@ -287,28 +287,23 @@ export type MentionsMembersType = Array<{ authorProfileName: string; }>; -async function getMessages( - conversationKey: string, - numMessagesToFetch: number -): Promise> { +async function getMessages({ + conversationKey, + messageId, +}: { + conversationKey: string; + messageId: string | null; +}): Promise> { const conversation = getConversationController().get(conversationKey); if (!conversation) { // no valid conversation, early return window?.log?.error('Failed to get convo on reducer.'); return []; } - let msgCount = numMessagesToFetch; - msgCount = - msgCount > Constants.CONVERSATION.MAX_MESSAGE_FETCH_COUNT - ? Constants.CONVERSATION.MAX_MESSAGE_FETCH_COUNT - : msgCount; - - if (msgCount < Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT) { - msgCount = Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT; - } + console.warn('getMessages with messageId', messageId); const messageSet = await getMessagesByConversation(conversationKey, { - limit: msgCount, + messageId, }); const messageProps: Array = messageSet.models.map(m => @@ -325,24 +320,27 @@ export type SortedMessageModelProps = MessageModelPropsWithoutConvoProps & { type FetchedMessageResults = { conversationKey: string; messagesProps: Array; + oldTopMessageId: string | null; }; -export const fetchMessagesForConversation = createAsyncThunk( +export const fetchTopMessagesForConversation = createAsyncThunk( 'messages/fetchByConversationKey', async ({ conversationKey, - count, + oldTopMessageId, }: { conversationKey: string; - count: number; + oldTopMessageId: string | null; }): Promise => { + console.warn('fetchTopMessagesForConversation oldTop:', oldTopMessageId); const beforeTimestamp = Date.now(); - // tslint:disable-next-line: no-console - perfStart('fetchMessagesForConversation'); - const messagesProps = await getMessages(conversationKey, count); + perfStart('fetchTopMessagesForConversation'); + const messagesProps = await getMessages({ + conversationKey, + messageId: oldTopMessageId, + }); const afterTimestamp = Date.now(); - // tslint:disable-next-line: no-console - perfEnd('fetchMessagesForConversation', 'fetchMessagesForConversation'); + perfEnd('fetchTopMessagesForConversation', 'fetchTopMessagesForConversation'); const time = afterTimestamp - beforeTimestamp; window?.log?.info(`Loading ${messagesProps.length} messages took ${time}ms to load.`); @@ -350,6 +348,7 @@ export const fetchMessagesForConversation = createAsyncThunk( return { conversationKey, messagesProps, + oldTopMessageId, }; } ); @@ -363,11 +362,12 @@ export function getEmptyConversationState(): ConversationsStateType { messageDetailProps: undefined, showRightPanel: false, selectedMessageIds: [], - areMoreMessagesBeingFetched: false, + areMoreTopMessagesBeingFetched: false, showScrollButton: false, mentionMembers: [], firstUnreadMessageId: undefined, haveDoneFirstScroll: false, + oldTopMessageId: null, }; } @@ -697,7 +697,7 @@ const conversationsSlice = createSlice({ conversationLookup: state.conversationLookup, selectedConversation: action.payload.id, - areMoreMessagesBeingFetched: false, + areMoreTopMessagesBeingFetched: false, messages: action.payload.initialMessages, showRightPanel: false, selectedMessageIds: [], @@ -708,6 +708,7 @@ const conversationsSlice = createSlice({ nextMessageToPlay: undefined, showScrollButton: false, animateQuotedMessageId: undefined, + oldTopMessageId: null, mentionMembers: [], firstUnreadMessageId: action.payload.firstUnreadIdOnOpen, @@ -762,26 +763,28 @@ const conversationsSlice = createSlice({ extraReducers: (builder: any) => { // Add reducers for additional action types here, and handle loading state as needed builder.addCase( - fetchMessagesForConversation.fulfilled, + fetchTopMessagesForConversation.fulfilled, (state: ConversationsStateType, action: PayloadAction) => { // this is called once the messages are loaded from the db for the currently selected conversation - const { messagesProps, conversationKey } = action.payload; + const { messagesProps, conversationKey, oldTopMessageId } = action.payload; // double check that this update is for the shown convo if (conversationKey === state.selectedConversation) { + console.warn('fullfilled', oldTopMessageId); return { ...state, + oldTopMessageId, messages: messagesProps, - areMoreMessagesBeingFetched: false, + areMoreTopMessagesBeingFetched: false, }; } return state; } ); - builder.addCase(fetchMessagesForConversation.pending, (state: ConversationsStateType) => { - state.areMoreMessagesBeingFetched = true; + builder.addCase(fetchTopMessagesForConversation.pending, (state: ConversationsStateType) => { + state.areMoreTopMessagesBeingFetched = true; }); - builder.addCase(fetchMessagesForConversation.rejected, (state: ConversationsStateType) => { - state.areMoreMessagesBeingFetched = false; + builder.addCase(fetchTopMessagesForConversation.rejected, (state: ConversationsStateType) => { + state.areMoreTopMessagesBeingFetched = false; }); }, }); @@ -855,7 +858,10 @@ export async function openConversationWithMessages(args: { // preload 30 messages perfStart('getMessages'); - const initialMessages = await getMessages(conversationKey, 30); + const initialMessages = await getMessages({ + conversationKey, + messageId: null, + }); perfEnd('getMessages', 'getMessages'); window.inboxStore?.dispatch( diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index c74b554f2..beb30a19a 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -590,9 +590,9 @@ export const getQuotedMessage = createSelector( (state: ConversationsStateType): ReplyingToMessageProps | undefined => state.quotedMessage ); -export const areMoreMessagesBeingFetched = createSelector( +export const areMoreTopMessagesBeingFetched = createSelector( getConversations, - (state: ConversationsStateType): boolean => state.areMoreMessagesBeingFetched || false + (state: ConversationsStateType): boolean => state.areMoreTopMessagesBeingFetched || false ); export const getHaveDoneFirstScroll = createSelector( @@ -1116,3 +1116,8 @@ export const getGenericReadableMessageSelectorProps = createSelector( return msgProps; } ); + +export const getOldTopMessageId = createSelector( + getConversations, + (state: ConversationsStateType): string | null => state.oldTopMessageId || null +);