feat: add text panel message search right panel

pull/147/merge
moonrailgun 1 year ago
parent 0ff8f558ef
commit 176528b303

@ -1,5 +1,4 @@
import type { DependencyList } from 'react';
import { isDevelopment, t } from '..';
import { showErrorToasts } from '../manager/ui';
import type { FunctionReturningPromise } from '../types';
import { useAsyncFn } from './useAsyncFn';

@ -84,6 +84,25 @@ export async function deleteMessage(messageId: string): Promise<boolean> {
return data;
}
/**
*
* @param converseId id
* @param messageText
*/
export async function searchMessage(
text: string,
converseId: string,
groupId?: string
): Promise<ChatMessage[]> {
const { data } = await request.post('/api/chat/message/searchMessage', {
text,
converseId,
groupId,
});
return data;
}
/**
* idid
*/

@ -70,7 +70,7 @@ export function formatShortTime(date: dayjs.ConfigType): string {
}
/**
* :
* YYYY-MM-DD HH:mm:ss
*/
export function formatFullTime(date: dayjs.ConfigType): string {
return dayjs(date).format('YYYY-MM-DD HH:mm:ss');

@ -61,156 +61,160 @@ const MessageActionIcon: React.FC<{ icon: string }> = (props) => (
/**
*
*/
const NormalMessage: React.FC<ChatMessageItemProps> = React.memo((props) => {
const { showAvatar, payload } = props;
const userInfo = useCachedUserInfo(payload.author ?? '');
const [isActionBtnActive, setIsActionBtnActive] = useState(false);
export const NormalMessage: React.FC<ChatMessageItemProps> = React.memo(
(props) => {
const { showAvatar, payload, hideAction = false } = props;
const userInfo = useCachedUserInfo(payload.author ?? '');
const [isActionBtnActive, setIsActionBtnActive] = useState(false);
const reactions = useMessageReactions(payload);
const reactions = useMessageReactions(payload);
const emojiAction = useChatMessageReactionAction(payload);
const moreActions = useChatMessageItemAction(payload, {
onClick: () => {
setIsActionBtnActive(false);
},
});
const emojiAction = useChatMessageReactionAction(payload);
const moreActions = useChatMessageItemAction(payload, {
onClick: () => {
setIsActionBtnActive(false);
},
});
// 禁止对消息进行操作,因为此时消息尚未发送到远程
const disableOperate =
payload.isLocal === true || payload.sendFailed === true;
// 禁止对消息进行操作,因为此时消息尚未发送到远程
const disableOperate =
hideAction === true ||
payload.isLocal === true ||
payload.sendFailed === true;
return (
<div
className={clsx(
'chat-message-item flex px-2 mobile:px-0 group relative select-text',
{
'bg-black bg-opacity-10': isActionBtnActive,
'hover:bg-black hover:bg-opacity-5': !isActionBtnActive,
}
)}
data-message-id={payload._id}
>
{/* 头像 */}
<div className="w-18 mobile:w-14 flex items-start justify-center pt-0.5">
{showAvatar ? (
<Popover
content={
!_isEmpty(userInfo) && (
<UserPopover userInfo={userInfo as UserBaseInfo} />
)
}
placement="top"
trigger="click"
>
<Avatar
className="cursor-pointer"
size={40}
src={userInfo.avatar}
name={userInfo.nickname}
/>
</Popover>
) : (
<div className="hidden group-hover:block opacity-40">
{formatShortTime(payload.createdAt)}
</div>
)}
</div>
{/* 主体 */}
return (
<div
className="flex flex-col flex-1 overflow-auto group"
onContextMenu={stopPropagation}
className={clsx(
'chat-message-item flex px-2 mobile:px-0 group relative select-text text-sm',
{
'bg-black bg-opacity-10': isActionBtnActive,
'hover:bg-black hover:bg-opacity-5': !isActionBtnActive,
}
)}
data-message-id={payload._id}
>
{showAvatar && (
<div className="flex items-center">
<div className="font-bold">
{userInfo.nickname ?? <span>&nbsp;</span>}
</div>
<div className="hidden group-hover:block opacity-40 ml-1 text-sm">
{/* 头像 */}
<div className="w-18 mobile:w-14 flex items-start justify-center pt-0.5">
{showAvatar ? (
<Popover
content={
!_isEmpty(userInfo) && (
<UserPopover userInfo={userInfo as UserBaseInfo} />
)
}
placement="top"
trigger="click"
>
<Avatar
className="cursor-pointer"
size={40}
src={userInfo.avatar}
name={userInfo.nickname}
/>
</Popover>
) : (
<div className="hidden group-hover:block opacity-40">
{formatShortTime(payload.createdAt)}
</div>
</div>
)}
)}
</div>
{/* 消息内容 */}
<AutoFolder
maxHeight={340}
backgroundColor="var(--tc-content-background-color)"
showFullText={
<div className="inline-block rounded-full bg-white dark:bg-black opacity-80 py-2 px-3 hover:opacity-100">
{t('点击展开更多')}
</div>
}
{/* 主体 */}
<div
className="flex flex-col flex-1 overflow-auto group"
onContextMenu={stopPropagation}
>
<div className="chat-message-item_body leading-6 break-words">
<MessageQuote payload={payload} />
{showAvatar && (
<div className="flex items-center">
<div className="font-bold">
{userInfo.nickname ?? <span>&nbsp;</span>}
</div>
<div className="hidden group-hover:block opacity-40 ml-1 text-sm">
{formatShortTime(payload.createdAt)}
</div>
</div>
)}
<span>{getMessageRender(payload.content)}</span>
{/* 消息内容 */}
<AutoFolder
maxHeight={340}
backgroundColor="var(--tc-content-background-color)"
showFullText={
<div className="inline-block rounded-full bg-white dark:bg-black opacity-80 py-2 px-3 hover:opacity-100">
{t('点击展开更多')}
</div>
}
>
<div className="chat-message-item_body leading-6 break-words">
<MessageQuote payload={payload} />
{payload.sendFailed === true && (
<Icon
className="inline-block ml-1"
icon="emojione:cross-mark-button"
/>
)}
<span>{getMessageRender(payload.content)}</span>
{payload.sendFailed === true && (
<Icon
className="inline-block ml-1"
icon="emojione:cross-mark-button"
/>
)}
{/* 解释器按钮 */}
{useRenderPluginMessageInterpreter(payload.content)}
</div>
</AutoFolder>
{/* 解释器按钮 */}
{useRenderPluginMessageInterpreter(payload.content)}
{/* 额外渲染 */}
<div>
{pluginMessageExtraParsers.map((parser) => (
<React.Fragment key={parser.name}>
{parser.render(payload)}
</React.Fragment>
))}
</div>
</AutoFolder>
{/* 额外渲染 */}
<div>
{pluginMessageExtraParsers.map((parser) => (
<React.Fragment key={parser.name}>
{parser.render(payload)}
</React.Fragment>
))}
{/* 消息反应 */}
{reactions}
</div>
{/* 消息反应 */}
{reactions}
</div>
{/* 操作 */}
{!disableOperate && (
<div
className={clsx(
'bg-white dark:bg-black rounded absolute right-2 cursor-pointer -top-3 shadow-sm flex',
{
'opacity-0 group-hover:opacity-100 bg-opacity-80 hover:bg-opacity-100':
!isActionBtnActive,
'opacity-100 bg-opacity-100': isActionBtnActive,
}
)}
>
<TcPopover
overlayClassName="chat-message-item_action-popover"
content={emojiAction}
placement="bottomLeft"
trigger={['click']}
onOpenChange={setIsActionBtnActive}
{/* 操作 */}
{!disableOperate && (
<div
className={clsx(
'bg-white dark:bg-black rounded absolute right-2 cursor-pointer -top-3 shadow-sm flex',
{
'opacity-0 group-hover:opacity-100 bg-opacity-80 hover:bg-opacity-100':
!isActionBtnActive,
'opacity-100 bg-opacity-100': isActionBtnActive,
}
)}
>
<div>
<MessageActionIcon icon="mdi:emoticon-happy-outline" />
</div>
</TcPopover>
<TcPopover
overlayClassName="chat-message-item_action-popover"
content={emojiAction}
placement="bottomLeft"
trigger={['click']}
onOpenChange={setIsActionBtnActive}
>
<div>
<MessageActionIcon icon="mdi:emoticon-happy-outline" />
</div>
</TcPopover>
<Dropdown
menu={moreActions}
placement="bottomLeft"
trigger={['click']}
onOpenChange={setIsActionBtnActive}
>
<div>
<MessageActionIcon icon="mdi:dots-horizontal" />
</div>
</Dropdown>
</div>
)}
</div>
);
});
<Dropdown
menu={moreActions}
placement="bottomLeft"
trigger={['click']}
onOpenChange={setIsActionBtnActive}
>
<div>
<MessageActionIcon icon="mdi:dots-horizontal" />
</div>
</Dropdown>
</div>
)}
</div>
);
}
);
NormalMessage.displayName = 'NormalMessage';
/**
@ -250,6 +254,7 @@ SystemMessageWithNickname.displayName = 'SystemMessageWithNickname';
interface ChatMessageItemProps {
showAvatar: boolean;
payload: LocalChatMessage;
hideAction?: boolean;
}
const ChatMessageItem: React.FC<ChatMessageItemProps> = React.memo((props) => {
const payload = props.payload;

@ -0,0 +1,62 @@
import { NormalMessage } from '@/components/ChatBox/ChatMessageList/Item';
import { Empty, Input } from 'antd';
import React from 'react';
import {
ChatMessage,
model,
showToasts,
t,
useAsyncRequest,
} from 'tailchat-shared';
export const MessageSearchPanel: React.FC<{
groupId?: string;
converseId: string;
}> = React.memo((props) => {
const { groupId, converseId } = props;
const [{ loading, value = [] }, handleSearch] = useAsyncRequest(
async (searchText: string) => {
if (searchText.length < 3) {
showToasts(t('搜索内容太短无法搜索'));
return;
}
const messages = await model.message.searchMessage(
searchText,
converseId,
groupId
);
return messages ?? [];
}
);
const searchedMessages = value as ChatMessage[];
return (
<div className="p-2">
<Input.Search
className="mb-2"
placeholder="请输入关键字"
loading={loading}
onSearch={handleSearch}
/>
{/* Result List */}
<div>
{searchedMessages.length === 0 && (
<Empty description={t('没有任何搜索结果')} />
)}
{searchedMessages.map((message) => (
<NormalMessage
key={message._id}
showAvatar={true}
payload={message}
hideAction={true}
/>
))}
</div>
</div>
);
});
MessageSearchPanel.displayName = 'MessageSearchPanel';

