Merge pull request #3854 from matrix-org/travis/ftue/user-lists/6.1-multidialog

Make the new DM invite dialog work for regular invites too
pull/21833/head
Travis Ralston 2020-01-16 15:06:52 -07:00 committed by GitHub
commit 8cdce8fee0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 200 additions and 87 deletions

View File

@ -57,13 +57,13 @@
@import "./views/dialogs/_ConfirmUserActionDialog.scss"; @import "./views/dialogs/_ConfirmUserActionDialog.scss";
@import "./views/dialogs/_CreateGroupDialog.scss"; @import "./views/dialogs/_CreateGroupDialog.scss";
@import "./views/dialogs/_CreateRoomDialog.scss"; @import "./views/dialogs/_CreateRoomDialog.scss";
@import "./views/dialogs/_DMInviteDialog.scss";
@import "./views/dialogs/_DeactivateAccountDialog.scss"; @import "./views/dialogs/_DeactivateAccountDialog.scss";
@import "./views/dialogs/_DeviceVerifyDialog.scss"; @import "./views/dialogs/_DeviceVerifyDialog.scss";
@import "./views/dialogs/_DevtoolsDialog.scss"; @import "./views/dialogs/_DevtoolsDialog.scss";
@import "./views/dialogs/_EncryptedEventDialog.scss"; @import "./views/dialogs/_EncryptedEventDialog.scss";
@import "./views/dialogs/_GroupAddressPicker.scss"; @import "./views/dialogs/_GroupAddressPicker.scss";
@import "./views/dialogs/_IncomingSasDialog.scss"; @import "./views/dialogs/_IncomingSasDialog.scss";
@import "./views/dialogs/_InviteDialog.scss";
@import "./views/dialogs/_MessageEditHistoryDialog.scss"; @import "./views/dialogs/_MessageEditHistoryDialog.scss";
@import "./views/dialogs/_RoomSettingsDialog.scss"; @import "./views/dialogs/_RoomSettingsDialog.scss";
@import "./views/dialogs/_RoomUpgradeDialog.scss"; @import "./views/dialogs/_RoomUpgradeDialog.scss";

View File

