diff --git a/lib/hooks/message-hooks.js b/lib/hooks/message-hooks.js new file mode 100644 --- /dev/null +++ b/lib/hooks/message-hooks.js @@ -0,0 +1,12 @@ +// @flow + +import { getOldestNonLocalMessageID } from '../shared/message-utils.js'; +import { useSelector } from '../utils/redux-utils.js'; + +function useOldestMessageServerID(threadID: string): ?string { + return useSelector(state => + getOldestNonLocalMessageID(threadID, state.messageStore), + ); +} + +export { useOldestMessageServerID }; diff --git a/lib/shared/message-utils.js b/lib/shared/message-utils.js --- a/lib/shared/message-utils.js +++ b/lib/shared/message-utils.js @@ -380,6 +380,24 @@ return thread?.messageIDs.find(id => !id.startsWith(localIDPrefix)); } +function getOldestNonLocalMessageID( + threadID: string, + messageStore: MessageStore, +): ?string { + const thread = messageStore.threads[threadID]; + if (!thread) { + return thread; + } + const { messageIDs } = thread; + for (let i = messageIDs.length - 1; i >= 0; i--) { + const id = messageIDs[i]; + if (!id.startsWith(localIDPrefix)) { + return id; + } + } + return undefined; +} + function getMessageTitle( messageInfo: | ComposableMessageInfo @@ -604,6 +622,7 @@ createMessageQuote, createMessageReply, getMostRecentNonLocalMessageID, + getOldestNonLocalMessageID, getMessageTitle, mergeThreadMessageInfos, useMessagePreview, diff --git a/native/chat/message-list.react.js b/native/chat/message-list.react.js --- a/native/chat/message-list.react.js +++ b/native/chat/message-list.react.js @@ -12,6 +12,7 @@ fetchMostRecentMessagesActionTypes, fetchMostRecentMessages, } from 'lib/actions/message-actions.js'; +import { useOldestMessageServerID } from 'lib/hooks/message-hooks.js'; import { registerFetchKey } from 'lib/reducers/loading-reducer.js'; import { messageKey } from 'lib/shared/message-utils.js'; import { useWatchThread } from 'lib/shared/thread-utils.js'; @@ -59,13 +60,10 @@ }; type Props = { ...BaseProps, - // Redux state +startReached: boolean, +styles: typeof unboundStyles, +indicatorStyle: IndicatorStyle, - // Redux dispatch functions +dispatchActionPromise: DispatchActionPromise, - // async functions that hit server APIs +fetchMessagesBeforeCursor: ( threadID: string, beforeMessageID: string, @@ -73,10 +71,9 @@ +fetchMostRecentMessages: ( threadID: string, ) => Promise, - // withOverlayContext +overlayContext: ?OverlayContextType, - // withKeyboardState +keyboardState: ?KeyboardState, + +oldestMessageServerID: ?string, }; type State = { +focusedMessageKey: ?string, @@ -140,15 +137,6 @@ } componentDidUpdate(prevProps: Props) { - const newListData = this.props.messageListData; - const oldListData = prevProps.messageListData; - if ( - this.state.loadingFromScroll && - (newListData.length > oldListData.length || this.props.startReached) - ) { - this.setState({ loadingFromScroll: false }); - } - const modalIsOpen = MessageList.modalOpen(this.props); const modalWasOpen = MessageList.modalOpen(prevProps); if (!modalIsOpen && modalWasOpen) { @@ -295,30 +283,30 @@ } this.setState({ loadingFromScroll: true }); - const oldestMessageServerID = this.oldestMessageServerID(); + const { oldestMessageServerID } = this.props; const threadID = this.props.threadInfo.id; - if (oldestMessageServerID) { - this.props.dispatchActionPromise( - fetchMessagesBeforeCursorActionTypes, - this.props.fetchMessagesBeforeCursor(threadID, oldestMessageServerID), - ); - } else { - this.props.dispatchActionPromise( - fetchMostRecentMessagesActionTypes, - this.props.fetchMostRecentMessages(threadID), - ); - } - }; - oldestMessageServerID(): ?string { - const data = this.props.messageListData; - for (let i = data.length - 1; i >= 0; i--) { - if (data[i].itemType === 'message' && data[i].messageInfo.id) { - return data[i].messageInfo.id; + (async () => { + try { + if (oldestMessageServerID) { + await this.props.dispatchActionPromise( + fetchMessagesBeforeCursorActionTypes, + this.props.fetchMessagesBeforeCursor( + threadID, + oldestMessageServerID, + ), + ); + } else { + await this.props.dispatchActionPromise( + fetchMostRecentMessagesActionTypes, + this.props.fetchMostRecentMessages(threadID), + ); + } + } finally { + this.setState({ loadingFromScroll: false }); } - } - return null; - } + })(); + }; } const unboundStyles = { @@ -360,6 +348,8 @@ ); const callFetchMostRecentMessages = useServerCall(fetchMostRecentMessages); + const oldestMessageServerID = useOldestMessageServerID(threadID); + useWatchThread(props.threadInfo); return ( @@ -373,6 +363,7 @@ fetchMostRecentMessages={callFetchMostRecentMessages} overlayContext={overlayContext} keyboardState={keyboardState} + oldestMessageServerID={oldestMessageServerID} /> ); }); diff --git a/web/chat/chat-message-list.react.js b/web/chat/chat-message-list.react.js --- a/web/chat/chat-message-list.react.js +++ b/web/chat/chat-message-list.react.js @@ -11,6 +11,7 @@ fetchMostRecentMessagesActionTypes, fetchMostRecentMessages, } from 'lib/actions/message-actions.js'; +import { useOldestMessageServerID } from 'lib/hooks/message-hooks.js'; import { registerFetchKey } from 'lib/reducers/loading-reducer.js'; import { type ChatMessageItem, @@ -46,13 +47,10 @@ type Props = { ...BaseProps, - // Redux state +activeChatThreadID: ?string, +messageListData: ?$ReadOnlyArray, +startReached: boolean, - // Redux dispatch functions +dispatchActionPromise: DispatchActionPromise, - // async functions that hit server APIs +fetchMessagesBeforeCursor: ( threadID: string, beforeMessageID: string, @@ -60,9 +58,9 @@ +fetchMostRecentMessages: ( threadID: string, ) => Promise, - // withInputState +inputState: ?InputState, +clearTooltip: () => mixed, + +oldestMessageServerID: ?string, }; type Snapshot = { +scrollTop: number, @@ -107,16 +105,6 @@ const { messageListData } = this.props; const prevMessageListData = prevProps.messageListData; - if ( - this.loadingFromScroll && - messageListData && - (!prevMessageListData || - messageListData.length > prevMessageListData.length || - this.props.startReached) - ) { - this.loadingFromScroll = false; - } - const { messageContainer } = this; if (messageContainer && prevMessageListData !== messageListData) { this.onScroll(); @@ -229,7 +217,7 @@ this.possiblyLoadMoreMessages(); }; - possiblyLoadMoreMessages() { + async possiblyLoadMoreMessages() { if (!this.messageContainer) { return; } @@ -250,29 +238,22 @@ const threadID = this.props.activeChatThreadID; invariant(threadID, 'should be set'); - const oldestMessageServerID = this.oldestMessageServerID(); - if (oldestMessageServerID) { - this.props.dispatchActionPromise( - fetchMessagesBeforeCursorActionTypes, - this.props.fetchMessagesBeforeCursor(threadID, oldestMessageServerID), - ); - } else { - this.props.dispatchActionPromise( - fetchMostRecentMessagesActionTypes, - this.props.fetchMostRecentMessages(threadID), - ); - } - } - - oldestMessageServerID(): ?string { - const data = this.props.messageListData; - invariant(data, 'should be set'); - for (let i = data.length - 1; i >= 0; i--) { - if (data[i].itemType === 'message' && data[i].messageInfo.id) { - return data[i].messageInfo.id; + try { + const { oldestMessageServerID } = this.props; + if (oldestMessageServerID) { + await this.props.dispatchActionPromise( + fetchMessagesBeforeCursorActionTypes, + this.props.fetchMessagesBeforeCursor(threadID, oldestMessageServerID), + ); + } else { + await this.props.dispatchActionPromise( + fetchMostRecentMessagesActionTypes, + this.props.fetchMostRecentMessages(threadID), + ); } + } finally { + this.loadingFromScroll = false; } - return null; } } @@ -324,6 +305,8 @@ return { getTextMessageMarkdownRules }; }, [getTextMessageMarkdownRules]); + const oldestMessageServerID = useOldestMessageServerID(threadInfo.id); + return ( );