Merge branch 'develop' into gsouquet/fix-backdrop-filter

This commit is contained in:
Germain Souquet 2021-07-06 15:09:55 +02:00
commit fbac730c62
16 changed files with 268 additions and 262 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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>
);
}
}

View file

@ -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();
};

View file

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

View file

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

View file

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

View 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;