diff --git a/.changeset/fix-pmp-collapsing.md b/.changeset/fix-pmp-collapsing.md new file mode 100644 index 000000000..18ce0539c --- /dev/null +++ b/.changeset/fix-pmp-collapsing.md @@ -0,0 +1,5 @@ +--- +default: patch +--- + +Fix per-message profile messages collapsing together when different profiles are used. diff --git a/.changeset/fix-pmp-edit-render.md b/.changeset/fix-pmp-edit-render.md new file mode 100644 index 000000000..433966f89 --- /dev/null +++ b/.changeset/fix-pmp-edit-render.md @@ -0,0 +1,5 @@ +--- +default: patch +--- + +Fix per-message profiles not updating avatar/name if edit events are recieved. diff --git a/.changeset/fix-pmp-encrypted-rooms.md b/.changeset/fix-pmp-encrypted-rooms.md new file mode 100644 index 000000000..9bfd9f532 --- /dev/null +++ b/.changeset/fix-pmp-encrypted-rooms.md @@ -0,0 +1,5 @@ +--- +default: patch +--- + +Fix per-message profiles not rendering in encrypted rooms. diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx index 909b0ab0b..8534d6b7c 100644 --- a/src/app/features/room/message/Message.tsx +++ b/src/app/features/room/message/Message.tsx @@ -49,7 +49,7 @@ import { Username, UsernameBold, } from '$components/message'; -import { canEditEvent, getEventEdits, getMemberAvatarMxc } from '$utils/room'; +import { canEditEvent, getEditedEvent, getEventEdits, getMemberAvatarMxc } from '$utils/room'; import { mxcUrlToHttp } from '$utils/matrix'; import { getSettings, MessageLayout, MessageSpacing, settingsAtom } from '$state/settings'; import { nicknamesAtom, setNicknameAtom } from '$state/nicknames'; @@ -380,18 +380,38 @@ function MessageInternal( const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); + const [editVersion, setEditVersion] = useState(0); + + useEffect(() => { + const onReplaced = () => setEditVersion((v) => v + 1); + mEvent.on('Event.replaced' as any, onReplaced); + return () => { + mEvent.off('Event.replaced' as any, onReplaced); + }; + }, [mEvent]); + /** * We read the per-message profile from the event content here. * We have to do this in the message component because the per-message profile can be different for each message, and we need to read it for each message individually. * We also want to avoid reading and parsing the per-message profile in a parent component like the timeline, because that would be inefficient and would cause unnecessary re-renders of the entire timeline whenever a per-message profile changes. */ - const pmp: PerMessageProfileBeeperFormat | undefined = useMemo( - () => - mEvent.event.content?.['com.beeper.per_message_profile'] as - | PerMessageProfileBeeperFormat - | undefined, - [mEvent] - ); + const pmp: PerMessageProfileBeeperFormat | undefined = useMemo(() => { + const evtId = mEvent.getId(); + const evtTimeline = evtId ? room.getTimelineForEvent(evtId) : undefined; + const editedEvent = + evtTimeline && evtId + ? getEditedEvent(evtId, mEvent, evtTimeline.getTimelineSet()) + : undefined; + + const resolvedContent = editedEvent + ? editedEvent.getContent()['m.new_content'] + : mEvent.getContent(); + + return resolvedContent?.['com.beeper.per_message_profile'] as + | PerMessageProfileBeeperFormat + | undefined; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [mEvent, room, editVersion]); /** * We convert the per-message profile from the Beeper format to our internal format here in the message component diff --git a/src/app/hooks/timeline/useProcessedTimeline.ts b/src/app/hooks/timeline/useProcessedTimeline.ts index d4ed3b129..f1f799308 100644 --- a/src/app/hooks/timeline/useProcessedTimeline.ts +++ b/src/app/hooks/timeline/useProcessedTimeline.ts @@ -143,8 +143,15 @@ export function useProcessedTimeline({ const typeMatch = normalizeMessageType(getPrevType.call(prevEvent)) === normalizeMessageType(type); const dividerOk = !newDivider || eventSender === mxUserId; - - collapsed = dividerOk && senderMatch && typeMatch && withinTimeThreshold; + const getPmpId = (ev: MatrixEvent): string | null => + ev.getContent()?.['com.beeper.per_message_profile']?.id ?? null; + + collapsed = + dividerOk && + senderMatch && + typeMatch && + withinTimeThreshold && + getPmpId(prevEvent) === getPmpId(mEvent); } else { const prevIsMessageEvent = MESSAGE_EVENT_TYPES.includes(getPrevType.call(prevEvent)); collapsed = !prevIsMessageEvent;