Allow connecting to an IS from address picker

This allows those who previously disconnected from an IS to either choose the
default IS or a custom one from Settings via the address picker dialog.

Part of https://github.com/vector-im/riot-web/issues/10619
This commit is contained in:
J. Ryan Stinnett 2019-08-29 15:20:14 +01:00
parent 752eb17893
commit 166fb696c2
10 changed files with 139 additions and 14 deletions

View file

@ -281,6 +281,12 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
box-shadow: 2px 15px 30px 0 $dialog-shadow-color; box-shadow: 2px 15px 30px 0 $dialog-shadow-color;
border-radius: 4px; border-radius: 4px;
overflow-y: auto; overflow-y: auto;
a:link,
a:hover,
a:visited {
@mixin mx_Dialog_link;
}
} }
.mx_Dialog_fixedWidth { .mx_Dialog_fixedWidth {

View file

@ -39,8 +39,7 @@ limitations under the License.
a:link, a:link,
a:hover, a:hover,
a:visited { a:visited {
color: $accent-color; @mixin mx_Dialog_link;
text-decoration: none;
} }
input[type=text], input[type=text],

View file

@ -67,3 +67,6 @@ limitations under the License.
pointer-events: none; pointer-events: none;
} }
.mx_AddressPickerDialog_identityServer {
margin-top: 1em;
}

View file

@ -202,6 +202,11 @@ $interactive-tooltip-fg-color: #ffffff;
background-color: $button-secondary-bg-color; background-color: $button-secondary-bg-color;
} }
@define-mixin mx_Dialog_link {
color: $accent-color;
text-decoration: none;
}
// Nasty hacks to apply a filter to arbitrary monochrome artwork to make it // Nasty hacks to apply a filter to arbitrary monochrome artwork to make it
// better match the theme. Typically applied to dark grey 'off' buttons or // better match the theme. Typically applied to dark grey 'off' buttons or
// light grey 'on' buttons. // light grey 'on' buttons.

View file

@ -328,3 +328,8 @@ $interactive-tooltip-fg-color: #ffffff;
color: $accent-color; color: $accent-color;
background-color: $button-secondary-bg-color; background-color: $button-secondary-bg-color;
} }
@define-mixin mx_Dialog_link {
color: $accent-color;
text-decoration: none;
}

View file

