feat: add friend nickname support in everywhere

pull/90/head
moonrailgun 2 years ago
parent 36608ed8f3
commit 20c16adeec

@ -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<string, string> {
const friendNicknameMap: Record<string, string> = 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]} ...`;
}
}

@ -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

@ -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;
}

@ -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<string, string> {
const friends = useAppSelector((state) => state.user.friends);
const friendNicknameMap = useMemo(
() => buildFriendNicknameMap(friends),
[friends]
);
return friendNicknameMap;
}

@ -14,8 +14,11 @@ function createStore() {
return store;
}
export const reduxStore = createStore();
const reduxStore = createStore();
export function getReduxStore() {
return reduxStore;
}
export type AppStore = ReturnType<typeof createStore>;
export type AppState = ReturnType<AppStore['getState']>;
export type AppDispatch = AppStore['dispatch'];

@ -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<UserListItemProps> = React.memo((props) => {
active={true}
>
<div className="mr-2">
<Popover content={props.popover} placement="left" trigger="click">
<Avatar
className={clsx({
'cursor-pointer': !!props.popover,
})}
src={userInfo.avatar}
name={userName}
isOnline={isOnline}
/>
</Popover>
{props.popover ? (
<Popover content={props.popover} placement="left" trigger="click">
<Avatar
className="cursor-pointer"
src={userInfo.avatar}
name={userName}
isOnline={isOnline}
/>
</Popover>
) : (
<Avatar src={userInfo.avatar} name={userName} isOnline={isOnline} />
)}
</div>
<div className="flex-1 text-gray-900 dark:text-white">
<span>{userName}</span>
{!hideDiscriminator && (
<span className="text-gray-500 dark:text-gray-300 opacity-0 group-hover:opacity-100">
#{userInfo.discriminator}
</span>
)}
<UserName
userId={props.userId}
showDiscriminator={!hideDiscriminator}
/>
</div>
<Space>{actions}</Space>
</Skeleton>

@ -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<UserNameProps> = React.memo((props) => {
const { userId, showDiscriminator, className, style } = props;
const cachedUserInfo = useCachedUserInfo(userId);
return (
<span className={className} style={style}>
{cachedUserInfo.nickname ?? <span>&nbsp;</span>}
{showDiscriminator && (
<UserNameDiscriminator discriminator={cachedUserInfo.discriminator} />
)}
</span>
);
});
UserNamePure.displayName = 'UserNamePure';
/**
* patch UserName
*/
export const UserName: React.FC<UserNameProps> = React.memo((props) => {
const { userId, className, style } = props;
const { userId, showDiscriminator, className, style } = props;
const cachedUserInfo = useCachedUserInfo(userId);
const friendNickname = useFriendNickname(userId);
return (
<span className={className} style={style}>
{cachedUserInfo.nickname}
{friendNickname ? (
<>
{friendNickname}
<span className="opacity-60">({cachedUserInfo.nickname})</span>
</>
) : (
cachedUserInfo.nickname ?? <span>&nbsp;</span>
)}
{showDiscriminator && (
<UserNameDiscriminator discriminator={cachedUserInfo.discriminator} />
)}
</span>
);
});
UserName.displayName = 'UserName';
const UserNameDiscriminator: React.FC<{ discriminator: string }> = React.memo(
({ discriminator }) => {
return (
<span className="text-gray-500 dark:text-gray-300 opacity-0 group-hover:opacity-100">
#{discriminator}
</span>
);
}
);
UserNameDiscriminator.displayName = 'UserNameDiscriminator';

@ -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';
/**
*

@ -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<{
<div className="w-80 -mx-4 -my-3 bg-inherit">
<UserProfileContainer userInfo={userInfo}>
<div className="text-xl">
<span className="font-semibold">{userInfo.nickname}</span>
<span className="font-semibold">
<UserName userId={userInfo._id} />
</span>
{!hideGroupMemberDiscriminator && (
<span className="opacity-60 ml-1">#{userInfo.discriminator}</span>
)}

@ -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<{
<div className="w-80 -mx-4 -my-3 bg-inherit">
<UserProfileContainer userInfo={userInfo}>
<div className="text-xl">
<span className="font-semibold">{userInfo.nickname}</span>
<span className="font-semibold">
<UserName userId={userInfo._id} />
</span>
<span className="opacity-60 ml-1">#{userInfo.discriminator}</span>
</div>

@ -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;
},
});
/**

@ -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<Props> = React.memo((props) => {
/>
</div>
<div>
<UserName className="font-bold" userId={inviteInfo.creator} />{' '}
<UserNamePure className="font-bold" userId={inviteInfo.creator} />{' '}
{t('邀请您加入群组')}
</div>
<div className="text-xl my-2 font-bold">{inviteInfo.group.name}</div>

@ -79,6 +79,7 @@ export const PersonalSidebar: React.FC = React.memo(() => {
>
{t('私信')}
</SidebarSection>
{converseList.map((converse) => {
return <SidebarDMItem key={converse._id} converse={converse} />;
})}

@ -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);

Loading…
Cancel
Save