diff --git a/tsconfig.json b/tsconfig.json
index ee574a85..2dfb7932 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -9,6 +9,7 @@
"moduleResolution": "node",
"strict": true,
"importsNotUsedAsValues": "error",
+ "resolveJsonModule": true,
"typeRoots": ["./node_modules/@types", "../node_modules/@types", "./types"]
}
}
diff --git a/web/package.json b/web/package.json
index be53c9c8..d9af4a3b 100644
--- a/web/package.json
+++ b/web/package.json
@@ -23,6 +23,7 @@
"@loadable/component": "^5.15.0",
"antd": "^4.16.6",
"clsx": "^1.1.1",
+ "emoji-mart": "^3.0.1",
"is-hotkey": "^0.2.0",
"jsonschema": "^1.4.0",
"jwt-decode": "^3.1.2",
@@ -51,6 +52,7 @@
"@testing-library/react-hooks": "^7.0.1",
"@types/copy-webpack-plugin": "^8.0.0",
"@types/dts-generator": "^2.1.6",
+ "@types/emoji-mart": "^3.0.8",
"@types/is-hotkey": "^0.1.5",
"@types/loadable__component": "^5.13.4",
"@types/mini-css-extract-plugin": "^1.4.3",
diff --git a/web/src/components/ChatBox/ChatMessageList/Item.tsx b/web/src/components/ChatBox/ChatMessageList/Item.tsx
index d24c0cb1..a78e903d 100644
--- a/web/src/components/ChatBox/ChatMessageList/Item.tsx
+++ b/web/src/components/ChatBox/ChatMessageList/Item.tsx
@@ -7,80 +7,21 @@ import {
SYSTEM_USERID,
t,
useCachedUserInfo,
- useChatBoxContext,
MessageHelper,
- recallMessage,
useAsync,
getCachedUserInfo,
- useAsyncRequest,
- deleteMessage,
- useGroupInfoContext,
- useUserInfo,
} from 'tailchat-shared';
import { Avatar } from '@/components/Avatar';
import { useRenderPluginMessageInterpreter } from './useRenderPluginMessageInterpreter';
import { getMessageRender } from '@/plugin/common';
import { Icon } from '@iconify/react';
-import { Divider, Dropdown, Menu } from 'antd';
+import { Divider, Dropdown } from 'antd';
import { UserName } from '@/components/UserName';
import './item.less';
import clsx from 'clsx';
-
-/**
- * 消息的会话操作
- */
-function useChatMessageItemAction(payload: ChatMessage): React.ReactElement {
- const context = useChatBoxContext();
- const groupInfo = useGroupInfoContext();
- const userInfo = useUserInfo();
-
- const [, handleRecallMessage] = useAsyncRequest(() => {
- return recallMessage(payload._id);
- }, [payload._id]);
-
- const [, handleDeleteMessage] = useAsyncRequest(() => {
- return deleteMessage(payload._id);
- }, [payload._id]);
-
- const isGroupOwner = groupInfo && groupInfo.owner === userInfo?._id; //
- const isMessageAuthor = payload.author === userInfo?._id;
-
- return (
-
- );
-}
+import { useChatMessageItemAction } from './useChatMessageItemAction';
+import { useChatMessageReaction } from './useChatMessageReaction';
+import { DevContainer } from '@/components/DevContainer';
/**
* 消息引用
@@ -106,6 +47,12 @@ const MessageQuote: React.FC<{ payload: ChatMessage }> = React.memo(
);
MessageQuote.displayName = 'MessageQuote';
+const MessageActionIcon: React.FC<{ icon: string }> = (props) => (
+
+
+
+);
+
/**
* 普通消息
*/
@@ -114,7 +61,8 @@ const NormalMessage: React.FC = React.memo((props) => {
const userInfo = useCachedUserInfo(payload.author ?? '');
const [isActionBtnActive, setIsActionBtnActive] = useState(false);
- const actions = useChatMessageItemAction(payload);
+ const emojiAction = useChatMessageReaction(payload);
+ const moreActions = useChatMessageItemAction(payload);
return (
= React.memo((props) => {
{/* 操作 */}
-
-
-
+
+
+
+
+
);
});
diff --git a/web/src/components/ChatBox/ChatMessageList/useChatMessageItemAction.tsx b/web/src/components/ChatBox/ChatMessageList/useChatMessageItemAction.tsx
new file mode 100644
index 00000000..b43ea2de
--- /dev/null
+++ b/web/src/components/ChatBox/ChatMessageList/useChatMessageItemAction.tsx
@@ -0,0 +1,71 @@
+import { Icon } from '@iconify/react';
+import { Menu } from 'antd';
+import React from 'react';
+import {
+ ChatMessage,
+ deleteMessage,
+ recallMessage,
+ t,
+ useAsyncRequest,
+ useChatBoxContext,
+ useGroupInfoContext,
+ useUserInfo,
+} from 'tailchat-shared';
+
+/**
+ * 消息的会话操作
+ */
+export function useChatMessageItemAction(
+ payload: ChatMessage
+): React.ReactElement {
+ const context = useChatBoxContext();
+ const groupInfo = useGroupInfoContext();
+ const userInfo = useUserInfo();
+
+ const [, handleRecallMessage] = useAsyncRequest(() => {
+ return recallMessage(payload._id);
+ }, [payload._id]);
+
+ const [, handleDeleteMessage] = useAsyncRequest(() => {
+ return deleteMessage(payload._id);
+ }, [payload._id]);
+
+ const isGroupOwner = groupInfo && groupInfo.owner === userInfo?._id; //
+ const isMessageAuthor = payload.author === userInfo?._id;
+
+ return (
+
+ );
+}
diff --git a/web/src/components/ChatBox/ChatMessageList/useChatMessageReaction.tsx b/web/src/components/ChatBox/ChatMessageList/useChatMessageReaction.tsx
new file mode 100644
index 00000000..7e48832f
--- /dev/null
+++ b/web/src/components/ChatBox/ChatMessageList/useChatMessageReaction.tsx
@@ -0,0 +1,16 @@
+import { EmojiPanel } from '@/components/EmojiPanel';
+import React, { useCallback } from 'react';
+import type { ChatMessage } from 'tailchat-shared';
+
+/**
+ * 消息的反应信息操作
+ */
+export function useChatMessageReaction(
+ payload: ChatMessage
+): React.ReactElement {
+ const handleSelect = useCallback((code: string) => {
+ console.log('code', code);
+ }, []);
+
+ return ;
+}
diff --git a/web/src/components/EmojiPanel/Picker.tsx b/web/src/components/EmojiPanel/Picker.tsx
new file mode 100644
index 00000000..13ea2bb3
--- /dev/null
+++ b/web/src/components/EmojiPanel/Picker.tsx
@@ -0,0 +1,80 @@
+import React, { useCallback } from 'react';
+import { NimblePicker, Data, EmojiData } from 'emoji-mart';
+import data from 'emoji-mart/data/twitter.json';
+
+import 'emoji-mart/css/emoji-mart.css';
+import { isValidStr } from 'tailchat-shared';
+
+const emojiData: Data = {
+ compressed: true,
+ categories: [
+ {
+ id: 'people',
+ name: 'Smileys & People',
+ emojis: data.categories[0].emojis,
+ },
+ {
+ id: 'nature',
+ name: 'Animals & Nature',
+ emojis: data.categories[1].emojis,
+ },
+ {
+ id: 'foods',
+ name: 'Food & Drink',
+ emojis: data.categories[2].emojis,
+ },
+ {
+ id: 'activity',
+ name: 'Activities',
+ emojis: data.categories[3].emojis,
+ },
+ {
+ id: 'places',
+ name: 'Travel & Places',
+ emojis: data.categories[4].emojis,
+ },
+ {
+ id: 'objects',
+ name: 'Objects',
+ emojis: data.categories[5].emojis,
+ },
+ {
+ id: 'symbols',
+ name: 'Symbols',
+ emojis: data.categories[6].emojis,
+ },
+ {
+ id: 'flags',
+ name: 'Flags',
+ emojis: data.categories[7].emojis,
+ },
+ ],
+ emojis: data.emojis,
+ aliases: data.aliases,
+};
+
+interface EmojiPickerProps {
+ onSelect: (code: string) => void;
+}
+
+/**
+ * emoji表情面板
+ */
+const EmojiPicker: React.FC = React.memo((props) => {
+ const handleSelect = useCallback(
+ (emoji: EmojiData) => {
+ const code = emoji.colons;
+ if (isValidStr(code)) {
+ props.onSelect(code);
+ }
+ },
+ [props.onSelect]
+ );
+
+ return (
+
+ );
+});
+EmojiPicker.displayName = 'EmojiPicker';
+
+export default EmojiPicker;
diff --git a/web/src/components/EmojiPanel/index.tsx b/web/src/components/EmojiPanel/index.tsx
new file mode 100644
index 00000000..8450e8b1
--- /dev/null
+++ b/web/src/components/EmojiPanel/index.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import { Loadable } from '../Loadable';
+
+const EmojiPicker = Loadable(
+ () =>
+ import(
+ /* webpackChunkName: 'emoji-picker' */ /* webpackPrefetch: true */ './Picker'
+ )
+);
+
+/**
+ * emoji表情面板
+ */
+export const EmojiPanel: React.FC<{
+ onSelect: (code: string) => void;
+}> = React.memo((props) => {
+ return ;
+});
+EmojiPanel.displayName = 'EmojiPanel';
diff --git a/yarn.lock b/yarn.lock
index febbaa91..7074b1a1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1054,6 +1054,13 @@
core-js-pure "^3.15.0"
regenerator-runtime "^0.13.4"
+"@babel/runtime@^7.0.0":
+ version "7.16.3"
+ resolved "https://registry.npmmirror.com/@babel/runtime/download/@babel/runtime-7.16.3.tgz?cache=0&sync_timestamp=1636494920863&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40babel%2Fruntime%2Fdownload%2F%40babel%2Fruntime-7.16.3.tgz#b86f0db02a04187a3c17caa77de69840165d42d5"
+ integrity sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.7", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
version "7.14.6"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d"
@@ -1858,6 +1865,13 @@
"@types/bluebird" "*"
typescript "*"
+"@types/emoji-mart@^3.0.8":
+ version "3.0.8"
+ resolved "https://registry.npmmirror.com/@types/emoji-mart/download/@types/emoji-mart-3.0.8.tgz#b0de7aabf39b5c4b6e378432bb3b99dbf903bc04"
+ integrity sha1-sN56q/ObXEtuN4QyuzuZ2/kDvAQ=
+ dependencies:
+ "@types/react" "*"
+
"@types/eslint-scope@^3.7.0":
version "3.7.0"
resolved "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.0.tgz#4792816e31119ebd506902a482caec4951fabd86"
@@ -4538,6 +4552,14 @@ emittery@^0.8.1:
resolved "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860"
integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==
+emoji-mart@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.npm.taobao.org/emoji-mart/download/emoji-mart-3.0.1.tgz#9ce86706e02aea0506345f98464814a662ca54c6"
+ integrity sha1-nOhnBuAq6gUGNF+YRkgUpmLKVMY=
+ dependencies:
+ "@babel/runtime" "^7.0.0"
+ prop-types "^15.6.0"
+
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
@@ -8566,7 +8588,7 @@ prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.5"
-prop-types@^15.6.2, prop-types@^15.7.2:
+prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==