diff --git a/src/app/hooks/useRoomMeta.ts b/src/app/hooks/useRoomMeta.ts index 086c3a56..d9920557 100644 --- a/src/app/hooks/useRoomMeta.ts +++ b/src/app/hooks/useRoomMeta.ts @@ -1,9 +1,54 @@ import { useEffect, useState } from 'react'; import { RoomJoinRulesEventContent } from 'matrix-js-sdk/lib/types'; -import { Room, RoomEvent, RoomEventHandlerMap } from 'matrix-js-sdk'; +import { + Room, + RoomEvent, + RoomEventHandlerMap, + RoomStateEvent, +} from 'matrix-js-sdk'; import { StateEvent } from '../../types/matrix/room'; import { useStateEvent } from './useStateEvent'; +// matrix-js-sdk's `Room.name` for a DM with no explicit `m.room.name` +// event falls back to a disambiguated peer label and, in many SDK +// versions, suffixes the peer's MXID as `Display Name (@id:server)` — +// either always or whenever a display-name collision is suspected. That +// produces the «Alex (@test6:vojo.chat)» pattern users see in the DM +// list, chat header and room intro even though no two members share +// the same display name. The handle is already shown as a separate +// subline in the chat header, so the parenthetical is pure noise. +// +// Workaround: when the room has no explicit `m.room.name` and is a 1:1 +// with exactly one other joined/invited member, take the peer's raw +// display name directly (`RoomMember.rawDisplayName` is the value from +// `m.room.member.content.displayname` without SDK disambiguation). +// Fall back to the local-part of the MXID, then the bare MXID. Group +// rooms and rooms with an explicit name keep the SDK output. +const resolveRoomName = (room: Room): string => { + const explicit = room.currentState.getStateEvents(StateEvent.RoomName, ''); + if (explicit) return room.name; + + const myUserId = room.client.getUserId(); + if (myUserId) { + const peers = room + .getMembersWithMembership('join') + .concat(room.getMembersWithMembership('invite')) + .filter((m) => m.userId !== myUserId); + if (peers.length === 1) { + const peer = peers[0]; + if (peer.rawDisplayName && peer.rawDisplayName.trim()) { + return peer.rawDisplayName.trim(); + } + const local = peer.userId.startsWith('@') + ? peer.userId.slice(1).split(':')[0] + : peer.userId; + return local || peer.userId; + } + } + + return room.name; +}; + export const useRoomAvatar = (room: Room, dm?: boolean): string | undefined => { const avatarEvent = useStateEvent(room, StateEvent.RoomAvatar); @@ -17,17 +62,23 @@ export const useRoomAvatar = (room: Room, dm?: boolean): string | undefined => { }; export const useRoomName = (room: Room): string => { - const [name, setName] = useState(room.name); + const [name, setName] = useState(() => resolveRoomName(room)); useEffect(() => { - setName(room.name); + setName(resolveRoomName(room)); - const handleRoomNameChange: RoomEventHandlerMap[RoomEvent.Name] = () => { - setName(room.name); + const recompute: RoomEventHandlerMap[RoomEvent.Name] = () => { + setName(resolveRoomName(room)); }; - room.on(RoomEvent.Name, handleRoomNameChange); + // RoomEvent.Name fires when m.room.name changes; + // RoomStateEvent.Members fires on every m.room.member event, + // covering both display-name updates of the peer and membership + // flips that could change which member counts as the DM peer. + room.on(RoomEvent.Name, recompute); + room.currentState.on(RoomStateEvent.Members, recompute); return () => { - room.removeListener(RoomEvent.Name, handleRoomNameChange); + room.removeListener(RoomEvent.Name, recompute); + room.currentState.removeListener(RoomStateEvent.Members, recompute); }; }, [room]);