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
@@ -17,6 +17,7 @@
getMentionTypeaheadUserSuggestions,
getTypeaheadRegexMatches,
getUserMentionsCandidates,
+ getMentionTypeaheadChatSuggestions,
type MentionTypeaheadSuggestionItem,
type TypeaheadMatchedStrings,
} from 'lib/shared/mention-utils.js';
@@ -27,6 +28,8 @@
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';
@@ -578,6 +581,9 @@
const dispatchActionPromise = useDispatchActionPromise();
const callJoinThread = useServerCall(joinThread);
const userSearchIndex = useSelector(userStoreMentionSearchIndex);
+ const chatMentionSearchIndex = useThreadChatMentionSearchIndex(
+ props.threadInfo,
+ );
const { parentThreadID } = props.threadInfo;
const parentThreadInfo = useSelector(state =>
@@ -589,6 +595,10 @@
parentThreadInfo,
);
+ const chatMentionCandidates = useThreadChatMentionCandidates(
+ props.threadInfo,
+ );
+
const typeaheadRegexMatches = React.useMemo(
() =>
getTypeaheadRegexMatches(
@@ -619,14 +629,15 @@
const setter = props.inputState.setTypeaheadState;
setter({
frozenUserMentionsCandidates: userMentionsCandidates,
+ frozenChatMentionsCandidates: chatMentionCandidates,
});
}
}, [
userMentionsCandidates,
props.inputState.setTypeaheadState,
props.inputState.typeaheadState.keepUpdatingThreadMembers,
+ chatMentionCandidates,
]);
-
const suggestedUsers = React.useMemo(() => {
if (!typeaheadMatchedStrings) {
return [];
@@ -643,6 +654,26 @@
viewerID,
typeaheadMatchedStrings,
]);
+ const suggestedChats = React.useMemo(() => {
+ if (!typeaheadMatchedStrings) {
+ return [];
+ }
+ return getMentionTypeaheadChatSuggestions(
+ chatMentionSearchIndex,
+ props.inputState.typeaheadState.frozenChatMentionsCandidates,
+ typeaheadMatchedStrings.textPrefix,
+ );
+ }, [
+ chatMentionSearchIndex,
+ props.inputState.typeaheadState.frozenChatMentionsCandidates,
+ typeaheadMatchedStrings,
+ ]);
+ const suggestions = React.useMemo(() => {
+ if (!typeaheadMatchedStrings) {
+ return [];
+ }
+ return [...suggestedUsers, ...suggestedChats];
+ }, [suggestedUsers, suggestedChats, typeaheadMatchedStrings]);
return (
);
diff --git a/web/input/input-state-container.react.js b/web/input/input-state-container.react.js
--- a/web/input/input-state-container.react.js
+++ b/web/input/input-state-container.react.js
@@ -176,6 +176,7 @@
canBeVisible: false,
keepUpdatingThreadMembers: true,
frozenUserMentionsCandidates: [],
+ frozenChatMentionsCandidates: {},
moveChoiceUp: null,
moveChoiceDown: null,
close: null,
diff --git a/web/input/input-state.js b/web/input/input-state.js
--- a/web/input/input-state.js
+++ b/web/input/input-state.js
@@ -9,7 +9,11 @@
type MediaMissionStep,
} from 'lib/types/media-types.js';
import type { RawTextMessageInfo } from 'lib/types/messages/text.js';
-import type { ThreadInfo, RelativeMemberInfo } from 'lib/types/thread-types.js';
+import type {
+ ThreadInfo,
+ RelativeMemberInfo,
+ ChatMentionCandidates,
+} from 'lib/types/thread-types.js';
export type PendingMultimediaUpload = {
+localID: string,
@@ -43,6 +47,7 @@
+canBeVisible: boolean,
+keepUpdatingThreadMembers: boolean,
+frozenUserMentionsCandidates: $ReadOnlyArray,
+ +frozenChatMentionsCandidates: ChatMentionCandidates,
+moveChoiceUp: ?() => void,
+moveChoiceDown: ?() => void,
+close: ?() => void,
diff --git a/web/utils/typeahead-utils.js b/web/utils/typeahead-utils.js
--- a/web/utils/typeahead-utils.js
+++ b/web/utils/typeahead-utils.js
@@ -8,11 +8,13 @@
getNewTextAndSelection,
type MentionTypeaheadSuggestionItem,
type TypeaheadTooltipActionItem,
+ encodeChatMentionText,
} from 'lib/shared/mention-utils.js';
import { validChatNameRegexString } from 'lib/shared/thread-utils.js';
import { stringForUserExplicit } from 'lib/shared/user-utils.js';
import type { SetState } from 'lib/types/hook-types.js';
+import ThreadAvatar from '../avatars/thread-avatar.react.js';
import UserAvatar from '../avatars/user-avatar.react.js';
import { typeaheadStyle } from '../chat/chat-constants.js';
import css from '../chat/typeahead-tooltip.css';
@@ -120,6 +122,29 @@
userInfo: suggestedUser,
},
});
+ } else if (suggestion.type === 'chat') {
+ const suggestedChat = suggestion.threadInfo;
+ const mentionText = `@[[${suggestedChat.id}:${encodeChatMentionText(
+ suggestedChat.uiName,
+ )}]]`;
+ actions.push({
+ key: suggestedChat.id,
+ execute: () => {
+ const { newText, newSelectionStart } = getNewTextAndSelection(
+ textBeforeAtSymbol,
+ inputStateDraft,
+ textPrefix,
+ mentionText,
+ );
+
+ inputStateSetDraft(newText);
+ inputStateSetTextCursorPosition(newSelectionStart);
+ },
+ actionButtonContent: {
+ type: 'chat',
+ threadInfo: suggestedChat,
+ },
+ });
}
}
return actions;
@@ -155,6 +180,15 @@
);
typeaheadButtonText = `@${stringForUserExplicit(suggestedUser)}`;
+ } else if (actionButtonContent.type === 'chat') {
+ const suggestedChat = actionButtonContent.threadInfo;
+ avatarComponent = (
+
+ );
+ typeaheadButtonText = `@${suggestedChat.uiName}`;
}
return (