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 PerformanceMonitor from "../performance";
|
||||||
import UIStore from "../stores/UIStore";
|
import UIStore from "../stores/UIStore";
|
||||||
import { SetupEncryptionStore } from "../stores/SetupEncryptionStore";
|
import { SetupEncryptionStore } from "../stores/SetupEncryptionStore";
|
||||||
|
import { RoomScrollStateStore } from "../stores/RoomScrollStateStore";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
|
@ -87,6 +88,7 @@ declare global {
|
||||||
mxPerformanceEntryNames: any;
|
mxPerformanceEntryNames: any;
|
||||||
mxUIStore: UIStore;
|
mxUIStore: UIStore;
|
||||||
mxSetupEncryptionStore?: SetupEncryptionStore;
|
mxSetupEncryptionStore?: SetupEncryptionStore;
|
||||||
|
mxRoomScrollStateStore?: RoomScrollStateStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Document {
|
interface Document {
|
||||||
|
|
|
@ -42,7 +42,7 @@ import eventSearch, { searchPagination } from '../../Searching';
|
||||||
import MainSplit from './MainSplit';
|
import MainSplit from './MainSplit';
|
||||||
import RightPanel from './RightPanel';
|
import RightPanel from './RightPanel';
|
||||||
import RoomViewStore from '../../stores/RoomViewStore';
|
import RoomViewStore from '../../stores/RoomViewStore';
|
||||||
import RoomScrollStateStore from '../../stores/RoomScrollStateStore';
|
import RoomScrollStateStore, { ScrollState } from '../../stores/RoomScrollStateStore';
|
||||||
import WidgetEchoStore from '../../stores/WidgetEchoStore';
|
import WidgetEchoStore from '../../stores/WidgetEchoStore';
|
||||||
import SettingsStore from "../../settings/SettingsStore";
|
import SettingsStore from "../../settings/SettingsStore";
|
||||||
import { Layout } from "../../settings/Layout";
|
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
|
// get the current scroll position of the room, so that it can be
|
||||||
// restored when we switch back to it.
|
// restored when we switch back to it.
|
||||||
//
|
//
|
||||||
private getScrollState() {
|
private getScrollState(): ScrollState {
|
||||||
const messagePanel = this.messagePanel;
|
const messagePanel = this.messagePanel;
|
||||||
if (!messagePanel) return null;
|
if (!messagePanel) return null;
|
||||||
|
|
||||||
|
|
|
@ -15,39 +15,42 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import { SetupEncryptionStore, Phase } from '../../../stores/SetupEncryptionStore';
|
import { SetupEncryptionStore, Phase } from '../../../stores/SetupEncryptionStore';
|
||||||
import SetupEncryptionBody from "./SetupEncryptionBody";
|
import SetupEncryptionBody from "./SetupEncryptionBody";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
@replaceableComponent("structures.auth.CompleteSecurity")
|
interface IProps {
|
||||||
export default class CompleteSecurity extends React.Component {
|
onFinished: () => void;
|
||||||
static propTypes = {
|
}
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor() {
|
interface IState {
|
||||||
super();
|
phase: Phase;
|
||||||
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("structures.auth.CompleteSecurity")
|
||||||
|
export default class CompleteSecurity extends React.Component<IProps, IState> {
|
||||||
|
constructor(props: IProps) {
|
||||||
|
super(props);
|
||||||
const store = SetupEncryptionStore.sharedInstance();
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
store.on("update", this._onStoreUpdate);
|
store.on("update", this.onStoreUpdate);
|
||||||
store.start();
|
store.start();
|
||||||
this.state = { phase: store.phase };
|
this.state = { phase: store.phase };
|
||||||
}
|
}
|
||||||
|
|
||||||
_onStoreUpdate = () => {
|
private onStoreUpdate = (): void => {
|
||||||
const store = SetupEncryptionStore.sharedInstance();
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
this.setState({ phase: store.phase });
|
this.setState({ phase: store.phase });
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillUnmount() {
|
public componentWillUnmount(): void {
|
||||||
const store = SetupEncryptionStore.sharedInstance();
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
store.off("update", this._onStoreUpdate);
|
store.off("update", this.onStoreUpdate);
|
||||||
store.stop();
|
store.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const AuthPage = sdk.getComponent("auth.AuthPage");
|
const AuthPage = sdk.getComponent("auth.AuthPage");
|
||||||
const CompleteSecurityBody = sdk.getComponent("auth.CompleteSecurityBody");
|
const CompleteSecurityBody = sdk.getComponent("auth.CompleteSecurityBody");
|
||||||
const { phase } = this.state;
|
const { phase } = this.state;
|
|
@ -15,20 +15,19 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import AuthPage from '../../views/auth/AuthPage';
|
import AuthPage from '../../views/auth/AuthPage';
|
||||||
import CompleteSecurityBody from '../../views/auth/CompleteSecurityBody';
|
import CompleteSecurityBody from '../../views/auth/CompleteSecurityBody';
|
||||||
import CreateCrossSigningDialog from '../../views/dialogs/security/CreateCrossSigningDialog';
|
import CreateCrossSigningDialog from '../../views/dialogs/security/CreateCrossSigningDialog';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
@replaceableComponent("structures.auth.E2eSetup")
|
interface IProps {
|
||||||
export default class E2eSetup extends React.Component {
|
onFinished: () => void;
|
||||||
static propTypes = {
|
accountPassword?: string;
|
||||||
onFinished: PropTypes.func.isRequired,
|
tokenLogin?: boolean;
|
||||||
accountPassword: PropTypes.string,
|
}
|
||||||
tokenLogin: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
@replaceableComponent("structures.auth.E2eSetup")
|
||||||
|
export default class E2eSetup extends React.Component<IProps> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<AuthPage>
|
<AuthPage>
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { _t, _td } from '../../../languageHandler';
|
import { _t, _td } from '../../../languageHandler';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
|
@ -31,27 +30,50 @@ import PassphraseField from '../../views/auth/PassphraseField';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { PASSWORD_MIN_SCORE } from '../../views/auth/RegistrationForm';
|
import { PASSWORD_MIN_SCORE } from '../../views/auth/RegistrationForm';
|
||||||
|
|
||||||
// Phases
|
import { IValidationResult } from "../../views/elements/Validation";
|
||||||
// Show the forgot password inputs
|
|
||||||
const PHASE_FORGOT = 1;
|
enum Phase {
|
||||||
// Email is in the process of being sent
|
// Show the forgot password inputs
|
||||||
const PHASE_SENDING_EMAIL = 2;
|
Forgot = 1,
|
||||||
// Email has been sent
|
// Email is in the process of being sent
|
||||||
const PHASE_EMAIL_SENT = 3;
|
SendingEmail = 2,
|
||||||
// User has clicked the link in email and completed reset
|
// Email has been sent
|
||||||
const PHASE_DONE = 4;
|
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")
|
@replaceableComponent("structures.auth.ForgotPassword")
|
||||||
export default class ForgotPassword extends React.Component {
|
export default class ForgotPassword extends React.Component<IProps, IState> {
|
||||||
static propTypes = {
|
private reset: PasswordReset;
|
||||||
serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired,
|
|
||||||
onServerConfigChange: PropTypes.func.isRequired,
|
|
||||||
onLoginClick: PropTypes.func,
|
|
||||||
onComplete: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
phase: PHASE_FORGOT,
|
phase: Phase.Forgot,
|
||||||
email: "",
|
email: "",
|
||||||
password: "",
|
password: "",
|
||||||
password2: "",
|
password2: "",
|
||||||
|
@ -64,30 +86,31 @@ export default class ForgotPassword extends React.Component {
|
||||||
serverIsAlive: true,
|
serverIsAlive: true,
|
||||||
serverErrorIsFatal: false,
|
serverErrorIsFatal: false,
|
||||||
serverDeadError: "",
|
serverDeadError: "",
|
||||||
|
passwordFieldValid: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
CountlyAnalytics.instance.track("onboarding_forgot_password_begin");
|
CountlyAnalytics.instance.track("onboarding_forgot_password_begin");
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
public componentDidMount() {
|
||||||
this.reset = null;
|
this.reset = null;
|
||||||
this._checkServerLiveliness(this.props.serverConfig);
|
this.checkServerLiveliness(this.props.serverConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
UNSAFE_componentWillReceiveProps(newProps) {
|
public UNSAFE_componentWillReceiveProps(newProps: IProps): void {
|
||||||
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
|
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
|
||||||
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
|
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
|
||||||
|
|
||||||
// Do a liveliness check on the new URLs
|
// 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 {
|
try {
|
||||||
await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(
|
await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(
|
||||||
serverConfig.hsUrl,
|
serverConfig.hsUrl,
|
||||||
|
@ -98,28 +121,28 @@ export default class ForgotPassword extends React.Component {
|
||||||
serverIsAlive: true,
|
serverIsAlive: true,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} 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({
|
this.setState({
|
||||||
phase: PHASE_SENDING_EMAIL,
|
phase: Phase.SendingEmail,
|
||||||
});
|
});
|
||||||
this.reset = new PasswordReset(this.props.serverConfig.hsUrl, this.props.serverConfig.isUrl);
|
this.reset = new PasswordReset(this.props.serverConfig.hsUrl, this.props.serverConfig.isUrl);
|
||||||
this.reset.resetPassword(email, password).then(() => {
|
this.reset.resetPassword(email, password).then(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
phase: PHASE_EMAIL_SENT,
|
phase: Phase.EmailSent,
|
||||||
});
|
});
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
this.showErrorDialog(_t('Failed to send email') + ": " + err.message);
|
this.showErrorDialog(_t('Failed to send email') + ": " + err.message);
|
||||||
this.setState({
|
this.setState({
|
||||||
phase: PHASE_FORGOT,
|
phase: Phase.Forgot,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onVerify = async ev => {
|
private onVerify = async (ev: React.MouseEvent): Promise<void> => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
if (!this.reset) {
|
if (!this.reset) {
|
||||||
console.error("onVerify called before submitPasswordReset!");
|
console.error("onVerify called before submitPasswordReset!");
|
||||||
|
@ -127,17 +150,17 @@ export default class ForgotPassword extends React.Component {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await this.reset.checkEmailLinkClicked();
|
await this.reset.checkEmailLinkClicked();
|
||||||
this.setState({ phase: PHASE_DONE });
|
this.setState({ phase: Phase.Done });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.showErrorDialog(err.message);
|
this.showErrorDialog(err.message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onSubmitForm = async ev => {
|
private onSubmitForm = async (ev: React.FormEvent): Promise<void> => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
// refresh the server errors, just in case the server came back online
|
// 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 });
|
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({
|
this.setState({
|
||||||
[stateKey]: ev.target.value,
|
[stateKey]: ev.currentTarget.value,
|
||||||
});
|
} as any);
|
||||||
};
|
};
|
||||||
|
|
||||||
onLoginClick = ev => {
|
private onLoginClick = (ev: React.MouseEvent): void => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.props.onLoginClick();
|
this.props.onLoginClick();
|
||||||
};
|
};
|
||||||
|
|
||||||
showErrorDialog(body, title) {
|
public showErrorDialog(description: string, title?: string) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createTrackedDialog('Forgot Password Error', '', ErrorDialog, {
|
Modal.createTrackedDialog('Forgot Password Error', '', ErrorDialog, {
|
||||||
title: title,
|
title,
|
||||||
description: body,
|
description,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onPasswordValidate(result) {
|
private onPasswordValidate(result: IValidationResult) {
|
||||||
this.setState({
|
this.setState({
|
||||||
passwordFieldValid: result.valid,
|
passwordFieldValid: result.valid,
|
||||||
});
|
});
|
||||||
|
@ -316,16 +339,16 @@ export default class ForgotPassword extends React.Component {
|
||||||
|
|
||||||
let resetPasswordJsx;
|
let resetPasswordJsx;
|
||||||
switch (this.state.phase) {
|
switch (this.state.phase) {
|
||||||
case PHASE_FORGOT:
|
case Phase.Forgot:
|
||||||
resetPasswordJsx = this.renderForgot();
|
resetPasswordJsx = this.renderForgot();
|
||||||
break;
|
break;
|
||||||
case PHASE_SENDING_EMAIL:
|
case Phase.SendingEmail:
|
||||||
resetPasswordJsx = this.renderSendingEmail();
|
resetPasswordJsx = this.renderSendingEmail();
|
||||||
break;
|
break;
|
||||||
case PHASE_EMAIL_SENT:
|
case Phase.EmailSent:
|
||||||
resetPasswordJsx = this.renderEmailSent();
|
resetPasswordJsx = this.renderEmailSent();
|
||||||
break;
|
break;
|
||||||
case PHASE_DONE:
|
case Phase.Done:
|
||||||
resetPasswordJsx = this.renderDone();
|
resetPasswordJsx = this.renderDone();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
|
@ -17,15 +17,25 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
|
import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
|
||||||
|
import { IntegrationManagerInstance } from "../../../integrations/IntegrationManagerInstance";
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import { SettingLevel } from "../../../settings/SettingLevel";
|
import { SettingLevel } from "../../../settings/SettingLevel";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
currentManager: IntegrationManagerInstance;
|
||||||
|
provisioningEnabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
@replaceableComponent("views.settings.SetIntegrationManager")
|
@replaceableComponent("views.settings.SetIntegrationManager")
|
||||||
export default class SetIntegrationManager extends React.Component {
|
export default class SetIntegrationManager extends React.Component<IProps, IState> {
|
||||||
constructor() {
|
constructor(props: IProps) {
|
||||||
super();
|
super(props);
|
||||||
|
|
||||||
const currentManager = IntegrationManagers.sharedInstance().getPrimaryManager();
|
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;
|
const current = this.state.provisioningEnabled;
|
||||||
SettingsStore.setValue("integrationProvisioning", null, SettingLevel.ACCOUNT, !current).catch(err => {
|
SettingsStore.setValue("integrationProvisioning", null, SettingLevel.ACCOUNT, !current).catch(err => {
|
||||||
console.error("Error changing integration manager provisioning");
|
console.error("Error changing integration manager provisioning");
|
||||||
|
@ -46,7 +56,7 @@ export default class SetIntegrationManager extends React.Component {
|
||||||
this.setState({ provisioningEnabled: !current });
|
this.setState({ provisioningEnabled: !current });
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
public render(): React.ReactNode {
|
||||||
const ToggleSwitch = sdk.getComponent("views.elements.ToggleSwitch");
|
const ToggleSwitch = sdk.getComponent("views.elements.ToggleSwitch");
|
||||||
|
|
||||||
const currentManager = this.state.currentManager;
|
const currentManager = this.state.currentManager;
|
|
@ -15,39 +15,48 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import PropTypes from "prop-types";
|
|
||||||
import { _t, pickBestLanguage } from "../../../languageHandler";
|
import { _t, pickBestLanguage } from "../../../languageHandler";
|
||||||
import * as sdk from "../../..";
|
import * as sdk from "../../..";
|
||||||
import { objectClone } from "../../../utils/objects";
|
import { objectClone } from "../../../utils/objects";
|
||||||
import StyledCheckbox from "../elements/StyledCheckbox";
|
import StyledCheckbox from "../elements/StyledCheckbox";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
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")
|
@replaceableComponent("views.terms.InlineTermsAgreement")
|
||||||
export default class InlineTermsAgreement extends React.Component {
|
export default class InlineTermsAgreement extends React.Component<IProps, IState> {
|
||||||
static propTypes = {
|
constructor(props: IProps) {
|
||||||
policiesAndServicePairs: PropTypes.array.isRequired, // array of service/policy pairs
|
super(props);
|
||||||
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();
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
policies: [],
|
policies: [],
|
||||||
busy: false,
|
busy: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
public componentDidMount(): void {
|
||||||
// Build all the terms the user needs to accept
|
// Build all the terms the user needs to accept
|
||||||
const policies = []; // { checked, url, name }
|
const policies = []; // { checked, url, name }
|
||||||
for (const servicePolicies of this.props.policiesAndServicePairs) {
|
for (const servicePolicies of this.props.policiesAndServicePairs) {
|
||||||
const availablePolicies = Object.values(servicePolicies.policies);
|
const availablePolicies = Object.values(servicePolicies.policies);
|
||||||
for (const policy of availablePolicies) {
|
for (const policy of availablePolicies) {
|
||||||
const language = pickBestLanguage(Object.keys(policy).filter(p => p !== 'version'));
|
const language = pickBestLanguage(Object.keys(policy).filter(p => p !== 'version'));
|
||||||
const renderablePolicy = {
|
const renderablePolicy: Policy = {
|
||||||
checked: false,
|
checked: false,
|
||||||
url: policy[language].url,
|
url: policy[language].url,
|
||||||
name: policy[language].name,
|
name: policy[language].name,
|
||||||
|
@ -59,13 +68,13 @@ export default class InlineTermsAgreement extends React.Component {
|
||||||
this.setState({ policies });
|
this.setState({ policies });
|
||||||
}
|
}
|
||||||
|
|
||||||
_togglePolicy = (index) => {
|
private togglePolicy = (index: number): void => {
|
||||||
const policies = objectClone(this.state.policies);
|
const policies = objectClone(this.state.policies);
|
||||||
policies[index].checked = !policies[index].checked;
|
policies[index].checked = !policies[index].checked;
|
||||||
this.setState({ policies });
|
this.setState({ policies });
|
||||||
};
|
};
|
||||||
|
|
||||||
_onContinue = () => {
|
private onContinue = (): void => {
|
||||||
const hasUnchecked = !!this.state.policies.some(p => !p.checked);
|
const hasUnchecked = !!this.state.policies.some(p => !p.checked);
|
||||||
if (hasUnchecked) return;
|
if (hasUnchecked) return;
|
||||||
|
|
||||||
|
@ -73,7 +82,7 @@ export default class InlineTermsAgreement extends React.Component {
|
||||||
this.props.onFinished(this.state.policies.map(p => p.url));
|
this.props.onFinished(this.state.policies.map(p => p.url));
|
||||||
};
|
};
|
||||||
|
|
||||||
_renderCheckboxes() {
|
private renderCheckboxes(): React.ReactNode[] {
|
||||||
const rendered = [];
|
const rendered = [];
|
||||||
for (let i = 0; i < this.state.policies.length; i++) {
|
for (let i = 0; i < this.state.policies.length; i++) {
|
||||||
const policy = this.state.policies[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 key={i} className='mx_InlineTermsAgreement_cbContainer'>
|
||||||
<div>{introText}</div>
|
<div>{introText}</div>
|
||||||
<div className='mx_InlineTermsAgreement_checkbox'>
|
<div className='mx_InlineTermsAgreement_checkbox'>
|
||||||
<StyledCheckbox onChange={() => this._togglePolicy(i)} checked={policy.checked}>
|
<StyledCheckbox onChange={() => this.togglePolicy(i)} checked={policy.checked}>
|
||||||
{_t("Accept")}
|
{_t("Accept")}
|
||||||
</StyledCheckbox>
|
</StyledCheckbox>
|
||||||
</div>
|
</div>
|
||||||
|
@ -103,16 +112,16 @@ export default class InlineTermsAgreement extends React.Component {
|
||||||
return rendered;
|
return rendered;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render(): React.ReactNode {
|
||||||
const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton");
|
const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton");
|
||||||
const hasUnchecked = !!this.state.policies.some(p => !p.checked);
|
const hasUnchecked = !!this.state.policies.some(p => !p.checked);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{this.props.introElement}
|
{this.props.introElement}
|
||||||
{this._renderCheckboxes()}
|
{this.renderCheckboxes()}
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
onClick={this._onContinue}
|
onClick={this.onContinue}
|
||||||
disabled={hasUnchecked || this.state.busy}
|
disabled={hasUnchecked || this.state.busy}
|
||||||
kind="primary_sm"
|
kind="primary_sm"
|
||||||
>
|
>
|
|
@ -15,18 +15,17 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
@replaceableComponent("views.verification.VerificationCancelled")
|
interface IProps {
|
||||||
export default class VerificationCancelled extends React.Component {
|
onDone: () => void;
|
||||||
static propTypes = {
|
}
|
||||||
onDone: PropTypes.func.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
@replaceableComponent("views.verification.VerificationCancelled")
|
||||||
|
export default class VerificationCancelled extends React.Component<IProps> {
|
||||||
|
public render(): React.ReactNode {
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
return <div>
|
return <div>
|
||||||
<p>{_t(
|
<p>{_t(
|
|
@ -15,18 +15,17 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
@replaceableComponent("views.verification.VerificationComplete")
|
interface IProps {
|
||||||
export default class VerificationComplete extends React.Component {
|
onDone: () => void;
|
||||||
static propTypes = {
|
}
|
||||||
onDone: PropTypes.func.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
@replaceableComponent("views.verification.VerificationComplete")
|
||||||
|
export default class VerificationComplete extends React.Component<IProps> {
|
||||||
|
public render(): React.ReactNode {
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
return <div>
|
return <div>
|
||||||
<h2>{_t("Verified!")}</h2>
|
<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 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 { _t, _td } from '../../../languageHandler';
|
||||||
import { PendingActionSpinner } from "../right_panel/EncryptionInfo";
|
import { PendingActionSpinner } from "../right_panel/EncryptionInfo";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
|
@ -23,24 +24,29 @@ import DialogButtons from "../elements/DialogButtons";
|
||||||
import { fixupColorFonts } from '../../../utils/FontManager';
|
import { fixupColorFonts } from '../../../utils/FontManager';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
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) {
|
function capFirst(s) {
|
||||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@replaceableComponent("views.verification.VerificationShowSas")
|
@replaceableComponent("views.verification.VerificationShowSas")
|
||||||
export default class VerificationShowSas extends React.Component {
|
export default class VerificationShowSas extends React.Component<IProps, IState> {
|
||||||
static propTypes = {
|
constructor(props: IProps) {
|
||||||
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) {
|
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
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),
|
// 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.
|
// also make sure we have a working emoji font to display the SAS emojis here.
|
||||||
// This is also done from LoggedInView.
|
// This is also done from LoggedInView.
|
||||||
fixupColorFonts();
|
fixupColorFonts();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMatchClick = () => {
|
private onMatchClick = (): void => {
|
||||||
this.setState({ pending: true });
|
this.setState({ pending: true });
|
||||||
this.props.onDone();
|
this.props.onDone();
|
||||||
};
|
};
|
||||||
|
|
||||||
onDontMatchClick = () => {
|
private onDontMatchClick = (): void => {
|
||||||
this.setState({ cancelling: true });
|
this.setState({ cancelling: true });
|
||||||
this.props.onCancel();
|
this.props.onCancel();
|
||||||
};
|
};
|
|
@ -923,12 +923,6 @@
|
||||||
"You've successfully verified this user.": "You've successfully verified this user.",
|
"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.",
|
"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",
|
"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:",
|
"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 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.",
|
"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",
|
"Edit devices": "Edit devices",
|
||||||
"Security": "Security",
|
"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.",
|
"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",
|
"Verify by scanning": "Verify by scanning",
|
||||||
"Ask %(displayName)s to scan your code:": "Ask %(displayName)s to scan your code:",
|
"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.",
|
"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 { PushRuleVectorState } from "./PushRuleVectorState";
|
||||||
import { NotificationUtils } from "./NotificationUtils";
|
import { NotificationUtils } from "./NotificationUtils";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
kind: Kind;
|
||||||
|
description: string;
|
||||||
|
vectorStateToActions: Action;
|
||||||
|
}
|
||||||
|
|
||||||
class VectorPushRuleDefinition {
|
class VectorPushRuleDefinition {
|
||||||
constructor(opts) {
|
private kind: Kind;
|
||||||
|
private description: string;
|
||||||
|
private vectorStateToActions: Action;
|
||||||
|
|
||||||
|
constructor(opts: IProps) {
|
||||||
this.kind = opts.kind;
|
this.kind = opts.kind;
|
||||||
this.description = opts.description;
|
this.description = opts.description;
|
||||||
this.vectorStateToActions = opts.vectorStateToActions;
|
this.vectorStateToActions = opts.vectorStateToActions;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate the rule actions and its enabled value into vector state
|
// Translate the rule actions and its enabled value into vector state
|
||||||
ruleToVectorState(rule) {
|
public ruleToVectorState(rule): VectorPushRuleDefinition {
|
||||||
let enabled = false;
|
let enabled = false;
|
||||||
if (rule) {
|
if (rule) {
|
||||||
enabled = rule.enabled;
|
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.
|
* The descriptions of rules managed by the Vector UI.
|
||||||
*/
|
*/
|
||||||
export const VectorPushRulesDefinitions = {
|
export const VectorPushRulesDefinitions = {
|
||||||
// Messages containing user's display name
|
// Messages containing user's display name
|
||||||
".m.rule.contains_display_name": new VectorPushRuleDefinition({
|
".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
|
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.
|
vectorStateToActions: { // The actions for each vector state, or null to disable the rule.
|
||||||
on: StandardActions.ACTION_NOTIFY,
|
on: StandardActions.ACTION_NOTIFY,
|
||||||
|
@ -80,7 +101,7 @@ export const VectorPushRulesDefinitions = {
|
||||||
|
|
||||||
// Messages containing user's username (localpart/MXID)
|
// Messages containing user's username (localpart/MXID)
|
||||||
".m.rule.contains_user_name": new VectorPushRuleDefinition({
|
".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
|
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.
|
vectorStateToActions: { // The actions for each vector state, or null to disable the rule.
|
||||||
on: StandardActions.ACTION_NOTIFY,
|
on: StandardActions.ACTION_NOTIFY,
|
||||||
|
@ -91,7 +112,7 @@ export const VectorPushRulesDefinitions = {
|
||||||
|
|
||||||
// Messages containing @room
|
// Messages containing @room
|
||||||
".m.rule.roomnotif": new VectorPushRuleDefinition({
|
".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
|
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.
|
vectorStateToActions: { // The actions for each vector state, or null to disable the rule.
|
||||||
on: StandardActions.ACTION_NOTIFY,
|
on: StandardActions.ACTION_NOTIFY,
|
||||||
|
@ -102,7 +123,7 @@ export const VectorPushRulesDefinitions = {
|
||||||
|
|
||||||
// Messages just sent to the user in a 1:1 room
|
// Messages just sent to the user in a 1:1 room
|
||||||
".m.rule.room_one_to_one": new VectorPushRuleDefinition({
|
".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
|
description: _td("Messages in one-to-one chats"), // passed through _t() translation in src/components/views/settings/Notifications.js
|
||||||
vectorStateToActions: {
|
vectorStateToActions: {
|
||||||
on: StandardActions.ACTION_NOTIFY,
|
on: StandardActions.ACTION_NOTIFY,
|
||||||
|
@ -113,7 +134,7 @@ export const VectorPushRulesDefinitions = {
|
||||||
|
|
||||||
// Encrypted messages just sent to the user in a 1:1 room
|
// Encrypted messages just sent to the user in a 1:1 room
|
||||||
".m.rule.encrypted_room_one_to_one": new VectorPushRuleDefinition({
|
".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
|
description: _td("Encrypted messages in one-to-one chats"), // passed through _t() translation in src/components/views/settings/Notifications.js
|
||||||
vectorStateToActions: {
|
vectorStateToActions: {
|
||||||
on: StandardActions.ACTION_NOTIFY,
|
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
|
// 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.
|
// By opposition, all other room messages are from group chat rooms.
|
||||||
".m.rule.message": new VectorPushRuleDefinition({
|
".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
|
description: _td("Messages in group chats"), // passed through _t() translation in src/components/views/settings/Notifications.js
|
||||||
vectorStateToActions: {
|
vectorStateToActions: {
|
||||||
on: StandardActions.ACTION_NOTIFY,
|
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
|
// 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.
|
// By opposition, all other room messages are from group chat rooms.
|
||||||
".m.rule.encrypted": new VectorPushRuleDefinition({
|
".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
|
description: _td("Encrypted messages in group chats"), // passed through _t() translation in src/components/views/settings/Notifications.js
|
||||||
vectorStateToActions: {
|
vectorStateToActions: {
|
||||||
on: StandardActions.ACTION_NOTIFY,
|
on: StandardActions.ACTION_NOTIFY,
|
||||||
|
@ -150,7 +171,7 @@ export const VectorPushRulesDefinitions = {
|
||||||
|
|
||||||
// Invitation for the user
|
// Invitation for the user
|
||||||
".m.rule.invite_for_me": new VectorPushRuleDefinition({
|
".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
|
description: _td("When I'm invited to a room"), // passed through _t() translation in src/components/views/settings/Notifications.js
|
||||||
vectorStateToActions: {
|
vectorStateToActions: {
|
||||||
on: StandardActions.ACTION_NOTIFY,
|
on: StandardActions.ACTION_NOTIFY,
|
||||||
|
@ -161,7 +182,7 @@ export const VectorPushRulesDefinitions = {
|
||||||
|
|
||||||
// Incoming call
|
// Incoming call
|
||||||
".m.rule.call": new VectorPushRuleDefinition({
|
".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
|
description: _td("Call invitation"), // passed through _t() translation in src/components/views/settings/Notifications.js
|
||||||
vectorStateToActions: {
|
vectorStateToActions: {
|
||||||
on: StandardActions.ACTION_NOTIFY,
|
on: StandardActions.ACTION_NOTIFY,
|
||||||
|
@ -172,7 +193,7 @@ export const VectorPushRulesDefinitions = {
|
||||||
|
|
||||||
// Notifications from bots
|
// Notifications from bots
|
||||||
".m.rule.suppress_notices": new VectorPushRuleDefinition({
|
".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
|
description: _td("Messages sent by bot"), // passed through _t() translation in src/components/views/settings/Notifications.js
|
||||||
vectorStateToActions: {
|
vectorStateToActions: {
|
||||||
// .m.rule.suppress_notices is a "negative" rule, we have to invert its enabled value for vector UI
|
// .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)
|
// Room upgrades (tombstones)
|
||||||
".m.rule.tombstone": new VectorPushRuleDefinition({
|
".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
|
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.
|
vectorStateToActions: { // The actions for each vector state, or null to disable the rule.
|
||||||
on: StandardActions.ACTION_NOTIFY,
|
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