mirror of
https://github.com/vector-im/element-web.git
synced 2025-11-08 04:01:07 +01:00
Change module API to be an instance getter (#31025)
* Change module API to be an instance getter Helps with circular dependencies by not instantating the module API on the initial evaluation of the files. * Add basic test * add another test
This commit is contained in:
parent
6cfe197a38
commit
146e4772ac
@ -31,7 +31,7 @@ import { UIFeature } from "../../../settings/UIFeature";
|
|||||||
import { ModuleRunner } from "../../../modules/ModuleRunner";
|
import { ModuleRunner } from "../../../modules/ModuleRunner";
|
||||||
import { Icon as AskToJoinIcon } from "../../../../res/img/element-icons/ask-to-join.svg";
|
import { Icon as AskToJoinIcon } from "../../../../res/img/element-icons/ask-to-join.svg";
|
||||||
import Field from "../elements/Field";
|
import Field from "../elements/Field";
|
||||||
import ModuleApi from "../../../modules/Api.ts";
|
import { ModuleApi } from "../../../modules/Api.ts";
|
||||||
|
|
||||||
const MemberEventHtmlReasonField = "io.element.html_reason";
|
const MemberEventHtmlReasonField = "io.element.html_reason";
|
||||||
|
|
||||||
@ -750,7 +750,7 @@ class RoomPreviewBar extends React.Component<IProps, IState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const WrappedRoomPreviewBar = (props: IProps): JSX.Element => {
|
const WrappedRoomPreviewBar = (props: IProps): JSX.Element => {
|
||||||
const moduleRenderer = ModuleApi.customComponents.roomPreviewBarRenderer;
|
const moduleRenderer = ModuleApi.instance.customComponents.roomPreviewBarRenderer;
|
||||||
if (moduleRenderer) {
|
if (moduleRenderer) {
|
||||||
return moduleRenderer(
|
return moduleRenderer(
|
||||||
{
|
{
|
||||||
|
|||||||
@ -41,7 +41,7 @@ import HiddenBody from "../components/views/messages/HiddenBody";
|
|||||||
import ViewSourceEvent from "../components/views/messages/ViewSourceEvent";
|
import ViewSourceEvent from "../components/views/messages/ViewSourceEvent";
|
||||||
import { shouldDisplayAsBeaconTile } from "../utils/beacon/timeline";
|
import { shouldDisplayAsBeaconTile } from "../utils/beacon/timeline";
|
||||||
import { type IBodyProps } from "../components/views/messages/IBodyProps";
|
import { type IBodyProps } from "../components/views/messages/IBodyProps";
|
||||||
import ModuleApi from "../modules/Api";
|
import { ModuleApi } from "../modules/Api";
|
||||||
import { TextualEventViewModel } from "../viewmodels/event-tiles/TextualEventViewModel";
|
import { TextualEventViewModel } from "../viewmodels/event-tiles/TextualEventViewModel";
|
||||||
import { TextualEventView } from "../../packages/shared-components/src/event-tiles/TextualEventView";
|
import { TextualEventView } from "../../packages/shared-components/src/event-tiles/TextualEventView";
|
||||||
import { ElementCallEventType } from "../call-types";
|
import { ElementCallEventType } from "../call-types";
|
||||||
@ -266,7 +266,7 @@ export function renderTile(
|
|||||||
// If we don't have a factory for this event, attempt
|
// If we don't have a factory for this event, attempt
|
||||||
// to find a custom component that can render it.
|
// to find a custom component that can render it.
|
||||||
// Will return null if no custom component can render it.
|
// Will return null if no custom component can render it.
|
||||||
return ModuleApi.customComponents.renderMessage({
|
return ModuleApi.instance.customComponents.renderMessage({
|
||||||
mxEvent: props.mxEvent,
|
mxEvent: props.mxEvent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -297,7 +297,7 @@ export function renderTile(
|
|||||||
case TimelineRenderingType.File:
|
case TimelineRenderingType.File:
|
||||||
case TimelineRenderingType.Notification:
|
case TimelineRenderingType.Notification:
|
||||||
case TimelineRenderingType.Thread:
|
case TimelineRenderingType.Thread:
|
||||||
return ModuleApi.customComponents.renderMessage(
|
return ModuleApi.instance.customComponents.renderMessage(
|
||||||
{
|
{
|
||||||
mxEvent: props.mxEvent,
|
mxEvent: props.mxEvent,
|
||||||
},
|
},
|
||||||
@ -318,7 +318,7 @@ export function renderTile(
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return ModuleApi.customComponents.renderMessage(
|
return ModuleApi.instance.customComponents.renderMessage(
|
||||||
{
|
{
|
||||||
mxEvent: props.mxEvent,
|
mxEvent: props.mxEvent,
|
||||||
},
|
},
|
||||||
@ -363,7 +363,7 @@ export function renderReplyTile(
|
|||||||
// If we don't have a factory for this event, attempt
|
// If we don't have a factory for this event, attempt
|
||||||
// to find a custom component that can render it.
|
// to find a custom component that can render it.
|
||||||
// Will return null if no custom component can render it.
|
// Will return null if no custom component can render it.
|
||||||
return ModuleApi.customComponents.renderMessage({
|
return ModuleApi.instance.customComponents.renderMessage({
|
||||||
mxEvent: props.mxEvent,
|
mxEvent: props.mxEvent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -384,7 +384,7 @@ export function renderReplyTile(
|
|||||||
permalinkCreator,
|
permalinkCreator,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return ModuleApi.customComponents.renderMessage(
|
return ModuleApi.instance.customComponents.renderMessage(
|
||||||
{
|
{
|
||||||
mxEvent: props.mxEvent,
|
mxEvent: props.mxEvent,
|
||||||
},
|
},
|
||||||
@ -429,7 +429,7 @@ export function haveRendererForEvent(
|
|||||||
|
|
||||||
// Check to see if we have any hints for this message, which indicates
|
// Check to see if we have any hints for this message, which indicates
|
||||||
// there is a custom renderer for the event.
|
// there is a custom renderer for the event.
|
||||||
if (ModuleApi.customComponents.getHintsForMessage(mxEvent)) {
|
if (ModuleApi.instance.customComponents.getHintsForMessage(mxEvent)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { _t } from "../languageHandler";
|
|||||||
import Modal from "../Modal";
|
import Modal from "../Modal";
|
||||||
import { FileDownloader } from "../utils/FileDownloader";
|
import { FileDownloader } from "../utils/FileDownloader";
|
||||||
import { MediaEventHelper } from "../utils/MediaEventHelper";
|
import { MediaEventHelper } from "../utils/MediaEventHelper";
|
||||||
import ModuleApi from "../modules/Api";
|
import { ModuleApi } from "../modules/Api";
|
||||||
|
|
||||||
export interface UseDownloadMediaReturn {
|
export interface UseDownloadMediaReturn {
|
||||||
download: () => Promise<void>;
|
download: () => Promise<void>;
|
||||||
@ -34,7 +34,7 @@ export function useDownloadMedia(url: string, fileName?: string, mxEvent?: Matri
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!mxEvent) return;
|
if (!mxEvent) return;
|
||||||
|
|
||||||
const hints = ModuleApi.customComponents.getHintsForMessage(mxEvent);
|
const hints = ModuleApi.instance.customComponents.getHintsForMessage(mxEvent);
|
||||||
if (hints?.allowDownloadingMedia) {
|
if (hints?.allowDownloadingMedia) {
|
||||||
setCanDownload(false);
|
setCanDownload(false);
|
||||||
hints
|
hints
|
||||||
|
|||||||
@ -39,7 +39,17 @@ const legacyCustomisationsFactory = <T extends object>(baseCustomisations: T) =>
|
|||||||
/**
|
/**
|
||||||
* Implementation of the @element-hq/element-web-module-api runtime module API.
|
* Implementation of the @element-hq/element-web-module-api runtime module API.
|
||||||
*/
|
*/
|
||||||
class ModuleApi implements Api {
|
export class ModuleApi implements Api {
|
||||||
|
private static _instance: ModuleApi;
|
||||||
|
|
||||||
|
public static get instance(): ModuleApi {
|
||||||
|
if (!ModuleApi._instance) {
|
||||||
|
ModuleApi._instance = new ModuleApi();
|
||||||
|
window.mxModuleApi = ModuleApi._instance;
|
||||||
|
}
|
||||||
|
return ModuleApi._instance;
|
||||||
|
}
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
public async _registerLegacyModule(LegacyModule: RuntimeModuleConstructor): Promise<void> {
|
public async _registerLegacyModule(LegacyModule: RuntimeModuleConstructor): Promise<void> {
|
||||||
ModuleRunner.instance.registerModule((api) => new LegacyModule(api));
|
ModuleRunner.instance.registerModule((api) => new LegacyModule(api));
|
||||||
@ -77,8 +87,3 @@ class ModuleApi implements Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type ModuleApiType = ModuleApi;
|
export type ModuleApiType = ModuleApi;
|
||||||
|
|
||||||
if (!window.mxModuleApi) {
|
|
||||||
window.mxModuleApi = new ModuleApi();
|
|
||||||
}
|
|
||||||
export default window.mxModuleApi;
|
|
||||||
|
|||||||
@ -30,7 +30,7 @@ import { type TimelineRenderingType } from "../contexts/RoomContext";
|
|||||||
import { launchPollEditor } from "../components/views/messages/MPollBody";
|
import { launchPollEditor } from "../components/views/messages/MPollBody";
|
||||||
import { Action } from "../dispatcher/actions";
|
import { Action } from "../dispatcher/actions";
|
||||||
import { type ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
|
import { type ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
|
||||||
import ModuleApi from "../modules/Api";
|
import { ModuleApi } from "../modules/Api";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether an event should allow actions like reply, reactions, edit, etc.
|
* Returns whether an event should allow actions like reply, reactions, edit, etc.
|
||||||
@ -78,7 +78,7 @@ export function canEditContent(matrixClient: MatrixClient, mxEvent: MatrixEvent)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ModuleApi.customComponents.getHintsForMessage(mxEvent)?.allowEditingEvent === false) {
|
if (ModuleApi.instance.customComponents.getHintsForMessage(mxEvent)?.allowEditingEvent === false) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import ElectronPlatform from "./platform/ElectronPlatform";
|
|||||||
import PWAPlatform from "./platform/PWAPlatform";
|
import PWAPlatform from "./platform/PWAPlatform";
|
||||||
import WebPlatform from "./platform/WebPlatform";
|
import WebPlatform from "./platform/WebPlatform";
|
||||||
import { initRageshake, initRageshakeStore } from "./rageshakesetup";
|
import { initRageshake, initRageshakeStore } from "./rageshakesetup";
|
||||||
import ModuleApi from "../modules/Api.ts";
|
import { ModuleApi } from "../modules/Api.ts";
|
||||||
|
|
||||||
export const rageshakePromise = initRageshake();
|
export const rageshakePromise = initRageshake();
|
||||||
|
|
||||||
@ -145,7 +145,7 @@ export async function loadPlugins(): Promise<void> {
|
|||||||
|
|
||||||
const modules = SdkConfig.get("modules");
|
const modules = SdkConfig.get("modules");
|
||||||
if (!modules?.length) return;
|
if (!modules?.length) return;
|
||||||
const moduleLoader = new ModuleLoader(ModuleApi);
|
const moduleLoader = new ModuleLoader(ModuleApi.instance);
|
||||||
window.mxModuleLoader = moduleLoader;
|
window.mxModuleLoader = moduleLoader;
|
||||||
for (const src of modules) {
|
for (const src of modules) {
|
||||||
// We need to instruct webpack to not mangle this import as it is not available at compile time
|
// We need to instruct webpack to not mangle this import as it is not available at compile time
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
|||||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||||
import RoomPreviewBar from "../../../../../src/components/views/rooms/RoomPreviewBar";
|
import RoomPreviewBar from "../../../../../src/components/views/rooms/RoomPreviewBar";
|
||||||
import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
|
import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
|
||||||
import ModuleApi from "../../../../../src/modules/Api.ts";
|
import { ModuleApi } from "../../../../../src/modules/Api.ts";
|
||||||
|
|
||||||
jest.mock("../../../../../src/IdentityAuthClient", () => {
|
jest.mock("../../../../../src/IdentityAuthClient", () => {
|
||||||
return jest.fn().mockImplementation(() => {
|
return jest.fn().mockImplementation(() => {
|
||||||
@ -500,7 +500,7 @@ describe("<RoomPreviewBar />", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should render Module roomPreviewBarRenderer if specified", () => {
|
it("should render Module roomPreviewBarRenderer if specified", () => {
|
||||||
jest.spyOn(ModuleApi.customComponents, "roomPreviewBarRenderer", "get").mockReturnValue(() => (
|
jest.spyOn(ModuleApi.instance.customComponents, "roomPreviewBarRenderer", "get").mockReturnValue(() => (
|
||||||
<>Test component</>
|
<>Test component</>
|
||||||
));
|
));
|
||||||
const { getByText } = render(<RoomPreviewBar />);
|
const { getByText } = render(<RoomPreviewBar />);
|
||||||
|
|||||||
@ -13,10 +13,13 @@ import {
|
|||||||
JSONEventFactory,
|
JSONEventFactory,
|
||||||
MessageEventFactory,
|
MessageEventFactory,
|
||||||
pickFactory,
|
pickFactory,
|
||||||
|
renderTile,
|
||||||
RoomCreateEventFactory,
|
RoomCreateEventFactory,
|
||||||
} from "../../../src/events/EventTileFactory";
|
} from "../../../src/events/EventTileFactory";
|
||||||
import SettingsStore from "../../../src/settings/SettingsStore";
|
import SettingsStore from "../../../src/settings/SettingsStore";
|
||||||
import { createTestClient, mkEvent } from "../../test-utils";
|
import { createTestClient, mkEvent } from "../../test-utils";
|
||||||
|
import { TimelineRenderingType } from "../../../src/contexts/RoomContext";
|
||||||
|
import { ModuleApi } from "../../../src/modules/Api";
|
||||||
|
|
||||||
const roomId = "!room:example.com";
|
const roomId = "!room:example.com";
|
||||||
|
|
||||||
@ -205,3 +208,54 @@ describe("pickFactory", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("renderTile", () => {
|
||||||
|
let client: MatrixClient;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
client = createTestClient();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rendering a tile defers to the module API", () => {
|
||||||
|
ModuleApi.instance.customComponents.renderMessage = jest.fn();
|
||||||
|
|
||||||
|
const messageEvent = mkEvent({
|
||||||
|
event: true,
|
||||||
|
type: EventType.RoomMessage,
|
||||||
|
user: client.getUserId()!,
|
||||||
|
room: roomId,
|
||||||
|
content: {
|
||||||
|
msgtype: MsgType.Text,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
renderTile(TimelineRenderingType.Room, { mxEvent: messageEvent, showHiddenEvents: false }, client);
|
||||||
|
|
||||||
|
expect(ModuleApi.instance.customComponents.renderMessage).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
mxEvent: messageEvent,
|
||||||
|
},
|
||||||
|
expect.any(Function),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rendering a tile for a message of unknown type defers to the module API", () => {
|
||||||
|
ModuleApi.instance.customComponents.renderMessage = jest.fn();
|
||||||
|
|
||||||
|
const messageEvent = mkEvent({
|
||||||
|
event: true,
|
||||||
|
type: "weird.type",
|
||||||
|
user: client.getUserId()!,
|
||||||
|
room: roomId,
|
||||||
|
content: {
|
||||||
|
msgtype: MsgType.Text,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
renderTile(TimelineRenderingType.Room, { mxEvent: messageEvent, showHiddenEvents: false }, client);
|
||||||
|
|
||||||
|
expect(ModuleApi.instance.customComponents.renderMessage).toHaveBeenCalledWith({
|
||||||
|
mxEvent: messageEvent,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user