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:
parent
752eb17893
commit
166fb696c2
10 changed files with 139 additions and 14 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -67,3 +67,6 @@ limitations under the License.
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_AddressPickerDialog_identityServer {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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",
|
||||||
|
|
30
src/utils/IdentityServerUtils.js
Normal file
30
src/utils/IdentityServerUtils.js
Normal 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,
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue