mirror of https://github.com/msgbyte/tailchat
feat: 收件箱侧边栏展示
parent
f1238badbd
commit
db917d26b9
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 收件箱记录项类型
|
||||
*/
|
||||
export interface InboxItem {
|
||||
_id: string;
|
||||
userId: string;
|
||||
readed: boolean;
|
||||
type: 'message';
|
||||
message?: {
|
||||
groupId?: string;
|
||||
converseId: string;
|
||||
messageId: string;
|
||||
messageSnippet: string;
|
||||
};
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
import type { InboxItem } from '../../model/inbox';
|
||||
import { useAppSelector } from './useAppSelector';
|
||||
|
||||
/**
|
||||
* 返回收件箱列表
|
||||
*/
|
||||
export function useInboxList(): InboxItem[] {
|
||||
return useAppSelector((state) => state.chat.inbox ?? []);
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import { useAppSelector, useDMConverseName } from 'tailchat-shared';
|
||||
|
||||
interface ConverseNameProps {
|
||||
converseId: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export const ConverseName: React.FC<ConverseNameProps> = React.memo((props) => {
|
||||
const { converseId, className, style } = props;
|
||||
const converse = useAppSelector((state) => state.chat.converses[converseId]);
|
||||
const converseName = useDMConverseName(converse);
|
||||
|
||||
return (
|
||||
<span className={className} style={style}>
|
||||
{converseName}
|
||||
</span>
|
||||
);
|
||||
});
|
||||
ConverseName.displayName = 'ConverseName';
|
@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { useGroupInfo } from 'tailchat-shared';
|
||||
|
||||
interface GroupNameProps {
|
||||
groupId: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export const GroupName: React.FC<GroupNameProps> = React.memo((props) => {
|
||||
const { groupId, className, style } = props;
|
||||
const groupInfo = useGroupInfo(groupId);
|
||||
|
||||
return (
|
||||
<span className={className} style={style}>
|
||||
{groupInfo?.name}
|
||||
</span>
|
||||
);
|
||||
});
|
||||
GroupName.displayName = 'GroupName';
|
@ -1,32 +1,86 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { CommonSidebarWrapper } from '@/components/CommonSidebarWrapper';
|
||||
import { t } from 'tailchat-shared';
|
||||
import { isValidStr, model, t, useInboxList } from 'tailchat-shared';
|
||||
import clsx from 'clsx';
|
||||
import _orderBy from 'lodash/orderBy';
|
||||
import { GroupName } from '@/components/GroupName';
|
||||
import { ConverseName } from '@/components/ConverseName';
|
||||
import { getMessageRender } from '@/plugin/common';
|
||||
|
||||
interface InboxSidebarProps {
|
||||
selectedItem: string;
|
||||
onSelect: (itemId: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 收件箱侧边栏组件
|
||||
*/
|
||||
export const InboxSidebar: React.FC = React.memo(() => {
|
||||
export const InboxSidebar: React.FC<InboxSidebarProps> = React.memo((props) => {
|
||||
const inbox = useInboxList();
|
||||
const list = useMemo(() => _orderBy(inbox, 'createdAt', 'desc'), [inbox]);
|
||||
|
||||
return (
|
||||
<CommonSidebarWrapper data-tc-role="sidebar-inbox">
|
||||
<div className="overflow-auto">
|
||||
{Array.from({ length: 20 }).map((_, i) => {
|
||||
{list.map((item) => {
|
||||
const { type } = item;
|
||||
|
||||
if (type === 'message') {
|
||||
const message: Partial<model.inbox.InboxItem['message']> =
|
||||
item.message ?? {};
|
||||
let title: React.ReactNode = '';
|
||||
if (isValidStr(message.groupId)) {
|
||||
title = <GroupName groupId={message.groupId} />;
|
||||
} else if (isValidStr(message.converseId)) {
|
||||
title = <ConverseName converseId={message.converseId} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<InboxSidebarItem
|
||||
key={item._id}
|
||||
title={title}
|
||||
desc={getMessageRender(message.messageSnippet ?? '')}
|
||||
source={'Tailchat'}
|
||||
selected={props.selectedItem === item._id}
|
||||
onSelect={() => props.onSelect(item._id)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</CommonSidebarWrapper>
|
||||
);
|
||||
});
|
||||
InboxSidebar.displayName = 'InboxSidebar';
|
||||
|
||||
const InboxSidebarItem: React.FC<{
|
||||
title: React.ReactNode;
|
||||
desc: React.ReactNode;
|
||||
source: string;
|
||||
selected: boolean;
|
||||
onSelect: () => void;
|
||||
}> = React.memo((props) => {
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className="p-2 overflow-auto cursor-pointer hover:bg-black hover:bg-opacity-10 dark:hover:bg-white dark:hover:bg-opacity-10"
|
||||
className={clsx(
|
||||
'p-2 overflow-auto cursor-pointer hover:bg-black hover:bg-opacity-10 dark:hover:bg-white dark:hover:bg-opacity-10',
|
||||
{
|
||||
'bg-black bg-opacity-10 dark:bg-white dark:bg-opacity-10':
|
||||
props.selected,
|
||||
}
|
||||
)}
|
||||
onClick={props.onSelect}
|
||||
>
|
||||
<div className="text-lg">Title {i}</div>
|
||||
<div className="text-lg overflow-ellipsis overflow-hidden">
|
||||
{props.title || <span> </span>}
|
||||
</div>
|
||||
<div className="break-all text-opacity-80 text-black dark:text-opacity-80 dark:text-white text-sm p-1 border-l-2 border-gray-500 border-opacity-50">
|
||||
DescDescDescDescDescDescDescDescDescDescDescDescDescDescDescDescDescDescDescDescDescDescDescDescDescDescDescDescDescDescDescDescDescDesc
|
||||
{props.desc}
|
||||
</div>
|
||||
<div className="text-xs text-opacity-50 text-black dark:text-opacity-50 dark:text-white">
|
||||
{t('来自')}: Tailchat
|
||||
{t('来自')}: {props.source}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CommonSidebarWrapper>
|
||||
);
|
||||
});
|
||||
InboxSidebar.displayName = 'InboxSidebar';
|
||||
InboxSidebarItem.displayName = 'InboxSidebarItem';
|
||||
|
Loading…
Reference in New Issue