mirror of
https://github.com/Novaloop-AG/healthchat-web.git
synced 2026-11-07 23:18:21 +00:00
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. */
|
/** called when the event is edited after we show it. */
|
||||||
private readonly onReplaced = (): void => {
|
private readonly onReplaced = (): void => {
|
||||||
|
this.forceUpdate();
|
||||||
// re-verify the event if it is replaced (the edit may not be verified)
|
// re-verify the event if it is replaced (the edit may not be verified)
|
||||||
this.verifyEvent();
|
this.verifyEvent();
|
||||||
};
|
};
|
||||||
@@ -995,6 +996,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
|||||||
public render(): ReactNode {
|
public render(): ReactNode {
|
||||||
const msgtype = this.props.mxEvent.getContent().msgtype;
|
const msgtype = this.props.mxEvent.getContent().msgtype;
|
||||||
const eventType = this.props.mxEvent.getType();
|
const eventType = this.props.mxEvent.getType();
|
||||||
|
const replacingEventId = this.props.mxEvent.replacingEventId();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
hasRenderer,
|
hasRenderer,
|
||||||
@@ -1335,6 +1337,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
|||||||
|
|
||||||
// overrides
|
// overrides
|
||||||
ref: this.tile,
|
ref: this.tile,
|
||||||
|
replacingEventId,
|
||||||
isSeeingThroughMessageHiddenForModeration,
|
isSeeingThroughMessageHiddenForModeration,
|
||||||
|
|
||||||
// appease TS
|
// 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.
|
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 parse from "html-react-parser";
|
||||||
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
|
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
|
||||||
import {
|
import {
|
||||||
@@ -169,6 +176,8 @@ export class EventContentBodyViewModel
|
|||||||
extends BaseViewModel<EventContentBodyViewSnapshot, EventContentBodyViewModelProps>
|
extends BaseViewModel<EventContentBodyViewSnapshot, EventContentBodyViewModelProps>
|
||||||
implements EventContentBodyViewModelInterface
|
implements EventContentBodyViewModelInterface
|
||||||
{
|
{
|
||||||
|
private watchedEvent?: MatrixEvent;
|
||||||
|
|
||||||
private static readonly computeBodySnapshot = (
|
private static readonly computeBodySnapshot = (
|
||||||
props: EventContentBodyViewModelProps,
|
props: EventContentBodyViewModelProps,
|
||||||
): Pick<EventContentBodyViewSnapshot, "body" | "formattedBody" | "className"> => {
|
): Pick<EventContentBodyViewSnapshot, "body" | "formattedBody" | "className"> => {
|
||||||
@@ -226,6 +235,8 @@ export class EventContentBodyViewModel
|
|||||||
};
|
};
|
||||||
|
|
||||||
super(propsWithSettingDefaults, EventContentBodyViewModel.computeSnapshot(propsWithSettingDefaults));
|
super(propsWithSettingDefaults, EventContentBodyViewModel.computeSnapshot(propsWithSettingDefaults));
|
||||||
|
this.watchEvent(propsWithSettingDefaults.mxEvent);
|
||||||
|
this.disposables.track(this.unwatchEvent);
|
||||||
|
|
||||||
const enableBigEmojiWatcherRef = SettingsStore.watchSetting(
|
const enableBigEmojiWatcherRef = SettingsStore.watchSetting(
|
||||||
"TextualBody.enableBigEmoji",
|
"TextualBody.enableBigEmoji",
|
||||||
@@ -246,7 +257,26 @@ export class EventContentBodyViewModel
|
|||||||
this.disposables.track(() => SettingsStore.unwatchSetting(shouldShowPillAvatarWatcherRef));
|
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 => {
|
public setEventContent = (mxEvent: MatrixEvent | undefined, content: IContent): void => {
|
||||||
|
this.watchEvent(mxEvent);
|
||||||
this.props.mxEvent = mxEvent;
|
this.props.mxEvent = mxEvent;
|
||||||
this.props.content = content;
|
this.props.content = content;
|
||||||
const { body, formattedBody, className } = EventContentBodyViewModel.computeBodySnapshot(this.props);
|
const { body, formattedBody, className } = EventContentBodyViewModel.computeBodySnapshot(this.props);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { type MouseEvent } from "react";
|
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 {
|
import {
|
||||||
BaseViewModel,
|
BaseViewModel,
|
||||||
LINKIFIED_DATA_ATTRIBUTE,
|
LINKIFIED_DATA_ATTRIBUTE,
|
||||||
@@ -60,6 +60,8 @@ export class TextualBodyViewModel
|
|||||||
extends BaseViewModel<TextualBodyViewSnapshot, TextualBodyViewModelProps>
|
extends BaseViewModel<TextualBodyViewSnapshot, TextualBodyViewModelProps>
|
||||||
implements TextualBodyViewModelInterface
|
implements TextualBodyViewModelInterface
|
||||||
{
|
{
|
||||||
|
private watchedEvent?: MatrixEvent;
|
||||||
|
|
||||||
private static readonly getKind = (mxEvent: MatrixEvent): TextualBodyViewKind => {
|
private static readonly getKind = (mxEvent: MatrixEvent): TextualBodyViewKind => {
|
||||||
const msgtype = mxEvent.getContent().msgtype as MsgType | undefined;
|
const msgtype = mxEvent.getContent().msgtype as MsgType | undefined;
|
||||||
|
|
||||||
@@ -120,7 +122,8 @@ export class TextualBodyViewModel
|
|||||||
| "editedMarkerTooltip"
|
| "editedMarkerTooltip"
|
||||||
| "editedMarkerCaption"
|
| "editedMarkerCaption"
|
||||||
> => {
|
> => {
|
||||||
if (!props.replacingEventId) {
|
const replacingEventId = props.replacingEventId ?? props.mxEvent.replacingEventId();
|
||||||
|
if (!replacingEventId) {
|
||||||
return {
|
return {
|
||||||
showEditedMarker: false,
|
showEditedMarker: false,
|
||||||
editedMarkerText: undefined,
|
editedMarkerText: undefined,
|
||||||
@@ -197,8 +200,26 @@ export class TextualBodyViewModel
|
|||||||
|
|
||||||
public constructor(props: TextualBodyViewModelProps) {
|
public constructor(props: TextualBodyViewModelProps) {
|
||||||
super(props, TextualBodyViewModel.computeSnapshot(props));
|
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 {
|
public setId(id: string | undefined): void {
|
||||||
this.props = {
|
this.props = {
|
||||||
...this.props,
|
...this.props,
|
||||||
@@ -213,6 +234,7 @@ export class TextualBodyViewModel
|
|||||||
...this.props,
|
...this.props,
|
||||||
mxEvent,
|
mxEvent,
|
||||||
};
|
};
|
||||||
|
this.watchEvent(mxEvent);
|
||||||
|
|
||||||
this.snapshot.merge(TextualBodyViewModel.computeEventSnapshot(this.props));
|
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 React, { type ComponentProps } from "react";
|
||||||
import { type MatrixClient, type MatrixEvent, PushRuleKind, type Room } from "matrix-js-sdk/src/matrix";
|
import { type MatrixClient, type MatrixEvent, PushRuleKind, type Room } from "matrix-js-sdk/src/matrix";
|
||||||
import { mocked, type MockedObject } from "jest-mock";
|
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 { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -174,6 +174,44 @@ describe("<TextualBody />", () => {
|
|||||||
expect(container).toHaveTextContent("* sender winks(edited)");
|
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", () => {
|
it("renders m.notice correctly", () => {
|
||||||
DMRoomMap.makeShared(defaultMatrixClient);
|
DMRoomMap.makeShared(defaultMatrixClient);
|
||||||
|
|
||||||
|
|||||||
@@ -214,6 +214,35 @@ describe("EventTile", () => {
|
|||||||
expect(screen.getByText("Can't load this message (m.room.message)")).toBeInTheDocument();
|
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", () => {
|
describe("EventTile in the right panel", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user