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

This commit is contained in:
David Baker 2018-11-20 18:08:39 +00:00
commit 8ccaf53d20
47 changed files with 2578 additions and 205 deletions

View file

@ -23,6 +23,8 @@ import request from 'browser-request';
import { _t } from '../../languageHandler';
import sanitizeHtml from 'sanitize-html';
import sdk from '../../index';
import { MatrixClient } from 'matrix-js-sdk';
import dis from '../../dispatcher';
class HomePage extends React.Component {
static displayName = 'HomePage';
@ -37,6 +39,10 @@ class HomePage extends React.Component {
homePageUrl: PropTypes.string,
};
static contextTypes = {
matrixClient: PropTypes.instanceOf(MatrixClient),
};
state = {
iframeSrc: '',
page: '',
@ -85,10 +91,47 @@ class HomePage extends React.Component {
this._unmounted = true;
}
onLoginClick() {
dis.dispatch({ action: 'start_login' });
}
onRegisterClick() {
dis.dispatch({ action: 'start_registration' });
}
render() {
let guestWarning = "";
if (this.context.matrixClient.isGuest()) {
guestWarning = (
<div className="mx_HomePage_guest_warning">
<img src="img/warning.svg" width="24" height="23" />
<div>
<div>
{ _t("You are currently using Riot anonymously as a guest.") }
</div>
<div>
{ _t(
'If you would like to create a Matrix account you can <a>register</a> now.',
{},
{ 'a': (sub) => <a href="#" onClick={this.onRegisterClick}>{ sub }</a> },
) }
</div>
<div>
{ _t(
'If you already have a Matrix account you can <a>log in</a> instead.',
{},
{ 'a': (sub) => <a href="#" onClick={this.onLoginClick}>{ sub }</a> },
) }
</div>
</div>
</div>
);
}
if (this.state.iframeSrc) {
return (
<div className="mx_HomePage">
{ guestWarning }
<iframe src={ this.state.iframeSrc } />
</div>
);
@ -96,6 +139,7 @@ class HomePage extends React.Component {
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
return (
<GeminiScrollbarWrapper autoshow={true} className="mx_HomePage">
{ guestWarning }
<div className="mx_HomePage_body" dangerouslySetInnerHTML={{ __html: this.state.page }}>
</div>
</GeminiScrollbarWrapper>

View file

@ -68,6 +68,11 @@ export default React.createClass({
// If true, poll to see if the auth flow has been completed
// out-of-band
poll: PropTypes.bool,
// If true, components will be told that the 'Continue' button
// is managed by some other party and should not be managed by
// the component itself.
continueIsManaged: PropTypes.bool,
},
getInitialState: function() {
@ -128,6 +133,12 @@ export default React.createClass({
}
},
tryContinue: function() {
if (this.refs.stageComponent && this.refs.stageComponent.tryContinue) {
this.refs.stageComponent.tryContinue();
}
},
_authStateUpdated: function(stageType, stageState) {
const oldStage = this.state.authStage;
this.setState({
@ -192,6 +203,7 @@ export default React.createClass({
fail={this._onAuthStageFailed}
setEmailSid={this._setEmailSid}
makeRegistrationUrl={this.props.makeRegistrationUrl}
showContinue={!this.props.continueIsManaged}
/>
);
},

View file

@ -82,6 +82,8 @@ const SIMPLE_SETTINGS = [
{ id: "TagPanel.disableTagPanel" },
{ id: "enableWidgetScreenshots" },
{ id: "RoomSubList.showEmpty" },
{ id: "pinMentionedRooms" },
{ id: "pinUnreadRooms" },
{ id: "showDeveloperTools" },
];

View file

@ -84,7 +84,10 @@ module.exports = React.createClass({
// letting you do that login type
this._stepRendererMap = {
'm.login.password': this._renderPasswordStep,
'm.login.cas': this._renderCasStep,
// CAS and SSO are the same thing, modulo the url we link to
'm.login.cas': () => this._renderSsoStep(this._loginLogic.getSsoLoginUrl("cas")),
'm.login.sso': () => this._renderSsoStep(this._loginLogic.getSsoLoginUrl("sso")),
};
this._initLoginLogic();
@ -186,10 +189,6 @@ module.exports = React.createClass({
}).done();
},
onCasLogin: function() {
this._loginLogic.redirectToCas();
},
_onLoginAsGuestClick: function() {
const self = this;
self.setState({
@ -403,10 +402,9 @@ module.exports = React.createClass({
);
},
_renderCasStep: function() {
const CasLogin = sdk.getComponent('login.CasLogin');
_renderSsoStep: function(url) {
return (
<CasLogin onSubmit={this.onCasLogin} />
<a href={url} className="mx_Login_sso_link">{ _t('Sign in with single sign-on') }</a>
);
},

View file

@ -0,0 +1,71 @@
/*
Copyright 2018 New Vector Ltd
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 sdk from '../../../index';
import dis from '../../../dispatcher';
import { _t } from '../../../languageHandler';
import Modal from '../../../Modal';
export default (props) => {
const _onLogoutClicked = () => {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createTrackedDialog('Logout e2e db too new', '', QuestionDialog, {
title: _t("Sign out"),
description: _t(
"To avoid losing your chat history, you must export your room keys " +
"before logging out. You will need to go back to the newer version of " +
"Riot to do this",
),
button: _t("Sign out"),
focus: false,
onFinished: (doLogout) => {
if (doLogout) {
dis.dispatch({action: 'logout'});
props.onFinished();
}
},
});
};
const description =
_t("You've previously used a newer version of Riot on %(host)s. " +
"To use this version again with end to end encryption, you will " +
"need to sign out and back in again. ",
{host: props.host},
);
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return (<BaseDialog className="mx_CryptoStoreTooNewDialog"
contentId='mx_Dialog_content'
title={_t("Incompatible Database")}
hasCancel={false}
onFinished={props.onFinished}
>
<div className="mx_Dialog_content" id='mx_Dialog_content'>
{ description }
</div>
<DialogButtons primaryButton={_t('Continue With Encryption Disabled')}
hasCancel={false}
onPrimaryButtonClick={props.onFinished}
>
<button onClick={_onLogoutClicked} >
{ _t('Sign out') }
</button>
</DialogButtons>
</BaseDialog>);
};

View file

@ -101,6 +101,9 @@ export default React.createClass({
},
onSubmit: function(ev) {
if (this.refs.uiAuth) {
this.refs.uiAuth.tryContinue();
}
this.setState({
doingUIAuth: true,
});
@ -217,6 +220,8 @@ export default React.createClass({
onAuthFinished={this._onUIAuthFinished}
inputs={{}}
poll={true}
ref="uiAuth"
continueIsManaged={true}
/>;
}
const inputClasses = classnames({

View file

@ -1,38 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
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.
*/
'use strict';
import React from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
displayName: 'CasLogin',
propTypes: {
onSubmit: PropTypes.func, // fn()
},
render: function() {
return (
<div>
<button onClick={this.props.onSubmit}>{ _t("Sign in with CAS") }</button>
</div>
);
},
});

View file

@ -222,6 +222,7 @@ export const TermsAuthEntry = React.createClass({
stageParams: PropTypes.object.isRequired,
errorText: PropTypes.string,
busy: PropTypes.bool,
showContinue: PropTypes.bool,
},
componentWillMount: function() {
@ -275,19 +276,30 @@ export const TermsAuthEntry = React.createClass({
});
},
_trySubmit: function(policyId) {
tryContinue: function() {
this._trySubmit();
},
_togglePolicy: function(policyId) {
const newToggles = {};
let allChecked = true;
for (const policy of this.state.policies) {
let checked = this.state.toggledPolicies[policy.id];
if (policy.id === policyId) checked = !checked;
newToggles[policy.id] = checked;
}
this.setState({"toggledPolicies": newToggles});
},
_trySubmit: function() {
let allChecked = true;
for (const policy of this.state.policies) {
let checked = this.state.toggledPolicies[policy.id];
allChecked = allChecked && checked;
}
this.setState({"toggledPolicies": newToggles});
if (allChecked) this.props.submitAuthDict({type: TermsAuthEntry.LOGIN_TYPE});
else this.setState({errorText: _t("Please review and accept all of the homeserver's policies")});
},
render: function() {
@ -303,27 +315,35 @@ export const TermsAuthEntry = React.createClass({
allChecked = allChecked && checked;
checkboxes.push(
<label key={"policy_checkbox_" + policy.id}>
<input type="checkbox" onClick={() => this._trySubmit(policy.id)} checked={checked} />
<label key={"policy_checkbox_" + policy.id} className="mx_InteractiveAuthEntryComponents_termsPolicy">
<input type="checkbox" onClick={() => this._togglePolicy(policy.id)} checked={checked} />
<a href={policy.url} target="_blank" rel="noopener">{ policy.name }</a>
</label>,
);
}
let errorSection;
if (this.props.errorText) {
if (this.props.errorText || this.state.errorText) {
errorSection = (
<div className="error" role="alert">
{ this.props.errorText }
{ this.props.errorText || this.state.errorText }
</div>
);
}
let submitButton;
if (this.props.showContinue !== false) {
// XXX: button classes
submitButton = <button className="mx_InteractiveAuthEntryComponents_termsSubmit mx_UserSettings_button"
onClick={this._trySubmit} disabled={!allChecked}>{_t("Accept")}</button>;
}
return (
<div>
<p>{_t("Please review and accept the policies of this homeserver:")}</p>
{ checkboxes }
{ errorSection }
{ submitButton }
</div>
);
},

View file

@ -278,6 +278,7 @@ export default class MImageBody extends React.Component {
let img = null;
let placeholder = null;
let gifLabel = null;
// e2e image hasn't been decrypted yet
if (content.file !== undefined && this.state.decryptedUrl === null) {
@ -302,11 +303,14 @@ export default class MImageBody extends React.Component {
onMouseLeave={this.onImageLeave} />;
}
if (this._isGif() && !SettingsStore.getValue("autoplayGifsAndVideos") && !this.state.hover) {
gifLabel = <p className="mx_MImageBody_gifLabel">GIF</p>;
}
const thumbnail = (
<div className="mx_MImageBody_thumbnail_container" style={{ maxHeight: maxHeight + "px" }} >
{ /* Calculate aspect ratio, using %padding will size _container correctly */ }
<div style={{ paddingBottom: (100 * infoHeight / infoWidth) + '%' }} />
{ showPlaceholder &&
<div className="mx_MImageBody_thumbnail" style={{
// Constrain width here so that spinner appears central to the loaded thumbnail
@ -320,6 +324,7 @@ export default class MImageBody extends React.Component {
<div style={{display: !showPlaceholder ? undefined : 'none'}}>
{ img }
{ gifLabel }
</div>
{ this.state.hover && this.getTooltip() }