mirror of
https://github.com/element-hq/element-web.git
synced 2025-12-05 01:10:40 +00:00
Compare commits
18 Commits
rav/reload
...
t3chguy/mo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01ffd72b9d | ||
|
|
0eee4e3a18 | ||
|
|
98ed46b0f3 | ||
|
|
78690f032d | ||
|
|
607dbb03df | ||
|
|
07e6f0af35 | ||
|
|
be318f9cb8 | ||
|
|
5ff42e8548 | ||
|
|
6599601000 | ||
|
|
6fb9098c30 | ||
|
|
9e50e59168 | ||
|
|
008b004976 | ||
|
|
68a2748813 | ||
|
|
61e266e8e5 | ||
|
|
a7c7c27ae8 | ||
|
|
1414b8ff54 | ||
|
|
c6a5d288ed | ||
|
|
8c2a035302 |
@@ -40,8 +40,6 @@ const config: Config = {
|
||||
"^!!raw-loader!.*": "jest-raw-loader",
|
||||
"recorderWorkletFactory": "<rootDir>/__mocks__/empty.js",
|
||||
"^fetch-mock$": "<rootDir>/node_modules/fetch-mock",
|
||||
// Requires ESM which is incompatible with our current Jest setup
|
||||
"^@element-hq/element-web-module-api$": "<rootDir>/__mocks__/empty.js",
|
||||
},
|
||||
transformIgnorePatterns: ["/node_modules/(?!(mime|matrix-js-sdk)).+$"],
|
||||
collectCoverageFrom: [
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@element-hq/element-web-module-api": "1.3.0",
|
||||
"@element-hq/element-web-module-api": "1.4.1",
|
||||
"@fontsource/inconsolata": "^5",
|
||||
"@fontsource/inter": "^5",
|
||||
"@formatjs/intl-segmenter": "^11.5.7",
|
||||
|
||||
@@ -31,6 +31,7 @@ import { UIFeature } from "../../../settings/UIFeature";
|
||||
import { ModuleRunner } from "../../../modules/ModuleRunner";
|
||||
import { Icon as AskToJoinIcon } from "../../../../res/img/element-icons/ask-to-join.svg";
|
||||
import Field from "../elements/Field";
|
||||
import ModuleApi from "../../../modules/Api.ts";
|
||||
|
||||
const MemberEventHtmlReasonField = "io.element.html_reason";
|
||||
|
||||
@@ -116,7 +117,7 @@ interface IState {
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
export default class RoomPreviewBar extends React.Component<IProps, IState> {
|
||||
class RoomPreviewBar extends React.Component<IProps, IState> {
|
||||
public static defaultProps = {
|
||||
onJoinClick() {},
|
||||
};
|
||||
@@ -747,3 +748,21 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const WrappedRoomPreviewBar = (props: IProps): JSX.Element => {
|
||||
const moduleRenderer = ModuleApi.customComponents.roomPreviewBarRenderer;
|
||||
if (moduleRenderer) {
|
||||
return moduleRenderer(
|
||||
{
|
||||
...props,
|
||||
roomId: props.room?.roomId ?? props.roomId,
|
||||
roomAlias: props.room?.getCanonicalAlias() ?? props.roomAlias,
|
||||
},
|
||||
(props) => <RoomPreviewBar {...props} />,
|
||||
);
|
||||
}
|
||||
|
||||
return <RoomPreviewBar {...props} />;
|
||||
};
|
||||
|
||||
export default WrappedRoomPreviewBar;
|
||||
|
||||
@@ -21,7 +21,11 @@ import { WidgetPermissionCustomisations } from "../customisations/WidgetPermissi
|
||||
import { WidgetVariableCustomisations } from "../customisations/WidgetVariables.ts";
|
||||
import { ConfigApi } from "./ConfigApi.ts";
|
||||
import { I18nApi } from "./I18nApi.ts";
|
||||
import { CustomComponentsApi } from "./customComponentApi.ts";
|
||||
import { CustomComponentsApi } from "./customComponentApi";
|
||||
import { WatchableProfile } from "./Profile.ts";
|
||||
import { NavigationApi } from "./Navigation.ts";
|
||||
import { openDialog } from "./Dialog.tsx";
|
||||
import { overwriteAccountAuth } from "./Auth.ts";
|
||||
|
||||
const legacyCustomisationsFactory = <T extends object>(baseCustomisations: T) => {
|
||||
let used = false;
|
||||
@@ -57,6 +61,11 @@ class ModuleApi implements Api {
|
||||
legacyCustomisationsFactory(WidgetVariableCustomisations);
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
|
||||
public readonly navigation = new NavigationApi();
|
||||
public readonly openDialog = openDialog;
|
||||
public readonly overwriteAccountAuth = overwriteAccountAuth;
|
||||
public readonly profile = new WatchableProfile();
|
||||
|
||||
public readonly config = new ConfigApi();
|
||||
public readonly i18n = new I18nApi();
|
||||
public readonly customComponents = new CustomComponentsApi();
|
||||
|
||||
43
src/modules/Auth.ts
Normal file
43
src/modules/Auth.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
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 { type AccountAuthInfo } from "@element-hq/element-web-module-api";
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import type { OverwriteLoginPayload } from "../dispatcher/payloads/OverwriteLoginPayload.ts";
|
||||
import { Action } from "../dispatcher/actions.ts";
|
||||
import defaultDispatcher from "../dispatcher/dispatcher.ts";
|
||||
import type { ActionPayload } from "../dispatcher/payloads.ts";
|
||||
|
||||
export async function overwriteAccountAuth(accountInfo: AccountAuthInfo): Promise<void> {
|
||||
const { promise, resolve } = Promise.withResolvers<void>();
|
||||
|
||||
const onAction = (payload: ActionPayload): void => {
|
||||
if (payload.action === Action.OnLoggedIn) {
|
||||
// We want to wait for the new login to complete before returning.
|
||||
// See `Action.OnLoggedIn` in dispatcher.
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
const dispatcherRef = defaultDispatcher.register(onAction);
|
||||
|
||||
defaultDispatcher.dispatch<OverwriteLoginPayload>(
|
||||
{
|
||||
action: Action.OverwriteLogin,
|
||||
credentials: {
|
||||
...accountInfo,
|
||||
guest: false,
|
||||
},
|
||||
},
|
||||
true,
|
||||
); // require to be sync to match inherited interface behaviour
|
||||
|
||||
// wait for login to complete
|
||||
await promise;
|
||||
defaultDispatcher.unregister(dispatcherRef);
|
||||
await sleep(0); // wait for the next tick to ensure the login is fully processed
|
||||
}
|
||||
52
src/modules/Dialog.tsx
Normal file
52
src/modules/Dialog.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
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 React, { type ComponentType, type JSX, useCallback } from "react";
|
||||
import { type DialogProps, type DialogOptions, type DialogHandle } from "@element-hq/element-web-module-api";
|
||||
|
||||
import Modal from "../Modal";
|
||||
import BaseDialog from "../components/views/dialogs/BaseDialog.tsx";
|
||||
|
||||
const OuterDialog = <M, P extends object>({
|
||||
title,
|
||||
Dialog,
|
||||
props,
|
||||
onFinished,
|
||||
}: {
|
||||
title: string;
|
||||
Dialog: ComponentType<DialogProps<M> & P>;
|
||||
props: P;
|
||||
onFinished(ok: boolean, model: M | null): void;
|
||||
}): JSX.Element => {
|
||||
const close = useCallback(() => onFinished(false, null), [onFinished]);
|
||||
const submit = useCallback((model: M) => onFinished(true, model), [onFinished]);
|
||||
return (
|
||||
<BaseDialog onFinished={close} title={title}>
|
||||
<Dialog {...props} onSubmit={submit} onCancel={close} />
|
||||
</BaseDialog>
|
||||
);
|
||||
};
|
||||
|
||||
export function openDialog<M, P extends object>(
|
||||
initialOptions: DialogOptions,
|
||||
Dialog: ComponentType<P & DialogProps<M>>,
|
||||
props: P,
|
||||
): DialogHandle<M> {
|
||||
const { close, finished } = Modal.createDialog(OuterDialog<M, P>, {
|
||||
title: initialOptions.title,
|
||||
Dialog,
|
||||
props,
|
||||
});
|
||||
|
||||
return {
|
||||
finished: finished.then(([ok, model]) => ({
|
||||
ok: ok ?? false,
|
||||
model: model ?? null,
|
||||
})),
|
||||
close: () => close(false, null),
|
||||
};
|
||||
}
|
||||
43
src/modules/Navigation.ts
Normal file
43
src/modules/Navigation.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
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 { type NavigationApi as INavigationApi } from "@element-hq/element-web-module-api";
|
||||
|
||||
import { navigateToPermalink } from "../utils/permalinks/navigator.ts";
|
||||
import { parsePermalink } from "../utils/permalinks/Permalinks.ts";
|
||||
import { getCachedRoomIDForAlias } from "../RoomAliasCache.ts";
|
||||
import { MatrixClientPeg } from "../MatrixClientPeg.ts";
|
||||
import dispatcher from "../dispatcher/dispatcher.ts";
|
||||
import { Action } from "../dispatcher/actions.ts";
|
||||
import SettingsStore from "../settings/SettingsStore.ts";
|
||||
|
||||
export class NavigationApi implements INavigationApi {
|
||||
public async toMatrixToLink(link: string, join = false): Promise<void> {
|
||||
navigateToPermalink(link);
|
||||
|
||||
const parts = parsePermalink(link);
|
||||
if (parts?.roomIdOrAlias && join) {
|
||||
let roomId: string | undefined = parts.roomIdOrAlias;
|
||||
if (roomId.startsWith("#")) {
|
||||
roomId = getCachedRoomIDForAlias(parts.roomIdOrAlias);
|
||||
if (!roomId) {
|
||||
// alias resolution failed
|
||||
const result = await MatrixClientPeg.safeGet().getRoomIdForAlias(parts.roomIdOrAlias);
|
||||
roomId = result.room_id;
|
||||
}
|
||||
}
|
||||
|
||||
if (roomId) {
|
||||
dispatcher.dispatch({
|
||||
action: Action.JoinRoom,
|
||||
canAskToJoin: SettingsStore.getValue("feature_ask_to_join"),
|
||||
roomId,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/modules/Profile.ts
Normal file
32
src/modules/Profile.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
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 { type Profile, Watchable } from "@element-hq/element-web-module-api";
|
||||
|
||||
import { OwnProfileStore } from "../stores/OwnProfileStore.ts";
|
||||
import { UPDATE_EVENT } from "../stores/AsyncStore.ts";
|
||||
|
||||
export class WatchableProfile extends Watchable<Profile> {
|
||||
public constructor() {
|
||||
super({});
|
||||
this.value = this.profile;
|
||||
|
||||
OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileChange);
|
||||
}
|
||||
|
||||
private get profile(): Profile {
|
||||
return {
|
||||
isGuest: OwnProfileStore.instance.matrixClient?.isGuest() ?? false,
|
||||
userId: OwnProfileStore.instance.matrixClient?.getUserId() ?? undefined,
|
||||
displayName: OwnProfileStore.instance.displayName ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
private readonly onProfileChange = (): void => {
|
||||
this.value = this.profile;
|
||||
};
|
||||
}
|
||||
@@ -12,9 +12,10 @@ import type {
|
||||
CustomComponentsApi as ICustomComponentsApi,
|
||||
CustomMessageRenderFunction,
|
||||
CustomMessageComponentProps as ModuleCustomMessageComponentProps,
|
||||
OriginalComponentProps,
|
||||
OriginalMessageComponentProps,
|
||||
CustomMessageRenderHints as ModuleCustomCustomMessageRenderHints,
|
||||
MatrixEvent as ModuleMatrixEvent,
|
||||
CustomRoomPreviewBarRenderFunction,
|
||||
} from "@element-hq/element-web-module-api";
|
||||
import type React from "react";
|
||||
|
||||
@@ -72,6 +73,7 @@ export class CustomComponentsApi implements ICustomComponentsApi {
|
||||
): void {
|
||||
this.registeredMessageRenderers.push({ eventTypeOrFilter: eventTypeOrFilter, renderer, hints });
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the correct renderer based on the event information.
|
||||
* @param mxEvent The message event being rendered.
|
||||
@@ -100,7 +102,7 @@ export class CustomComponentsApi implements ICustomComponentsApi {
|
||||
*/
|
||||
public renderMessage(
|
||||
props: CustomMessageComponentProps,
|
||||
originalComponent?: (props?: OriginalComponentProps) => React.JSX.Element,
|
||||
originalComponent?: (props?: OriginalMessageComponentProps) => React.JSX.Element,
|
||||
): React.JSX.Element | null {
|
||||
const moduleEv = CustomComponentsApi.getModuleMatrixEvent(props.mxEvent);
|
||||
const renderer = moduleEv && this.selectRenderer(moduleEv);
|
||||
@@ -134,4 +136,21 @@ export class CustomComponentsApi implements ICustomComponentsApi {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private _roomPreviewBarRenderer?: CustomRoomPreviewBarRenderFunction;
|
||||
|
||||
/**
|
||||
* Get the custom room preview bar renderer, if any has been registered.
|
||||
*/
|
||||
public get roomPreviewBarRenderer(): CustomRoomPreviewBarRenderFunction | undefined {
|
||||
return this._roomPreviewBarRenderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a custom room preview bar renderer.
|
||||
* @param renderer - the function that will render the custom room preview bar.
|
||||
*/
|
||||
public registerRoomPreviewBar(renderer: CustomRoomPreviewBarRenderFunction): void {
|
||||
this._roomPreviewBarRenderer = renderer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -510,8 +510,8 @@ export class RoomViewStore extends EventEmitter {
|
||||
});
|
||||
|
||||
// take a copy of roomAlias & roomId as they may change by the time the join is complete
|
||||
const { roomAlias, roomId = payload.roomId } = this.state;
|
||||
const address = roomAlias || roomId!;
|
||||
const { roomAlias, roomId } = this.state;
|
||||
const address = payload.roomId || roomAlias || roomId!;
|
||||
|
||||
const joinOpts: IJoinRoomOpts = {
|
||||
viaServers: this.state.viaServers || [],
|
||||
@@ -520,7 +520,6 @@ export class RoomViewStore extends EventEmitter {
|
||||
if (SettingsStore.getValue("feature_share_history_on_invite")) {
|
||||
joinOpts.acceptSharedHistory = true;
|
||||
}
|
||||
|
||||
try {
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
await retry<Room, MatrixError>(
|
||||
|
||||
@@ -53,6 +53,7 @@ import { ElementWidgetActions } from "../../../../src/stores/widgets/ElementWidg
|
||||
jest.mock("../../../../src/stores/OwnProfileStore", () => ({
|
||||
OwnProfileStore: {
|
||||
instance: {
|
||||
on: jest.fn(),
|
||||
isProfileInfoFetched: true,
|
||||
removeListener: jest.fn(),
|
||||
getHttpAvatarUrl: jest.fn().mockReturnValue("http://avatar_url"),
|
||||
|
||||
@@ -43,6 +43,7 @@ import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permal
|
||||
jest.mock("../../../../../src/stores/OwnProfileStore", () => ({
|
||||
OwnProfileStore: {
|
||||
instance: {
|
||||
on: jest.fn(),
|
||||
isProfileInfoFetched: true,
|
||||
removeListener: jest.fn(),
|
||||
getHttpAvatarUrl: jest.fn().mockReturnValue("http://avatar_url"),
|
||||
|
||||
@@ -48,6 +48,7 @@ jest.mock("../../../../../src/settings/SettingsStore", () => ({
|
||||
jest.mock("../../../../../src/stores/OwnProfileStore", () => ({
|
||||
OwnProfileStore: {
|
||||
instance: {
|
||||
on: jest.fn(),
|
||||
displayName: "Ernie",
|
||||
getHttpAvatarUrl: jest.fn().mockReturnValue("image.com/img"),
|
||||
},
|
||||
|
||||
@@ -16,6 +16,7 @@ import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
import RoomPreviewBar from "../../../../../src/components/views/rooms/RoomPreviewBar";
|
||||
import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
|
||||
import ModuleApi from "../../../../../src/modules/Api.ts";
|
||||
|
||||
jest.mock("../../../../../src/IdentityAuthClient", () => {
|
||||
return jest.fn().mockImplementation(() => {
|
||||
@@ -497,4 +498,12 @@ describe("<RoomPreviewBar />", () => {
|
||||
expect(onCancelAskToJoin).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render Module roomPreviewBarRenderer if specified", () => {
|
||||
jest.spyOn(ModuleApi.customComponents, "roomPreviewBarRenderer", "get").mockReturnValue(() => (
|
||||
<>Test component</>
|
||||
));
|
||||
const { getByText } = render(<RoomPreviewBar />);
|
||||
expect(getByText("Test component")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
30
test/unit-tests/modules/Auth-test.ts
Normal file
30
test/unit-tests/modules/Auth-test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
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 defaultDispatcher from "../../../src/dispatcher/dispatcher.ts";
|
||||
import { overwriteAccountAuth } from "../../../src/modules/Auth.ts";
|
||||
|
||||
describe("overwriteAccountAuth", () => {
|
||||
it("should call overwrite login with accountInfo", () => {
|
||||
const spy = jest.spyOn(defaultDispatcher, "dispatch");
|
||||
|
||||
const accountInfo = {
|
||||
userId: "@user:server.com",
|
||||
deviceId: "DEVICEID",
|
||||
accessToken: "TOKEN",
|
||||
homeserverUrl: "https://server.com",
|
||||
};
|
||||
overwriteAccountAuth(accountInfo);
|
||||
expect(spy).toHaveBeenCalledWith(
|
||||
{
|
||||
action: "overwrite_login",
|
||||
credentials: expect.objectContaining(accountInfo),
|
||||
},
|
||||
true,
|
||||
);
|
||||
});
|
||||
});
|
||||
23
test/unit-tests/modules/Dialog-test.tsx
Normal file
23
test/unit-tests/modules/Dialog-test.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
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 React from "react";
|
||||
import { screen } from "jest-matrix-react";
|
||||
|
||||
import { openDialog } from "../../../src/modules/Dialog.tsx";
|
||||
|
||||
describe("openDialog", () => {
|
||||
it("should open a dialog with the expected title", async () => {
|
||||
const Dialog = () => <>Dialog Content</>;
|
||||
|
||||
const title = "Test Dialog";
|
||||
openDialog({ title }, Dialog, {});
|
||||
|
||||
await expect(screen.findByText("Test Dialog")).resolves.toBeInTheDocument();
|
||||
expect(screen.getByText("Dialog Content")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
43
test/unit-tests/modules/Navigation-test.ts
Normal file
43
test/unit-tests/modules/Navigation-test.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
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 { mocked } from "jest-mock";
|
||||
|
||||
import * as navigator from "../../../src/utils/permalinks/navigator";
|
||||
import { NavigationApi } from "../../../src/modules/Navigation.ts";
|
||||
import { stubClient } from "../../test-utils";
|
||||
import defaultDispatcher from "../../../src/dispatcher/dispatcher.ts";
|
||||
|
||||
describe("NavigationApi", () => {
|
||||
const api = new NavigationApi();
|
||||
|
||||
describe("toMatrixToLink", () => {
|
||||
it("should call navigateToPermalink with the correct parameters", async () => {
|
||||
const link = "https://matrix.to/#/!roomId:server.com";
|
||||
const spy = jest.spyOn(navigator, "navigateToPermalink");
|
||||
|
||||
await api.toMatrixToLink(link);
|
||||
expect(spy).toHaveBeenCalledWith(link);
|
||||
});
|
||||
|
||||
it("should resolve the room alias to a room id when join=true", async () => {
|
||||
const cli = stubClient();
|
||||
mocked(cli.getRoomIdForAlias).mockResolvedValue({ room_id: "!roomId:server.com", servers: [] });
|
||||
|
||||
const link = "https://matrix.to/#/#alias:server.com";
|
||||
const spy = jest.spyOn(defaultDispatcher, "dispatch");
|
||||
|
||||
await api.toMatrixToLink(link, true);
|
||||
expect(spy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "join_room",
|
||||
roomId: "!roomId:server.com",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1682,10 +1682,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@element-hq/element-call-embedded/-/element-call-embedded-0.14.1.tgz#358c537e147ff3d48028cfb65d414cfe89ac1371"
|
||||
integrity sha512-1ODnohNvg7bgR8tg+rIF81MYGChNXVD96lBWkCI96ygjGg7U+HqqA8sY0YsRN5oJ9aLDQPicSr09XwLEXSPmjQ==
|
||||
|
||||
"@element-hq/element-web-module-api@1.3.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@element-hq/element-web-module-api/-/element-web-module-api-1.3.0.tgz#6067fa654174d1dd0953447bb036e38f9dfa51a5"
|
||||
integrity sha512-rEV0xnT/tNYPIdqHWWiz2KZo96UeZR0YChfoVLiPT46ZlEYyxqkjxT5bOm1eL2/CiYRe8t1yka3UDkIjq481/g==
|
||||
"@element-hq/element-web-module-api@1.4.1":
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@element-hq/element-web-module-api/-/element-web-module-api-1.4.1.tgz#a46526d58985190f9989bf1686ea872687d3c6e1"
|
||||
integrity sha512-A8yaQtX7QoKThzzZVU+VYOFhpiNyppEMuIQijK48RvhVp1nwmy0cTD6u/6Yn64saNwJjtna+Oy+Qzo/TfwwhxQ==
|
||||
|
||||
"@element-hq/element-web-playwright-common@^1.4.4":
|
||||
version "1.4.4"
|
||||
|
||||
Reference in New Issue
Block a user