diff --git a/client/shared/helper/converse-helper.ts b/client/shared/helper/converse-helper.ts index 5c07037b..3c916b8b 100644 --- a/client/shared/helper/converse-helper.ts +++ b/client/shared/helper/converse-helper.ts @@ -1,8 +1,9 @@ -import { isValidStr } from '..'; +import { getReduxStore, isValidStr } from '..'; import { getCachedConverseInfo, getCachedUserInfo } from '../cache/cache'; import { t } from '../i18n'; import type { ChatConverseInfo } from '../model/converse'; import { appendUserDMConverse } from '../model/user'; +import type { FriendInfo } from '../redux/slices/user'; /** * 确保私信会话存在 @@ -26,6 +27,22 @@ export async function ensureDMConverse( return converse; } +export function buildFriendNicknameMap( + friends: FriendInfo[] +): Record { + const friendNicknameMap: Record = friends.reduce( + (prev, curr) => { + return { + ...prev, + [curr.id]: curr.nickname, + }; + }, + {} + ); + + return friendNicknameMap; +} + /** * 获取私信会话的会话名 * @param userId 当前用户的ID(即自己) @@ -40,16 +57,26 @@ export async function getDMConverseName( } const otherConverseMembers = converse.members.filter((m) => m !== userId); // 成员Id - const len = otherConverseMembers.length; const otherMembersInfo = await Promise.all( otherConverseMembers.map((memberId) => getCachedUserInfo(memberId)) ); + const friends = getReduxStore().getState().user.friends; + const friendNicknameMap = buildFriendNicknameMap(friends); + + const memberNicknames = otherMembersInfo.map((m) => { + if (friendNicknameMap[m._id]) { + return friendNicknameMap[m._id]; + } + + return m.nickname ?? ''; + }); + const len = memberNicknames.length; if (len === 1) { - return otherMembersInfo[0]?.nickname ?? ''; + return memberNicknames[0] ?? ''; } else if (len === 2) { - return `${otherMembersInfo[0]?.nickname}, ${otherMembersInfo[1]?.nickname}`; + return `${memberNicknames[0]}, ${memberNicknames[1]}`; } else { - return `${otherMembersInfo[0]?.nickname}, ${otherMembersInfo[1]?.nickname} ...`; + return `${memberNicknames[0]}, ${memberNicknames[1]} ...`; } } diff --git a/client/shared/index.tsx b/client/shared/index.tsx index e343dec2..de51fd6a 100644 --- a/client/shared/index.tsx +++ b/client/shared/index.tsx @@ -192,6 +192,7 @@ export { useDMConverseList } from './redux/hooks/useConverse'; export { useConverseAck } from './redux/hooks/useConverseAck'; export { useConverseMessage } from './redux/hooks/useConverseMessage'; export { useDMConverseName } from './redux/hooks/useDMConverseName'; +export { useFriendNickname } from './redux/hooks/useFriendNickname'; export { useGroupInfo, useGroupMemberIds, @@ -219,7 +220,7 @@ export { } from './redux/slices'; export type { ChatConverseState } from './redux/slices/chat'; export { setupRedux } from './redux/setup'; -export { reduxStore, ReduxProvider } from './redux/store'; +export { getReduxStore, ReduxProvider } from './redux/store'; export type { AppStore, AppState, AppDispatch } from './redux/store'; // store diff --git a/client/shared/redux/hooks/useDMConverseName.ts b/client/shared/redux/hooks/useDMConverseName.ts index 51769d8b..6abfd549 100644 --- a/client/shared/redux/hooks/useDMConverseName.ts +++ b/client/shared/redux/hooks/useDMConverseName.ts @@ -1,17 +1,19 @@ import { getDMConverseName } from '../../helper/converse-helper'; -import { isValidStr, useAsync } from '../../index'; +import { isValidStr, useAppSelector, useAsync } from '../../index'; import type { ChatConverseState } from '../slices/chat'; import { useUserId } from './useUserInfo'; +import type { FriendInfo } from '../slices/user'; export function useDMConverseName(converse: ChatConverseState) { const userId = useUserId(); + const friends: FriendInfo[] = useAppSelector((state) => state.user.friends); const { value: name = '' } = useAsync(async () => { if (!isValidStr(userId)) { return ''; } return getDMConverseName(userId, converse); - }, [userId, converse.name, converse.members.join(',')]); + }, [userId, converse.name, converse.members.join(','), friends]); return name; } diff --git a/client/shared/redux/hooks/useFriendNickname.ts b/client/shared/redux/hooks/useFriendNickname.ts new file mode 100644 index 00000000..061a94b7 --- /dev/null +++ b/client/shared/redux/hooks/useFriendNickname.ts @@ -0,0 +1,33 @@ +import { useMemo } from 'react'; +import { buildFriendNicknameMap } from '../../helper/converse-helper'; +import { isValidStr } from '../../utils/string-helper'; +import { useAppSelector } from './useAppSelector'; + +/** + * 获取好友自定义的昵称 + * 用于覆盖原始昵称 + * + * @param userId 用户id + */ +export function useFriendNickname(userId: string): string | null { + const nickname = useAppSelector( + (state) => state.user.friends.find((f) => f.id === userId)?.nickname + ); + + if (isValidStr(nickname)) { + return nickname; + } + + return null; +} + +export function useFriendNicknameMap(): Record { + const friends = useAppSelector((state) => state.user.friends); + + const friendNicknameMap = useMemo( + () => buildFriendNicknameMap(friends), + [friends] + ); + + return friendNicknameMap; +} diff --git a/client/shared/redux/store.ts b/client/shared/redux/store.ts index ebe702fe..ff31df4e 100644 --- a/client/shared/redux/store.ts +++ b/client/shared/redux/store.ts @@ -14,8 +14,11 @@ function createStore() { return store; } -export const reduxStore = createStore(); +const reduxStore = createStore(); +export function getReduxStore() { + return reduxStore; +} export type AppStore = ReturnType; export type AppState = ReturnType; export type AppDispatch = AppStore['dispatch']; diff --git a/client/web/src/components/UserListItem.tsx b/client/web/src/components/UserListItem.tsx index 4e1dcd4c..ebc08e68 100644 --- a/client/web/src/components/UserListItem.tsx +++ b/client/web/src/components/UserListItem.tsx @@ -3,7 +3,7 @@ import { Avatar } from 'tailchat-design'; import _isEmpty from 'lodash/isEmpty'; import { Popover, PopoverProps, Skeleton, Space } from 'antd'; import { useCachedUserInfo, useCachedOnlineStatus } from 'tailchat-shared'; -import clsx from 'clsx'; +import { UserName } from './UserName'; interface UserListItemProps { userId: string; @@ -26,24 +26,24 @@ export const UserListItem: React.FC = React.memo((props) => { active={true} >
- - - + {props.popover ? ( + + + + ) : ( + + )}
- {userName} - {!hideDiscriminator && ( - - #{userInfo.discriminator} - - )} +
{actions} diff --git a/client/web/src/components/UserName.tsx b/client/web/src/components/UserName.tsx index 97d43eb4..03b695a1 100644 --- a/client/web/src/components/UserName.tsx +++ b/client/web/src/components/UserName.tsx @@ -1,20 +1,66 @@ import React from 'react'; -import { useCachedUserInfo } from 'tailchat-shared'; +import { useCachedUserInfo, useFriendNickname } from 'tailchat-shared'; interface UserNameProps { userId: string; className?: string; style?: React.CSSProperties; + showDiscriminator?: boolean; } +/** + * 纯净版的 UserName, 无需redux上下文 + */ +export const UserNamePure: React.FC = React.memo((props) => { + const { userId, showDiscriminator, className, style } = props; + const cachedUserInfo = useCachedUserInfo(userId); + + return ( + + {cachedUserInfo.nickname ??  } + + {showDiscriminator && ( + + )} + + ); +}); +UserNamePure.displayName = 'UserNamePure'; + +/** + * 增加好友名称patch的 UserName + */ export const UserName: React.FC = React.memo((props) => { - const { userId, className, style } = props; + const { userId, showDiscriminator, className, style } = props; const cachedUserInfo = useCachedUserInfo(userId); + const friendNickname = useFriendNickname(userId); return ( - {cachedUserInfo.nickname} + {friendNickname ? ( + <> + {friendNickname} + ({cachedUserInfo.nickname}) + + ) : ( + cachedUserInfo.nickname ??   + )} + + {showDiscriminator && ( + + )} ); }); UserName.displayName = 'UserName'; + +const UserNameDiscriminator: React.FC<{ discriminator: string }> = React.memo( + ({ discriminator }) => { + return ( + + #{discriminator} + + ); + } +); +UserNameDiscriminator.displayName = 'UserNameDiscriminator'; diff --git a/client/web/src/components/UserProfileContainer.tsx b/client/web/src/components/UserProfileContainer.tsx index 03a29b58..bf7c1709 100644 --- a/client/web/src/components/UserProfileContainer.tsx +++ b/client/web/src/components/UserProfileContainer.tsx @@ -1,7 +1,7 @@ import { fetchImagePrimaryColor } from '@/utils/image-helper'; import React, { PropsWithChildren } from 'react'; import { AvatarWithPreview, getTextColorHex } from 'tailchat-design'; -import { parseUrlStr, useAsync, UserBaseInfo } from 'tailchat-shared'; +import { useAsync, UserBaseInfo } from 'tailchat-shared'; /** * 用户信息容器 diff --git a/client/web/src/components/popover/UserPopover/GroupUserPopover.tsx b/client/web/src/components/popover/UserPopover/GroupUserPopover.tsx index c8831dbb..f4118101 100644 --- a/client/web/src/components/popover/UserPopover/GroupUserPopover.tsx +++ b/client/web/src/components/popover/UserPopover/GroupUserPopover.tsx @@ -1,3 +1,4 @@ +import { UserName } from '@/components/UserName'; import { fetchImagePrimaryColor } from '@/utils/image-helper'; import { Space, Tag } from 'antd'; import React, { useEffect } from 'react'; @@ -33,7 +34,9 @@ export const GroupUserPopover: React.FC<{
- {userInfo.nickname} + + + {!hideGroupMemberDiscriminator && ( #{userInfo.discriminator} )} diff --git a/client/web/src/components/popover/UserPopover/PersonalUserPopover.tsx b/client/web/src/components/popover/UserPopover/PersonalUserPopover.tsx index b0fed710..71239973 100644 --- a/client/web/src/components/popover/UserPopover/PersonalUserPopover.tsx +++ b/client/web/src/components/popover/UserPopover/PersonalUserPopover.tsx @@ -1,3 +1,4 @@ +import { UserName } from '@/components/UserName'; import { fetchImagePrimaryColor } from '@/utils/image-helper'; import { Space, Tag } from 'antd'; import React, { useEffect } from 'react'; @@ -24,7 +25,9 @@ export const PersonalUserPopover: React.FC<{
- {userInfo.nickname} + + + #{userInfo.discriminator}
diff --git a/client/web/src/hooks/useGroupMemberAction.ts b/client/web/src/hooks/useGroupMemberAction.ts index 27e99fc3..62e2b807 100644 --- a/client/web/src/hooks/useGroupMemberAction.ts +++ b/client/web/src/hooks/useGroupMemberAction.ts @@ -15,6 +15,7 @@ import { useSearch, } from 'tailchat-shared'; import _compact from 'lodash/compact'; +import { useFriendNicknameMap } from 'tailchat-shared/redux/hooks/useFriendNickname'; /** * 群组成员管理相关操作 @@ -23,6 +24,7 @@ export function useGroupMemberAction(groupId: string) { const groupInfo = useGroupInfo(groupId); const members = groupInfo?.members ?? []; const userInfos = useGroupMemberInfos(groupId); + const friendNicknameMap = useFriendNicknameMap(); const { handleMuteMember, handleUnmuteMember } = useMemberMuteAction( groupId, @@ -31,7 +33,19 @@ export function useGroupMemberAction(groupId: string) { const { searchText, setSearchText, isSearching, searchResult } = useSearch({ dataSource: userInfos, - filterFn: (item, searchText) => item.nickname.includes(searchText), + filterFn: (item, searchText) => { + if (friendNicknameMap[item._id]) { + if (friendNicknameMap[item._id].includes(searchText)) { + return true; + } + } + + if (item.nickname.includes(searchText)) { + return true; + } + + return false; + }, }); /** diff --git a/client/web/src/routes/Invite/InviteInfo.tsx b/client/web/src/routes/Invite/InviteInfo.tsx index 94ed142e..9e4d6da1 100644 --- a/client/web/src/routes/Invite/InviteInfo.tsx +++ b/client/web/src/routes/Invite/InviteInfo.tsx @@ -1,7 +1,7 @@ import { Avatar } from 'tailchat-design'; import { InviteCodeExpiredAt } from '@/components/InviteCodeExpiredAt'; import { LoadingSpinner } from '@/components/LoadingSpinner'; -import { UserName } from '@/components/UserName'; +import { UserNamePure } from '@/components/UserName'; import { Divider } from 'antd'; import React from 'react'; import { @@ -60,7 +60,7 @@ export const InviteInfo: React.FC = React.memo((props) => { />
- {' '} + {' '} {t('邀请您加入群组')}
{inviteInfo.group.name}
diff --git a/client/web/src/routes/Main/Content/Personal/Sidebar.tsx b/client/web/src/routes/Main/Content/Personal/Sidebar.tsx index 72ed374b..238f3148 100644 --- a/client/web/src/routes/Main/Content/Personal/Sidebar.tsx +++ b/client/web/src/routes/Main/Content/Personal/Sidebar.tsx @@ -79,6 +79,7 @@ export const PersonalSidebar: React.FC = React.memo(() => { > {t('私信')} + {converseList.map((converse) => { return ; })} diff --git a/client/web/src/routes/Main/Provider.tsx b/client/web/src/routes/Main/Provider.tsx index 649d0dfe..2126cc83 100644 --- a/client/web/src/routes/Main/Provider.tsx +++ b/client/web/src/routes/Main/Provider.tsx @@ -6,7 +6,7 @@ import { t, ReduxProvider, UserLoginInfo, - reduxStore, + getReduxStore, } from 'tailchat-shared'; import React, { PropsWithChildren } from 'react'; import { LoadingSpinner } from '@/components/LoadingSpinner'; @@ -41,7 +41,7 @@ function useAppState() { // 到这里 userLoginInfo 必定存在 // 创建Redux store - const store = reduxStore; + const store = getReduxStore(); store.dispatch(userActions.setUserInfo(userLoginInfo)); setGlobalStore(store);