mirror of https://github.com/msgbyte/tailchat
feat: 增加emoji表情管理
parent
9642f2c436
commit
71ff176a66
@ -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 (
|
||||||
|
<Menu>
|
||||||
|
{context.hasContext && (
|
||||||
|
<Menu.Item
|
||||||
|
key="reply"
|
||||||
|
icon={<Icon icon="mdi:reply" />}
|
||||||
|
onClick={() => context.setReplyMsg(payload)}
|
||||||
|
>
|
||||||
|
{t('回复')}
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(isGroupOwner || isMessageAuthor) && (
|
||||||
|
<Menu.Item
|
||||||
|
key="recall"
|
||||||
|
icon={<Icon icon="mdi:restore" />}
|
||||||
|
onClick={handleRecallMessage}
|
||||||
|
>
|
||||||
|
{t('撤回')}
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 仅群组管理员可见 */}
|
||||||
|
{isGroupOwner && (
|
||||||
|
<Menu.Item
|
||||||
|
key="delete"
|
||||||
|
danger={true}
|
||||||
|
icon={<Icon icon="mdi:delete-outline" />}
|
||||||
|
onClick={handleDeleteMessage}
|
||||||
|
>
|
||||||
|
{t('删除')}
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
@ -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 <EmojiPanel onSelect={handleSelect} />;
|
||||||
|
}
|
@ -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<EmojiPickerProps> = React.memo((props) => {
|
||||||
|
const handleSelect = useCallback(
|
||||||
|
(emoji: EmojiData) => {
|
||||||
|
const code = emoji.colons;
|
||||||
|
if (isValidStr(code)) {
|
||||||
|
props.onSelect(code);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[props.onSelect]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NimblePicker set="twitter" data={emojiData} onSelect={handleSelect} />
|
||||||
|
);
|
||||||
|
});
|
||||||
|
EmojiPicker.displayName = 'EmojiPicker';
|
||||||
|
|
||||||
|
export default EmojiPicker;
|
@ -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 <EmojiPicker onSelect={props.onSelect} />;
|
||||||
|
});
|
||||||
|
EmojiPanel.displayName = 'EmojiPanel';
|
Loading…
Reference in New Issue