Page MenuHomePhorge

D5463.1765300399.diff
No OneTemporary

Size
13 KB
Referenced Files
None
Subscribers
None

D5463.1765300399.diff

diff --git a/lib/selectors/user-selectors.js b/lib/selectors/user-selectors.js
--- a/lib/selectors/user-selectors.js
+++ b/lib/selectors/user-selectors.js
@@ -113,36 +113,38 @@
baseRelativeMemberInfoSelectorForMembersOfThread,
);
+function filterPotentialMembers(
+ userInfos: UserInfos,
+ currentUserID: ?string,
+): { [id: string]: AccountUserInfo } {
+ const availableUsers: { [id: string]: AccountUserInfo } = {};
+
+ for (const id in userInfos) {
+ const { username, relationshipStatus } = userInfos[id];
+ if (id === currentUserID || !username) {
+ continue;
+ }
+ if (
+ relationshipStatus !== userRelationshipStatus.BLOCKED_VIEWER &&
+ relationshipStatus !== userRelationshipStatus.BOTH_BLOCKED
+ ) {
+ availableUsers[id] = { id, username, relationshipStatus };
+ }
+ }
+ return availableUsers;
+}
+
const userInfoSelectorForPotentialMembers: (state: BaseAppState<*>) => {
[id: string]: AccountUserInfo,
} = createSelector(
(state: BaseAppState<*>) => state.userStore.userInfos,
(state: BaseAppState<*>) => state.currentUserInfo && state.currentUserInfo.id,
- (
- userInfos: UserInfos,
- currentUserID: ?string,
- ): { [id: string]: AccountUserInfo } => {
- const availableUsers: { [id: string]: AccountUserInfo } = {};
-
- for (const id in userInfos) {
- const { username, relationshipStatus } = userInfos[id];
- if (id === currentUserID || !username) {
- continue;
- }
- if (
- relationshipStatus !== userRelationshipStatus.BLOCKED_VIEWER &&
- relationshipStatus !== userRelationshipStatus.BOTH_BLOCKED
- ) {
- availableUsers[id] = { id, username, relationshipStatus };
- }
- }
- return availableUsers;
- },
+ filterPotentialMembers,
);
function searchIndexFromUserInfos(userInfos: {
[id: string]: AccountUserInfo,
-}) {
+}): SearchIndex {
const searchIndex = new SearchIndex();
for (const id in userInfos) {
searchIndex.addEntry(id, userInfos[id].username);
@@ -214,4 +216,6 @@
isLoggedIn,
userStoreSearchIndex,
usersWithPersonalThreadSelector,
+ filterPotentialMembers,
+ searchIndexFromUserInfos,
};
diff --git a/lib/shared/search-utils.js b/lib/shared/search-utils.js
--- a/lib/shared/search-utils.js
+++ b/lib/shared/search-utils.js
@@ -21,12 +21,16 @@
function getPotentialMemberItems(
text: string,
userInfos: { +[id: string]: AccountUserInfo },
- searchIndex: SearchIndex,
+ inputSearchIndex: SearchIndex | $ReadOnlyArray<SearchIndex>,
excludeUserIDs: $ReadOnlyArray<string>,
inputParentThreadInfo: ?ThreadInfo,
inputCommunityThreadInfo: ?ThreadInfo,
threadType: ?ThreadType,
): UserListItem[] {
+ const searchIndexes = Array.isArray(inputSearchIndex)
+ ? inputSearchIndex
+ : [inputSearchIndex];
+
const communityThreadInfo =
inputCommunityThreadInfo && inputCommunityThreadInfo.id !== genesis.id
? inputCommunityThreadInfo
@@ -53,6 +57,9 @@
if (excludeUserIDs.includes(id)) {
return;
}
+ if (results.some(item => item.id === id)) {
+ return;
+ }
if (
communityThreadInfo &&
!threadMemberHasPermission(
@@ -74,9 +81,11 @@
appendUserInfo(userInfos[id]);
}
} else {
- const ids = searchIndex.getSearchResults(text);
- for (const id of ids) {
- appendUserInfo(userInfos[id]);
+ for (const searchIndex of searchIndexes) {
+ const ids = searchIndex.getSearchResults(text);
+ for (const id of ids) {
+ appendUserInfo(userInfos[id]);
+ }
}
}
diff --git a/web/chat/chat-message-list-container.react.js b/web/chat/chat-message-list-container.react.js
--- a/web/chat/chat-message-list-container.react.js
+++ b/web/chat/chat-message-list-container.react.js
@@ -18,7 +18,6 @@
threadIsPending,
} from 'lib/shared/thread-utils.js';
import { threadTypes } from 'lib/types/thread-types.js';
-import type { AccountUserInfo } from 'lib/types/user-types.js';
import ChatInputBar from './chat-input-bar.react.js';
import css from './chat-message-list-container.css';
@@ -38,13 +37,20 @@
const isChatCreation =
useSelector(state => state.navInfo.chatMode) === 'create';
- const selectedUserIDs = useSelector(state => state.navInfo.selectedUserList);
+ const selectedUserIDs = useSelector(
+ state => state.navInfo.selectedUserList ?? [],
+ );
const otherUserInfos = useSelector(userInfoSelectorForPotentialMembers);
- const userInfoInputArray: $ReadOnlyArray<AccountUserInfo> = React.useMemo(
- () => selectedUserIDs?.map(id => otherUserInfos[id]).filter(Boolean) ?? [],
- [otherUserInfos, selectedUserIDs],
+ const [userInfoInputArray, setUserInfoInputArray] = React.useState(() =>
+ selectedUserIDs.map(id => otherUserInfos[id]).filter(Boolean),
);
+ React.useEffect(() => {
+ if (!isChatCreation) {
+ setUserInfoInputArray([]);
+ }
+ }, [isChatCreation]);
+
const loggedInUserInfo = useLoggedInUserInfo();
invariant(loggedInUserInfo, 'loggedInUserInfo should be set');
@@ -110,51 +116,43 @@
invariant(threadInfo, 'ThreadInfo should be set');
const dispatch = useDispatch();
-
- // The effect removes members from list in navInfo
- // if some of the user IDs don't exist in redux store
React.useEffect(() => {
if (!isChatCreation) {
return;
}
- const existingSelectedUsersSet = new Set(
- userInfoInputArray.map(userInfo => userInfo.id),
- );
- if (
- selectedUserIDs?.length !== existingSelectedUsersSet.size ||
- !_isEqual(new Set(selectedUserIDs), existingSelectedUsersSet)
- ) {
- dispatch({
- type: updateNavInfoActionType,
- payload: {
- selectedUserList: Array.from(existingSelectedUsersSet),
- },
- });
+ const newSelectedUserIDs = userInfoInputArray.map(user => user.id);
+ if (_isEqual(new Set(selectedUserIDs), new Set(newSelectedUserIDs))) {
+ return;
}
- }, [
- dispatch,
- isChatCreation,
- otherUserInfos,
- selectedUserIDs,
- userInfoInputArray,
- ]);
+ const payload = {
+ selectedUserList: newSelectedUserIDs,
+ };
+ dispatch({
+ type: updateNavInfoActionType,
+ payload,
+ });
+ }, [dispatch, isChatCreation, selectedUserIDs, userInfoInputArray]);
React.useEffect(() => {
- if (isChatCreation && activeChatThreadID !== threadInfo?.id) {
- let payload = {
- activeChatThreadID: threadInfo?.id,
+ if (!isChatCreation) {
+ return;
+ }
+ if (activeChatThreadID === threadInfo?.id) {
+ return;
+ }
+ let payload = {
+ activeChatThreadID: threadInfo?.id,
+ };
+ if (threadIsPending(threadInfo?.id)) {
+ payload = {
+ ...payload,
+ pendingThread: threadInfo,
};
- if (threadIsPending(threadInfo?.id)) {
- payload = {
- ...payload,
- pendingThread: threadInfo,
- };
- }
- dispatch({
- type: updateNavInfoActionType,
- payload,
- });
}
+ dispatch({
+ type: updateNavInfoActionType,
+ payload,
+ });
}, [activeChatThreadID, dispatch, isChatCreation, threadInfo]);
const inputState = React.useContext(InputStateContext);
@@ -230,6 +228,7 @@
const chatUserSelection = (
<ChatThreadComposer
userInfoInputArray={userInfoInputArray}
+ setUserInfoInputArray={setUserInfoInputArray}
otherUserInfos={otherUserInfos}
threadID={threadInfo.id}
inputState={inputState}
diff --git a/web/chat/chat-thread-composer.react.js b/web/chat/chat-thread-composer.react.js
--- a/web/chat/chat-thread-composer.react.js
+++ b/web/chat/chat-thread-composer.react.js
@@ -3,12 +3,23 @@
import * as React from 'react';
import { useDispatch } from 'react-redux';
+import { searchUsers } from 'lib/actions/user-actions.js';
import SWMansionIcon from 'lib/components/SWMansionIcon.react.js';
import { useENSNames } from 'lib/hooks/ens-cache.js';
-import { userSearchIndexForPotentialMembers } from 'lib/selectors/user-selectors.js';
+import {
+ filterPotentialMembers,
+ userSearchIndexForPotentialMembers,
+ searchIndexFromUserInfos,
+} from 'lib/selectors/user-selectors.js';
import { getPotentialMemberItems } from 'lib/shared/search-utils.js';
import { threadIsPending } from 'lib/shared/thread-utils.js';
-import type { AccountUserInfo, UserListItem } from 'lib/types/user-types.js';
+import type { SetState } from 'lib/types/hook-types.js';
+import type {
+ AccountUserInfo,
+ UserListItem,
+ GlobalAccountUserInfo,
+} from 'lib/types/user-types.js';
+import { useServerCall } from 'lib/utils/action-utils.js';
import css from './chat-thread-composer.css';
import Button from '../components/button.react.js';
@@ -20,6 +31,7 @@
type Props = {
+userInfoInputArray: $ReadOnlyArray<AccountUserInfo>,
+ +setUserInfoInputArray: SetState<$ReadOnlyArray<AccountUserInfo>>,
+otherUserInfos: { [id: string]: AccountUserInfo },
+threadID: string,
+inputState: InputState,
@@ -30,12 +42,56 @@
| 'keep-active-thread';
function ChatThreadComposer(props: Props): React.Node {
- const { userInfoInputArray, otherUserInfos, threadID, inputState } = props;
+ const {
+ userInfoInputArray,
+ setUserInfoInputArray,
+ otherUserInfos,
+ threadID,
+ inputState,
+ } = props;
const [usernameInputText, setUsernameInputText] = React.useState('');
- const dispatch = useDispatch();
+ const userInfos = useSelector(state => state.userStore.userInfos);
+ const viewerID = useSelector(state => state.currentUserInfo?.id);
+
+ const [serverSearchUserInfos, setServerSearchUserInfos] = React.useState<
+ $ReadOnlyArray<GlobalAccountUserInfo>,
+ >([]);
+ const callSearchUsers = useServerCall(searchUsers);
+ React.useEffect(() => {
+ (async () => {
+ if (usernameInputText.length === 0) {
+ setServerSearchUserInfos([]);
+ } else {
+ const { userInfos: serverUserInfos } = await callSearchUsers(
+ usernameInputText,
+ );
+ setServerSearchUserInfos(serverUserInfos);
+ }
+ })();
+ }, [callSearchUsers, usernameInputText]);
+
+ const filteredServerUserInfos = React.useMemo(() => {
+ const result = {};
+ for (const user of serverSearchUserInfos) {
+ if (!(user.id in userInfos)) {
+ result[user.id] = user;
+ }
+ }
+ return filterPotentialMembers(result, viewerID);
+ }, [serverSearchUserInfos, userInfos, viewerID]);
+
+ const mergedUserInfos = React.useMemo(
+ () => ({ ...filteredServerUserInfos, ...otherUserInfos }),
+ [filteredServerUserInfos, otherUserInfos],
+ );
+
const userSearchIndex = useSelector(userSearchIndexForPotentialMembers);
+ const filteredServerUsersSearchIndex = React.useMemo(
+ () => searchIndexFromUserInfos(filteredServerUserInfos),
+ [filteredServerUserInfos],
+ );
const userInfoInputIDs = React.useMemo(
() => userInfoInputArray.map(userInfo => userInfo.id),
@@ -46,42 +102,38 @@
() =>
getPotentialMemberItems(
usernameInputText,
- otherUserInfos,
- userSearchIndex,
+ mergedUserInfos,
+ [userSearchIndex, filteredServerUsersSearchIndex],
userInfoInputIDs,
),
- [usernameInputText, otherUserInfos, userSearchIndex, userInfoInputIDs],
+ [
+ usernameInputText,
+ mergedUserInfos,
+ userSearchIndex,
+ filteredServerUsersSearchIndex,
+ userInfoInputIDs,
+ ],
);
const userListItemsWithENSNames = useENSNames(userListItems);
const onSelectUserFromSearch = React.useCallback(
- (id: string) => {
- const selectedUserIDs = userInfoInputArray.map(user => user.id);
- dispatch({
- type: updateNavInfoActionType,
- payload: {
- selectedUserList: [...selectedUserIDs, id],
- },
- });
+ (id: string, username: string) => {
+ setUserInfoInputArray(previousUserInfoInputArray => [
+ ...previousUserInfoInputArray,
+ { id, username },
+ ]);
setUsernameInputText('');
},
- [dispatch, userInfoInputArray],
+ [setUserInfoInputArray],
);
const onRemoveUserFromSelected = React.useCallback(
(id: string) => {
- const selectedUserIDs = userInfoInputArray.map(user => user.id);
- if (!selectedUserIDs.includes(id)) {
- return;
- }
- dispatch({
- type: updateNavInfoActionType,
- payload: {
- selectedUserList: selectedUserIDs.filter(userID => userID !== id),
- },
- });
+ setUserInfoInputArray(previousUserInfoInputArray =>
+ previousUserInfoInputArray.filter(user => user.id !== id),
+ );
},
- [dispatch, userInfoInputArray],
+ [setUserInfoInputArray],
);
const userSearchResultList = React.useMemo(() => {
@@ -98,7 +150,12 @@
<li key={userSearchResult.id} className={css.searchResultsItem}>
<Button
variant="text"
- onClick={() => onSelectUserFromSearch(userSearchResult.id)}
+ onClick={() =>
+ onSelectUserFromSearch(
+ userSearchResult.id,
+ userSearchResult.username,
+ )
+ }
className={css.searchResultsButton}
>
<div className={css.userName}>{userSearchResult.username}</div>
@@ -115,6 +172,7 @@
usernameInputText,
]);
+ const dispatch = useDispatch();
const hideSearch = React.useCallback(
(threadBehavior: ActiveThreadBehavior = 'keep-active-thread') => {
dispatch({

File Metadata

Mime Type
text/plain
Expires
Tue, Dec 9, 5:13 PM (2 h, 9 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5855959
Default Alt Text
D5463.1765300399.diff (13 KB)

Event Timeline