diff --git a/src/RoomInvite.js b/src/RoomInvite.js index aaddd58d0b..2eccf69b0f 100644 --- a/src/RoomInvite.js +++ b/src/RoomInvite.js @@ -1,6 +1,7 @@ /* Copyright 2016 OpenMarket 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"); 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 { _t } from './languageHandler'; import SettingsStore from "./settings/SettingsStore"; +import {KIND_DM, KIND_INVITE} from "./components/views/dialogs/InviteDialog"; /** * Invites multiple addresses to a room @@ -46,7 +48,7 @@ export function showStartChatInviteDialog() { // This new 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, {}, + 'Start DM', '', InviteDialog, {kind: KIND_DM}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true, ); return; @@ -72,6 +74,16 @@ export function showStartChatInviteDialog() { } 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"); Modal.createTrackedDialog('Chat Invite', '', AddressPickerDialog, { diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 6b8e532854..7448b1a5a3 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -19,7 +19,7 @@ import PropTypes from 'prop-types'; import {_t} from "../../../languageHandler"; import * as sdk from "../../../index"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; -import {makeUserPermalink} from "../../../utils/permalinks/Permalinks"; +import {makeRoomPermalink, makeUserPermalink} from "../../../utils/permalinks/Permalinks"; import DMRoomMap from "../../../utils/DMRoomMap"; import {RoomMember} from "matrix-js-sdk/src/matrix"; import SdkConfig from "../../../SdkConfig"; @@ -34,7 +34,8 @@ import {humanizeTime} from "../../../utils/humanize"; import createRoom from "../../../createRoom"; 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 INCREMENT_ROOMS_SHOWN = 5; // Number of rooms to add when 'show more' is clicked @@ -276,13 +277,28 @@ export default class InviteDialog extends React.PureComponent { static propTypes = { // Takes an array of user IDs/emails to invite. 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; _editorRef: any = null; - constructor() { - super(); + constructor(props) { + super(props); + + if (props.kind === KIND_INVITE && !props.roomId) { + throw new Error("When using KIND_INVITE a roomId is required for an InviteDialog"); + } this.state = { targets: [], // array of Member objects (see interface above) @@ -390,6 +406,21 @@ export default class InviteDialog extends React.PureComponent { 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 = () => { this.setState({busy: true}); const targetIds = this.state.targets.map(t => t.userId); @@ -417,15 +448,7 @@ export default class InviteDialog extends React.PureComponent { createRoomPromise = createRoom().then(roomId => { return inviteMultipleToRoom(roomId, targetIds); }).then(result => { - 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(", "), - }), - }); + if (this._shouldAbortAfterInviteError(result)) { return true; // abort } }); @@ -444,6 +467,33 @@ export default class InviteDialog 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 = () => { // We do not want the user to close the dialog while an action is in progress if (this.state.busy) return; @@ -658,7 +708,11 @@ export default class InviteDialog extends React.PureComponent { let showNum = kind === 'recents' ? this.state.numRecentsShown : this.state.numSuggestionsShown; const showMoreFn = kind === 'recents' ? this._showMoreRecents.bind(this) : this._showMoreSuggestions.bind(this); 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 // members separately because we want to filter sourceMembers but trust the mixin arrays to have @@ -805,33 +859,54 @@ export default class InviteDialog extends React.PureComponent { spinner = ; } - 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 profile link.", + {userId}, + {a: (sub) => {sub}}, + ); + 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 " + + "share this room.", {}, + {a: (sub) => {sub}}, + ); + buttonText = _t("Invite"); + goButtonFn = this._inviteUsers; + } + return (
-

- {_t( - "If you can't find someone, ask them for their username, or share your " + - "username (%(userId)s) or profile link.", - {userId}, - {a: (sub) => {sub}}, - )} -

+

{helpText}

{this._renderEditor()}
- {_t("Go")} + {buttonText} {spinner}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b6f61570cd..f8b17db7c5 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -372,7 +372,7 @@ "Render simple counters in room header": "Render simple counters in room header", "Multiple integration managers": "Multiple integration managers", "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", "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)", @@ -1438,16 +1438,6 @@ "View Servers in Room": "View Servers in Room", "Toolbox": "Toolbox", "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 profile link.": "If you can't find someone, ask them for their username, or share your username (%(userId)s) or profile link.", - "Go": "Go", "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.", "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.", "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.", + "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 profile link.": "If you can't find someone, ask them for their username, or share your username (%(userId)s) or profile link.", + "Go": "Go", + "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.": "If you can't find someone, ask them for their username (e.g. @user:server.com) or share this room.", "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.", "Start verification": "Start verification",