Page MenuHomePhorge

D5463.1768560093.diff
No OneTemporary

Size
11 KB
Referenced Files
None
Subscribers
None

D5463.1768560093.diff

diff --git a/keyserver/src/creators/thread-creator.js b/keyserver/src/creators/thread-creator.js
--- a/keyserver/src/creators/thread-creator.js
+++ b/keyserver/src/creators/thread-creator.js
@@ -79,14 +79,12 @@
throw new ServerError('not_logged_in');
}
- const forceAddMembers = options?.forceAddMembers ?? false;
const updatesForCurrentSession =
options?.updatesForCurrentSession ?? 'return';
const silentlyFailMembers = options?.silentlyFailMembers ?? false;
const threadType = request.type;
- const shouldCreateRelationships =
- forceAddMembers || threadType === threadTypes.PERSONAL;
+ const shouldCreateRelationships = options?.forceAddMembers ?? false;
let parentThreadID = request.parentThreadID ? request.parentThreadID : null;
const initialMemberIDsFromRequest =
request.initialMemberIDs && request.initialMemberIDs.length > 0
diff --git a/keyserver/src/responders/thread-responders.js b/keyserver/src/responders/thread-responders.js
--- a/keyserver/src/responders/thread-responders.js
+++ b/keyserver/src/responders/thread-responders.js
@@ -155,6 +155,7 @@
return await createThread(viewer, request, {
silentlyFailMembers: request.type === threadTypes.SIDEBAR,
+ forceAddMembers: true,
});
}
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,31 +113,33 @@
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: {
@@ -215,4 +217,5 @@
isLoggedIn,
userStoreSearchIndex,
usersWithPersonalThreadSelector,
+ filterPotentialMembers,
};
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
@@ -16,7 +16,6 @@
threadIsPending,
} from 'lib/shared/thread-utils';
import { threadTypes } from 'lib/types/thread-types';
-import type { AccountUserInfo } from 'lib/types/user-types';
import { InputStateContext } from '../input/input-state';
import { updateNavInfoActionType } from '../redux/action-types';
@@ -36,12 +35,32 @@
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 dispatch = useDispatch();
+ React.useEffect(() => {
+ if (isChatCreation) {
+ dispatch({
+ type: updateNavInfoActionType,
+ payload: {
+ selectedUserList: userInfoInputArray.map(user => user.id),
+ },
+ });
+ }
+ }, [dispatch, isChatCreation, userInfoInputArray]);
+
const viewerID = useSelector(state => state.currentUserInfo?.id);
invariant(viewerID, 'should be set');
@@ -104,7 +123,6 @@
]);
invariant(threadInfo, 'ThreadInfo should be set');
- const dispatch = useDispatch();
React.useEffect(() => {
if (isChatCreation && activeChatThreadID !== threadInfo?.id) {
let payload = {
@@ -196,6 +214,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,10 +3,13 @@
import * as React from 'react';
import { useDispatch } from 'react-redux';
-import { userSearchIndexForPotentialMembers } from 'lib/selectors/user-selectors';
+import { searchUsers } from 'lib/actions/user-actions';
+import { filterPotentialMembers } from 'lib/selectors/user-selectors';
+import SearchIndex from 'lib/shared/search-index';
import { getPotentialMemberItems } from 'lib/shared/search-utils';
import { threadIsPending } from 'lib/shared/thread-utils';
import type { AccountUserInfo, UserListItem } from 'lib/types/user-types';
+import { useServerCall } from 'lib/utils/action-utils';
import Button from '../components/button.react';
import Label from '../components/label.react';
@@ -19,6 +22,9 @@
type Props = {
+userInfoInputArray: $ReadOnlyArray<AccountUserInfo>,
+ +setUserInfoInputArray: (
+ ($ReadOnlyArray<AccountUserInfo>) => $ReadOnlyArray<AccountUserInfo>,
+ ) => void,
+otherUserInfos: { [id: string]: AccountUserInfo },
+threadID: string,
+inputState: InputState,
@@ -29,12 +35,61 @@
| '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 userSearchIndex = useSelector(userSearchIndexForPotentialMembers);
+ const userInfos = useSelector(state => state.userStore.userInfos);
+ const viewerID = useSelector(state => state.currentUserInfo?.id);
+ const [serverSearchUserInfos, setServerSearchUserInfos] = React.useState<{
+ [id: string]: AccountUserInfo,
+ }>({});
+ const callSearchUsers = useServerCall(searchUsers);
+ React.useEffect(() => {
+ (async () => {
+ if (usernameInputText.length === 0) {
+ setServerSearchUserInfos({});
+ } else {
+ const { userInfos: serverUserInfos } = await callSearchUsers(
+ usernameInputText,
+ );
+ const result = {};
+ for (const user of serverUserInfos) {
+ if (!(user.id in userInfos)) {
+ result[user.id] = user;
+ }
+ }
+ const potentialMembers = filterPotentialMembers(result, viewerID);
+ setServerSearchUserInfos(potentialMembers);
+ }
+ })();
+ }, [userInfos, callSearchUsers, usernameInputText, viewerID]);
+
+ const {
+ mergedUserInfos,
+ userSearchIndex,
+ }: {
+ mergedUserInfos: { [id: string]: AccountUserInfo },
+ userSearchIndex: SearchIndex,
+ } = React.useMemo(() => {
+ const bothUserInfos = { ...serverSearchUserInfos, ...otherUserInfos };
+
+ const searchIndex = new SearchIndex();
+ for (const id in bothUserInfos) {
+ searchIndex.addEntry(id, bothUserInfos[id].username);
+ }
+
+ return {
+ mergedUserInfos: bothUserInfos,
+ userSearchIndex: searchIndex,
+ };
+ }, [serverSearchUserInfos, otherUserInfos]);
const userInfoInputIDs = React.useMemo(
() => userInfoInputArray.map(userInfo => userInfo.id),
@@ -45,41 +100,31 @@
() =>
getPotentialMemberItems(
usernameInputText,
- otherUserInfos,
+ mergedUserInfos,
userSearchIndex,
userInfoInputIDs,
),
- [usernameInputText, otherUserInfos, userSearchIndex, userInfoInputIDs],
+ [usernameInputText, mergedUserInfos, userSearchIndex, userInfoInputIDs],
);
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(() => {
@@ -96,7 +141,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>
@@ -113,6 +163,7 @@
usernameInputText,
]);
+ const dispatch = useDispatch();
const hideSearch = React.useCallback(
(threadBehavior: ActiveThreadBehavior = 'keep-active-thread') => {
dispatch({

File Metadata

Mime Type
text/plain
Expires
Fri, Jan 16, 10:41 AM (6 h, 36 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5943675
Default Alt Text
D5463.1768560093.diff (11 KB)

Event Timeline