From b5a922a43ca191405d8fd1574e5573da66462e26 Mon Sep 17 00:00:00 2001 From: 7w1 Date: Sun, 5 Jul 2026 15:44:11 -0500 Subject: [PATCH] support searching via nicknames --- .changeset/search-nicknames-autocomplete.md | 5 +++++ .../autocomplete/UserMentionAutocomplete.tsx | 9 ++++++--- .../common-settings/members/Members.tsx | 10 ++++++++-- src/app/features/room/MembersDrawer.tsx | 8 ++++++-- src/app/utils/room.ts | 19 ++++++++++++++----- 5 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 .changeset/search-nicknames-autocomplete.md diff --git a/.changeset/search-nicknames-autocomplete.md b/.changeset/search-nicknames-autocomplete.md new file mode 100644 index 000000000..7304462df --- /dev/null +++ b/.changeset/search-nicknames-autocomplete.md @@ -0,0 +1,5 @@ +--- +default: patch +--- + +Add support for searching users via nicknames in the autocomplete menu. diff --git a/src/app/components/editor/autocomplete/UserMentionAutocomplete.tsx b/src/app/components/editor/autocomplete/UserMentionAutocomplete.tsx index d378fa45e..194b080d7 100644 --- a/src/app/components/editor/autocomplete/UserMentionAutocomplete.tsx +++ b/src/app/components/editor/autocomplete/UserMentionAutocomplete.tsx @@ -1,5 +1,5 @@ import type { KeyboardEvent as ReactKeyboardEvent } from 'react'; -import { useEffect } from 'react'; +import { useCallback, useEffect } from 'react'; import type { Editor } from 'slate'; import { Avatar, MenuItem, Text } from 'folds'; import { userFallbackIcon } from '$components/icons/phosphor'; @@ -86,8 +86,6 @@ const SEARCH_OPTIONS: UseAsyncSearchOptions = { }; const mxIdToName = (mxId: string) => getMxIdLocalPart(mxId) ?? mxId; -const getRoomMemberStr: SearchItemStrGetter = (m, query) => - getMemberSearchStr(m, query, mxIdToName); export function UserMentionAutocomplete({ room, @@ -102,6 +100,11 @@ export function UserMentionAutocomplete({ const roomAliasOrId = room.getCanonicalAlias() || roomId; const members = useRoomMembers(mx, roomId); + const getRoomMemberStr = useCallback>( + (m, searchQuery) => getMemberSearchStr(m, searchQuery, mxIdToName, nicknames), + [nicknames] + ); + const [result, search, resetSearch] = useAsyncSearch(members, getRoomMemberStr, SEARCH_OPTIONS); const autoCompleteMembers = (result ? result.items.slice(0, 20) : members.slice(0, 20)).filter( withAllowedMembership diff --git a/src/app/features/common-settings/members/Members.tsx b/src/app/features/common-settings/members/Members.tsx index a091bae09..27de106a1 100644 --- a/src/app/features/common-settings/members/Members.tsx +++ b/src/app/features/common-settings/members/Members.tsx @@ -42,6 +42,8 @@ import { useFlattenPowerTagMembers, useGetMemberPowerTag } from '$hooks/useMembe import { useRoomCreators } from '$hooks/useRoomCreators'; import { getMouseEventCords } from '$utils/dom'; import { getMxIdServer } from '$utils/mxIdHelper'; +import { useAtomValue } from 'jotai'; +import { nicknamesAtom } from '$state/nicknames'; const SEARCH_OPTIONS: UseAsyncSearchOptions = { limit: 1000, @@ -54,14 +56,13 @@ const SEARCH_OPTIONS: UseAsyncSearchOptions = { }; const mxIdToName = (mxId: string) => getMxIdLocalPart(mxId) ?? mxId; -const getRoomMemberStr: SearchItemStrGetter = (m, query) => - getMemberSearchStr(m, query, mxIdToName); type MembersProps = { requestClose: () => void; }; export function Members({ requestClose }: MembersProps) { const mx = useMatrixClient(); + const nicknames = useAtomValue(nicknamesAtom); const useAuthentication = useMediaAuthentication(); const room = useRoom(); const members = useRoomMembers(mx, room.roomId); @@ -94,6 +95,11 @@ export function Members({ requestClose }: MembersProps) { [members, membershipFilter, memberSort, memberPowerSort] ); + const getRoomMemberStr = useCallback>( + (m, query) => getMemberSearchStr(m, query, mxIdToName, nicknames), + [nicknames] + ); + const [result, search, resetSearch] = useAsyncSearch( sortedMembers, getRoomMemberStr, diff --git a/src/app/features/room/MembersDrawer.tsx b/src/app/features/room/MembersDrawer.tsx index 0e930730c..4a375befa 100644 --- a/src/app/features/room/MembersDrawer.tsx +++ b/src/app/features/room/MembersDrawer.tsx @@ -233,8 +233,6 @@ const SEARCH_OPTIONS: UseAsyncSearchOptions = { }; const mxIdToName = (mxId: string) => getMxIdLocalPart(mxId) ?? mxId; -const getRoomMemberStr: SearchItemStrGetter = (m, query) => - getMemberSearchStr(m, query, mxIdToName); type MembersDrawerProps = { room: Room; @@ -242,6 +240,7 @@ type MembersDrawerProps = { }; export function MembersDrawer({ room, members }: MembersDrawerProps) { const mx = useMatrixClient(); + const nicknames = useAtomValue(nicknamesAtom); const useAuthentication = useMediaAuthentication(); const scrollRef = useRef(null); const searchInputRef = useRef(null); @@ -273,6 +272,11 @@ export function MembersDrawer({ room, members }: MembersDrawerProps) { [members, membershipFilter, memberSort, memberPowerSort] ); + const getRoomMemberStr = useCallback>( + (m, query) => getMemberSearchStr(m, query, mxIdToName, nicknames), + [nicknames] + ); + const [result, search, resetSearch] = useAsyncSearch( filteredMembers, getRoomMemberStr, diff --git a/src/app/utils/room.ts b/src/app/utils/room.ts index 6d201f34a..381739cd1 100644 --- a/src/app/utils/room.ts +++ b/src/app/utils/room.ts @@ -595,11 +595,20 @@ export const getMemberDisplayName = ( export const getMemberSearchStr = ( member: RoomMember, query: string, - mxIdToName: (mxId: string) => string -): string[] => [ - member.rawDisplayName === member.userId ? mxIdToName(member.userId) : member.rawDisplayName, - query.startsWith('@') || query.indexOf(':') > -1 ? member.userId : mxIdToName(member.userId), -]; + mxIdToName: (mxId: string) => string, + nicknames?: Record +): string[] => { + const nickname = nicknames?.[member.userId]; + const displayName = + member.rawDisplayName === member.userId ? mxIdToName(member.userId) : member.rawDisplayName; + const idStr = + query.startsWith('@') || query.indexOf(':') > -1 ? member.userId : mxIdToName(member.userId); + + const strings: string[] = []; + if (nickname) strings.push(nickname); + strings.push(displayName, idStr); + return strings; +}; export const getMemberAvatarMxc = (room: Room, userId: string): string | undefined => { const member = room.getMember(userId);