diff --git a/lib/selectors/user-selectors.js b/lib/selectors/user-selectors.js --- a/lib/selectors/user-selectors.js +++ b/lib/selectors/user-selectors.js @@ -7,8 +7,6 @@ getAvatarForUser, getRandomDefaultEmojiAvatar, } from '../shared/avatar-utils.js'; -import SearchIndex from '../shared/search-index.js'; -import SentencePrefixSearchIndex from '../shared/sentence-prefix-search-index.js'; import { getSingleOtherUser } from '../shared/thread-utils.js'; import type { ClientEmojiAvatar } from '../types/avatar-types'; import type { MinimallyEncodedRawThreadInfo } from '../types/minimally-encoded-thread-permissions-types.js'; @@ -170,30 +168,6 @@ state.dataLoaded ); -const addUsersToSearchIndex = ( - userInfos: UserInfos, - searchIndex: SearchIndex | SentencePrefixSearchIndex, -): void => { - for (const id in userInfos) { - const { username } = userInfos[id]; - if (!username) { - continue; - } - searchIndex.addEntry(id, username); - } -}; - -const userStoreMentionSearchIndex: ( - state: BaseAppState<>, -) => SentencePrefixSearchIndex = createSelector( - (state: BaseAppState<>) => state.userStore.userInfos, - (userInfos: UserInfos) => { - const searchIndex = new SentencePrefixSearchIndex(); - addUsersToSearchIndex(userInfos, searchIndex); - return searchIndex; - }, -); - const usersWithPersonalThreadSelector: ( state: BaseAppState<>, ) => $ReadOnlySet = createSelector( @@ -240,7 +214,6 @@ relativeMemberInfoSelectorForMembersOfThread, userInfoSelectorForPotentialMembers, isLoggedIn, - userStoreMentionSearchIndex, usersWithPersonalThreadSelector, savedEmojiAvatarSelectorForCurrentUser, }; diff --git a/lib/shared/mention-utils.js b/lib/shared/mention-utils.js --- a/lib/shared/mention-utils.js +++ b/lib/shared/mention-utils.js @@ -1,9 +1,13 @@ // @flow +import * as React from 'react'; + import { oldValidUsernameRegexString } from './account-utils.js'; import SentencePrefixSearchIndex from './sentence-prefix-search-index.js'; import { threadOtherMembers } from './thread-utils.js'; import { stringForUserExplicit } from './user-utils.js'; +import { useENSNames } from '../hooks/ens-cache.js'; +import { useUserSearchIndex } from '../selectors/nav-selectors.js'; import { threadTypes } from '../types/thread-types-enum.js'; import type { ChatMentionCandidates, @@ -100,21 +104,41 @@ return null; } -function getMentionTypeaheadUserSuggestions( - userSearchIndex: SentencePrefixSearchIndex, +const useENSNamesOptions = { allAtOnce: true }; +function useMentionTypeaheadUserSuggestions( threadMembers: $ReadOnlyArray, viewerID: ?string, - usernamePrefix: string, + typeaheadMatchedStrings: ?TypeaheadMatchedStrings, ): $ReadOnlyArray { - const userIDs = userSearchIndex.getSearchResults(usernamePrefix); - const usersInThread = threadOtherMembers(threadMembers, viewerID); - - return usersInThread - .filter(user => usernamePrefix.length === 0 || userIDs.includes(user.id)) - .sort((userA, userB) => - stringForUserExplicit(userA).localeCompare(stringForUserExplicit(userB)), - ) - .map(userInfo => ({ type: 'user', userInfo })); + const userSearchIndex = useUserSearchIndex(threadMembers); + const resolvedThredMembers = useENSNames(threadMembers, useENSNamesOptions); + + return React.useMemo(() => { + if (!typeaheadMatchedStrings) { + return []; + } + + const { query: usernamePrefix } = typeaheadMatchedStrings; + const usersInThread = threadOtherMembers(resolvedThredMembers, viewerID); + + return usersInThread + .filter( + user => + usernamePrefix.length === 0 || + userSearchIndex.getSearchResults(usernamePrefix).includes(user.id), + ) + .sort((userA, userB) => + stringForUserExplicit(userA).localeCompare( + stringForUserExplicit(userB), + ), + ) + .map(userInfo => ({ type: 'user', userInfo })); + }, [ + userSearchIndex, + resolvedThredMembers, + typeaheadMatchedStrings, + viewerID, + ]); } function getMentionTypeaheadChatSuggestions( @@ -177,7 +201,7 @@ markdownUserMentionRegex, isUserMentioned, extractUserMentionsFromText, - getMentionTypeaheadUserSuggestions, + useMentionTypeaheadUserSuggestions, getMentionTypeaheadChatSuggestions, getNewTextAndSelection, getTypeaheadRegexMatches, diff --git a/native/chat/chat-input-bar.react.js b/native/chat/chat-input-bar.react.js --- a/native/chat/chat-input-bar.react.js +++ b/native/chat/chat-input-bar.react.js @@ -37,11 +37,10 @@ } from 'lib/hooks/chat-mention-hooks.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import { threadInfoSelector } from 'lib/selectors/thread-selectors.js'; -import { userStoreMentionSearchIndex } from 'lib/selectors/user-selectors.js'; import { colorIsDark } from 'lib/shared/color-utils.js'; import { useEditMessage } from 'lib/shared/edit-messages-utils.js'; import { - getMentionTypeaheadUserSuggestions, + useMentionTypeaheadUserSuggestions, getMentionTypeaheadChatSuggestions, getTypeaheadRegexMatches, type Selection, @@ -296,7 +295,6 @@ +dispatchActionPromise: DispatchActionPromise, +joinThread: (request: ClientThreadJoinRequest) => Promise, +inputState: ?InputState, - +userSearchIndex: SentencePrefixSearchIndex, +userMentionsCandidates: $ReadOnlyArray, +chatMentionSearchIndex: SentencePrefixSearchIndex, +chatMentionCandidates: ChatMentionCandidates, @@ -1257,8 +1255,6 @@ const dispatchActionPromise = useDispatchActionPromise(); const callJoinThread = useJoinThread(); - const userSearchIndex = useSelector(userStoreMentionSearchIndex); - const { getChatMentionSearchIndex } = useChatMentionContext(); const chatMentionSearchIndex = getChatMentionSearchIndex(props.threadInfo); @@ -1302,50 +1298,43 @@ [selectionState.text, selectionState.selection], ); - const typeaheadResults: { - typeaheadMatchedStrings: ?TypeaheadMatchedStrings, - suggestions: $ReadOnlyArray, - } = React.useMemo(() => { - if (!typeaheadRegexMatches) { - return { - typeaheadMatchedStrings: null, - suggestions: [], - }; - } + const typeaheadMatchedStrings: ?TypeaheadMatchedStrings = React.useMemo( + () => + typeaheadRegexMatches !== null + ? { + textBeforeAtSymbol: typeaheadRegexMatches[1] ?? '', + query: typeaheadRegexMatches[4] ?? '', + } + : null, + [typeaheadRegexMatches], + ); - const typeaheadMatchedStrings: TypeaheadMatchedStrings = { - textBeforeAtSymbol: typeaheadRegexMatches[1] ?? '', - query: typeaheadRegexMatches[4] ?? '', - }; + const suggestedUsers = useMentionTypeaheadUserSuggestions( + userMentionsCandidates, + viewerID, + typeaheadMatchedStrings, + ); - const suggestedUsers = getMentionTypeaheadUserSuggestions( - userSearchIndex, - userMentionsCandidates, - viewerID, - typeaheadMatchedStrings.query, - ); - const suggestedChats = getMentionTypeaheadChatSuggestions( - chatMentionSearchIndex, - chatMentionCandidates, - typeaheadMatchedStrings.query, - ); - const suggestions: $ReadOnlyArray = [ - ...suggestedUsers, - ...suggestedChats, - ]; + const suggestions: $ReadOnlyArray = + React.useMemo(() => { + if (!typeaheadRegexMatches || !typeaheadMatchedStrings) { + return []; + } + + const suggestedChats = getMentionTypeaheadChatSuggestions( + chatMentionSearchIndex, + chatMentionCandidates, + typeaheadMatchedStrings.query, + ); - return { + return [...suggestedUsers, ...suggestedChats]; + }, [ + chatMentionCandidates, + chatMentionSearchIndex, + typeaheadRegexMatches, typeaheadMatchedStrings, - suggestions, - }; - }, [ - chatMentionCandidates, - chatMentionSearchIndex, - typeaheadRegexMatches, - userMentionsCandidates, - userSearchIndex, - viewerID, - ]); + suggestedUsers, + ]); return ( ); } diff --git a/web/chat/chat-input-bar.react.js b/web/chat/chat-input-bar.react.js --- a/web/chat/chat-input-bar.react.js +++ b/web/chat/chat-input-bar.react.js @@ -16,14 +16,13 @@ } from 'lib/hooks/chat-mention-hooks.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import { threadInfoSelector } from 'lib/selectors/thread-selectors.js'; -import { userStoreMentionSearchIndex } from 'lib/selectors/user-selectors.js'; import { - getMentionTypeaheadUserSuggestions, getTypeaheadRegexMatches, getUserMentionsCandidates, getMentionTypeaheadChatSuggestions, type MentionTypeaheadSuggestionItem, type TypeaheadMatchedStrings, + useMentionTypeaheadUserSuggestions, } from 'lib/shared/mention-utils.js'; import { localIDPrefix, trimMessage } from 'lib/shared/message-utils.js'; import { @@ -586,7 +585,6 @@ const calendarQuery = useSelector(nonThreadCalendarQuery); const dispatchActionPromise = useDispatchActionPromise(); const callJoinThread = useJoinThread(); - const userSearchIndex = useSelector(userStoreMentionSearchIndex); const { getChatMentionSearchIndex } = useChatMentionContext(); const chatMentionSearchIndex = getChatMentionSearchIndex(props.threadInfo); @@ -644,16 +642,16 @@ chatMentionCandidates, ]); + const suggestedUsers = useMentionTypeaheadUserSuggestions( + props.inputState.typeaheadState.frozenUserMentionsCandidates, + viewerID, + typeaheadMatchedStrings, + ); + const suggestions = React.useMemo(() => { if (!typeaheadMatchedStrings) { return ([]: $ReadOnlyArray); } - const suggestedUsers = getMentionTypeaheadUserSuggestions( - userSearchIndex, - props.inputState.typeaheadState.frozenUserMentionsCandidates, - viewerID, - typeaheadMatchedStrings.query, - ); const suggestedChats = getMentionTypeaheadChatSuggestions( chatMentionSearchIndex, props.inputState.typeaheadState.frozenChatMentionsCandidates, @@ -664,11 +662,9 @@ ...suggestedChats, ]: $ReadOnlyArray); }, [ + suggestedUsers, typeaheadMatchedStrings, - userSearchIndex, - props.inputState.typeaheadState.frozenUserMentionsCandidates, props.inputState.typeaheadState.frozenChatMentionsCandidates, - viewerID, chatMentionSearchIndex, ]);