mirror of
https://github.com/element-hq/element-web.git
synced 2025-12-19 02:20:17 +00:00
Compare commits
44 Commits
hs/safety-
...
midhun/mod
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6dc1431270 | ||
|
|
f4e8e79af8 | ||
|
|
507eaa02df | ||
|
|
b94d40f166 | ||
|
|
c2d68f8dc0 | ||
|
|
3be766d79c | ||
|
|
335491eabc | ||
|
|
2449557aa8 | ||
|
|
eebf227cf4 | ||
|
|
ebc9e3ace6 | ||
|
|
61306a1e4a | ||
|
|
a9fed64637 | ||
|
|
8a875e8c6d | ||
|
|
620ba9231d | ||
|
|
f2104b5ec0 | ||
|
|
1c0738be0f | ||
|
|
c78461db0b | ||
|
|
2b05d51e41 | ||
|
|
6f6b3bdd8f | ||
|
|
da11cff6ff | ||
|
|
302b6567ea | ||
|
|
b8c79f46ee | ||
|
|
0e8a617beb | ||
|
|
a94328a125 | ||
|
|
4d7d06bfc0 | ||
|
|
c31d4fea8d | ||
|
|
a5f3876a38 | ||
|
|
206905c2f5 | ||
|
|
51499fa106 | ||
|
|
1ebead1c8a | ||
|
|
738eac9b90 | ||
|
|
2dd743dea0 | ||
|
|
ced886aa07 | ||
|
|
de5a75777f | ||
|
|
809b41aa59 | ||
|
|
b6b1658805 | ||
|
|
afa340eb18 | ||
|
|
7ac4a4a2d4 | ||
|
|
66bf1dd469 | ||
|
|
9ae447f14f | ||
|
|
a02a5ac849 | ||
|
|
e4dee7ab63 | ||
|
|
9129c35407 | ||
|
|
4b701b55b1 |
@@ -81,7 +81,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.12.5",
|
"@babel/runtime": "^7.12.5",
|
||||||
"@element-hq/element-web-module-api": "1.4.1",
|
"@element-hq/element-web-module-api": "1.5.0",
|
||||||
"@fontsource/inconsolata": "^5",
|
"@fontsource/inconsolata": "^5",
|
||||||
"@fontsource/inter": "^5",
|
"@fontsource/inter": "^5",
|
||||||
"@formatjs/intl-segmenter": "^11.5.7",
|
"@formatjs/intl-segmenter": "^11.5.7",
|
||||||
|
|||||||
@@ -211,6 +211,10 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.mx_SpaceButton_withIcon .mx_SpaceButton_icon {
|
||||||
|
background-color: $panel-actions;
|
||||||
|
}
|
||||||
|
|
||||||
&.mx_SpaceButton_home .mx_SpaceButton_icon::before {
|
&.mx_SpaceButton_home .mx_SpaceButton_icon::before {
|
||||||
mask-image: url("@vector-im/compound-design-tokens/icons/home-solid.svg");
|
mask-image: url("@vector-im/compound-design-tokens/icons/home-solid.svg");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const notLoggedInMap: Record<Exclude<Views, Views.LOGGED_IN>, ScreenName> = {
|
|||||||
[Views.LOCK_STOLEN]: "SessionLockStolen",
|
[Views.LOCK_STOLEN]: "SessionLockStolen",
|
||||||
};
|
};
|
||||||
|
|
||||||
const loggedInPageTypeMap: Record<PageType, ScreenName> = {
|
const loggedInPageTypeMap: Record<PageType | string, ScreenName> = {
|
||||||
[PageType.HomePage]: "Home",
|
[PageType.HomePage]: "Home",
|
||||||
[PageType.RoomView]: "Room",
|
[PageType.RoomView]: "Room",
|
||||||
[PageType.UserView]: "User",
|
[PageType.UserView]: "User",
|
||||||
@@ -48,10 +48,10 @@ export default class PosthogTrackers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private view: Views = Views.LOADING;
|
private view: Views = Views.LOADING;
|
||||||
private pageType?: PageType;
|
private pageType?: PageType | string;
|
||||||
private override?: ScreenName;
|
private override?: ScreenName;
|
||||||
|
|
||||||
public trackPageChange(view: Views, pageType: PageType | undefined, durationMs: number): void {
|
public trackPageChange(view: Views, pageType: PageType | string | undefined, durationMs: number): void {
|
||||||
this.view = view;
|
this.view = view;
|
||||||
this.pageType = pageType;
|
this.pageType = pageType;
|
||||||
if (this.override) return;
|
if (this.override) return;
|
||||||
|
|||||||
@@ -68,7 +68,8 @@ import { monitorSyncedPushRules } from "../../utils/pushRules/monitorSyncedPushR
|
|||||||
import { type ConfigOptions } from "../../SdkConfig";
|
import { type ConfigOptions } from "../../SdkConfig";
|
||||||
import { MatrixClientContextProvider } from "./MatrixClientContextProvider";
|
import { MatrixClientContextProvider } from "./MatrixClientContextProvider";
|
||||||
import { Landmark, LandmarkNavigation } from "../../accessibility/LandmarkNavigation";
|
import { Landmark, LandmarkNavigation } from "../../accessibility/LandmarkNavigation";
|
||||||
import { SDKContext, SdkContextClass } from "../../contexts/SDKContext.ts";
|
import { ModuleApi } from "../../modules/Api.ts";
|
||||||
|
import { SDKContext } from "../../contexts/SDKContext.ts";
|
||||||
|
|
||||||
// We need to fetch each pinned message individually (if we don't already have it)
|
// We need to fetch each pinned message individually (if we don't already have it)
|
||||||
// so each pinned message may trigger a request. Limit the number per room for sanity.
|
// so each pinned message may trigger a request. Limit the number per room for sanity.
|
||||||
@@ -679,6 +680,10 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
let pageElement;
|
let pageElement;
|
||||||
|
|
||||||
|
const moduleRenderer = this.props.page_type
|
||||||
|
? ModuleApi.instance.navigation.locationRenderers.get(this.props.page_type)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
switch (this.props.page_type) {
|
switch (this.props.page_type) {
|
||||||
case PageTypes.RoomView:
|
case PageTypes.RoomView:
|
||||||
pageElement = (
|
pageElement = (
|
||||||
@@ -690,7 +695,6 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||||||
key={this.props.currentRoomId || "roomview"}
|
key={this.props.currentRoomId || "roomview"}
|
||||||
justCreatedOpts={this.props.roomJustCreatedOpts}
|
justCreatedOpts={this.props.roomJustCreatedOpts}
|
||||||
forceTimeline={this.props.forceTimeline}
|
forceTimeline={this.props.forceTimeline}
|
||||||
roomViewStore={SdkContextClass.instance.roomViewStore}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
@@ -706,6 +710,13 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
default: {
|
||||||
|
if (moduleRenderer) {
|
||||||
|
pageElement = moduleRenderer();
|
||||||
|
} else {
|
||||||
|
console.warn(`Couldn't render page type "${this.props.page_type}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const wrapperClasses = classNames({
|
const wrapperClasses = classNames({
|
||||||
@@ -747,6 +758,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||||||
)}
|
)}
|
||||||
<SpacePanel />
|
<SpacePanel />
|
||||||
{!useNewRoomList && <BackdropPanel backgroundImage={this.state.backgroundImage} />}
|
{!useNewRoomList && <BackdropPanel backgroundImage={this.state.backgroundImage} />}
|
||||||
|
{!moduleRenderer && (
|
||||||
<div
|
<div
|
||||||
className="mx_LeftPanel_wrapper--user"
|
className="mx_LeftPanel_wrapper--user"
|
||||||
ref={this._resizeContainer}
|
ref={this._resizeContainer}
|
||||||
@@ -758,9 +770,10 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||||||
resizeNotifier={this.context.resizeNotifier}
|
resizeNotifier={this.context.resizeNotifier}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ResizeHandle passRef={this.resizeHandler} id="lp-resizer" />
|
{!moduleRenderer && <ResizeHandle passRef={this.resizeHandler} id="lp-resizer" />}
|
||||||
<div className="mx_RoomView_wrapper">{pageElement}</div>
|
<div className="mx_RoomView_wrapper">{pageElement}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ import { ShareFormat, type SharePayload } from "../../dispatcher/payloads/ShareP
|
|||||||
import Markdown from "../../Markdown";
|
import Markdown from "../../Markdown";
|
||||||
import { sanitizeHtmlParams } from "../../Linkify";
|
import { sanitizeHtmlParams } from "../../Linkify";
|
||||||
import { isOnlyAdmin } from "../../utils/membership";
|
import { isOnlyAdmin } from "../../utils/membership";
|
||||||
|
import { ModuleApi } from "../../modules/Api.ts";
|
||||||
|
|
||||||
// legacy export
|
// legacy export
|
||||||
export { default as Views } from "../../Views";
|
export { default as Views } from "../../Views";
|
||||||
@@ -175,9 +176,11 @@ interface IProps {
|
|||||||
interface IState {
|
interface IState {
|
||||||
// the master view we are showing.
|
// the master view we are showing.
|
||||||
view: Views;
|
view: Views;
|
||||||
// What the LoggedInView would be showing if visible
|
// What the LoggedInView would be showing if visible.
|
||||||
|
// A member of the enum for standard pages or a string for those provided by
|
||||||
|
// a module.
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
page_type?: PageType;
|
page_type?: PageType | string;
|
||||||
// The ID of the room we're viewing. This is either populated directly
|
// The ID of the room we're viewing. This is either populated directly
|
||||||
// in the case where we view a room by ID or by RoomView when it resolves
|
// in the case where we view a room by ID or by RoomView when it resolves
|
||||||
// what ID an alias points at.
|
// what ID an alias points at.
|
||||||
@@ -1922,7 +1925,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||||||
subAction: params?.action,
|
subAction: params?.action,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
logger.info(`Ignoring showScreen for '${screen}'`);
|
if (ModuleApi.instance.navigation.locationRenderers.get(screen)) {
|
||||||
|
this.setState({ page_type: screen });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import { type CallState, type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
|||||||
import { debounce, throttle } from "lodash";
|
import { debounce, throttle } from "lodash";
|
||||||
import { CryptoEvent } from "matrix-js-sdk/src/crypto-api";
|
import { CryptoEvent } from "matrix-js-sdk/src/crypto-api";
|
||||||
import { type ViewRoomOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
|
import { type ViewRoomOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
|
||||||
|
import { type RoomViewProps } from "@element-hq/element-web-module-api";
|
||||||
|
|
||||||
import shouldHideEvent from "../../shouldHideEvent";
|
import shouldHideEvent from "../../shouldHideEvent";
|
||||||
import { _t } from "../../languageHandler";
|
import { _t } from "../../languageHandler";
|
||||||
@@ -148,7 +149,7 @@ if (DEBUG) {
|
|||||||
debuglog = logger.log.bind(console);
|
debuglog = logger.log.bind(console);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IRoomProps {
|
interface IRoomProps extends RoomViewProps {
|
||||||
threepidInvite?: IThreepidInvite;
|
threepidInvite?: IThreepidInvite;
|
||||||
oobData?: IOOBData;
|
oobData?: IOOBData;
|
||||||
|
|
||||||
@@ -158,19 +159,17 @@ interface IRoomProps {
|
|||||||
|
|
||||||
// Called with the credentials of a registered user (if they were a ROU that transitioned to PWLU)
|
// Called with the credentials of a registered user (if they were a ROU that transitioned to PWLU)
|
||||||
onRegistered?(credentials: IMatrixClientCreds): void;
|
onRegistered?(credentials: IMatrixClientCreds): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The RoomViewStore instance for the room to be displayed.
|
* Only necessary if RoomView should get it's RoomViewStore through the MultiRoomViewStore.
|
||||||
|
* Omitting this will mean that RoomView renders for the room held in SDKContext.RoomViewStore.
|
||||||
*/
|
*/
|
||||||
roomViewStore: RoomViewStore;
|
roomId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { MainSplitContentType };
|
export { MainSplitContentType };
|
||||||
|
|
||||||
export interface IRoomState {
|
export interface IRoomState {
|
||||||
/**
|
|
||||||
* The RoomViewStore instance for the room we are displaying
|
|
||||||
*/
|
|
||||||
roomViewStore: RoomViewStore;
|
|
||||||
room?: Room;
|
room?: Room;
|
||||||
roomId?: string;
|
roomId?: string;
|
||||||
roomAlias?: string;
|
roomAlias?: string;
|
||||||
@@ -389,6 +388,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||||||
private messagePanel: TimelinePanel | null = null;
|
private messagePanel: TimelinePanel | null = null;
|
||||||
private roomViewBody = createRef<HTMLDivElement>();
|
private roomViewBody = createRef<HTMLDivElement>();
|
||||||
|
|
||||||
|
private roomViewStore: RoomViewStore;
|
||||||
|
|
||||||
public static contextType = SDKContext;
|
public static contextType = SDKContext;
|
||||||
declare public context: React.ContextType<typeof SDKContext>;
|
declare public context: React.ContextType<typeof SDKContext>;
|
||||||
|
|
||||||
@@ -401,9 +402,14 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||||||
throw new Error("Unable to create RoomView without MatrixClient");
|
throw new Error("Unable to create RoomView without MatrixClient");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (props.roomId) {
|
||||||
|
this.roomViewStore = this.context.multiRoomViewStore.getRoomViewStoreForRoom(props.roomId);
|
||||||
|
} else {
|
||||||
|
this.roomViewStore = context.roomViewStore;
|
||||||
|
}
|
||||||
|
|
||||||
const llMembers = context.client.hasLazyLoadMembersEnabled();
|
const llMembers = context.client.hasLazyLoadMembersEnabled();
|
||||||
this.state = {
|
this.state = {
|
||||||
roomViewStore: props.roomViewStore,
|
|
||||||
roomId: undefined,
|
roomId: undefined,
|
||||||
roomLoading: true,
|
roomLoading: true,
|
||||||
peekLoading: false,
|
peekLoading: false,
|
||||||
@@ -535,7 +541,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private getMainSplitContentType = (room: Room): MainSplitContentType => {
|
private getMainSplitContentType = (room: Room): MainSplitContentType => {
|
||||||
if (this.state.roomViewStore.isViewingCall() || isVideoRoom(room)) {
|
if (this.roomViewStore.isViewingCall() || isVideoRoom(room)) {
|
||||||
return MainSplitContentType.Call;
|
return MainSplitContentType.Call;
|
||||||
}
|
}
|
||||||
if (this.context.widgetLayoutStore.hasMaximisedWidget(room)) {
|
if (this.context.widgetLayoutStore.hasMaximisedWidget(room)) {
|
||||||
@@ -549,8 +555,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const roomLoadError = this.state.roomViewStore.getRoomLoadError() ?? undefined;
|
const roomLoadError = this.roomViewStore.getRoomLoadError() ?? undefined;
|
||||||
if (!initial && !roomLoadError && this.state.roomId !== this.state.roomViewStore.getRoomId()) {
|
if (!initial && !roomLoadError && this.state.roomId !== this.roomViewStore.getRoomId()) {
|
||||||
// RoomView explicitly does not support changing what room
|
// RoomView explicitly does not support changing what room
|
||||||
// is being viewed: instead it should just be re-mounted when
|
// is being viewed: instead it should just be re-mounted when
|
||||||
// switching rooms. Therefore, if the room ID changes, we
|
// switching rooms. Therefore, if the room ID changes, we
|
||||||
@@ -564,7 +570,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||||||
// it was, it means we're about to be unmounted.
|
// it was, it means we're about to be unmounted.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const roomViewStore = this.state.roomViewStore;
|
const roomViewStore = this.roomViewStore;
|
||||||
const roomId = roomViewStore.getRoomId() ?? null;
|
const roomId = roomViewStore.getRoomId() ?? null;
|
||||||
const roomAlias = roomViewStore.getRoomAlias() ?? undefined;
|
const roomAlias = roomViewStore.getRoomAlias() ?? undefined;
|
||||||
const roomLoading = roomViewStore.isRoomLoading();
|
const roomLoading = roomViewStore.isRoomLoading();
|
||||||
@@ -611,7 +617,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||||||
newState.showRightPanel = false;
|
newState.showRightPanel = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialEventId = this.state.roomViewStore.getInitialEventId() ?? this.state.initialEventId;
|
const initialEventId = this.roomViewStore.getInitialEventId() ?? this.state.initialEventId;
|
||||||
if (initialEventId) {
|
if (initialEventId) {
|
||||||
let initialEvent = room?.findEventById(initialEventId);
|
let initialEvent = room?.findEventById(initialEventId);
|
||||||
// The event does not exist in the current sync data
|
// The event does not exist in the current sync data
|
||||||
@@ -637,13 +643,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||||||
action: Action.ShowThread,
|
action: Action.ShowThread,
|
||||||
rootEvent: thread.rootEvent,
|
rootEvent: thread.rootEvent,
|
||||||
initialEvent,
|
initialEvent,
|
||||||
highlighted: this.state.roomViewStore.isInitialEventHighlighted(),
|
highlighted: this.roomViewStore.isInitialEventHighlighted(),
|
||||||
scroll_into_view: this.state.roomViewStore.initialEventScrollIntoView(),
|
scroll_into_view: this.roomViewStore.initialEventScrollIntoView(),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
newState.initialEventId = initialEventId;
|
newState.initialEventId = initialEventId;
|
||||||
newState.isInitialEventHighlighted = this.state.roomViewStore.isInitialEventHighlighted();
|
newState.isInitialEventHighlighted = this.roomViewStore.isInitialEventHighlighted();
|
||||||
newState.initialEventScrollIntoView = this.state.roomViewStore.initialEventScrollIntoView();
|
newState.initialEventScrollIntoView = this.roomViewStore.initialEventScrollIntoView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -903,7 +909,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||||||
this.context.client.on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
|
this.context.client.on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
|
||||||
}
|
}
|
||||||
// Start listening for RoomViewStore updates
|
// Start listening for RoomViewStore updates
|
||||||
this.state.roomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
this.roomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||||
|
|
||||||
this.context.rightPanelStore.on(UPDATE_EVENT, this.onRightPanelStoreUpdate);
|
this.context.rightPanelStore.on(UPDATE_EVENT, this.onRightPanelStoreUpdate);
|
||||||
|
|
||||||
@@ -1020,7 +1026,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||||||
|
|
||||||
window.removeEventListener("beforeunload", this.onPageUnload);
|
window.removeEventListener("beforeunload", this.onPageUnload);
|
||||||
|
|
||||||
this.state.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
this.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||||
|
|
||||||
this.context.rightPanelStore.off(UPDATE_EVENT, this.onRightPanelStoreUpdate);
|
this.context.rightPanelStore.off(UPDATE_EVENT, this.onRightPanelStoreUpdate);
|
||||||
WidgetEchoStore.removeListener(UPDATE_EVENT, this.onWidgetEchoStoreUpdate);
|
WidgetEchoStore.removeListener(UPDATE_EVENT, this.onWidgetEchoStoreUpdate);
|
||||||
@@ -1048,6 +1054,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||||||
// clean up if this was a local room
|
// clean up if this was a local room
|
||||||
this.context.client?.store.removeRoom(this.state.room.roomId);
|
this.context.client?.store.removeRoom(this.state.room.roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.props.roomId) this.context.multiRoomViewStore.removeRoomViewStore(this.props.roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onRightPanelStoreUpdate = (): void => {
|
private onRightPanelStoreUpdate = (): void => {
|
||||||
@@ -2070,7 +2078,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||||||
if (!this.state.room || !this.context?.client) return null;
|
if (!this.state.room || !this.context?.client) return null;
|
||||||
const names = this.state.room.getDefaultRoomName(this.context.client.getSafeUserId());
|
const names = this.state.room.getDefaultRoomName(this.context.client.getSafeUserId());
|
||||||
return (
|
return (
|
||||||
<ScopedRoomContextProvider {...this.state}>
|
<ScopedRoomContextProvider {...this.state} roomViewStore={this.roomViewStore}>
|
||||||
<LocalRoomCreateLoader
|
<LocalRoomCreateLoader
|
||||||
localRoom={localRoom}
|
localRoom={localRoom}
|
||||||
names={names}
|
names={names}
|
||||||
@@ -2082,7 +2090,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||||||
|
|
||||||
private renderLocalRoomView(localRoom: LocalRoom): ReactNode {
|
private renderLocalRoomView(localRoom: LocalRoom): ReactNode {
|
||||||
return (
|
return (
|
||||||
<ScopedRoomContextProvider {...this.state}>
|
<ScopedRoomContextProvider {...this.state} roomViewStore={this.roomViewStore}>
|
||||||
<LocalRoomView
|
<LocalRoomView
|
||||||
e2eStatus={this.state.e2eStatus}
|
e2eStatus={this.state.e2eStatus}
|
||||||
localRoom={localRoom}
|
localRoom={localRoom}
|
||||||
@@ -2098,7 +2106,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||||||
|
|
||||||
private renderWaitingForThirdPartyRoomView(inviteEvent: MatrixEvent): ReactNode {
|
private renderWaitingForThirdPartyRoomView(inviteEvent: MatrixEvent): ReactNode {
|
||||||
return (
|
return (
|
||||||
<ScopedRoomContextProvider {...this.state}>
|
<ScopedRoomContextProvider {...this.state} roomViewStore={this.roomViewStore}>
|
||||||
<WaitingForThirdPartyRoomView
|
<WaitingForThirdPartyRoomView
|
||||||
resizeNotifier={this.context.resizeNotifier}
|
resizeNotifier={this.context.resizeNotifier}
|
||||||
roomView={this.roomView}
|
roomView={this.roomView}
|
||||||
@@ -2640,7 +2648,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScopedRoomContextProvider {...this.state}>
|
<ScopedRoomContextProvider {...this.state} roomViewStore={this.roomViewStore}>
|
||||||
<div className={mainClasses} ref={this.roomView} onKeyDown={this.onReactKeyDown}>
|
<div className={mainClasses} ref={this.roomView} onKeyDown={this.onReactKeyDown}>
|
||||||
{showChatEffects && this.roomView.current && (
|
{showChatEffects && this.roomView.current && (
|
||||||
<EffectsOverlay roomWidth={this.roomView.current.offsetWidth} />
|
<EffectsOverlay roomWidth={this.roomView.current.offsetWidth} />
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ import { ThreadsActivityCentre } from "./threads-activity-centre/";
|
|||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import { Landmark, LandmarkNavigation } from "../../../accessibility/LandmarkNavigation";
|
import { Landmark, LandmarkNavigation } from "../../../accessibility/LandmarkNavigation";
|
||||||
import { KeyboardShortcut } from "../settings/KeyboardShortcut";
|
import { KeyboardShortcut } from "../settings/KeyboardShortcut";
|
||||||
|
import { ModuleApi } from "../../../modules/Api.ts";
|
||||||
|
import { useModuleSpacePanelItems } from "../../../modules/ExtrasApi.ts";
|
||||||
import { ReleaseAnnouncement } from "../../structures/ReleaseAnnouncement";
|
import { ReleaseAnnouncement } from "../../structures/ReleaseAnnouncement";
|
||||||
|
|
||||||
const useSpaces = (): [Room[], MetaSpace[], Room[], SpaceKey] => {
|
const useSpaces = (): [Room[], MetaSpace[], Room[], SpaceKey] => {
|
||||||
@@ -290,6 +292,8 @@ const InnerSpacePanel = React.memo<IInnerSpacePanelProps>(
|
|||||||
const [invites, metaSpaces, actualSpaces, activeSpace] = useSpaces();
|
const [invites, metaSpaces, actualSpaces, activeSpace] = useSpaces();
|
||||||
const activeSpaces = activeSpace ? [activeSpace] : [];
|
const activeSpaces = activeSpace ? [activeSpace] : [];
|
||||||
|
|
||||||
|
const moduleSpaceItems = useModuleSpacePanelItems(ModuleApi.instance.extras);
|
||||||
|
|
||||||
const metaSpacesSection = metaSpaces
|
const metaSpacesSection = metaSpaces
|
||||||
.filter((key) => !(key === MetaSpace.VideoRooms && !SettingsStore.getValue("feature_video_rooms")))
|
.filter((key) => !(key === MetaSpace.VideoRooms && !SettingsStore.getValue("feature_video_rooms")))
|
||||||
.map((key) => {
|
.map((key) => {
|
||||||
@@ -341,6 +345,27 @@ const InnerSpacePanel = React.memo<IInnerSpacePanelProps>(
|
|||||||
</Draggable>
|
</Draggable>
|
||||||
))}
|
))}
|
||||||
{children}
|
{children}
|
||||||
|
{moduleSpaceItems.map((item) => (
|
||||||
|
<li
|
||||||
|
key={item.spaceKey}
|
||||||
|
className={classNames("mx_SpaceItem", {
|
||||||
|
collapsed: isPanelCollapsed,
|
||||||
|
})}
|
||||||
|
role="treeitem"
|
||||||
|
aria-selected={false} // TODO
|
||||||
|
>
|
||||||
|
<SpaceButton
|
||||||
|
{...item}
|
||||||
|
isNarrow={isPanelCollapsed}
|
||||||
|
size="32px"
|
||||||
|
selected={activeSpace === item.spaceKey}
|
||||||
|
onClick={() => {
|
||||||
|
SpaceStore.instance.setActiveSpace(item.spaceKey);
|
||||||
|
item.onSelected?.();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
{shouldShowComponent(UIComponent.CreateSpaces) && (
|
{shouldShowComponent(UIComponent.CreateSpaces) && (
|
||||||
<CreateSpaceButton isPanelCollapsed={isPanelCollapsed} setPanelCollapsed={setPanelCollapsed} />
|
<CreateSpaceButton isPanelCollapsed={isPanelCollapsed} setPanelCollapsed={setPanelCollapsed} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ type ButtonProps<T extends keyof HTMLElementTagNameMap> = Omit<
|
|||||||
className?: string;
|
className?: string;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
label: string;
|
label: string;
|
||||||
|
icon?: JSX.Element;
|
||||||
contextMenuTooltip?: string;
|
contextMenuTooltip?: string;
|
||||||
notificationState?: NotificationState;
|
notificationState?: NotificationState;
|
||||||
isNarrow?: boolean;
|
isNarrow?: boolean;
|
||||||
@@ -65,6 +66,7 @@ export const SpaceButton = <T extends keyof HTMLElementTagNameMap>({
|
|||||||
space,
|
space,
|
||||||
spaceKey: _spaceKey,
|
spaceKey: _spaceKey,
|
||||||
className,
|
className,
|
||||||
|
icon,
|
||||||
selected,
|
selected,
|
||||||
label,
|
label,
|
||||||
contextMenuTooltip,
|
contextMenuTooltip,
|
||||||
@@ -84,7 +86,7 @@ export const SpaceButton = <T extends keyof HTMLElementTagNameMap>({
|
|||||||
|
|
||||||
let avatar = (
|
let avatar = (
|
||||||
<div className="mx_SpaceButton_avatarPlaceholder">
|
<div className="mx_SpaceButton_avatarPlaceholder">
|
||||||
<div className="mx_SpaceButton_icon" />
|
<div className="mx_SpaceButton_icon">{icon}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
if (space) {
|
if (space) {
|
||||||
@@ -143,6 +145,7 @@ export const SpaceButton = <T extends keyof HTMLElementTagNameMap>({
|
|||||||
mx_SpaceButton_active: selected,
|
mx_SpaceButton_active: selected,
|
||||||
mx_SpaceButton_hasMenuOpen: menuDisplayed,
|
mx_SpaceButton_hasMenuOpen: menuDisplayed,
|
||||||
mx_SpaceButton_narrow: isNarrow,
|
mx_SpaceButton_narrow: isNarrow,
|
||||||
|
mx_SpaceButton_withIcon: Boolean(icon),
|
||||||
})}
|
})}
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
title={!isNarrow || menuDisplayed ? undefined : label}
|
title={!isNarrow || menuDisplayed ? undefined : label}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { createContext } from "react";
|
|||||||
|
|
||||||
import { type IRoomState } from "../components/structures/RoomView";
|
import { type IRoomState } from "../components/structures/RoomView";
|
||||||
import { Layout } from "../settings/enums/Layout";
|
import { Layout } from "../settings/enums/Layout";
|
||||||
|
import { type RoomViewStore } from "../stores/RoomViewStore";
|
||||||
|
|
||||||
export enum TimelineRenderingType {
|
export enum TimelineRenderingType {
|
||||||
Room = "Room",
|
Room = "Room",
|
||||||
@@ -29,11 +30,12 @@ export enum MainSplitContentType {
|
|||||||
Call,
|
Call,
|
||||||
}
|
}
|
||||||
|
|
||||||
const RoomContext = createContext<
|
export interface RoomContextType extends IRoomState {
|
||||||
IRoomState & {
|
|
||||||
threadId?: string;
|
threadId?: string;
|
||||||
|
roomViewStore: RoomViewStore;
|
||||||
}
|
}
|
||||||
>({
|
|
||||||
|
const RoomContext = createContext<RoomContextType>({
|
||||||
roomLoading: true,
|
roomLoading: true,
|
||||||
peekLoading: false,
|
peekLoading: false,
|
||||||
shouldPeek: true,
|
shouldPeek: true,
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import { WidgetPermissionStore } from "../stores/widgets/WidgetPermissionStore";
|
|||||||
import { OidcClientStore } from "../stores/oidc/OidcClientStore";
|
import { OidcClientStore } from "../stores/oidc/OidcClientStore";
|
||||||
import WidgetStore from "../stores/WidgetStore";
|
import WidgetStore from "../stores/WidgetStore";
|
||||||
import ResizeNotifier from "../utils/ResizeNotifier";
|
import ResizeNotifier from "../utils/ResizeNotifier";
|
||||||
|
import { MultiRoomViewStore } from "../stores/MultiRoomViewStore";
|
||||||
|
|
||||||
// This context is available to components under MatrixChat,
|
// This context is available to components under MatrixChat,
|
||||||
// the context must not be used by components outside a SdkContextClass tree.
|
// the context must not be used by components outside a SdkContextClass tree.
|
||||||
@@ -66,6 +67,7 @@ export class SdkContextClass {
|
|||||||
protected _UserProfilesStore?: UserProfilesStore;
|
protected _UserProfilesStore?: UserProfilesStore;
|
||||||
protected _OidcClientStore?: OidcClientStore;
|
protected _OidcClientStore?: OidcClientStore;
|
||||||
protected _ResizeNotifier?: ResizeNotifier;
|
protected _ResizeNotifier?: ResizeNotifier;
|
||||||
|
protected _MultiRoomViewStore?: MultiRoomViewStore;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Automatically construct stores which need to be created eagerly so they can register with
|
* Automatically construct stores which need to be created eagerly so they can register with
|
||||||
@@ -183,6 +185,13 @@ export class SdkContextClass {
|
|||||||
return this._ResizeNotifier;
|
return this._ResizeNotifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get multiRoomViewStore(): MultiRoomViewStore {
|
||||||
|
if (!this._MultiRoomViewStore) {
|
||||||
|
this._MultiRoomViewStore = new MultiRoomViewStore(defaultDispatcher, this);
|
||||||
|
}
|
||||||
|
return this._MultiRoomViewStore;
|
||||||
|
}
|
||||||
|
|
||||||
public onLoggedOut(): void {
|
public onLoggedOut(): void {
|
||||||
this._UserProfilesStore = undefined;
|
this._UserProfilesStore = undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
54
src/modules/AccountDataApi.ts
Normal file
54
src/modules/AccountDataApi.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 Element Creations Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Watchable, type AccountDataApi as IAccountDataApi } from "@element-hq/element-web-module-api";
|
||||||
|
import { ClientEvent, type MatrixEvent, type MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
|
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||||
|
|
||||||
|
export class AccountDataApi implements IAccountDataApi {
|
||||||
|
public get(eventType: string): Watchable<unknown> {
|
||||||
|
const cli = MatrixClientPeg.safeGet();
|
||||||
|
return new AccountDataWatchable(cli, eventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async set(eventType: string, content: any): Promise<void> {
|
||||||
|
const cli = MatrixClientPeg.safeGet();
|
||||||
|
//@ts-expect-error: JS-SDK accepts known event-types, intentionally allow arbitrary types.
|
||||||
|
await cli.setAccountData(eventType, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async delete(eventType: string): Promise<void> {
|
||||||
|
const cli = MatrixClientPeg.safeGet();
|
||||||
|
//@ts-expect-error: JS-SDK accepts known event-types, intentionally allow arbitrary types.
|
||||||
|
await cli.deleteAccountData(eventType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccountDataWatchable extends Watchable<unknown> {
|
||||||
|
public constructor(
|
||||||
|
private cli: MatrixClient,
|
||||||
|
private eventType: string,
|
||||||
|
) {
|
||||||
|
//@ts-expect-error: JS-SDK accepts known event-types, intentionally allow arbitrary types.
|
||||||
|
super(cli.getAccountData(eventType)?.getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
private onAccountData = (event: MatrixEvent): void => {
|
||||||
|
if (event.getType() === this.eventType) {
|
||||||
|
this.value = event.getContent();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected onFirstWatch(): void {
|
||||||
|
this.cli.on(ClientEvent.AccountData, this.onAccountData);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onLastWatch(): void {
|
||||||
|
this.cli.off(ClientEvent.AccountData, this.onAccountData);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,9 @@ import { WatchableProfile } from "./Profile.ts";
|
|||||||
import { NavigationApi } from "./Navigation.ts";
|
import { NavigationApi } from "./Navigation.ts";
|
||||||
import { openDialog } from "./Dialog.tsx";
|
import { openDialog } from "./Dialog.tsx";
|
||||||
import { overwriteAccountAuth } from "./Auth.ts";
|
import { overwriteAccountAuth } from "./Auth.ts";
|
||||||
|
import { ElementWebExtrasApi } from "./ExtrasApi.ts";
|
||||||
|
import { ElementWebBuiltinsApi } from "./BuiltinsApi.tsx";
|
||||||
|
import { ClientApi } from "./ClientApi.ts";
|
||||||
|
|
||||||
const legacyCustomisationsFactory = <T extends object>(baseCustomisations: T) => {
|
const legacyCustomisationsFactory = <T extends object>(baseCustomisations: T) => {
|
||||||
let used = false;
|
let used = false;
|
||||||
@@ -79,7 +82,10 @@ export class ModuleApi implements Api {
|
|||||||
public readonly config = new ConfigApi();
|
public readonly config = new ConfigApi();
|
||||||
public readonly i18n = new I18nApi();
|
public readonly i18n = new I18nApi();
|
||||||
public readonly customComponents = new CustomComponentsApi();
|
public readonly customComponents = new CustomComponentsApi();
|
||||||
|
public readonly extras = new ElementWebExtrasApi();
|
||||||
|
public readonly builtins = new ElementWebBuiltinsApi();
|
||||||
public readonly rootNode = document.getElementById("matrixchat")!;
|
public readonly rootNode = document.getElementById("matrixchat")!;
|
||||||
|
public readonly client = new ClientApi();
|
||||||
|
|
||||||
public createRoot(element: Element): Root {
|
public createRoot(element: Element): Root {
|
||||||
return createRoot(element);
|
return createRoot(element);
|
||||||
|
|||||||
75
src/modules/BuiltinsApi.tsx
Normal file
75
src/modules/BuiltinsApi.tsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 Element Creations Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { type RoomViewProps, type BuiltinsApi } from "@element-hq/element-web-module-api";
|
||||||
|
|
||||||
|
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||||
|
import type { Room } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
|
interface RoomViewPropsWithRoomId extends RoomViewProps {
|
||||||
|
roomId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RoomAvatarProps {
|
||||||
|
room: Room;
|
||||||
|
size?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Components {
|
||||||
|
roomView: React.ComponentType<RoomViewPropsWithRoomId>;
|
||||||
|
roomAvatar: React.ComponentType<RoomAvatarProps>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ElementWebBuiltinsApi implements BuiltinsApi {
|
||||||
|
private _roomView?: React.ComponentType<RoomViewPropsWithRoomId>;
|
||||||
|
private _roomAvatar?: React.ComponentType<RoomAvatarProps>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the components used by the API.
|
||||||
|
*
|
||||||
|
* This only really exists here because referencing these components directly causes a nightmare of
|
||||||
|
* circular dependencies that break the whole app, so instead we avoid referencing it here
|
||||||
|
* and pass it in from somewhere it's already referenced (see related comment in app.tsx).
|
||||||
|
*
|
||||||
|
* @param component The components used by the api, see {@link Components}
|
||||||
|
*/
|
||||||
|
public setComponents(components: Components): void {
|
||||||
|
this._roomView = components.roomView;
|
||||||
|
this._roomAvatar = components.roomAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRoomViewComponent(): React.ComponentType<RoomViewPropsWithRoomId> {
|
||||||
|
if (!this._roomView) {
|
||||||
|
throw new Error("No RoomView component has been set");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._roomView;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRoomAvatarComponent(): React.ComponentType<RoomAvatarProps> {
|
||||||
|
if (!this._roomAvatar) {
|
||||||
|
throw new Error("No RoomAvatar component has been set");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._roomAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public renderRoomView(roomId: string): React.ReactNode {
|
||||||
|
const Component = this.getRoomViewComponent();
|
||||||
|
return <Component roomId={roomId} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
public renderRoomAvatar(roomId: string, size?: string): React.ReactNode {
|
||||||
|
const room = MatrixClientPeg.safeGet().getRoom(roomId);
|
||||||
|
if (!room) {
|
||||||
|
throw new Error(`No room such room: ${roomId}`);
|
||||||
|
}
|
||||||
|
const Component = this.getRoomAvatarComponent();
|
||||||
|
return <Component room={room} size={size} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/modules/ClientApi.ts
Normal file
25
src/modules/ClientApi.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 Element Creations Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
import type { ClientApi as IClientApi, Room } from "@element-hq/element-web-module-api";
|
||||||
|
import { Room as ModuleRoom } from "./models/Room";
|
||||||
|
import { AccountDataApi } from "./AccountDataApi";
|
||||||
|
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||||
|
|
||||||
|
export class ClientApi implements IClientApi {
|
||||||
|
private accountDataApi?: AccountDataApi;
|
||||||
|
|
||||||
|
public getRoom(roomId: string): Room | null {
|
||||||
|
const sdkRoom = MatrixClientPeg.safeGet().getRoom(roomId);
|
||||||
|
if (sdkRoom) return new ModuleRoom(sdkRoom);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get accountData(): AccountDataApi {
|
||||||
|
this.accountDataApi ??= new AccountDataApi();
|
||||||
|
return this.accountDataApi;
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/modules/ExtrasApi.ts
Normal file
50
src/modules/ExtrasApi.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 New Vector Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { type SpacePanelItemProps, type ExtrasApi } from "@element-hq/element-web-module-api";
|
||||||
|
import { TypedEventEmitter } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
|
import { useTypedEventEmitter } from "../hooks/useEventEmitter";
|
||||||
|
|
||||||
|
export interface ModuleSpacePanelItem extends SpacePanelItemProps {
|
||||||
|
spaceKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ExtrasApiEvent {
|
||||||
|
SpacePanelItemsChanged = "SpacePanelItemsChanged",
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EmittedEvents {
|
||||||
|
[ExtrasApiEvent.SpacePanelItemsChanged]: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ElementWebExtrasApi extends TypedEventEmitter<keyof EmittedEvents, EmittedEvents> implements ExtrasApi {
|
||||||
|
public spacePanelItems = new Map<string, SpacePanelItemProps>();
|
||||||
|
|
||||||
|
public setSpacePanelItem(spacekey: string, item: SpacePanelItemProps): void {
|
||||||
|
this.spacePanelItems.set(spacekey, item);
|
||||||
|
this.emit(ExtrasApiEvent.SpacePanelItemsChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useModuleSpacePanelItems(api: ElementWebExtrasApi): ModuleSpacePanelItem[] {
|
||||||
|
const getItems = (): ModuleSpacePanelItem[] => {
|
||||||
|
return Array.from(api.spacePanelItems.entries()).map(([spaceKey, item]) => ({
|
||||||
|
spaceKey,
|
||||||
|
...item,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const [items, setItems] = useState<ModuleSpacePanelItem[]>(getItems);
|
||||||
|
|
||||||
|
useTypedEventEmitter(api, ExtrasApiEvent.SpacePanelItemsChanged, () => {
|
||||||
|
setItems(getItems());
|
||||||
|
});
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
@@ -5,8 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
|||||||
Please see LICENSE files in the repository root for full details.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type NavigationApi as INavigationApi } from "@element-hq/element-web-module-api";
|
import type {
|
||||||
|
LocationRenderFunction,
|
||||||
|
NavigationApi as INavigationApi,
|
||||||
|
OpenRoomOptions,
|
||||||
|
} from "@element-hq/element-web-module-api";
|
||||||
import { navigateToPermalink } from "../utils/permalinks/navigator.ts";
|
import { navigateToPermalink } from "../utils/permalinks/navigator.ts";
|
||||||
import { parsePermalink } from "../utils/permalinks/Permalinks.ts";
|
import { parsePermalink } from "../utils/permalinks/Permalinks.ts";
|
||||||
import dispatcher from "../dispatcher/dispatcher.ts";
|
import dispatcher from "../dispatcher/dispatcher.ts";
|
||||||
@@ -14,28 +17,32 @@ import { Action } from "../dispatcher/actions.ts";
|
|||||||
import type { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload.ts";
|
import type { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload.ts";
|
||||||
|
|
||||||
export class NavigationApi implements INavigationApi {
|
export class NavigationApi implements INavigationApi {
|
||||||
|
public locationRenderers = new Map<string, LocationRenderFunction>();
|
||||||
|
|
||||||
public async toMatrixToLink(link: string, join = false): Promise<void> {
|
public async toMatrixToLink(link: string, join = false): Promise<void> {
|
||||||
navigateToPermalink(link);
|
navigateToPermalink(link);
|
||||||
|
|
||||||
const parts = parsePermalink(link);
|
const parts = parsePermalink(link);
|
||||||
if (parts?.roomIdOrAlias) {
|
if (parts?.roomIdOrAlias) {
|
||||||
if (parts.roomIdOrAlias.startsWith("#")) {
|
this.openRoom(parts.roomIdOrAlias, {
|
||||||
dispatcher.dispatch<ViewRoomPayload>({
|
viaServers: parts.viaServers ?? undefined,
|
||||||
action: Action.ViewRoom,
|
autoJoin: join,
|
||||||
room_alias: parts.roomIdOrAlias,
|
|
||||||
via_servers: parts.viaServers ?? undefined,
|
|
||||||
auto_join: join,
|
|
||||||
metricsTrigger: undefined,
|
|
||||||
});
|
});
|
||||||
} else {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerLocationRenderer(path: string, renderer: LocationRenderFunction): void {
|
||||||
|
this.locationRenderers.set(path, renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public openRoom(roomIdOrAlias: string, opts: OpenRoomOptions = {}): void {
|
||||||
|
const key = roomIdOrAlias.startsWith("#") ? "room_alias" : "room_id";
|
||||||
dispatcher.dispatch<ViewRoomPayload>({
|
dispatcher.dispatch<ViewRoomPayload>({
|
||||||
action: Action.ViewRoom,
|
action: Action.ViewRoom,
|
||||||
room_id: parts.roomIdOrAlias,
|
[key]: roomIdOrAlias,
|
||||||
via_servers: parts.viaServers ?? undefined,
|
via_servers: opts.viaServers,
|
||||||
auto_join: join,
|
auto_join: opts.autoJoin,
|
||||||
metricsTrigger: undefined,
|
metricsTrigger: undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
45
src/modules/models/Room.ts
Normal file
45
src/modules/models/Room.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 Element Creations Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { type Room as IRoom, Watchable } from "@element-hq/element-web-module-api";
|
||||||
|
import { RoomEvent, type Room as SdkRoom } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
|
export class Room implements IRoom {
|
||||||
|
public name: Watchable<string>;
|
||||||
|
|
||||||
|
public constructor(private sdkRoom: SdkRoom) {
|
||||||
|
this.name = new WatchableName(sdkRoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLastActiveTimestamp(): number {
|
||||||
|
return this.sdkRoom.getLastActiveTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get id(): string {
|
||||||
|
return this.sdkRoom.roomId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom watchable for room name.
|
||||||
|
*/
|
||||||
|
class WatchableName extends Watchable<string> {
|
||||||
|
public constructor(private sdkRoom: SdkRoom) {
|
||||||
|
super(sdkRoom.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onNameUpdate = (): void => {
|
||||||
|
super.value = this.sdkRoom.name;
|
||||||
|
};
|
||||||
|
protected onFirstWatch(): void {
|
||||||
|
this.sdkRoom.on(RoomEvent.Name, this.onNameUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onLastWatch(): void {
|
||||||
|
this.sdkRoom.off(RoomEvent.Name, this.onNameUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
67
src/stores/MultiRoomViewStore.ts
Normal file
67
src/stores/MultiRoomViewStore.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 New Vector Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
|
import { RoomViewStore } from "./RoomViewStore";
|
||||||
|
import { type MatrixDispatcher } from "../dispatcher/dispatcher";
|
||||||
|
import { type SdkContextClass } from "../contexts/SDKContext";
|
||||||
|
import { Action } from "../dispatcher/actions";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acts as a cache of many RoomViewStore instances, creating them as necessary
|
||||||
|
* given a room ID.
|
||||||
|
*/
|
||||||
|
export class MultiRoomViewStore {
|
||||||
|
/**
|
||||||
|
* Map from room-id to RVS instance.
|
||||||
|
*/
|
||||||
|
private stores: Map<string, RoomViewStore> = new Map();
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private dispatcher: MatrixDispatcher,
|
||||||
|
private sdkContextClass: SdkContextClass,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a RVS instance for the room identified by the given roomId.
|
||||||
|
*/
|
||||||
|
public getRoomViewStoreForRoom(roomId: string): RoomViewStore {
|
||||||
|
// Get existing store / create new store
|
||||||
|
const store = this.stores.has(roomId)
|
||||||
|
? this.stores.get(roomId)!
|
||||||
|
: new RoomViewStore(this.dispatcher, this.sdkContextClass, roomId);
|
||||||
|
|
||||||
|
// RoomView component does not render the room unless you call viewRoom
|
||||||
|
store.viewRoom({
|
||||||
|
action: Action.ViewRoom,
|
||||||
|
room_id: roomId,
|
||||||
|
metricsTrigger: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cache the store, okay to do even if the store is already in the map
|
||||||
|
this.stores.set(roomId, store);
|
||||||
|
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a RVS instance that was created by {@link getRoomViewStoreForRoom}.
|
||||||
|
*/
|
||||||
|
public removeRoomViewStore(roomId: string): void {
|
||||||
|
const didRemove = this.stores.delete(roomId);
|
||||||
|
if (!didRemove) {
|
||||||
|
logger.warn(`removeRoomViewStore called with ${roomId} but no store exists for this room.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
for (const id of this.stores.keys()) {
|
||||||
|
this.removeRoomViewStore(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -153,6 +153,7 @@ export class RoomViewStore extends EventEmitter {
|
|||||||
public constructor(
|
public constructor(
|
||||||
dis: MatrixDispatcher,
|
dis: MatrixDispatcher,
|
||||||
private readonly stores: SdkContextClass,
|
private readonly stores: SdkContextClass,
|
||||||
|
private readonly lockedToRoomId?: string,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.resetDispatcher(dis);
|
this.resetDispatcher(dis);
|
||||||
@@ -187,7 +188,7 @@ export class RoomViewStore extends EventEmitter {
|
|||||||
|
|
||||||
const lastRoomId = this.state.roomId;
|
const lastRoomId = this.state.roomId;
|
||||||
this.state = Object.assign(this.state, newState);
|
this.state = Object.assign(this.state, newState);
|
||||||
if (lastRoomId !== this.state.roomId) {
|
if (!this.lockedToRoomId && lastRoomId !== this.state.roomId) {
|
||||||
if (lastRoomId) this.emitForRoom(lastRoomId, false);
|
if (lastRoomId) this.emitForRoom(lastRoomId, false);
|
||||||
if (this.state.roomId) this.emitForRoom(this.state.roomId, true);
|
if (this.state.roomId) this.emitForRoom(this.state.roomId, true);
|
||||||
|
|
||||||
@@ -204,6 +205,9 @@ export class RoomViewStore extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onDispatch(payload: ActionPayload): void {
|
private onDispatch(payload: ActionPayload): void {
|
||||||
|
if (this.lockedToRoomId && payload.room_id && this.lockedToRoomId !== payload.room_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// eslint-disable-line @typescript-eslint/naming-convention
|
// eslint-disable-line @typescript-eslint/naming-convention
|
||||||
switch (payload.action) {
|
switch (payload.action) {
|
||||||
// view_room:
|
// view_room:
|
||||||
@@ -324,7 +328,7 @@ export class RoomViewStore extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async viewRoom(payload: ViewRoomPayload): Promise<void> {
|
public async viewRoom(payload: ViewRoomPayload): Promise<void> {
|
||||||
if (payload.room_id) {
|
if (payload.room_id) {
|
||||||
const room = MatrixClientPeg.safeGet().getRoom(payload.room_id);
|
const room = MatrixClientPeg.safeGet().getRoom(payload.room_id);
|
||||||
|
|
||||||
|
|||||||
@@ -12,15 +12,15 @@ import { NotificationLevel } from "./NotificationLevel";
|
|||||||
import { arrayDiff } from "../../utils/arrays";
|
import { arrayDiff } from "../../utils/arrays";
|
||||||
import { type RoomNotificationState } from "./RoomNotificationState";
|
import { type RoomNotificationState } from "./RoomNotificationState";
|
||||||
import { NotificationState, NotificationStateEvents } from "./NotificationState";
|
import { NotificationState, NotificationStateEvents } from "./NotificationState";
|
||||||
import { type FetchRoomFn } from "./ListNotificationState";
|
|
||||||
import { DefaultTagID } from "../room-list/models";
|
import { DefaultTagID } from "../room-list/models";
|
||||||
import RoomListStore from "../room-list/RoomListStore";
|
import RoomListStore from "../room-list/RoomListStore";
|
||||||
|
import { RoomNotificationStateStore } from "./RoomNotificationStateStore";
|
||||||
|
|
||||||
export class SpaceNotificationState extends NotificationState {
|
export class SpaceNotificationState extends NotificationState {
|
||||||
public rooms: Room[] = []; // exposed only for tests
|
public rooms: Room[] = []; // exposed only for tests
|
||||||
private states: { [spaceId: string]: RoomNotificationState } = {};
|
private states: { [spaceId: string]: RoomNotificationState } = {};
|
||||||
|
|
||||||
public constructor(private getRoomFn: FetchRoomFn) {
|
public constructor() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ export class SpaceNotificationState extends NotificationState {
|
|||||||
state.off(NotificationStateEvents.Update, this.onRoomNotificationStateUpdate);
|
state.off(NotificationStateEvents.Update, this.onRoomNotificationStateUpdate);
|
||||||
}
|
}
|
||||||
for (const newRoom of diff.added) {
|
for (const newRoom of diff.added) {
|
||||||
const state = this.getRoomFn(newRoom);
|
const state = RoomNotificationStateStore.instance.getRoomState(newRoom);
|
||||||
state.on(NotificationStateEvents.Update, this.onRoomNotificationStateUpdate);
|
state.on(NotificationStateEvents.Update, this.onRoomNotificationStateUpdate);
|
||||||
this.states[newRoom.roomId] = state;
|
this.states[newRoom.roomId] = state;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import defaultDispatcher from "../../dispatcher/dispatcher";
|
|||||||
import RoomListStore from "../room-list/RoomListStore";
|
import RoomListStore from "../room-list/RoomListStore";
|
||||||
import SettingsStore from "../../settings/SettingsStore";
|
import SettingsStore from "../../settings/SettingsStore";
|
||||||
import DMRoomMap from "../../utils/DMRoomMap";
|
import DMRoomMap from "../../utils/DMRoomMap";
|
||||||
import { type FetchRoomFn } from "../notifications/ListNotificationState";
|
|
||||||
import { SpaceNotificationState } from "../notifications/SpaceNotificationState";
|
import { SpaceNotificationState } from "../notifications/SpaceNotificationState";
|
||||||
import { RoomNotificationStateStore } from "../notifications/RoomNotificationStateStore";
|
import { RoomNotificationStateStore } from "../notifications/RoomNotificationStateStore";
|
||||||
import { DefaultTagID } from "../room-list/models";
|
import { DefaultTagID } from "../room-list/models";
|
||||||
@@ -63,6 +62,7 @@ import { type ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePage
|
|||||||
import { type SwitchSpacePayload } from "../../dispatcher/payloads/SwitchSpacePayload";
|
import { type SwitchSpacePayload } from "../../dispatcher/payloads/SwitchSpacePayload";
|
||||||
import { type AfterLeaveRoomPayload } from "../../dispatcher/payloads/AfterLeaveRoomPayload";
|
import { type AfterLeaveRoomPayload } from "../../dispatcher/payloads/AfterLeaveRoomPayload";
|
||||||
import { SdkContextClass } from "../../contexts/SDKContext";
|
import { SdkContextClass } from "../../contexts/SDKContext";
|
||||||
|
import { ModuleApi } from "../../modules/Api.ts";
|
||||||
|
|
||||||
const ACTIVE_SPACE_LS_KEY = "mx_active_space";
|
const ACTIVE_SPACE_LS_KEY = "mx_active_space";
|
||||||
|
|
||||||
@@ -111,10 +111,6 @@ export const getChildOrder = (
|
|||||||
return [validOrder(order) ?? NaN, ts, roomId]; // NaN has lodash sort it at the end in asc
|
return [validOrder(order) ?? NaN, ts, roomId]; // NaN has lodash sort it at the end in asc
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRoomFn: FetchRoomFn = (room: Room) => {
|
|
||||||
return RoomNotificationStateStore.instance.getRoomState(room);
|
|
||||||
};
|
|
||||||
|
|
||||||
type SpaceStoreActions =
|
type SpaceStoreActions =
|
||||||
| SettingUpdatedPayload
|
| SettingUpdatedPayload
|
||||||
| ViewRoomPayload
|
| ViewRoomPayload
|
||||||
@@ -258,7 +254,9 @@ export class SpaceStoreClass extends AsyncStoreWithClient<EmptyObject> {
|
|||||||
if (!space || !this.matrixClient || space === this.activeSpace) return;
|
if (!space || !this.matrixClient || space === this.activeSpace) return;
|
||||||
|
|
||||||
let cliSpace: Room | null = null;
|
let cliSpace: Room | null = null;
|
||||||
if (!isMetaSpace(space)) {
|
if (ModuleApi.instance.extras.spacePanelItems.has(space)) {
|
||||||
|
// it's a "space" provided by a module: that's good enough
|
||||||
|
} else if (!isMetaSpace(space)) {
|
||||||
cliSpace = this.matrixClient.getRoom(space);
|
cliSpace = this.matrixClient.getRoom(space);
|
||||||
if (!cliSpace?.isSpaceRoom()) return;
|
if (!cliSpace?.isSpaceRoom()) return;
|
||||||
} else if (!this.enabledMetaSpaces.includes(space)) {
|
} else if (!this.enabledMetaSpaces.includes(space)) {
|
||||||
@@ -293,6 +291,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient<EmptyObject> {
|
|||||||
context_switch: true,
|
context_switch: true,
|
||||||
metricsTrigger: "WebSpaceContextSwitch",
|
metricsTrigger: "WebSpaceContextSwitch",
|
||||||
});
|
});
|
||||||
|
} else if (ModuleApi.instance.extras.spacePanelItems.has(space)) {
|
||||||
|
// module will handle this
|
||||||
} else {
|
} else {
|
||||||
defaultDispatcher.dispatch<ViewHomePagePayload>({
|
defaultDispatcher.dispatch<ViewHomePagePayload>({
|
||||||
action: Action.ViewHomePage,
|
action: Action.ViewHomePage,
|
||||||
@@ -1214,7 +1214,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient<EmptyObject> {
|
|||||||
const lastSpaceId = window.localStorage.getItem(ACTIVE_SPACE_LS_KEY) as MetaSpace;
|
const lastSpaceId = window.localStorage.getItem(ACTIVE_SPACE_LS_KEY) as MetaSpace;
|
||||||
const valid =
|
const valid =
|
||||||
lastSpaceId &&
|
lastSpaceId &&
|
||||||
(!isMetaSpace(lastSpaceId) ? this.matrixClient.getRoom(lastSpaceId) : enabledMetaSpaces[lastSpaceId]);
|
(ModuleApi.instance.extras.spacePanelItems.has(lastSpaceId) ||
|
||||||
|
(!isMetaSpace(lastSpaceId) ? this.matrixClient.getRoom(lastSpaceId) : enabledMetaSpaces[lastSpaceId]));
|
||||||
if (valid) {
|
if (valid) {
|
||||||
// don't context switch here as it may break permalinks
|
// don't context switch here as it may break permalinks
|
||||||
this.setActiveSpace(lastSpaceId, false);
|
this.setActiveSpace(lastSpaceId, false);
|
||||||
@@ -1369,7 +1370,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<EmptyObject> {
|
|||||||
return this.notificationStateMap.get(key)!;
|
return this.notificationStateMap.get(key)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = new SpaceNotificationState(getRoomFn);
|
const state = new SpaceNotificationState();
|
||||||
this.notificationStateMap.set(key, state);
|
this.notificationStateMap.set(key, state);
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
|||||||
Please see LICENSE files in the repository root for full details.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type Room, type HierarchyRoom } from "matrix-js-sdk/src/matrix";
|
import { type HierarchyRoom } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import { _t } from "../../languageHandler";
|
import { _t } from "../../languageHandler";
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ export const getMetaSpaceName = (spaceKey: MetaSpace, allRoomsInHome = false): s
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SpaceKey = MetaSpace | Room["roomId"];
|
export type SpaceKey = string;
|
||||||
|
|
||||||
export interface ISuggestedRoom extends HierarchyRoom {
|
export interface ISuggestedRoom extends HierarchyRoom {
|
||||||
viaServers: string[];
|
viaServers: string[];
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ import { ModuleRunner } from "../modules/ModuleRunner";
|
|||||||
import { parseQs } from "./url_utils";
|
import { parseQs } from "./url_utils";
|
||||||
import { getInitialScreenAfterLogin, getScreenFromLocation, init as initRouting, onNewScreen } from "./routing";
|
import { getInitialScreenAfterLogin, getScreenFromLocation, init as initRouting, onNewScreen } from "./routing";
|
||||||
import { UserFriendlyError } from "../languageHandler";
|
import { UserFriendlyError } from "../languageHandler";
|
||||||
|
import { ModuleApi } from "../modules/Api";
|
||||||
|
import { RoomView } from "../components/structures/RoomView";
|
||||||
|
import RoomAvatar from "../components/views/avatars/RoomAvatar";
|
||||||
|
|
||||||
logger.log(`Application is running in ${process.env.NODE_ENV} mode`);
|
logger.log(`Application is running in ${process.env.NODE_ENV} mode`);
|
||||||
|
|
||||||
@@ -53,6 +56,10 @@ function onTokenLoginCompleted(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function loadApp(fragParams: QueryDict, matrixChatRef: React.Ref<MatrixChat>): Promise<ReactElement> {
|
export async function loadApp(fragParams: QueryDict, matrixChatRef: React.Ref<MatrixChat>): Promise<ReactElement> {
|
||||||
|
// XXX: This lives here because certain components import so many things that importing it in a sensible place (eg.
|
||||||
|
// the builtins module or init.tsx) causes a circular dependency.
|
||||||
|
ModuleApi.instance.builtins.setComponents({ roomView: RoomView, roomAvatar: RoomAvatar });
|
||||||
|
|
||||||
initRouting();
|
initRouting();
|
||||||
const platform = PlatformPeg.get();
|
const platform = PlatformPeg.get();
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import { type MockedObject } from "jest-mock";
|
|||||||
import { type EventTimeline, EventType, type MatrixClient, type MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
import { type EventTimeline, EventType, type MatrixClient, type MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||||
|
|
||||||
import { type IRoomState, MainSplitContentType } from "../../src/components/structures/RoomView";
|
import { MainSplitContentType } from "../../src/components/structures/RoomView";
|
||||||
import { TimelineRenderingType } from "../../src/contexts/RoomContext";
|
import { type RoomContextType, TimelineRenderingType } from "../../src/contexts/RoomContext";
|
||||||
import { Layout } from "../../src/settings/enums/Layout";
|
import { Layout } from "../../src/settings/enums/Layout";
|
||||||
import { mkEvent } from "./test-utils";
|
import { mkEvent } from "./test-utils";
|
||||||
import { SdkContextClass } from "../../src/contexts/SDKContext";
|
import { SdkContextClass } from "../../src/contexts/SDKContext";
|
||||||
@@ -43,7 +43,7 @@ export const makeRoomWithStateEvents = (
|
|||||||
return room1;
|
return room1;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getRoomContext(room: Room, override: Partial<IRoomState>): IRoomState {
|
export function getRoomContext(room: Room, override: Partial<RoomContextType>): RoomContextType {
|
||||||
return {
|
return {
|
||||||
roomViewStore: SdkContextClass.instance.roomViewStore,
|
roomViewStore: SdkContextClass.instance.roomViewStore,
|
||||||
room,
|
room,
|
||||||
|
|||||||
@@ -650,6 +650,7 @@ export function mkStubRoom(
|
|||||||
getJoinedMembers: jest.fn().mockReturnValue([]),
|
getJoinedMembers: jest.fn().mockReturnValue([]),
|
||||||
getLiveTimeline: jest.fn().mockReturnValue(stubTimeline),
|
getLiveTimeline: jest.fn().mockReturnValue(stubTimeline),
|
||||||
getLastLiveEvent: jest.fn().mockReturnValue(undefined),
|
getLastLiveEvent: jest.fn().mockReturnValue(undefined),
|
||||||
|
getLastActiveTimestamp: jest.fn().mockReturnValue(1183140000),
|
||||||
getMember: jest.fn().mockReturnValue({
|
getMember: jest.fn().mockReturnValue({
|
||||||
userId: "@member:domain.bla",
|
userId: "@member:domain.bla",
|
||||||
name: "Member",
|
name: "Member",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { render } from "jest-matrix-react";
|
|||||||
|
|
||||||
import MessagePanel, { shouldFormContinuation } from "../../../../src/components/structures/MessagePanel";
|
import MessagePanel, { shouldFormContinuation } from "../../../../src/components/structures/MessagePanel";
|
||||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||||
import RoomContext, { TimelineRenderingType } from "../../../../src/contexts/RoomContext";
|
import RoomContext, { type RoomContextType, TimelineRenderingType } from "../../../../src/contexts/RoomContext";
|
||||||
import DMRoomMap from "../../../../src/utils/DMRoomMap";
|
import DMRoomMap from "../../../../src/utils/DMRoomMap";
|
||||||
import * as TestUtilsMatrix from "../../../test-utils";
|
import * as TestUtilsMatrix from "../../../test-utils";
|
||||||
import {
|
import {
|
||||||
@@ -29,7 +29,6 @@ import {
|
|||||||
mockClientPushProcessor,
|
mockClientPushProcessor,
|
||||||
} from "../../../test-utils";
|
} from "../../../test-utils";
|
||||||
import type ResizeNotifier from "../../../../src/utils/ResizeNotifier";
|
import type ResizeNotifier from "../../../../src/utils/ResizeNotifier";
|
||||||
import { type IRoomState } from "../../../../src/components/structures/RoomView";
|
|
||||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||||
import { ScopedRoomContextProvider } from "../../../../src/contexts/ScopedRoomContext.tsx";
|
import { ScopedRoomContextProvider } from "../../../../src/contexts/ScopedRoomContext.tsx";
|
||||||
import { SdkContextClass } from "../../../../src/contexts/SDKContext.ts";
|
import { SdkContextClass } from "../../../../src/contexts/SDKContext.ts";
|
||||||
@@ -92,9 +91,9 @@ describe("MessagePanel", function () {
|
|||||||
showAvatarChanges: false,
|
showAvatarChanges: false,
|
||||||
showDisplaynameChanges: true,
|
showDisplaynameChanges: true,
|
||||||
showHiddenEvents: false,
|
showHiddenEvents: false,
|
||||||
} as unknown as IRoomState;
|
} as unknown as RoomContextType;
|
||||||
|
|
||||||
const getComponent = (props = {}, roomContext: Partial<IRoomState> = {}) => (
|
const getComponent = (props = {}, roomContext: Partial<RoomContextType> = {}) => (
|
||||||
<ScopedRoomContextProvider {...defaultRoomContext} {...roomContext}>
|
<ScopedRoomContextProvider {...defaultRoomContext} {...roomContext}>
|
||||||
<MessagePanel {...defaultProps} {...props} />
|
<MessagePanel {...defaultProps} {...props} />
|
||||||
</ScopedRoomContextProvider>
|
</ScopedRoomContextProvider>
|
||||||
|
|||||||
@@ -89,7 +89,6 @@ describe("RoomView", () => {
|
|||||||
let cli: MockedObject<MatrixClient>;
|
let cli: MockedObject<MatrixClient>;
|
||||||
let room: Room;
|
let room: Room;
|
||||||
let rooms: Map<string, Room>;
|
let rooms: Map<string, Room>;
|
||||||
let roomCount = 0;
|
|
||||||
let stores: SdkContextClass;
|
let stores: SdkContextClass;
|
||||||
let crypto: CryptoApi;
|
let crypto: CryptoApi;
|
||||||
|
|
||||||
@@ -100,7 +99,9 @@ describe("RoomView", () => {
|
|||||||
mockPlatformPeg({ reload: () => {} });
|
mockPlatformPeg({ reload: () => {} });
|
||||||
cli = mocked(stubClient());
|
cli = mocked(stubClient());
|
||||||
|
|
||||||
room = new Room(`!${roomCount++}:example.org`, cli, "@alice:example.org");
|
const roomName = (expect.getState().currentTestName ?? "").replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
|
||||||
|
|
||||||
|
room = new Room(`!${roomName}:example.org`, cli, "@alice:example.org");
|
||||||
jest.spyOn(room, "findPredecessor");
|
jest.spyOn(room, "findPredecessor");
|
||||||
room.getPendingEvents = () => [];
|
room.getPendingEvents = () => [];
|
||||||
rooms = new Map();
|
rooms = new Map();
|
||||||
@@ -158,7 +159,6 @@ describe("RoomView", () => {
|
|||||||
threepidInvite={undefined as any}
|
threepidInvite={undefined as any}
|
||||||
forceTimeline={false}
|
forceTimeline={false}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
roomViewStore={stores.roomViewStore}
|
|
||||||
/>
|
/>
|
||||||
</SDKContext.Provider>
|
</SDKContext.Provider>
|
||||||
</MatrixClientContext.Provider>,
|
</MatrixClientContext.Provider>,
|
||||||
@@ -197,7 +197,6 @@ describe("RoomView", () => {
|
|||||||
threepidInvite={undefined}
|
threepidInvite={undefined}
|
||||||
forceTimeline={false}
|
forceTimeline={false}
|
||||||
onRegistered={jest.fn()}
|
onRegistered={jest.fn()}
|
||||||
roomViewStore={stores.roomViewStore}
|
|
||||||
/>
|
/>
|
||||||
</SDKContext.Provider>
|
</SDKContext.Provider>
|
||||||
</MatrixClientContext.Provider>,
|
</MatrixClientContext.Provider>,
|
||||||
@@ -211,6 +210,26 @@ describe("RoomView", () => {
|
|||||||
return ref.current!;
|
return ref.current!;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
it("gets a room view store from MultiRoomViewStore when given a room ID", async () => {
|
||||||
|
stores.multiRoomViewStore.getRoomViewStoreForRoom = jest.fn().mockReturnValue(stores.roomViewStore);
|
||||||
|
|
||||||
|
const ref = createRef<RoomView>();
|
||||||
|
render(
|
||||||
|
<MatrixClientContext.Provider value={cli}>
|
||||||
|
<SDKContext.Provider value={stores}>
|
||||||
|
<RoomView
|
||||||
|
threepidInvite={undefined as any}
|
||||||
|
forceTimeline={false}
|
||||||
|
ref={ref}
|
||||||
|
roomId="!room:example.dummy"
|
||||||
|
/>
|
||||||
|
</SDKContext.Provider>
|
||||||
|
</MatrixClientContext.Provider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(stores.multiRoomViewStore.getRoomViewStoreForRoom).toHaveBeenCalledWith("!room:example.dummy");
|
||||||
|
});
|
||||||
|
|
||||||
it("should show member list right panel phase on Action.ViewUser without `payload.member`", async () => {
|
it("should show member list right panel phase on Action.ViewUser without `payload.member`", async () => {
|
||||||
const spy = jest.spyOn(stores.rightPanelStore, "showOrHidePhase");
|
const spy = jest.spyOn(stores.rightPanelStore, "showOrHidePhase");
|
||||||
await renderRoomView(false);
|
await renderRoomView(false);
|
||||||
@@ -707,7 +726,7 @@ describe("RoomView", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should switch rooms when edit is clicked on a search result for a different room", async () => {
|
it("should switch rooms when edit is clicked on a search result for a different room", async () => {
|
||||||
const room2 = new Room(`!${roomCount++}:example.org`, cli, "@alice:example.org");
|
const room2 = new Room(`!roomswitchtest:example.org`, cli, "@alice:example.org");
|
||||||
rooms.set(room2.roomId, room2);
|
rooms.set(room2.roomId, room2);
|
||||||
|
|
||||||
room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Join);
|
room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Join);
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalink
|
|||||||
import ResizeNotifier from "../../../../src/utils/ResizeNotifier";
|
import ResizeNotifier from "../../../../src/utils/ResizeNotifier";
|
||||||
import { createTestClient, getRoomContext, mkRoom, mockPlatformPeg, stubClient } from "../../../test-utils";
|
import { createTestClient, getRoomContext, mkRoom, mockPlatformPeg, stubClient } from "../../../test-utils";
|
||||||
import { mkThread } from "../../../test-utils/threads";
|
import { mkThread } from "../../../test-utils/threads";
|
||||||
import { type IRoomState } from "../../../../src/components/structures/RoomView";
|
|
||||||
import { ScopedRoomContextProvider } from "../../../../src/contexts/ScopedRoomContext.tsx";
|
import { ScopedRoomContextProvider } from "../../../../src/contexts/ScopedRoomContext.tsx";
|
||||||
|
import type { RoomContextType } from "../../../../src/contexts/RoomContext.ts";
|
||||||
|
|
||||||
jest.mock("../../../../src/utils/Feedback");
|
jest.mock("../../../../src/utils/Feedback");
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ describe("ThreadPanel", () => {
|
|||||||
mockRoom.getLastLiveEvent.mockReturnValue(mockEvent);
|
mockRoom.getLastLiveEvent.mockReturnValue(mockEvent);
|
||||||
const roomContextObject = {
|
const roomContextObject = {
|
||||||
room: mockRoom,
|
room: mockRoom,
|
||||||
} as unknown as IRoomState;
|
} as unknown as RoomContextType;
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<ScopedRoomContextProvider {...roomContextObject}>
|
<ScopedRoomContextProvider {...roomContextObject}>
|
||||||
<MatrixClientContext.Provider value={mockClient}>
|
<MatrixClientContext.Provider value={mockClient}>
|
||||||
|
|||||||
@@ -1099,12 +1099,12 @@ exports[`RoomView invites renders an invite room 1`] = `
|
|||||||
class="mx_RoomPreviewBar_message"
|
class="mx_RoomPreviewBar_message"
|
||||||
>
|
>
|
||||||
<h3>
|
<h3>
|
||||||
Do you want to join !2:example.org?
|
Do you want to join !roomviewinvitesrendersaninviteroom:example.org?
|
||||||
</h3>
|
</h3>
|
||||||
<p>
|
<p>
|
||||||
<span
|
<span
|
||||||
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
|
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
|
||||||
data-color="4"
|
data-color="2"
|
||||||
data-testid="avatar-img"
|
data-testid="avatar-img"
|
||||||
data-type="round"
|
data-type="round"
|
||||||
role="presentation"
|
role="presentation"
|
||||||
@@ -1185,7 +1185,7 @@ exports[`RoomView should not display the timeline when the room encryption is lo
|
|||||||
aria-label="Open room settings"
|
aria-label="Open room settings"
|
||||||
aria-live="off"
|
aria-live="off"
|
||||||
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
|
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
|
||||||
data-color="5"
|
data-color="1"
|
||||||
data-testid="avatar-img"
|
data-testid="avatar-img"
|
||||||
data-type="round"
|
data-type="round"
|
||||||
role="button"
|
role="button"
|
||||||
@@ -1212,7 +1212,7 @@ exports[`RoomView should not display the timeline when the room encryption is lo
|
|||||||
<span
|
<span
|
||||||
class="mx_RoomHeader_truncated mx_lineClamp"
|
class="mx_RoomHeader_truncated mx_lineClamp"
|
||||||
>
|
>
|
||||||
!12:example.org
|
!roomviewshouldnotdisplaythetimelinewhentheroomencryptionisloading:example.org
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1397,7 +1397,7 @@ exports[`RoomView should not display the timeline when the room encryption is lo
|
|||||||
aria-label="Open room settings"
|
aria-label="Open room settings"
|
||||||
aria-live="off"
|
aria-live="off"
|
||||||
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
|
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
|
||||||
data-color="5"
|
data-color="1"
|
||||||
data-testid="avatar-img"
|
data-testid="avatar-img"
|
||||||
data-type="round"
|
data-type="round"
|
||||||
role="button"
|
role="button"
|
||||||
@@ -1424,7 +1424,7 @@ exports[`RoomView should not display the timeline when the room encryption is lo
|
|||||||
<span
|
<span
|
||||||
class="mx_RoomHeader_truncated mx_lineClamp"
|
class="mx_RoomHeader_truncated mx_lineClamp"
|
||||||
>
|
>
|
||||||
!12:example.org
|
!roomviewshouldnotdisplaythetimelinewhentheroomencryptionisloading:example.org
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1787,7 +1787,7 @@ exports[`RoomView video rooms should render joined video room view 1`] = `
|
|||||||
aria-label="Open room settings"
|
aria-label="Open room settings"
|
||||||
aria-live="off"
|
aria-live="off"
|
||||||
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
|
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
|
||||||
data-color="4"
|
data-color="2"
|
||||||
data-testid="avatar-img"
|
data-testid="avatar-img"
|
||||||
data-type="round"
|
data-type="round"
|
||||||
role="button"
|
role="button"
|
||||||
@@ -1814,7 +1814,7 @@ exports[`RoomView video rooms should render joined video room view 1`] = `
|
|||||||
<span
|
<span
|
||||||
class="mx_RoomHeader_truncated mx_lineClamp"
|
class="mx_RoomHeader_truncated mx_lineClamp"
|
||||||
>
|
>
|
||||||
!17:example.org
|
!roomviewvideoroomsshouldrenderjoinedvideoroomview:example.org
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,10 +15,9 @@ import RecordingPlayback, {
|
|||||||
PlaybackLayout,
|
PlaybackLayout,
|
||||||
} from "../../../../../src/components/views/audio_messages/RecordingPlayback";
|
} from "../../../../../src/components/views/audio_messages/RecordingPlayback";
|
||||||
import { Playback } from "../../../../../src/audio/Playback";
|
import { Playback } from "../../../../../src/audio/Playback";
|
||||||
import { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
import { type RoomContextType, TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||||
import { createAudioContext } from "../../../../../src/audio/compat";
|
import { createAudioContext } from "../../../../../src/audio/compat";
|
||||||
import { flushPromises } from "../../../../test-utils";
|
import { flushPromises } from "../../../../test-utils";
|
||||||
import { type IRoomState } from "../../../../../src/components/structures/RoomView";
|
|
||||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||||
|
|
||||||
jest.mock("../../../../../src/WorkerManager", () => ({
|
jest.mock("../../../../../src/WorkerManager", () => ({
|
||||||
@@ -54,7 +53,10 @@ describe("<RecordingPlayback />", () => {
|
|||||||
|
|
||||||
const mockChannelData = new Float32Array();
|
const mockChannelData = new Float32Array();
|
||||||
|
|
||||||
const defaultRoom = { roomId: "!room:server.org", timelineRenderingType: TimelineRenderingType.File } as IRoomState;
|
const defaultRoom = {
|
||||||
|
roomId: "!room:server.org",
|
||||||
|
timelineRenderingType: TimelineRenderingType.File,
|
||||||
|
} as RoomContextType;
|
||||||
const getComponent = (props: React.ComponentProps<typeof RecordingPlayback>, room = defaultRoom) =>
|
const getComponent = (props: React.ComponentProps<typeof RecordingPlayback>, room = defaultRoom) =>
|
||||||
render(
|
render(
|
||||||
<ScopedRoomContextProvider {...room}>
|
<ScopedRoomContextProvider {...room}>
|
||||||
|
|||||||
@@ -31,8 +31,7 @@ import { WidgetMessagingStore } from "../../../../../src/stores/widgets/WidgetMe
|
|||||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||||
import { ConnectionState } from "../../../../../src/models/Call";
|
import { ConnectionState } from "../../../../../src/models/Call";
|
||||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext";
|
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext";
|
||||||
import { type IRoomState } from "../../../../../src/components/structures/RoomView";
|
import RoomContext, { type RoomContextType } from "../../../../../src/contexts/RoomContext";
|
||||||
import RoomContext from "../../../../../src/contexts/RoomContext";
|
|
||||||
|
|
||||||
describe("<RoomCallBanner />", () => {
|
describe("<RoomCallBanner />", () => {
|
||||||
let client: Mocked<MatrixClient>;
|
let client: Mocked<MatrixClient>;
|
||||||
@@ -51,7 +50,7 @@ describe("<RoomCallBanner />", () => {
|
|||||||
emit: jest.fn(),
|
emit: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let roomContext: IRoomState;
|
let roomContext: RoomContextType;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
stubClient();
|
stubClient();
|
||||||
@@ -79,7 +78,7 @@ describe("<RoomCallBanner />", () => {
|
|||||||
...RoomContext,
|
...RoomContext,
|
||||||
roomId: room.roomId,
|
roomId: room.roomId,
|
||||||
roomViewStore: mockRoomViewStore,
|
roomViewStore: mockRoomViewStore,
|
||||||
} as unknown as IRoomState;
|
} as unknown as RoomContextType;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
|
|||||||
@@ -27,8 +27,7 @@ import { mocked } from "jest-mock";
|
|||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
|
|
||||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||||
import { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
import { type RoomContextType, TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||||
import { type IRoomState } from "../../../../../src/components/structures/RoomView";
|
|
||||||
import { canEditContent } from "../../../../../src/utils/EventUtils";
|
import { canEditContent } from "../../../../../src/utils/EventUtils";
|
||||||
import { copyPlaintext, getSelectedText } from "../../../../../src/utils/strings";
|
import { copyPlaintext, getSelectedText } from "../../../../../src/utils/strings";
|
||||||
import MessageContextMenu from "../../../../../src/components/views/context_menus/MessageContextMenu";
|
import MessageContextMenu from "../../../../../src/components/views/context_menus/MessageContextMenu";
|
||||||
@@ -711,18 +710,18 @@ describe("MessageContextMenu", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function createRightClickMenuWithContent(eventContent: object, context?: Partial<IRoomState>): RenderResult {
|
function createRightClickMenuWithContent(eventContent: object, context?: Partial<RoomContextType>): RenderResult {
|
||||||
return createMenuWithContent(eventContent, { rightClick: true }, context);
|
return createMenuWithContent(eventContent, { rightClick: true }, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createRightClickMenu(mxEvent: MatrixEvent, context?: Partial<IRoomState>): RenderResult {
|
function createRightClickMenu(mxEvent: MatrixEvent, context?: Partial<RoomContextType>): RenderResult {
|
||||||
return createMenu(mxEvent, { rightClick: true }, context);
|
return createMenu(mxEvent, { rightClick: true }, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMenuWithContent(
|
function createMenuWithContent(
|
||||||
eventContent: object,
|
eventContent: object,
|
||||||
props?: Partial<MessageContextMenu["props"]>,
|
props?: Partial<MessageContextMenu["props"]>,
|
||||||
context?: Partial<IRoomState>,
|
context?: Partial<RoomContextType>,
|
||||||
): RenderResult {
|
): RenderResult {
|
||||||
// XXX: We probably shouldn't be assuming all events are going to be message events, but considering this
|
// XXX: We probably shouldn't be assuming all events are going to be message events, but considering this
|
||||||
// test is for the Message context menu, it's a fairly safe assumption.
|
// test is for the Message context menu, it's a fairly safe assumption.
|
||||||
@@ -739,7 +738,7 @@ function makeDefaultRoom(): Room {
|
|||||||
function createMenu(
|
function createMenu(
|
||||||
mxEvent: MatrixEvent,
|
mxEvent: MatrixEvent,
|
||||||
props?: Partial<MessageContextMenu["props"]>,
|
props?: Partial<MessageContextMenu["props"]>,
|
||||||
context: Partial<IRoomState> = {},
|
context: Partial<RoomContextType> = {},
|
||||||
beacons: Map<BeaconIdentifier, Beacon> = new Map(),
|
beacons: Map<BeaconIdentifier, Beacon> = new Map(),
|
||||||
room: Room = makeDefaultRoom(),
|
room: Room = makeDefaultRoom(),
|
||||||
): RenderResult {
|
): RenderResult {
|
||||||
@@ -754,7 +753,7 @@ function createMenu(
|
|||||||
client.getRoom = jest.fn().mockReturnValue(room);
|
client.getRoom = jest.fn().mockReturnValue(room);
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
<ScopedRoomContextProvider {...(context as IRoomState)}>
|
<ScopedRoomContextProvider {...(context as RoomContextType)}>
|
||||||
<MessageContextMenu mxEvent={mxEvent} onFinished={jest.fn()} {...props} />
|
<MessageContextMenu mxEvent={mxEvent} onFinished={jest.fn()} {...props} />
|
||||||
</ScopedRoomContextProvider>,
|
</ScopedRoomContextProvider>,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ import {
|
|||||||
} from "../../../../test-utils";
|
} from "../../../../test-utils";
|
||||||
import DateSeparator from "../../../../../src/components/views/messages/DateSeparator";
|
import DateSeparator from "../../../../../src/components/views/messages/DateSeparator";
|
||||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext";
|
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext";
|
||||||
import { type IRoomState } from "../../../../../src/components/structures/RoomView";
|
import RoomContext, { type RoomContextType } from "../../../../../src/contexts/RoomContext";
|
||||||
import RoomContext from "../../../../../src/contexts/RoomContext";
|
|
||||||
|
|
||||||
jest.mock("../../../../../src/settings/SettingsStore");
|
jest.mock("../../../../../src/settings/SettingsStore");
|
||||||
|
|
||||||
@@ -50,7 +49,7 @@ describe("DateSeparator", () => {
|
|||||||
...RoomContext,
|
...RoomContext,
|
||||||
roomId,
|
roomId,
|
||||||
roomViewStore: mockRoomViewStore,
|
roomViewStore: mockRoomViewStore,
|
||||||
} as unknown as IRoomState;
|
} as unknown as RoomContextType;
|
||||||
|
|
||||||
const mockClient = getMockClientWithEventEmitter({
|
const mockClient = getMockClientWithEventEmitter({
|
||||||
timestampToEvent: jest.fn(),
|
timestampToEvent: jest.fn(),
|
||||||
|
|||||||
@@ -29,8 +29,7 @@ import {
|
|||||||
makeBeaconInfoEvent,
|
makeBeaconInfoEvent,
|
||||||
} from "../../../../test-utils";
|
} from "../../../../test-utils";
|
||||||
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
|
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
|
||||||
import RoomContext, { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
import RoomContext, { type RoomContextType, TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||||
import { type IRoomState } from "../../../../../src/components/structures/RoomView";
|
|
||||||
import dispatcher from "../../../../../src/dispatcher/dispatcher";
|
import dispatcher from "../../../../../src/dispatcher/dispatcher";
|
||||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||||
import { Action } from "../../../../../src/dispatcher/actions";
|
import { Action } from "../../../../../src/dispatcher/actions";
|
||||||
@@ -115,8 +114,8 @@ describe("<MessageActionBar />", () => {
|
|||||||
canSendMessages: true,
|
canSendMessages: true,
|
||||||
canReact: true,
|
canReact: true,
|
||||||
room,
|
room,
|
||||||
} as unknown as IRoomState;
|
} as unknown as RoomContextType;
|
||||||
const getComponent = (props = {}, roomContext: Partial<IRoomState> = {}) =>
|
const getComponent = (props = {}, roomContext: Partial<RoomContextType> = {}) =>
|
||||||
render(
|
render(
|
||||||
<ScopedRoomContextProvider {...defaultRoomContext} {...roomContext}>
|
<ScopedRoomContextProvider {...defaultRoomContext} {...roomContext}>
|
||||||
<MessageActionBar {...defaultProps} {...props} />
|
<MessageActionBar {...defaultProps} {...props} />
|
||||||
|
|||||||
@@ -27,12 +27,12 @@ import {
|
|||||||
import DocumentOffset from "../../../../../src/editor/offset";
|
import DocumentOffset from "../../../../../src/editor/offset";
|
||||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||||
import EditorStateTransfer from "../../../../../src/utils/EditorStateTransfer";
|
import EditorStateTransfer from "../../../../../src/utils/EditorStateTransfer";
|
||||||
import { type IRoomState } from "../../../../../src/components/structures/RoomView";
|
|
||||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||||
import Autocompleter, { type IProviderCompletions } from "../../../../../src/autocomplete/Autocompleter";
|
import Autocompleter, { type IProviderCompletions } from "../../../../../src/autocomplete/Autocompleter";
|
||||||
import NotifProvider from "../../../../../src/autocomplete/NotifProvider";
|
import NotifProvider from "../../../../../src/autocomplete/NotifProvider";
|
||||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||||
|
import type { RoomContextType } from "../../../../../src/contexts/RoomContext.ts";
|
||||||
|
|
||||||
describe("<EditMessageComposer/>", () => {
|
describe("<EditMessageComposer/>", () => {
|
||||||
const userId = "@alice:server.org";
|
const userId = "@alice:server.org";
|
||||||
@@ -75,7 +75,7 @@ describe("<EditMessageComposer/>", () => {
|
|||||||
|
|
||||||
const defaultRoomContext = getRoomContext(room, {});
|
const defaultRoomContext = getRoomContext(room, {});
|
||||||
|
|
||||||
const getComponent = (editState: EditorStateTransfer, roomContext: IRoomState = defaultRoomContext) =>
|
const getComponent = (editState: EditorStateTransfer, roomContext: RoomContextType = defaultRoomContext) =>
|
||||||
render(<EditMessageComposerWithMatrixClient editState={editState} />, {
|
render(<EditMessageComposerWithMatrixClient editState={editState} />, {
|
||||||
wrapper: ({ children }) => (
|
wrapper: ({ children }) => (
|
||||||
<MatrixClientContext.Provider value={mockClient}>
|
<MatrixClientContext.Provider value={mockClient}>
|
||||||
|
|||||||
@@ -30,14 +30,13 @@ import { mkEncryptedMatrixEvent } from "matrix-js-sdk/src/testing";
|
|||||||
|
|
||||||
import EventTile, { type EventTileProps } from "../../../../../src/components/views/rooms/EventTile";
|
import EventTile, { type EventTileProps } from "../../../../../src/components/views/rooms/EventTile";
|
||||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||||
import { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
import { type RoomContextType, TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||||
import { filterConsole, flushPromises, getRoomContext, mkEvent, mkMessage, stubClient } from "../../../../test-utils";
|
import { filterConsole, flushPromises, getRoomContext, mkEvent, mkMessage, stubClient } from "../../../../test-utils";
|
||||||
import { mkThread } from "../../../../test-utils/threads";
|
import { mkThread } from "../../../../test-utils/threads";
|
||||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||||
import dis from "../../../../../src/dispatcher/dispatcher";
|
import dis from "../../../../../src/dispatcher/dispatcher";
|
||||||
import { Action } from "../../../../../src/dispatcher/actions";
|
import { Action } from "../../../../../src/dispatcher/actions";
|
||||||
import { type IRoomState } from "../../../../../src/components/structures/RoomView";
|
|
||||||
import PinningUtils from "../../../../../src/utils/PinningUtils";
|
import PinningUtils from "../../../../../src/utils/PinningUtils";
|
||||||
import { Layout } from "../../../../../src/settings/enums/Layout";
|
import { Layout } from "../../../../../src/settings/enums/Layout";
|
||||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||||
@@ -52,7 +51,7 @@ describe("EventTile", () => {
|
|||||||
|
|
||||||
/** wrap the EventTile up in context providers, and with basic properties, as it would be by MessagePanel normally. */
|
/** wrap the EventTile up in context providers, and with basic properties, as it would be by MessagePanel normally. */
|
||||||
function WrappedEventTile(props: {
|
function WrappedEventTile(props: {
|
||||||
roomContext: IRoomState;
|
roomContext: RoomContextType;
|
||||||
eventTilePropertyOverrides?: Partial<EventTileProps>;
|
eventTilePropertyOverrides?: Partial<EventTileProps>;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
@@ -71,7 +70,7 @@ describe("EventTile", () => {
|
|||||||
function getComponent(
|
function getComponent(
|
||||||
overrides: Partial<EventTileProps> = {},
|
overrides: Partial<EventTileProps> = {},
|
||||||
renderingType: TimelineRenderingType = TimelineRenderingType.Room,
|
renderingType: TimelineRenderingType = TimelineRenderingType.Room,
|
||||||
roomContext: Partial<IRoomState> = {},
|
roomContext: Partial<RoomContextType> = {},
|
||||||
) {
|
) {
|
||||||
const context = getRoomContext(room, {
|
const context = getRoomContext(room, {
|
||||||
timelineRenderingType: renderingType,
|
timelineRenderingType: renderingType,
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import {
|
|||||||
import MessageComposer from "../../../../../src/components/views/rooms/MessageComposer";
|
import MessageComposer from "../../../../../src/components/views/rooms/MessageComposer";
|
||||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||||
import { type IRoomState } from "../../../../../src/components/structures/RoomView";
|
|
||||||
import ResizeNotifier from "../../../../../src/utils/ResizeNotifier";
|
import ResizeNotifier from "../../../../../src/utils/ResizeNotifier";
|
||||||
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
|
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
|
||||||
import { LocalRoom } from "../../../../../src/models/LocalRoom";
|
import { LocalRoom } from "../../../../../src/models/LocalRoom";
|
||||||
@@ -36,6 +35,7 @@ import { addTextToComposerRTL } from "../../../../test-utils/composer";
|
|||||||
import UIStore, { UI_EVENTS } from "../../../../../src/stores/UIStore";
|
import UIStore, { UI_EVENTS } from "../../../../../src/stores/UIStore";
|
||||||
import { Action } from "../../../../../src/dispatcher/actions";
|
import { Action } from "../../../../../src/dispatcher/actions";
|
||||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||||
|
import type { RoomContextType } from "../../../../../src/contexts/RoomContext.ts";
|
||||||
|
|
||||||
const openStickerPicker = async (): Promise<void> => {
|
const openStickerPicker = async (): Promise<void> => {
|
||||||
await userEvent.click(screen.getByLabelText("More options"));
|
await userEvent.click(screen.getByLabelText("More options"));
|
||||||
@@ -155,7 +155,7 @@ describe("MessageComposer", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("when receiving a »reply_to_event«", () => {
|
describe("when receiving a »reply_to_event«", () => {
|
||||||
let roomContext: IRoomState;
|
let roomContext: RoomContextType;
|
||||||
let resizeNotifier: ResizeNotifier;
|
let resizeNotifier: ResizeNotifier;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -458,7 +458,7 @@ function wrapAndRender(
|
|||||||
canSendMessages,
|
canSendMessages,
|
||||||
tombstone,
|
tombstone,
|
||||||
narrow,
|
narrow,
|
||||||
} as unknown as IRoomState;
|
} as unknown as RoomContextType;
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
room,
|
room,
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import { render, screen, waitFor } from "jest-matrix-react";
|
|||||||
|
|
||||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||||
import { createTestClient, getRoomContext, mkStubRoom } from "../../../../test-utils";
|
import { createTestClient, getRoomContext, mkStubRoom } from "../../../../test-utils";
|
||||||
import { type IRoomState } from "../../../../../src/components/structures/RoomView";
|
|
||||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||||
import MessageComposerButtons from "../../../../../src/components/views/rooms/MessageComposerButtons";
|
import MessageComposerButtons from "../../../../../src/components/views/rooms/MessageComposerButtons";
|
||||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||||
|
import type { RoomContextType } from "../../../../../src/contexts/RoomContext.ts";
|
||||||
|
|
||||||
describe("MessageComposerButtons", () => {
|
describe("MessageComposerButtons", () => {
|
||||||
// @ts-ignore - we're deliberately not implementing the whole interface here, but
|
// @ts-ignore - we're deliberately not implementing the whole interface here, but
|
||||||
@@ -50,7 +50,7 @@ describe("MessageComposerButtons", () => {
|
|||||||
|
|
||||||
function wrapAndRender(component: React.ReactElement, narrow: boolean) {
|
function wrapAndRender(component: React.ReactElement, narrow: boolean) {
|
||||||
const mockRoom = mkStubRoom("myfakeroom", "myfakeroom", mockClient) as any;
|
const mockRoom = mkStubRoom("myfakeroom", "myfakeroom", mockClient) as any;
|
||||||
const defaultRoomContext: IRoomState = getRoomContext(mockRoom, { narrow });
|
const defaultRoomContext: RoomContextType = getRoomContext(mockRoom, { narrow });
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
<MatrixClientContext.Provider value={mockClient}>
|
<MatrixClientContext.Provider value={mockClient}>
|
||||||
|
|||||||
@@ -22,16 +22,16 @@ import {
|
|||||||
} from "../../../../test-utils";
|
} from "../../../../test-utils";
|
||||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||||
import NewRoomIntro from "../../../../../src/components/views/rooms/NewRoomIntro";
|
import NewRoomIntro from "../../../../../src/components/views/rooms/NewRoomIntro";
|
||||||
import { type IRoomState } from "../../../../../src/components/structures/RoomView";
|
|
||||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||||
import { DirectoryMember } from "../../../../../src/utils/direct-messages";
|
import { DirectoryMember } from "../../../../../src/utils/direct-messages";
|
||||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||||
import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
|
import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
|
||||||
|
import type { RoomContextType } from "../../../../../src/contexts/RoomContext.ts";
|
||||||
|
|
||||||
const renderNewRoomIntro = (client: MatrixClient, room: Room | LocalRoom) => {
|
const renderNewRoomIntro = (client: MatrixClient, room: Room | LocalRoom) => {
|
||||||
render(
|
render(
|
||||||
<MatrixClientContext.Provider value={client}>
|
<MatrixClientContext.Provider value={client}>
|
||||||
<ScopedRoomContextProvider {...({ room, roomId: room.roomId } as unknown as IRoomState)}>
|
<ScopedRoomContextProvider {...({ room, roomId: room.roomId } as unknown as RoomContextType)}>
|
||||||
<NewRoomIntro />
|
<NewRoomIntro />
|
||||||
</ScopedRoomContextProvider>
|
</ScopedRoomContextProvider>
|
||||||
</MatrixClientContext.Provider>,
|
</MatrixClientContext.Provider>,
|
||||||
|
|||||||
@@ -41,8 +41,7 @@ import RoomHeader from "../../../../../../src/components/views/rooms/RoomHeader/
|
|||||||
import DMRoomMap from "../../../../../../src/utils/DMRoomMap";
|
import DMRoomMap from "../../../../../../src/utils/DMRoomMap";
|
||||||
import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg";
|
||||||
import { ScopedRoomContextProvider } from "../../../../../../src/contexts/ScopedRoomContext";
|
import { ScopedRoomContextProvider } from "../../../../../../src/contexts/ScopedRoomContext";
|
||||||
import { type IRoomState } from "../../../../../../src/components/structures/RoomView";
|
import RoomContext, { type RoomContextType } from "../../../../../../src/contexts/RoomContext";
|
||||||
import RoomContext from "../../../../../../src/contexts/RoomContext";
|
|
||||||
import RightPanelStore from "../../../../../../src/stores/right-panel/RightPanelStore";
|
import RightPanelStore from "../../../../../../src/stores/right-panel/RightPanelStore";
|
||||||
import { RightPanelPhases } from "../../../../../../src/stores/right-panel/RightPanelStorePhases";
|
import { RightPanelPhases } from "../../../../../../src/stores/right-panel/RightPanelStorePhases";
|
||||||
import LegacyCallHandler from "../../../../../../src/LegacyCallHandler";
|
import LegacyCallHandler from "../../../../../../src/LegacyCallHandler";
|
||||||
@@ -85,7 +84,7 @@ describe("RoomHeader", () => {
|
|||||||
emit: jest.fn(),
|
emit: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let roomContext: IRoomState;
|
let roomContext: RoomContextType;
|
||||||
|
|
||||||
function getWrapper(): RenderOptions {
|
function getWrapper(): RenderOptions {
|
||||||
return {
|
return {
|
||||||
@@ -121,7 +120,7 @@ describe("RoomHeader", () => {
|
|||||||
...RoomContext,
|
...RoomContext,
|
||||||
roomId: ROOM_ID,
|
roomId: ROOM_ID,
|
||||||
roomViewStore: mockRoomViewStore,
|
roomViewStore: mockRoomViewStore,
|
||||||
} as unknown as IRoomState;
|
} as unknown as RoomContextType;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import SendMessageComposer, {
|
|||||||
isQuickReaction,
|
isQuickReaction,
|
||||||
} from "../../../../../src/components/views/rooms/SendMessageComposer";
|
} from "../../../../../src/components/views/rooms/SendMessageComposer";
|
||||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||||
import { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
import { type RoomContextType, TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||||
import EditorModel from "../../../../../src/editor/model";
|
import EditorModel from "../../../../../src/editor/model";
|
||||||
import { createPartCreator } from "../../../editor/mock";
|
import { createPartCreator } from "../../../editor/mock";
|
||||||
import { createTestClient, mkEvent, mkStubRoom, stubClient } from "../../../../test-utils";
|
import { createTestClient, mkEvent, mkStubRoom, stubClient } from "../../../../test-utils";
|
||||||
@@ -25,7 +25,7 @@ import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
|||||||
import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
|
import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
|
||||||
import DocumentOffset from "../../../../../src/editor/offset";
|
import DocumentOffset from "../../../../../src/editor/offset";
|
||||||
import { Layout } from "../../../../../src/settings/enums/Layout";
|
import { Layout } from "../../../../../src/settings/enums/Layout";
|
||||||
import { type IRoomState, MainSplitContentType } from "../../../../../src/components/structures/RoomView";
|
import { MainSplitContentType } from "../../../../../src/components/structures/RoomView";
|
||||||
import { mockPlatformPeg } from "../../../../test-utils/platform";
|
import { mockPlatformPeg } from "../../../../test-utils/platform";
|
||||||
import { doMaybeLocalRoomAction } from "../../../../../src/utils/local-room";
|
import { doMaybeLocalRoomAction } from "../../../../../src/utils/local-room";
|
||||||
import { addTextToComposer } from "../../../../test-utils/composer";
|
import { addTextToComposer } from "../../../../test-utils/composer";
|
||||||
@@ -37,7 +37,7 @@ jest.mock("../../../../../src/utils/local-room", () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
describe("<SendMessageComposer/>", () => {
|
describe("<SendMessageComposer/>", () => {
|
||||||
const defaultRoomContext: IRoomState = {
|
const defaultRoomContext: RoomContextType = {
|
||||||
roomViewStore: SdkContextClass.instance.roomViewStore,
|
roomViewStore: SdkContextClass.instance.roomViewStore,
|
||||||
roomLoading: true,
|
roomLoading: true,
|
||||||
peekLoading: false,
|
peekLoading: false,
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
import { type EventTimeline, type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
import { type EventTimeline, type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import { getRoomContext, mkEvent, mkStubRoom, stubClient } from "../../../../../test-utils";
|
import { getRoomContext, mkEvent, mkStubRoom, stubClient } from "../../../../../test-utils";
|
||||||
import { type IRoomState } from "../../../../../../src/components/structures/RoomView";
|
|
||||||
import EditorStateTransfer from "../../../../../../src/utils/EditorStateTransfer";
|
import EditorStateTransfer from "../../../../../../src/utils/EditorStateTransfer";
|
||||||
|
import type { RoomContextType } from "../../../../../../src/contexts/RoomContext";
|
||||||
|
|
||||||
export function createMocks(eventContent = "Replying <strong>to</strong> this new content") {
|
export function createMocks(eventContent = "Replying <strong>to</strong> this new content") {
|
||||||
const mockClient = stubClient();
|
const mockClient = stubClient();
|
||||||
@@ -31,7 +31,7 @@ export function createMocks(eventContent = "Replying <strong>to</strong> this ne
|
|||||||
return eventId === mockEvent.getId() ? mockEvent : null;
|
return eventId === mockEvent.getId() ? mockEvent : null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultRoomContext: IRoomState = getRoomContext(mockRoom, {
|
const defaultRoomContext: RoomContextType = getRoomContext(mockRoom, {
|
||||||
liveTimeline: { getEvents: (): MatrixEvent[] => [] } as unknown as EventTimeline,
|
liveTimeline: { getEvents: (): MatrixEvent[] => [] } as unknown as EventTimeline,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
72
test/unit-tests/modules/AccountDataApi-test.ts
Normal file
72
test/unit-tests/modules/AccountDataApi-test.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 Element Creations Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ClientEvent } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
|
import { AccountDataApi } from "../../../src/modules/AccountDataApi";
|
||||||
|
import { mkEvent, stubClient } from "../../test-utils/test-utils";
|
||||||
|
|
||||||
|
describe("AccountDataApi", () => {
|
||||||
|
describe("AccountDataWatchable", () => {
|
||||||
|
it("should return content of account data event on get()", () => {
|
||||||
|
const cli = stubClient();
|
||||||
|
const api = new AccountDataApi();
|
||||||
|
// Mock cli to return a event
|
||||||
|
const content = { foo: "bar" };
|
||||||
|
const event = mkEvent({ content, type: "m.test", user: "@foobar:matrix.org", event: true });
|
||||||
|
cli.getAccountData = () => event;
|
||||||
|
expect(api.get("m.test").value).toStrictEqual(content);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should update value on event", () => {
|
||||||
|
const cli = stubClient();
|
||||||
|
const api = new AccountDataApi();
|
||||||
|
// Mock cli to return a event
|
||||||
|
const content = { foo: "bar" };
|
||||||
|
const event = mkEvent({ content, type: "m.test", user: "@foobar:matrix.org", event: true });
|
||||||
|
cli.getAccountData = () => event;
|
||||||
|
|
||||||
|
const watchable = api.get("m.test");
|
||||||
|
expect(watchable.value).toStrictEqual(content);
|
||||||
|
|
||||||
|
const fn = jest.fn();
|
||||||
|
watchable.watch(fn);
|
||||||
|
|
||||||
|
// Let's say that the account data event changed
|
||||||
|
const event2 = mkEvent({
|
||||||
|
content: { foo: "abc" },
|
||||||
|
type: "m.test",
|
||||||
|
user: "@foobar:matrix.org",
|
||||||
|
event: true,
|
||||||
|
});
|
||||||
|
cli.emit(ClientEvent.AccountData, event2);
|
||||||
|
// Watchable value should have been updated
|
||||||
|
expect(watchable.value).toStrictEqual({ foo: "abc" });
|
||||||
|
// Watched callbacks should be called
|
||||||
|
expect(fn).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// Make sure unwatch removed the event listener
|
||||||
|
cli.off = jest.fn();
|
||||||
|
watchable.unwatch(fn);
|
||||||
|
expect(cli.off).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set account data via js-sdk on set()", async () => {
|
||||||
|
const cli = stubClient();
|
||||||
|
const api = new AccountDataApi();
|
||||||
|
await api.set("m.test", { foo: "bar" });
|
||||||
|
expect(cli.setAccountData).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should delete account data via js-sdk on set()", async () => {
|
||||||
|
const cli = stubClient();
|
||||||
|
const api = new AccountDataApi();
|
||||||
|
await api.delete("m.test");
|
||||||
|
expect(cli.deleteAccountData).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
53
test/unit-tests/modules/BuiltinsApi-test.tsx
Normal file
53
test/unit-tests/modules/BuiltinsApi-test.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 Element Creations Ltd.
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { render } from "jest-matrix-react";
|
||||||
|
|
||||||
|
import { ElementWebBuiltinsApi } from "../../../src/modules/BuiltinsApi.tsx";
|
||||||
|
import { stubClient } from "../../test-utils/test-utils";
|
||||||
|
|
||||||
|
const Avatar: React.FC<{ room: { roomId: string }; size: string }> = ({ room, size }) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Avatar, {room.roomId}, {size}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("ElementWebBuiltinsApi", () => {
|
||||||
|
it("returns the RoomView component thats been set", () => {
|
||||||
|
const builtinsApi = new ElementWebBuiltinsApi();
|
||||||
|
const sentinel = {};
|
||||||
|
builtinsApi.setComponents({ roomView: sentinel, roomAvatar: Avatar } as any);
|
||||||
|
expect(builtinsApi.getRoomViewComponent()).toBe(sentinel);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns rendered RoomView component", () => {
|
||||||
|
const builtinsApi = new ElementWebBuiltinsApi();
|
||||||
|
const RoomView = () => <div>hello world</div>;
|
||||||
|
builtinsApi.setComponents({ roomView: RoomView, roomAvatar: Avatar } as any);
|
||||||
|
const { container } = render(<> {builtinsApi.renderRoomView("!foo:m.org")}</>);
|
||||||
|
expect(container).toHaveTextContent("hello world");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns rendered RoomAvatar component", () => {
|
||||||
|
stubClient();
|
||||||
|
const builtinsApi = new ElementWebBuiltinsApi();
|
||||||
|
builtinsApi.setComponents({ roomView: {}, roomAvatar: Avatar } as any);
|
||||||
|
const { container } = render(<> {builtinsApi.renderRoomAvatar("!foo:m.org", "50")}</>);
|
||||||
|
expect(container).toHaveTextContent("Avatar");
|
||||||
|
expect(container).toHaveTextContent("!foo:m.org");
|
||||||
|
expect(container).toHaveTextContent("50");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw error if called before components are set", () => {
|
||||||
|
stubClient();
|
||||||
|
const builtinsApi = new ElementWebBuiltinsApi();
|
||||||
|
expect(() => builtinsApi.renderRoomAvatar("!foo:m.org")).toThrow("No RoomAvatar component has been set");
|
||||||
|
expect(() => builtinsApi.renderRoomView("!foo:m.org")).toThrow("No RoomView component has been set");
|
||||||
|
});
|
||||||
|
});
|
||||||
20
test/unit-tests/modules/ClientApi-test.ts
Normal file
20
test/unit-tests/modules/ClientApi-test.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 Element Creations Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ClientApi } from "../../../src/modules/ClientApi";
|
||||||
|
import { Room } from "../../../src/modules/models/Room";
|
||||||
|
import { stubClient } from "../../test-utils/test-utils";
|
||||||
|
|
||||||
|
describe("ClientApi", () => {
|
||||||
|
it("should return module room from getRoom()", () => {
|
||||||
|
stubClient();
|
||||||
|
const client = new ClientApi();
|
||||||
|
const moduleRoom = client.getRoom("!foo:matrix.org");
|
||||||
|
expect(moduleRoom).toBeInstanceOf(Room);
|
||||||
|
expect(moduleRoom?.id).toStrictEqual("!foo:matrix.org");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -37,5 +37,25 @@ describe("NavigationApi", () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should dispatch correct action on openRoom", () => {
|
||||||
|
const spy = jest.spyOn(defaultDispatcher, "dispatch");
|
||||||
|
// Non alias
|
||||||
|
api.openRoom("!foo:m.org");
|
||||||
|
expect(spy).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
action: "view_room",
|
||||||
|
room_id: "!foo:m.org",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// Alias
|
||||||
|
api.openRoom("#bar:m.org");
|
||||||
|
expect(spy).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
action: "view_room",
|
||||||
|
room_alias: "#bar:m.org",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
50
test/unit-tests/modules/models/Room-test.ts
Normal file
50
test/unit-tests/modules/models/Room-test.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 Element Creations Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Room } from "../../../../src/modules/models/Room";
|
||||||
|
import { mkRoom, stubClient } from "../../../test-utils";
|
||||||
|
|
||||||
|
describe("Room", () => {
|
||||||
|
it("should return id from sdk room", () => {
|
||||||
|
const cli = stubClient();
|
||||||
|
const sdkRoom = mkRoom(cli, "!foo:m.org");
|
||||||
|
const room = new Room(sdkRoom);
|
||||||
|
expect(room.id).toStrictEqual("!foo:m.org");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return last timestamp from sdk room", () => {
|
||||||
|
const cli = stubClient();
|
||||||
|
const sdkRoom = mkRoom(cli, "!foo:m.org");
|
||||||
|
const room = new Room(sdkRoom);
|
||||||
|
expect(room.getLastActiveTimestamp()).toStrictEqual(sdkRoom.getLastActiveTimestamp());
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("watchableName", () => {
|
||||||
|
it("should return name from sdkRoom", () => {
|
||||||
|
const cli = stubClient();
|
||||||
|
const sdkRoom = mkRoom(cli, "!foo:m.org");
|
||||||
|
sdkRoom.name = "Foo Name";
|
||||||
|
const room = new Room(sdkRoom);
|
||||||
|
expect(room.name.value).toStrictEqual("Foo Name");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should add/remove event listener on sdk room", () => {
|
||||||
|
const cli = stubClient();
|
||||||
|
const sdkRoom = mkRoom(cli, "!foo:m.org");
|
||||||
|
sdkRoom.name = "Foo Name";
|
||||||
|
|
||||||
|
const room = new Room(sdkRoom);
|
||||||
|
const fn = jest.fn();
|
||||||
|
|
||||||
|
room.name.watch(fn);
|
||||||
|
expect(sdkRoom.on).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
room.name.unwatch(fn);
|
||||||
|
expect(sdkRoom.off).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
101
test/unit-tests/stores/MultiRoomViewStore-test.ts
Normal file
101
test/unit-tests/stores/MultiRoomViewStore-test.ts
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 New Vector Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { MultiRoomViewStore } from "../../../src/stores/MultiRoomViewStore";
|
||||||
|
import { RoomViewStore } from "../../../src/stores/RoomViewStore";
|
||||||
|
import { Action } from "../../../src/dispatcher/actions";
|
||||||
|
import type { MatrixDispatcher } from "../../../src/dispatcher/dispatcher";
|
||||||
|
import { TestSdkContext } from "../TestSdkContext";
|
||||||
|
|
||||||
|
jest.mock("../../../src/stores/RoomViewStore");
|
||||||
|
|
||||||
|
describe("MultiRoomViewStore", () => {
|
||||||
|
let multiRoomViewStore: MultiRoomViewStore;
|
||||||
|
let mockDispatcher: MatrixDispatcher;
|
||||||
|
let mockSdkContext: TestSdkContext;
|
||||||
|
let mockRoomViewStore: jest.Mocked<RoomViewStore>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
// Create mock dispatcher
|
||||||
|
mockDispatcher = {
|
||||||
|
dispatch: jest.fn(),
|
||||||
|
register: jest.fn(),
|
||||||
|
unregister: jest.fn(),
|
||||||
|
} as unknown as MatrixDispatcher;
|
||||||
|
|
||||||
|
// Create mock SDK context
|
||||||
|
mockSdkContext = new TestSdkContext();
|
||||||
|
|
||||||
|
// Create mock RoomViewStore instance
|
||||||
|
mockRoomViewStore = {
|
||||||
|
viewRoom: jest.fn(),
|
||||||
|
dispose: jest.fn(),
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
(RoomViewStore as jest.MockedClass<typeof RoomViewStore>).mockImplementation(() => mockRoomViewStore as any);
|
||||||
|
|
||||||
|
// Create the MultiRoomViewStore instance
|
||||||
|
multiRoomViewStore = new MultiRoomViewStore(mockDispatcher, mockSdkContext);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getRoomViewStoreForRoom", () => {
|
||||||
|
it("should create a new RoomViewStore for a room that doesn't exist in cache", () => {
|
||||||
|
const roomId = "!room1:example.com";
|
||||||
|
|
||||||
|
const result = multiRoomViewStore.getRoomViewStoreForRoom(roomId);
|
||||||
|
|
||||||
|
expect(RoomViewStore).toHaveBeenCalledWith(mockDispatcher, mockSdkContext, roomId);
|
||||||
|
expect(mockRoomViewStore.viewRoom).toHaveBeenCalledWith({
|
||||||
|
action: Action.ViewRoom,
|
||||||
|
room_id: roomId,
|
||||||
|
metricsTrigger: undefined,
|
||||||
|
});
|
||||||
|
expect(result).toBe(mockRoomViewStore);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return existing RoomViewStore for a room that exists in cache", () => {
|
||||||
|
const roomId = "!room1:example.com";
|
||||||
|
|
||||||
|
// First call creates the store
|
||||||
|
const firstResult = multiRoomViewStore.getRoomViewStoreForRoom(roomId);
|
||||||
|
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
// Should return the same store
|
||||||
|
const secondResult = multiRoomViewStore.getRoomViewStoreForRoom(roomId);
|
||||||
|
|
||||||
|
expect(RoomViewStore).not.toHaveBeenCalled();
|
||||||
|
expect(mockRoomViewStore.viewRoom).toHaveBeenCalledWith({
|
||||||
|
action: Action.ViewRoom,
|
||||||
|
room_id: roomId,
|
||||||
|
metricsTrigger: undefined,
|
||||||
|
});
|
||||||
|
expect(secondResult).toBe(firstResult);
|
||||||
|
expect(secondResult).toBe(mockRoomViewStore);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("removeRoomViewStore", () => {
|
||||||
|
it("should remove an existing RoomViewStore from cache", () => {
|
||||||
|
const roomId = "!room1:example.com";
|
||||||
|
|
||||||
|
multiRoomViewStore.getRoomViewStoreForRoom(roomId);
|
||||||
|
multiRoomViewStore.removeRoomViewStore(roomId);
|
||||||
|
|
||||||
|
// New store should be created now
|
||||||
|
jest.clearAllMocks();
|
||||||
|
(RoomViewStore as jest.MockedClass<typeof RoomViewStore>).mockImplementation(
|
||||||
|
() => mockRoomViewStore as any,
|
||||||
|
);
|
||||||
|
|
||||||
|
multiRoomViewStore.getRoomViewStoreForRoom(roomId);
|
||||||
|
expect(RoomViewStore).toHaveBeenCalledWith(mockDispatcher, mockSdkContext, roomId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
11
yarn.lock
11
yarn.lock
@@ -1602,10 +1602,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@element-hq/element-call-embedded/-/element-call-embedded-0.16.1.tgz#28bdbde426051cc2a3228a36e7196e0a254569d3"
|
resolved "https://registry.yarnpkg.com/@element-hq/element-call-embedded/-/element-call-embedded-0.16.1.tgz#28bdbde426051cc2a3228a36e7196e0a254569d3"
|
||||||
integrity sha512-g3v/QFuNy8YVRGrKC5SxjIYvgBh6biOHgejhJT2Jk/yjOOUEuP0y2PBaADm+suPD9BB/Vk1jPxFk2uEIpEzhpA==
|
integrity sha512-g3v/QFuNy8YVRGrKC5SxjIYvgBh6biOHgejhJT2Jk/yjOOUEuP0y2PBaADm+suPD9BB/Vk1jPxFk2uEIpEzhpA==
|
||||||
|
|
||||||
"@element-hq/element-web-module-api@1.4.1":
|
"@element-hq/element-web-module-api@1.5.0":
|
||||||
version "1.4.1"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/@element-hq/element-web-module-api/-/element-web-module-api-1.4.1.tgz#a46526d58985190f9989bf1686ea872687d3c6e1"
|
resolved "https://registry.yarnpkg.com/@element-hq/element-web-module-api/-/element-web-module-api-1.5.0.tgz#077a528917f4eb558059a2a5286b9bb6a2fb1690"
|
||||||
integrity sha512-A8yaQtX7QoKThzzZVU+VYOFhpiNyppEMuIQijK48RvhVp1nwmy0cTD6u/6Yn64saNwJjtna+Oy+Qzo/TfwwhxQ==
|
integrity sha512-WI/iMADRouXp9WhQy5jov6Z4eKKlHEPh20DKoCsKZ9dWaYcW/MiBhzi09PZxay+o0RLZXA6aDPxpxaIX3lZXag==
|
||||||
|
|
||||||
"@element-hq/element-web-playwright-common@^2.0.0":
|
"@element-hq/element-web-playwright-common@^2.0.0":
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
@@ -4085,13 +4085,14 @@
|
|||||||
|
|
||||||
"@vector-im/matrix-wysiwyg-wasm@link:../../../.cache/yarn/v6/npm-@vector-im-matrix-wysiwyg-2.40.0-53c9ca5ea907d91e4515da64f20a82e5586b882c-integrity/node_modules/bindings/wysiwyg-wasm":
|
"@vector-im/matrix-wysiwyg-wasm@link:../../../.cache/yarn/v6/npm-@vector-im-matrix-wysiwyg-2.40.0-53c9ca5ea907d91e4515da64f20a82e5586b882c-integrity/node_modules/bindings/wysiwyg-wasm":
|
||||||
version "0.0.0"
|
version "0.0.0"
|
||||||
|
uid ""
|
||||||
|
|
||||||
"@vector-im/matrix-wysiwyg@2.40.0":
|
"@vector-im/matrix-wysiwyg@2.40.0":
|
||||||
version "2.40.0"
|
version "2.40.0"
|
||||||
resolved "https://registry.yarnpkg.com/@vector-im/matrix-wysiwyg/-/matrix-wysiwyg-2.40.0.tgz#53c9ca5ea907d91e4515da64f20a82e5586b882c"
|
resolved "https://registry.yarnpkg.com/@vector-im/matrix-wysiwyg/-/matrix-wysiwyg-2.40.0.tgz#53c9ca5ea907d91e4515da64f20a82e5586b882c"
|
||||||
integrity sha512-8LRFLs5PEKYs4lOL7aJ4lL/hGCrvEvOYkCR3JggXYXDVMtX4LmfdlKYucSAe98pCmqAAbLRvlRcR1bTOYvM8ug==
|
integrity sha512-8LRFLs5PEKYs4lOL7aJ4lL/hGCrvEvOYkCR3JggXYXDVMtX4LmfdlKYucSAe98pCmqAAbLRvlRcR1bTOYvM8ug==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vector-im/matrix-wysiwyg-wasm" "link:../../../.cache/yarn/v6/npm-@vector-im-matrix-wysiwyg-2.40.0-53c9ca5ea907d91e4515da64f20a82e5586b882c-integrity/node_modules/bindings/wysiwyg-wasm"
|
"@vector-im/matrix-wysiwyg-wasm" "link:../../Library/Caches/Yarn/v6/npm-@vector-im-matrix-wysiwyg-2.40.0-53c9ca5ea907d91e4515da64f20a82e5586b882c-integrity/node_modules/bindings/wysiwyg-wasm"
|
||||||
|
|
||||||
"@vitest/expect@3.2.4":
|
"@vitest/expect@3.2.4":
|
||||||
version "3.2.4"
|
version "3.2.4"
|
||||||
|
|||||||
Reference in New Issue
Block a user