Fix msgtype changes from edits not rerendering timeline tiles (#33416)
* Fix msgtype changes from edits not rerendering timeline tiles * Change approach to view model listeners instead * Repaint the mounted tile on MatrixEventEvent.Replaced
This commit is contained in:
@@ -620,6 +620,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
|
||||
/** called when the event is edited after we show it. */
|
||||
private readonly onReplaced = (): void => {
|
||||
this.forceUpdate();
|
||||
// re-verify the event if it is replaced (the edit may not be verified)
|
||||
this.verifyEvent();
|
||||
};
|
||||
@@ -995,6 +996,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
public render(): ReactNode {
|
||||
const msgtype = this.props.mxEvent.getContent().msgtype;
|
||||
const eventType = this.props.mxEvent.getType();
|
||||
const replacingEventId = this.props.mxEvent.replacingEventId();
|
||||
|
||||
const {
|
||||
hasRenderer,
|
||||
@@ -1335,6 +1337,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
|
||||
// overrides
|
||||
ref: this.tile,
|
||||
replacingEventId,
|
||||
isSeeingThroughMessageHiddenForModeration,
|
||||
|
||||
// appease TS
|
||||
|
||||
@@ -5,7 +5,14 @@ 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 IContent, type MatrixClient, type MatrixEvent, MsgType, PushRuleKind } from "matrix-js-sdk/src/matrix";
|
||||
import {
|
||||
type IContent,
|
||||
type MatrixClient,
|
||||
type MatrixEvent,
|
||||
MatrixEventEvent,
|
||||
MsgType,
|
||||
PushRuleKind,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import parse from "html-react-parser";
|
||||
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
|
||||
import {
|
||||
@@ -169,6 +176,8 @@ export class EventContentBodyViewModel
|
||||
extends BaseViewModel<EventContentBodyViewSnapshot, EventContentBodyViewModelProps>
|
||||
implements EventContentBodyViewModelInterface
|
||||
{
|
||||
private watchedEvent?: MatrixEvent;
|
||||
|
||||
private static readonly computeBodySnapshot = (
|
||||
props: EventContentBodyViewModelProps,
|
||||
): Pick<EventContentBodyViewSnapshot, "body" | "formattedBody" | "className"> => {
|
||||
@@ -226,6 +235,8 @@ export class EventContentBodyViewModel
|
||||
};
|
||||
|
||||
super(propsWithSettingDefaults, EventContentBodyViewModel.computeSnapshot(propsWithSettingDefaults));
|
||||
this.watchEvent(propsWithSettingDefaults.mxEvent);
|
||||
this.disposables.track(this.unwatchEvent);
|
||||
|
||||
const enableBigEmojiWatcherRef = SettingsStore.watchSetting(
|
||||
"TextualBody.enableBigEmoji",
|
||||
@@ -246,7 +257,26 @@ export class EventContentBodyViewModel
|
||||
this.disposables.track(() => SettingsStore.unwatchSetting(shouldShowPillAvatarWatcherRef));
|
||||
}
|
||||
|
||||
private readonly onEventContentChanged = (): void => {
|
||||
const mxEvent = this.props.mxEvent;
|
||||
if (!mxEvent) return;
|
||||
this.setEventContent(mxEvent, mxEvent.getContent());
|
||||
};
|
||||
|
||||
private watchEvent(mxEvent?: MatrixEvent): void {
|
||||
if (this.watchedEvent === mxEvent) return;
|
||||
this.unwatchEvent();
|
||||
this.watchedEvent = mxEvent;
|
||||
this.watchedEvent?.on(MatrixEventEvent.Replaced, this.onEventContentChanged);
|
||||
}
|
||||
|
||||
private readonly unwatchEvent = (): void => {
|
||||
this.watchedEvent?.off(MatrixEventEvent.Replaced, this.onEventContentChanged);
|
||||
this.watchedEvent = undefined;
|
||||
};
|
||||
|
||||
public setEventContent = (mxEvent: MatrixEvent | undefined, content: IContent): void => {
|
||||
this.watchEvent(mxEvent);
|
||||
this.props.mxEvent = mxEvent;
|
||||
this.props.content = content;
|
||||
const { body, formattedBody, className } = EventContentBodyViewModel.computeBodySnapshot(this.props);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import React, { type MouseEvent } from "react";
|
||||
import { MsgType, type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { MatrixEventEvent, MsgType, type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import {
|
||||
BaseViewModel,
|
||||
LINKIFIED_DATA_ATTRIBUTE,
|
||||
@@ -60,6 +60,8 @@ export class TextualBodyViewModel
|
||||
extends BaseViewModel<TextualBodyViewSnapshot, TextualBodyViewModelProps>
|
||||
implements TextualBodyViewModelInterface
|
||||
{
|
||||
private watchedEvent?: MatrixEvent;
|
||||
|
||||
private static readonly getKind = (mxEvent: MatrixEvent): TextualBodyViewKind => {
|
||||
const msgtype = mxEvent.getContent().msgtype as MsgType | undefined;
|
||||
|
||||
@@ -120,7 +122,8 @@ export class TextualBodyViewModel
|
||||
| "editedMarkerTooltip"
|
||||
| "editedMarkerCaption"
|
||||
> => {
|
||||
if (!props.replacingEventId) {
|
||||
const replacingEventId = props.replacingEventId ?? props.mxEvent.replacingEventId();
|
||||
if (!replacingEventId) {
|
||||
return {
|
||||
showEditedMarker: false,
|
||||
editedMarkerText: undefined,
|
||||
@@ -197,8 +200,26 @@ export class TextualBodyViewModel
|
||||
|
||||
public constructor(props: TextualBodyViewModelProps) {
|
||||
super(props, TextualBodyViewModel.computeSnapshot(props));
|
||||
this.watchEvent(props.mxEvent);
|
||||
this.disposables.track(this.unwatchEvent);
|
||||
}
|
||||
|
||||
private readonly onEventContentChanged = (): void => {
|
||||
this.snapshot.merge(TextualBodyViewModel.computeEventSnapshot(this.props));
|
||||
};
|
||||
|
||||
private watchEvent(mxEvent: MatrixEvent): void {
|
||||
if (this.watchedEvent === mxEvent) return;
|
||||
this.unwatchEvent();
|
||||
this.watchedEvent = mxEvent;
|
||||
this.watchedEvent.on(MatrixEventEvent.Replaced, this.onEventContentChanged);
|
||||
}
|
||||
|
||||
private readonly unwatchEvent = (): void => {
|
||||
this.watchedEvent?.off(MatrixEventEvent.Replaced, this.onEventContentChanged);
|
||||
this.watchedEvent = undefined;
|
||||
};
|
||||
|
||||
public setId(id: string | undefined): void {
|
||||
this.props = {
|
||||
...this.props,
|
||||
@@ -213,6 +234,7 @@ export class TextualBodyViewModel
|
||||
...this.props,
|
||||
mxEvent,
|
||||
};
|
||||
this.watchEvent(mxEvent);
|
||||
|
||||
this.snapshot.merge(TextualBodyViewModel.computeEventSnapshot(this.props));
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
import React, { type ComponentProps } from "react";
|
||||
import { type MatrixClient, type MatrixEvent, PushRuleKind, type Room } from "matrix-js-sdk/src/matrix";
|
||||
import { mocked, type MockedObject } from "jest-mock";
|
||||
import { render, waitFor } from "jest-matrix-react";
|
||||
import { act, render, waitFor } from "jest-matrix-react";
|
||||
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
|
||||
|
||||
import {
|
||||
@@ -174,6 +174,44 @@ describe("<TextualBody />", () => {
|
||||
expect(container).toHaveTextContent("* sender winks(edited)");
|
||||
});
|
||||
|
||||
it("updates the body kind when an edit changes msgtype to m.emote", async () => {
|
||||
DMRoomMap.makeShared(defaultMatrixClient);
|
||||
|
||||
const ev = mkEvent({
|
||||
type: "m.room.message",
|
||||
room: room1Id,
|
||||
user: "sender",
|
||||
content: {
|
||||
body: "hello",
|
||||
msgtype: "m.text",
|
||||
},
|
||||
event: true,
|
||||
});
|
||||
|
||||
const { container } = getComponent({ mxEvent: ev });
|
||||
expect(container).toHaveTextContent("hello");
|
||||
|
||||
const edit = mkEvent({
|
||||
type: "m.room.message",
|
||||
room: room1Id,
|
||||
user: "sender",
|
||||
content: {
|
||||
"body": "* waves",
|
||||
"msgtype": "m.emote",
|
||||
"m.new_content": {
|
||||
body: "waves",
|
||||
msgtype: "m.emote",
|
||||
},
|
||||
},
|
||||
event: true,
|
||||
});
|
||||
act(() => {
|
||||
ev.makeReplaced(edit);
|
||||
});
|
||||
|
||||
await waitFor(() => expect(container).toHaveTextContent("* sender waves(edited)"));
|
||||
});
|
||||
|
||||
it("renders m.notice correctly", () => {
|
||||
DMRoomMap.makeShared(defaultMatrixClient);
|
||||
|
||||
|
||||
@@ -214,6 +214,35 @@ describe("EventTile", () => {
|
||||
expect(screen.getByText("Can't load this message (m.room.message)")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("updates msgtype-derived tile classes when an edit changes msgtype to m.emote", async () => {
|
||||
const { container } = getComponent();
|
||||
expect(container.querySelector(".mx_EventTile_emote")).toBeNull();
|
||||
|
||||
const edit = new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
room_id: ROOM_ID,
|
||||
sender: "@alice:example.org",
|
||||
content: {
|
||||
"body": "* waves",
|
||||
"msgtype": "m.emote",
|
||||
"m.new_content": {
|
||||
body: "waves",
|
||||
msgtype: "m.emote",
|
||||
},
|
||||
"m.relates_to": {
|
||||
rel_type: "m.replace",
|
||||
event_id: mxEvent.getId(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
act(() => {
|
||||
mxEvent.makeReplaced(edit);
|
||||
});
|
||||
|
||||
await waitFor(() => expect(container.querySelector(".mx_EventTile_emote")).not.toBeNull());
|
||||
});
|
||||
});
|
||||
|
||||
describe("EventTile in the right panel", () => {
|
||||
|
||||
Reference in New Issue
Block a user