feat: 频道未读提示

pull/13/head
moonrailgun 4 years ago
parent 8a993203bb
commit b31ffdf6e3

@ -106,6 +106,8 @@ export {
useGroupInfo, useGroupInfo,
useGroupPanel, useGroupPanel,
useIsGroupOwner, useIsGroupOwner,
useGroupUnread,
useGroupTextPanelUnread,
} from './redux/hooks/useGroup'; } from './redux/hooks/useGroup';
export { useUserInfo, useUserId } from './redux/hooks/useUserInfo'; export { useUserInfo, useUserId } from './redux/hooks/useUserInfo';
export { userActions, groupActions } from './redux/slices'; export { userActions, groupActions } from './redux/slices';

@ -1,7 +1,8 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import type { GroupInfo, GroupPanel } from '../../model/group'; import { GroupInfo, GroupPanel, GroupPanelType } from '../../model/group';
import { isValidStr } from '../../utils/string-helper'; import { isValidStr } from '../../utils/string-helper';
import { useAppSelector } from './useAppSelector'; import { useAppSelector } from './useAppSelector';
import { useUnread } from './useUnread';
import { useUserId } from './useUserInfo'; import { useUserId } from './useUserInfo';
/** /**
@ -41,3 +42,28 @@ export function useIsGroupOwner(groupId: string, userId?: string): boolean {
return typeof selfUserId === 'string' && groupInfo?.owner === selfUserId; return typeof selfUserId === 'string' && groupInfo?.owner === selfUserId;
} }
} }
/**
*
* @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
*/
export function useGroupTextPanelUnread(textPanelId: string): boolean {
const unread = useUnread([textPanelId]);
return unread[0];
}

@ -0,0 +1,23 @@
import { useAppSelector } from './useAppSelector';
/**
*
*/
export function useUnread(converseIds: string[]) {
const ack = useAppSelector((state) => state.chat.ack);
const lastMessageMap = useAppSelector((state) => state.chat.lastMessageMap);
return converseIds.map((converseId) => {
if (
ack[converseId] === undefined &&
lastMessageMap[converseId] !== undefined
) {
// 没有已读记录且远程有数据
return true;
}
// 当远端最后一条消息的id > 本地已读状态的最后一条消息id,
// 则返回true(有未读消息)
return lastMessageMap[converseId] > ack[converseId];
});
}

@ -43,6 +43,10 @@ export const ChatMessageList = React.forwardRef<
const onUpdateReadedMessageRef = useUpdateRef(props.onUpdateReadedMessage); const onUpdateReadedMessageRef = useUpdateRef(props.onUpdateReadedMessage);
useEffect(() => { useEffect(() => {
if (props.messages.length === 0) {
return;
}
if (containerRef.current?.scrollTop === 0) { if (containerRef.current?.scrollTop === 0) {
// 当前列表在最低 // 当前列表在最低
onUpdateReadedMessageRef.current( onUpdateReadedMessageRef.current(
@ -52,6 +56,10 @@ export const ChatMessageList = React.forwardRef<
}, [props.messages.length]); }, [props.messages.length]);
const handleScroll = useCallback(() => { const handleScroll = useCallback(() => {
if (props.messages.length === 0) {
return;
}
if (containerRef.current?.scrollTop === 0) { if (containerRef.current?.scrollTop === 0) {
onUpdateReadedMessageRef.current( onUpdateReadedMessageRef.current(
props.messages[props.messages.length - 1]._id props.messages[props.messages.length - 1]._id

@ -31,7 +31,7 @@ export function useMessageAck(converseId: string, messages: ChatMessage[]) {
lastMessageIdRef.current = lastMessageId; lastMessageIdRef.current = lastMessageId;
}, },
1000, 1000,
{ leading: false, trailing: true } { leading: true, trailing: true }
), ),
[] []
); );

@ -1,9 +1,15 @@
import React from 'react'; import React from 'react';
import { GroupPanelType, isValidStr, useGroupInfo } from 'tailchat-shared'; import {
GroupPanel,
GroupPanelType,
isValidStr,
useGroupInfo,
} from 'tailchat-shared';
import { useParams } from 'react-router'; import { useParams } from 'react-router';
import { GroupHeader } from './GroupHeader'; import { GroupHeader } from './GroupHeader';
import { GroupSection } from '@/components/GroupSection'; import { GroupSection } from '@/components/GroupSection';
import { GroupPanelItem } from '@/components/GroupPanelItem'; import { GroupPanelItem } from '@/components/GroupPanelItem';
import { GroupTextPanelItem } from './TextPanelItem';
interface GroupParams { interface GroupParams {
groupId: string; groupId: string;
@ -17,6 +23,17 @@ export const Sidebar: React.FC = React.memo(() => {
const groupInfo = useGroupInfo(groupId); const groupInfo = useGroupInfo(groupId);
const groupPanels = groupInfo?.panels ?? []; const groupPanels = groupInfo?.panels ?? [];
const renderItem = (panel: GroupPanel) =>
panel.type === GroupPanelType.TEXT ? (
<GroupTextPanelItem groupId={groupId} panel={panel} />
) : (
<GroupPanelItem
name={panel.name}
icon={<div>#</div>}
to={`/main/group/${groupId}/${panel.id}`}
/>
);
return ( return (
<div> <div>
<GroupHeader groupId={groupId} /> <GroupHeader groupId={groupId} />
@ -30,22 +47,11 @@ export const Sidebar: React.FC = React.memo(() => {
{groupPanels {groupPanels
.filter((sub) => sub.parentId === panel.id) .filter((sub) => sub.parentId === panel.id)
.map((sub) => ( .map((sub) => (
<div key={sub.id}> <div key={sub.id}>{renderItem(sub)}</div>
<GroupPanelItem
name={sub.name}
icon={<div>#</div>}
to={`/main/group/${groupId}/${sub.id}`}
/>
</div>
))} ))}
</GroupSection> </GroupSection>
) : ( ) : (
<GroupPanelItem <div key={panel.id}>{renderItem(panel)}</div>
key={panel.id}
name={panel.name}
icon={<div>#</div>}
to={`/main/group/${groupId}/${panel.id}`}
/>
) )
)} )}
</div> </div>

@ -0,0 +1,25 @@
import { GroupPanelItem } from '@/components/GroupPanelItem';
import React from 'react';
import { GroupPanel, useGroupTextPanelUnread } from 'tailchat-shared';
interface GroupTextPanelItemProps {
groupId: string;
panel: GroupPanel;
}
export const GroupTextPanelItem: React.FC<GroupTextPanelItemProps> = React.memo(
(props) => {
const { groupId, panel } = props;
const panelId = panel.id;
const hasUnread = useGroupTextPanelUnread(panelId);
return (
<GroupPanelItem
name={panel.name}
icon={<div>#</div>}
to={`/main/group/${groupId}/${panel.id}`}
badge={hasUnread}
/>
);
}
);
GroupTextPanelItem.displayName = 'GroupTextPanelItem';
Loading…
Cancel
Save