@ -14,11 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
.mx_DMInviteDialog_addressBar { .mx_InviteDialog_addressBar {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
.mx_DMInviteDialog_editor { .mx_InviteDialog_editor {
flex: 1; flex: 1;
width: 100%; // Needed to make the Field inside grow width: 100%; // Needed to make the Field inside grow
background-color: $user-tile-hover-bg-color; background-color: $user-tile-hover-bg-color;
@ -28,7 +28,7 @@ limitations under the License.
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
.mx_DMInviteDialog_userTile { .mx_InviteDialog_userTile {
display: inline-block; display: inline-block;
float: left; float: left;
position: relative; position: relative;
@ -61,14 +61,14 @@ limitations under the License.
} }
} }
.mx_DMInviteDialog_goButton { .mx_InviteDialog_goButton {
width: 48px; width: 48px;
margin-left: 10px; margin-left: 10px;
height: 25px; height: 25px;
line-height: 25px; line-height: 25px;
} }
.mx_DMInviteDialog_buttonAndSpinner { .mx_InviteDialog_buttonAndSpinner {
.mx_Spinner { .mx_Spinner {
// Width and height are required to trick the layout engine. // Width and height are required to trick the layout engine.
width: 20px; width: 20px;
@ -80,7 +80,7 @@ limitations under the License.
} }
} }
.mx_DMInviteDialog_section { .mx_InviteDialog_section {
padding-bottom: 10px; padding-bottom: 10px;
h3 { h3 {
@ -91,7 +91,7 @@ limitations under the License.
} }
} }
.mx_DMInviteDialog_roomTile { .mx_InviteDialog_roomTile {
cursor: pointer; cursor: pointer;
padding: 5px 10px; padding: 5px 10px;
@ -104,7 +104,7 @@ limitations under the License.
vertical-align: middle; vertical-align: middle;
} }
.mx_DMInviteDialog_roomTile_avatarStack { .mx_InviteDialog_roomTile_avatarStack {
display: inline-block; display: inline-block;
position: relative; position: relative;
width: 36px; width: 36px;
@ -117,7 +117,7 @@ limitations under the License.
} }
} }
.mx_DMInviteDialog_roomTile_selected { .mx_InviteDialog_roomTile_selected {
width: 36px; width: 36px;
height: 36px; height: 36px;
border-radius: 36px; border-radius: 36px;
@ -141,20 +141,20 @@ limitations under the License.
} }
} }
.mx_DMInviteDialog_roomTile_name { .mx_InviteDialog_roomTile_name {
font-weight: 600; font-weight: 600;
font-size: 14px; font-size: 14px;
color: $primary-fg-color; color: $primary-fg-color;
margin-left: 7px; margin-left: 7px;
} }
.mx_DMInviteDialog_roomTile_userId { .mx_InviteDialog_roomTile_userId {
font-size: 12px; font-size: 12px;
color: $muted-fg-color; color: $muted-fg-color;
margin-left: 7px; margin-left: 7px;
} }
.mx_DMInviteDialog_roomTile_time { .mx_InviteDialog_roomTile_time {
text-align: right; text-align: right;
font-size: 12px; font-size: 12px;
color: $muted-fg-color; color: $muted-fg-color;
@ -162,16 +162,16 @@ limitations under the License.
line-height: 36px; // Height of the avatar to keep the time vertically aligned line-height: 36px; // Height of the avatar to keep the time vertically aligned
} }
.mx_DMInviteDialog_roomTile_highlight { .mx_InviteDialog_roomTile_highlight {
font-weight: 900; font-weight: 900;
} }
} }
// Many of these styles are stolen from mx_UserPill, but adjusted for the invite dialog. // Many of these styles are stolen from mx_UserPill, but adjusted for the invite dialog.
.mx_DMInviteDialog_userTile { .mx_InviteDialog_userTile {
margin-right: 8px; margin-right: 8px;
.mx_DMInviteDialog_userTile_pill { .mx_InviteDialog_userTile_pill {
background-color: $username-variant1-color; background-color: $username-variant1-color;
border-radius: 12px; border-radius: 12px;
display: inline-block; display: inline-block;
@ -181,27 +181,27 @@ limitations under the License.
padding-right: 8px; padding-right: 8px;
color: #ffffff; // this is fine without a var because it's for both themes color: #ffffff; // this is fine without a var because it's for both themes
.mx_DMInviteDialog_userTile_avatar { .mx_InviteDialog_userTile_avatar {
border-radius: 20px; border-radius: 20px;
position: relative; position: relative;
left: -5px; left: -5px;
top: 2px; top: 2px;
} }
img.mx_DMInviteDialog_userTile_avatar { img.mx_InviteDialog_userTile_avatar {
vertical-align: top; vertical-align: top;
} }
.mx_DMInviteDialog_userTile_name { .mx_InviteDialog_userTile_name {
vertical-align: top; vertical-align: top;
} }
.mx_DMInviteDialog_userTile_threepidAvatar { .mx_InviteDialog_userTile_threepidAvatar {
background-color: #ffffff; // this is fine without a var because it's for both themes background-color: #ffffff; // this is fine without a var because it's for both themes
} }
} }
.mx_DMInviteDialog_userTile_remove { .mx_InviteDialog_userTile_remove {
display: inline-block; display: inline-block;
margin-left: 4px; margin-left: 4px;
} }

View File

@ -1,6 +1,7 @@
/* /*
Copyright 2016 OpenMarket Ltd Copyright 2016 OpenMarket Ltd
Copyright 2017, 2018 New Vector Ltd Copyright 2017, 2018 New Vector Ltd
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -26,6 +27,7 @@ import dis from './dispatcher';
import DMRoomMap from './utils/DMRoomMap'; import DMRoomMap from './utils/DMRoomMap';
import { _t } from './languageHandler'; import { _t } from './languageHandler';
import SettingsStore from "./settings/SettingsStore"; import SettingsStore from "./settings/SettingsStore";
import {KIND_DM, KIND_INVITE} from "./components/views/dialogs/InviteDialog";
/** /**
* Invites multiple addresses to a room * Invites multiple addresses to a room
@ -44,9 +46,9 @@ export function inviteMultipleToRoom(roomId, addrs) {
export function showStartChatInviteDialog() { export function showStartChatInviteDialog() {
if (SettingsStore.isFeatureEnabled("feature_ftue_dms")) { if (SettingsStore.isFeatureEnabled("feature_ftue_dms")) {
// This new dialog handles the room creation internally - we don't need to worry about it. // This new dialog handles the room creation internally - we don't need to worry about it.
const DMInviteDialog = sdk.getComponent("dialogs.DMInviteDialog"); const InviteDialog = sdk.getComponent("dialogs.InviteDialog");
Modal.createTrackedDialog( Modal.createTrackedDialog(
'Start DM', '', DMInviteDialog, {}, 'Start DM', '', InviteDialog, {kind: KIND_DM},
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
); );
return; return;
@ -72,6 +74,16 @@ export function showStartChatInviteDialog() {
} }
export function showRoomInviteDialog(roomId) { export function showRoomInviteDialog(roomId) {
if (SettingsStore.isFeatureEnabled("feature_ftue_dms")) {
// This new dialog handles the room creation internally - we don't need to worry about it.
const InviteDialog = sdk.getComponent("dialogs.InviteDialog");
Modal.createTrackedDialog(
'Invite Users', '', InviteDialog, {kind: KIND_INVITE, roomId},
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
);
return;
}
const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog"); const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
Modal.createTrackedDialog('Chat Invite', '', AddressPickerDialog, { Modal.createTrackedDialog('Chat Invite', '', AddressPickerDialog, {

View File

@ -19,7 +19,7 @@ import PropTypes from 'prop-types';
import {_t} from "../../../languageHandler"; import {_t} from "../../../languageHandler";
import * as sdk from "../../../index"; import * as sdk from "../../../index";
import {MatrixClientPeg} from "../../../MatrixClientPeg"; import {MatrixClientPeg} from "../../../MatrixClientPeg";
import {makeUserPermalink} from "../../../utils/permalinks/Permalinks"; import {makeRoomPermalink, makeUserPermalink} from "../../../utils/permalinks/Permalinks";
import DMRoomMap from "../../../utils/DMRoomMap"; import DMRoomMap from "../../../utils/DMRoomMap";
import {RoomMember} from "matrix-js-sdk/src/matrix"; import {RoomMember} from "matrix-js-sdk/src/matrix";
import SdkConfig from "../../../SdkConfig"; import SdkConfig from "../../../SdkConfig";
@ -34,7 +34,8 @@ import {humanizeTime} from "../../../utils/humanize";
import createRoom from "../../../createRoom"; import createRoom from "../../../createRoom";
import {inviteMultipleToRoom} from "../../../RoomInvite"; import {inviteMultipleToRoom} from "../../../RoomInvite";
// TODO: [TravisR] Make this generic for all kinds of invites export const KIND_DM = "dm";
export const KIND_INVITE = "invite";
const INITIAL_ROOMS_SHOWN = 3; // Number of rooms to show at first const INITIAL_ROOMS_SHOWN = 3; // Number of rooms to show at first
const INCREMENT_ROOMS_SHOWN = 5; // Number of rooms to add when 'show more' is clicked const INCREMENT_ROOMS_SHOWN = 5; // Number of rooms to add when 'show more' is clicked
@ -140,11 +141,11 @@ class DMUserTile extends React.PureComponent {
const avatarSize = 20; const avatarSize = 20;
const avatar = this.props.member.isEmail const avatar = this.props.member.isEmail
? <img ? <img
className='mx_DMInviteDialog_userTile_avatar mx_DMInviteDialog_userTile_threepidAvatar' className='mx_InviteDialog_userTile_avatar mx_InviteDialog_userTile_threepidAvatar'
src={require("../../../../res/img/icon-email-pill-avatar.svg")} src={require("../../../../res/img/icon-email-pill-avatar.svg")}
width={avatarSize} height={avatarSize} /> width={avatarSize} height={avatarSize} />
: <BaseAvatar : <BaseAvatar
className='mx_DMInviteDialog_userTile_avatar' className='mx_InviteDialog_userTile_avatar'
url={getHttpUriForMxc( url={getHttpUriForMxc(
MatrixClientPeg.get().getHomeserverUrl(), this.props.member.getMxcAvatarUrl(), MatrixClientPeg.get().getHomeserverUrl(), this.props.member.getMxcAvatarUrl(),
avatarSize, avatarSize, "crop")} avatarSize, avatarSize, "crop")}
@ -154,13 +155,13 @@ class DMUserTile extends React.PureComponent {
height={avatarSize} />; height={avatarSize} />;
return ( return (
<span className='mx_DMInviteDialog_userTile'> <span className='mx_InviteDialog_userTile'>
<span className='mx_DMInviteDialog_userTile_pill'> <span className='mx_InviteDialog_userTile_pill'>
{avatar} {avatar}
<span className='mx_DMInviteDialog_userTile_name'>{this.props.member.name}</span> <span className='mx_InviteDialog_userTile_name'>{this.props.member.name}</span>
</span> </span>
<AccessibleButton <AccessibleButton
className='mx_DMInviteDialog_userTile_remove' className='mx_InviteDialog_userTile_remove'
onClick={this._onRemove} onClick={this._onRemove}
> >
<img src={require("../../../../res/img/icon-pill-remove.svg")} alt={_t('Remove')} width={8} height={8} /> <img src={require("../../../../res/img/icon-pill-remove.svg")} alt={_t('Remove')} width={8} height={8} />
@ -211,7 +212,7 @@ class DMRoomTile extends React.PureComponent {
// Highlight the word the user entered // Highlight the word the user entered
const substr = str.substring(i, filterStr.length + i); const substr = str.substring(i, filterStr.length + i);
result.push(<span className='mx_DMInviteDialog_roomTile_highlight' key={i + 'bold'}>{substr}</span>); result.push(<span className='mx_InviteDialog_roomTile_highlight' key={i + 'bold'}>{substr}</span>);
i += substr.length; i += substr.length;
} }
@ -229,7 +230,7 @@ class DMRoomTile extends React.PureComponent {
let timestamp = null; let timestamp = null;
if (this.props.lastActiveTs) { if (this.props.lastActiveTs) {
const humanTs = humanizeTime(this.props.lastActiveTs); const humanTs = humanizeTime(this.props.lastActiveTs);
timestamp = <span className='mx_DMInviteDialog_roomTile_time'>{humanTs}</span>; timestamp = <span className='mx_InviteDialog_roomTile_time'>{humanTs}</span>;
} }
const avatarSize = 36; const avatarSize = 36;
@ -249,47 +250,74 @@ class DMRoomTile extends React.PureComponent {
let checkmark = null; let checkmark = null;
if (this.props.isSelected) { if (this.props.isSelected) {
// To reduce flickering we put the 'selected' room tile above the real avatar // To reduce flickering we put the 'selected' room tile above the real avatar
checkmark = <div className='mx_DMInviteDialog_roomTile_selected' />; checkmark = <div className='mx_InviteDialog_roomTile_selected' />;
} }
// To reduce flickering we put the checkmark on top of the actual avatar (prevents // To reduce flickering we put the checkmark on top of the actual avatar (prevents
// the browser from reloading the image source when the avatar remounts). // the browser from reloading the image source when the avatar remounts).
const stackedAvatar = ( const stackedAvatar = (
<span className='mx_DMInviteDialog_roomTile_avatarStack'> <span className='mx_InviteDialog_roomTile_avatarStack'>
{avatar} {avatar}
{checkmark} {checkmark}
</span> </span>
); );
return ( return (
<div className='mx_DMInviteDialog_roomTile' onClick={this._onClick}> <div className='mx_InviteDialog_roomTile' onClick={this._onClick}>
{stackedAvatar} {stackedAvatar}
<span className='mx_DMInviteDialog_roomTile_name'>{this._highlightName(this.props.member.name)}</span> <span className='mx_InviteDialog_roomTile_name'>{this._highlightName(this.props.member.name)}</span>
<span className='mx_DMInviteDialog_roomTile_userId'>{this._highlightName(this.props.member.userId)}</span> <span className='mx_InviteDialog_roomTile_userId'>{this._highlightName(this.props.member.userId)}</span>
{timestamp} {timestamp}
</div> </div>
); );
} }
} }
export default class DMInviteDialog extends React.PureComponent { export default class InviteDialog extends React.PureComponent {
static propTypes = { static propTypes = {
// Takes an array of user IDs/emails to invite. // Takes an array of user IDs/emails to invite.
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
// The kind of invite being performed. Assumed to be KIND_DM if
// not provided.
kind: PropTypes.string,
// The room ID this dialog is for. Only required for KIND_INVITE.
roomId: PropTypes.string,
};
static defaultProps = {
kind: KIND_DM,
}; };
_debounceTimer: number = null; _debounceTimer: number = null;
_editorRef: any = null; _editorRef: any = null;
constructor() { constructor(props) {
super(); super(props);
if (props.kind === KIND_INVITE && !props.roomId) {
throw new Error("When using KIND_INVITE a roomId is required for an InviteDialog");
}
let alreadyInvited = [];
if (props.roomId) {
const room = MatrixClientPeg.get().getRoom(props.roomId);
if (!room) throw new Error("Room ID given to InviteDialog does not look like a room");
alreadyInvited = [
...room.getMembersWithMembership('invite'),
...room.getMembersWithMembership('join'),
...room.getMembersWithMembership('ban'), // so we don't try to invite them
].map(m => m.userId);
}
this.state = { this.state = {
targets: [], // array of Member objects (see interface above) targets: [], // array of Member objects (see interface above)
filterText: "", filterText: "",
recents: this._buildRecents(), recents: this._buildRecents(alreadyInvited),
numRecentsShown: INITIAL_ROOMS_SHOWN, numRecentsShown: INITIAL_ROOMS_SHOWN,
suggestions: this._buildSuggestions(), suggestions: this._buildSuggestions(alreadyInvited),
numSuggestionsShown: INITIAL_ROOMS_SHOWN, numSuggestionsShown: INITIAL_ROOMS_SHOWN,
serverResultsMixin: [], // { user: DirectoryMember, userId: string }[], like recents and suggestions serverResultsMixin: [], // { user: DirectoryMember, userId: string }[], like recents and suggestions
threepidResultsMixin: [], // { user: ThreepidMember, userId: string}[], like recents and suggestions threepidResultsMixin: [], // { user: ThreepidMember, userId: string}[], like recents and suggestions
@ -304,10 +332,13 @@ export default class DMInviteDialog extends React.PureComponent {
this._editorRef = createRef(); this._editorRef = createRef();
} }
_buildRecents(): {userId: string, user: RoomMember, lastActive: number} { _buildRecents(excludedTargetIds: string[]): {userId: string, user: RoomMember, lastActive: number} {
const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals(); const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals();
const recents = []; const recents = [];
for (const userId in rooms) { for (const userId in rooms) {
// Filter out user IDs that are already in the room / should be excluded
if (excludedTargetIds.includes(userId)) continue;
const room = rooms[userId]; const room = rooms[userId];
const member = room.getMember(userId); const member = room.getMember(userId);
if (!member) continue; // just skip people who don't have memberships for some reason if (!member) continue; // just skip people who don't have memberships for some reason
@ -326,7 +357,7 @@ export default class DMInviteDialog extends React.PureComponent {
return recents; return recents;
} }
_buildSuggestions(): {userId: string, user: RoomMember} { _buildSuggestions(excludedTargetIds: string[]): {userId: string, user: RoomMember} {
const maxConsideredMembers = 200; const maxConsideredMembers = 200;
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const excludedUserIds = [client.getUserId(), SdkConfig.get()['welcomeUserId']]; const excludedUserIds = [client.getUserId(), SdkConfig.get()['welcomeUserId']];
@ -343,6 +374,11 @@ export default class DMInviteDialog extends React.PureComponent {
const joinedMembers = room.getJoinedMembers().filter(u => !excludedUserIds.includes(u.userId)); const joinedMembers = room.getJoinedMembers().filter(u => !excludedUserIds.includes(u.userId));
for (const member of joinedMembers) { for (const member of joinedMembers) {
// Filter out user IDs that are already in the room / should be excluded
if (excludedTargetIds.includes(member.userId)) {
continue;
}
if (!members[member.userId]) { if (!members[member.userId]) {
members[member.userId] = { members[member.userId] = {
member: member, member: member,
@ -390,6 +426,21 @@ export default class DMInviteDialog extends React.PureComponent {
return members.map(m => ({userId: m.member.userId, user: m.member})); return members.map(m => ({userId: m.member.userId, user: m.member}));
} }
_shouldAbortAfterInviteError(result): boolean {
const failedUsers = Object.keys(result.states).filter(a => result.states[a] === 'error');
if (failedUsers.length > 0) {
console.log("Failed to invite users: ", result);
this.setState({
busy: false,
errorText: _t("Failed to invite the following users to chat: %(csvUsers)s", {
csvUsers: failedUsers.join(", "),
}),
});
return true; // abort
}
return false;
}
_startDm = () => { _startDm = () => {
this.setState({busy: true}); this.setState({busy: true});
const targetIds = this.state.targets.map(t => t.userId); const targetIds = this.state.targets.map(t => t.userId);
@ -417,15 +468,7 @@ export default class DMInviteDialog extends React.PureComponent {
createRoomPromise = createRoom().then(roomId => { createRoomPromise = createRoom().then(roomId => {
return inviteMultipleToRoom(roomId, targetIds); return inviteMultipleToRoom(roomId, targetIds);
}).then(result => { }).then(result => {
const failedUsers = Object.keys(result.states).filter(a => result.states[a] === 'error'); if (this._shouldAbortAfterInviteError(result)) {
if (failedUsers.length > 0) {
console.log("Failed to invite users: ", result);
this.setState({
busy: false,
errorText: _t("Failed to invite the following users to chat: %(csvUsers)s", {
csvUsers: failedUsers.join(", "),
}),
});
return true; // abort return true; // abort
} }
}); });
@ -444,6 +487,35 @@ export default class DMInviteDialog extends React.PureComponent {
}); });
}; };
_inviteUsers = () => {
this.setState({busy: true});
const targetIds = this.state.targets.map(t => t.userId);
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
if (!room) {
console.error("Failed to find the room to invite users to");
this.setState({
busy: false,
errorText: _t("Something went wrong trying to invite the users."),
});
return;
}
inviteMultipleToRoom(this.props.roomId, targetIds).then(result => {
if (!this._shouldAbortAfterInviteError(result)) { // handles setting error message too
this.props.onFinished();
}
}).catch(err => {
console.error(err);
this.setState({
busy: false,
errorText: _t(
"We couldn't invite those users. Please check the users you want to invite and try again.",
),
});
});
};
_cancel = () => { _cancel = () => {
// We do not want the user to close the dialog while an action is in progress // We do not want the user to close the dialog while an action is in progress
if (this.state.busy) return; if (this.state.busy) return;
@ -658,7 +730,11 @@ export default class DMInviteDialog extends React.PureComponent {
let showNum = kind === 'recents' ? this.state.numRecentsShown : this.state.numSuggestionsShown; let showNum = kind === 'recents' ? this.state.numRecentsShown : this.state.numSuggestionsShown;
const showMoreFn = kind === 'recents' ? this._showMoreRecents.bind(this) : this._showMoreSuggestions.bind(this); const showMoreFn = kind === 'recents' ? this._showMoreRecents.bind(this) : this._showMoreSuggestions.bind(this);
const lastActive = (m) => kind === 'recents' ? m.lastActive : null; const lastActive = (m) => kind === 'recents' ? m.lastActive : null;
const sectionName = kind === 'recents' ? _t("Recent Conversations") : _t("Suggestions"); let sectionName = kind === 'recents' ? _t("Recent Conversations") : _t("Suggestions");
if (this.props.kind === KIND_INVITE) {
sectionName = kind === 'recents' ? _t("Recently Direct Messaged") : _t("Suggestions");
}
// Mix in the server results if we have any, but only if we're searching. We track the additional // Mix in the server results if we have any, but only if we're searching. We track the additional
// members separately because we want to filter sourceMembers but trust the mixin arrays to have // members separately because we want to filter sourceMembers but trust the mixin arrays to have
@ -690,7 +766,7 @@ export default class DMInviteDialog extends React.PureComponent {
if (sourceMembers.length === 0 && additionalMembers.length === 0) { if (sourceMembers.length === 0 && additionalMembers.length === 0) {
return ( return (
<div className='mx_DMInviteDialog_section'> <div className='mx_InviteDialog_section'>
<h3>{sectionName}</h3> <h3>{sectionName}</h3>
<p>{_t("No results")}</p> <p>{_t("No results")}</p>
</div> </div>
@ -731,7 +807,7 @@ export default class DMInviteDialog extends React.PureComponent {
/> />
)); ));
return ( return (
<div className='mx_DMInviteDialog_section'> <div className='mx_InviteDialog_section'>
<h3>{sectionName}</h3> <h3>{sectionName}</h3>
{tiles} {tiles}
{showMore} {showMore}
@ -754,7 +830,7 @@ export default class DMInviteDialog extends React.PureComponent {
/> />
); );
return ( return (
<div className='mx_DMInviteDialog_editor' onClick={this._onClickInputArea}> <div className='mx_InviteDialog_editor' onClick={this._onClickInputArea}>
{targets} {targets}
{input} {input}
</div> </div>
@ -805,33 +881,54 @@ export default class DMInviteDialog extends React.PureComponent {
spinner = <Spinner w={20} h={20} />; spinner = <Spinner w={20} h={20} />;
} }
const userId = MatrixClientPeg.get().getUserId();
let title;
let helpText;
let buttonText;
let goButtonFn;
if (this.props.kind === KIND_DM) {
const userId = MatrixClientPeg.get().getUserId();
title = _t("Direct Messages");
helpText = _t(
"If you can't find someone, ask them for their username, or share your " +
"username (%(userId)s) or <a>profile link</a>.",
{userId},
{a: (sub) => <a href={makeUserPermalink(userId)} rel="noopener" target="_blank">{sub}</a>},
);
buttonText = _t("Go");
goButtonFn = this._startDm;
} else { // KIND_INVITE
title = _t("Invite to this room");
helpText = _t(
"If you can't find someone, ask them for their username (e.g. @user:server.com) or " +
"<a>share this room</a>.", {},
{a: (sub) => <a href={makeRoomPermalink(this.props.roomId)} rel="noopener" target="_blank">{sub}</a>},
);
buttonText = _t("Invite");
goButtonFn = this._inviteUsers;
}
return ( return (
<BaseDialog <BaseDialog
className='mx_DMInviteDialog' className='mx_InviteDialog'
hasCancel={true} hasCancel={true}
onFinished={this._cancel} onFinished={this._cancel}
title={_t("Direct Messages")} title={title}
> >
<div className='mx_DMInviteDialog_content'> <div className='mx_InviteDialog_content'>
<p> <p>{helpText}</p>
{_t( <div className='mx_InviteDialog_addressBar'>
"If you can't find someone, ask them for their username, or share your " +
"username (%(userId)s) or <a>profile link</a>.",
{userId},
{a: (sub) => <a href={makeUserPermalink(userId)} rel="noopener" target="_blank">{sub}</a>},
)}
</p>
<div className='mx_DMInviteDialog_addressBar'>
{this._renderEditor()} {this._renderEditor()}
<div className='mx_DMInviteDialog_buttonAndSpinner'> <div className='mx_InviteDialog_buttonAndSpinner'>
<AccessibleButton <AccessibleButton
kind="primary" kind="primary"
onClick={this._startDm} onClick={goButtonFn}
className='mx_DMInviteDialog_goButton' className='mx_InviteDialog_goButton'
disabled={this.state.busy} disabled={this.state.busy}
> >
{_t("Go")} {buttonText}
</AccessibleButton> </AccessibleButton>
{spinner} {spinner}
</div> </div>

View File

@ -372,7 +372,7 @@
"Render simple counters in room header": "Render simple counters in room header", "Render simple counters in room header": "Render simple counters in room header",
"Multiple integration managers": "Multiple integration managers", "Multiple integration managers": "Multiple integration managers",
"Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)", "Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)",
"New DM invite dialog (under development)": "New DM invite dialog (under development)", "New invite dialog": "New invite dialog",
"Show a presence dot next to DMs in the room list": "Show a presence dot next to DMs in the room list", "Show a presence dot next to DMs in the room list": "Show a presence dot next to DMs in the room list",
"Enable cross-signing to verify per-user instead of per-device (in development)": "Enable cross-signing to verify per-user instead of per-device (in development)", "Enable cross-signing to verify per-user instead of per-device (in development)": "Enable cross-signing to verify per-user instead of per-device (in development)",
"Enable local event indexing and E2EE search (requires restart)": "Enable local event indexing and E2EE search (requires restart)", "Enable local event indexing and E2EE search (requires restart)": "Enable local event indexing and E2EE search (requires restart)",
@ -1438,16 +1438,6 @@
"View Servers in Room": "View Servers in Room", "View Servers in Room": "View Servers in Room",
"Toolbox": "Toolbox", "Toolbox": "Toolbox",
"Developer Tools": "Developer Tools", "Developer Tools": "Developer Tools",
"Failed to invite the following users to chat: %(csvUsers)s": "Failed to invite the following users to chat: %(csvUsers)s",
"We couldn't create your DM. Please check the users you want to invite and try again.": "We couldn't create your DM. Please check the users you want to invite and try again.",
"Failed to find the following users": "Failed to find the following users",
"The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s",
"Recent Conversations": "Recent Conversations",
"Suggestions": "Suggestions",
"Show more": "Show more",
"Direct Messages": "Direct Messages",
"If you can't find someone, ask them for their username, or share your username (%(userId)s) or <a>profile link</a>.": "If you can't find someone, ask them for their username, or share your username (%(userId)s) or <a>profile link</a>.",
"Go": "Go",
"An error has occurred.": "An error has occurred.", "An error has occurred.": "An error has occurred.",
"Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.", "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.",
"Verifying this user will mark their device as trusted, and also mark your device as trusted to them.": "Verifying this user will mark their device as trusted, and also mark your device as trusted to them.", "Verifying this user will mark their device as trusted, and also mark your device as trusted to them.": "Verifying this user will mark their device as trusted, and also mark your device as trusted to them.",
@ -1457,6 +1447,20 @@
"Enable 'Manage Integrations' in Settings to do this.": "Enable 'Manage Integrations' in Settings to do this.", "Enable 'Manage Integrations' in Settings to do this.": "Enable 'Manage Integrations' in Settings to do this.",
"Integrations not allowed": "Integrations not allowed", "Integrations not allowed": "Integrations not allowed",
"Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.", "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.",
"Failed to invite the following users to chat: %(csvUsers)s": "Failed to invite the following users to chat: %(csvUsers)s",
"We couldn't create your DM. Please check the users you want to invite and try again.": "We couldn't create your DM. Please check the users you want to invite and try again.",
"Something went wrong trying to invite the users.": "Something went wrong trying to invite the users.",
"We couldn't invite those users. Please check the users you want to invite and try again.": "We couldn't invite those users. Please check the users you want to invite and try again.",
"Failed to find the following users": "Failed to find the following users",
"The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s",
"Recent Conversations": "Recent Conversations",
"Suggestions": "Suggestions",
"Recently Direct Messaged": "Recently Direct Messaged",
"Show more": "Show more",
"Direct Messages": "Direct Messages",
"If you can't find someone, ask them for their username, or share your username (%(userId)s) or <a>profile link</a>.": "If you can't find someone, ask them for their username, or share your username (%(userId)s) or <a>profile link</a>.",
"Go": "Go",
"If you can't find someone, ask them for their username (e.g. @user:server.com) or <a>share this room</a>.": "If you can't find someone, ask them for their username (e.g. @user:server.com) or <a>share this room</a>.",
"You added a new device '%(displayName)s', which is requesting encryption keys.": "You added a new device '%(displayName)s', which is requesting encryption keys.", "You added a new device '%(displayName)s', which is requesting encryption keys.": "You added a new device '%(displayName)s', which is requesting encryption keys.",
"Your unverified device '%(displayName)s' is requesting encryption keys.": "Your unverified device '%(displayName)s' is requesting encryption keys.", "Your unverified device '%(displayName)s' is requesting encryption keys.": "Your unverified device '%(displayName)s' is requesting encryption keys.",
"Start verification": "Start verification", "Start verification": "Start verification",

View File

@ -130,7 +130,7 @@ export const SETTINGS = {
}, },
"feature_ftue_dms": { "feature_ftue_dms": {
isFeature: true, isFeature: true,
displayName: _td("New DM invite dialog (under development)"), displayName: _td("New invite dialog"),
supportedLevels: LEVELS_FEATURE, supportedLevels: LEVELS_FEATURE,
default: false, default: false,
}, },