feat: 增加代码允许输入表情

pull/81/head
moonrailgun 3 years ago
parent a323911e1e
commit 628148fb6f

@ -21,4 +21,25 @@ regMessageTextDecorators(() => ({
return `[img]${plain}[/img]`;
},
mention: (userId, userName) => `[at=${userId}]${userName}[/at]`,
emoji: (emojiCode) => `[emoji]${stripColons(emojiCode)}[/emoji]`,
}));
/**
* Removes colons on either side
* of the string if present
*/
function stripColons(str: string): string {
const colonIndex = str.indexOf(':');
if (colonIndex > -1) {
// :emoji: (http://www.emoji-cheat-sheet.com/)
if (colonIndex === str.length - 1) {
str = str.substring(0, colonIndex);
return stripColons(str);
} else {
str = str.substr(colonIndex + 1);
return stripColons(str);
}
}
return str;
}

@ -0,0 +1,11 @@
import { Emoji } from '@capital/component';
import React from 'react';
import type { TagProps } from '../bbcode/type';
export const EmojiTag: React.FC<TagProps> = React.memo((props) => {
const { node } = props;
const code = node.content.join('');
return <Emoji emoji={code} />;
});
EmojiTag.displayName = 'EmojiTag';

@ -4,6 +4,7 @@ import { ImgTag } from './ImgTag';
import { MentionTag } from './MentionTag';
import { PlainText } from './PlainText';
import { UrlTag } from './UrlTag';
import { EmojiTag } from './EmojiTag';
import './styles.less';
@ -12,3 +13,4 @@ registerBBCodeTag('url', UrlTag);
registerBBCodeTag('img', ImgTag);
registerBBCodeTag('code', CodeTag);
registerBBCodeTag('at', MentionTag);
registerBBCodeTag('emoji', EmojiTag);

