mirror of
https://github.com/vector-im/element-web.git
synced 2026-05-08 13:46:16 +02:00
some experimental module APIs
This commit is contained in:
parent
fc391169da
commit
6b4a7833db
@ -2736,6 +2736,12 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||
if (b) extraButtons.push(b);
|
||||
}
|
||||
|
||||
const roomBanners: JSX.Element[] = [];
|
||||
for (const cb of ModuleApi.instance.extras.roomBannerCallbacks) {
|
||||
const b = cb(this.state.room.roomId);
|
||||
if (b) roomBanners.push(b);
|
||||
}
|
||||
|
||||
return (
|
||||
<ScopedRoomContextProvider {...this.state} roomViewStore={this.roomViewStore}>
|
||||
<div
|
||||
@ -2767,6 +2773,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||
extraButtons={<>{extraButtons}</>}
|
||||
/>
|
||||
)}
|
||||
{roomBanners}
|
||||
{mainSplitBody}
|
||||
</div>
|
||||
</MainSplit>
|
||||
|
||||
@ -54,6 +54,7 @@ import { type MatrixClientProps, withMatrixClientHOC } from "../../../contexts/M
|
||||
import { UIFeature } from "../../../settings/UIFeature";
|
||||
import { formatTimeLeft } from "../../../DateUtils";
|
||||
import RoomReplacedSvg from "../../../../res/img/room_replaced.svg";
|
||||
import { ModuleApi } from "../../../modules/Api";
|
||||
|
||||
// The prefix used when persisting editor drafts to localstorage.
|
||||
export const WYSIWYG_EDITOR_STATE_STORAGE_PREFIX = "mx_wysiwyg_state_";
|
||||
@ -665,6 +666,14 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
||||
|
||||
const showSendButton = canSendMessages && (!this.state.isComposerEmpty || this.state.haveRecording);
|
||||
|
||||
const composerLeftComponents: JSX.Element[] = [];
|
||||
if (canSendMessages) {
|
||||
for (const cb of ModuleApi.instance.extras.composerLeftComponentCallbacks) {
|
||||
const c = cb(this.props.room.roomId);
|
||||
if (c) composerLeftComponents.push(c);
|
||||
}
|
||||
}
|
||||
|
||||
const classes = classNames({
|
||||
"mx_MessageComposer": true,
|
||||
"mx_MessageComposer--compact": this.props.compact,
|
||||
@ -682,6 +691,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
||||
/>
|
||||
<div className="mx_MessageComposer_row">
|
||||
{leftIcon}
|
||||
{composerLeftComponents}
|
||||
{composer}
|
||||
<div className="mx_MessageComposer_actions">
|
||||
{controls}
|
||||
|
||||
@ -60,6 +60,7 @@ import { type IDiff } from "../../../editor/diff";
|
||||
import { getBlobSafeMimeType } from "../../../utils/blobs";
|
||||
import { EMOJI_REGEX } from "../../../HtmlUtils";
|
||||
import { attachMentions, attachRelation } from "../../../utils/messages";
|
||||
import { ModuleApi } from "../../../modules/Api";
|
||||
|
||||
// The prefix used when persisting editor drafts to localstorage.
|
||||
export const EDITOR_STATE_STORAGE_PREFIX = "mx_cider_state_";
|
||||
@ -416,6 +417,11 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||
// don't bother sending an empty message
|
||||
if (!content.body.trim()) return;
|
||||
|
||||
// Apply module event content transforms
|
||||
for (const cb of ModuleApi.instance.extras.eventContentTransformCallbacks) {
|
||||
content = cb(roomId, content as Record<string, unknown>) as RoomMessageEventContent;
|
||||
}
|
||||
|
||||
if (SettingsStore.getValue("Performance.addSendMessageTimingMetadata")) {
|
||||
decorateStartSendingTime(content);
|
||||
}
|
||||
|
||||
@ -28,6 +28,7 @@ import { createRedactEventDialog } from "../../../dialogs/ConfirmRedactDialog";
|
||||
import { endEditing, cancelPreviousPendingEdit } from "./editing";
|
||||
import type EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
|
||||
import { createMessageContent, EMOTE_PREFIX } from "./createMessageContent";
|
||||
import { ModuleApi } from "../../../../../modules/Api";
|
||||
import { isContentModified } from "./isContentModified";
|
||||
import { CommandCategories, getCommand } from "../../../../../slash-commands/SlashCommands";
|
||||
import { runSlashCommand, shouldSendAnyway } from "../../../../../editor/commands";
|
||||
@ -122,6 +123,11 @@ export async function sendMessage(
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply module event content transforms
|
||||
for (const cb of ModuleApi.instance.extras.eventContentTransformCallbacks) {
|
||||
content = cb(roomId, content as Record<string, unknown>) as RoomMessageEventContent;
|
||||
}
|
||||
|
||||
if (SettingsStore.getValue("Performance.addSendMessageTimingMetadata")) {
|
||||
decorateStartSendingTime(content);
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import { type Api, type RuntimeModuleConstructor } from "@element-hq/element-web-module-api";
|
||||
import { MatrixClient, EventType } from "matrix-js-sdk/src/matrix";
|
||||
import { I18nApi } from "@element-hq/web-shared-components";
|
||||
|
||||
import { ModuleRunner } from "./ModuleRunner.ts";
|
||||
@ -52,6 +53,7 @@ export class ModuleApi implements Api {
|
||||
public static get instance(): ModuleApi {
|
||||
if (!ModuleApi._instance) {
|
||||
ModuleApi._instance = new ModuleApi();
|
||||
ModuleApi.patchClientForEnvelopeTransforms();
|
||||
window.mxModuleApi = ModuleApi._instance;
|
||||
}
|
||||
return ModuleApi._instance;
|
||||
@ -98,6 +100,43 @@ export class ModuleApi implements Api {
|
||||
public createRoot(element: Element): Root {
|
||||
return createRoot(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Patches MatrixClient.sendEventHttpRequest to apply encrypted envelope transforms.
|
||||
* Must be called once at startup.
|
||||
*
|
||||
* XXX: TODO: FIXME: this is a horrific workaround to avoid touching js-sdk
|
||||
* We should expose the hook in js-sdk instead, obviously.
|
||||
*/
|
||||
public static patchClientForEnvelopeTransforms(): void {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const proto = MatrixClient.prototype as any;
|
||||
const original = proto.sendEventHttpRequest;
|
||||
proto.sendEventHttpRequest = function (
|
||||
this: MatrixClient,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
event: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
...args: any[]
|
||||
) {
|
||||
// If the event was encrypted and there are envelope transform callbacks, apply them
|
||||
if (
|
||||
event.isEncrypted?.() &&
|
||||
event.getWireType?.() === EventType.RoomMessageEncrypted &&
|
||||
ModuleApi._instance?.extras.encryptedEnvelopeTransformCallbacks.length
|
||||
) {
|
||||
const roomId = event.getRoomId();
|
||||
if (roomId) {
|
||||
let content = event.event.content;
|
||||
for (const cb of ModuleApi._instance.extras.encryptedEnvelopeTransformCallbacks) {
|
||||
content = cb(roomId, content);
|
||||
}
|
||||
event.event.content = content;
|
||||
}
|
||||
}
|
||||
return original.call(this, event, ...args);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export type ModuleApiType = ModuleApi;
|
||||
|
||||
@ -17,4 +17,19 @@ export class ClientApi implements IClientApi {
|
||||
if (sdkRoom) return new ModuleRoom(sdkRoom);
|
||||
return null;
|
||||
}
|
||||
|
||||
public async downloadMxc(mxcUrl: string): Promise<string> {
|
||||
const client = MatrixClientPeg.safeGet();
|
||||
// useAuthentication=true produces the authenticated /_matrix/client/v1/media/download URL
|
||||
const httpUrl = client.mxcUrlToHttp(mxcUrl, undefined, undefined, undefined, false, true);
|
||||
if (!httpUrl) throw new Error(`Cannot resolve mxc URL: ${mxcUrl}`);
|
||||
const accessToken = client.getAccessToken();
|
||||
const response = await fetch(httpUrl, {
|
||||
headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : {},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status} downloading ${mxcUrl}`);
|
||||
}
|
||||
return response.text();
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,9 @@ import {
|
||||
type SpacePanelItemProps,
|
||||
type ExtrasApi,
|
||||
type RoomHeaderButtonsCallback,
|
||||
type RoomBannerCallback,
|
||||
type ComposerLeftComponentCallback,
|
||||
type EventContentTransformCallback,
|
||||
} from "@element-hq/element-web-module-api";
|
||||
import { TypedEventEmitter } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
@ -32,6 +35,10 @@ export class ElementWebExtrasApi extends TypedEventEmitter<keyof EmittedEvents,
|
||||
public spacePanelItems = new Map<string, SpacePanelItemProps>();
|
||||
public visibleRoomBySpaceKey = new Map<string, () => string[]>();
|
||||
public roomHeaderButtonsCallbacks: RoomHeaderButtonsCallback[] = [];
|
||||
public roomBannerCallbacks: RoomBannerCallback[] = [];
|
||||
public composerLeftComponentCallbacks: ComposerLeftComponentCallback[] = [];
|
||||
public eventContentTransformCallbacks: EventContentTransformCallback[] = [];
|
||||
public encryptedEnvelopeTransformCallbacks: EventContentTransformCallback[] = [];
|
||||
|
||||
public setSpacePanelItem(spacekey: string, item: SpacePanelItemProps): void {
|
||||
this.spacePanelItems.set(spacekey, item);
|
||||
@ -45,6 +52,22 @@ export class ElementWebExtrasApi extends TypedEventEmitter<keyof EmittedEvents,
|
||||
public addRoomHeaderButtonCallback(cb: RoomHeaderButtonsCallback): void {
|
||||
this.roomHeaderButtonsCallbacks.push(cb);
|
||||
}
|
||||
|
||||
public addRoomBannerCallback(cb: RoomBannerCallback): void {
|
||||
this.roomBannerCallbacks.push(cb);
|
||||
}
|
||||
|
||||
public addComposerLeftComponentCallback(cb: ComposerLeftComponentCallback): void {
|
||||
this.composerLeftComponentCallbacks.push(cb);
|
||||
}
|
||||
|
||||
public addEventContentTransformCallback(cb: EventContentTransformCallback): void {
|
||||
this.eventContentTransformCallbacks.push(cb);
|
||||
}
|
||||
|
||||
public addEncryptedEnvelopeTransformCallback(cb: EventContentTransformCallback): void {
|
||||
this.encryptedEnvelopeTransformCallbacks.push(cb);
|
||||
}
|
||||
}
|
||||
|
||||
export function useModuleSpacePanelItems(api: ElementWebExtrasApi): ModuleSpacePanelItem[] {
|
||||
|
||||
@ -55,6 +55,7 @@ export class CustomComponentsApi implements ICustomComponentsApi {
|
||||
}
|
||||
return {
|
||||
content: mxEvent.getContent(),
|
||||
wireContent: mxEvent.getWireContent(),
|
||||
eventId,
|
||||
originServerTs: mxEvent.getTs(),
|
||||
roomId,
|
||||
|
||||
@ -5,7 +5,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.
|
||||
*/
|
||||
|
||||
import { type Room as IRoom, Watchable } from "@element-hq/element-web-module-api";
|
||||
import { type Room as IRoom, type MatrixEvent as ModuleMatrixEvent, 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 {
|
||||
@ -22,6 +22,25 @@ export class Room implements IRoom {
|
||||
public get id(): string {
|
||||
return this.sdkRoom.roomId;
|
||||
}
|
||||
|
||||
public getStateEvent(eventType: string, stateKey: string = ""): ModuleMatrixEvent | null {
|
||||
const event = this.sdkRoom.currentState.getStateEvents(eventType, stateKey);
|
||||
if (!event) return null;
|
||||
const eventId = event.getId();
|
||||
const roomId = event.getRoomId();
|
||||
const sender = event.getSender();
|
||||
if (!eventId || !roomId || !sender) return null;
|
||||
return {
|
||||
content: event.getContent(),
|
||||
eventId,
|
||||
originServerTs: event.getTs(),
|
||||
roomId,
|
||||
sender,
|
||||
stateKey: event.getStateKey(),
|
||||
type: event.getType(),
|
||||
unsigned: event.getUnsigned(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user