@ -51,7 +51,14 @@ export function showStartChatInviteDialog() {
Modal.createTrackedDialog('Start a chat', '', AddressPickerDialog, { Modal.createTrackedDialog('Start a chat', '', AddressPickerDialog, {
title: _t('Start a chat'), title: _t('Start a chat'),
description: _t("Who would you like to communicate with?"), description: _t("Who would you like to communicate with?"),
placeholder: _t("Email, name or Matrix ID"), placeholder: (validAddressTypes) => {
// The set of valid address type can be mutated inside the dialog
// when you first have no IS but agree to use one in the dialog.
if (validAddressTypes.includes('email')) {
return _t("Email, name or Matrix ID");
}
return _t("Name or Matrix ID");
},
validAddressTypes, validAddressTypes,
button: _t("Start Chat"), button: _t("Start Chat"),
onFinished: _onStartDmFinished, onFinished: _onStartDmFinished,
@ -69,7 +76,14 @@ export function showRoomInviteDialog(roomId) {
Modal.createTrackedDialog('Chat Invite', '', AddressPickerDialog, { Modal.createTrackedDialog('Chat Invite', '', AddressPickerDialog, {
title: _t('Invite new room members'), title: _t('Invite new room members'),
button: _t('Send Invites'), button: _t('Send Invites'),
placeholder: _t("Email, name or Matrix ID"), placeholder: (validAddressTypes) => {
// The set of valid address type can be mutated inside the dialog
// when you first have no IS but agree to use one in the dialog.
if (validAddressTypes.includes('email')) {
return _t("Email, name or Matrix ID");
}
return _t("Name or Matrix ID");
},
validAddressTypes, validAddressTypes,
onFinished: (shouldInvite, addrs) => { onFinished: (shouldInvite, addrs) => {
_onRoomInviteFinished(roomId, shouldInvite, addrs); _onRoomInviteFinished(roomId, shouldInvite, addrs);

View file

@ -24,11 +24,14 @@ import createReactClass from 'create-react-class';
import { _t, _td } from '../../../languageHandler'; import { _t, _td } from '../../../languageHandler';
import sdk from '../../../index'; import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg'; import MatrixClientPeg from '../../../MatrixClientPeg';
import dis from '../../../dispatcher';
import Promise from 'bluebird'; import Promise from 'bluebird';
import { addressTypes, getAddressType } from '../../../UserAddress.js'; import { addressTypes, getAddressType } from '../../../UserAddress.js';
import GroupStore from '../../../stores/GroupStore'; import GroupStore from '../../../stores/GroupStore';
import * as Email from '../../../email'; import * as Email from '../../../email';
import IdentityAuthClient from '../../../IdentityAuthClient'; import IdentityAuthClient from '../../../IdentityAuthClient';
import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from '../../../utils/IdentityServerUtils';
import { abbreviateUrl } from '../../../utils/UrlUtils';
const TRUNCATE_QUERY_LIST = 40; const TRUNCATE_QUERY_LIST = 40;
const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200; const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200;
@ -49,7 +52,7 @@ module.exports = createReactClass({
// Extra node inserted after picker input, dropdown and errors // Extra node inserted after picker input, dropdown and errors
extraNode: PropTypes.node, extraNode: PropTypes.node,
value: PropTypes.string, value: PropTypes.string,
placeholder: PropTypes.string, placeholder: PropTypes.oneOfType(PropTypes.string, PropTypes.func),
roomId: PropTypes.string, roomId: PropTypes.string,
button: PropTypes.string, button: PropTypes.string,
focus: PropTypes.bool, focus: PropTypes.bool,
@ -91,6 +94,9 @@ module.exports = createReactClass({
// List of UserAddressType objects representing the set of // List of UserAddressType objects representing the set of
// auto-completion results for the current search query. // auto-completion results for the current search query.
suggestedList: [], suggestedList: [],
// List of address types initialised from props, but may change while the
// dialog is open.
validAddressTypes: this.props.validAddressTypes,
}; };
}, },
@ -101,6 +107,15 @@ module.exports = createReactClass({
} }
}, },
getPlaceholder() {
const { placeholder } = this.props;
if (typeof placeholder === "string") {
return placeholder;
}
// Otherwise it's a function, as checked by prop types.
return placeholder(this.state.validAddressTypes);
},
onButtonClick: function() { onButtonClick: function() {
let selectedList = this.state.selectedList.slice(); let selectedList = this.state.selectedList.slice();
// Check the text input field to see if user has an unconverted address // Check the text input field to see if user has an unconverted address
@ -434,7 +449,7 @@ module.exports = createReactClass({
// This is important, otherwise there's no way to invite // This is important, otherwise there's no way to invite
// a perfectly valid address if there are close matches. // a perfectly valid address if there are close matches.
const addrType = getAddressType(query); const addrType = getAddressType(query);
if (this.props.validAddressTypes.includes(addrType)) { if (this.state.validAddressTypes.includes(addrType)) {
if (addrType === 'email' && !Email.looksValid(query)) { if (addrType === 'email' && !Email.looksValid(query)) {
this.setState({searchError: _t("That doesn't look like a valid email address")}); this.setState({searchError: _t("That doesn't look like a valid email address")});
return; return;
@ -470,7 +485,7 @@ module.exports = createReactClass({
isKnown: false, isKnown: false,
}; };
if (!this.props.validAddressTypes.includes(addrType)) { if (!this.state.validAddressTypes.includes(addrType)) {
hasError = true; hasError = true;
} else if (addrType === 'mx-user-id') { } else if (addrType === 'mx-user-id') {
const user = MatrixClientPeg.get().getUser(addrObj.address); const user = MatrixClientPeg.get().getUser(addrObj.address);
@ -571,6 +586,24 @@ module.exports = createReactClass({
this._addAddressesToList(text.split(/[\s,]+/)); this._addAddressesToList(text.split(/[\s,]+/));
}, },
onUseDefaultIdentityServerClick(e) {
e.preventDefault();
// Update the IS in account data. Actually using it may trigger terms.
useDefaultIdentityServer();
// Add email as a valid address type.
const { validAddressTypes } = this.state;
validAddressTypes.push('email');
this.setState({ validAddressTypes });
},
onManageSettingsClick(e) {
e.preventDefault();
dis.dispatch({ action: 'view_user_settings' });
this.onCancel();
},
render: function() { render: function() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
@ -610,7 +643,7 @@ module.exports = createReactClass({
ref="textinput" ref="textinput"
className="mx_AddressPickerDialog_input" className="mx_AddressPickerDialog_input"
onChange={this.onQueryChanged} onChange={this.onQueryChanged}
placeholder={this.props.placeholder} placeholder={this.getPlaceholder()}
defaultValue={this.props.value} defaultValue={this.props.value}
autoFocus={this.props.focus}> autoFocus={this.props.focus}>
</textarea>, </textarea>,
@ -621,7 +654,7 @@ module.exports = createReactClass({
let error; let error;
let addressSelector; let addressSelector;
if (this.state.invalidAddressError) { if (this.state.invalidAddressError) {
const validTypeDescriptions = this.props.validAddressTypes.map((t) => _t(addressTypeName[t])); const validTypeDescriptions = this.state.validAddressTypes.map((t) => _t(addressTypeName[t]));
error = <div className="mx_AddressPickerDialog_error"> error = <div className="mx_AddressPickerDialog_error">
{ _t("You have entered an invalid address.") } { _t("You have entered an invalid address.") }
<br /> <br />
@ -644,6 +677,33 @@ module.exports = createReactClass({
); );
} }
let identityServer;
if (this.props.pickerType === 'user' && !this.state.validAddressTypes.includes('email')) {
const defaultIdentityServerUrl = getDefaultIdentityServerUrl();
if (defaultIdentityServerUrl) {
identityServer = <div className="mx_AddressPickerDialog_identityServer">{_t(
"Use an identity server to invite by email. " +
"<default>Use the default (%(defaultIdentityServerName)s)</default> " +
"or manage in <settings>Settings</settings>.",
{
defaultIdentityServerName: abbreviateUrl(defaultIdentityServerUrl),
},
{
default: sub => <a href="#" onClick={this.onUseDefaultIdentityServerClick}>{sub}</a>,
settings: sub => <a href="#" onClick={this.onManageSettingsClick}>{sub}</a>,
},
)}</div>;
} else {
identityServer = <div className="mx_AddressPickerDialog_identityServer">{_t(
"Use an identity server to invite by email. " +
"Manage in <settings>Settings</settings>.",
{}, {
settings: sub => <a href="#" onClick={this.onManageSettingsClick}>{sub}</a>,
},
)}</div>;
}
}
return ( return (
<BaseDialog className="mx_AddressPickerDialog" onKeyDown={this.onKeyDown} <BaseDialog className="mx_AddressPickerDialog" onKeyDown={this.onKeyDown}
onFinished={this.props.onFinished} title={this.props.title}> onFinished={this.props.onFinished} title={this.props.title}>
@ -653,6 +713,7 @@ module.exports = createReactClass({
{ error } { error }
{ addressSelector } { addressSelector }
{ this.props.extraNode } { this.props.extraNode }
{ identityServer }
</div> </div>
<DialogButtons primaryButton={this.props.button} <DialogButtons primaryButton={this.props.button}
onPrimaryButtonClick={this.onButtonClick} onPrimaryButtonClick={this.onButtonClick}

View file

@ -20,13 +20,13 @@ import PropTypes from 'prop-types';
import {_t} from "../../../languageHandler"; import {_t} from "../../../languageHandler";
import sdk from '../../../index'; import sdk from '../../../index';
import MatrixClientPeg from "../../../MatrixClientPeg"; import MatrixClientPeg from "../../../MatrixClientPeg";
import SdkConfig from "../../../SdkConfig";
import Modal from '../../../Modal'; import Modal from '../../../Modal';
import dis from "../../../dispatcher"; import dis from "../../../dispatcher";
import { getThreepidBindStatus } from '../../../boundThreepids'; import { getThreepidBindStatus } from '../../../boundThreepids';
import IdentityAuthClient from "../../../IdentityAuthClient"; import IdentityAuthClient from "../../../IdentityAuthClient";
import {SERVICE_TYPES} from "matrix-js-sdk"; import {SERVICE_TYPES} from "matrix-js-sdk";
import {abbreviateUrl, unabbreviateUrl} from "../../../utils/UrlUtils"; import {abbreviateUrl, unabbreviateUrl} from "../../../utils/UrlUtils";
import { getDefaultIdentityServerUrl } from '../../../utils/IdentityServerUtils';
/** /**
* Check an IS URL is valid, including liveness check * Check an IS URL is valid, including liveness check
@ -66,10 +66,10 @@ export default class SetIdServer extends React.Component {
super(); super();
let defaultIdServer = ''; let defaultIdServer = '';
if (!MatrixClientPeg.get().getIdentityServerUrl() && SdkConfig.get()['validated_server_config']['isUrl']) { if (!MatrixClientPeg.get().getIdentityServerUrl() && getDefaultIdentityServerUrl()) {
// If no ID server is configured but there's one in the config, prepopulate // If no ID server is configured but there's one in the config, prepopulate
// the field to help the user. // the field to help the user.
defaultIdServer = abbreviateUrl(SdkConfig.get()['validated_server_config']['isUrl']); defaultIdServer = abbreviateUrl(getDefaultIdentityServerUrl());
} }
this.state = { this.state = {
@ -253,10 +253,10 @@ export default class SetIdServer extends React.Component {
}); });
let newFieldVal = ''; let newFieldVal = '';
if (SdkConfig.get()['validated_server_config']['isUrl']) { if (getDefaultIdentityServerUrl()) {
// Prepopulate the client's default so the user at least has some idea of // Prepopulate the client's default so the user at least has some idea of
// a valid value they might enter // a valid value they might enter
newFieldVal = abbreviateUrl(SdkConfig.get()['validated_server_config']['isUrl']); newFieldVal = abbreviateUrl(getDefaultIdentityServerUrl());
} }
this.setState({ this.setState({

View file

@ -1161,6 +1161,8 @@
"That doesn't look like a valid email address": "That doesn't look like a valid email address", "That doesn't look like a valid email address": "That doesn't look like a valid email address",
"You have entered an invalid address.": "You have entered an invalid address.", "You have entered an invalid address.": "You have entered an invalid address.",
"Try using one of the following valid address types: %(validTypesList)s.": "Try using one of the following valid address types: %(validTypesList)s.", "Try using one of the following valid address types: %(validTypesList)s.": "Try using one of the following valid address types: %(validTypesList)s.",
"Use an identity server to invite by email. <default>Use the default (%(defaultIdentityServerName)s)</default> or manage in <settings>Settings</settings>.": "Use an identity server to invite by email. <default>Use the default (%(defaultIdentityServerName)s)</default> or manage in <settings>Settings</settings>.",
"Use an identity server to invite by email. Manage in <settings>Settings</settings>.": "Use an identity server to invite by email. Manage in <settings>Settings</settings>.",
"The following users may not exist": "The following users may not exist", "The following users may not exist": "The following users may not exist",
"Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?": "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?", "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?": "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?",
"Invite anyway and never warn me again": "Invite anyway and never warn me again", "Invite anyway and never warn me again": "Invite anyway and never warn me again",

View file

@ -0,0 +1,30 @@
/*
Copyright 2019 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.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import SdkConfig from '../SdkConfig';
import MatrixClientPeg from '../MatrixClientPeg';
export function getDefaultIdentityServerUrl() {
return SdkConfig.get()['validated_server_config']['isUrl'];
}
export function useDefaultIdentityServer() {
const url = getDefaultIdentityServerUrl();
// Account data change will update localstorage, client, etc through dispatcher
MatrixClientPeg.get().setAccountData("m.identity_server", {
base_url: url,
});
}