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