diff --git a/lib/components/chat-mention-provider.react.js b/lib/components/chat-mention-provider.react.js
new file mode 100644
--- /dev/null
+++ b/lib/components/chat-mention-provider.react.js
@@ -0,0 +1,227 @@
+// @flow
+
+import * as React from 'react';
+
+import genesis from '../facts/genesis.js';
+import { threadInfoSelector } from '../selectors/thread-selectors.js';
+import SentencePrefixSearchIndex from '../shared/sentence-prefix-search-index.js';
+import { threadTypes } from '../types/thread-types-enum.js';
+import type {
+ ChatMentionCandidates,
+ ChatMentionCandidatesObj,
+ ResolvedThreadInfo,
+ ThreadInfo,
+} from '../types/thread-types.js';
+import { useResolvedThreadInfosObj } from '../utils/entity-helpers.js';
+import { useSelector } from '../utils/redux-utils.js';
+
+type Props = {
+ +children: React.Node,
+};
+export type ChatMentionContextType = {
+ +getChatMentionSearchIndex: (
+ threadInfo: ThreadInfo,
+ ) => SentencePrefixSearchIndex,
+ +communityThreadIDForGenesisThreads: { +[id: string]: string },
+ +chatMentionCandidatesObj: ChatMentionCandidatesObj,
+};
+
+const emptySearchIndex = new SentencePrefixSearchIndex();
+const ChatMentionContext: React.Context =
+ React.createContext({
+ getChatMentionSearchIndex: () => emptySearchIndex,
+ communityThreadIDForGenesisThreads: {},
+ chatMentionCandidatesObj: {},
+ });
+
+function ChatMentionContextProvider(props: Props): React.Node {
+ const { children } = props;
+
+ const { communityThreadIDForGenesisThreads, chatMentionCandidatesObj } =
+ useChatMentionCandidatesObjAndUtils();
+ const searchIndices = useChatMentionSearchIndex(chatMentionCandidatesObj);
+
+ const getChatMentionSearchIndex = React.useCallback(
+ (threadInfo: ThreadInfo) => {
+ if (threadInfo.community === genesis.id) {
+ return searchIndices[communityThreadIDForGenesisThreads[threadInfo.id]];
+ }
+ return searchIndices[threadInfo.community ?? threadInfo.id];
+ },
+ [communityThreadIDForGenesisThreads, searchIndices],
+ );
+
+ const value = React.useMemo(
+ () => ({
+ getChatMentionSearchIndex,
+ communityThreadIDForGenesisThreads,
+ chatMentionCandidatesObj,
+ }),
+ [
+ getChatMentionSearchIndex,
+ communityThreadIDForGenesisThreads,
+ chatMentionCandidatesObj,
+ ],
+ );
+
+ return (
+
+ {children}
+
+ );
+}
+
+function getChatMentionCandidates(threadInfos: {
+ +[id: string]: ResolvedThreadInfo,
+}): {
+ chatMentionCandidatesObj: ChatMentionCandidatesObj,
+ communityThreadIDForGenesisThreads: { +[id: string]: string },
+} {
+ const result = {};
+ const visitedGenesisThreads = new Set();
+ const communityThreadIDForGenesisThreads = {};
+ for (const currentThreadID in threadInfos) {
+ const currentThreadInfo = threadInfos[currentThreadID];
+ const { community: currentThreadCommunity } = currentThreadInfo;
+ if (!currentThreadCommunity) {
+ if (!result[currentThreadID]) {
+ result[currentThreadID] = { [currentThreadID]: currentThreadInfo };
+ }
+ continue;
+ }
+ if (!result[currentThreadCommunity]) {
+ result[currentThreadCommunity] = {
+ [currentThreadCommunity]: threadInfos[currentThreadCommunity],
+ };
+ }
+ // Handle GENESIS community case: mentioning inside GENESIS should only
+ // show chats and threads inside the top level that is below GENESIS.
+ if (threadInfos[currentThreadCommunity].type === threadTypes.GENESIS) {
+ if (visitedGenesisThreads.has(currentThreadID)) {
+ continue;
+ }
+ const threadTraversePath = [currentThreadInfo];
+ visitedGenesisThreads.add(currentThreadID);
+ let currentlySelectedThreadID = currentThreadInfo.parentThreadID;
+ while (currentlySelectedThreadID) {
+ const currentlySelectedThreadInfo =
+ threadInfos[currentlySelectedThreadID];
+ if (
+ visitedGenesisThreads.has(currentlySelectedThreadID) ||
+ !currentlySelectedThreadInfo ||
+ currentlySelectedThreadInfo.type === threadTypes.GENESIS
+ ) {
+ break;
+ }
+ threadTraversePath.push(currentlySelectedThreadInfo);
+ visitedGenesisThreads.add(currentlySelectedThreadID);
+ currentlySelectedThreadID = currentlySelectedThreadInfo.parentThreadID;
+ }
+ const lastThreadInTraversePath =
+ threadTraversePath[threadTraversePath.length - 1];
+ let lastThreadInTraversePathParentID;
+ if (lastThreadInTraversePath.parentThreadID) {
+ lastThreadInTraversePathParentID = threadInfos[
+ lastThreadInTraversePath.parentThreadID
+ ]
+ ? lastThreadInTraversePath.parentThreadID
+ : lastThreadInTraversePath.id;
+ } else {
+ lastThreadInTraversePathParentID = lastThreadInTraversePath.id;
+ }
+ if (
+ threadInfos[lastThreadInTraversePathParentID].type ===
+ threadTypes.GENESIS
+ ) {
+ if (!result[lastThreadInTraversePath.id]) {
+ result[lastThreadInTraversePath.id] = {};
+ }
+ for (const threadInfo of threadTraversePath) {
+ result[lastThreadInTraversePath.id][threadInfo.id] = threadInfo;
+ communityThreadIDForGenesisThreads[threadInfo.id] =
+ lastThreadInTraversePath.id;
+ }
+ if (
+ lastThreadInTraversePath.type !== threadTypes.PERSONAL &&
+ lastThreadInTraversePath.type !== threadTypes.PRIVATE
+ ) {
+ result[genesis.id][lastThreadInTraversePath.id] =
+ lastThreadInTraversePath;
+ }
+ } else {
+ if (
+ !communityThreadIDForGenesisThreads[lastThreadInTraversePathParentID]
+ ) {
+ result[lastThreadInTraversePathParentID] = {};
+ communityThreadIDForGenesisThreads[lastThreadInTraversePathParentID] =
+ lastThreadInTraversePathParentID;
+ }
+ const lastThreadInTraversePathParentCommunityThreadID =
+ communityThreadIDForGenesisThreads[lastThreadInTraversePathParentID];
+ for (const threadInfo of threadTraversePath) {
+ result[lastThreadInTraversePathParentCommunityThreadID][
+ threadInfo.id
+ ] = threadInfo;
+ communityThreadIDForGenesisThreads[threadInfo.id] =
+ lastThreadInTraversePathParentCommunityThreadID;
+ }
+ }
+ continue;
+ }
+ result[currentThreadCommunity][currentThreadID] = currentThreadInfo;
+ }
+ return {
+ chatMentionCandidatesObj: result,
+ communityThreadIDForGenesisThreads,
+ };
+}
+
+function useChatMentionCandidatesObjAndUtils(): {
+ chatMentionCandidatesObj: ChatMentionCandidatesObj,
+ resolvedThreadInfos: ChatMentionCandidates,
+ communityThreadIDForGenesisThreads: { +[id: string]: string },
+} {
+ const threadInfos = useSelector(threadInfoSelector);
+ const resolvedThreadInfos = useResolvedThreadInfosObj(threadInfos);
+ const { chatMentionCandidatesObj, communityThreadIDForGenesisThreads } =
+ React.useMemo(
+ () => getChatMentionCandidates(resolvedThreadInfos),
+ [resolvedThreadInfos],
+ );
+ return {
+ chatMentionCandidatesObj,
+ resolvedThreadInfos,
+ communityThreadIDForGenesisThreads,
+ };
+}
+
+function useChatMentionSearchIndex(
+ chatMentionCandidatesObj: ChatMentionCandidatesObj,
+): {
+ +[id: string]: SentencePrefixSearchIndex,
+} {
+ return React.useMemo(() => {
+ const result = {};
+ for (const communityThreadID in chatMentionCandidatesObj) {
+ const searchIndex = new SentencePrefixSearchIndex();
+ const searchIndexEntries = [];
+ for (const threadID in chatMentionCandidatesObj[communityThreadID]) {
+ searchIndexEntries.push({
+ id: threadID,
+ uiName: chatMentionCandidatesObj[communityThreadID][threadID].uiName,
+ });
+ }
+ // Sort the keys so that the order of the search result is consistent
+ searchIndexEntries.sort(({ uiName: uiNameA }, { uiName: uiNameB }) =>
+ uiNameA.localeCompare(uiNameB),
+ );
+ for (const { id, uiName } of searchIndexEntries) {
+ searchIndex.addEntry(id, uiName);
+ }
+ result[communityThreadID] = searchIndex;
+ }
+ return result;
+ }, [chatMentionCandidatesObj]);
+}
+
+export { ChatMentionContextProvider, ChatMentionContext };
diff --git a/lib/hooks/chat-mention-hooks.js b/lib/hooks/chat-mention-hooks.js
new file mode 100644
--- /dev/null
+++ b/lib/hooks/chat-mention-hooks.js
@@ -0,0 +1,47 @@
+// @flow
+
+import invariant from 'invariant';
+import * as React from 'react';
+
+import {
+ ChatMentionContext,
+ type ChatMentionContextType,
+} from '../components/chat-mention-provider.react.js';
+import genesis from '../facts/genesis.js';
+import type {
+ ChatMentionCandidates,
+ ThreadInfo,
+} from '../types/thread-types.js';
+
+function useChatMentionContext(): ChatMentionContextType {
+ const context = React.useContext(ChatMentionContext);
+ invariant(context, 'ChatMentionContext not found');
+
+ return context;
+}
+
+function useThreadChatMentionCandidates(
+ threadInfo: ThreadInfo,
+): ChatMentionCandidates {
+ const { communityThreadIDForGenesisThreads, chatMentionCandidatesObj } =
+ useChatMentionContext();
+ return React.useMemo(() => {
+ const communityID =
+ threadInfo.community === genesis.id
+ ? communityThreadIDForGenesisThreads[threadInfo.id]
+ : threadInfo.community ?? threadInfo.id;
+ const allChatsWithinCommunity = chatMentionCandidatesObj[communityID];
+ if (!allChatsWithinCommunity) {
+ return {};
+ }
+ const { [threadInfo.id]: _, ...result } = allChatsWithinCommunity;
+ return result;
+ }, [
+ chatMentionCandidatesObj,
+ communityThreadIDForGenesisThreads,
+ threadInfo.community,
+ threadInfo.id,
+ ]);
+}
+
+export { useThreadChatMentionCandidates, useChatMentionContext };
diff --git a/lib/shared/thread-utils.js b/lib/shared/thread-utils.js
--- a/lib/shared/thread-utils.js
+++ b/lib/shared/thread-utils.js
@@ -45,7 +45,6 @@
usersWithPersonalThreadSelector,
} from '../selectors/user-selectors.js';
import { getUserAvatarForThread } from '../shared/avatar-utils.js';
-import SentencePrefixSearchIndex from '../shared/sentence-prefix-search-index.js';
import type { CalendarQuery } from '../types/entry-types.js';
import { messageTypes } from '../types/message-types-enum.js';
import {
@@ -79,9 +78,6 @@
type ClientNewThreadRequest,
type NewThreadResult,
type ChangeThreadSettingsPayload,
- type ResolvedThreadInfo,
- type ChatMentionCandidatesObj,
- type ChatMentionCandidates,
type UserProfileThreadInfo,
} from '../types/thread-types.js';
import { updateTypes } from '../types/update-types-enum.js';
@@ -99,7 +95,6 @@
} from '../utils/action-utils.js';
import type { DispatchActionPromise } from '../utils/action-utils.js';
import type { GetENSNames } from '../utils/ens-helpers.js';
-import { useResolvedThreadInfosObj } from '../utils/entity-helpers.js';
import {
ET,
entityTextToRawString,
@@ -1639,161 +1634,6 @@
} within this ${communityOrThreadNoun(threadInfo)}`;
}
-function getChatMentionCandidates(threadInfos: {
- +[id: string]: ResolvedThreadInfo,
-}): {
- chatMentionCandidatesObj: ChatMentionCandidatesObj,
- communityThreadIDForGenesisThreads: { +[id: string]: string },
-} {
- const result = {};
- const visitedGenesisThreads = new Set();
- const communityThreadIDForGenesisThreads = {};
- for (const currentThreadID in threadInfos) {
- const currentThreadInfo = threadInfos[currentThreadID];
- const { community: currentThreadCommunity } = currentThreadInfo;
- if (!currentThreadCommunity) {
- if (!result[currentThreadID]) {
- result[currentThreadID] = { [currentThreadID]: currentThreadInfo };
- }
- continue;
- }
- if (!result[currentThreadCommunity]) {
- result[currentThreadCommunity] = {
- [currentThreadCommunity]: threadInfos[currentThreadCommunity],
- };
- }
- // Handle GENESIS community case: mentioning inside GENESIS should only
- // show chats and threads inside the top level that is below GENESIS.
- if (threadInfos[currentThreadCommunity].type === threadTypes.GENESIS) {
- if (visitedGenesisThreads.has(currentThreadID)) {
- continue;
- }
- const threadTraversePath = [currentThreadInfo];
- visitedGenesisThreads.add(currentThreadID);
- let currentlySelectedThreadID = currentThreadInfo.parentThreadID;
- while (currentlySelectedThreadID) {
- const currentlySelectedThreadInfo =
- threadInfos[currentlySelectedThreadID];
- if (
- visitedGenesisThreads.has(currentlySelectedThreadID) ||
- !currentlySelectedThreadInfo ||
- currentlySelectedThreadInfo.type === threadTypes.GENESIS
- ) {
- break;
- }
- threadTraversePath.push(currentlySelectedThreadInfo);
- visitedGenesisThreads.add(currentlySelectedThreadID);
- currentlySelectedThreadID = currentlySelectedThreadInfo.parentThreadID;
- }
- const lastThreadInTraversePath =
- threadTraversePath[threadTraversePath.length - 1];
- let lastThreadInTraversePathParentID;
- if (lastThreadInTraversePath.parentThreadID) {
- lastThreadInTraversePathParentID = threadInfos[
- lastThreadInTraversePath.parentThreadID
- ]
- ? lastThreadInTraversePath.parentThreadID
- : lastThreadInTraversePath.id;
- } else {
- lastThreadInTraversePathParentID = lastThreadInTraversePath.id;
- }
- if (
- threadInfos[lastThreadInTraversePathParentID].type ===
- threadTypes.GENESIS
- ) {
- if (!result[lastThreadInTraversePath.id]) {
- result[lastThreadInTraversePath.id] = {};
- }
- for (const threadInfo of threadTraversePath) {
- result[lastThreadInTraversePath.id][threadInfo.id] = threadInfo;
- communityThreadIDForGenesisThreads[threadInfo.id] =
- lastThreadInTraversePath.id;
- }
- if (
- lastThreadInTraversePath.type !== threadTypes.PERSONAL &&
- lastThreadInTraversePath.type !== threadTypes.PRIVATE
- ) {
- result[genesis.id][lastThreadInTraversePath.id] =
- lastThreadInTraversePath;
- }
- } else {
- if (
- !communityThreadIDForGenesisThreads[lastThreadInTraversePathParentID]
- ) {
- result[lastThreadInTraversePathParentID] = {};
- communityThreadIDForGenesisThreads[lastThreadInTraversePathParentID] =
- lastThreadInTraversePathParentID;
- }
- const lastThreadInTraversePathParentCommunityThreadID =
- communityThreadIDForGenesisThreads[lastThreadInTraversePathParentID];
- for (const threadInfo of threadTraversePath) {
- result[lastThreadInTraversePathParentCommunityThreadID][
- threadInfo.id
- ] = threadInfo;
- communityThreadIDForGenesisThreads[threadInfo.id] =
- lastThreadInTraversePathParentCommunityThreadID;
- }
- }
- continue;
- }
- result[currentThreadCommunity][currentThreadID] = currentThreadInfo;
- }
- return {
- chatMentionCandidatesObj: result,
- communityThreadIDForGenesisThreads,
- };
-}
-
-function useChatMentionCandidatesObjAndUtils(): {
- chatMentionCandidatesObj: ChatMentionCandidatesObj,
- resolvedThreadInfos: ChatMentionCandidates,
- communityThreadIDForGenesisThreads: { +[id: string]: string },
-} {
- const threadInfos = useSelector(threadInfoSelector);
- const resolvedThreadInfos = useResolvedThreadInfosObj(threadInfos);
- const { chatMentionCandidatesObj, communityThreadIDForGenesisThreads } =
- React.useMemo(
- () => getChatMentionCandidates(resolvedThreadInfos),
- [resolvedThreadInfos],
- );
- return {
- chatMentionCandidatesObj,
- resolvedThreadInfos,
- communityThreadIDForGenesisThreads,
- };
-}
-
-function useChatMentionCandidatesObj(): ChatMentionCandidatesObj {
- const { chatMentionCandidatesObj } = useChatMentionCandidatesObjAndUtils();
- return chatMentionCandidatesObj;
-}
-
-function useThreadChatMentionCandidates(
- threadInfo: ThreadInfo,
-): ChatMentionCandidates {
- const { chatMentionCandidatesObj, communityThreadIDForGenesisThreads } =
- useChatMentionCandidatesObjAndUtils();
- return React.useMemo(() => {
- let communityID,
- result = {};
- if (threadInfo.community === genesis.id) {
- communityID = communityThreadIDForGenesisThreads[threadInfo.id];
- } else {
- communityID = threadInfo.community ?? threadInfo.id;
- }
- if (chatMentionCandidatesObj[communityID]) {
- result = { ...chatMentionCandidatesObj[communityID] };
- }
- delete result[threadInfo.id];
- return result;
- }, [
- chatMentionCandidatesObj,
- communityThreadIDForGenesisThreads,
- threadInfo.community,
- threadInfo.id,
- ]);
-}
-
function useUserProfileThreadInfo(userInfo: ?UserInfo): ?UserProfileThreadInfo {
const userID = userInfo?.id;
const username = userInfo?.username;
@@ -1852,50 +1692,6 @@
]);
}
-function useChatMentionSearchIndex(): {
- +[id: string]: SentencePrefixSearchIndex,
-} {
- const { chatMentionCandidatesObj } = useChatMentionCandidatesObjAndUtils();
- return React.useMemo(() => {
- const result = {};
- for (const communityThreadID in chatMentionCandidatesObj) {
- const searchIndex = new SentencePrefixSearchIndex();
- const searchIndexEntries = [];
- for (const threadID in chatMentionCandidatesObj[communityThreadID]) {
- searchIndexEntries.push({
- id: threadID,
- uiName: chatMentionCandidatesObj[communityThreadID][threadID].uiName,
- });
- }
- // Sort the keys so that the order of the search result is consistent
- searchIndexEntries.sort(({ uiName: uiNameA }, { uiName: uiNameB }) =>
- uiNameA.localeCompare(uiNameB),
- );
- for (const { id, uiName } of searchIndexEntries) {
- searchIndex.addEntry(id, uiName);
- }
- result[communityThreadID] = searchIndex;
- }
- return result;
- }, [chatMentionCandidatesObj]);
-}
-
-function useThreadChatMentionSearchIndex(
- threadInfo: ThreadInfo,
-): SentencePrefixSearchIndex {
- const chatMentionCandidatesSearchIndex = useChatMentionSearchIndex();
- const { communityThreadIDForGenesisThreads } =
- useChatMentionCandidatesObjAndUtils();
- if (threadInfo.community === genesis.id) {
- return chatMentionCandidatesSearchIndex[
- communityThreadIDForGenesisThreads[threadInfo.id]
- ];
- }
- return chatMentionCandidatesSearchIndex[
- threadInfo.community ?? threadInfo.id
- ];
-}
-
export {
threadHasPermission,
viewerIsMember,
@@ -1961,8 +1757,5 @@
useRoleMemberCountsForCommunity,
useRoleUserSurfacedPermissions,
getThreadsToDeleteText,
- useChatMentionCandidatesObj,
- useThreadChatMentionCandidates,
useUserProfileThreadInfo,
- useThreadChatMentionSearchIndex,
};
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
@@ -31,6 +31,10 @@
joinThread,
newThreadActionTypes,
} from 'lib/actions/thread-actions.js';
+import {
+ useChatMentionContext,
+ useThreadChatMentionCandidates,
+} 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';
@@ -58,8 +62,6 @@
threadActualMembers,
checkIfDefaultMembersAreVoiced,
draftKeyFromThreadID,
- useThreadChatMentionCandidates,
- useThreadChatMentionSearchIndex,
} from 'lib/shared/thread-utils.js';
import type { CalendarQuery } from 'lib/types/entry-types.js';
import type { LoadingStatus } from 'lib/types/loading-types.js';
@@ -1266,9 +1268,8 @@
const userSearchIndex = useSelector(userStoreMentionSearchIndex);
- const chatMentionSearchIndex = useThreadChatMentionSearchIndex(
- props.threadInfo,
- );
+ const { getChatMentionSearchIndex } = useChatMentionContext();
+ const chatMentionSearchIndex = getChatMentionSearchIndex(props.threadInfo);
const { parentThreadID } = props.threadInfo;
const parentThreadInfo = useSelector(state =>
diff --git a/native/chat/message-list-types.js b/native/chat/message-list-types.js
--- a/native/chat/message-list-types.js
+++ b/native/chat/message-list-types.js
@@ -4,7 +4,7 @@
import invariant from 'invariant';
import * as React from 'react';
-import { useThreadChatMentionCandidates } from 'lib/shared/thread-utils.js';
+import { useThreadChatMentionCandidates } from 'lib/hooks/chat-mention-hooks.js';
import type { ThreadInfo } from 'lib/types/thread-types.js';
import { type UserInfo } from 'lib/types/user-types.js';
diff --git a/native/chat/message-preview.react.js b/native/chat/message-preview.react.js
--- a/native/chat/message-preview.react.js
+++ b/native/chat/message-preview.react.js
@@ -4,8 +4,8 @@
import * as React from 'react';
import { Text } from 'react-native';
+import { useThreadChatMentionCandidates } from 'lib/hooks/chat-mention-hooks.js';
import { useMessagePreview } from 'lib/shared/message-utils.js';
-import { useThreadChatMentionCandidates } from 'lib/shared/thread-utils.js';
import { type MessageInfo } from 'lib/types/message-types.js';
import { type ThreadInfo } from 'lib/types/thread-types.js';
diff --git a/native/chat/sidebar-input-bar-height-measurer.react.js b/native/chat/sidebar-input-bar-height-measurer.react.js
--- a/native/chat/sidebar-input-bar-height-measurer.react.js
+++ b/native/chat/sidebar-input-bar-height-measurer.react.js
@@ -4,7 +4,7 @@
import { View, StyleSheet } from 'react-native';
import { useLoggedInUserInfo } from 'lib/hooks/account-hooks.js';
-import { useThreadChatMentionCandidates } from 'lib/shared/thread-utils.js';
+import { useThreadChatMentionCandidates } from 'lib/hooks/chat-mention-hooks.js';
import { DummyChatInputBar } from './chat-input-bar.react.js';
import { useMessageListScreenWidth } from './composed-message-width.js';
diff --git a/native/chat/sidebar-navigation.js b/native/chat/sidebar-navigation.js
--- a/native/chat/sidebar-navigation.js
+++ b/native/chat/sidebar-navigation.js
@@ -5,10 +5,10 @@
import { ENSCacheContext } from 'lib/components/ens-cache-provider.react.js';
import { useLoggedInUserInfo } from 'lib/hooks/account-hooks.js';
+import { useThreadChatMentionCandidates } from 'lib/hooks/chat-mention-hooks.js';
import {
createPendingSidebar,
createUnresolvedPendingSidebar,
- useThreadChatMentionCandidates,
} from 'lib/shared/thread-utils.js';
import type {
ThreadInfo,
diff --git a/native/chat/utils.js b/native/chat/utils.js
--- a/native/chat/utils.js
+++ b/native/chat/utils.js
@@ -5,12 +5,10 @@
import Animated from 'react-native-reanimated';
import { useLoggedInUserInfo } from 'lib/hooks/account-hooks.js';
+import { useThreadChatMentionCandidates } from 'lib/hooks/chat-mention-hooks.js';
import { colorIsDark } from 'lib/shared/color-utils.js';
import { messageKey } from 'lib/shared/message-utils.js';
-import {
- viewerIsMember,
- useThreadChatMentionCandidates,
-} from 'lib/shared/thread-utils.js';
+import { viewerIsMember } from 'lib/shared/thread-utils.js';
import type { ThreadInfo } from 'lib/types/thread-types.js';
import { clusterEndHeight } from './chat-constants.js';
diff --git a/native/root.react.js b/native/root.react.js
--- a/native/root.react.js
+++ b/native/root.react.js
@@ -19,6 +19,7 @@
import { Provider } from 'react-redux';
import { PersistGate as ReduxPersistGate } from 'redux-persist/es/integration/react.js';
+import { ChatMentionContextProvider } from 'lib/components/chat-mention-provider.react.js';
import { EditUserAvatarProvider } from 'lib/components/edit-user-avatar-provider.react.js';
import { ENSCacheProvider } from 'lib/components/ens-cache-provider.react.js';
import IntegrityHandler from 'lib/components/integrity-handler.react.js';
@@ -259,7 +260,9 @@
-
+
+
+
diff --git a/web/app.react.js b/web/app.react.js
--- a/web/app.react.js
+++ b/web/app.react.js
@@ -15,6 +15,7 @@
fetchEntriesActionTypes,
updateCalendarQueryActionTypes,
} from 'lib/actions/entry-actions.js';
+import { ChatMentionContextProvider } from 'lib/components/chat-mention-provider.react.js';
import { EditUserAvatarProvider } from 'lib/components/edit-user-avatar-provider.react.js';
import {
ModalProvider,
@@ -193,14 +194,16 @@
-
-
-
-
-
-
-
- {content}
+
+
+
+
+
+
+
+
+ {content}
+
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
@@ -10,6 +10,10 @@
newThreadActionTypes,
} from 'lib/actions/thread-actions.js';
import SWMansionIcon from 'lib/components/SWMansionIcon.react.js';
+import {
+ useChatMentionContext,
+ useThreadChatMentionCandidates,
+} 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';
@@ -28,8 +32,6 @@
threadFrozenDueToViewerBlock,
threadActualMembers,
checkIfDefaultMembersAreVoiced,
- useThreadChatMentionCandidates,
- useThreadChatMentionSearchIndex,
} from 'lib/shared/thread-utils.js';
import type { CalendarQuery } from 'lib/types/entry-types.js';
import type { LoadingStatus } from 'lib/types/loading-types.js';
@@ -581,9 +583,8 @@
const dispatchActionPromise = useDispatchActionPromise();
const callJoinThread = useServerCall(joinThread);
const userSearchIndex = useSelector(userStoreMentionSearchIndex);
- const chatMentionSearchIndex = useThreadChatMentionSearchIndex(
- props.threadInfo,
- );
+ const { getChatMentionSearchIndex } = useChatMentionContext();
+ const chatMentionSearchIndex = getChatMentionSearchIndex(props.threadInfo);
const { parentThreadID } = props.threadInfo;
const parentThreadInfo = useSelector(state =>
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
@@ -12,6 +12,7 @@
fetchMostRecentMessagesActionTypes,
fetchMostRecentMessages,
} from 'lib/actions/message-actions.js';
+import { useThreadChatMentionCandidates } from 'lib/hooks/chat-mention-hooks.js';
import { useOldestMessageServerID } from 'lib/hooks/message-hooks.js';
import { registerFetchKey } from 'lib/reducers/loading-reducer.js';
import {
@@ -19,10 +20,7 @@
useMessageListData,
} from 'lib/selectors/chat-selectors.js';
import { messageKey } from 'lib/shared/message-utils.js';
-import {
- threadIsPending,
- useThreadChatMentionCandidates,
-} from 'lib/shared/thread-utils.js';
+import { threadIsPending } from 'lib/shared/thread-utils.js';
import type { FetchMessageInfosPayload } from 'lib/types/message-types.js';
import { threadTypes } from 'lib/types/thread-types-enum.js';
import { type ThreadInfo } from 'lib/types/thread-types.js';
diff --git a/web/chat/message-preview.react.js b/web/chat/message-preview.react.js
--- a/web/chat/message-preview.react.js
+++ b/web/chat/message-preview.react.js
@@ -4,8 +4,8 @@
import invariant from 'invariant';
import * as React from 'react';
+import { useThreadChatMentionCandidates } from 'lib/hooks/chat-mention-hooks.js';
import { useMessagePreview } from 'lib/shared/message-utils.js';
-import { useThreadChatMentionCandidates } from 'lib/shared/thread-utils.js';
import { type MessageInfo } from 'lib/types/message-types.js';
import { type ThreadInfo } from 'lib/types/thread-types.js';
diff --git a/web/components/message-result.react.js b/web/components/message-result.react.js
--- a/web/components/message-result.react.js
+++ b/web/components/message-result.react.js
@@ -3,9 +3,9 @@
import classNames from 'classnames';
import * as React from 'react';
+import { useThreadChatMentionCandidates } from 'lib/hooks/chat-mention-hooks.js';
import { useStringForUser } from 'lib/hooks/ens-cache.js';
import type { ChatMessageInfoItem } from 'lib/selectors/chat-selectors.js';
-import { useThreadChatMentionCandidates } from 'lib/shared/thread-utils.js';
import type { ThreadInfo } from 'lib/types/thread-types.js';
import { longAbsoluteDate } from 'lib/utils/date-utils.js';
diff --git a/web/modals/threads/sidebars/sidebar.react.js b/web/modals/threads/sidebars/sidebar.react.js
--- a/web/modals/threads/sidebars/sidebar.react.js
+++ b/web/modals/threads/sidebars/sidebar.react.js
@@ -4,9 +4,9 @@
import * as React from 'react';
import { useModalContext } from 'lib/components/modal-provider.react.js';
+import { useThreadChatMentionCandidates } from 'lib/hooks/chat-mention-hooks.js';
import type { ChatThreadItem } from 'lib/selectors/chat-selectors.js';
import { useMessagePreview } from 'lib/shared/message-utils.js';
-import { useThreadChatMentionCandidates } from 'lib/shared/thread-utils.js';
import { shortAbsoluteDate } from 'lib/utils/date-utils.js';
import { useResolvedThreadInfo } from 'lib/utils/entity-helpers.js';
diff --git a/web/modals/threads/subchannels/subchannel.react.js b/web/modals/threads/subchannels/subchannel.react.js
--- a/web/modals/threads/subchannels/subchannel.react.js
+++ b/web/modals/threads/subchannels/subchannel.react.js
@@ -4,9 +4,9 @@
import * as React from 'react';
import { useModalContext } from 'lib/components/modal-provider.react.js';
+import { useThreadChatMentionCandidates } from 'lib/hooks/chat-mention-hooks.js';
import { type ChatThreadItem } from 'lib/selectors/chat-selectors.js';
import { useMessagePreview } from 'lib/shared/message-utils.js';
-import { useThreadChatMentionCandidates } from 'lib/shared/thread-utils.js';
import { shortAbsoluteDate } from 'lib/utils/date-utils.js';
import { useResolvedThreadInfo } from 'lib/utils/entity-helpers.js';
diff --git a/web/selectors/thread-selectors.js b/web/selectors/thread-selectors.js
--- a/web/selectors/thread-selectors.js
+++ b/web/selectors/thread-selectors.js
@@ -7,10 +7,10 @@
import { ENSCacheContext } from 'lib/components/ens-cache-provider.react.js';
import { useLoggedInUserInfo } from 'lib/hooks/account-hooks.js';
+import { useThreadChatMentionCandidates } from 'lib/hooks/chat-mention-hooks.js';
import {
createPendingSidebar,
threadInHomeChatList,
- useThreadChatMentionCandidates,
} from 'lib/shared/thread-utils.js';
import type {
ComposableMessageInfo,