Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
748cfbf
Refactor EventTile to MVVM - Step 1 - Create a skeleton
rbondesson Mar 16, 2026
83f8609
First refactoring run complete
rbondesson Mar 16, 2026
33c1255
Extract leaf components and clean up
rbondesson Mar 16, 2026
af3fa7e
Extracting reactionsrow and added missing wiring
rbondesson Mar 17, 2026
5d00dbd
Replacing the old EventTile with the new. Keeping the old for reference
rbondesson Mar 17, 2026
9f6e478
All existing tests now pass
rbondesson Mar 17, 2026
221bd16
Refactor names of components, tests and symbols
rbondesson Mar 17, 2026
71b1b7e
Fix for rendering of profiles
rbondesson Mar 17, 2026
4a36211
Additional tests for profile rendering
rbondesson Mar 17, 2026
a5b61f4
Refactoring tests
rbondesson Mar 17, 2026
006b701
Additional tests for better coverage
rbondesson Mar 17, 2026
65745f1
Audit fixes for EventTileView
rbondesson Mar 17, 2026
c96ee9e
Additional view model tests.
rbondesson Mar 17, 2026
69fff28
Clean up EventTile barrel and interfaces
rbondesson Mar 17, 2026
575625f
Clean up of view model and presenter
rbondesson Mar 17, 2026
504d284
Clean up EvenTileView
rbondesson Mar 17, 2026
0eeee59
Optimizing rendering branches
rbondesson Mar 17, 2026
2a3542f
Memory fixes
rbondesson Mar 18, 2026
70f54a9
Restructure properties and better naming
rbondesson Mar 18, 2026
8790742
Optimize rendering
rbondesson Mar 18, 2026
82b2f7e
Reorder properties in types and interfaces
rbondesson Mar 18, 2026
8cdad17
Avoid exposing internal presenter to callers
rbondesson Mar 18, 2026
87aa2f3
Fix to restore handling of RoomMember
rbondesson Mar 18, 2026
b0f1b90
Refactor types and added documentation
rbondesson Mar 18, 2026
ca34a82
Fix lint errors
rbondesson Mar 18, 2026
1fbff5a
Merge remote-tracking branch 'origin/develop' into refactor/event-tile
rbondesson Mar 18, 2026
94dafa9
Fix Prettier errors
rbondesson Mar 18, 2026
8668b0e
Fix Prettier errors
rbondesson Mar 18, 2026
465aa87
Changed filename, fix for footer handling and updated snapshots
rbondesson Mar 18, 2026
e007939
Updated snapshot
rbondesson Mar 18, 2026
c779fbc
Calculate data-has-reply correctly
rbondesson Mar 18, 2026
a6082d2
Fix Prettier errors
rbondesson Mar 18, 2026
7104480
Fix for playwright tests
rbondesson Mar 18, 2026
7265dc0
Fix for receipt update listener
rbondesson Mar 18, 2026
609affc
Merge remote-tracking branch 'origin/develop' into refactor/event-tile
rbondesson Mar 19, 2026
ba7bd0b
Merge fixes for moved test
rbondesson Mar 19, 2026
fac9521
Changes for readability
rbondesson Mar 19, 2026
9d6c49d
Fix for missing receipt check box
rbondesson Mar 19, 2026
3ee2bea
Align EventTile to MVVM
rbondesson Mar 19, 2026
b4ae048
Align EventTile to MVVM
rbondesson Mar 19, 2026
9d69ffe
Align EventTile to MVVM
rbondesson Mar 19, 2026
6419ab1
Added tests for better converage
rbondesson Mar 19, 2026
e86dd6b
Remove the old component
rbondesson Mar 19, 2026
b9933e0
Fix SonarCloud issue
rbondesson Mar 19, 2026
e1b4dc1
Fix a runtime error for missing key
rbondesson Mar 19, 2026
05a00ea
Fix for missing Timestamp in threads view
rbondesson Mar 19, 2026
fe755a0
Fix for rendering issues and add tests
rbondesson Mar 19, 2026
d6b1d71
Align EventTile to MVVM pattern
rbondesson Mar 19, 2026
f8c50be
Render branch optimization after aligning to MVVM
rbondesson Mar 19, 2026
b425da1
Merge branch 'element-hq:develop' into refactor/event-tile
rbondesson Mar 20, 2026
41a3a0c
Merge branch 'element-hq:develop' into refactor/event-tile
rbondesson Mar 20, 2026
97ed8fa
Added documentation important symbols and types
rbondesson Mar 20, 2026
76e680e
Restore logic for avatar click handler and add tests
rbondesson Mar 20, 2026
9e50f22
Fix for Prettier error
rbondesson Mar 20, 2026
bd41a07
Merge branch 'element-hq:develop' into refactor/event-tile
rbondesson Mar 20, 2026
42f40df
Merge branch 'element-hq:develop' into refactor/event-tile
rbondesson Mar 23, 2026
4441b1e
Fix Sonar issues
rbondesson Mar 23, 2026
fc673e1
Fix for Sonar issue
rbondesson Mar 23, 2026
af3a93c
Fix failing tests
rbondesson Mar 23, 2026
ccdd75a
Fix sonar issues
rbondesson Mar 24, 2026
e6e2b8b
Merge branch 'element-hq:develop' into refactor/event-tile
rbondesson Mar 24, 2026
88dd7f2
Merge remote-tracking branch 'origin/develop' into refactor/event-tile
rbondesson Mar 26, 2026
ccfe653
Merge branch 'refactor/event-tile' of https://github.com/ZacksBot/ele…
rbondesson Mar 26, 2026
1567471
Fix for merging errors
rbondesson Mar 26, 2026
3ebd6f6
Merge fixes
rbondesson Mar 26, 2026
f112f16
Merge branch 'element-hq:develop' into refactor/event-tile
rbondesson Mar 26, 2026
59a2527
Merge remote-tracking branch 'origin/develop' into refactor/event-tile
rbondesson Mar 31, 2026
dd3eecc
Merge fixes
rbondesson Mar 31, 2026
dcba556
Merge remote-tracking branch 'origin/develop' into refactor/event-tile
rbondesson Apr 7, 2026
6abd046
Corrections after merge
rbondesson Apr 7, 2026
b168489
Clean up MVVM pattern after merge
rbondesson Apr 7, 2026
8c5bac0
Extractning EventTileCommands and move files according to the MVVM gu…
rbondesson Apr 7, 2026
214ec65
Clean up of the MVVM pattern
rbondesson Apr 7, 2026
7f84b66
Changes for better readability and documentation
rbondesson Apr 7, 2026
7f6cc1b
Changes for better rendering performance
rbondesson Apr 7, 2026
4402343
Fix for action bar layout in threads list
rbondesson Apr 7, 2026
5e2993d
Fix sonar issues
rbondesson Apr 8, 2026
e1368d8
Sonar fixes for MessageStatus amd EncryptionIndicator
rbondesson Apr 8, 2026
d97d92f
Merge branch 'element-hq:develop' into refactor/event-tile
rbondesson Apr 8, 2026
c3e3671
Leave accessability for EventTile context menu as is
rbondesson Apr 8, 2026
0893525
Fix for using memo with mxEvent
rbondesson Apr 8, 2026
18d9fbb
Merge branch 'element-hq:develop' into refactor/event-tile
rbondesson Apr 8, 2026
9c503ef
Merge remote-tracking branch 'origin/develop' into refactor/event-tile
rbondesson Apr 8, 2026
d7836d4
Fix merge errors
rbondesson Apr 8, 2026
802202f
Merge branch 'develop' into refactor/event-tile
rbondesson Apr 9, 2026
b6652e9
Merge remote-tracking branch 'origin/develop' into refactor/event-tile
rbondesson Apr 21, 2026
01da64b
Fix merge snapshot error
rbondesson Apr 21, 2026
36fb641
Merge branch 'develop' into refactor/event-tile
rbondesson Apr 21, 2026
7869203
Move EventTile interaction and command logic into EventTileViewModel
rbondesson Apr 21, 2026
fa8bf1d
Make EventTile the thin view-model host
rbondesson Apr 21, 2026
51a21ab
Moving more helpers out of the presenter
rbondesson Apr 21, 2026
09ff72b
Removing the presenter
rbondesson Apr 21, 2026
d6ea203
Clean up EventTile
rbondesson Apr 21, 2026
c3d62eb
Move inline types to EventTile barrel
rbondesson Apr 21, 2026
9a6f6b7
Fix prettier and lint errors
rbondesson Apr 21, 2026
f63198c
Fixes for avoiding duplicate subscriptions
rbondesson Apr 21, 2026
4b3bc52
Improved handling of event listeners for better performance
rbondesson Apr 21, 2026
68799cf
Fix Sonar Cloud issues
rbondesson Apr 21, 2026
967e654
Merge branch 'develop' into refactor/event-tile
rbondesson Apr 21, 2026
f75134f
Merge branch 'develop' into refactor/event-tile
rbondesson Apr 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 18 additions & 19 deletions apps/web/src/components/structures/MessagePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,9 @@ import SettingsStore from "../../settings/SettingsStore";
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
import { Layout } from "../../settings/enums/Layout";
import EventTile, {
type EventTileHandle,
type GetRelationsForEvent,
type IReadReceiptProps,
isEligibleForSpecialReceipt,
type UnwrappedEventTile,
type ReadReceiptProps,
} from "../views/rooms/EventTile";
import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer";
import defaultDispatcher from "../../dispatcher/dispatcher";
Expand All @@ -49,7 +48,7 @@ import type EditorStateTransfer from "../../utils/EditorStateTransfer";
import { Action } from "../../dispatcher/actions";
import { getEventDisplayInfo } from "../../utils/EventRenderingUtils";
import { type IReadReceiptPosition } from "../views/rooms/ReadReceiptMarker";
import { haveRendererForEvent } from "../../events/EventTileFactory";
import { haveRendererForEvent, isMessageEvent } from "../../events/EventTileFactory";
import { editorRoomKey } from "../../Editing";
import { hasThreadSummary } from "../../utils/EventUtils";
import { type BaseGrouper } from "./grouper/BaseGrouper";
Expand Down Expand Up @@ -219,7 +218,7 @@ interface IState {

interface IReadReceiptForUser {
lastShownEventId: string;
receipt: IReadReceiptProps;
receipt: ReadReceiptProps;
}

/* (almost) stateless UI component which builds the event tiles in the room timeline.
Expand Down Expand Up @@ -248,7 +247,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
// This is recomputed on each render. It's only stored on the component
// for ease of passing the data around since it's computed in one pass
// over all events.
private readReceiptsByEvent: Map<string, IReadReceiptProps[]> = new Map();
private readReceiptsByEvent: Map<string, ReadReceiptProps[]> = new Map();

// Track read receipts by user ID. For each user ID we've ever shown a
// a read receipt for, we store an object:
Expand Down Expand Up @@ -277,7 +276,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
public scrollPanel = createRef<ScrollPanel>();

private showTypingNotificationsWatcherRef?: string;
private eventTiles: Record<string, UnwrappedEventTile> = {};
private eventTiles: Record<string, EventTileHandle> = {};

// A map to allow groupers to maintain consistent keys even if their first event is uprooted due to back-pagination.
public grouperKeyMap = new WeakMap<MatrixEvent, string>();
Expand Down Expand Up @@ -368,14 +367,10 @@ export default class MessagePanel extends React.Component<IProps, IState> {

/* get the DOM node representing the given event */
public getNodeForEventId(eventId: string): HTMLElement | undefined {
if (!this.eventTiles) {
return undefined;
}

return this.eventTiles[eventId]?.ref?.current ?? undefined;
return this.getTileForEventId(eventId)?.ref?.current ?? undefined;
}

public getTileForEventId(eventId?: string): UnwrappedEventTile | undefined {
public getTileForEventId(eventId?: string): EventTileHandle | undefined {
if (!this.eventTiles || !eventId) {
return undefined;
}
Expand Down Expand Up @@ -619,6 +614,10 @@ export default class MessagePanel extends React.Component<IProps, IState> {
return !status || status === EventStatus.SENT;
}

private isEligibleForSpecialReceipt(event: MatrixEvent): boolean {
return isMessageEvent(event) || event.getType() === EventType.RoomMessageEncrypted;
}

private getEventTiles(): ReactNode[] {
// first figure out which is the last event in the list which we're
// actually going to show; this allows us to behave slightly
Expand Down Expand Up @@ -646,7 +645,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
lastShownEvent = event;
}

if (!foundLastSuccessfulEvent && this.isSentState(event) && isEligibleForSpecialReceipt(event)) {
if (!foundLastSuccessfulEvent && this.isSentState(event) && this.isEligibleForSpecialReceipt(event)) {
foundLastSuccessfulEvent = true;
// If we are not sender of this last successful event eligible for special receipt then we stop here
// As we do not want to render our sent receipt if there are more receipts below it and events sent
Expand Down Expand Up @@ -869,7 +868,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {

// Get a list of read receipts that should be shown next to this event
// Receipts are objects which have a 'userId', 'roomMember' and 'ts'.
private getReadReceiptsForEvent(event: MatrixEvent): IReadReceiptProps[] | null {
private getReadReceiptsForEvent(event: MatrixEvent): ReadReceiptProps[] | null {
const myUserId = MatrixClientPeg.safeGet().credentials.userId;

// get list of read receipts, sorted most recent first
Expand All @@ -880,7 +879,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {

const receiptDestination = this.context.threadId ? room.getThread(this.context.threadId) : room;

const receipts: IReadReceiptProps[] = [];
const receipts: ReadReceiptProps[] = [];

if (!receiptDestination) {
logger.debug(
Expand Down Expand Up @@ -909,8 +908,8 @@ export default class MessagePanel extends React.Component<IProps, IState> {
// Get an object that maps from event ID to a list of read receipts that
// should be shown next to that event. If a hidden event has read receipts,
// they are folded into the receipts of the last shown event.
private getReadReceiptsByShownEvent(events: WrappedEvent[]): Map<string, IReadReceiptProps[]> {
const receiptsByEvent: Map<string, IReadReceiptProps[]> = new Map();
private getReadReceiptsByShownEvent(events: WrappedEvent[]): Map<string, ReadReceiptProps[]> {
const receiptsByEvent: Map<string, ReadReceiptProps[]> = new Map();
const receiptsByUserId: Map<string, IReadReceiptForUser> = new Map();

let lastShownEventId: string | undefined;
Expand Down Expand Up @@ -965,7 +964,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
return receiptsByEvent;
}

private collectEventTile = (eventId: string, node: UnwrappedEventTile): void => {
private readonly collectEventTile = (eventId: string, node: EventTileHandle): void => {
this.eventTiles[eventId] = node;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import ErrorBoundary from "../views/elements/ErrorBoundary";
import RoomHeader from "../views/rooms/RoomHeader/RoomHeader.tsx";
import ScrollPanel from "./ScrollPanel";
import NewRoomIntro from "../views/rooms/NewRoomIntro";
import { UnwrappedEventTile } from "../views/rooms/EventTile";
import EventTile from "../views/rooms/EventTile";
import { _t } from "../../languageHandler";
import SdkConfig from "../../SdkConfig";
import { useScopedRoomContext } from "../../contexts/ScopedRoomContext.tsx";
Expand Down Expand Up @@ -50,7 +50,7 @@ export const WaitingForThirdPartyRoomView: React.FC<Props> = ({ roomView, resize
subtitle={_t("room|waiting_for_join_subtitle", { brand })}
/>
<NewRoomIntro />
<UnwrappedEventTile mxEvent={inviteEvent} />
<EventTile mxEvent={inviteEvent} withErrorBoundary={false} />
</ScrollPanel>
</div>
</main>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContex
import EndPollDialog from "../dialogs/EndPollDialog";
import { isPollEnded } from "../messages/MPollBody";
import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { type GetRelationsForEvent, type IEventTileOps } from "../rooms/EventTile";
import { type EventTileOps, type GetRelationsForEvent } from "../rooms/EventTile";
import { type OpenForwardDialogPayload } from "../../../dispatcher/payloads/OpenForwardDialogPayload";
import { type OpenReportEventDialogPayload } from "../../../dispatcher/payloads/OpenReportEventDialogPayload";
import { createMapSiteLinkFromEvent } from "../../../utils/location";
Expand Down Expand Up @@ -115,7 +115,7 @@ interface IProps extends MenuProps {
/* the MatrixEvent associated with the context menu */
mxEvent: MatrixEvent;
// An optional EventTileOps implementation that can be used to unhide preview widgets
eventTileOps?: IEventTileOps;
eventTileOps?: EventTileOps;
// Callback called when the menu is dismissed
permalinkCreator?: RoomPermalinkCreator;
/* an optional function to be called when the user clicks collapse thread, if not provided hide button */
Expand Down Expand Up @@ -289,7 +289,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
};

private onUnhidePreviewClick = (): void => {
this.props.eventTileOps?.unhideWidget();
this.props.eventTileOps?.unhideWidget?.();
this.closeMenu();
};

Expand Down Expand Up @@ -488,7 +488,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
);

let unhidePreviewButton: JSX.Element | undefined;
if (eventTileOps?.isWidgetHidden()) {
if (eventTileOps?.isWidgetHidden?.()) {
unhidePreviewButton = (
<IconizedContextMenuOption
icon={<VisibilityOnIcon />}
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/components/views/messages/MessageEvent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import MPollBody from "./MPollBody";
import MLocationBody from "./MLocationBody";
import MjolnirBody from "./MjolnirBody";
import MBeaconBody from "./MBeaconBody";
import { type GetRelationsForEvent, type IEventTileOps } from "../rooms/EventTile";
import { type EventTileOps, type GetRelationsForEvent } from "../rooms/EventTile";
import {
DecryptionFailureBodyFactory,
FileBodyFactory,
Expand All @@ -60,7 +60,7 @@ interface IProps extends Omit<IBodyProps, "onMessageAllowed" | "mediaEventHelper
}

export interface IOperableEventTile {
getEventTileOps(): IEventTileOps | null;
getEventTileOps(): EventTileOps | null;
}

const baseBodyTypes = new Map<string, React.ComponentType<IBodyProps>>([
Expand Down Expand Up @@ -126,7 +126,7 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
}
}

public getEventTileOps = (): IEventTileOps | null => {
public getEventTileOps = (): EventTileOps | null => {
return (this.body.current as IOperableEventTile)?.getEventTileOps?.() || null;
};

Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/views/messages/TextualBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import RoomContext from "../../../contexts/RoomContext";
import AccessibleButton from "../elements/AccessibleButton";
import { getParentEventId } from "../../../utils/Reply";
import { EditWysiwygComposer } from "../rooms/wysiwyg_composer";
import { type IEventTileOps } from "../rooms/EventTile";
import { type EventTileOps } from "../rooms/EventTile";
import { UrlPreviewGroupViewModel } from "../../../viewmodels/message-body/UrlPreviewGroupViewModel.ts";
import { useMediaVisible } from "../../../hooks/useMediaVisible.ts";
import ImageView from "../elements/ImageView.tsx";
Expand Down Expand Up @@ -175,7 +175,7 @@ class InnerTextualBody extends React.Component<Props> {
}
};

public getEventTileOps = (): IEventTileOps => ({
public getEventTileOps = (): EventTileOps => ({
isWidgetHidden: () => {
// This controls whether the Show preview button is visibile.
return this.props.urlPreviewViewModel.isPreviewHiddenByUser;
Expand Down
Loading
Loading