From c9ea5f8f4ffca79d1a180d80e2ce7cafbc443d76 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Fri, 16 May 2025 15:49:07 +0100 Subject: [PATCH] Custom component concept piece --- .../element-web-module-api.api.md | 491 +++++++++--------- .../src/api/custom-components.ts | 86 +++ .../element-web-module-api/src/api/index.ts | 3 + packages/element-web-module-api/src/index.ts | 2 + 4 files changed, 349 insertions(+), 233 deletions(-) create mode 100644 packages/element-web-module-api/src/api/custom-components.ts diff --git a/packages/element-web-module-api/element-web-module-api.api.md b/packages/element-web-module-api/element-web-module-api.api.md index a4b9456cb6..1e41a6da51 100644 --- a/packages/element-web-module-api/element-web-module-api.api.md +++ b/packages/element-web-module-api/element-web-module-api.api.md @@ -1,233 +1,258 @@ -## API Report File for "@element-hq/element-web-module-api" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { ModuleApi } from '@matrix-org/react-sdk-module-api'; -import { Root } from 'react-dom/client'; -import { RuntimeModule } from '@matrix-org/react-sdk-module-api'; - -// @alpha @deprecated (undocumented) -export interface AliasCustomisations { - // (undocumented) - getDisplayAliasForAliasSet?(canonicalAlias: string | null, altAliases: string[]): string | null; -} - -// Warning: (ae-incompatible-release-tags) The symbol "Api" is marked as @public, but its signature references "LegacyModuleApiExtension" which is marked as @alpha -// Warning: (ae-incompatible-release-tags) The symbol "Api" is marked as @public, but its signature references "LegacyCustomisationsApiExtension" which is marked as @alpha -// -// @public -export interface Api extends LegacyModuleApiExtension, LegacyCustomisationsApiExtension { - readonly config: ConfigApi; - createRoot(element: Element): Root; - readonly i18n: I18nApi; - readonly rootNode: HTMLElement; -} - -// @alpha @deprecated (undocumented) -export interface ChatExportCustomisations { - getForceChatExportParameters(): { - format?: ExportFormat; - range?: ExportType; - numberOfMessages?: number; - includeAttachments?: boolean; - sizeMb?: number; - }; -} - -// @alpha @deprecated (undocumented) -export interface ComponentVisibilityCustomisations { - shouldShowComponent?(component: "UIComponent.sendInvites" | "UIComponent.roomCreation" | "UIComponent.spaceCreation" | "UIComponent.exploreRooms" | "UIComponent.addIntegrations" | "UIComponent.filterContainer" | "UIComponent.roomOptionsMenu"): boolean; -} - -// @public -export interface Config { - // (undocumented) - brand: string; -} - -// @public -export interface ConfigApi { - // (undocumented) - get(): Config; - // (undocumented) - get(key: K): Config[K]; - // (undocumented) - get(key?: K): Config | Config[K]; -} - -// @alpha @deprecated (undocumented) -export interface DirectoryCustomisations { - // (undocumented) - requireCanonicalAliasAccessToPublish?(): boolean; -} - -// @public -export interface I18nApi { - get language(): string; - register(translations: Partial): void; - translate(key: keyof Translations, variables?: Variables): string; -} - -// @alpha @deprecated (undocumented) -export type LegacyCustomisations = (customisations: T) => void; - -// @alpha @deprecated (undocumented) -export interface LegacyCustomisationsApiExtension { - // @deprecated (undocumented) - readonly _registerLegacyAliasCustomisations: LegacyCustomisations; - // @deprecated (undocumented) - readonly _registerLegacyChatExportCustomisations: LegacyCustomisations>; - // @deprecated (undocumented) - readonly _registerLegacyComponentVisibilityCustomisations: LegacyCustomisations; - // @deprecated (undocumented) - readonly _registerLegacyDirectoryCustomisations: LegacyCustomisations; - // @deprecated (undocumented) - readonly _registerLegacyLifecycleCustomisations: LegacyCustomisations; - // @deprecated (undocumented) - readonly _registerLegacyMediaCustomisations: LegacyCustomisations>; - // @deprecated (undocumented) - readonly _registerLegacyRoomListCustomisations: LegacyCustomisations>; - // @deprecated (undocumented) - readonly _registerLegacyUserIdentifierCustomisations: LegacyCustomisations; - // @deprecated (undocumented) - readonly _registerLegacyWidgetPermissionsCustomisations: LegacyCustomisations>; - // @deprecated (undocumented) - readonly _registerLegacyWidgetVariablesCustomisations: LegacyCustomisations; -} - -// @alpha @deprecated (undocumented) -export interface LegacyModuleApiExtension { - // @deprecated - _registerLegacyModule(LegacyModule: RuntimeModuleConstructor): Promise; -} - -// @alpha @deprecated (undocumented) -export interface LifecycleCustomisations { - // (undocumented) - onLoggedOutAndStorageCleared?(): void; -} - -// @alpha @deprecated (undocumented) -export interface Media { - // (undocumented) - downloadSource(): Promise; - // (undocumented) - getSquareThumbnailHttp(dim: number): string | null; - // (undocumented) - getThumbnailHttp(width: number, height: number, mode?: "scale" | "crop"): string | null; - // (undocumented) - getThumbnailOfSourceHttp(width: number, height: number, mode?: "scale" | "crop"): string | null; - // (undocumented) - readonly hasThumbnail: boolean; - // (undocumented) - readonly isEncrypted: boolean; - // (undocumented) - readonly srcHttp: string | null; - // (undocumented) - readonly srcMxc: string; - // (undocumented) - readonly thumbnailHttp: string | null; - // (undocumented) - readonly thumbnailMxc: string | null | undefined; -} - -// @alpha @deprecated (undocumented) -export interface MediaContructable { - // (undocumented) - new (prepared: PreparedMedia): Media; -} - -// @alpha @deprecated (undocumented) -export interface MediaCustomisations { - // (undocumented) - readonly Media: MediaContructable; - // (undocumented) - mediaFromContent(content: Content, client?: Client): Media; - // (undocumented) - mediaFromMxc(mxc?: string, client?: Client): Media; -} - -// @public -export interface Module { - // (undocumented) - load(): Promise; -} - -// @public -export interface ModuleFactory { - // (undocumented) - new (api: Api): Module; - // (undocumented) - readonly moduleApiVersion: string; - // (undocumented) - readonly prototype: Module; -} - -// @public -export class ModuleIncompatibleError extends Error { - constructor(pluginVersion: string); -} - -// @public -export class ModuleLoader { - constructor(api: Api); - // Warning: (ae-forgotten-export) The symbol "ModuleExport" needs to be exported by the entry point index.d.ts - // - // (undocumented) - load(moduleExport: ModuleExport): Promise; - // (undocumented) - start(): Promise; -} - -// @alpha @deprecated (undocumented) -export interface RoomListCustomisations { - isRoomVisible?(room: Room): boolean; -} - -// @alpha @deprecated (undocumented) -export type RuntimeModuleConstructor = new (api: ModuleApi) => RuntimeModule; - -// @public -export type Translations = Record; - -// @alpha @deprecated (undocumented) -export interface UserIdentifierCustomisations { - getDisplayUserIdentifier(userId: string, opts: { - roomId?: string; - withDisplayName?: boolean; - }): string | null; -} - -// @public -export type Variables = { - count?: number; - [key: string]: number | string | undefined; -}; - -// @alpha @deprecated (undocumented) -export interface WidgetPermissionsCustomisations { - preapproveCapabilities?(widget: Widget, requestedCapabilities: Set): Promise>; -} - -// @alpha @deprecated (undocumented) -export interface WidgetVariablesCustomisations { - isReady?(): Promise; - provideVariables?(): { - currentUserId: string; - userDisplayName?: string; - userHttpAvatarUrl?: string; - clientId?: string; - clientTheme?: string; - clientLanguage?: string; - deviceId?: string; - baseUrl?: string; - }; -} - -// (No @packageDocumentation comment for this package) - -``` +## API Report File for "@element-hq/element-web-module-api" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { JSX } from 'react'; +import { ModuleApi } from '@matrix-org/react-sdk-module-api'; +import { Root } from 'react-dom/client'; +import { RuntimeModule } from '@matrix-org/react-sdk-module-api'; + +// @alpha @deprecated (undocumented) +export interface AliasCustomisations { + // (undocumented) + getDisplayAliasForAliasSet?(canonicalAlias: string | null, altAliases: string[]): string | null; +} + +// Warning: (ae-incompatible-release-tags) The symbol "Api" is marked as @public, but its signature references "LegacyModuleApiExtension" which is marked as @alpha +// Warning: (ae-incompatible-release-tags) The symbol "Api" is marked as @public, but its signature references "LegacyCustomisationsApiExtension" which is marked as @alpha +// +// @public +export interface Api extends LegacyModuleApiExtension, LegacyCustomisationsApiExtension { + readonly config: ConfigApi; + createRoot(element: Element): Root; + // (undocumented) + readonly customComponents: CustomComponentsApi; + readonly i18n: I18nApi; + readonly rootNode: HTMLElement; +} + +// @alpha @deprecated (undocumented) +export interface ChatExportCustomisations { + getForceChatExportParameters(): { + format?: ExportFormat; + range?: ExportType; + numberOfMessages?: number; + includeAttachments?: boolean; + sizeMb?: number; + }; +} + +// @alpha @deprecated (undocumented) +export interface ComponentVisibilityCustomisations { + shouldShowComponent?(component: "UIComponent.sendInvites" | "UIComponent.roomCreation" | "UIComponent.spaceCreation" | "UIComponent.exploreRooms" | "UIComponent.addIntegrations" | "UIComponent.filterContainer" | "UIComponent.roomOptionsMenu"): boolean; +} + +// @public +export interface Config { + // (undocumented) + brand: string; +} + +// @public +export interface ConfigApi { + // (undocumented) + get(): Config; + // (undocumented) + get(key: K): Config[K]; + // (undocumented) + get(key?: K): Config | Config[K]; +} + +// @public +export type CustomComponentProps = { + [CustomComponentTarget.TextualEvent]: { + mxEvent: any; + stripReply: boolean; + }; +}; + +// @public +export type CustomComponentRenderFunction = (props: CustomComponentProps[T], originalComponent: JSX.Element) => JSX.Element | null; + +// @public +export interface CustomComponentsApi { + register(target: T, renderer: CustomComponentRenderFunction): void; +} + +// @public +export enum CustomComponentTarget { + // (undocumented) + TextualEvent = "TextualEvent" +} + +// @alpha @deprecated (undocumented) +export interface DirectoryCustomisations { + // (undocumented) + requireCanonicalAliasAccessToPublish?(): boolean; +} + +// @public +export interface I18nApi { + get language(): string; + register(translations: Partial): void; + translate(key: keyof Translations, variables?: Variables): string; +} + +// @alpha @deprecated (undocumented) +export type LegacyCustomisations = (customisations: T) => void; + +// @alpha @deprecated (undocumented) +export interface LegacyCustomisationsApiExtension { + // @deprecated (undocumented) + readonly _registerLegacyAliasCustomisations: LegacyCustomisations; + // @deprecated (undocumented) + readonly _registerLegacyChatExportCustomisations: LegacyCustomisations>; + // @deprecated (undocumented) + readonly _registerLegacyComponentVisibilityCustomisations: LegacyCustomisations; + // @deprecated (undocumented) + readonly _registerLegacyDirectoryCustomisations: LegacyCustomisations; + // @deprecated (undocumented) + readonly _registerLegacyLifecycleCustomisations: LegacyCustomisations; + // @deprecated (undocumented) + readonly _registerLegacyMediaCustomisations: LegacyCustomisations>; + // @deprecated (undocumented) + readonly _registerLegacyRoomListCustomisations: LegacyCustomisations>; + // @deprecated (undocumented) + readonly _registerLegacyUserIdentifierCustomisations: LegacyCustomisations; + // @deprecated (undocumented) + readonly _registerLegacyWidgetPermissionsCustomisations: LegacyCustomisations>; + // @deprecated (undocumented) + readonly _registerLegacyWidgetVariablesCustomisations: LegacyCustomisations; +} + +// @alpha @deprecated (undocumented) +export interface LegacyModuleApiExtension { + // @deprecated + _registerLegacyModule(LegacyModule: RuntimeModuleConstructor): Promise; +} + +// @alpha @deprecated (undocumented) +export interface LifecycleCustomisations { + // (undocumented) + onLoggedOutAndStorageCleared?(): void; +} + +// @alpha @deprecated (undocumented) +export interface Media { + // (undocumented) + downloadSource(): Promise; + // (undocumented) + getSquareThumbnailHttp(dim: number): string | null; + // (undocumented) + getThumbnailHttp(width: number, height: number, mode?: "scale" | "crop"): string | null; + // (undocumented) + getThumbnailOfSourceHttp(width: number, height: number, mode?: "scale" | "crop"): string | null; + // (undocumented) + readonly hasThumbnail: boolean; + // (undocumented) + readonly isEncrypted: boolean; + // (undocumented) + readonly srcHttp: string | null; + // (undocumented) + readonly srcMxc: string; + // (undocumented) + readonly thumbnailHttp: string | null; + // (undocumented) + readonly thumbnailMxc: string | null | undefined; +} + +// @alpha @deprecated (undocumented) +export interface MediaContructable { + // (undocumented) + new (prepared: PreparedMedia): Media; +} + +// @alpha @deprecated (undocumented) +export interface MediaCustomisations { + // (undocumented) + readonly Media: MediaContructable; + // (undocumented) + mediaFromContent(content: Content, client?: Client): Media; + // (undocumented) + mediaFromMxc(mxc?: string, client?: Client): Media; +} + +// @public +export interface Module { + // (undocumented) + load(): Promise; +} + +// @public +export interface ModuleFactory { + // (undocumented) + new (api: Api): Module; + // (undocumented) + readonly moduleApiVersion: string; + // (undocumented) + readonly prototype: Module; +} + +// @public +export class ModuleIncompatibleError extends Error { + constructor(pluginVersion: string); +} + +// @public +export class ModuleLoader { + constructor(api: Api); + // Warning: (ae-forgotten-export) The symbol "ModuleExport" needs to be exported by the entry point index.d.ts + // + // (undocumented) + load(moduleExport: ModuleExport): Promise; + // (undocumented) + start(): Promise; +} + +// @alpha @deprecated (undocumented) +export interface RoomListCustomisations { + isRoomVisible?(room: Room): boolean; +} + +// @alpha @deprecated (undocumented) +export type RuntimeModuleConstructor = new (api: ModuleApi) => RuntimeModule; + +// @public +export type Translations = Record; + +// @alpha @deprecated (undocumented) +export interface UserIdentifierCustomisations { + getDisplayUserIdentifier(userId: string, opts: { + roomId?: string; + withDisplayName?: boolean; + }): string | null; +} + +// @public +export type Variables = { + count?: number; + [key: string]: number | string | undefined; +}; + +// @alpha @deprecated (undocumented) +export interface WidgetPermissionsCustomisations { + preapproveCapabilities?(widget: Widget, requestedCapabilities: Set): Promise>; +} + +// @alpha @deprecated (undocumented) +export interface WidgetVariablesCustomisations { + isReady?(): Promise; + provideVariables?(): { + currentUserId: string; + userDisplayName?: string; + userHttpAvatarUrl?: string; + clientId?: string; + clientTheme?: string; + clientLanguage?: string; + deviceId?: string; + baseUrl?: string; + }; +} + +// (No @packageDocumentation comment for this package) + +``` diff --git a/packages/element-web-module-api/src/api/custom-components.ts b/packages/element-web-module-api/src/api/custom-components.ts new file mode 100644 index 0000000000..582a281ce1 --- /dev/null +++ b/packages/element-web-module-api/src/api/custom-components.ts @@ -0,0 +1,86 @@ +import { ComponentType, JSX, MouseEventHandler, PropsWithChildren } from "react"; + +/** + * Target of the renderer function. + * @public + */ +export enum CustomComponentTarget { + /** + * TODO: We should make this more descriptive. + * The renderer for text events in the timeline, such as "m.room.message" + */ + TextualBody = "TextualBody", + MessageContextMenu = "MessageContextMenu", +} + +export interface ContextMenuItem { + label: string; + iconClassName: string; + onClick: ( + e: React.MouseEvent | React.KeyboardEvent | React.FormEvent, + ) => void | Promise; +} + +/** + * Properties for the render component. + * @public + */ +export type CustomComponentProps = { + [CustomComponentTarget.TextualBody]: { + /** + * The Matrix event information. + * TODO: Should this just use the types from matrix-js-sdk? + */ + mxEvent: any; + /** + * Words to highlight on + */ + highlights?: string[]; + /** + * Should previews be shown for this event + */ + showUrlPreview?: boolean; + /** + * Is this event being rendered to a static export + */ + forExport?: boolean; + }; + [CustomComponentTarget.MessageContextMenu]: { + /** + * The Matrix event information. + * TODO: Should this just use the types from matrix-js-sdk? + */ + mxEvent: any; + /** + * Close the menu + */ + closeMenu: () => void; + }; +}; +/** + * Render function. Returning null skips this function and passes it onto the next registered renderer. + * @public + */ +export type CustomComponentRenderFunction = ( + props: CustomComponentProps[T], + originalComponent: JSX.Element, +) => JSX.Element | null; + +/** + * The API for creating custom components. + * @public + */ +export interface CustomComponentsApi { + /** + * Register a renderer for a component type. + * @param target - The target type of component + * @param renderer - The render method. + */ + register(target: T, renderer: CustomComponentRenderFunction): void; + + /** + * Register a context menu section with a given set of items. + * @param items - A set of items to render. + */ + buildContextMenuBlock(items: ContextMenuItem[]): JSX.Element; +} diff --git a/packages/element-web-module-api/src/api/index.ts b/packages/element-web-module-api/src/api/index.ts index f20d9db300..35dd52d967 100644 --- a/packages/element-web-module-api/src/api/index.ts +++ b/packages/element-web-module-api/src/api/index.ts @@ -10,6 +10,7 @@ import { LegacyModuleApiExtension } from "./legacy-modules"; import { LegacyCustomisationsApiExtension } from "./legacy-customisations"; import { ConfigApi } from "./config"; import { I18nApi } from "./i18n"; +import { CustomComponentsApi, CustomComponentTarget } from "./custom-components"; /** * Module interface for modules to implement. @@ -86,6 +87,8 @@ export interface Api extends LegacyModuleApiExtension, LegacyCustomisationsApiEx * @public */ readonly rootNode: HTMLElement; + + readonly customComponents: CustomComponentsApi; /** * Create a ReactDOM root for rendering React components. * Exposed to allow modules to avoid needing to bundle their own ReactDOM. diff --git a/packages/element-web-module-api/src/index.ts b/packages/element-web-module-api/src/index.ts index b503c88188..6640814d1f 100644 --- a/packages/element-web-module-api/src/index.ts +++ b/packages/element-web-module-api/src/index.ts @@ -9,5 +9,7 @@ export { ModuleLoader, ModuleIncompatibleError } from "./loader"; export type { Api, Module, ModuleFactory } from "./api"; export type { Config, ConfigApi } from "./api/config"; export type { I18nApi, Variables, Translations } from "./api/i18n"; +export type * from "./api/custom-components"; +export { CustomComponentTarget } from "./api/custom-components"; export type * from "./api/legacy-modules"; export type * from "./api/legacy-customisations";