Skip to content

Conversation

@Half-Shot
Copy link
Member

@Half-Shot Half-Shot commented May 19, 2025

This adds support for defining custom components for rendering messages. The intention is that the module API holds a loose collection of strings/RegExps that map to a set of render functions. This allows injecting a custom component for specific message types / contents.

Element-side branch: https://github.com/element-hq/element-web/compare/hs/add-custom-component-modules
Example module: <hookshot PR>

  • Docs
  • Tests (on EWeb ✔️ )

@Half-Shot Half-Shot changed the title Custom component concept piece Support custom components May 19, 2025
@Half-Shot Half-Shot marked this pull request as ready for review May 20, 2025 12:28
@Half-Shot Half-Shot requested a review from a team as a code owner May 20, 2025 12:28
@Half-Shot Half-Shot requested a review from t3chguy May 29, 2025 10:32
@Half-Shot Half-Shot changed the title Support custom components Support custom components for messages Jun 3, 2025
Comment on lines 20 to 28
/**
* Words to highlight on (e.g. from search results).
* May be undefined if the client does not need to highlight
*/
highlights?: string[];
/**
* Should previews be shown for this event
*/
showUrlPreview?: boolean;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These seem a little arbitrary, why these but not e.g. highlightLink, editState?

I think the level of this abstraction may make it difficult if not impossible to edit events which have been "decorated" with e.g. your openproject module

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Admittedly this was completely random as I was building out the API, so probably need to think about what properties are interesting to modules. I don't want to just include all of them at the risk that we might remove them later, but some would be useful.

We can also take the approach here of just adding on-demand rather than doing all of them right now, which reduces our commitments.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a shape where a lot are handled upstream e.g. url previews should just be a thing that's glued onto the bottom of the event, where the module could somehow inhibit the behaviour if it doesn't want url previews to be scraped out of the body rather than each module needing its own URL Preview renderer

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, makes sense. I've included a mechanism where a custom component may override props for rendering the original component. Such that URL previews are now handled by Element, but can be disabled by the module.

As far as edited events go, it looks tricky to export that behaviour. To keep this simple for now I've just removed props that I'm not using for my demo module. I think we can be additive here and add new properties as they become important to users.

Comment on lines 69 to 74
* customComponents.registerMessageRenderer("m.room.message", (props, originalComponent) => {
* return <YourCustomComponent mxEvent={props.mxEvent} />;
* });
* customComponents.registerMessageRenderer(/m\.room\.(topic|name)/, (props, originalComponent) => {
* return <YourCustomStateRenderer mxEvent={props.mxEvent} />;
* });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the API distinct state vs not rather than being quite foot-gunny such that if the latter sees an m.room.name timeline event it'd render it?

Copy link
Member Author

@Half-Shot Half-Shot Jun 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, we could tighten up this example. I don't think we need a separate function for state, it's generally good practice within matrix to be checking state keys.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the spirit of the ongoing MVVM work I'd also be keen on simplifying the interface passed, passing entire js-sdk objects around is quite brittle and given we're in the talks of swapping out the js-sdk for the rust-sdk would be guaranteed to break

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what we can do here for the short term, to be honest. I feel like trying to factor MatrixEvent into some sort of common structure is going to have a slight performance overhead per event, and it just feels gross to maintain another structure that we'd have to keep in line with changes to the event structure.

I don't know about the rest of product here but I'd be more in favor of just having a breaking change when the rust-sdk pieces land to support whatever comes next.

@Half-Shot Half-Shot mentioned this pull request Jun 9, 2025
7 tasks
* );
* ```
*/
registerMessageRenderer(
Copy link
Member Author

@Half-Shot Half-Shot Jun 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This API has changed a bit again, to allow for message hints. The intention is that for some messages we do not want the user to be able to edit the message due to the changes that may have been made to the event tile. I've added a hints type that can be extended in the future to do more things, but for now tells Element never to show an edit UI for any event that matches our eventTypeOrFilter.

The other problem with the old iteration was that CustomMessageRenderFunction was not deterministic as it did some filtering inside of the function, and might have returned null to passthrough to another module or the base app. That's no longer the case, and instead the eventTypeOrFilter function/string is the only way to filter whether or not a module is in use. The advantage here is knowing which hints to pass back to the base app is now easier to figure out as we know which renderer will be used.

One thing to note that it is possible that mxEvent changes between being checked for message hints / v.s. rendering so there is a potential gap, but this is probably acceptable.

@Half-Shot Half-Shot enabled auto-merge June 12, 2025 10:11
@Half-Shot Half-Shot force-pushed the hs/custom-component-api branch from 8e4056f to 953d029 Compare June 12, 2025 10:55
@Half-Shot
Copy link
Member Author

CodeQL refuses to run on my fork, so reopening.

@Half-Shot Half-Shot closed this Jun 12, 2025
auto-merge was automatically disabled June 12, 2025 11:02

Pull request was closed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants