diff --git a/lib/selectors/nav-selectors.js b/lib/selectors/nav-selectors.js --- a/lib/selectors/nav-selectors.js +++ b/lib/selectors/nav-selectors.js @@ -14,7 +14,11 @@ import type { CalendarFilter } from '../types/filter-types.js'; import type { BaseNavInfo } from '../types/nav-types.js'; import type { BaseAppState } from '../types/redux-types.js'; -import type { RawThreadInfo, ThreadInfo } from '../types/thread-types.js'; +import type { + RelativeMemberInfo, + RawThreadInfo, + ThreadInfo, +} from '../types/thread-types'; import type { UserInfo } from '../types/user-types.js'; import { getConfig } from '../utils/config.js'; import { values } from '../utils/objects.js'; @@ -72,12 +76,49 @@ }, ); -// Without allAtOnce, useThreadSearchIndex is very expensive. useENSNames would -// trigger its recalculation for each ENS name as it streams in, but we would -// prefer to trigger its recaculation just once for every update of the -// underlying Redux data. +// Without allAtOnce, useThreadSearchIndex and useUserSearchIndex are very +// expensive. useENSNames would trigger their recalculation for each ENS name +// as it streams in, but we would prefer to trigger their recaculation just +// once for every update of the underlying Redux data. const useENSNamesOptions = { allAtOnce: true }; +function useUserSearchIndex( + userInfos: $ReadOnlyArray, +): SearchIndex { + const membersWithENSNames = useENSNames(userInfos, useENSNamesOptions); + + const memberMap = React.useMemo(() => { + const result = new Map(); + for (const userInfo of membersWithENSNames) { + result.set(userInfo.id, userInfo); + } + return result; + }, [membersWithENSNames]); + + return React.useMemo(() => { + const searchIndex = new SearchIndex(); + + for (const userInfo of userInfos) { + const searchTextArray = []; + + const rawUsername = userInfo.username; + if (rawUsername) { + searchTextArray.push(rawUsername); + } + + const resolvedUserInfo = memberMap.get(userInfo.id); + const resolvedUsername = resolvedUserInfo?.username; + if (resolvedUsername && resolvedUsername !== rawUsername) { + searchTextArray.push(resolvedUsername); + } + + searchIndex.addEntry(userInfo.id, searchTextArray.join(' ')); + } + + return searchIndex; + }, [userInfos, memberMap]); +} + function useThreadSearchIndex( threadInfos: $ReadOnlyArray, ): SearchIndex { @@ -167,6 +208,7 @@ export { timeUntilCalendarRangeExpiration, currentCalendarQuery, + useUserSearchIndex, useThreadSearchIndex, useGlobalThreadSearchIndex, }; diff --git a/lib/shared/search-utils.js b/lib/shared/search-utils.js --- a/lib/shared/search-utils.js +++ b/lib/shared/search-utils.js @@ -23,6 +23,7 @@ ChatMessageInfoItem, MessageListData, } from '../selectors/chat-selectors.js'; +import { useUserSearchIndex } from '../selectors/nav-selectors.js'; import { relationshipBlockedInEitherDirection } from '../shared/relationship-utils.js'; import type { MessageInfo, RawMessageInfo } from '../types/message-types.js'; import { userRelationshipStatus } from '../types/relationship-types.js'; @@ -89,7 +90,6 @@ function usePotentialMemberItems({ text, userInfos, - searchIndex, excludeUserIDs, includeServerSearchUsers, inputParentThreadInfo, @@ -98,13 +98,14 @@ }: { +text: string, +userInfos: { +[id: string]: AccountUserInfo }, - +searchIndex: SearchIndex, +excludeUserIDs: $ReadOnlyArray, +includeServerSearchUsers?: $ReadOnlyArray, +inputParentThreadInfo?: ?ThreadInfo, +inputCommunityThreadInfo?: ?ThreadInfo, +threadType?: ?ThreadType, }): UserListItem[] { + const searchIndex: SearchIndex = useUserSearchIndex(values(userInfos)); + const communityThreadInfo = React.useMemo( () => inputCommunityThreadInfo && inputCommunityThreadInfo.id !== genesis.id diff --git a/native/chat/compose-subchannel.react.js b/native/chat/compose-subchannel.react.js --- a/native/chat/compose-subchannel.react.js +++ b/native/chat/compose-subchannel.react.js @@ -13,10 +13,7 @@ } from 'lib/actions/thread-actions.js'; import { useENSNames } from 'lib/hooks/ens-cache.js'; import { threadInfoSelector } from 'lib/selectors/thread-selectors.js'; -import { - userInfoSelectorForPotentialMembers, - userSearchIndexForPotentialMembers, -} from 'lib/selectors/user-selectors.js'; +import { userInfoSelectorForPotentialMembers } from 'lib/selectors/user-selectors.js'; import { usePotentialMemberItems } from 'lib/shared/search-utils.js'; import { threadInFilterList, userIsMember } from 'lib/shared/thread-utils.js'; import { type ThreadType, threadTypes } from 'lib/types/thread-types-enum.js'; @@ -195,7 +192,6 @@ }, [newlyCreatedThreadInfo, pushNewThread]); const otherUserInfos = useSelector(userInfoSelectorForPotentialMembers); - const userSearchIndex = useSelector(userSearchIndexForPotentialMembers); const { community } = parentThreadInfo; const communityThreadInfo = useSelector(state => community ? threadInfoSelector(state)[community] : null, @@ -203,7 +199,6 @@ const userSearchResults = usePotentialMemberItems({ text: usernameInputText, userInfos: otherUserInfos, - searchIndex: userSearchIndex, excludeUserIDs: userInfoInputIDs, inputParentThreadInfo: parentThreadInfo, inputCommunityThreadInfo: communityThreadInfo, diff --git a/native/chat/message-list-container.react.js b/native/chat/message-list-container.react.js --- a/native/chat/message-list-container.react.js +++ b/native/chat/message-list-container.react.js @@ -8,10 +8,7 @@ import genesis from 'lib/facts/genesis.js'; import { threadInfoSelector } from 'lib/selectors/thread-selectors.js'; -import { - userInfoSelectorForPotentialMembers, - userSearchIndexForPotentialMembers, -} from 'lib/selectors/user-selectors.js'; +import { userInfoSelectorForPotentialMembers } from 'lib/selectors/user-selectors.js'; import { usePotentialMemberItems, useSearchUsers, @@ -249,14 +246,12 @@ >([]); const otherUserInfos = useSelector(userInfoSelectorForPotentialMembers); - const userSearchIndex = useSelector(userSearchIndexForPotentialMembers); const serverSearchResults = useSearchUsers(usernameInputText); const userSearchResults = usePotentialMemberItems({ text: usernameInputText, userInfos: otherUserInfos, - searchIndex: userSearchIndex, excludeUserIDs: userInfoInputArray.map(userInfo => userInfo.id), includeServerSearchUsers: serverSearchResults, }); diff --git a/native/chat/settings/add-users-modal.react.js b/native/chat/settings/add-users-modal.react.js --- a/native/chat/settings/add-users-modal.react.js +++ b/native/chat/settings/add-users-modal.react.js @@ -10,10 +10,7 @@ import { useENSNames } from 'lib/hooks/ens-cache.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import { threadInfoSelector } from 'lib/selectors/thread-selectors.js'; -import { - userInfoSelectorForPotentialMembers, - userSearchIndexForPotentialMembers, -} from 'lib/selectors/user-selectors.js'; +import { userInfoSelectorForPotentialMembers } from 'lib/selectors/user-selectors.js'; import { usePotentialMemberItems } from 'lib/shared/search-utils.js'; import { threadActualMembers } from 'lib/shared/thread-utils.js'; import type { ThreadInfo } from 'lib/types/thread-types.js'; @@ -165,7 +162,6 @@ ); const otherUserInfos = useSelector(userInfoSelectorForPotentialMembers); - const userSearchIndex = useSelector(userSearchIndexForPotentialMembers); const { parentThreadID, community } = props.route.params.threadInfo; const parentThreadInfo = useSelector(state => parentThreadID ? threadInfoSelector(state)[parentThreadID] : null, @@ -176,7 +172,6 @@ const userSearchResults = usePotentialMemberItems({ text: usernameInputText, userInfos: otherUserInfos, - searchIndex: userSearchIndex, excludeUserIDs, inputParentThreadInfo: parentThreadInfo, inputCommunityThreadInfo: communityThreadInfo, diff --git a/native/community-creation/community-creation-members.react.js b/native/community-creation/community-creation-members.react.js --- a/native/community-creation/community-creation-members.react.js +++ b/native/community-creation/community-creation-members.react.js @@ -9,10 +9,7 @@ } from 'lib/actions/thread-actions.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import { threadInfoSelector } from 'lib/selectors/thread-selectors.js'; -import { - userInfoSelectorForPotentialMembers, - userSearchIndexForPotentialMembers, -} from 'lib/selectors/user-selectors.js'; +import { userInfoSelectorForPotentialMembers } from 'lib/selectors/user-selectors.js'; import { usePotentialMemberItems } from 'lib/shared/search-utils.js'; import type { LoadingStatus } from 'lib/types/loading-types.js'; import { threadTypes } from 'lib/types/thread-types-enum.js'; @@ -68,7 +65,6 @@ const { setOptions } = navigation; const otherUserInfos = useSelector(userInfoSelectorForPotentialMembers); - const userSearchIndex = useSelector(userSearchIndexForPotentialMembers); const [usernameInputText, setUsernameInputText] = React.useState(''); const [selectedUsers, setSelectedUsers] = React.useState< @@ -147,7 +143,6 @@ const userSearchResults = usePotentialMemberItems({ text: usernameInputText, userInfos: otherUserInfos, - searchIndex: userSearchIndex, excludeUserIDs: selectedUserIDs, threadType: announcement ? threadTypes.COMMUNITY_ANNOUNCEMENT_ROOT diff --git a/web/chat/chat-thread-composer.react.js b/web/chat/chat-thread-composer.react.js --- a/web/chat/chat-thread-composer.react.js +++ b/web/chat/chat-thread-composer.react.js @@ -9,7 +9,6 @@ import SWMansionIcon from 'lib/components/SWMansionIcon.react.js'; import { useLoggedInUserInfo } from 'lib/hooks/account-hooks.js'; import { useENSNames } from 'lib/hooks/ens-cache.js'; -import { userSearchIndexForPotentialMembers } from 'lib/selectors/user-selectors.js'; import { usePotentialMemberItems, useSearchUsers, @@ -32,7 +31,6 @@ import type { InputState } from '../input/input-state.js'; import Alert from '../modals/alert.react.js'; import { updateNavInfoActionType } from '../redux/action-types.js'; -import { useSelector } from '../redux/redux-utils.js'; type Props = { +userInfoInputArray: $ReadOnlyArray, @@ -51,7 +49,6 @@ const [usernameInputText, setUsernameInputText] = React.useState(''); const dispatch = useDispatch(); - const userSearchIndex = useSelector(userSearchIndexForPotentialMembers); const userInfoInputIDs = React.useMemo( () => userInfoInputArray.map(userInfo => userInfo.id), @@ -63,7 +60,6 @@ const userListItems = usePotentialMemberItems({ text: usernameInputText, userInfos: otherUserInfos, - searchIndex: userSearchIndex, excludeUserIDs: userInfoInputIDs, includeServerSearchUsers: serverSearchResults, }); diff --git a/web/modals/threads/members/add-members-modal.react.js b/web/modals/threads/members/add-members-modal.react.js --- a/web/modals/threads/members/add-members-modal.react.js +++ b/web/modals/threads/members/add-members-modal.react.js @@ -8,10 +8,7 @@ } from 'lib/actions/thread-actions.js'; import { useENSNames } from 'lib/hooks/ens-cache.js'; import { threadInfoSelector } from 'lib/selectors/thread-selectors.js'; -import { - userSearchIndexForPotentialMembers, - userInfoSelectorForPotentialMembers, -} from 'lib/selectors/user-selectors.js'; +import { userInfoSelectorForPotentialMembers } from 'lib/selectors/user-selectors.js'; import { usePotentialMemberItems } from 'lib/shared/search-utils.js'; import { threadActualMembers } from 'lib/shared/thread-utils.js'; import { useDispatchActionPromise } from 'lib/utils/action-utils.js'; @@ -45,7 +42,6 @@ community ? threadInfoSelector(state)[community] : null, ); const otherUserInfos = useSelector(userInfoSelectorForPotentialMembers); - const userSearchIndex = useSelector(userSearchIndexForPotentialMembers); const excludeUserIDs = React.useMemo( () => threadActualMembers(threadInfo.members).concat( @@ -57,7 +53,6 @@ const userSearchResults = usePotentialMemberItems({ text: searchText, userInfos: otherUserInfos, - searchIndex: userSearchIndex, excludeUserIDs, inputParentThreadInfo: parentThreadInfo, inputCommunityThreadInfo: communityThreadInfo,