diff --git a/lib/components/chat-mention-provider.react.js b/lib/components/chat-mention-provider.react.js
index f119ac86c..3efc61de6 100644
--- a/lib/components/chat-mention-provider.react.js
+++ b/lib/components/chat-mention-provider.react.js
@@ -1,244 +1,245 @@
// @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(resolvedThreadInfos: {
- +[id: string]: ResolvedThreadInfo,
-}): {
+function getChatMentionCandidates(
+ threadInfos: { +[id: string]: ThreadInfo },
+ resolvedThreadInfos: { +[id: string]: ResolvedThreadInfo },
+): {
chatMentionCandidatesObj: ChatMentionCandidatesObj,
communityThreadIDForGenesisThreads: { +[id: string]: string },
} {
const result: {
[string]: {
[string]: ResolvedThreadInfo,
},
} = {};
const visitedGenesisThreads = new Set();
const communityThreadIDForGenesisThreads: { [string]: string } = {};
for (const currentThreadID in resolvedThreadInfos) {
const currentResolvedThreadInfo = resolvedThreadInfos[currentThreadID];
const { community: currentThreadCommunity } = currentResolvedThreadInfo;
if (!currentThreadCommunity) {
if (!result[currentThreadID]) {
result[currentThreadID] = {
[currentThreadID]: currentResolvedThreadInfo,
};
}
continue;
}
if (!result[currentThreadCommunity]) {
result[currentThreadCommunity] = {};
result[currentThreadCommunity][currentThreadCommunity] =
resolvedThreadInfos[currentThreadCommunity];
}
// Handle GENESIS community case: mentioning inside GENESIS should only
// show chats and threads inside the top level that is below GENESIS.
if (
resolvedThreadInfos[currentThreadCommunity].type === threadTypes.GENESIS
) {
if (visitedGenesisThreads.has(currentThreadID)) {
continue;
}
const threadTraversePath = [currentResolvedThreadInfo];
visitedGenesisThreads.add(currentThreadID);
let currentlySelectedThreadID = currentResolvedThreadInfo.parentThreadID;
while (currentlySelectedThreadID) {
const currentlySelectedThreadInfo =
resolvedThreadInfos[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 = resolvedThreadInfos[
lastThreadInTraversePath.parentThreadID
]
? lastThreadInTraversePath.parentThreadID
: lastThreadInTraversePath.id;
} else {
lastThreadInTraversePathParentID = lastThreadInTraversePath.id;
}
if (
resolvedThreadInfos[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] = currentResolvedThreadInfo;
}
return {
chatMentionCandidatesObj: result,
communityThreadIDForGenesisThreads,
};
}
// Without allAtOnce, useChatMentionCandidatesObjAndUtils is very expensive.
// useResolvedThreadInfosObj 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.
const useResolvedThreadInfosObjOptions = { allAtOnce: true };
function useChatMentionCandidatesObjAndUtils(): {
chatMentionCandidatesObj: ChatMentionCandidatesObj,
resolvedThreadInfos: ChatMentionCandidates,
communityThreadIDForGenesisThreads: { +[id: string]: string },
} {
const threadInfos = useSelector(threadInfoSelector);
const resolvedThreadInfos = useResolvedThreadInfosObj(
threadInfos,
useResolvedThreadInfosObjOptions,
);
const { chatMentionCandidatesObj, communityThreadIDForGenesisThreads } =
React.useMemo(
- () => getChatMentionCandidates(resolvedThreadInfos),
- [resolvedThreadInfos],
+ () => getChatMentionCandidates(threadInfos, resolvedThreadInfos),
+ [threadInfos, resolvedThreadInfos],
);
return {
chatMentionCandidatesObj,
resolvedThreadInfos,
communityThreadIDForGenesisThreads,
};
}
function useChatMentionSearchIndex(
chatMentionCandidatesObj: ChatMentionCandidatesObj,
): {
+[id: string]: SentencePrefixSearchIndex,
} {
return React.useMemo(() => {
const result: { [string]: SentencePrefixSearchIndex } = {};
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 };