From 176528b303c5d25f26ad364f2f5c5cdd4d1cff61 Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Thu, 2 Nov 2023 00:21:01 +0800 Subject: [PATCH] feat: add text panel message search right panel --- client/shared/hooks/useAsyncRequest.ts | 1 - client/shared/model/message.ts | 19 ++ client/shared/utils/date-helper.ts | 2 +- .../ChatBox/ChatMessageList/Item.tsx | 267 +++++++++--------- .../components/Panel/common/MessageSearch.tsx | 62 ++++ .../src/components/Panel/group/TextPanel.tsx | 16 ++ .../Panel/personal/ConversePanel.tsx | 14 + server/services/core/chat/message.service.ts | 5 + 8 files changed, 253 insertions(+), 133 deletions(-) create mode 100644 client/web/src/components/Panel/common/MessageSearch.tsx diff --git a/client/shared/hooks/useAsyncRequest.ts b/client/shared/hooks/useAsyncRequest.ts index 07555099403..0a1da0dd98f 100644 --- a/client/shared/hooks/useAsyncRequest.ts +++ b/client/shared/hooks/useAsyncRequest.ts @@ -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'; diff --git a/client/shared/model/message.ts b/client/shared/model/message.ts index 186d53c3b4d..023dacb0fb4 100644 --- a/client/shared/model/message.ts +++ b/client/shared/model/message.ts @@ -84,6 +84,25 @@ export async function deleteMessage(messageId: string): Promise { return data; } +/** + * 搜索聊天记录 + * @param converseId 会话id + * @param messageText 聊天文本 + */ +export async function searchMessage( + text: string, + converseId: string, + groupId?: string +): Promise { + const { data } = await request.post('/api/chat/message/searchMessage', { + text, + converseId, + groupId, + }); + + return data; +} + /** * 基于会话id获取会话最后一条消息的id */ diff --git a/client/shared/utils/date-helper.ts b/client/shared/utils/date-helper.ts index 60f2dd86ca0..eace9cca2f2 100644 --- a/client/shared/utils/date-helper.ts +++ b/client/shared/utils/date-helper.ts @@ -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'); diff --git a/client/web/src/components/ChatBox/ChatMessageList/Item.tsx b/client/web/src/components/ChatBox/ChatMessageList/Item.tsx index ab2d585b8cd..f900a50ad79 100644 --- a/client/web/src/components/ChatBox/ChatMessageList/Item.tsx +++ b/client/web/src/components/ChatBox/ChatMessageList/Item.tsx @@ -61,156 +61,160 @@ const MessageActionIcon: React.FC<{ icon: string }> = (props) => ( /** * 普通消息 */ -const NormalMessage: React.FC = React.memo((props) => { - const { showAvatar, payload } = props; - const userInfo = useCachedUserInfo(payload.author ?? ''); - const [isActionBtnActive, setIsActionBtnActive] = useState(false); +export const NormalMessage: React.FC = 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 ( -
- {/* 头像 */} -
- {showAvatar ? ( - - ) - } - placement="top" - trigger="click" - > - - - ) : ( -
- {formatShortTime(payload.createdAt)} -
- )} -
- - {/* 主体 */} + return (
- {showAvatar && ( -
-
- {userInfo.nickname ??  } -
-
+ {/* 头像 */} +
+ {showAvatar ? ( + + ) + } + placement="top" + trigger="click" + > + + + ) : ( +
{formatShortTime(payload.createdAt)}
-
- )} + )} +
- {/* 消息内容 */} - - {t('点击展开更多')} -
- } + {/* 主体 */} +
-
- + {showAvatar && ( +
+
+ {userInfo.nickname ??  } +
+
+ {formatShortTime(payload.createdAt)} +
+
+ )} - {getMessageRender(payload.content)} + {/* 消息内容 */} + + {t('点击展开更多')} +
+ } + > +
+ - {payload.sendFailed === true && ( - - )} + {getMessageRender(payload.content)} + + {payload.sendFailed === true && ( + + )} + + {/* 解释器按钮 */} + {useRenderPluginMessageInterpreter(payload.content)} +
+ - {/* 解释器按钮 */} - {useRenderPluginMessageInterpreter(payload.content)} + {/* 额外渲染 */} +
+ {pluginMessageExtraParsers.map((parser) => ( + + {parser.render(payload)} + + ))}
- - {/* 额外渲染 */} -
- {pluginMessageExtraParsers.map((parser) => ( - - {parser.render(payload)} - - ))} + {/* 消息反应 */} + {reactions}
- {/* 消息反应 */} - {reactions} -
- - {/* 操作 */} - {!disableOperate && ( -
- -
- -
-
+ +
+ +
+
- -
- -
-
-
- )} -
- ); -}); + +
+ +
+
+
+ )} + + ); + } +); NormalMessage.displayName = 'NormalMessage'; /** @@ -250,6 +254,7 @@ SystemMessageWithNickname.displayName = 'SystemMessageWithNickname'; interface ChatMessageItemProps { showAvatar: boolean; payload: LocalChatMessage; + hideAction?: boolean; } const ChatMessageItem: React.FC = React.memo((props) => { const payload = props.payload; diff --git a/client/web/src/components/Panel/common/MessageSearch.tsx b/client/web/src/components/Panel/common/MessageSearch.tsx new file mode 100644 index 00000000000..68af95ee62c --- /dev/null +++ b/client/web/src/components/Panel/common/MessageSearch.tsx @@ -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 ( +
+ + + {/* Result List */} +
+ {searchedMessages.length === 0 && ( + + )} + + {searchedMessages.map((message) => ( + + ))} +
+
+ ); +}); +MessageSearchPanel.displayName = 'MessageSearchPanel'; diff --git a/client/web/src/components/Panel/group/TextPanel.tsx b/client/web/src/components/Panel/group/TextPanel.tsx index 42046caa44c..5f181453900 100644 --- a/client/web/src/components/Panel/group/TextPanel.tsx +++ b/client/web/src/components/Panel/group/TextPanel.tsx @@ -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 = React.memo( }) } />, + + setRightPanel({ + name: t('聊天记录'), + panel: ( + + ), + }) + } + />, ]} > = React.memo(({ converse }) => { @@ -135,6 +136,19 @@ export const ConversePanel: React.FC = React.memo( } /> ), + + setRightPanel({ + name: t('聊天记录'), + panel: , + }) + } + />, ]); }} > diff --git a/server/services/core/chat/message.service.ts b/server/services/core/chat/message.service.ts index 0a763e398f0..22acc9db5b4 100644 --- a/server/services/core/chat/message.service.ts +++ b/server/services/core/chat/message.service.ts @@ -404,6 +404,11 @@ class MessageService extends TcService { content: { $regex: text, }, + author: { + $not: { + $eq: SYSTEM_USERID, + }, + }, }) .sort({ _id: -1 }) .limit(10)