Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F32320134
D5463.1765300399.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
13 KB
Referenced Files
None
Subscribers
None
D5463.1765300399.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D5463: [web] Call searchUsers in chat composer
Attached
Detach File
Event Timeline
Log In to Comment