Merge branch 'develop' into feat/add-plain-text-mode

This commit is contained in:
Florian Duros 2022-10-26 17:16:34 +02:00 committed by GitHub
commit 423f87a43a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
65 changed files with 1008 additions and 296 deletions

View file

@ -24,13 +24,6 @@ import { ILoginParams, LoginFlow } from "matrix-js-sdk/src/@types/auth";
import { IMatrixClientCreds } from "./MatrixClientPeg";
import SecurityCustomisations from "./customisations/Security";
export {
IdentityProviderBrand,
IIdentityProvider,
ISSOFlow,
LoginFlow,
} from "matrix-js-sdk/src/@types/auth";
interface ILoginOptions {
defaultDeviceDisplayName?: string;
}

View file

@ -435,7 +435,16 @@ export const Notifier = {
if (actions?.notify) {
this._performCustomEventHandling(ev);
if (SdkContextClass.instance.roomViewStore.getRoomId() === room.roomId &&
const store = SdkContextClass.instance.roomViewStore;
const isViewingRoom = store.getRoomId() === room.roomId;
const threadId: string | undefined = ev.getId() !== ev.threadRootId
? ev.threadRootId
: undefined;
const isViewingThread = store.getThreadId() === threadId;
const isViewingEventTimeline = isViewingRoom && (!threadId || isViewingThread);
if (isViewingEventTimeline &&
UserActivity.sharedInstance().userActiveRecently() &&
!Modal.hasDialogs()
) {

View file

@ -63,6 +63,15 @@ const DEFAULT_ROOM_SUBSCRIPTION_INFO = {
required_state: [
["*", "*"], // all events
],
include_old_rooms: {
timeline_limit: 0,
required_state: [ // state needed to handle space navigation and tombstone chains
[EventType.RoomCreate, ""],
[EventType.RoomTombstone, ""],
[EventType.SpaceChild, "*"],
[EventType.SpaceParent, "*"],
],
},
};
export type PartialSlidingSyncRequest = {
@ -121,6 +130,16 @@ export class SlidingSyncManager {
[EventType.SpaceParent, "*"], // all space parents
[EventType.RoomMember, this.client.getUserId()!], // lets the client calculate that we are in fact in the room
],
include_old_rooms: {
timeline_limit: 0,
required_state: [
[EventType.RoomCreate, ""],
[EventType.RoomTombstone, ""], // lets JS SDK hide rooms which are dead
[EventType.SpaceChild, "*"], // all space children
[EventType.SpaceParent, "*"], // all space parents
[EventType.RoomMember, this.client.getUserId()!], // lets the client calculate that we are in fact in the room
],
},
filters: {
room_types: ["m.space"],
},
@ -176,7 +195,7 @@ export class SlidingSyncManager {
list = {
ranges: [[0, 20]],
sort: [
"by_highlight_count", "by_notification_count", "by_recency",
"by_notification_level", "by_recency",
],
timeline_limit: 1, // most recent message display: though this seems to only be needed for favourites?
required_state: [
@ -187,6 +206,16 @@ export class SlidingSyncManager {
[EventType.RoomCreate, ""], // for isSpaceRoom checks
[EventType.RoomMember, this.client.getUserId()], // lets the client calculate that we are in fact in the room
],
include_old_rooms: {
timeline_limit: 0,
required_state: [
[EventType.RoomCreate, ""],
[EventType.RoomTombstone, ""], // lets JS SDK hide rooms which are dead
[EventType.SpaceChild, "*"], // all space children
[EventType.SpaceParent, "*"], // all space parents
[EventType.RoomMember, this.client.getUserId()!], // lets the client calculate that we are in fact in the room
],
},
};
list = Object.assign(list, updateArgs);
} else {

View file

@ -835,6 +835,13 @@ export default class MessagePanel extends React.Component<IProps, IState> {
: room;
const receipts: IReadReceiptProps[] = [];
if (!receiptDestination) {
logger.debug("Discarding request, could not find the receiptDestination for event: "
+ this.context.threadId);
return receipts;
}
receiptDestination.getReceiptsForEvent(event).forEach((r) => {
if (
!r.userId ||

View file

@ -55,6 +55,7 @@ import Spinner from "../views/elements/Spinner";
import { ComposerInsertPayload, ComposerType } from "../../dispatcher/payloads/ComposerInsertPayload";
import Heading from '../views/typography/Heading';
import { SdkContextClass } from '../../contexts/SDKContext';
import { ThreadPayload } from '../../dispatcher/payloads/ThreadPayload';
interface IProps {
room: Room;
@ -132,6 +133,11 @@ export default class ThreadView extends React.Component<IProps, IState> {
metricsTrigger: undefined, // room doesn't change
});
}
dis.dispatch<ThreadPayload>({
action: Action.ViewThread,
thread_id: null,
});
}
public componentDidUpdate(prevProps) {
@ -225,6 +231,10 @@ export default class ThreadView extends React.Component<IProps, IState> {
};
private async postThreadUpdate(thread: Thread): Promise<void> {
dis.dispatch<ThreadPayload>({
action: Action.ViewThread,
thread_id: thread.id,
});
thread.emit(ThreadEvent.ViewThread);
await thread.fetchInitialEvents();
this.updateThreadRelation();

View file

@ -18,9 +18,10 @@ import React, { ReactNode } from 'react';
import { ConnectionError, MatrixError } from "matrix-js-sdk/src/http-api";
import classNames from "classnames";
import { logger } from "matrix-js-sdk/src/logger";
import { ISSOFlow, LoginFlow } from "matrix-js-sdk/src/@types/auth";
import { _t, _td } from '../../../languageHandler';
import Login, { ISSOFlow, LoginFlow } from '../../../Login';
import Login from '../../../Login';
import SdkConfig from '../../../SdkConfig';
import { messageForResourceLimitError } from '../../../utils/ErrorUtils';
import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils";

View file

@ -19,6 +19,7 @@ import React, { Fragment, ReactNode } from 'react';
import { MatrixClient } from "matrix-js-sdk/src/client";
import classNames from "classnames";
import { logger } from "matrix-js-sdk/src/logger";
import { ISSOFlow } from "matrix-js-sdk/src/@types/auth";
import { _t, _td } from '../../../languageHandler';
import { messageForResourceLimitError } from '../../../utils/ErrorUtils';
@ -26,7 +27,7 @@ import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils";
import * as Lifecycle from '../../../Lifecycle';
import { IMatrixClientCreds, MatrixClientPeg } from "../../../MatrixClientPeg";
import AuthPage from "../../views/auth/AuthPage";
import Login, { ISSOFlow } from "../../../Login";
import Login from "../../../Login";
import dis from "../../../dispatcher/dispatcher";
import SSOButtons from "../../views/elements/SSOButtons";
import ServerPicker from '../../views/elements/ServerPicker';

View file

@ -17,13 +17,14 @@ limitations under the License.
import React from 'react';
import { logger } from "matrix-js-sdk/src/logger";
import { Optional } from "matrix-events-sdk";
import { ISSOFlow, LoginFlow } from "matrix-js-sdk/src/@types/auth";
import { _t } from '../../../languageHandler';
import dis from '../../../dispatcher/dispatcher';
import * as Lifecycle from '../../../Lifecycle';
import Modal from '../../../Modal';
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { ISSOFlow, LoginFlow, sendLoginRequest } from "../../../Login";
import { sendLoginRequest } from "../../../Login";
import AuthPage from "../../views/auth/AuthPage";
import { SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY } from "../../../BasePlatform";
import SSOButtons from "../../views/elements/SSOButtons";

View file

@ -75,7 +75,7 @@ type IProps<T extends keyof JSX.IntrinsicElements> = DynamicHtmlElementProps<T>
onClick: ((e: ButtonEvent) => void | Promise<void>) | null;
};
interface IAccessibleButtonProps extends React.InputHTMLAttributes<Element> {
export interface IAccessibleButtonProps extends React.InputHTMLAttributes<Element> {
ref?: React.Ref<Element>;
}

View file

@ -0,0 +1,56 @@
/*
Copyright 2022 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 { _t } from '../../../languageHandler';
import Modal from '../../../Modal';
import InfoDialog from '../dialogs/InfoDialog';
import AccessibleButton, { IAccessibleButtonProps } from './AccessibleButton';
interface Props extends IAccessibleButtonProps {
title: string;
description: string | React.ReactNode;
}
const LearnMore: React.FC<Props> = ({
title,
description,
...rest
}) => {
const onClick = () => {
Modal.createDialog(
InfoDialog,
{
title,
description,
button: _t('Got it'),
hasCloseButton: true,
},
);
};
return <AccessibleButton
{...rest}
kind='link_inline'
onClick={onClick}
className='mx_LearnMore_button'
>
{ _t('Learn more') }
</AccessibleButton>;
};
export default LearnMore;

View file

@ -19,11 +19,11 @@ import { chunk } from "lodash";
import classNames from "classnames";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { Signup } from "@matrix-org/analytics-events/types/typescript/Signup";
import { IdentityProviderBrand, IIdentityProvider, ISSOFlow } from "matrix-js-sdk/src/@types/auth";
import PlatformPeg from "../../../PlatformPeg";
import AccessibleButton from "./AccessibleButton";
import { _t } from "../../../languageHandler";
import { IdentityProviderBrand, IIdentityProvider, ISSOFlow } from "../../../Login";
import AccessibleTooltipButton from "./AccessibleTooltipButton";
import { mediaFromMxc } from "../../../customisations/Media";
import { PosthogAnalytics } from "../../../PosthogAnalytics";

View file

@ -111,7 +111,12 @@ export default class NotificationBadge extends React.PureComponent<XOR<IProps, I
public render(): React.ReactElement {
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
const { notification, showUnsentTooltip, onClick } = this.props;
const { notification, showUnsentTooltip, forceCount, onClick } = this.props;
if (notification.isIdle) return null;
if (forceCount) {
if (!notification.hasUnreadCount) return null; // Can't render a badge
}
let label: string;
let tooltip: JSX.Element;

View file

@ -570,8 +570,7 @@ export default class RoomSublist extends React.Component<IProps, IState> {
const slidingList = SlidingSyncManager.instance.slidingSync.getList(slidingSyncIndex);
isAlphabetical = slidingList.sort[0] === "by_name";
isUnreadFirst = (
slidingList.sort[0] === "by_highlight_count" ||
slidingList.sort[0] === "by_notification_count"
slidingList.sort[0] === "by_notification_level"
);
}

View file

@ -38,6 +38,7 @@ import {
import { DevicesState } from './useOwnDevices';
import FilteredDeviceListHeader from './FilteredDeviceListHeader';
import Spinner from '../../elements/Spinner';
import LearnMore from '../../elements/LearnMore';
interface Props {
devices: DevicesDictionary;
@ -73,48 +74,88 @@ const getFilteredSortedDevices = (devices: DevicesDictionary, filter?: DeviceSec
const ALL_FILTER_ID = 'ALL';
type DeviceFilterKey = DeviceSecurityVariation | typeof ALL_FILTER_ID;
const securityCardContent: Record<DeviceSecurityVariation, {
title: string;
description: string;
learnMoreDescription: React.ReactNode | string;
}> = {
[DeviceSecurityVariation.Verified]: {
title: _t('Verified sessions'),
description: _t('For best security, sign out from any session that you don\'t recognize or use anymore.'),
learnMoreDescription: <>
<p>{ _t('Verified sessions have logged in with your credentials and then been verified, either using your secure passphrase or by cross-verifying.') }
</p>
<p>
{ _t(
`This means they hold encryption keys for your previous messages, ` +
`and confirm to other users you are communicating with that these sessions are really you.`,
)
}
</p>
</>,
},
[DeviceSecurityVariation.Unverified]: {
title: _t('Unverified sessions'),
description: _t(
`Verify your sessions for enhanced secure messaging or ` +
`sign out from those you don't recognize or use anymore.`,
),
learnMoreDescription: <>
<p>{ _t('Unverified sessions are sessions that have logged in with your credentials but have not been cross-verified.') }
</p>
<p>
{ _t(
`You should make especially certain that you recognise these sessions ` +
`as they could represent an unauthorised use of your account.`,
)
}
</p>
</>,
},
[DeviceSecurityVariation.Inactive]: {
title: _t('Inactive sessions'),
description: _t(
`Consider signing out from old sessions ` +
`(%(inactiveAgeDays)s days or older) you don't use anymore.`,
{ inactiveAgeDays: INACTIVE_DEVICE_AGE_DAYS },
),
learnMoreDescription: <>
<p>{ _t('Inactive sessions are sessions you have not used in some time, but they continue to receive encryption keys.') }
</p>
<p>
{ _t(
`Removing inactive sessions improves security and performance, ` +
`and makes it easier for you to identify if a new session is suspicious.`,
)
}
</p>
</>,
},
};
const isSecurityVariation = (filter?: DeviceFilterKey): filter is DeviceSecurityVariation =>
Object.values<string>(DeviceSecurityVariation).includes(filter);
const FilterSecurityCard: React.FC<{ filter?: DeviceFilterKey }> = ({ filter }) => {
switch (filter) {
case DeviceSecurityVariation.Verified:
return <div className='mx_FilteredDeviceList_securityCard'>
<DeviceSecurityCard
variation={DeviceSecurityVariation.Verified}
heading={_t('Verified sessions')}
description={_t(
`For best security, sign out from any session` +
` that you don't recognize or use anymore.`,
)}
/>
</div>
;
case DeviceSecurityVariation.Unverified:
return <div className='mx_FilteredDeviceList_securityCard'>
<DeviceSecurityCard
variation={DeviceSecurityVariation.Unverified}
heading={_t('Unverified sessions')}
description={_t(
`Verify your sessions for enhanced secure messaging or sign out`
+ ` from those you don't recognize or use anymore.`,
)}
/>
</div>
;
case DeviceSecurityVariation.Inactive:
return <div className='mx_FilteredDeviceList_securityCard'>
<DeviceSecurityCard
variation={DeviceSecurityVariation.Inactive}
heading={_t('Inactive sessions')}
description={_t(
`Consider signing out from old sessions ` +
`(%(inactiveAgeDays)s days or older) you don't use anymore`,
{ inactiveAgeDays: INACTIVE_DEVICE_AGE_DAYS },
)}
/>
</div>
;
default:
return null;
if (isSecurityVariation(filter)) {
const { title, description, learnMoreDescription } = securityCardContent[filter];
return <div className='mx_FilteredDeviceList_securityCard'>
<DeviceSecurityCard
variation={filter}
heading={title}
description={<span>
{ description }
<LearnMore
title={title}
description={learnMoreDescription}
/>
</span>}
/>
</div>
;
}
return null;
};
const getNoResultsMessage = (filter?: DeviceSecurityVariation): string => {

View file

@ -116,6 +116,11 @@ export enum Action {
*/
ViewRoom = "view_room",
/**
* Changes thread based on payload parameters. Should be used with ThreadPayload.
*/
ViewThread = "view_thread",
/**
* Changes room based on room list order and payload parameters. Should be used with ViewRoomDeltaPayload.
*/

View file

@ -0,0 +1,26 @@
/*
Copyright 2022 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 { ActionPayload } from "../payloads";
import { Action } from "../actions";
/* eslint-disable camelcase */
export interface ThreadPayload extends Pick<ActionPayload, "action"> {
action: Action.ViewThread;
thread_id: string | null;
}
/* eslint-enable camelcase */

View file

@ -52,7 +52,6 @@ export const useSlidingSyncRoomSearch = () => {
ranges: [[0, limit]],
filters: {
room_name_like: term,
is_tombstoned: false,
},
});
const rooms = [];

View file

@ -1778,10 +1778,16 @@
"Verify session": "Verify session",
"Verified sessions": "Verified sessions",
"For best security, sign out from any session that you don't recognize or use anymore.": "For best security, sign out from any session that you don't recognize or use anymore.",
"Verified sessions have logged in with your credentials and then been verified, either using your secure passphrase or by cross-verifying.": "Verified sessions have logged in with your credentials and then been verified, either using your secure passphrase or by cross-verifying.",
"This means they hold encryption keys for your previous messages, and confirm to other users you are communicating with that these sessions are really you.": "This means they hold encryption keys for your previous messages, and confirm to other users you are communicating with that these sessions are really you.",
"Unverified sessions": "Unverified sessions",
"Verify your sessions for enhanced secure messaging or sign out from those you don't recognize or use anymore.": "Verify your sessions for enhanced secure messaging or sign out from those you don't recognize or use anymore.",
"Unverified sessions are sessions that have logged in with your credentials but have not been cross-verified.": "Unverified sessions are sessions that have logged in with your credentials but have not been cross-verified.",
"You should make especially certain that you recognise these sessions as they could represent an unauthorised use of your account.": "You should make especially certain that you recognise these sessions as they could represent an unauthorised use of your account.",
"Inactive sessions": "Inactive sessions",
"Consider signing out from old sessions (%(inactiveAgeDays)s days or older) you don't use anymore": "Consider signing out from old sessions (%(inactiveAgeDays)s days or older) you don't use anymore",
"Consider signing out from old sessions (%(inactiveAgeDays)s days or older) you don't use anymore.": "Consider signing out from old sessions (%(inactiveAgeDays)s days or older) you don't use anymore.",
"Inactive sessions are sessions you have not used in some time, but they continue to receive encryption keys.": "Inactive sessions are sessions you have not used in some time, but they continue to receive encryption keys.",
"Removing inactive sessions improves security and performance, and makes it easier for you to identify if a new session is suspicious.": "Removing inactive sessions improves security and performance, and makes it easier for you to identify if a new session is suspicious.",
"No verified sessions found.": "No verified sessions found.",
"No unverified sessions found.": "No unverified sessions found.",
"No inactive sessions found.": "No inactive sessions found.",
@ -1801,6 +1807,7 @@
"Security recommendations": "Security recommendations",
"Improve your account security by following these recommendations": "Improve your account security by following these recommendations",
"View all": "View all",
"Consider signing out from old sessions (%(inactiveAgeDays)s days or older) you don't use anymore": "Consider signing out from old sessions (%(inactiveAgeDays)s days or older) you don't use anymore",
"Failed to set pusher state": "Failed to set pusher state",
"Unable to remove contact information": "Unable to remove contact information",
"Remove %(email)s?": "Remove %(email)s?",

View file

@ -50,6 +50,7 @@ import { awaitRoomDownSync } from "../utils/RoomUpgrade";
import { UPDATE_EVENT } from "./AsyncStore";
import { SdkContextClass } from "../contexts/SDKContext";
import { CallStore } from "./CallStore";
import { ThreadPayload } from "../dispatcher/payloads/ThreadPayload";
const NUM_JOIN_RETRY = 5;
@ -66,6 +67,10 @@ interface State {
* The ID of the room currently being viewed
*/
roomId: string | null;
/**
* The ID of the thread currently being viewed
*/
threadId: string | null;
/**
* The ID of the room being subscribed to (in Sliding Sync)
*/
@ -109,6 +114,7 @@ const INITIAL_STATE: State = {
joining: false,
joinError: null,
roomId: null,
threadId: null,
subscribingRoomId: null,
initialEventId: null,
initialEventPixelOffset: null,
@ -200,6 +206,9 @@ export class RoomViewStore extends EventEmitter {
case Action.ViewRoom:
this.viewRoom(payload);
break;
case Action.ViewThread:
this.viewThread(payload);
break;
// for these events blank out the roomId as we are no longer in the RoomView
case 'view_welcome_page':
case Action.ViewHomePage:
@ -430,6 +439,12 @@ export class RoomViewStore extends EventEmitter {
}
}
private viewThread(payload: ThreadPayload): void {
this.setState({
threadId: payload.thread_id,
});
}
private viewRoomError(payload: ViewRoomErrorPayload): void {
this.setState({
roomId: payload.room_id,
@ -550,6 +565,10 @@ export class RoomViewStore extends EventEmitter {
return this.state.roomId;
}
public getThreadId(): Optional<string> {
return this.state.threadId;
}
// The event to scroll to when the room is first viewed
public getInitialEventId(): Optional<string> {
return this.state.initialEventId;

View file

@ -37,6 +37,9 @@ export class RoomNotificationState extends NotificationState implements IDestroy
this.room.on(RoomEvent.Receipt, this.handleReadReceipt);
this.room.on(RoomEvent.MyMembership, this.handleMembershipUpdate);
this.room.on(RoomEvent.LocalEchoUpdated, this.handleLocalEchoUpdated);
this.room.on(RoomEvent.Timeline, this.handleRoomEventUpdate);
this.room.on(RoomEvent.Redaction, this.handleRoomEventUpdate);
this.room.on(RoomEvent.UnreadNotifications, this.handleNotificationCountUpdate); // for server-sent counts
if (cli.canSupport.get(Feature.ThreadUnreadNotifications) === ServerSupport.Unsupported) {
this.threadsState?.on(NotificationStateEvents.Update, this.handleThreadsUpdate);
@ -56,6 +59,8 @@ export class RoomNotificationState extends NotificationState implements IDestroy
this.room.removeListener(RoomEvent.Receipt, this.handleReadReceipt);
this.room.removeListener(RoomEvent.MyMembership, this.handleMembershipUpdate);
this.room.removeListener(RoomEvent.LocalEchoUpdated, this.handleLocalEchoUpdated);
this.room.removeListener(RoomEvent.Timeline, this.handleRoomEventUpdate);
this.room.removeListener(RoomEvent.Redaction, this.handleRoomEventUpdate);
if (cli.canSupport.get(Feature.ThreadUnreadNotifications) === ServerSupport.Unsupported) {
this.room.removeListener(RoomEvent.UnreadNotifications, this.handleNotificationCountUpdate);
} else if (this.threadsState) {
@ -93,6 +98,11 @@ export class RoomNotificationState extends NotificationState implements IDestroy
this.updateNotificationState();
};
private handleRoomEventUpdate = (event: MatrixEvent, room: Room | null) => {
if (room?.roomId !== this.room.roomId) return; // ignore - not for us or notifications timeline
this.updateNotificationState();
};
private handleAccountDataUpdate = (ev: MatrixEvent) => {
if (ev.getType() === "m.push_rules") {
this.updateNotificationState();

View file

@ -24,7 +24,7 @@ import SettingsStore from "../../settings/SettingsStore";
import { DefaultTagID, OrderedDefaultTagIDs, RoomUpdateCause, TagID } from "./models";
import { IListOrderingMap, ITagMap, ITagSortingMap, ListAlgorithm, SortAlgorithm } from "./algorithms/models";
import { ActionPayload } from "../../dispatcher/payloads";
import defaultDispatcher from "../../dispatcher/dispatcher";
import defaultDispatcher, { MatrixDispatcher } from "../../dispatcher/dispatcher";
import { readReceiptChangeIsFor } from "../../utils/read-receipts";
import { FILTER_CHANGED, IFilterCondition } from "./filters/IFilterCondition";
import { Algorithm, LIST_UPDATED_EVENT } from "./algorithms/Algorithm";
@ -65,8 +65,8 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> implements
this.emit(LISTS_UPDATE_EVENT);
});
constructor() {
super(defaultDispatcher);
constructor(dis: MatrixDispatcher) {
super(dis);
this.setMaxListeners(20); // RoomList + LeftPanel + 8xRoomSubList + spares
this.algorithm.start();
}
@ -613,11 +613,11 @@ export default class RoomListStore {
if (!RoomListStore.internalInstance) {
if (SettingsStore.getValue("feature_sliding_sync")) {
logger.info("using SlidingRoomListStoreClass");
const instance = new SlidingRoomListStoreClass();
const instance = new SlidingRoomListStoreClass(defaultDispatcher, SdkContextClass.instance);
instance.start();
RoomListStore.internalInstance = instance;
} else {
const instance = new RoomListStoreClass();
const instance = new RoomListStoreClass(defaultDispatcher);
instance.start();
RoomListStore.internalInstance = instance;
}

View file

@ -21,12 +21,10 @@ import { MSC3575Filter, SlidingSyncEvent } from "matrix-js-sdk/src/sliding-sync"
import { RoomUpdateCause, TagID, OrderedDefaultTagIDs, DefaultTagID } from "./models";
import { ITagMap, ListAlgorithm, SortAlgorithm } from "./algorithms/models";
import { ActionPayload } from "../../dispatcher/payloads";
import defaultDispatcher from "../../dispatcher/dispatcher";
import { MatrixDispatcher } from "../../dispatcher/dispatcher";
import { IFilterCondition } from "./filters/IFilterCondition";
import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
import { RoomListStore as Interface, RoomListStoreEvent } from "./Interface";
import { SlidingSyncManager } from "../../SlidingSyncManager";
import SpaceStore from "../spaces/SpaceStore";
import { MetaSpace, SpaceKey, UPDATE_SELECTED_SPACE } from "../spaces";
import { LISTS_LOADING_EVENT } from "./RoomListStore";
import { UPDATE_EVENT } from "../AsyncStore";
@ -38,7 +36,7 @@ interface IState {
export const SlidingSyncSortToFilter: Record<SortAlgorithm, string[]> = {
[SortAlgorithm.Alphabetic]: ["by_name", "by_recency"],
[SortAlgorithm.Recent]: ["by_highlight_count", "by_notification_count", "by_recency"],
[SortAlgorithm.Recent]: ["by_notification_level", "by_recency"],
[SortAlgorithm.Manual]: ["by_recency"],
};
@ -48,21 +46,18 @@ const filterConditions: Record<TagID, MSC3575Filter> = {
},
[DefaultTagID.Favourite]: {
tags: ["m.favourite"],
is_tombstoned: false,
},
// TODO https://github.com/vector-im/element-web/issues/23207
// DefaultTagID.SavedItems,
[DefaultTagID.DM]: {
is_dm: true,
is_invite: false,
is_tombstoned: false,
// If a DM has a Favourite & Low Prio tag then it'll be shown in those lists instead
not_tags: ["m.favourite", "m.lowpriority"],
},
[DefaultTagID.Untagged]: {
is_dm: false,
is_invite: false,
is_tombstoned: false,
not_room_types: ["m.space"],
not_tags: ["m.favourite", "m.lowpriority"],
// spaces filter added dynamically
@ -71,7 +66,6 @@ const filterConditions: Record<TagID, MSC3575Filter> = {
tags: ["m.lowpriority"],
// If a room has both Favourite & Low Prio tags then it'll be shown under Favourites
not_tags: ["m.favourite"],
is_tombstoned: false,
},
// TODO https://github.com/vector-im/element-web/issues/23207
// DefaultTagID.ServerNotice,
@ -87,25 +81,25 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
private counts: Record<TagID, number> = {};
private stickyRoomId: string | null;
public constructor() {
super(defaultDispatcher);
public constructor(dis: MatrixDispatcher, private readonly context: SdkContextClass) {
super(dis);
this.setMaxListeners(20); // RoomList + LeftPanel + 8xRoomSubList + spares
}
public async setTagSorting(tagId: TagID, sort: SortAlgorithm) {
logger.info("SlidingRoomListStore.setTagSorting ", tagId, sort);
this.tagIdToSortAlgo[tagId] = sort;
const slidingSyncIndex = SlidingSyncManager.instance.getOrAllocateListIndex(tagId);
const slidingSyncIndex = this.context.slidingSyncManager.getOrAllocateListIndex(tagId);
switch (sort) {
case SortAlgorithm.Alphabetic:
await SlidingSyncManager.instance.ensureListRegistered(
await this.context.slidingSyncManager.ensureListRegistered(
slidingSyncIndex, {
sort: SlidingSyncSortToFilter[SortAlgorithm.Alphabetic],
},
);
break;
case SortAlgorithm.Recent:
await SlidingSyncManager.instance.ensureListRegistered(
await this.context.slidingSyncManager.ensureListRegistered(
slidingSyncIndex, {
sort: SlidingSyncSortToFilter[SortAlgorithm.Recent],
},
@ -174,10 +168,13 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
// check all lists for each tag we know about and see if the room is there
const tags: TagID[] = [];
for (const tagId in this.tagIdToSortAlgo) {
const index = SlidingSyncManager.instance.getOrAllocateListIndex(tagId);
const { roomIndexToRoomId } = SlidingSyncManager.instance.slidingSync.getListData(index);
for (const roomIndex in roomIndexToRoomId) {
const roomId = roomIndexToRoomId[roomIndex];
const index = this.context.slidingSyncManager.getOrAllocateListIndex(tagId);
const listData = this.context.slidingSyncManager.slidingSync.getListData(index);
if (!listData) {
continue;
}
for (const roomIndex in listData.roomIndexToRoomId) {
const roomId = listData.roomIndexToRoomId[roomIndex];
if (roomId === room.roomId) {
tags.push(tagId);
break;
@ -207,7 +204,7 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
// this room will not move due to it being viewed: it is sticky. This can be null to indicate
// no sticky room if you aren't viewing a room.
this.stickyRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
this.stickyRoomId = this.context.roomViewStore.getRoomId();
let stickyRoomNewIndex = -1;
const stickyRoomOldIndex = (tagMap[tagId] || []).findIndex((room) => {
return room.roomId === this.stickyRoomId;
@ -264,7 +261,7 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
}
private onSlidingSyncListUpdate(listIndex: number, joinCount: number, roomIndexToRoomId: Record<number, string>) {
const tagId = SlidingSyncManager.instance.listIdForIndex(listIndex);
const tagId = this.context.slidingSyncManager.listIdForIndex(listIndex);
this.counts[tagId]= joinCount;
this.refreshOrderedLists(tagId, roomIndexToRoomId);
// let the UI update
@ -273,7 +270,7 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
private onRoomViewStoreUpdated() {
// we only care about this to know when the user has clicked on a room to set the stickiness value
if (SdkContextClass.instance.roomViewStore.getRoomId() === this.stickyRoomId) {
if (this.context.roomViewStore.getRoomId() === this.stickyRoomId) {
return;
}
@ -296,14 +293,17 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
if (room) {
// resort it based on the slidingSync view of the list. This may cause this old sticky
// room to cease to exist.
const index = SlidingSyncManager.instance.getOrAllocateListIndex(tagId);
const { roomIndexToRoomId } = SlidingSyncManager.instance.slidingSync.getListData(index);
this.refreshOrderedLists(tagId, roomIndexToRoomId);
const index = this.context.slidingSyncManager.getOrAllocateListIndex(tagId);
const listData = this.context.slidingSyncManager.slidingSync.getListData(index);
if (!listData) {
continue;
}
this.refreshOrderedLists(tagId, listData.roomIndexToRoomId);
hasUpdatedAnyList = true;
}
}
// in the event we didn't call refreshOrderedLists, it helps to still remember the sticky room ID.
this.stickyRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
this.stickyRoomId = this.context.roomViewStore.getRoomId();
if (hasUpdatedAnyList) {
this.emit(LISTS_UPDATE_EVENT);
@ -313,11 +313,11 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
protected async onReady(): Promise<any> {
logger.info("SlidingRoomListStore.onReady");
// permanent listeners: never get destroyed. Could be an issue if we want to test this in isolation.
SlidingSyncManager.instance.slidingSync.on(SlidingSyncEvent.List, this.onSlidingSyncListUpdate.bind(this));
SdkContextClass.instance.roomViewStore.addListener(UPDATE_EVENT, this.onRoomViewStoreUpdated.bind(this));
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdated.bind(this));
if (SpaceStore.instance.activeSpace) {
this.onSelectedSpaceUpdated(SpaceStore.instance.activeSpace, false);
this.context.slidingSyncManager.slidingSync.on(SlidingSyncEvent.List, this.onSlidingSyncListUpdate.bind(this));
this.context.roomViewStore.addListener(UPDATE_EVENT, this.onRoomViewStoreUpdated.bind(this));
this.context.spaceStore.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdated.bind(this));
if (this.context.spaceStore.activeSpace) {
this.onSelectedSpaceUpdated(this.context.spaceStore.activeSpace, false);
}
// sliding sync has an initial response for spaces. Now request all the lists.
@ -332,8 +332,8 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
const sort = SortAlgorithm.Recent; // default to recency sort, TODO: read from config
this.tagIdToSortAlgo[tagId] = sort;
this.emit(LISTS_LOADING_EVENT, tagId, true);
const index = SlidingSyncManager.instance.getOrAllocateListIndex(tagId);
SlidingSyncManager.instance.ensureListRegistered(index, {
const index = this.context.slidingSyncManager.getOrAllocateListIndex(tagId);
this.context.slidingSyncManager.ensureListRegistered(index, {
filters: filter,
sort: SlidingSyncSortToFilter[sort],
}).then(() => {
@ -350,9 +350,18 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
const oldSpace = filters.spaces?.[0];
filters.spaces = (activeSpace && activeSpace != MetaSpace.Home) ? [activeSpace] : undefined;
if (oldSpace !== activeSpace) {
// include subspaces in this list
this.context.spaceStore.traverseSpace(activeSpace, (roomId: string) => {
if (roomId === activeSpace) {
return;
}
filters.spaces.push(roomId); // add subspace
}, false);
this.emit(LISTS_LOADING_EVENT, tagId, true);
SlidingSyncManager.instance.ensureListRegistered(
SlidingSyncManager.instance.getOrAllocateListIndex(tagId),
const index = this.context.slidingSyncManager.getOrAllocateListIndex(tagId);
this.context.slidingSyncManager.ensureListRegistered(
index,
{
filters: filters,
},

View file

@ -18,6 +18,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
import {
ClientWidgetApi,
IModalWidgetOpenRequest,
IRoomEvent,
IStickerActionRequest,
IStickyActionRequest,
ITemplateParams,
@ -465,7 +466,7 @@ export class StopGapWidget extends EventEmitter {
private onToDeviceEvent = async (ev: MatrixEvent) => {
await this.client.decryptEventIfNeeded(ev);
if (ev.isDecryptionFailure()) return;
await this.messaging.feedToDevice(ev.getEffectiveEvent(), ev.isEncrypted());
await this.messaging.feedToDevice(ev.getEffectiveEvent() as IRoomEvent, ev.isEncrypted());
};
private feedEvent(ev: MatrixEvent) {
@ -509,7 +510,7 @@ export class StopGapWidget extends EventEmitter {
this.readUpToMap[ev.getRoomId()] = ev.getId();
const raw = ev.getEffectiveEvent();
this.messaging.feedEvent(raw, this.eventListenerRoomId).catch(e => {
this.messaging.feedEvent(raw as IRoomEvent, this.eventListenerRoomId).catch(e => {
logger.error("Error sending event to widget: ", e);
});
}

View file

@ -34,7 +34,7 @@ import {
} from "matrix-widget-api";
import { ClientEvent, ITurnServer as IClientTurnServer } from "matrix-js-sdk/src/client";
import { EventType } from "matrix-js-sdk/src/@types/event";
import { IContent, IEvent, MatrixEvent } from "matrix-js-sdk/src/models/event";
import { IContent, MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Room } from "matrix-js-sdk/src/models/room";
import { logger } from "matrix-js-sdk/src/logger";
import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread";
@ -310,7 +310,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary
const rooms = this.pickRooms(roomIds);
const allResults: IEvent[] = [];
const allResults: IRoomEvent[] = [];
for (const room of rooms) {
const results: MatrixEvent[] = [];
const events = room.getLiveTimeline().getEvents(); // timelines are most recent last
@ -323,7 +323,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
results.push(ev);
}
results.forEach(e => allResults.push(e.getEffectiveEvent()));
results.forEach(e => allResults.push(e.getEffectiveEvent() as IRoomEvent));
}
return allResults;
}
@ -337,7 +337,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary
const rooms = this.pickRooms(roomIds);
const allResults: IEvent[] = [];
const allResults: IRoomEvent[] = [];
for (const room of rooms) {
const results: MatrixEvent[] = [];
const state: Map<string, MatrixEvent> = room.currentState.events.get(eventType);
@ -350,7 +350,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
}
}
results.slice(0, limitPerRoom).forEach(e => allResults.push(e.getEffectiveEvent()));
results.slice(0, limitPerRoom).forEach(e => allResults.push(e.getEffectiveEvent() as IRoomEvent));
}
return allResults;
}
@ -459,7 +459,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
});
return {
chunk: events.map(e => e.getEffectiveEvent()),
chunk: events.map(e => e.getEffectiveEvent() as IRoomEvent),
nextBatch,
prevBatch,
};

View file

@ -74,14 +74,14 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
}
return (
<div className="mx_VoiceBroadcastPlaybackBody">
<div className="mx_VoiceBroadcastBody">
<VoiceBroadcastHeader
live={live}
sender={sender}
room={room}
showBroadcast={true}
/>
<div className="mx_VoiceBroadcastPlaybackBody_controls">
<div className="mx_VoiceBroadcastBody_controls">
{ control }
</div>
</div>

View file

@ -27,7 +27,7 @@ export const VoiceBroadcastRecordingBody: React.FC<VoiceBroadcastRecordingBodyPr
} = useVoiceBroadcastRecording(recording);
return (
<div className="mx_VoiceBroadcastRecordingBody">
<div className="mx_VoiceBroadcastBody">
<VoiceBroadcastHeader
live={live}
sender={sender}

View file

@ -52,15 +52,15 @@ export const VoiceBroadcastRecordingPip: React.FC<VoiceBroadcastRecordingPipProp
: <VoiceBroadcastControl onClick={toggleRecording} icon={PauseIcon} label={_t("pause voice broadcast")} />;
return <div
className="mx_VoiceBroadcastRecordingPip"
className="mx_VoiceBroadcastBody mx_VoiceBroadcastBody--pip"
>
<VoiceBroadcastHeader
live={live}
sender={sender}
room={room}
/>
<hr className="mx_VoiceBroadcastRecordingPip_divider" />
<div className="mx_VoiceBroadcastRecordingPip_controls">
<hr className="mx_VoiceBroadcastBody_divider" />
<div className="mx_VoiceBroadcastBody_controls">
{ toggleControl }
<VoiceBroadcastControl
icon={StopIcon}

View file

@ -68,7 +68,7 @@ export const useVoiceBroadcastRecording = (recording: VoiceBroadcastRecording) =
const live = [
VoiceBroadcastInfoState.Started,
VoiceBroadcastInfoState.Paused,
VoiceBroadcastInfoState.Running,
VoiceBroadcastInfoState.Resumed,
].includes(recordingState);
return {

View file

@ -49,7 +49,7 @@ export const VoiceBroadcastChunkEventType = "io.element.voice_broadcast_chunk";
export enum VoiceBroadcastInfoState {
Started = "started",
Paused = "paused",
Running = "running",
Resumed = "resumed",
Stopped = "stopped",
}

View file

@ -105,15 +105,15 @@ export class VoiceBroadcastRecording
public async resume(): Promise<void> {
if (this.state !== VoiceBroadcastInfoState.Paused) return;
this.setState(VoiceBroadcastInfoState.Running);
this.setState(VoiceBroadcastInfoState.Resumed);
await this.getRecorder().start();
await this.sendInfoStateEvent(VoiceBroadcastInfoState.Running);
await this.sendInfoStateEvent(VoiceBroadcastInfoState.Resumed);
}
public toggle = async (): Promise<void> => {
if (this.getState() === VoiceBroadcastInfoState.Paused) return this.resume();
if ([VoiceBroadcastInfoState.Started, VoiceBroadcastInfoState.Running].includes(this.getState())) {
if ([VoiceBroadcastInfoState.Started, VoiceBroadcastInfoState.Resumed].includes(this.getState())) {
return this.pause();
}
};