Merge branch 'develop' into gsouquet/fix-backdrop-filter
This commit is contained in:
commit
fbac730c62
16 changed files with 268 additions and 262 deletions
2
src/@types/global.d.ts
vendored
2
src/@types/global.d.ts
vendored
|
@ -46,6 +46,7 @@ import { VoiceRecordingStore } from "../stores/VoiceRecordingStore";
|
|||
import PerformanceMonitor from "../performance";
|
||||
import UIStore from "../stores/UIStore";
|
||||
import { SetupEncryptionStore } from "../stores/SetupEncryptionStore";
|
||||
import { RoomScrollStateStore } from "../stores/RoomScrollStateStore";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
@ -87,6 +88,7 @@ declare global {
|
|||
mxPerformanceEntryNames: any;
|
||||
mxUIStore: UIStore;
|
||||
mxSetupEncryptionStore?: SetupEncryptionStore;
|
||||
mxRoomScrollStateStore?: RoomScrollStateStore;
|
||||
}
|
||||
|
||||
interface Document {
|
||||
|
|
|
@ -42,7 +42,7 @@ import eventSearch, { searchPagination } from '../../Searching';
|
|||
import MainSplit from './MainSplit';
|
||||
import RightPanel from './RightPanel';
|
||||
import RoomViewStore from '../../stores/RoomViewStore';
|
||||
import RoomScrollStateStore from '../../stores/RoomScrollStateStore';
|
||||
import RoomScrollStateStore, { ScrollState } from '../../stores/RoomScrollStateStore';
|
||||
import WidgetEchoStore from '../../stores/WidgetEchoStore';
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import { Layout } from "../../settings/Layout";
|
||||
|
@ -1577,7 +1577,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
// get the current scroll position of the room, so that it can be
|
||||
// restored when we switch back to it.
|
||||
//
|
||||
private getScrollState() {
|
||||
private getScrollState(): ScrollState {
|
||||
const messagePanel = this.messagePanel;
|
||||
if (!messagePanel) return null;
|
||||
|
||||
|
|
|
@ -15,39 +15,42 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import * as sdk from '../../../index';
|
||||
import { SetupEncryptionStore, Phase } from '../../../stores/SetupEncryptionStore';
|
||||
import SetupEncryptionBody from "./SetupEncryptionBody";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("structures.auth.CompleteSecurity")
|
||||
export default class CompleteSecurity extends React.Component {
|
||||
static propTypes = {
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
};
|
||||
interface IProps {
|
||||
onFinished: () => void;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
interface IState {
|
||||
phase: Phase;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.auth.CompleteSecurity")
|
||||
export default class CompleteSecurity extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
const store = SetupEncryptionStore.sharedInstance();
|
||||
store.on("update", this._onStoreUpdate);
|
||||
store.on("update", this.onStoreUpdate);
|
||||
store.start();
|
||||
this.state = { phase: store.phase };
|
||||
}
|
||||
|
||||
_onStoreUpdate = () => {
|
||||
private onStoreUpdate = (): void => {
|
||||
const store = SetupEncryptionStore.sharedInstance();
|
||||
this.setState({ phase: store.phase });
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
public componentWillUnmount(): void {
|
||||
const store = SetupEncryptionStore.sharedInstance();
|
||||
store.off("update", this._onStoreUpdate);
|
||||
store.off("update", this.onStoreUpdate);
|
||||
store.stop();
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const AuthPage = sdk.getComponent("auth.AuthPage");
|
||||
const CompleteSecurityBody = sdk.getComponent("auth.CompleteSecurityBody");
|
||||
const { phase } = this.state;
|
|
@ -15,20 +15,19 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import AuthPage from '../../views/auth/AuthPage';
|
||||
import CompleteSecurityBody from '../../views/auth/CompleteSecurityBody';
|
||||
import CreateCrossSigningDialog from '../../views/dialogs/security/CreateCrossSigningDialog';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("structures.auth.E2eSetup")
|
||||
export default class E2eSetup extends React.Component {
|
||||
static propTypes = {
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
accountPassword: PropTypes.string,
|
||||
tokenLogin: PropTypes.bool,
|
||||
};
|
||||
interface IProps {
|
||||
onFinished: () => void;
|
||||
accountPassword?: string;
|
||||
tokenLogin?: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.auth.E2eSetup")
|
||||
export default class E2eSetup extends React.Component<IProps> {
|
||||
render() {
|
||||
return (
|
||||
<AuthPage>
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t, _td } from '../../../languageHandler';
|
||||
import * as sdk from '../../../index';
|
||||
import Modal from "../../../Modal";
|
||||
|
@ -31,27 +30,50 @@ import PassphraseField from '../../views/auth/PassphraseField';
|
|||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { PASSWORD_MIN_SCORE } from '../../views/auth/RegistrationForm';
|
||||
|
||||
// Phases
|
||||
// Show the forgot password inputs
|
||||
const PHASE_FORGOT = 1;
|
||||
// Email is in the process of being sent
|
||||
const PHASE_SENDING_EMAIL = 2;
|
||||
// Email has been sent
|
||||
const PHASE_EMAIL_SENT = 3;
|
||||
// User has clicked the link in email and completed reset
|
||||
const PHASE_DONE = 4;
|
||||
import { IValidationResult } from "../../views/elements/Validation";
|
||||
|
||||
enum Phase {
|
||||
// Show the forgot password inputs
|
||||
Forgot = 1,
|
||||
// Email is in the process of being sent
|
||||
SendingEmail = 2,
|
||||
// Email has been sent
|
||||
EmailSent = 3,
|
||||
// User has clicked the link in email and completed reset
|
||||
Done = 4,
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
serverConfig: ValidatedServerConfig;
|
||||
onServerConfigChange: () => void;
|
||||
onLoginClick?: () => void;
|
||||
onComplete: () => void;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
phase: Phase;
|
||||
email: string;
|
||||
password: string;
|
||||
password2: string;
|
||||
errorText: string;
|
||||
|
||||
// We perform liveliness checks later, but for now suppress the errors.
|
||||
// We also track the server dead errors independently of the regular errors so
|
||||
// that we can render it differently, and override any other error the user may
|
||||
// be seeing.
|
||||
serverIsAlive: boolean;
|
||||
serverErrorIsFatal: boolean;
|
||||
serverDeadError: string;
|
||||
|
||||
passwordFieldValid: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.auth.ForgotPassword")
|
||||
export default class ForgotPassword extends React.Component {
|
||||
static propTypes = {
|
||||
serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired,
|
||||
onServerConfigChange: PropTypes.func.isRequired,
|
||||
onLoginClick: PropTypes.func,
|
||||
onComplete: PropTypes.func.isRequired,
|
||||
};
|
||||
export default class ForgotPassword extends React.Component<IProps, IState> {
|
||||
private reset: PasswordReset;
|
||||
|
||||
state = {
|
||||
phase: PHASE_FORGOT,
|
||||
phase: Phase.Forgot,
|
||||
email: "",
|
||||
password: "",
|
||||
password2: "",
|
||||
|
@ -64,30 +86,31 @@ export default class ForgotPassword extends React.Component {
|
|||
serverIsAlive: true,
|
||||
serverErrorIsFatal: false,
|
||||
serverDeadError: "",
|
||||
passwordFieldValid: false,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
CountlyAnalytics.instance.track("onboarding_forgot_password_begin");
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
public componentDidMount() {
|
||||
this.reset = null;
|
||||
this._checkServerLiveliness(this.props.serverConfig);
|
||||
this.checkServerLiveliness(this.props.serverConfig);
|
||||
}
|
||||
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
// eslint-disable-next-line camelcase
|
||||
UNSAFE_componentWillReceiveProps(newProps) {
|
||||
public UNSAFE_componentWillReceiveProps(newProps: IProps): void {
|
||||
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
|
||||
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
|
||||
|
||||
// Do a liveliness check on the new URLs
|
||||
this._checkServerLiveliness(newProps.serverConfig);
|
||||
this.checkServerLiveliness(newProps.serverConfig);
|
||||
}
|
||||
|
||||
async _checkServerLiveliness(serverConfig) {
|
||||
private async checkServerLiveliness(serverConfig): Promise<void> {
|
||||
try {
|
||||
await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(
|
||||
serverConfig.hsUrl,
|
||||
|
@ -98,28 +121,28 @@ export default class ForgotPassword extends React.Component {
|
|||
serverIsAlive: true,
|
||||
});
|
||||
} catch (e) {
|
||||
this.setState(AutoDiscoveryUtils.authComponentStateForError(e, "forgot_password"));
|
||||
this.setState(AutoDiscoveryUtils.authComponentStateForError(e, "forgot_password") as IState);
|
||||
}
|
||||
}
|
||||
|
||||
submitPasswordReset(email, password) {
|
||||
public submitPasswordReset(email: string, password: string): void {
|
||||
this.setState({
|
||||
phase: PHASE_SENDING_EMAIL,
|
||||
phase: Phase.SendingEmail,
|
||||
});
|
||||
this.reset = new PasswordReset(this.props.serverConfig.hsUrl, this.props.serverConfig.isUrl);
|
||||
this.reset.resetPassword(email, password).then(() => {
|
||||
this.setState({
|
||||
phase: PHASE_EMAIL_SENT,
|
||||
phase: Phase.EmailSent,
|
||||
});
|
||||
}, (err) => {
|
||||
this.showErrorDialog(_t('Failed to send email') + ": " + err.message);
|
||||
this.setState({
|
||||
phase: PHASE_FORGOT,
|
||||
phase: Phase.Forgot,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onVerify = async ev => {
|
||||
private onVerify = async (ev: React.MouseEvent): Promise<void> => {
|
||||
ev.preventDefault();
|
||||
if (!this.reset) {
|
||||
console.error("onVerify called before submitPasswordReset!");
|
||||
|
@ -127,17 +150,17 @@ export default class ForgotPassword extends React.Component {
|
|||
}
|
||||
try {
|
||||
await this.reset.checkEmailLinkClicked();
|
||||
this.setState({ phase: PHASE_DONE });
|
||||
this.setState({ phase: Phase.Done });
|
||||
} catch (err) {
|
||||
this.showErrorDialog(err.message);
|
||||
}
|
||||
};
|
||||
|
||||
onSubmitForm = async ev => {
|
||||
private onSubmitForm = async (ev: React.FormEvent): Promise<void> => {
|
||||
ev.preventDefault();
|
||||
|
||||
// refresh the server errors, just in case the server came back online
|
||||
await this._checkServerLiveliness(this.props.serverConfig);
|
||||
await this.checkServerLiveliness(this.props.serverConfig);
|
||||
|
||||
await this['password_field'].validate({ allowEmpty: false });
|
||||
|
||||
|
@ -172,27 +195,27 @@ export default class ForgotPassword extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
onInputChanged = (stateKey, ev) => {
|
||||
private onInputChanged = (stateKey: string, ev: React.FormEvent<HTMLInputElement>) => {
|
||||
this.setState({
|
||||
[stateKey]: ev.target.value,
|
||||
});
|
||||
[stateKey]: ev.currentTarget.value,
|
||||
} as any);
|
||||
};
|
||||
|
||||
onLoginClick = ev => {
|
||||
private onLoginClick = (ev: React.MouseEvent): void => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.props.onLoginClick();
|
||||
};
|
||||
|
||||
showErrorDialog(body, title) {
|
||||
public showErrorDialog(description: string, title?: string) {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Forgot Password Error', '', ErrorDialog, {
|
||||
title: title,
|
||||
description: body,
|
||||
title,
|
||||
description,
|
||||
});
|
||||
}
|
||||
|
||||
onPasswordValidate(result) {
|
||||
private onPasswordValidate(result: IValidationResult) {
|
||||
this.setState({
|
||||
passwordFieldValid: result.valid,
|
||||
});
|
||||
|
@ -316,16 +339,16 @@ export default class ForgotPassword extends React.Component {
|
|||
|
||||
let resetPasswordJsx;
|
||||
switch (this.state.phase) {
|
||||
case PHASE_FORGOT:
|
||||
case Phase.Forgot:
|
||||
resetPasswordJsx = this.renderForgot();
|
||||
break;
|
||||
case PHASE_SENDING_EMAIL:
|
||||
case Phase.SendingEmail:
|
||||
resetPasswordJsx = this.renderSendingEmail();
|
||||
break;
|
||||
case PHASE_EMAIL_SENT:
|
||||
case Phase.EmailSent:
|
||||
resetPasswordJsx = this.renderEmailSent();
|
||||
break;
|
||||
case PHASE_DONE:
|
||||
case Phase.Done:
|
||||
resetPasswordJsx = this.renderDone();
|
||||
break;
|
||||
}
|
|
@ -17,15 +17,25 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
|
||||
import { IntegrationManagerInstance } from "../../../integrations/IntegrationManagerInstance";
|
||||
import * as sdk from '../../../index';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { SettingLevel } from "../../../settings/SettingLevel";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
|
||||
}
|
||||
|
||||
interface IState {
|
||||
currentManager: IntegrationManagerInstance;
|
||||
provisioningEnabled: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.settings.SetIntegrationManager")
|
||||
export default class SetIntegrationManager extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
export default class SetIntegrationManager extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
const currentManager = IntegrationManagers.sharedInstance().getPrimaryManager();
|
||||
|
||||
|
@ -35,7 +45,7 @@ export default class SetIntegrationManager extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
onProvisioningToggled = () => {
|
||||
private onProvisioningToggled = (): void => {
|
||||
const current = this.state.provisioningEnabled;
|
||||
SettingsStore.setValue("integrationProvisioning", null, SettingLevel.ACCOUNT, !current).catch(err => {
|
||||
console.error("Error changing integration manager provisioning");
|
||||
|
@ -46,7 +56,7 @@ export default class SetIntegrationManager extends React.Component {
|
|||
this.setState({ provisioningEnabled: !current });
|
||||
};
|
||||
|
||||
render() {
|
||||
public render(): React.ReactNode {
|
||||
const ToggleSwitch = sdk.getComponent("views.elements.ToggleSwitch");
|
||||
|
||||
const currentManager = this.state.currentManager;
|
|
@ -15,39 +15,48 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { _t, pickBestLanguage } from "../../../languageHandler";
|
||||
import * as sdk from "../../..";
|
||||
import { objectClone } from "../../../utils/objects";
|
||||
import StyledCheckbox from "../elements/StyledCheckbox";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
policiesAndServicePairs: any[];
|
||||
onFinished: (string) => void;
|
||||
agreedUrls: string[]; // array of URLs the user has accepted
|
||||
introElement: Node;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
policies: Policy[];
|
||||
busy: boolean;
|
||||
}
|
||||
|
||||
interface Policy {
|
||||
checked: boolean;
|
||||
url: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.terms.InlineTermsAgreement")
|
||||
export default class InlineTermsAgreement extends React.Component {
|
||||
static propTypes = {
|
||||
policiesAndServicePairs: PropTypes.array.isRequired, // array of service/policy pairs
|
||||
agreedUrls: PropTypes.array.isRequired, // array of URLs the user has accepted
|
||||
onFinished: PropTypes.func.isRequired, // takes an argument of accepted URLs
|
||||
introElement: PropTypes.node,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
export default class InlineTermsAgreement extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
policies: [],
|
||||
busy: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
public componentDidMount(): void {
|
||||
// Build all the terms the user needs to accept
|
||||
const policies = []; // { checked, url, name }
|
||||
for (const servicePolicies of this.props.policiesAndServicePairs) {
|
||||
const availablePolicies = Object.values(servicePolicies.policies);
|
||||
for (const policy of availablePolicies) {
|
||||
const language = pickBestLanguage(Object.keys(policy).filter(p => p !== 'version'));
|
||||
const renderablePolicy = {
|
||||
const renderablePolicy: Policy = {
|
||||
checked: false,
|
||||
url: policy[language].url,
|
||||
name: policy[language].name,
|
||||
|
@ -59,13 +68,13 @@ export default class InlineTermsAgreement extends React.Component {
|
|||
this.setState({ policies });
|
||||
}
|
||||
|
||||
_togglePolicy = (index) => {
|
||||
private togglePolicy = (index: number): void => {
|
||||
const policies = objectClone(this.state.policies);
|
||||
policies[index].checked = !policies[index].checked;
|
||||
this.setState({ policies });
|
||||
};
|
||||
|
||||
_onContinue = () => {
|
||||
private onContinue = (): void => {
|
||||
const hasUnchecked = !!this.state.policies.some(p => !p.checked);
|
||||
if (hasUnchecked) return;
|
||||
|
||||
|
@ -73,7 +82,7 @@ export default class InlineTermsAgreement extends React.Component {
|
|||
this.props.onFinished(this.state.policies.map(p => p.url));
|
||||
};
|
||||
|
||||
_renderCheckboxes() {
|
||||
private renderCheckboxes(): React.ReactNode[] {
|
||||
const rendered = [];
|
||||
for (let i = 0; i < this.state.policies.length; i++) {
|
||||
const policy = this.state.policies[i];
|
||||
|
@ -93,7 +102,7 @@ export default class InlineTermsAgreement extends React.Component {
|
|||
<div key={i} className='mx_InlineTermsAgreement_cbContainer'>
|
||||
<div>{introText}</div>
|
||||
<div className='mx_InlineTermsAgreement_checkbox'>
|
||||
<StyledCheckbox onChange={() => this._togglePolicy(i)} checked={policy.checked}>
|
||||
<StyledCheckbox onChange={() => this.togglePolicy(i)} checked={policy.checked}>
|
||||
{_t("Accept")}
|
||||
</StyledCheckbox>
|
||||
</div>
|
||||
|
@ -103,16 +112,16 @@ export default class InlineTermsAgreement extends React.Component {
|
|||
return rendered;
|
||||
}
|
||||
|
||||
render() {
|
||||
public render(): React.ReactNode {
|
||||
const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton");
|
||||
const hasUnchecked = !!this.state.policies.some(p => !p.checked);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{this.props.introElement}
|
||||
{this._renderCheckboxes()}
|
||||
{this.renderCheckboxes()}
|
||||
<AccessibleButton
|
||||
onClick={this._onContinue}
|
||||
onClick={this.onContinue}
|
||||
disabled={hasUnchecked || this.state.busy}
|
||||
kind="primary_sm"
|
||||
>
|
|
@ -15,18 +15,17 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.verification.VerificationCancelled")
|
||||
export default class VerificationCancelled extends React.Component {
|
||||
static propTypes = {
|
||||
onDone: PropTypes.func.isRequired,
|
||||
}
|
||||
interface IProps {
|
||||
onDone: () => void;
|
||||
}
|
||||
|
||||
render() {
|
||||
@replaceableComponent("views.verification.VerificationCancelled")
|
||||
export default class VerificationCancelled extends React.Component<IProps> {
|
||||
public render(): React.ReactNode {
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
return <div>
|
||||
<p>{_t(
|
|
@ -15,18 +15,17 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.verification.VerificationComplete")
|
||||
export default class VerificationComplete extends React.Component {
|
||||
static propTypes = {
|
||||
onDone: PropTypes.func.isRequired,
|
||||
}
|
||||
interface IProps {
|
||||
onDone: () => void;
|
||||
}
|
||||
|
||||
render() {
|
||||
@replaceableComponent("views.verification.VerificationComplete")
|
||||
export default class VerificationComplete extends React.Component<IProps> {
|
||||
public render(): React.ReactNode {
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
return <div>
|
||||
<h2>{_t("Verified!")}</h2>
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 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 { _t } from '../../../languageHandler';
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import VerificationQRCode from "../elements/crypto/VerificationQRCode";
|
||||
import Spinner from "../elements/Spinner";
|
||||
import { SCAN_QR_CODE_METHOD } from "matrix-js-sdk/src/crypto/verification/QRCode";
|
||||
|
||||
@replaceableComponent("views.verification.VerificationQREmojiOptions")
|
||||
export default class VerificationQREmojiOptions extends React.Component {
|
||||
static propTypes = {
|
||||
request: PropTypes.object.isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
onStartEmoji: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { request } = this.props;
|
||||
const showQR = request.otherPartySupportsMethod(SCAN_QR_CODE_METHOD);
|
||||
|
||||
let qrCode;
|
||||
if (showQR) {
|
||||
qrCode = <VerificationQRCode qrCodeData={request.qrCodeData} />;
|
||||
} else {
|
||||
qrCode = <div className='mx_VerificationQREmojiOptions_noQR'><Spinner /></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{_t("Verify this session by completing one of the following:")}
|
||||
<div className='mx_IncomingSasDialog_startOptions'>
|
||||
<div className='mx_IncomingSasDialog_startOption'>
|
||||
<p>{_t("Scan this unique code")}</p>
|
||||
{qrCode}
|
||||
</div>
|
||||
<div className='mx_IncomingSasDialog_betweenText'>{_t("or")}</div>
|
||||
<div className='mx_IncomingSasDialog_startOption'>
|
||||
<p>{_t("Compare unique emoji")}</p>
|
||||
<span className='mx_IncomingSasDialog_helpText'>{_t("Compare a unique set of emoji if you don't have a camera on either device")}</span>
|
||||
<AccessibleButton onClick={this.props.onStartEmoji} kind='primary'>
|
||||
{_t("Start")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>
|
||||
<AccessibleButton onClick={this.props.onCancel} kind='danger'>
|
||||
{_t("Cancel")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -15,7 +15,8 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { SAS } from "matrix-js-sdk/src/crypto/verification/SAS";
|
||||
import { DeviceInfo } from "matrix-js-sdk/src//crypto/deviceinfo";
|
||||
import { _t, _td } from '../../../languageHandler';
|
||||
import { PendingActionSpinner } from "../right_panel/EncryptionInfo";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
|
@ -23,24 +24,29 @@ import DialogButtons from "../elements/DialogButtons";
|
|||
import { fixupColorFonts } from '../../../utils/FontManager';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
pending?: boolean;
|
||||
displayName?: string; // required if pending is true
|
||||
device?: DeviceInfo;
|
||||
onDone: () => void;
|
||||
onCancel: () => void;
|
||||
sas: SAS.sas;
|
||||
isSelf?: boolean;
|
||||
inDialog?: boolean; // whether this component is being shown in a dialog and to use DialogButtons
|
||||
}
|
||||
|
||||
interface IState {
|
||||
pending: boolean;
|
||||
cancelling?: boolean;
|
||||
}
|
||||
|
||||
function capFirst(s) {
|
||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||
}
|
||||
|
||||
@replaceableComponent("views.verification.VerificationShowSas")
|
||||
export default class VerificationShowSas extends React.Component {
|
||||
static propTypes = {
|
||||
pending: PropTypes.bool,
|
||||
displayName: PropTypes.string, // required if pending is true
|
||||
device: PropTypes.object,
|
||||
onDone: PropTypes.func.isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
sas: PropTypes.object.isRequired,
|
||||
isSelf: PropTypes.bool,
|
||||
inDialog: PropTypes.bool, // whether this component is being shown in a dialog and to use DialogButtons
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
export default class VerificationShowSas extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
|
@ -48,19 +54,19 @@ export default class VerificationShowSas extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
public componentWillMount(): void {
|
||||
// As this component is also used before login (during complete security),
|
||||
// also make sure we have a working emoji font to display the SAS emojis here.
|
||||
// This is also done from LoggedInView.
|
||||
fixupColorFonts();
|
||||
}
|
||||
|
||||
onMatchClick = () => {
|
||||
private onMatchClick = (): void => {
|
||||
this.setState({ pending: true });
|
||||
this.props.onDone();
|
||||
};
|
||||
|
||||
onDontMatchClick = () => {
|
||||
private onDontMatchClick = (): void => {
|
||||
this.setState({ cancelling: true });
|
||||
this.props.onCancel();
|
||||
};
|
|
@ -923,12 +923,6 @@
|
|||
"You've successfully verified this user.": "You've successfully verified this user.",
|
||||
"Secure messages with this user are end-to-end encrypted and not able to be read by third parties.": "Secure messages with this user are end-to-end encrypted and not able to be read by third parties.",
|
||||
"Got It": "Got It",
|
||||
"Verify this session by completing one of the following:": "Verify this session by completing one of the following:",
|
||||
"Scan this unique code": "Scan this unique code",
|
||||
"or": "or",
|
||||
"Compare unique emoji": "Compare unique emoji",
|
||||
"Compare a unique set of emoji if you don't have a camera on either device": "Compare a unique set of emoji if you don't have a camera on either device",
|
||||
"Start": "Start",
|
||||
"Confirm the emoji below are displayed on both sessions, in the same order:": "Confirm the emoji below are displayed on both sessions, in the same order:",
|
||||
"Verify this user by confirming the following emoji appear on their screen.": "Verify this user by confirming the following emoji appear on their screen.",
|
||||
"Verify this session by confirming the following number appears on its screen.": "Verify this session by confirming the following number appears on its screen.",
|
||||
|
@ -1833,6 +1827,12 @@
|
|||
"Edit devices": "Edit devices",
|
||||
"Security": "Security",
|
||||
"The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what %(brand)s supports. Try with a different client.": "The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what %(brand)s supports. Try with a different client.",
|
||||
"Scan this unique code": "Scan this unique code",
|
||||
"Compare unique emoji": "Compare unique emoji",
|
||||
"Compare a unique set of emoji if you don't have a camera on either device": "Compare a unique set of emoji if you don't have a camera on either device",
|
||||
"Start": "Start",
|
||||
"or": "or",
|
||||
"Verify this session by completing one of the following:": "Verify this session by completing one of the following:",
|
||||
"Verify by scanning": "Verify by scanning",
|
||||
"Ask %(displayName)s to scan your code:": "Ask %(displayName)s to scan your code:",
|
||||
"If you can't scan the code above, verify by comparing unique emoji.": "If you can't scan the code above, verify by comparing unique emoji.",
|
||||
|
|
|
@ -20,15 +20,25 @@ import { StandardActions } from "./StandardActions";
|
|||
import { PushRuleVectorState } from "./PushRuleVectorState";
|
||||
import { NotificationUtils } from "./NotificationUtils";
|
||||
|
||||
interface IProps {
|
||||
kind: Kind;
|
||||
description: string;
|
||||
vectorStateToActions: Action;
|
||||
}
|
||||
|
||||
class VectorPushRuleDefinition {
|
||||
constructor(opts) {
|
||||
private kind: Kind;
|
||||
private description: string;
|
||||
private vectorStateToActions: Action;
|
||||
|
||||
constructor(opts: IProps) {
|
||||
this.kind = opts.kind;
|
||||
this.description = opts.description;
|
||||
this.vectorStateToActions = opts.vectorStateToActions;
|
||||
}
|
||||
|
||||
// Translate the rule actions and its enabled value into vector state
|
||||
ruleToVectorState(rule) {
|
||||
public ruleToVectorState(rule): VectorPushRuleDefinition {
|
||||
let enabled = false;
|
||||
if (rule) {
|
||||
enabled = rule.enabled;
|
||||
|
@ -63,13 +73,24 @@ class VectorPushRuleDefinition {
|
|||
}
|
||||
}
|
||||
|
||||
enum Kind {
|
||||
Override = "override",
|
||||
Underride = "underride",
|
||||
}
|
||||
|
||||
interface Action {
|
||||
on: StandardActions;
|
||||
loud: StandardActions;
|
||||
off: StandardActions;
|
||||
}
|
||||
|
||||
/**
|
||||
* The descriptions of rules managed by the Vector UI.
|
||||
*/
|
||||
export const VectorPushRulesDefinitions = {
|
||||
// Messages containing user's display name
|
||||
".m.rule.contains_display_name": new VectorPushRuleDefinition({
|
||||
kind: "override",
|
||||
kind: Kind.Override,
|
||||
description: _td("Messages containing my display name"), // passed through _t() translation in src/components/views/settings/Notifications.js
|
||||
vectorStateToActions: { // The actions for each vector state, or null to disable the rule.
|
||||
on: StandardActions.ACTION_NOTIFY,
|
||||
|
@ -80,7 +101,7 @@ export const VectorPushRulesDefinitions = {
|
|||
|
||||
// Messages containing user's username (localpart/MXID)
|
||||
".m.rule.contains_user_name": new VectorPushRuleDefinition({
|
||||
kind: "override",
|
||||
kind: Kind.Override,
|
||||
description: _td("Messages containing my username"), // passed through _t() translation in src/components/views/settings/Notifications.js
|
||||
vectorStateToActions: { // The actions for each vector state, or null to disable the rule.
|
||||
on: StandardActions.ACTION_NOTIFY,
|
||||
|
@ -91,7 +112,7 @@ export const VectorPushRulesDefinitions = {
|
|||
|
||||
// Messages containing @room
|
||||
".m.rule.roomnotif": new VectorPushRuleDefinition({
|
||||
kind: "override",
|
||||
kind: Kind.Override,
|
||||
description: _td("Messages containing @room"), // passed through _t() translation in src/components/views/settings/Notifications.js
|
||||
vectorStateToActions: { // The actions for each vector state, or null to disable the rule.
|
||||
on: StandardActions.ACTION_NOTIFY,
|
||||
|
@ -102,7 +123,7 @@ export const VectorPushRulesDefinitions = {
|
|||
|
||||
// Messages just sent to the user in a 1:1 room
|
||||
".m.rule.room_one_to_one": new VectorPushRuleDefinition({
|
||||
kind: "underride",
|
||||
kind: Kind.Underride,
|
||||
description: _td("Messages in one-to-one chats"), // passed through _t() translation in src/components/views/settings/Notifications.js
|
||||
vectorStateToActions: {
|
||||
on: StandardActions.ACTION_NOTIFY,
|
||||
|
@ -113,7 +134,7 @@ export const VectorPushRulesDefinitions = {
|
|||
|
||||
// Encrypted messages just sent to the user in a 1:1 room
|
||||
".m.rule.encrypted_room_one_to_one": new VectorPushRuleDefinition({
|
||||
kind: "underride",
|
||||
kind: Kind.Underride,
|
||||
description: _td("Encrypted messages in one-to-one chats"), // passed through _t() translation in src/components/views/settings/Notifications.js
|
||||
vectorStateToActions: {
|
||||
on: StandardActions.ACTION_NOTIFY,
|
||||
|
@ -126,7 +147,7 @@ export const VectorPushRulesDefinitions = {
|
|||
// 1:1 room messages are catched by the .m.rule.room_one_to_one rule if any defined
|
||||
// By opposition, all other room messages are from group chat rooms.
|
||||
".m.rule.message": new VectorPushRuleDefinition({
|
||||
kind: "underride",
|
||||
kind: Kind.Underride,
|
||||
description: _td("Messages in group chats"), // passed through _t() translation in src/components/views/settings/Notifications.js
|
||||
vectorStateToActions: {
|
||||
on: StandardActions.ACTION_NOTIFY,
|
||||
|
@ -139,7 +160,7 @@ export const VectorPushRulesDefinitions = {
|
|||
// Encrypted 1:1 room messages are catched by the .m.rule.encrypted_room_one_to_one rule if any defined
|
||||
// By opposition, all other room messages are from group chat rooms.
|
||||
".m.rule.encrypted": new VectorPushRuleDefinition({
|
||||
kind: "underride",
|
||||
kind: Kind.Underride,
|
||||
description: _td("Encrypted messages in group chats"), // passed through _t() translation in src/components/views/settings/Notifications.js
|
||||
vectorStateToActions: {
|
||||
on: StandardActions.ACTION_NOTIFY,
|
||||
|
@ -150,7 +171,7 @@ export const VectorPushRulesDefinitions = {
|
|||
|
||||
// Invitation for the user
|
||||
".m.rule.invite_for_me": new VectorPushRuleDefinition({
|
||||
kind: "underride",
|
||||
kind: Kind.Underride,
|
||||
description: _td("When I'm invited to a room"), // passed through _t() translation in src/components/views/settings/Notifications.js
|
||||
vectorStateToActions: {
|
||||
on: StandardActions.ACTION_NOTIFY,
|
||||
|
@ -161,7 +182,7 @@ export const VectorPushRulesDefinitions = {
|
|||
|
||||
// Incoming call
|
||||
".m.rule.call": new VectorPushRuleDefinition({
|
||||
kind: "underride",
|
||||
kind: Kind.Underride,
|
||||
description: _td("Call invitation"), // passed through _t() translation in src/components/views/settings/Notifications.js
|
||||
vectorStateToActions: {
|
||||
on: StandardActions.ACTION_NOTIFY,
|
||||
|
@ -172,7 +193,7 @@ export const VectorPushRulesDefinitions = {
|
|||
|
||||
// Notifications from bots
|
||||
".m.rule.suppress_notices": new VectorPushRuleDefinition({
|
||||
kind: "override",
|
||||
kind: Kind.Override,
|
||||
description: _td("Messages sent by bot"), // passed through _t() translation in src/components/views/settings/Notifications.js
|
||||
vectorStateToActions: {
|
||||
// .m.rule.suppress_notices is a "negative" rule, we have to invert its enabled value for vector UI
|
||||
|
@ -184,7 +205,7 @@ export const VectorPushRulesDefinitions = {
|
|||
|
||||
// Room upgrades (tombstones)
|
||||
".m.rule.tombstone": new VectorPushRuleDefinition({
|
||||
kind: "override",
|
||||
kind: Kind.Override,
|
||||
description: _td("When rooms are upgraded"), // passed through _t() translation in src/components/views/settings/Notifications.js
|
||||
vectorStateToActions: { // The actions for each vector state, or null to disable the rule.
|
||||
on: StandardActions.ACTION_NOTIFY,
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
Copyright 2017 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Stores where the user has scrolled to in each room
|
||||
*/
|
||||
class RoomScrollStateStore {
|
||||
constructor() {
|
||||
// A map from room id to scroll state.
|
||||
//
|
||||
// If there is no special scroll state (ie, we are following the live
|
||||
// timeline), the scroll state is null. Otherwise, it is an object with
|
||||
// the following properties:
|
||||
//
|
||||
// focussedEvent: the ID of the 'focussed' event. Typically this is
|
||||
// the last event fully visible in the viewport, though if we
|
||||
// have done an explicit scroll to an explicit event, it will be
|
||||
// that event.
|
||||
//
|
||||
// pixelOffset: the number of pixels the window is scrolled down
|
||||
// from the focussedEvent.
|
||||
this._scrollStateMap = {};
|
||||
}
|
||||
|
||||
getScrollState(roomId) {
|
||||
return this._scrollStateMap[roomId];
|
||||
}
|
||||
|
||||
setScrollState(roomId, scrollState) {
|
||||
this._scrollStateMap[roomId] = scrollState;
|
||||
}
|
||||
}
|
||||
|
||||
if (global.mx_RoomScrollStateStore === undefined) {
|
||||
global.mx_RoomScrollStateStore = new RoomScrollStateStore();
|
||||
}
|
||||
export default global.mx_RoomScrollStateStore;
|
53
src/stores/RoomScrollStateStore.ts
Normal file
53
src/stores/RoomScrollStateStore.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
Copyright 2017 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.
|
||||
*/
|
||||
|
||||
export interface ScrollState {
|
||||
focussedEvent: string;
|
||||
pixelOffset: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores where the user has scrolled to in each room
|
||||
*/
|
||||
export class RoomScrollStateStore {
|
||||
// A map from room id to scroll state.
|
||||
//
|
||||
// If there is no special scroll state (ie, we are following the live
|
||||
// timeline), the scroll state is null. Otherwise, it is an object with
|
||||
// the following properties:
|
||||
//
|
||||
// focussedEvent: the ID of the 'focussed' event. Typically this is
|
||||
// the last event fully visible in the viewport, though if we
|
||||
// have done an explicit scroll to an explicit event, it will be
|
||||
// that event.
|
||||
//
|
||||
// pixelOffset: the number of pixels the window is scrolled down
|
||||
// from the focussedEvent.
|
||||
private scrollStateMap = new Map<string, ScrollState>();
|
||||
|
||||
public getScrollState(roomId: string): ScrollState {
|
||||
return this.scrollStateMap.get(roomId);
|
||||
}
|
||||
|
||||
setScrollState(roomId: string, scrollState: ScrollState): void {
|
||||
this.scrollStateMap.set(roomId, scrollState);
|
||||
}
|
||||
}
|
||||
|
||||
if (window.mxRoomScrollStateStore === undefined) {
|
||||
window.mxRoomScrollStateStore = new RoomScrollStateStore();
|
||||
}
|
||||
export default window.mxRoomScrollStateStore;
|
Loading…
Reference in a new issue