From acceba872655704a2fd2007fd5591baca305e997 Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Mon, 20 Sep 2021 16:47:50 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B6=88=E6=81=AF=E5=9B=9E=E5=A4=8D?= =?UTF-8?q?=E4=B8=8E=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- shared/contexts/ChatBoxContext.tsx | 50 ++++++++++++++ shared/i18n/langs/en-US/translation.json | 1 + shared/i18n/langs/zh-CN/translation.json | 1 + shared/index.tsx | 4 ++ shared/utils/msg-helper.ts | 4 ++ .../components/ChatBox/ChatBoxPlaceholder.tsx | 22 +++++++ .../ChatBox/ChatMessageList/Item.tsx | 38 +++++++++-- web/src/components/ChatBox/ChatReply.tsx | 36 ++++++++++ web/src/components/ChatBox/index.tsx | 65 ++++++++----------- 9 files changed, 178 insertions(+), 43 deletions(-) create mode 100644 shared/contexts/ChatBoxContext.tsx create mode 100644 shared/utils/msg-helper.ts create mode 100644 web/src/components/ChatBox/ChatBoxPlaceholder.tsx create mode 100644 web/src/components/ChatBox/ChatReply.tsx diff --git a/shared/contexts/ChatBoxContext.tsx b/shared/contexts/ChatBoxContext.tsx new file mode 100644 index 00000000..8bbb1c9e --- /dev/null +++ b/shared/contexts/ChatBoxContext.tsx @@ -0,0 +1,50 @@ +import React, { useCallback, useContext, useState } from 'react'; +import _noop from 'lodash/noop'; +import type { ReplyMsgType } from '../utils/msg-helper'; + +/** + * 一个消息盒的上下文 + */ + +interface ChatBoxContextProps { + replyMsg: ReplyMsgType | null; + setReplyMsg: (msg: ReplyMsgType | null) => void; +} +const ChatBoxContext = React.createContext({ + replyMsg: null, + setReplyMsg: _noop, +}); +ChatBoxContext.displayName = 'ChatBoxContext'; + +export const ChatBoxContextProvider: React.FC = React.memo((props) => { + const [replyMsg, setReplyMsg] = useState(null); + + return ( + + {props.children} + + ); +}); +ChatBoxContextProvider.displayName = 'ChatBoxContextProvider'; + +export function useChatBoxContext(): ChatBoxContextProps & { + hasContext: boolean; + clearReplyMsg: () => void; +} { + const context = useContext(ChatBoxContext); + const clearReplyMsg = useCallback(() => { + context.setReplyMsg(null); + }, [context.setReplyMsg]); + + return { + hasContext: context.setReplyMsg !== _noop, + replyMsg: context.replyMsg, + setReplyMsg: context.setReplyMsg, + clearReplyMsg, + }; +} diff --git a/shared/i18n/langs/en-US/translation.json b/shared/i18n/langs/en-US/translation.json index ae0cc111..e31f5244 100644 --- a/shared/i18n/langs/en-US/translation.json +++ b/shared/i18n/langs/en-US/translation.json @@ -144,6 +144,7 @@ "kf7d829eb": "Wait to process", "kf87f3059": "Plugin Store", "kfa01c850": "No private message found", + "kfa493f3f": "Reply", "kfaddd61d": "Chat Service", "kfbecf2a7": "Are you sure you want to delete the panel group [{{name}}] and all subordinate panels" } diff --git a/shared/i18n/langs/zh-CN/translation.json b/shared/i18n/langs/zh-CN/translation.json index da138c48..3ba8c041 100644 --- a/shared/i18n/langs/zh-CN/translation.json +++ b/shared/i18n/langs/zh-CN/translation.json @@ -144,6 +144,7 @@ "kf7d829eb": "待处理", "kf87f3059": "插件中心", "kfa01c850": "找不到私信会话", + "kfa493f3f": "回复", "kfaddd61d": "聊天服务", "kfbecf2a7": "确定要删除面板组 【{{name}}】 以及下级的所有面板么" } diff --git a/shared/index.tsx b/shared/index.tsx index 1fc1f3e1..4c9142ac 100644 --- a/shared/index.tsx +++ b/shared/index.tsx @@ -34,6 +34,10 @@ export { buildPortal, DefaultEventEmitter } from './components/Portal'; export { TcProvider } from './components/Provider'; // contexts +export { + ChatBoxContextProvider, + useChatBoxContext, +} from './contexts/ChatBoxContext'; export { useColorScheme } from './contexts/ColorSchemeContext'; // i18n diff --git a/shared/utils/msg-helper.ts b/shared/utils/msg-helper.ts new file mode 100644 index 00000000..6aba24f6 --- /dev/null +++ b/shared/utils/msg-helper.ts @@ -0,0 +1,4 @@ +import type { ChatMessage } from '../model/message'; + +const replyMsgFields = ['_id', 'content', 'author'] as const; +export type ReplyMsgType = Pick; diff --git a/web/src/components/ChatBox/ChatBoxPlaceholder.tsx b/web/src/components/ChatBox/ChatBoxPlaceholder.tsx new file mode 100644 index 00000000..17323068 --- /dev/null +++ b/web/src/components/ChatBox/ChatBoxPlaceholder.tsx @@ -0,0 +1,22 @@ +import { Skeleton } from 'antd'; +import React from 'react'; + +export const ChatBoxPlaceholder: React.FC = React.memo(() => { + const paragraph = { rows: 1 }; + + return ( +
+ + + + + + + + + + +
+ ); +}); +ChatBoxPlaceholder.displayName = 'ChatBoxPlaceholder'; diff --git a/web/src/components/ChatBox/ChatMessageList/Item.tsx b/web/src/components/ChatBox/ChatMessageList/Item.tsx index f965010a..2f93dd3e 100644 --- a/web/src/components/ChatBox/ChatMessageList/Item.tsx +++ b/web/src/components/ChatBox/ChatMessageList/Item.tsx @@ -2,12 +2,32 @@ import React from 'react'; import { ChatMessage, formatShortTime, + t, useCachedUserInfo, + useChatBoxContext, } from 'tailchat-shared'; import { Avatar } from '@/components/Avatar'; -import clsx from 'clsx'; import { useRenderPluginMessageInterpreter } from './useRenderPluginMessageInterpreter'; import { getMessageRender } from '@/plugin/common'; +import { Icon } from '@iconify/react'; +import { Dropdown, Menu } from 'antd'; + +/** + * 消息的会话信息 + */ +function useChatMessageItemAction(payload: ChatMessage): React.ReactElement { + const context = useChatBoxContext(); + + return ( + + {context.hasContext && ( + context.setReplyMsg(payload)}> + {t('回复')} + + )} + + ); +} interface ChatMessageItemProps { showAvatar: boolean; @@ -18,10 +38,11 @@ export const ChatMessageItem: React.FC = React.memo( const { showAvatar, payload } = props; const userInfo = useCachedUserInfo(payload.author ?? ''); + const actions = useChatMessageItemAction(payload); + return ( -
+
+ {/* 头像 */}
{showAvatar ? ( @@ -31,6 +52,8 @@ export const ChatMessageItem: React.FC = React.memo(
)}
+ + {/* 主体 */}
{showAvatar && (
@@ -48,6 +71,13 @@ export const ChatMessageItem: React.FC = React.memo( {useRenderPluginMessageInterpreter(payload.content)}
+ + {/* 操作 */} + +
+ +
+
); } diff --git a/web/src/components/ChatBox/ChatReply.tsx b/web/src/components/ChatBox/ChatReply.tsx new file mode 100644 index 00000000..db6866db --- /dev/null +++ b/web/src/components/ChatBox/ChatReply.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { t, useChatBoxContext } from 'tailchat-shared'; +import _isNil from 'lodash/isNil'; +import { getMessageRender } from '@/plugin/common'; +import { UserName } from '../UserName'; +import { Icon } from '@iconify/react'; + +export const ChatReply: React.FC = React.memo(() => { + const { replyMsg, clearReplyMsg } = useChatBoxContext(); + + if (_isNil(replyMsg)) { + return null; + } + + return ( +
+
+
+ + {t('回复')}{' '} + {replyMsg.author && }:{' '} + + + {getMessageRender(replyMsg.content)} + + +
+
+
+ ); +}); +ChatReply.displayName = 'ChatReply'; diff --git a/web/src/components/ChatBox/index.tsx b/web/src/components/ChatBox/index.tsx index 9d0d0fe7..7dd784dc 100644 --- a/web/src/components/ChatBox/index.tsx +++ b/web/src/components/ChatBox/index.tsx @@ -1,29 +1,12 @@ -import { Skeleton } from 'antd'; import React, { useRef } from 'react'; -import { useConverseMessage } from 'tailchat-shared'; +import { ChatBoxContextProvider, useConverseMessage } from 'tailchat-shared'; import { AlertErrorView } from '../AlertErrorView'; +import { ChatBoxPlaceholder } from './ChatBoxPlaceholder'; import { ChatInputBox } from './ChatInputBox'; import { ChatMessageList, ChatMessageListRef } from './ChatMessageList'; +import { ChatReply } from './ChatReply'; import { useMessageAck } from './useMessageAck'; -const ChatBoxPlaceholder: React.FC = React.memo(() => { - return ( -
- - - - - - - - - - -
- ); -}); -ChatBoxPlaceholder.displayName = 'ChatBoxPlaceholder'; - type ChatBoxProps = | { converseId: string; @@ -53,26 +36,30 @@ export const ChatBox: React.FC = React.memo((props) => { } return ( -
- + +
+ + + - { - // 发送消息后滚动到底部 - handleSendMessage({ - converseId: props.converseId, - groupId: props.groupId, - content: msg, - }).then(() => { - chatMessageListRef.current?.scrollToBottom(); - }); - }} - /> -
+ { + // 发送消息后滚动到底部 + handleSendMessage({ + converseId: props.converseId, + groupId: props.groupId, + content: msg, + }).then(() => { + chatMessageListRef.current?.scrollToBottom(); + }); + }} + /> +
+ ); }); ChatBox.displayName = 'ChatBox';