From d0513406ee1d78ca82f144a8e72f59a23050965a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 11 Nov 2020 13:36:17 +0000 Subject: [PATCH] Pass filter text when clicking explore/dm prompt --- src/RoomInvite.js | 4 +- src/components/structures/MatrixChat.tsx | 7 +- src/components/structures/RoomDirectory.js | 4 +- src/components/views/dialogs/InviteDialog.js | 230 ++++++++++-------- .../views/elements/DirectorySearchBox.js | 30 ++- src/components/views/rooms/RoomList.tsx | 6 +- 6 files changed, 153 insertions(+), 128 deletions(-) diff --git a/src/RoomInvite.js b/src/RoomInvite.js index 7eb7f5dbb2..06d3fb04e8 100644 --- a/src/RoomInvite.js +++ b/src/RoomInvite.js @@ -40,11 +40,11 @@ export function inviteMultipleToRoom(roomId, addrs) { return inviter.invite(addrs).then(states => Promise.resolve({states, inviter})); } -export function showStartChatInviteDialog() { +export function showStartChatInviteDialog(initialText) { // This dialog handles the room creation internally - we don't need to worry about it. const InviteDialog = sdk.getComponent("dialogs.InviteDialog"); Modal.createTrackedDialog( - 'Start DM', '', InviteDialog, {kind: KIND_DM}, + 'Start DM', '', InviteDialog, {kind: KIND_DM, initialText}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true, ); } diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 22cd73eff7..b2c94e4a8b 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -653,8 +653,9 @@ export default class MatrixChat extends React.PureComponent { } case Action.ViewRoomDirectory: { const RoomDirectory = sdk.getComponent("structures.RoomDirectory"); - Modal.createTrackedDialog('Room directory', '', RoomDirectory, {}, - 'mx_RoomDirectory_dialogWrapper', false, true); + Modal.createTrackedDialog('Room directory', '', RoomDirectory, { + initialText: payload.initialText, + }, 'mx_RoomDirectory_dialogWrapper', false, true); // View the welcome or home page if we need something to look at this.viewSomethingBehindModal(); @@ -677,7 +678,7 @@ export default class MatrixChat extends React.PureComponent { this.chatCreateOrReuse(payload.user_id); break; case 'view_create_chat': - showStartChatInviteDialog(); + showStartChatInviteDialog(payload.initialText || ""); break; case 'view_invite': showRoomInviteDialog(payload.roomId); diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js index ece70e3a8f..e3323b05fa 100644 --- a/src/components/structures/RoomDirectory.js +++ b/src/components/structures/RoomDirectory.js @@ -44,6 +44,7 @@ function track(action) { export default class RoomDirectory extends React.Component { static propTypes = { + initialText: PropTypes.string, onFinished: PropTypes.func.isRequired, }; @@ -61,7 +62,7 @@ export default class RoomDirectory extends React.Component { error: null, instanceId: undefined, roomServer: MatrixClientPeg.getHomeserverName(), - filterString: null, + filterString: this.props.initialText || "", selectedCommunityId: SettingsStore.getValue("feature_communities_v2_prototypes") ? selectedCommunityId : null, @@ -686,6 +687,7 @@ export default class RoomDirectory extends React.Component { onJoinClick={this.onJoinFromSearchClick} placeholder={placeholder} showJoinButton={showJoinButton} + initialText={this.props.initialText} /> {dropdown} ; diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 99878569d3..9b7c5803a1 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -308,10 +308,14 @@ export default class InviteDialog extends React.PureComponent { // The room ID this dialog is for. Only required for KIND_INVITE. roomId: PropTypes.string, + + // Initial value to populate the filter with + initialText: PropTypes.string, }; static defaultProps = { kind: KIND_DM, + initialText: "", }; _debounceTimer: number = null; @@ -338,7 +342,7 @@ export default class InviteDialog extends React.PureComponent { this.state = { targets: [], // array of Member objects (see interface above) - filterText: "", + filterText: this.props.initialText, recents: InviteDialog.buildRecents(alreadyInvited), numRecentsShown: INITIAL_ROOMS_SHOWN, suggestions: this._buildSuggestions(alreadyInvited), @@ -356,6 +360,12 @@ export default class InviteDialog extends React.PureComponent { this._editorRef = createRef(); } + componentDidMount() { + if (this.props.initialText) { + this._updateSuggestions(this.props.initialText); + } + } + static buildRecents(excludedTargetIds: Set): {userId: string, user: RoomMember, lastActive: number} { const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals(); // map of userId => js-sdk Room @@ -687,6 +697,115 @@ export default class InviteDialog extends React.PureComponent { } }; + _updateSuggestions = async (term) => { + MatrixClientPeg.get().searchUserDirectory({term}).then(async r => { + if (term !== this.state.filterText) { + // Discard the results - we were probably too slow on the server-side to make + // these results useful. This is a race we want to avoid because we could overwrite + // more accurate results. + return; + } + + if (!r.results) r.results = []; + + // While we're here, try and autocomplete a search result for the mxid itself + // if there's no matches (and the input looks like a mxid). + if (term[0] === '@' && term.indexOf(':') > 1) { + try { + const profile = await MatrixClientPeg.get().getProfileInfo(term); + if (profile) { + // If we have a profile, we have enough information to assume that + // the mxid can be invited - add it to the list. We stick it at the + // top so it is most obviously presented to the user. + r.results.splice(0, 0, { + user_id: term, + display_name: profile['displayname'], + avatar_url: profile['avatar_url'], + }); + } + } catch (e) { + console.warn("Non-fatal error trying to make an invite for a user ID"); + console.warn(e); + + // Add a result anyways, just without a profile. We stick it at the + // top so it is most obviously presented to the user. + r.results.splice(0, 0, { + user_id: term, + display_name: term, + avatar_url: null, + }); + } + } + + this.setState({ + serverResultsMixin: r.results.map(u => ({ + userId: u.user_id, + user: new DirectoryMember(u), + })), + }); + }).catch(e => { + console.error("Error searching user directory:"); + console.error(e); + this.setState({serverResultsMixin: []}); // clear results because it's moderately fatal + }); + + // Whenever we search the directory, also try to search the identity server. It's + // all debounced the same anyways. + if (!this.state.canUseIdentityServer) { + // The user doesn't have an identity server set - warn them of that. + this.setState({tryingIdentityServer: true}); + return; + } + if (term.indexOf('@') > 0 && Email.looksValid(term) && SettingsStore.getValue(UIFeature.IdentityServer)) { + // Start off by suggesting the plain email while we try and resolve it + // to a real account. + this.setState({ + // per above: the userId is a lie here - it's just a regular identifier + threepidResultsMixin: [{user: new ThreepidMember(term), userId: term}], + }); + try { + const authClient = new IdentityAuthClient(); + const token = await authClient.getAccessToken(); + if (term !== this.state.filterText) return; // abandon hope + + const lookup = await MatrixClientPeg.get().lookupThreePid( + 'email', + term, + undefined, // callback + token, + ); + if (term !== this.state.filterText) return; // abandon hope + + if (!lookup || !lookup.mxid) { + // We weren't able to find anyone - we're already suggesting the plain email + // as an alternative, so do nothing. + return; + } + + // We append the user suggestion to give the user an option to click + // the email anyways, and so we don't cause things to jump around. In + // theory, the user would see the user pop up and think "ah yes, that + // person!" + const profile = await MatrixClientPeg.get().getProfileInfo(lookup.mxid); + if (term !== this.state.filterText || !profile) return; // abandon hope + this.setState({ + threepidResultsMixin: [...this.state.threepidResultsMixin, { + user: new DirectoryMember({ + user_id: lookup.mxid, + display_name: profile.displayname, + avatar_url: profile.avatar_url, + }), + userId: lookup.mxid, + }], + }); + } catch (e) { + console.error("Error searching identity server:"); + console.error(e); + this.setState({threepidResultsMixin: []}); // clear results because it's moderately fatal + } + } + }; + _updateFilter = (e) => { const term = e.target.value; this.setState({filterText: term}); @@ -697,113 +816,8 @@ export default class InviteDialog extends React.PureComponent { if (this._debounceTimer) { clearTimeout(this._debounceTimer); } - this._debounceTimer = setTimeout(async () => { - MatrixClientPeg.get().searchUserDirectory({term}).then(async r => { - if (term !== this.state.filterText) { - // Discard the results - we were probably too slow on the server-side to make - // these results useful. This is a race we want to avoid because we could overwrite - // more accurate results. - return; - } - - if (!r.results) r.results = []; - - // While we're here, try and autocomplete a search result for the mxid itself - // if there's no matches (and the input looks like a mxid). - if (term[0] === '@' && term.indexOf(':') > 1) { - try { - const profile = await MatrixClientPeg.get().getProfileInfo(term); - if (profile) { - // If we have a profile, we have enough information to assume that - // the mxid can be invited - add it to the list. We stick it at the - // top so it is most obviously presented to the user. - r.results.splice(0, 0, { - user_id: term, - display_name: profile['displayname'], - avatar_url: profile['avatar_url'], - }); - } - } catch (e) { - console.warn("Non-fatal error trying to make an invite for a user ID"); - console.warn(e); - - // Add a result anyways, just without a profile. We stick it at the - // top so it is most obviously presented to the user. - r.results.splice(0, 0, { - user_id: term, - display_name: term, - avatar_url: null, - }); - } - } - - this.setState({ - serverResultsMixin: r.results.map(u => ({ - userId: u.user_id, - user: new DirectoryMember(u), - })), - }); - }).catch(e => { - console.error("Error searching user directory:"); - console.error(e); - this.setState({serverResultsMixin: []}); // clear results because it's moderately fatal - }); - - // Whenever we search the directory, also try to search the identity server. It's - // all debounced the same anyways. - if (!this.state.canUseIdentityServer) { - // The user doesn't have an identity server set - warn them of that. - this.setState({tryingIdentityServer: true}); - return; - } - if (term.indexOf('@') > 0 && Email.looksValid(term) && SettingsStore.getValue(UIFeature.IdentityServer)) { - // Start off by suggesting the plain email while we try and resolve it - // to a real account. - this.setState({ - // per above: the userId is a lie here - it's just a regular identifier - threepidResultsMixin: [{user: new ThreepidMember(term), userId: term}], - }); - try { - const authClient = new IdentityAuthClient(); - const token = await authClient.getAccessToken(); - if (term !== this.state.filterText) return; // abandon hope - - const lookup = await MatrixClientPeg.get().lookupThreePid( - 'email', - term, - undefined, // callback - token, - ); - if (term !== this.state.filterText) return; // abandon hope - - if (!lookup || !lookup.mxid) { - // We weren't able to find anyone - we're already suggesting the plain email - // as an alternative, so do nothing. - return; - } - - // We append the user suggestion to give the user an option to click - // the email anyways, and so we don't cause things to jump around. In - // theory, the user would see the user pop up and think "ah yes, that - // person!" - const profile = await MatrixClientPeg.get().getProfileInfo(lookup.mxid); - if (term !== this.state.filterText || !profile) return; // abandon hope - this.setState({ - threepidResultsMixin: [...this.state.threepidResultsMixin, { - user: new DirectoryMember({ - user_id: lookup.mxid, - display_name: profile.displayname, - avatar_url: profile.avatar_url, - }), - userId: lookup.mxid, - }], - }); - } catch (e) { - console.error("Error searching identity server:"); - console.error(e); - this.setState({threepidResultsMixin: []}); // clear results because it's moderately fatal - } - } + this._debounceTimer = setTimeout(() => { + this._updateSuggestions(term); }, 150); // 150ms debounce (human reaction time + some) }; diff --git a/src/components/views/elements/DirectorySearchBox.js b/src/components/views/elements/DirectorySearchBox.js index c2e8e4fd68..644b69417b 100644 --- a/src/components/views/elements/DirectorySearchBox.js +++ b/src/components/views/elements/DirectorySearchBox.js @@ -20,8 +20,8 @@ import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; export default class DirectorySearchBox extends React.Component { - constructor() { - super(); + constructor(props) { + super(props); this._collectInput = this._collectInput.bind(this); this._onClearClick = this._onClearClick.bind(this); this._onChange = this._onChange.bind(this); @@ -31,7 +31,7 @@ export default class DirectorySearchBox extends React.Component { this.input = null; this.state = { - value: '', + value: this.props.initialText || '', }; } @@ -90,15 +90,20 @@ export default class DirectorySearchBox extends React.Component { } return
- - { joinButton } - -
; + + { joinButton } + + ; } } @@ -109,4 +114,5 @@ DirectorySearchBox.propTypes = { onJoinClick: PropTypes.func, placeholder: PropTypes.string, showJoinButton: PropTypes.bool, + initialText: PropTypes.string, }; diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index de54fabc53..6e677f2b01 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -285,11 +285,13 @@ export default class RoomList extends React.PureComponent { }; private onStartChat = () => { - dis.dispatch({action: "view_create_chat"}); + const initialText = RoomListStore.instance.getFirstNameFilterCondition()?.search; + dis.dispatch({ action: "view_create_chat", initialText }); }; private onExplore = () => { - dis.fire(Action.ViewRoomDirectory); + const initialText = RoomListStore.instance.getFirstNameFilterCondition()?.search; + dis.dispatch({ action: Action.ViewRoomDirectory, initialText }); }; private renderCommunityInvites(): TemporaryTile[] {