Merge branches 'develop' and 't3chguy/report_event' of https://github.com/matrix-org/matrix-react-sdk into t3chguy/report_event

 Conflicts:
	src/i18n/strings/en_EN.json
This commit is contained in:
Michael Telatynski 2019-09-12 12:45:16 +01:00
commit 9a15d4cfc1
305 changed files with 8157 additions and 1832 deletions

View file

@ -19,15 +19,19 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { _t, _td } from '../../../languageHandler';
import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg';
import dis from '../../../dispatcher';
import Promise from 'bluebird';
import { addressTypes, getAddressType } from '../../../UserAddress.js';
import GroupStore from '../../../stores/GroupStore';
import * as Email from '../../../email';
import IdentityAuthClient from '../../../IdentityAuthClient';
import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from '../../../utils/IdentityServerUtils';
import { abbreviateUrl } from '../../../utils/UrlUtils';
const TRUNCATE_QUERY_LIST = 40;
const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200;
@ -39,7 +43,7 @@ const addressTypeName = {
};
module.exports = React.createClass({
module.exports = createReactClass({
displayName: "AddressPickerDialog",
propTypes: {
@ -48,7 +52,7 @@ module.exports = React.createClass({
// Extra node inserted after picker input, dropdown and errors
extraNode: PropTypes.node,
value: PropTypes.string,
placeholder: PropTypes.string,
placeholder: PropTypes.oneOfType(PropTypes.string, PropTypes.func),
roomId: PropTypes.string,
button: PropTypes.string,
focus: PropTypes.bool,
@ -90,6 +94,9 @@ module.exports = React.createClass({
// List of UserAddressType objects representing the set of
// auto-completion results for the current search query.
suggestedList: [],
// List of address types initialised from props, but may change while the
// dialog is open.
validAddressTypes: this.props.validAddressTypes,
};
},
@ -100,6 +107,15 @@ module.exports = React.createClass({
}
},
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() {
let selectedList = this.state.selectedList.slice();
// Check the text input field to see if user has an unconverted address
@ -433,7 +449,7 @@ module.exports = React.createClass({
// This is important, otherwise there's no way to invite
// a perfectly valid address if there are close matches.
const addrType = getAddressType(query);
if (this.props.validAddressTypes.includes(addrType)) {
if (this.state.validAddressTypes.includes(addrType)) {
if (addrType === 'email' && !Email.looksValid(query)) {
this.setState({searchError: _t("That doesn't look like a valid email address")});
return;
@ -469,7 +485,7 @@ module.exports = React.createClass({
isKnown: false,
};
if (!this.props.validAddressTypes.includes(addrType)) {
if (!this.state.validAddressTypes.includes(addrType)) {
hasError = true;
} else if (addrType === 'mx-user-id') {
const user = MatrixClientPeg.get().getUser(addrObj.address);
@ -570,12 +586,37 @@ module.exports = React.createClass({
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() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const AddressSelector = sdk.getComponent("elements.AddressSelector");
this.scrollElement = null;
let inputLabel;
if (this.props.description) {
inputLabel = <div className="mx_AddressPickerDialog_label">
<label htmlFor="textinput">{this.props.description}</label>
</div>;
}
const query = [];
// create the invite list
if (this.state.selectedList.length > 0) {
@ -602,7 +643,7 @@ module.exports = React.createClass({
ref="textinput"
className="mx_AddressPickerDialog_input"
onChange={this.onQueryChanged}
placeholder={this.props.placeholder}
placeholder={this.getPlaceholder()}
defaultValue={this.props.value}
autoFocus={this.props.focus}>
</textarea>,
@ -613,7 +654,7 @@ module.exports = React.createClass({
let error;
let addressSelector;
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">
{ _t("You have entered an invalid address.") }
<br />
@ -636,17 +677,43 @@ module.exports = React.createClass({
);
}
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 (
<BaseDialog className="mx_AddressPickerDialog" onKeyDown={this.onKeyDown}
onFinished={this.props.onFinished} title={this.props.title}>
<div className="mx_AddressPickerDialog_label">
<label htmlFor="textinput">{ this.props.description }</label>
</div>
{inputLabel}
<div className="mx_Dialog_content">
<div className="mx_AddressPickerDialog_inputContainer">{ query }</div>
{ error }
{ addressSelector }
{ this.props.extraNode }
{ identityServer }
</div>
<DialogButtons primaryButton={this.props.button}
onPrimaryButtonClick={this.onButtonClick}

View file

@ -16,12 +16,13 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import {SettingLevel} from "../../../settings/SettingsStore";
import SettingsStore from "../../../settings/SettingsStore";
export default React.createClass({
export default createReactClass({
propTypes: {
unknownProfileUsers: PropTypes.array.isRequired, // [ {userId, errorText}... ]
onInviteAnyways: PropTypes.func.isRequired,

View file

@ -16,6 +16,7 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import FocusTrap from 'focus-trap-react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
@ -32,7 +33,7 @@ import MatrixClientPeg from '../../../MatrixClientPeg';
* Includes a div for the title, and a keypress handler which cancels the
* dialog on escape.
*/
export default React.createClass({
export default createReactClass({
displayName: 'BaseDialog',
propTypes: {

View file

@ -15,13 +15,14 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
/*
* A dialog for confirming a redaction.
*/
export default React.createClass({
export default createReactClass({
displayName: 'ConfirmRedactDialog',
render: function() {

View file

@ -15,6 +15,7 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import { MatrixClient } from 'matrix-js-sdk';
import sdk from '../../../index';
@ -29,7 +30,7 @@ import { GroupMemberType } from '../../../groups';
* to make it obvious what is going to happen.
* Also tweaks the style for 'dangerous' actions (albeit only with colour)
*/
export default React.createClass({
export default createReactClass({
displayName: 'ConfirmUserActionDialog',
propTypes: {
// matrix-js-sdk (room) member object. Supply either this or 'groupMember'

View file

@ -15,13 +15,14 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import dis from '../../../dispatcher';
import { _t } from '../../../languageHandler';
import MatrixClientPeg from '../../../MatrixClientPeg';
export default React.createClass({
export default createReactClass({
displayName: 'CreateGroupDialog',
propTypes: {
onFinished: PropTypes.func.isRequired,

View file

@ -15,12 +15,13 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import SdkConfig from '../../../SdkConfig';
import { _t } from '../../../languageHandler';
export default React.createClass({
export default createReactClass({
displayName: 'CreateRoomDialog',
propTypes: {
onFinished: PropTypes.func.isRequired,

View file

@ -2,6 +2,7 @@
Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 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.
@ -241,6 +242,16 @@ export default class DeviceVerifyDialog extends React.Component {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton');
let text;
if (MatrixClientPeg.get().getUserId() === this.props.userId) {
text = _t("To verify that this device can be trusted, please check that the key you see " +
"in User Settings on that device matches the key below:");
} else {
text = _t("To verify that this device can be trusted, please contact its owner using some other " +
"means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings " +
"for this device matches the key below:");
}
const key = FormattingUtils.formatCryptoKey(this.props.device.getFingerprint());
const body = (
<div>
@ -250,10 +261,7 @@ export default class DeviceVerifyDialog extends React.Component {
{_t("Use two-way text verification")}
</AccessibleButton>
<p>
{ _t("To verify that this device can be trusted, please contact its " +
"owner using some other means (e.g. in person or a phone call) " +
"and ask them whether the key they see in their User Settings " +
"for this device matches the key below:") }
{ text }
</p>
<div className="mx_DeviceVerifyDialog_cryptoSection">
<ul>

View file

@ -26,11 +26,12 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
export default React.createClass({
export default createReactClass({
displayName: 'ErrorDialog',
propTypes: {
title: PropTypes.string,

View file

@ -17,12 +17,13 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import classNames from "classnames";
export default React.createClass({
export default createReactClass({
displayName: 'InfoDialog',
propTypes: {
className: PropTypes.string,

View file

@ -16,6 +16,7 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import sdk from '../../../index';
@ -23,7 +24,7 @@ import { _t } from '../../../languageHandler';
import AccessibleButton from '../elements/AccessibleButton';
export default React.createClass({
export default createReactClass({
displayName: 'InteractiveAuthDialog',
propTypes: {

View file

@ -16,6 +16,7 @@ limitations under the License.
import Modal from '../../../Modal';
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import sdk from '../../../index';
@ -29,7 +30,7 @@ import { _t, _td } from '../../../languageHandler';
* should not, and `undefined` if the dialog is cancelled. (In other words:
* truthy: do the key share. falsy: don't share the keys).
*/
export default React.createClass({
export default createReactClass({
propTypes: {
matrixClient: PropTypes.object.isRequired,
userId: PropTypes.string.isRequired,

View file

@ -16,11 +16,12 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
export default React.createClass({
export default createReactClass({
displayName: 'QuestionDialog',
propTypes: {
title: PropTypes.string,

View file

@ -15,13 +15,14 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg';
import Modal from '../../../Modal';
import { _t } from '../../../languageHandler';
export default React.createClass({
export default createReactClass({
displayName: 'RoomUpgradeDialog',
propTypes: {

View file

@ -16,6 +16,7 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import SdkConfig from '../../../SdkConfig';
@ -23,7 +24,7 @@ import Modal from '../../../Modal';
import { _t } from '../../../languageHandler';
export default React.createClass({
export default createReactClass({
displayName: 'SessionRestoreErrorDialog',
propTypes: {

View file

@ -16,6 +16,7 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import Email from '../../../email';
@ -29,7 +30,7 @@ import Modal from '../../../Modal';
*
* On success, `onFinished(true)` is called.
*/
export default React.createClass({
export default createReactClass({
displayName: 'SetEmailDialog',
propTypes: {
onFinished: PropTypes.func.isRequired,

View file

@ -17,6 +17,7 @@ limitations under the License.
import Promise from 'bluebird';
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg';
@ -34,7 +35,7 @@ const USERNAME_CHECK_DEBOUNCE_MS = 250;
*
* On success, `onFinished(true, newDisplayName)` is called.
*/
export default React.createClass({
export default createReactClass({
displayName: 'SetMxIdDialog',
propTypes: {
onFinished: PropTypes.func.isRequired,

View file

@ -17,6 +17,7 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
@ -62,7 +63,7 @@ const WarmFuzzy = function(props) {
*
* On success, `onFinished()` when finished
*/
export default React.createClass({
export default createReactClass({
displayName: 'SetPasswordDialog',
propTypes: {
onFinished: PropTypes.func.isRequired,

View file

@ -0,0 +1,172 @@
/*
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 React from 'react';
import PropTypes from 'prop-types';
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
import {Room} from "matrix-js-sdk";
import sdk from '../../../index';
import {dialogTermsInteractionCallback, TermsNotSignedError} from "../../../Terms";
import classNames from 'classnames';
import ScalarMessaging from "../../../ScalarMessaging";
export default class TabbedIntegrationManagerDialog extends React.Component {
static propTypes = {
/**
* Called with:
* * success {bool} True if the user accepted any douments, false if cancelled
* * agreedUrls {string[]} List of agreed URLs
*/
onFinished: PropTypes.func.isRequired,
/**
* Optional room where the integration manager should be open to
*/
room: PropTypes.instanceOf(Room),
/**
* Optional screen to open on the integration manager
*/
screen: PropTypes.string,
/**
* Optional integration ID to open in the integration manager
*/
integrationId: PropTypes.string,
};
constructor(props) {
super(props);
this.state = {
managers: IntegrationManagers.sharedInstance().getOrderedManagers(),
busy: true,
currentIndex: 0,
currentConnected: false,
currentLoading: true,
currentScalarClient: null,
};
}
componentDidMount(): void {
this.openManager(0, true);
}
openManager = async (i: number, force = false) => {
if (i === this.state.currentIndex && !force) return;
const manager = this.state.managers[i];
const client = manager.getScalarClient();
this.setState({
busy: true,
currentIndex: i,
currentLoading: true,
currentConnected: false,
currentScalarClient: client,
});
ScalarMessaging.setOpenManagerUrl(manager.uiUrl);
client.setTermsInteractionCallback((policyInfo, agreedUrls) => {
// To avoid visual glitching of two modals stacking briefly, we customise the
// terms dialog sizing when it will appear for the integrations manager so that
// it gets the same basic size as the IM's own modal.
return dialogTermsInteractionCallback(
policyInfo, agreedUrls, 'mx_TermsDialog_forIntegrationsManager',
);
});
try {
await client.connect();
if (!client.hasCredentials()) {
this.setState({
busy: false,
currentLoading: false,
currentConnected: false,
});
} else {
this.setState({
busy: false,
currentLoading: false,
currentConnected: true,
});
}
} catch (e) {
if (e instanceof TermsNotSignedError) {
return;
}
console.error(e);
this.setState({
busy: false,
currentLoading: false,
currentConnected: false,
});
}
};
_renderTabs() {
const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton");
return this.state.managers.map((m, i) => {
const classes = classNames({
'mx_TabbedIntegrationManagerDialog_tab': true,
'mx_TabbedIntegrationManagerDialog_currentTab': this.state.currentIndex === i,
});
return (
<AccessibleButton
className={classes}
onClick={() => this.openManager(i)}
key={`tab_${i}`}
disabled={this.state.busy}
>
{m.name}
</AccessibleButton>
);
});
}
_renderTab() {
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
let uiUrl = null;
if (this.state.currentScalarClient) {
uiUrl = this.state.currentScalarClient.getScalarInterfaceUrlForRoom(
this.props.room,
this.props.screen,
this.props.integrationId,
);
}
return <IntegrationsManager
configured={true}
loading={this.state.currentLoading}
connected={this.state.currentConnected}
url={uiUrl}
onFinished={() => {/* no-op */}}
/>;
}
render() {
return (
<div className='mx_TabbedIntegrationManagerDialog_container'>
<div className='mx_TabbedIntegrationManagerDialog_tabs'>
{this._renderTabs()}
</div>
<div className='mx_TabbedIntegrationManagerDialog_currentManager'>
{this._renderTab()}
</div>
</div>
);
}
}

View file

@ -15,10 +15,11 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import sdk from '../../../index';
export default React.createClass({
export default createReactClass({
displayName: 'TextInputDialog',
propTypes: {
title: PropTypes.string,

View file

@ -16,11 +16,10 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg';
import GeminiScrollbar from 'react-gemini-scrollbar';
import Resend from '../../../Resend';
import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
import { markAllDevicesKnown } from '../../../cryptodevices';
@ -67,7 +66,7 @@ UnknownDeviceList.propTypes = {
};
export default React.createClass({
export default createReactClass({
displayName: 'UnknownDeviceDialog',
propTypes: {

View file

@ -15,6 +15,7 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import sdk from '../../../../index';
import MatrixClientPeg from '../../../../MatrixClientPeg';
import Modal from '../../../../Modal';
@ -29,7 +30,7 @@ const RESTORE_TYPE_RECOVERYKEY = 1;
/**
* Dialog for restoring e2e keys from a backup and the user's recovery key
*/
export default React.createClass({
export default createReactClass({
getInitialState: function() {
return {
backupInfo: null,