Merge remote-tracking branch 'origin/develop' into dbkr/disco_is

This commit is contained in:
David Baker 2019-08-14 10:03:32 +01:00
commit 735c6d73d8
35 changed files with 493 additions and 333 deletions

View file

@ -935,7 +935,7 @@ export default React.createClass({
const CreateRoomDialog = sdk.getComponent('dialogs.CreateRoomDialog');
const modal = Modal.createTrackedDialog('Create Room', '', CreateRoomDialog);
const [shouldCreate, name, noFederate] = await modal;
const [shouldCreate, name, noFederate] = await modal.finished;
if (shouldCreate) {
const createOpts = {};
if (name) createOpts.name = name;

View file

@ -15,13 +15,13 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
import SdkConfig from "../../../SdkConfig";
import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils";
import * as ServerType from '../../views/auth/ServerTypeSelector';
import ServerConfig from "./ServerConfig";
const MODULAR_URL = 'https://modular.im/?utm_source=riot-web&utm_medium=web&utm_campaign=riot-web-authentication';
@ -33,49 +33,8 @@ const MODULAR_URL = 'https://modular.im/?utm_source=riot-web&utm_medium=web&utm_
* This is a variant of ServerConfig with only the HS field and different body
* text that is specific to the Modular case.
*/
export default class ModularServerConfig extends React.PureComponent {
static propTypes = {
onServerConfigChange: PropTypes.func,
// The current configuration that the user is expecting to change.
serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired,
delayTimeMs: PropTypes.number, // time to wait before invoking onChanged
// Called after the component calls onServerConfigChange
onAfterSubmit: PropTypes.func,
// Optional text for the submit button. If falsey, no button will be shown.
submitText: PropTypes.string,
// Optional class for the submit button. Only applies if the submit button
// is to be rendered.
submitClass: PropTypes.string,
};
static defaultProps = {
onServerConfigChange: function() {},
customHsUrl: "",
delayTimeMs: 0,
};
constructor(props) {
super(props);
this.state = {
busy: false,
errorText: "",
hsUrl: props.serverConfig.hsUrl,
isUrl: props.serverConfig.isUrl,
};
}
componentWillReceiveProps(newProps) {
if (newProps.serverConfig.hsUrl === this.state.hsUrl &&
newProps.serverConfig.isUrl === this.state.isUrl) return;
this.validateAndApplyServer(newProps.serverConfig.hsUrl, newProps.serverConfig.isUrl);
}
export default class ModularServerConfig extends ServerConfig {
static propTypes = ServerConfig.propTypes;
async validateAndApplyServer(hsUrl, isUrl) {
// Always try and use the defaults first
@ -120,35 +79,6 @@ export default class ModularServerConfig extends React.PureComponent {
return this.validateAndApplyServer(this.state.hsUrl, ServerType.TYPES.PREMIUM.identityServerUrl);
}
onHomeserverBlur = (ev) => {
this._hsTimeoutId = this._waitThenInvoke(this._hsTimeoutId, () => {
this.validateServer();
});
};
onHomeserverChange = (ev) => {
const hsUrl = ev.target.value;
this.setState({ hsUrl });
};
onSubmit = async (ev) => {
ev.preventDefault();
ev.stopPropagation();
const result = await this.validateServer();
if (!result) return; // Do not continue.
if (this.props.onAfterSubmit) {
this.props.onAfterSubmit();
}
};
_waitThenInvoke(existingTimeoutId, fn) {
if (existingTimeoutId) {
clearTimeout(existingTimeoutId);
}
return setTimeout(fn.bind(this), this.props.delayTimeMs);
}
render() {
const Field = sdk.getComponent('elements.Field');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');

View file

@ -2,6 +2,7 @@
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2018, 2019 New Vector Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -69,10 +70,10 @@ module.exports = React.createClass({
fieldValid: {},
// The ISO2 country code selected in the phone number entry
phoneCountry: this.props.defaultPhoneCountry,
username: "",
email: "",
phoneNumber: "",
password: "",
username: this.props.defaultUsername || "",
email: this.props.defaultEmail || "",
phoneNumber: this.props.defaultPhoneNumber || "",
password: this.props.defaultPassword || "",
passwordConfirm: "",
passwordComplexity: null,
passwordSafe: false,
@ -90,7 +91,7 @@ module.exports = React.createClass({
}
const self = this;
if (this.state.email == '') {
if (this.state.email === '') {
const haveIs = Boolean(this.props.serverConfig.isUrl);
let desc;
@ -455,7 +456,6 @@ module.exports = React.createClass({
ref={field => this[FIELD_EMAIL] = field}
type="text"
label={emailPlaceholder}
defaultValue={this.props.defaultEmail}
value={this.state.email}
onChange={this.onEmailChange}
onValidate={this.onEmailValidate}
@ -469,7 +469,6 @@ module.exports = React.createClass({
ref={field => this[FIELD_PASSWORD] = field}
type="password"
label={_t("Password")}
defaultValue={this.props.defaultPassword}
value={this.state.password}
onChange={this.onPasswordChange}
onValidate={this.onPasswordValidate}
@ -483,7 +482,6 @@ module.exports = React.createClass({
ref={field => this[FIELD_PASSWORD_CONFIRM] = field}
type="password"
label={_t("Confirm")}
defaultValue={this.props.defaultPassword}
value={this.state.passwordConfirm}
onChange={this.onPasswordConfirmChange}
onValidate={this.onPasswordConfirmValidate}
@ -512,7 +510,6 @@ module.exports = React.createClass({
ref={field => this[FIELD_PHONE_NUMBER] = field}
type="text"
label={phoneLabel}
defaultValue={this.props.defaultPhoneNumber}
value={this.state.phoneNumber}
prefix={phoneCountry}
onChange={this.onPhoneNumberChange}
@ -528,7 +525,6 @@ module.exports = React.createClass({
type="text"
autoFocus={true}
label={_t("Username")}
defaultValue={this.props.defaultUsername}
value={this.state.username}
onChange={this.onUsernameChange}
onValidate={this.onUsernameValidate}

View file

@ -1,6 +1,7 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2018 New Vector Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -19,7 +20,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import { MatrixClient } from 'matrix-js-sdk';
import AvatarLogic from '../../../Avatar';
import sdk from '../../../index';
import SettingsStore from "../../../settings/SettingsStore";
import AccessibleButton from '../elements/AccessibleButton';
@ -121,6 +121,10 @@ module.exports = React.createClass({
);
urls.push(defaultImageUrl); // lowest priority
}
// deduplicate URLs
urls = Array.from(new Set(urls));
return {
imageUrls: urls,
defaultImageUrl: defaultImageUrl,

View file

@ -22,7 +22,6 @@ import qs from 'querystring';
import React from 'react';
import PropTypes from 'prop-types';
import MatrixClientPeg from '../../../MatrixClientPeg';
import ScalarAuthClient from '../../../ScalarAuthClient';
import WidgetMessaging from '../../../WidgetMessaging';
import AccessibleButton from './AccessibleButton';
import Modal from '../../../Modal';
@ -35,7 +34,7 @@ import WidgetUtils from '../../../utils/WidgetUtils';
import dis from '../../../dispatcher';
import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
import classNames from 'classnames';
import { showIntegrationsManager } from '../../../integrations/integrations';
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
const ENABLE_REACT_PERF = false;
@ -178,9 +177,22 @@ export default class AppTile extends React.Component {
return;
}
const managers = IntegrationManagers.sharedInstance();
if (!managers.hasManager()) {
console.warn("No integration manager - not setting scalar token", url);
this.setState({
error: null,
widgetUrl: this._addWurlParams(this.props.url),
initialising: false,
});
return;
}
// TODO: Pick the right manager for the widget
// Fetch the token before loading the iframe as we need it to mangle the URL
if (!this._scalarClient) {
this._scalarClient = new ScalarAuthClient();
this._scalarClient = managers.getPrimaryManager().getScalarClient();
}
this._scalarClient.getScalarToken().done((token) => {
// Append scalar_token as a query param if not already present
@ -189,7 +201,7 @@ export default class AppTile extends React.Component {
const params = qs.parse(u.query);
if (!params.scalar_token) {
params.scalar_token = encodeURIComponent(token);
// u.search must be set to undefined, so that u.format() uses query paramerters - https://nodejs.org/docs/latest/api/url.html#url_url_format_url_options
// u.search must be set to undefined, so that u.format() uses query parameters - https://nodejs.org/docs/latest/api/url.html#url_url_format_url_options
u.search = undefined;
u.query = params;
}
@ -251,11 +263,12 @@ export default class AppTile extends React.Component {
if (this.props.onEditClick) {
this.props.onEditClick();
} else {
showIntegrationsManager({
room: this.props.room,
screen: 'type_' + this.props.type,
integrationId: this.props.id,
});
// TODO: Open the right manager for the widget
IntegrationManagers.sharedInstance().getPrimaryManager().open(
this.props.room,
this.props.type,
this.props.id,
);
}
}

View file

@ -48,7 +48,7 @@ export default class Field extends React.PureComponent {
onValidate: PropTypes.func,
// If specified, contents will appear as a tooltip on the element and
// validation feedback tooltips will be suppressed.
tooltip: PropTypes.node,
tooltipContent: PropTypes.node,
// All other props pass through to the <input>.
};
@ -137,8 +137,7 @@ export default class Field extends React.PureComponent {
}, VALIDATION_THROTTLE_MS);
render() {
const { element, prefix, onValidate, children, ...inputProps } = this.props;
delete inputProps.tooltip; // needs to be removed from props but we don't need it here
const { element, prefix, onValidate, children, tooltipContent, ...inputProps } = this.props;
const inputElement = element || "input";
@ -170,11 +169,11 @@ export default class Field extends React.PureComponent {
// Handle displaying feedback on validity
const Tooltip = sdk.getComponent("elements.Tooltip");
let fieldTooltip;
if (this.props.tooltip || this.state.feedback) {
if (tooltipContent || this.state.feedback) {
fieldTooltip = <Tooltip
tooltipClassName="mx_Field_tooltip"
visible={this.state.feedbackVisible}
label={this.props.tooltip || this.state.feedback}
label={tooltipContent || this.state.feedback}
/>;
}

View file

@ -18,9 +18,8 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import ScalarAuthClient from '../../../ScalarAuthClient';
import { _t } from '../../../languageHandler';
import { showIntegrationsManager } from '../../../integrations/integrations';
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
export default class ManageIntegsButton extends React.Component {
constructor(props) {
@ -30,12 +29,17 @@ export default class ManageIntegsButton extends React.Component {
onManageIntegrations = (ev) => {
ev.preventDefault();
showIntegrationsManager({ room: this.props.room });
const managers = IntegrationManagers.sharedInstance();
if (!managers.hasManager()) {
managers.openNoManagerDialog();
} else {
managers.getPrimaryManager().open(this.props.room);
}
};
render() {
let integrationsButton = <div />;
if (ScalarAuthClient.isPossible()) {
if (IntegrationManagers.sharedInstance().hasManager()) {
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
integrationsButton = (
<AccessibleButton

View file

@ -1,5 +1,6 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -17,7 +18,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import MatrixClientPeg from '../../../MatrixClientPeg';
import { ContentRepo } from 'matrix-js-sdk';
import { _t } from '../../../languageHandler';
import sdk from '../../../index';
import Modal from '../../../Modal';
@ -31,12 +31,21 @@ module.exports = React.createClass({
mxEvent: PropTypes.object.isRequired,
},
onAvatarClick: function(name) {
const httpUrl = MatrixClientPeg.get().mxcUrlToHttp(this.props.mxEvent.getContent().url);
onAvatarClick: function() {
const cli = MatrixClientPeg.get();
const ev = this.props.mxEvent;
const httpUrl = cli.mxcUrlToHttp(ev.getContent().url);
const room = cli.getRoom(this.props.mxEvent.getRoomId());
const text = _t('%(senderDisplayName)s changed the avatar for %(roomName)s', {
senderDisplayName: ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(),
roomName: room ? room.name : '',
});
const ImageView = sdk.getComponent("elements.ImageView");
const params = {
src: httpUrl,
name: name,
name: text,
};
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox");
},
@ -44,29 +53,22 @@ module.exports = React.createClass({
render: function() {
const ev = this.props.mxEvent;
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
const name = _t('%(senderDisplayName)s changed the avatar for %(roomName)s', {
senderDisplayName: senderDisplayName,
roomName: room ? room.name : '',
});
const RoomAvatar = sdk.getComponent("avatars.RoomAvatar");
if (!ev.getContent().url || ev.getContent().url.trim().length === 0) {
return (
<div className="mx_TextualEvent">
{ _t('%(senderDisplayName)s removed the room avatar.', {senderDisplayName: senderDisplayName}) }
{ _t('%(senderDisplayName)s removed the room avatar.', {senderDisplayName}) }
</div>
);
}
const url = ContentRepo.getHttpUriForMxc(
MatrixClientPeg.get().getHomeserverUrl(),
ev.getContent().url,
Math.ceil(14 * window.devicePixelRatio),
Math.ceil(14 * window.devicePixelRatio),
'crop',
);
const room = MatrixClientPeg.get().getRoom(ev.getRoomId());
// Provide all arguments to RoomAvatar via oobData because the avatar is historic
const oobData = {
avatarUrl: ev.getContent().url,
name: room ? room.name : "",
};
return (
<div className="mx_RoomAvatarEvent">
@ -75,8 +77,8 @@ module.exports = React.createClass({
{
'img': () =>
<AccessibleButton key="avatar" className="mx_RoomAvatarEvent_avatar"
onClick={this.onAvatarClick.bind(this, name)}>
<BaseAvatar width={14} height={14} url={url} name={name} />
onClick={this.onAvatarClick}>
<RoomAvatar width={14} height={14} oobData={oobData} />
</AccessibleButton>,
})
}

View file

@ -25,7 +25,6 @@ import highlight from 'highlight.js';
import * as HtmlUtils from '../../../HtmlUtils';
import {formatDate} from '../../../DateUtils';
import sdk from '../../../index';
import ScalarAuthClient from '../../../ScalarAuthClient';
import Modal from '../../../Modal';
import SdkConfig from '../../../SdkConfig';
import dis from '../../../dispatcher';
@ -35,6 +34,7 @@ import SettingsStore from "../../../settings/SettingsStore";
import ReplyThread from "../elements/ReplyThread";
import {host as matrixtoHost} from '../../../matrix-to';
import {pillifyLinks} from '../../../utils/pillify';
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
module.exports = React.createClass({
displayName: 'TextualBody',
@ -318,12 +318,19 @@ module.exports = React.createClass({
// which requires the user to click through and THEN we can open the link in a new tab because
// the window.open command occurs in the same stack frame as the onClick callback.
const managers = IntegrationManagers.sharedInstance();
if (!managers.hasManager()) {
managers.openNoManagerDialog();
return;
}
// Go fetch a scalar token
const scalarClient = new ScalarAuthClient();
const integrationManager = managers.getPrimaryManager();
const scalarClient = integrationManager.getScalarClient();
scalarClient.connect().then(() => {
const completeUrl = scalarClient.getStarterLink(starterLink);
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
const integrationsUrl = SdkConfig.get().integrations_ui_url;
const integrationsUrl = integrationManager.uiUrl;
Modal.createTrackedDialog('Add an integration', '', QuestionDialog, {
title: _t("Add an Integration"),
description:

View file

@ -29,7 +29,7 @@ import { _t } from '../../../languageHandler';
import WidgetUtils from '../../../utils/WidgetUtils';
import WidgetEchoStore from "../../../stores/WidgetEchoStore";
import AccessibleButton from '../elements/AccessibleButton';
import { showIntegrationsManager } from '../../../integrations/integrations';
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
// The maximum number of widgets that can be added in a room
const MAX_WIDGETS = 2;
@ -128,10 +128,7 @@ module.exports = React.createClass({
},
_launchManageIntegrations: function() {
showIntegrationsManager({
room: this.props.room,
screen: 'add_integ',
});
IntegrationManagers.sharedInstance().getPrimaryManager().open(this.props.room, 'add_integ');
},
onClickAddWidget: function(e) {

View file

@ -18,13 +18,12 @@ import {_t, _td} from '../../../languageHandler';
import AppTile from '../elements/AppTile';
import MatrixClientPeg from '../../../MatrixClientPeg';
import sdk from '../../../index';
import ScalarAuthClient from '../../../ScalarAuthClient';
import dis from '../../../dispatcher';
import AccessibleButton from '../elements/AccessibleButton';
import WidgetUtils from '../../../utils/WidgetUtils';
import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
import PersistedElement from "../elements/PersistedElement";
import { showIntegrationsManager } from '../../../integrations/integrations';
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
const widgetType = 'm.stickerpicker';
@ -67,8 +66,9 @@ export default class Stickerpicker extends React.Component {
_acquireScalarClient() {
if (this.scalarClient) return Promise.resolve(this.scalarClient);
if (ScalarAuthClient.isPossible()) {
this.scalarClient = new ScalarAuthClient();
// TODO: Pick the right manager for the widget
if (IntegrationManagers.sharedInstance().hasManager()) {
this.scalarClient = IntegrationManagers.sharedInstance().getPrimaryManager().getScalarClient();
return this.scalarClient.connect().then(() => {
this.forceUpdate();
return this.scalarClient;
@ -348,11 +348,12 @@ export default class Stickerpicker extends React.Component {
* Launch the integrations manager on the stickers integration page
*/
_launchManageIntegrations() {
showIntegrationsManager({
room: this.props.room,
screen: `type_${widgetType}`,
integrationId: this.state.widgetId,
});
// TODO: Open the right integration manager for the widget
IntegrationManagers.sharedInstance().getPrimaryManager().open(
this.props.room,
`type_${widgetType}`,
this.state.widgetId,
);
}
render() {

View file

@ -1,5 +1,5 @@
/*
Copyright 2019 New Vector Ltd
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.
@ -14,15 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import request from 'browser-request';
import url from 'url';
import React from 'react';
import {_t} from "../../../languageHandler";
import sdk from '../../../index';
import MatrixClientPeg from "../../../MatrixClientPeg";
import SdkConfig from "../../../SdkConfig";
import Field from "../elements/Field";
import Modal from '../../../Modal';
import dis from "../../../dispatcher";
/**
* If a url has no path component, etc. abbreviate it to just the hostname
@ -59,41 +58,39 @@ function unabbreviateUrl(u) {
/**
* Check an IS URL is valid, including liveness check
*
* @param {string} isUrl The url to check
* @param {string} u The url to check
* @returns {string} null if url passes all checks, otherwise i18ned error string
*/
async function checkIsUrl(isUrl) {
const parsedUrl = url.parse(isUrl);
async function checkIdentityServerUrl(u) {
const parsedUrl = url.parse(u);
if (parsedUrl.protocol !== 'https:') return _t("Identity Server URL must be HTTPS");
// XXX: duplicated logic from js-sdk but it's quite tied up in the validation logic in the
// js-sdk so probably as easy to duplicate it than to separate it out so we can reuse it
return new Promise((resolve) => {
request(
// also XXX: we don't really know whether to hit /v1 or /v2 for this: we
// probably want a /versions endpoint like the C/S API.
{ method: "GET", url: isUrl + '/_matrix/identity/api/v1' },
(err, response, body) => {
if (err) {
resolve(_t("Could not connect to ID Server"));
} else if (response.status < 200 || response.status >= 300) {
resolve(_t("Not a valid ID Server (status code %(code)s)", {code: response.status}));
} else {
resolve(null);
}
},
);
});
try {
const response = await fetch(u + '/_matrix/identity/api/v1');
if (response.ok) {
return null;
} else if (response.status < 200 || response.status >= 300) {
return _t("Not a valid Identity Server (status code %(code)s)", {code: response.status});
} else {
return _t("Could not connect to Identity Server");
}
} catch (e) {
return _t("Could not connect to Identity Server");
}
}
export default class SetIdServer extends React.Component {
constructor() {
super();
let defaultIdServer = abbreviateUrl(MatrixClientPeg.get().getIdentityServerUrl());
if (!defaultIdServer) {
defaultIdServer = abbreviateUrl(SdkConfig.get()['validated_server_config']['idServer']) || '';
let defaultIdServer = '';
if (!MatrixClientPeg.get().getIdentityServerUrl() && SdkConfig.get()['validated_server_config']['isUrl']) {
// If no ID server is configured but there's one in the config, prepopulate
// the field to help the user.
defaultIdServer = abbreviateUrl(SdkConfig.get()['validated_server_config']['isUrl']);
}
this.state = {
@ -115,7 +112,7 @@ export default class SetIdServer extends React.Component {
const InlineSpinner = sdk.getComponent('views.elements.InlineSpinner');
return <div>
<InlineSpinner />
{ _t("Checking Server") }
{ _t("Checking server") }
</div>;
} else if (this.state.error) {
return this.state.error;
@ -128,18 +125,21 @@ export default class SetIdServer extends React.Component {
return !!this.state.idServer && !this.state.busy;
};
_saveIdServer = async () => {
_saveIdServer = async (e) => {
e.preventDefault();
this.setState({busy: true});
const fullUrl = unabbreviateUrl(this.state.idServer);
const errStr = await checkIsUrl(fullUrl);
const errStr = await checkIdentityServerUrl(fullUrl);
let newFormValue = this.state.idServer;
if (!errStr) {
MatrixClientPeg.get().setIdentityServerUrl(fullUrl);
localStorage.removeItem("mx_is_access_token");
localStorage.setItem("mx_is_url", fullUrl);
dis.dispatch({action: 'id_server_changed'});
newFormValue = '';
}
this.setState({
@ -184,6 +184,7 @@ export default class SetIdServer extends React.Component {
render() {
const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton');
const Field = sdk.getComponent('elements.Field');
const idServerUrl = this.state.currentClientIdServer;
let sectionTitle;
let bodyText;
@ -198,9 +199,9 @@ export default class SetIdServer extends React.Component {
} else {
sectionTitle = _t("Identity Server");
bodyText = _t(
"You are not currently using an Identity Server. " +
"You are not currently using an identity server. " +
"To discover and be discoverable by existing contacts you know, " +
"add one below",
"add one below.",
);
}
@ -230,12 +231,12 @@ export default class SetIdServer extends React.Component {
id="mx_SetIdServer_idServer"
type="text" value={this.state.idServer} autoComplete="off"
onChange={this._onIdentityServerChanged}
tooltip={this._getTooltip()}
tooltipContent={this._getTooltip()}
/>
<input className="mx_Dialog_primary"
type="submit" value={_t("Change")}
<AccessibleButton type="submit" kind="primary_sm"
onClick={this._saveIdServer}
disabled={!this._idServerChangeEnabled()}
/>
>{_t("Change")}</AccessibleButton>
{discoSection}
</form>
);

View file

@ -1,6 +1,7 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
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 LanguageDropdown from "../../../elements/LanguageDropdown";
import AccessibleButton from "../../../elements/AccessibleButton";
import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog";
import PropTypes from "prop-types";
import {THEMES} from "../../../../../themes";
import PlatformPeg from "../../../../../PlatformPeg";
import MatrixClientPeg from "../../../../../MatrixClientPeg";
import sdk from "../../../../..";
@ -43,9 +45,22 @@ export default class GeneralUserSettingsTab extends React.Component {
this.state = {
language: languageHandler.getCurrentLanguage(),
theme: SettingsStore.getValueAt(SettingLevel.ACCOUNT, "theme"),
haveIdServer: Boolean(MatrixClientPeg.get().getIdentityServerUrl()),
};
this.dispatcherRef = dis.register(this._onAction);
}
componentWillUnmount() {
dis.unregister(this.dispatcherRef);
}
_onAction = (payload) => {
if (payload.action === 'id_server_changed') {
this.setState({haveIdServer: Boolean(MatrixClientPeg.get().getIdentityServerUrl())});
}
};
_onLanguageChange = (newLanguage) => {
if (this.state.language === newLanguage) return;
@ -122,7 +137,7 @@ export default class GeneralUserSettingsTab extends React.Component {
onFinished={this._onPasswordChanged} />
);
const threepidSection = MatrixClientPeg.get().getIdentityServerUrl() ? <div>
const threepidSection = this.state.haveIdServer ? <div>
<span className="mx_SettingsTab_subheading">{_t("Email addresses")}</span>
<EmailAddresses />
@ -160,8 +175,9 @@ export default class GeneralUserSettingsTab extends React.Component {
<span className="mx_SettingsTab_subheading">{_t("Theme")}</span>
<Field id="theme" label={_t("Theme")} element="select"
value={this.state.theme} onChange={this._onThemeChange}>
<option value="light">{_t("Light theme")}</option>
<option value="dark">{_t("Dark theme")}</option>
{Object.entries(THEMES).map(([theme, text]) => {
return <option key={theme} value={theme}>{_t(text)}</option>;
})}
</Field>
<SettingsFlag name="useCompactLayout" level={SettingLevel.ACCOUNT} />
</div>