-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Support for custom message components via Module API #30074
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
213a191
Add new custom component api.
Half-Shot 3281a41
Remove context menu, refactor
Half-Shot 4d81b36
fix types
Half-Shot afab6c2
Add a test for custom modules.
Half-Shot 1ed3f20
tidy
Half-Shot 1c5bc4a
Merge remote-tracking branch 'origin/develop' into hs/add-custom-comp…
Half-Shot 1b2d9b3
Rewrite for new API
Half-Shot 757e4e1
Update tests
Half-Shot ce428b5
lint
Half-Shot f740dc3
Allow passing in props to original component
Half-Shot 9136d84
Add hinting
Half-Shot ec13bdc
Update tests to be complete
Half-Shot e60a68e
lint a bit more
Half-Shot 62f6260
Merge remote-tracking branch 'origin/develop' into hs/add-custom-comp…
Half-Shot c6d4f38
update docstring
Half-Shot ff986e4
update @element-hq/element-web-module-api to 1.1.0
Half-Shot 5aee224
fix types
Half-Shot 42edbab
updates
Half-Shot 391bd15
hide jump to bottom button that was causing flakes
Half-Shot 2948572
lint
Half-Shot d97d999
lint
Half-Shot 66e7381
Use module matrix event interface instead.
Half-Shot 706b33f
update to new module sdk
Half-Shot bba40ca
adapt custom module sample
Half-Shot f44308b
Merge branch 'develop' into hs/add-custom-component-modules
Half-Shot 6d2f1c2
Issues caught by Sonar
Half-Shot 9eb5e89
lint
Half-Shot 9f7359e
fix issues
Half-Shot 3f39741
make the comment make sense
Half-Shot bb0f2d6
fix import
Half-Shot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| /* | ||
| 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 Page } from "@playwright/test"; | ||
|
|
||
| import { test, expect } from "../../element-web-test"; | ||
|
|
||
| const screenshotOptions = (page: Page) => ({ | ||
| mask: [page.locator(".mx_MessageTimestamp")], | ||
| // Hide the jump to bottom button in the timeline to avoid flakiness | ||
| // Exclude timestamp and read marker from snapshot | ||
| css: ` | ||
| .mx_JumpToBottomButton { | ||
| display: none !important; | ||
| } | ||
| .mx_TopUnreadMessagesBar, .mx_MessagePanel_myReadMarker { | ||
| display: none !important; | ||
| } | ||
| `, | ||
| }); | ||
| test.describe("Custom Component API", () => { | ||
| test.use({ | ||
| displayName: "Manny", | ||
| config: { | ||
| modules: ["/modules/custom-component-module.js"], | ||
| }, | ||
| page: async ({ page }, use) => { | ||
| await page.route("/modules/custom-component-module.js", async (route) => { | ||
| await route.fulfill({ path: "playwright/sample-files/custom-component-module.js" }); | ||
| }); | ||
| await use(page); | ||
| }, | ||
| room: async ({ page, app, user, bot }, use) => { | ||
| const roomId = await app.client.createRoom({ name: "TestRoom" }); | ||
| await use({ roomId }); | ||
| }, | ||
| }); | ||
| test.describe("basic functionality", () => { | ||
| test( | ||
| "should replace the render method of a textual event", | ||
| { tag: "@screenshot" }, | ||
| async ({ page, room, app }) => { | ||
| await app.viewRoomById(room.roomId); | ||
| await app.client.sendMessage(room.roomId, "Simple message"); | ||
| await expect(await page.locator(".mx_EventTile_last")).toMatchScreenshot( | ||
| "custom-component-tile.png", | ||
| screenshotOptions(page), | ||
| ); | ||
| }, | ||
| ); | ||
| test( | ||
| "should fall through if one module does not render a component", | ||
| { tag: "@screenshot" }, | ||
| async ({ page, room, app }) => { | ||
| await app.viewRoomById(room.roomId); | ||
| await app.client.sendMessage(room.roomId, "Fall through here"); | ||
| await expect(await page.locator(".mx_EventTile_last")).toMatchScreenshot( | ||
| "custom-component-tile-fall-through.png", | ||
| screenshotOptions(page), | ||
| ); | ||
| }, | ||
| ); | ||
| test( | ||
| "should render the original content of a textual event conditionally", | ||
| { tag: "@screenshot" }, | ||
| async ({ page, room, app }) => { | ||
| await app.viewRoomById(room.roomId); | ||
| await app.client.sendMessage(room.roomId, "Do not replace me"); | ||
| await expect(await page.locator(".mx_EventTile_last")).toMatchScreenshot( | ||
| "custom-component-tile-original.png", | ||
| screenshotOptions(page), | ||
| ); | ||
| }, | ||
| ); | ||
| test("should disallow editing when the allowEditingEvent hint is set to false", async ({ page, room, app }) => { | ||
| await app.viewRoomById(room.roomId); | ||
| await app.client.sendMessage(room.roomId, "Do not show edits"); | ||
| await page.getByText("Do not show edits").hover(); | ||
| await expect( | ||
| await page.getByRole("toolbar", { name: "Message Actions" }).getByRole("button", { name: "Edit" }), | ||
| ).not.toBeVisible(); | ||
| }); | ||
| test( | ||
| "should render the next registered component if the filter function throws", | ||
| { tag: "@screenshot" }, | ||
| async ({ page, room, app }) => { | ||
| await app.viewRoomById(room.roomId); | ||
| await app.client.sendMessage(room.roomId, "Crash the filter!"); | ||
| await expect(await page.locator(".mx_EventTile_last")).toMatchScreenshot( | ||
| "custom-component-crash-handle-filter.png", | ||
| screenshotOptions(page), | ||
| ); | ||
| }, | ||
| ); | ||
| test( | ||
| "should render original component if the render function throws", | ||
| { tag: "@screenshot" }, | ||
| async ({ page, room, app }) => { | ||
| await app.viewRoomById(room.roomId); | ||
| await app.client.sendMessage(room.roomId, "Crash the renderer!"); | ||
| await expect(await page.locator(".mx_EventTile_last")).toMatchScreenshot( | ||
| "custom-component-crash-handle-renderer.png", | ||
| screenshotOptions(page), | ||
| ); | ||
| }, | ||
| ); | ||
| }); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| /* | ||
| 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. | ||
| */ | ||
|
|
||
| export default class CustomComponentModule { | ||
| static moduleApiVersion = "^1.2.0"; | ||
| constructor(api) { | ||
| this.api = api; | ||
| this.api.customComponents.registerMessageRenderer( | ||
| (evt) => evt.content.body === "Do not show edits", | ||
| (_props, originalComponent) => { | ||
| return originalComponent(); | ||
| }, | ||
| { allowEditingEvent: false }, | ||
| ); | ||
| this.api.customComponents.registerMessageRenderer( | ||
| (evt) => evt.content.body === "Fall through here", | ||
| (props) => { | ||
| const body = props.mxEvent.content.body; | ||
| return `Fallthrough text for ${body}`; | ||
| }, | ||
| ); | ||
| this.api.customComponents.registerMessageRenderer( | ||
| (evt) => { | ||
| if (evt.content.body === "Crash the filter!") { | ||
| throw new Error("Fail test!"); | ||
| } | ||
| return false; | ||
| }, | ||
| () => { | ||
| return `Should not render!`; | ||
| }, | ||
| ); | ||
| this.api.customComponents.registerMessageRenderer( | ||
| (evt) => evt.content.body === "Crash the renderer!", | ||
| () => { | ||
| throw new Error("Fail test!"); | ||
| }, | ||
| ); | ||
| // Order is specific here to avoid this overriding the other renderers | ||
| this.api.customComponents.registerMessageRenderer("m.room.message", (props, originalComponent) => { | ||
| const body = props.mxEvent.content.body; | ||
| if (body === "Do not replace me") { | ||
| return originalComponent(); | ||
| } else if (body === "Fall through here") { | ||
| return null; | ||
| } | ||
| return `Custom text for ${body}`; | ||
| }); | ||
| } | ||
| async load() {} | ||
| } |
Binary file added
BIN
+5.73 KB
...modules/custom-component.spec.ts/custom-component-crash-handle-filter-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+4.66 KB
...dules/custom-component.spec.ts/custom-component-crash-handle-renderer-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+5.79 KB
...s/modules/custom-component.spec.ts/custom-component-tile-fall-through-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+6.03 KB
...ight/snapshots/modules/custom-component.spec.ts/custom-component-tile-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+4.61 KB
...shots/modules/custom-component.spec.ts/custom-component-tile-original-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.