Merge branch 'develop' into travis/tabbed-managers

This commit is contained in:
Travis Ralston 2019-08-27 09:07:52 -06:00
commit 3eddded039
45 changed files with 638 additions and 118 deletions

View file

@ -19,6 +19,7 @@ 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';
@ -39,7 +40,7 @@ const addressTypeName = {
};
module.exports = React.createClass({
module.exports = createReactClass({
displayName: "AddressPickerDialog",
propTypes: {

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

@ -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

@ -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,

View file

@ -25,6 +25,11 @@ import {autoCompleteCreator} from '../../../editor/parts';
import {renderModel} from '../../../editor/render';
import {Room} from 'matrix-js-sdk';
import TypingStore from "../../../stores/TypingStore";
import EMOJIBASE from 'emojibase-data/en/compact.json';
import SettingsStore from "../../../settings/SettingsStore";
import EMOTICON_REGEX from 'emojibase-regex/emoticon';
const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s$');
const IS_MAC = navigator.platform.indexOf("Mac") !== -1;
@ -70,6 +75,35 @@ export default class BasicMessageEditor extends React.Component {
this._modifiedFlag = false;
}
_replaceEmoticon = (caret, inputType, diff) => {
const {model} = this.props;
const range = model.startRange(caret);
// expand range max 8 characters backwards from caret,
// as a space to look for an emoticon
let n = 8;
range.expandBackwardsWhile((index, offset) => {
const part = model.parts[index];
n -= 1;
return n >= 0 && (part.type === "plain" || part.type === "pill-candidate");
});
const emoticonMatch = REGEX_EMOTICON_WHITESPACE.exec(range.text);
if (emoticonMatch) {
const query = emoticonMatch[1].toLowerCase().replace("-", "");
const data = EMOJIBASE.find(e => e.emoticon ? e.emoticon.toLowerCase() === query : false);
if (data) {
const hasPrecedingSpace = emoticonMatch[0][0] === " ";
// we need the range to only comprise of the emoticon
// because we'll replace the whole range with an emoji,
// so move the start forward to the start of the emoticon.
// Take + 1 because index is reported without the possible preceding space.
range.moveStart(emoticonMatch.index + (hasPrecedingSpace ? 1 : 0));
// this returns the amount of added/removed characters during the replace
// so the caret position can be adjusted.
return range.replace([this.props.model.partCreator.plain(data.unicode + " ")]);
}
}
}
_updateEditorState = (caret, inputType, diff) => {
renderModel(this._editorRef, this.props.model);
if (caret) {
@ -262,6 +296,9 @@ export default class BasicMessageEditor extends React.Component {
componentDidMount() {
const model = this.props.model;
model.setUpdateCallback(this._updateEditorState);
if (SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji')) {
model.setTransformCallback(this._replaceEmoticon);
}
const partCreator = model.partCreator;
// TODO: does this allow us to get rid of EditorStateTransfer?
// not really, but we could not serialize the parts, and just change the autoCompleter

View file

@ -16,6 +16,7 @@ limitations under the License.
import url from 'url';
import React from 'react';
import PropTypes from 'prop-types';
import {_t} from "../../../languageHandler";
import sdk from '../../../index';
import MatrixClientPeg from "../../../MatrixClientPeg";
@ -55,6 +56,12 @@ async function checkIdentityServerUrl(u) {
}
export default class SetIdServer extends React.Component {
static propTypes = {
// Whether or not the ID server is missing terms. This affects the text
// shown to the user.
missingTerms: PropTypes.bool,
};
constructor() {
super();
@ -153,31 +160,17 @@ export default class SetIdServer extends React.Component {
// Double check that the identity server even has terms of service.
const terms = await MatrixClientPeg.get().getTerms(SERVICE_TYPES.IS, fullUrl);
if (!terms || !terms["policies"] || Object.keys(terms["policies"]).length <= 0) {
const QuestionDialog = sdk.getComponent("views.dialogs.QuestionDialog");
Modal.createTrackedDialog('No Terms Warning', '', QuestionDialog, {
title: _t("Identity server has no terms of service"),
description: (
<div>
<span className="warning">
{_t("The identity server you have chosen does not have any terms of service.")}
</span>
<span>
&nbsp;{_t("Only continue if you trust the owner of the server.")}
</span>
</div>
),
button: _t("Continue"),
onFinished: async (confirmed) => {
if (!confirmed) return;
this._saveIdServer(fullUrl);
},
});
this._showNoTermsWarning(fullUrl);
return;
}
this._saveIdServer(fullUrl);
} catch (e) {
console.error(e);
if (e.cors === "rejected" || e.httpStatus === 404) {
this._showNoTermsWarning(fullUrl);
return;
}
errStr = _t("Terms of service not accepted or the identity server is invalid.");
}
}
@ -190,6 +183,28 @@ export default class SetIdServer extends React.Component {
});
};
_showNoTermsWarning(fullUrl) {
const QuestionDialog = sdk.getComponent("views.dialogs.QuestionDialog");
Modal.createTrackedDialog('No Terms Warning', '', QuestionDialog, {
title: _t("Identity server has no terms of service"),
description: (
<div>
<span className="warning">
{_t("The identity server you have chosen does not have any terms of service.")}
</span>
<span>
&nbsp;{_t("Only continue if you trust the owner of the server.")}
</span>
</div>
),
button: _t("Continue"),
onFinished: async (confirmed) => {
if (!confirmed) return;
this._saveIdServer(fullUrl);
},
});
}
_onDisconnectClicked = async () => {
this.setState({disconnectBusy: true});
try {
@ -266,6 +281,13 @@ export default class SetIdServer extends React.Component {
{},
{ server: sub => <b>{abbreviateUrl(idServerUrl)}</b> },
);
if (this.props.missingTerms) {
bodyText = _t(
"If you don't want to use <server /> to discover and be discoverable by existing " +
"contacts you know, enter another identity server below.",
{}, {server: sub => <b>{abbreviateUrl(idServerUrl)}</b>},
);
}
} else {
sectionTitle = _t("Identity Server");
bodyText = _t(
@ -278,16 +300,25 @@ export default class SetIdServer extends React.Component {
let discoSection;
if (idServerUrl) {
let discoButtonContent = _t("Disconnect");
let discoBodyText = _t(
"Disconnecting from your identity server will mean you " +
"won't be discoverable by other users and you won't be " +
"able to invite others by email or phone.",
);
if (this.props.missingTerms) {
discoBodyText = _t(
"Using an identity server is optional. If you choose not to " +
"use an identity server, you won't be discoverable by other users " +
"and you won't be able to invite others by email or phone.",
);
discoButtonContent = _t("Do not use an identity server");
}
if (this.state.disconnectBusy) {
const InlineSpinner = sdk.getComponent('views.elements.InlineSpinner');
discoButtonContent = <InlineSpinner />;
}
discoSection = <div>
<span className="mx_SettingsTab_subsectionText">{_t(
"Disconnecting from your identity server will mean you " +
"won't be discoverable by other users and you won't be " +
"able to invite others by email or phone.",
)}</span>
<span className="mx_SettingsTab_subsectionText">{discoBodyText}</span>
<AccessibleButton onClick={this._onDisconnectClicked} kind="danger_sm">
{discoButtonContent}
</AccessibleButton>

View file

@ -249,6 +249,8 @@ export default class GeneralUserSettingsTab extends React.Component {
}
_renderDiscoverySection() {
const SetIdServer = sdk.getComponent("views.settings.SetIdServer");
if (this.state.requiredPolicyInfo.hasTerms) {
const InlineTermsAgreement = sdk.getComponent("views.terms.InlineTermsAgreement");
const intro = <span className="mx_SettingsTab_subsectionText">
@ -258,17 +260,22 @@ export default class GeneralUserSettingsTab extends React.Component {
{serverName: this.state.idServerName},
)}
</span>;
return <InlineTermsAgreement
policiesAndServicePairs={this.state.requiredPolicyInfo.policiesAndServices}
agreedUrls={this.state.requiredPolicyInfo.agreedUrls}
onFinished={this.state.requiredPolicyInfo.resolve}
introElement={intro}
/>;
return (
<div>
<InlineTermsAgreement
policiesAndServicePairs={this.state.requiredPolicyInfo.policiesAndServices}
agreedUrls={this.state.requiredPolicyInfo.agreedUrls}
onFinished={this.state.requiredPolicyInfo.resolve}
introElement={intro}
/>
{ /* has its own heading as it includes the current ID server */ }
<SetIdServer missingTerms={true} />
</div>
);
}
const EmailAddresses = sdk.getComponent("views.settings.discovery.EmailAddresses");
const PhoneNumbers = sdk.getComponent("views.settings.discovery.PhoneNumbers");
const SetIdServer = sdk.getComponent("views.settings.SetIdServer");
const threepidSection = this.state.haveIdServer ? <div className='mx_GeneralUserSettingsTab_discovery'>
<span className="mx_SettingsTab_subheading">{_t("Email addresses")}</span>