feat: personal room name overrides (MSC4431-style)

Users can right-click any room and 'Rename for me...' to set a local
display name visible only to them. Stored in account data under
io.lotus.room_names. Shows a pencil indicator on renamed rooms.
useLocalRoomName() hook overrides useRoomName() when a local name exists.

Also includes:
- Rich room topic rendering via RoomTopicContent object (formatted_body
  support in RoomTopicViewer with HTML sanitization via sanitizeCustomHtml)
- Edit history viewer: clicking '(edited)' on a message opens a modal
  showing all prior versions with timestamps (EditHistoryModal.tsx)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 17:21:11 -04:00
co-authored by Claude Sonnet 4.6
parent 134ebb231d
commit 3a72b7c1c5
13 changed files with 581 additions and 53 deletions
+15
View File
@@ -128,6 +128,7 @@ import { useAccessiblePowerTagColors, useGetMemberPowerTag } from '../../hooks/u
import { useTheme } from '../../hooks/useTheme';
import { useRoomCreatorsTag } from '../../hooks/useRoomCreatorsTag';
import { usePowerLevelTags } from '../../hooks/usePowerLevelTags';
import { EditHistoryModal } from './message/EditHistoryModal';
const TimelineFloat = as<'div', css.TimelineFloatVariants>(
({ position, className, ...props }, ref) => (
@@ -480,6 +481,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
const canSendReaction = permissions.event(MessageEvent.Reaction, mx.getSafeUserId());
const canPinEvent = permissions.stateEvent(StateEvent.RoomPinnedEvents, mx.getSafeUserId());
const [editId, setEditId] = useState<string>();
const [editHistoryEvent, setEditHistoryEvent] = useState<MatrixEvent | undefined>();
const roomToParents = useAtomValue(roomToParentsAtom);
const unread = useRoomUnread(room.roomId, roomToUnreadAtom);
@@ -1123,6 +1125,9 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
msgType={mEvent.getContent().msgtype ?? ''}
ts={mEvent.getTs()}
edited={!!editedEvent}
onEditHistoryClick={
editedEvent ? () => setEditHistoryEvent(mEvent) : undefined
}
getContent={getContent}
mediaAutoLoad={mediaAutoLoad}
urlPreview={showUrlPreview}
@@ -1229,6 +1234,9 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
msgType={mEvent.getContent().msgtype ?? ''}
ts={mEvent.getTs()}
edited={!!editedEvent}
onEditHistoryClick={
editedEvent ? () => setEditHistoryEvent(mEvent) : undefined
}
getContent={getContent}
mediaAutoLoad={mediaAutoLoad}
urlPreview={showUrlPreview}
@@ -2191,6 +2199,13 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
</TimelineFloat>
)}
</Box>
{editHistoryEvent && (
<EditHistoryModal
room={room}
mEvent={editHistoryEvent}
onClose={() => setEditHistoryEvent(undefined)}
/>
)}
</ReadPositionsContext.Provider>
);
}