@ -42,7 +42,7 @@ const AppContainer: React.FC = React.memo((props) => {
id="tailchat-app"
className={clsx(
'tailchat-app',
'absolute inset-0 select-none',
'absolute inset-0 select-none overflow-hidden',
{
dark: isDarkMode,
},

@ -0,0 +1,38 @@
import { Icon } from '@/components/Icon';
import { Dropdown } from 'antd';
import React, { useCallback, useState } from 'react';
import { useChatInputActionContext } from './context';
import { EmojiPanel } from '@/components/Emoji';
export const ChatInputEmotion: React.FC = React.memo(() => {
const actionContext = useChatInputActionContext();
const { appendMsg } = actionContext;
const [visible, setVisible] = useState(false);
const handleSelect = useCallback(
async (code: string) => {
appendMsg(code);
setVisible(false);
},
[appendMsg]
);
const menu = <EmojiPanel onSelect={handleSelect} />;
return (
<Dropdown
visible={visible}
onVisibleChange={setVisible}
overlay={menu}
placement="topRight"
trigger={['click']}
>
<Icon
className="text-2xl cursor-pointer"
icon="mdi:emoticon-happy-outline"
/>
</Dropdown>
);
});
ChatInputEmotion.displayName = 'ChatInputEmotion';

@ -6,9 +6,12 @@ import type { SuggestionDataItem } from 'react-mentions';
*/
export interface ChatInputActionContextProps {
sendMsg: (message: string) => void;
appendMsg: (message: string) => void;
}
export const ChatInputActionContext =
React.createContext<ChatInputActionContextProps | null>(null);
React.createContext<ChatInputActionContextProps>(
{} as ChatInputActionContextProps
);
ChatInputActionContext.displayName = 'ChatInputContext';
export function useChatInputActionContext() {

@ -12,6 +12,7 @@ import {
SendMessagePayloadMeta,
useSharedEventHandler,
} from 'tailchat-shared';
import { ChatInputEmotion } from './Emotion';
interface ChatInputBoxProps {
onSendMsg: (msg: string, meta?: SendMessagePayloadMeta) => void;
@ -28,6 +29,15 @@ export const ChatInputBox: React.FC<ChatInputBoxProps> = React.memo((props) => {
inputRef.current?.focus();
}, [message, mentions]);
const handleAppendMsg = useCallback(
(append: string) => {
setMessage(message + append);
inputRef.current?.focus();
},
[message]
);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => {
if (isEnterHotkey(e.nativeEvent)) {
@ -71,7 +81,12 @@ export const ChatInputBox: React.FC<ChatInputBoxProps> = React.memo((props) => {
});
return (
<ChatInputActionContext.Provider value={{ sendMsg: props.onSendMsg }}>
<ChatInputActionContext.Provider
value={{
sendMsg: props.onSendMsg,
appendMsg: handleAppendMsg,
}}
>
<div className="px-4 py-2">
<div className="bg-white dark:bg-gray-600 flex rounded-md items-center">
<ChatInputBoxInput
@ -85,7 +100,8 @@ export const ChatInputBox: React.FC<ChatInputBoxProps> = React.memo((props) => {
onPaste={handlePaste}
/>
<div className="px-2">
<div className="px-2 flex space-x-1">
<ChatInputEmotion />
<ChatInputAddon />
</div>
</div>

@ -12,8 +12,22 @@
align-items: center;
}
}
.chat-message-item_body {
.emoji-mart-emoji {
height: 16px;
vertical-align: middle;
display: inline-block;
margin: 0 4px;
> span {
vertical-align: top;
}
}
}
}
.chat-message-item_action-popover {
padding-top: 0;

@ -104,7 +104,7 @@ const NormalMessage: React.FC<ChatMessageItemProps> = React.memo((props) => {
)}
{/* 消息内容 */}
<div className="leading-6 break-words">
<div className="chat-message-item_body leading-6 break-words">
<MessageQuote payload={payload} />
<span>{getMessageRender(payload.content)}</span>

@ -0,0 +1,12 @@
import { regMessageTextDecorators } from '@/plugin/common';
import { preprocessMessage } from '../preprocessMessage';
regMessageTextDecorators(() => ({
emoji: (code) => `[emoji]${code.substring(1, code.length - 1)}[/emoji]`,
}));
test('preprocessMessage', () => {
expect(preprocessMessage('anystring :face: anystring :heart:')).toBe(
'anystring [emoji]face[/emoji] anystring [emoji]heart[/emoji]'
);
});

@ -5,6 +5,7 @@ import { ChatBoxPlaceholder } from './ChatBoxPlaceholder';
import { ChatInputBox } from './ChatInputBox';
import { ChatMessageList } from './ChatMessageList';
import { ChatReply } from './ChatReply';
import { preprocessMessage } from './preprocessMessage';
import { useMessageAck } from './useMessageAck';
type ChatBoxProps =
@ -59,7 +60,7 @@ const ChatBoxInner: React.FC<ChatBoxProps> = React.memo((props) => {
handleSendMessage({
converseId: props.converseId,
groupId: props.groupId,
content: msg,
content: preprocessMessage(msg),
meta,
});
}}

@ -0,0 +1,15 @@
import { getMessageTextDecorators } from '@/plugin/common';
const emojiNameRegex = /:([a-zA-Z0-9_\-\+]+):/g;
/**
*
*/
export function preprocessMessage(message: string) {
/**
* emoji
*/
return message.replaceAll(emojiNameRegex, (code) =>
getMessageTextDecorators().emoji(code)
);
}

@ -38,16 +38,14 @@ export class ErrorBoundary extends React.Component<
typeof description === 'undefined' ? componentStack : description;
if (error) {
return (
<div className="p-2">
<Problem
text={
<>
<h3>{t('页面出现了一些问题')}</h3>
<p title={errorDescription ?? ''}>{errorMessage}</p>
</>
}
/>
</div>
<Problem
text={
<>
<h3>{t('页面出现了一些问题')}</h3>
<p title={errorDescription ?? ''}>{errorMessage}</p>
</>
}
/>
);
}

@ -106,6 +106,7 @@ const defaultMessageTextDecorators = {
url: (plain: string) => plain,
image: (plain: string, attrs: Record<string, unknown>) => plain,
mention: (userId: string, userName: string) => `@${userName}`,
emoji: (emojiCode: string) => emojiCode,
};
const [_getMessageTextDecorators, regMessageTextDecorators] = buildRegFn<
() => Partial<typeof defaultMessageTextDecorators>

@ -32,3 +32,4 @@ export {
export { Loading } from '@/components/Loading';
export { SidebarView } from '@/components/SidebarView';
export { GroupPanelSelector } from '@/components/GroupPanelSelector';
export { Emoji } from '@/components/Emoji';

Loading…
Cancel
Save