@ -22,6 +22,7 @@ import {
import { useFriendNicknameMap } from 'tailchat-shared/redux/hooks/useFriendNickname';
import { MembersPanel } from './MembersPanel';
import { GroupPanelContainer } from './shared/GroupPanelContainer';
import { MessageSearchPanel } from '../common/MessageSearch';
/**
*
@ -131,6 +132,21 @@ export const TextPanel: React.FC<TextPanelProps> = React.memo(
})
}
/>,
<IconBtn
key="search"
title={t('聊天记录搜索')}
shape="square"
icon="mdi:text-search"
iconClassName="text-2xl"
onClick={() =>
setRightPanel({
name: t('聊天记录'),
panel: (
<MessageSearchPanel groupId={groupId} converseId={panelId} />
),
})
}
/>,
]}
>
<ChatInputMentionsContextProvider

@ -16,6 +16,7 @@ import { OpenedPanelTip } from '@/components/OpenedPanelTip';
import { IconBtn } from '@/components/IconBtn';
import { DMPluginPanelActionProps, pluginPanelActions } from '@/plugin/common';
import { CreateDMConverse } from '@/components/modals/CreateDMConverse';
import { MessageSearchPanel } from '../common/MessageSearch';
const ConversePanelTitle: React.FC<{ converse: ChatConverseState }> =
React.memo(({ converse }) => {
@ -135,6 +136,19 @@ export const ConversePanel: React.FC<ConversePanelProps> = React.memo(
}
/>
),
<IconBtn
key="search"
title={t('聊天记录搜索')}
shape="square"
icon="mdi:text-search"
iconClassName="text-2xl"
onClick={() =>
setRightPanel({
name: t('聊天记录'),
panel: <MessageSearchPanel converseId={converseId} />,
})
}
/>,
]);
}}
>

@ -404,6 +404,11 @@ class MessageService extends TcService {
content: {
$regex: text,
},
author: {
$not: {
$eq: SYSTEM_USERID,
},
},
})
.sort({ _id: -1 })
.limit(10)

Loading…
Cancel
Save