Page MenuHomePhabricator

D8903.diff
No OneTemporary

D8903.diff

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
@@ -8,9 +8,11 @@
} from './thread-utils.js';
import { stringForUserExplicit } from './user-utils.js';
import { threadTypes } from '../types/thread-types-enum.js';
-import {
- type ThreadInfo,
- type RelativeMemberInfo,
+import type {
+ ThreadInfo,
+ RelativeMemberInfo,
+ ChatMentionCandidates,
+ ResolvedThreadInfo,
} from '../types/thread-types.js';
export type TypeaheadMatchedStrings = {
@@ -41,6 +43,10 @@
const chatMentionRegexString = `^(?<!\\\\)(@\\[\\[([0-9,|]+):(${validChatNameRegexString}?)(?<!\\\\)\\]\\])`;
const chatMentionRegex: RegExp = new RegExp(chatMentionRegexString);
+function encodeChatMentionText(text: string): string {
+ return text.replace(/]/g, '\\]');
+}
+
function decodeChatMentionText(text: string): string {
return text.replace(/\\]/g, ']');
}
@@ -80,6 +86,21 @@
);
}
+function getMentionTypeaheadChatSuggestions(
+ chatSearchIndex: SentencePrefixSearchIndex,
+ chatMentionCandidates: ChatMentionCandidates,
+ chatPrefix: string,
+): $ReadOnlyArray<ResolvedThreadInfo> {
+ const threadIDs = chatSearchIndex.getSearchResults(chatPrefix);
+ const result = [];
+ for (const threadID in chatMentionCandidates) {
+ if (chatPrefix.length === 0 || threadIDs.includes(threadID)) {
+ result.push(chatMentionCandidates[threadID]);
+ }
+ }
+ return result;
+}
+
function getNewTextAndSelection(
textBeforeAtSymbol: string,
entireText: string,
@@ -122,9 +143,11 @@
isUserMentioned,
extractUserMentionsFromText,
getMentionTypeaheadUserSuggestions,
+ getMentionTypeaheadChatSuggestions,
getNewTextAndSelection,
getTypeaheadRegexMatches,
getUserMentionsCandidates,
chatMentionRegex,
+ encodeChatMentionText,
decodeChatMentionText,
};
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 from './avatar.react.js';
import { useSelector } from '../redux/redux-utils.js';
type Props = {
- +threadInfo: RawThreadInfo | ThreadInfo,
+ +threadInfo: RawThreadInfo | ThreadInfo | ResolvedThreadInfo,
+size: 'micro' | 'small' | 'large' | 'profile',
};
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,9 +38,11 @@
import { useEditMessage } from 'lib/shared/edit-messages-utils.js';
import {
getMentionTypeaheadUserSuggestions,
+ getMentionTypeaheadChatSuggestions,
getTypeaheadRegexMatches,
type Selection,
getUserMentionsCandidates,
+ encodeChatMentionText,
} from 'lib/shared/mention-utils.js';
import {
localIDPrefix,
@@ -58,6 +60,7 @@
checkIfDefaultMembersAreVoiced,
draftKeyFromThreadID,
useThreadChatMentionCandidates,
+ useThreadChatMentionSearchIndex,
} from 'lib/shared/thread-utils.js';
import { stringForUserExplicit } from 'lib/shared/user-utils.js';
import type { CalendarQuery } from 'lib/types/entry-types.js';
@@ -70,11 +73,13 @@
} from 'lib/types/message-types.js';
import type { Dispatch } from 'lib/types/redux-types.js';
import { threadPermissions } from 'lib/types/thread-permission-types.js';
-import {
- type ThreadInfo,
- type ClientThreadJoinRequest,
- type ThreadJoinPayload,
- type RelativeMemberInfo,
+import type {
+ ThreadInfo,
+ ResolvedThreadInfo,
+ ClientThreadJoinRequest,
+ ThreadJoinPayload,
+ RelativeMemberInfo,
+ ChatMentionCandidates,
} from 'lib/types/thread-types.js';
import { type UserInfos } from 'lib/types/user-types.js';
import {
@@ -91,6 +96,7 @@
} from './message-editing-context.react.js';
import type { RemoveEditMode } from './message-list-types.js';
import TypeaheadTooltip from './typeahead-tooltip.react.js';
+import ThreadAvatar from '../avatars/thread-avatar.react.js';
import UserAvatar from '../avatars/user-avatar.react.js';
import Button from '../components/button.react.js';
// eslint-disable-next-line import/extensions
@@ -171,6 +177,8 @@
+inputState: ?InputState,
+userSearchIndex: SentencePrefixSearchIndex,
+userMentionsCandidates: $ReadOnlyArray<RelativeMemberInfo>,
+ +chatMentionSearchIndex: SentencePrefixSearchIndex,
+ +chatMentionCandidates: ChatMentionCandidates,
+parentThreadInfo: ?ThreadInfo,
+editedMessagePreview: ?MessagePreviewResult,
+editedMessageInfo: ?MessageInfo,
@@ -498,20 +506,37 @@
}
typeaheadTooltipButton = ({ item, suggestionText, styles }) => {
+ let text = suggestionText;
+ let avatarComponent = null;
+ if (item.type === 'user') {
+ text = suggestionText;
+ avatarComponent = <UserAvatar size="small" userID={item.user.id} />;
+ } else if (item.type === 'chat') {
+ text = `@${item.chat.uiName}`;
+ avatarComponent = <ThreadAvatar size="small" threadInfo={item.chat} />;
+ }
return (
<>
- <UserAvatar size="small" userID={item.id} />
+ {avatarComponent}
<Text style={styles.buttonLabel} numberOfLines={1}>
- {suggestionText}
+ {text}
</Text>
</>
);
};
typeaheadTooltipSuggestionTextExtractor = (
- item: RelativeMemberInfo,
+ item:
+ | { type: 'user', user: RelativeMemberInfo }
+ | { type: 'chat', chat: ResolvedThreadInfo },
): string => {
- return `@${stringForUserExplicit(item)}`;
+ if (item.type === 'user') {
+ return `@${stringForUserExplicit(item.user)}`;
+ }
+ if (item.type === 'chat') {
+ return `@[[${item.chat.id}:${encodeChatMentionText(item.chat.uiName)}]]`;
+ }
+ return '';
};
render() {
@@ -580,13 +605,22 @@
this.props.viewerID,
typeaheadMatchedStrings.textPrefix,
);
+ const suggestedChats = getMentionTypeaheadChatSuggestions(
+ this.props.chatMentionSearchIndex,
+ this.props.chatMentionCandidates,
+ typeaheadMatchedStrings.textPrefix,
+ );
+ const suggestions = [
+ ...suggestedUsers.map(user => ({ type: 'user', user })),
+ ...suggestedChats.map(chat => ({ type: 'chat', chat })),
+ ];
- if (suggestedUsers.length > 0) {
+ if (suggestions.length > 0) {
typeaheadTooltip = (
<TypeaheadTooltip
text={this.state.text}
matchedStrings={typeaheadMatchedStrings}
- suggestions={suggestedUsers}
+ suggestions={suggestions}
focusAndUpdateTextAndSelection={this.focusAndUpdateTextAndSelection}
typeaheadButtonRenderer={this.typeaheadTooltipButton}
suggestionTextExtractor={
@@ -1272,6 +1306,10 @@
const userSearchIndex = useSelector(userStoreMentionSearchIndex);
+ const chatMentionSearchIndex = useThreadChatMentionSearchIndex(
+ props.threadInfo,
+ );
+
const { parentThreadID } = props.threadInfo;
const parentThreadInfo = useSelector(state =>
parentThreadID ? threadInfoSelector(state)[parentThreadID] : null,
@@ -1316,6 +1354,8 @@
inputState={inputState}
userSearchIndex={userSearchIndex}
userMentionsCandidates={userMentionsCandidates}
+ chatMentionSearchIndex={chatMentionSearchIndex}
+ chatMentionCandidates={chatMentionCandidates}
parentThreadInfo={parentThreadInfo}
editedMessagePreview={editedMessagePreview}
editedMessageInfo={editedMessageInfo}
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
@@ -1,11 +1,12 @@
// @flow
import { oldValidUsernameRegexString } from 'lib/shared/account-utils.js';
+import { validChatNameRegexString } from 'lib/shared/thread-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+)|^)@(${oldValidUsernameRegexString}|${validChatNameRegexString})?$`,
);
export { nativeMentionTypeaheadRegex };

File Metadata

Mime Type
text/plain
Expires
Tue, Oct 22, 10:35 PM (21 h, 6 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2344758
Default Alt Text
D8903.diff (8 KB)

Event Timeline