From 0c5e5c3cf47b570d4ec060a9ff5e7656aadfe58e Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Wed, 1 Feb 2023 10:39:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=AE=BE=E5=AE=9A=E9=9D=99=E9=9F=B3?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E4=B8=8B=E7=BE=A4=E7=BB=84=E5=B0=8F=E7=BA=A2?= =?UTF-8?q?=E7=82=B9=E4=B8=BA=E7=81=B0=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shared/hooks/model/useGroupUnreadState.ts | 53 +++++++++++++++++++ client/shared/index.tsx | 2 +- client/shared/redux/hooks/useGroup.ts | 17 +----- .../web/src/routes/Main/Navbar/GroupNav.tsx | 9 ++-- .../web/src/routes/Main/Navbar/InboxNav.tsx | 6 ++- client/web/src/routes/Main/Navbar/NavItem.tsx | 11 ++-- 6 files changed, 70 insertions(+), 28 deletions(-) create mode 100644 client/shared/hooks/model/useGroupUnreadState.ts diff --git a/client/shared/hooks/model/useGroupUnreadState.ts b/client/shared/hooks/model/useGroupUnreadState.ts new file mode 100644 index 00000000..813ca0d4 --- /dev/null +++ b/client/shared/hooks/model/useGroupUnreadState.ts @@ -0,0 +1,53 @@ +import { GroupPanelType } from '../../model/group'; +import { useGroupInfo } from '../../redux/hooks/useGroup'; +import { useUserNotifyMute } from './useUserSettings'; +import _zip from 'lodash/zip'; +import { useUnread } from '../../redux/hooks/useUnread'; + +/** + * 检查群组未读消息的状态 + * @param groupId 群组id + */ +export function useGroupUnreadState( + groupId: string +): 'none' | 'muted' | 'unread' { + const group = useGroupInfo(groupId); + const groupTextPanelIds = (group?.panels ?? []) + .filter((panel) => panel.type === GroupPanelType.TEXT) + .map((p) => p.id); + + const { mutedList } = useUserNotifyMute(); + + const unreadList = useUnread(groupTextPanelIds); + const unreadEntires = _zip(groupTextPanelIds, unreadList); + let hasUnread = false; + let hasUnmutedUnread = false; + const isGroupMuted = mutedList.includes(groupId); // 群组自身是否被禁用 + + for (const [panelId, isUnread] of unreadEntires) { + if (isUnread === true) { + hasUnread = true; + + if (isGroupMuted) { + // 如果群组已经被静音,则无需做后续判断,跳出循环 + break; + } + + if (panelId && !mutedList.includes(panelId)) { + // 如果面板没有并禁言,且有未读消息 + hasUnmutedUnread = true; + break; + } + } + } + + if (hasUnread) { + if (hasUnmutedUnread) { + return 'unread'; // 有未读消息,显示红点 + } else { + return 'muted'; // 有未读消息,但是未读消息均被静音,显示灰点 + } + } else { + return 'none'; // 没有未读消息,不显示任何状态 + } +} diff --git a/client/shared/index.tsx b/client/shared/index.tsx index 4f9a70d2..587203fb 100644 --- a/client/shared/index.tsx +++ b/client/shared/index.tsx @@ -54,6 +54,7 @@ export { useLanguage } from './i18n/language'; // hooks export { createUseStorageState } from './hooks/factory/createUseStorageState'; export { useAvailableServices } from './hooks/model/useAvailableServices'; +export { useGroupUnreadState } from './hooks/model/useGroupUnreadState'; export { useMessageNotifyEventFilter } from './hooks/model/useMessageNotifyEventFilter'; export { useUserInfoList } from './hooks/model/useUserInfoList'; export { useUsernames } from './hooks/model/useUsernames'; @@ -189,7 +190,6 @@ export { useGroupPanels, useGroupPanelInfo, useIsGroupOwner, - useGroupUnread, useGroupTextPanelUnread, } from './redux/hooks/useGroup'; export { useGroupAck } from './redux/hooks/useGroupAck'; diff --git a/client/shared/redux/hooks/useGroup.ts b/client/shared/redux/hooks/useGroup.ts index 627c9b2c..affb1483 100644 --- a/client/shared/redux/hooks/useGroup.ts +++ b/client/shared/redux/hooks/useGroup.ts @@ -1,6 +1,6 @@ import { useMemo } from 'react'; import { useUserInfoList } from '../..'; -import { GroupInfo, GroupPanel, GroupPanelType } from '../../model/group'; +import type { GroupInfo, GroupPanel } from '../../model/group'; import type { UserBaseInfo } from '../../model/user'; import { isValidStr } from '../../utils/string-helper'; import { useAppSelector } from './useAppSelector'; @@ -75,21 +75,6 @@ export function useIsGroupOwner(groupId: string, userId?: string): boolean { } } -/** - * 检查群组是否有未读消息 - * @param groupId 群组id - */ -export function useGroupUnread(groupId: string): boolean { - const group = useGroupInfo(groupId); - const groupTextPanelIds = (group?.panels ?? []) - .filter((panel) => panel.type === GroupPanelType.TEXT) - .map((p) => p.id); - - const unread = useUnread(groupTextPanelIds); - - return unread.some((u) => u === true); -} - /** * 检查群组聊天面板是否有未读消息 * @param textPanelId 文字面板id diff --git a/client/web/src/routes/Main/Navbar/GroupNav.tsx b/client/web/src/routes/Main/Navbar/GroupNav.tsx index 7669f6af..53e726c8 100644 --- a/client/web/src/routes/Main/Navbar/GroupNav.tsx +++ b/client/web/src/routes/Main/Navbar/GroupNav.tsx @@ -8,7 +8,7 @@ import { t, useAppSelector, useGroupAck, - useGroupUnread, + useGroupUnreadState, } from 'tailchat-shared'; import { NavbarNavItem } from './NavItem'; import { Dropdown } from 'antd'; @@ -18,7 +18,7 @@ import { Dropdown } from 'antd'; */ const GroupNavItem: React.FC<{ group: GroupInfo }> = React.memo(({ group }) => { const groupId = group._id; - const hasUnread = useGroupUnread(groupId); + const unreadState = useGroupUnreadState(groupId); const { markGroupAllAck } = useGroupAck(groupId); const menu = { @@ -42,7 +42,10 @@ const GroupNavItem: React.FC<{ group: GroupInfo }> = React.memo(({ group }) => { name={group.name} to={`/main/group/${group._id}`} showPill={true} - badge={hasUnread} + badge={['muted', 'unread'].includes(unreadState)} + badgeProps={{ + status: unreadState === 'unread' ? 'error' : 'default', + }} > { const inbox = useInboxList(); + const unreadList = inbox.filter((i) => !i.readed); return ( { name={t('收件箱')} to={'/main/inbox'} showPill={true} - badge={inbox.filter((i) => !i.readed).length} + badge={unreadList.length > 0} + badgeProps={{ + count: unreadList.length, + }} data-testid="inbox" > diff --git a/client/web/src/routes/Main/Navbar/NavItem.tsx b/client/web/src/routes/Main/Navbar/NavItem.tsx index 236b51d7..ec6edc5a 100644 --- a/client/web/src/routes/Main/Navbar/NavItem.tsx +++ b/client/web/src/routes/Main/Navbar/NavItem.tsx @@ -1,4 +1,4 @@ -import { Tooltip, Badge } from 'antd'; +import { Tooltip, Badge, BadgeProps } from 'antd'; import type { ClassValue } from 'clsx'; import clsx from 'clsx'; import React, { PropsWithChildren } from 'react'; @@ -11,7 +11,8 @@ export const NavbarNavItem: React.FC< className?: ClassValue; to?: string; showPill?: boolean; - badge?: boolean | number; + badge?: boolean; + badgeProps?: BadgeProps; onClick?: () => void; ['data-testid']?: string; }> @@ -67,11 +68,7 @@ export const NavbarNavItem: React.FC< {inner}
- {badge === true ? ( - - ) : ( - - )} + {badge === true && }
);