Pass filter text when clicking explore/dm prompt
parent
a481f3bdf1
commit
d0513406ee
|
@ -40,11 +40,11 @@ export function inviteMultipleToRoom(roomId, addrs) {
|
||||||
return inviter.invite(addrs).then(states => Promise.resolve({states, inviter}));
|
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.
|
// This dialog handles the room creation internally - we don't need to worry about it.
|
||||||
const InviteDialog = sdk.getComponent("dialogs.InviteDialog");
|
const InviteDialog = sdk.getComponent("dialogs.InviteDialog");
|
||||||
Modal.createTrackedDialog(
|
Modal.createTrackedDialog(
|
||||||
'Start DM', '', InviteDialog, {kind: KIND_DM},
|
'Start DM', '', InviteDialog, {kind: KIND_DM, initialText},
|
||||||
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
|
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -653,8 +653,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
case Action.ViewRoomDirectory: {
|
case Action.ViewRoomDirectory: {
|
||||||
const RoomDirectory = sdk.getComponent("structures.RoomDirectory");
|
const RoomDirectory = sdk.getComponent("structures.RoomDirectory");
|
||||||
Modal.createTrackedDialog('Room directory', '', RoomDirectory, {},
|
Modal.createTrackedDialog('Room directory', '', RoomDirectory, {
|
||||||
'mx_RoomDirectory_dialogWrapper', false, true);
|
initialText: payload.initialText,
|
||||||
|
}, 'mx_RoomDirectory_dialogWrapper', false, true);
|
||||||
|
|
||||||
// View the welcome or home page if we need something to look at
|
// View the welcome or home page if we need something to look at
|
||||||
this.viewSomethingBehindModal();
|
this.viewSomethingBehindModal();
|
||||||
|
@ -677,7 +678,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
this.chatCreateOrReuse(payload.user_id);
|
this.chatCreateOrReuse(payload.user_id);
|
||||||
break;
|
break;
|
||||||
case 'view_create_chat':
|
case 'view_create_chat':
|
||||||
showStartChatInviteDialog();
|
showStartChatInviteDialog(payload.initialText || "");
|
||||||
break;
|
break;
|
||||||
case 'view_invite':
|
case 'view_invite':
|
||||||
showRoomInviteDialog(payload.roomId);
|
showRoomInviteDialog(payload.roomId);
|
||||||
|
|
|
@ -44,6 +44,7 @@ function track(action) {
|
||||||
|
|
||||||
export default class RoomDirectory extends React.Component {
|
export default class RoomDirectory extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
initialText: PropTypes.string,
|
||||||
onFinished: PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -61,7 +62,7 @@ export default class RoomDirectory extends React.Component {
|
||||||
error: null,
|
error: null,
|
||||||
instanceId: undefined,
|
instanceId: undefined,
|
||||||
roomServer: MatrixClientPeg.getHomeserverName(),
|
roomServer: MatrixClientPeg.getHomeserverName(),
|
||||||
filterString: null,
|
filterString: this.props.initialText || "",
|
||||||
selectedCommunityId: SettingsStore.getValue("feature_communities_v2_prototypes")
|
selectedCommunityId: SettingsStore.getValue("feature_communities_v2_prototypes")
|
||||||
? selectedCommunityId
|
? selectedCommunityId
|
||||||
: null,
|
: null,
|
||||||
|
@ -686,6 +687,7 @@ export default class RoomDirectory extends React.Component {
|
||||||
onJoinClick={this.onJoinFromSearchClick}
|
onJoinClick={this.onJoinFromSearchClick}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
showJoinButton={showJoinButton}
|
showJoinButton={showJoinButton}
|
||||||
|
initialText={this.props.initialText}
|
||||||
/>
|
/>
|
||||||
{dropdown}
|
{dropdown}
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -308,10 +308,14 @@ export default class InviteDialog extends React.PureComponent {
|
||||||
|
|
||||||
// The room ID this dialog is for. Only required for KIND_INVITE.
|
// The room ID this dialog is for. Only required for KIND_INVITE.
|
||||||
roomId: PropTypes.string,
|
roomId: PropTypes.string,
|
||||||
|
|
||||||
|
// Initial value to populate the filter with
|
||||||
|
initialText: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
kind: KIND_DM,
|
kind: KIND_DM,
|
||||||
|
initialText: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
_debounceTimer: number = null;
|
_debounceTimer: number = null;
|
||||||
|
@ -338,7 +342,7 @@ export default class InviteDialog extends React.PureComponent {
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
targets: [], // array of Member objects (see interface above)
|
targets: [], // array of Member objects (see interface above)
|
||||||
filterText: "",
|
filterText: this.props.initialText,
|
||||||
recents: InviteDialog.buildRecents(alreadyInvited),
|
recents: InviteDialog.buildRecents(alreadyInvited),
|
||||||
numRecentsShown: INITIAL_ROOMS_SHOWN,
|
numRecentsShown: INITIAL_ROOMS_SHOWN,
|
||||||
suggestions: this._buildSuggestions(alreadyInvited),
|
suggestions: this._buildSuggestions(alreadyInvited),
|
||||||
|
@ -356,6 +360,12 @@ export default class InviteDialog extends React.PureComponent {
|
||||||
this._editorRef = createRef();
|
this._editorRef = createRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
if (this.props.initialText) {
|
||||||
|
this._updateSuggestions(this.props.initialText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static buildRecents(excludedTargetIds: Set<string>): {userId: string, user: RoomMember, lastActive: number} {
|
static buildRecents(excludedTargetIds: Set<string>): {userId: string, user: RoomMember, lastActive: number} {
|
||||||
const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals(); // map of userId => js-sdk Room
|
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) => {
|
_updateFilter = (e) => {
|
||||||
const term = e.target.value;
|
const term = e.target.value;
|
||||||
this.setState({filterText: term});
|
this.setState({filterText: term});
|
||||||
|
@ -697,113 +816,8 @@ export default class InviteDialog extends React.PureComponent {
|
||||||
if (this._debounceTimer) {
|
if (this._debounceTimer) {
|
||||||
clearTimeout(this._debounceTimer);
|
clearTimeout(this._debounceTimer);
|
||||||
}
|
}
|
||||||
this._debounceTimer = setTimeout(async () => {
|
this._debounceTimer = setTimeout(() => {
|
||||||
MatrixClientPeg.get().searchUserDirectory({term}).then(async r => {
|
this._updateSuggestions(term);
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 150); // 150ms debounce (human reaction time + some)
|
}, 150); // 150ms debounce (human reaction time + some)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,8 @@ import * as sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
export default class DirectorySearchBox extends React.Component {
|
export default class DirectorySearchBox extends React.Component {
|
||||||
constructor() {
|
constructor(props) {
|
||||||
super();
|
super(props);
|
||||||
this._collectInput = this._collectInput.bind(this);
|
this._collectInput = this._collectInput.bind(this);
|
||||||
this._onClearClick = this._onClearClick.bind(this);
|
this._onClearClick = this._onClearClick.bind(this);
|
||||||
this._onChange = this._onChange.bind(this);
|
this._onChange = this._onChange.bind(this);
|
||||||
|
@ -31,7 +31,7 @@ export default class DirectorySearchBox extends React.Component {
|
||||||
this.input = null;
|
this.input = null;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
value: '',
|
value: this.props.initialText || '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,15 +90,20 @@ export default class DirectorySearchBox extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className={`mx_DirectorySearchBox ${this.props.className} mx_textinput`}>
|
return <div className={`mx_DirectorySearchBox ${this.props.className} mx_textinput`}>
|
||||||
<input type="text" name="dirsearch" value={this.state.value}
|
<input
|
||||||
className="mx_textinput_icon mx_textinput_search"
|
type="text"
|
||||||
ref={this._collectInput}
|
name="dirsearch"
|
||||||
onChange={this._onChange} onKeyUp={this._onKeyUp}
|
value={this.state.value}
|
||||||
placeholder={this.props.placeholder} autoFocus
|
className="mx_textinput_icon mx_textinput_search"
|
||||||
/>
|
ref={this._collectInput}
|
||||||
{ joinButton }
|
onChange={this._onChange}
|
||||||
<AccessibleButton className="mx_DirectorySearchBox_clear" onClick={this._onClearClick}></AccessibleButton>
|
onKeyUp={this._onKeyUp}
|
||||||
</div>;
|
placeholder={this.props.placeholder}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
{ joinButton }
|
||||||
|
<AccessibleButton className="mx_DirectorySearchBox_clear" onClick={this._onClearClick} />
|
||||||
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,4 +114,5 @@ DirectorySearchBox.propTypes = {
|
||||||
onJoinClick: PropTypes.func,
|
onJoinClick: PropTypes.func,
|
||||||
placeholder: PropTypes.string,
|
placeholder: PropTypes.string,
|
||||||
showJoinButton: PropTypes.bool,
|
showJoinButton: PropTypes.bool,
|
||||||
|
initialText: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
|
@ -285,11 +285,13 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private onStartChat = () => {
|
private onStartChat = () => {
|
||||||
dis.dispatch({action: "view_create_chat"});
|
const initialText = RoomListStore.instance.getFirstNameFilterCondition()?.search;
|
||||||
|
dis.dispatch({ action: "view_create_chat", initialText });
|
||||||
};
|
};
|
||||||
|
|
||||||
private onExplore = () => {
|
private onExplore = () => {
|
||||||
dis.fire(Action.ViewRoomDirectory);
|
const initialText = RoomListStore.instance.getFirstNameFilterCondition()?.search;
|
||||||
|
dis.dispatch({ action: Action.ViewRoomDirectory, initialText });
|
||||||
};
|
};
|
||||||
|
|
||||||
private renderCommunityInvites(): TemporaryTile[] {
|
private renderCommunityInvites(): TemporaryTile[] {
|
||||||
|
|
Loading…
Reference in New Issue