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 @@ -5,10 +5,11 @@ import { threadOtherMembers, chatNameMaxLength } from './thread-utils.js'; import { stringForUserExplicit } from './user-utils.js'; import { threadTypes } from '../types/thread-types-enum.js'; -import { - type ThreadInfo, - type RelativeMemberInfo, - type ResolvedThreadInfo, +import type { + ThreadInfo, + RelativeMemberInfo, + ResolvedThreadInfo, + ChatMentionCandidates, } from '../types/thread-types.js'; import { idSchemaRegex } from '../utils/validation-utils.js'; @@ -108,6 +109,34 @@ .map(userInfo => ({ type: 'user', userInfo })); } +function getMentionTypeaheadChatSuggestions( + chatSearchIndex: SentencePrefixSearchIndex, + chatMentionCandidates: ChatMentionCandidates, + chatPrefix: string, +): $ReadOnlyArray { + const result = []; + if (chatPrefix.length === 0) { + for (const threadID in chatMentionCandidates) { + result.push({ + type: 'chat', + threadInfo: chatMentionCandidates[threadID], + }); + } + } else { + const threadIDs = chatSearchIndex.getSearchResults(chatPrefix); + for (const threadID of threadIDs) { + if (!chatMentionCandidates[threadID]) { + continue; + } + result.push({ + type: 'chat', + threadInfo: chatMentionCandidates[threadID], + }); + } + } + return result; +} + function getNewTextAndSelection( textBeforeAtSymbol: string, entireText: string, @@ -150,6 +179,7 @@ isUserMentioned, extractUserMentionsFromText, getMentionTypeaheadUserSuggestions, + getMentionTypeaheadChatSuggestions, getNewTextAndSelection, getTypeaheadRegexMatches, getUserMentionsCandidates, 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 @@ -111,8 +111,8 @@ const chatNameMaxLength = 191; const chatNameMinLength = 0; const secondCharRange = `{${chatNameMinLength},${chatNameMaxLength}}`; -const validChatNameRegexString = `^.${secondCharRange}$`; -const validChatNameRegex: RegExp = new RegExp(validChatNameRegexString); +const validChatNameRegexString = `.${secondCharRange}`; +const validChatNameRegex: RegExp = new RegExp(`^${validChatNameRegexString}$`); function threadHasPermission( threadInfo: ?(ThreadInfo | RawThreadInfo), @@ -1868,6 +1868,7 @@ getAvailableThreadMemberActions, threadMembersWithoutAddedAshoat, validChatNameRegex, + validChatNameRegexString, chatNameMaxLength, patchThreadInfoToIncludeMentionedMembersOfParent, threadInfoInsideCommunity, diff --git a/native/avatars/thread-avatar.react.js b/native/avatars/thread-avatar.react.js --- a/native/avatars/thread-avatar.react.js +++ b/native/avatars/thread-avatar.react.js @@ -8,13 +8,17 @@ } from 'lib/shared/avatar-utils.js'; import { getSingleOtherUser } from 'lib/shared/thread-utils.js'; import { threadTypes } from 'lib/types/thread-types-enum.js'; -import { type RawThreadInfo, type ThreadInfo } from 'lib/types/thread-types.js'; +import type { + RawThreadInfo, + ThreadInfo, + ResolvedThreadInfo, +} from 'lib/types/thread-types.js'; import Avatar, { type AvatarSize } from './avatar.react.js'; import { useSelector } from '../redux/redux-utils.js'; type Props = { - +threadInfo: RawThreadInfo | ThreadInfo, + +threadInfo: RawThreadInfo | ThreadInfo | ResolvedThreadInfo, +size: AvatarSize, }; 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 @@ -38,6 +38,7 @@ import { useEditMessage } from 'lib/shared/edit-messages-utils.js'; import { getMentionTypeaheadUserSuggestions, + getMentionTypeaheadChatSuggestions, getTypeaheadRegexMatches, type Selection, getUserMentionsCandidates, @@ -58,6 +59,7 @@ 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'; @@ -74,6 +76,7 @@ ClientThreadJoinRequest, ThreadJoinPayload, RelativeMemberInfo, + ChatMentionCandidates, } from 'lib/types/thread-types.js'; import { type UserInfos } from 'lib/types/user-types.js'; import { @@ -173,6 +176,8 @@ +inputState: ?InputState, +userSearchIndex: SentencePrefixSearchIndex, +userMentionsCandidates: $ReadOnlyArray, + +chatMentionSearchIndex: SentencePrefixSearchIndex, + +chatMentionCandidates: ChatMentionCandidates, +parentThreadInfo: ?ThreadInfo, +editedMessagePreview: ?MessagePreviewResult, +editedMessageInfo: ?MessageInfo, @@ -565,13 +570,19 @@ this.props.viewerID, typeaheadMatchedStrings.query, ); + const suggestedChats = getMentionTypeaheadChatSuggestions( + this.props.chatMentionSearchIndex, + this.props.chatMentionCandidates, + typeaheadMatchedStrings.query, + ); + const suggestions = [...suggestedUsers, ...suggestedChats]; - if (suggestedUsers.length > 0) { + if (suggestions.length > 0) { typeaheadTooltip = ( parentThreadID ? threadInfoSelector(state)[parentThreadID] : null, @@ -1299,6 +1314,8 @@ inputState={inputState} userSearchIndex={userSearchIndex} userMentionsCandidates={userMentionsCandidates} + chatMentionSearchIndex={chatMentionSearchIndex} + chatMentionCandidates={chatMentionCandidates} parentThreadInfo={parentThreadInfo} editedMessagePreview={editedMessagePreview} editedMessageInfo={editedMessageInfo} diff --git a/native/chat/mention-typeahead-tooltip-button.react.js b/native/chat/mention-typeahead-tooltip-button.react.js --- a/native/chat/mention-typeahead-tooltip-button.react.js +++ b/native/chat/mention-typeahead-tooltip-button.react.js @@ -8,6 +8,7 @@ MentionTypeaheadSuggestionItem, } from 'lib/shared/mention-utils.js'; +import ThreadAvatar from '../avatars/thread-avatar.react.js'; import UserAvatar from '../avatars/user-avatar.react.js'; import Button from '../components/button.react.js'; import { useStyles } from '../themes/colors.js'; @@ -26,6 +27,14 @@ ); typeaheadTooltipButtonText = item.actionButtonContent.userInfo.username; + } else if (item.actionButtonContent.type === 'chat') { + typeaheadTooltipButtonText = item.actionButtonContent.threadInfo.uiName; + avatarComponent = ( + + ); } return ( diff --git a/native/utils/typeahead-utils.js b/native/utils/typeahead-utils.js --- a/native/utils/typeahead-utils.js +++ b/native/utils/typeahead-utils.js @@ -2,7 +2,6 @@ import * as React from 'react'; -import { oldValidUsernameRegexString } from 'lib/shared/account-utils.js'; import { getNewTextAndSelection, getRawChatMention, @@ -10,12 +9,13 @@ type TypeaheadTooltipActionItem, type MentionTypeaheadSuggestionItem, } from 'lib/shared/mention-utils.js'; +import { validChatNameRegexString } from 'lib/shared/thread-utils.js'; import { stringForUserExplicit } from 'lib/shared/user-utils.js'; // Native regex is a little bit different than web one as // there are no named capturing groups yet on native. const nativeMentionTypeaheadRegex: RegExp = new RegExp( - `((^(.|\n)*\\s+)|^)@(${oldValidUsernameRegexString})?$`, + `((^(.|\n)*\\s+)|^)@(${validChatNameRegexString})?$`, ); export type TypeaheadTooltipActionsParams = {