feat: 文本输入框增加了mention功能

pull/81/head
moonrailgun 3 years ago
parent a911fecad4
commit 9ff4f8858f

File diff suppressed because it is too large Load Diff

@ -1,2 +1 @@
# https://npm.taobao.org/mirrors/
registry = https://registry.npm.taobao.org
registry = https://registry.npmmirror.com

@ -40,6 +40,7 @@
"react-dom": "17.0.2",
"react-easy-crop": "^3.5.2",
"react-helmet": "^6.1.0",
"react-mentions": "^4.3.1",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
"react-split": "^2.0.14",
@ -68,6 +69,7 @@
"@types/node": "^15.12.5",
"@types/react-dom": "^17.0.8",
"@types/react-helmet": "^6.1.2",
"@types/react-mentions": "^4.1.5",
"@types/react-router": "^5.1.15",
"@types/react-router-dom": "^5.1.7",
"@types/react-transition-group": "^4.4.2",

@ -1,12 +0,0 @@
import React, { useContext } from 'react';
export interface ChatInputActionContextProps {
sendMsg: (message: string) => void;
}
export const ChatInputActionContext =
React.createContext<ChatInputActionContextProps | null>(null);
ChatInputActionContext.displayName = 'ChatInputContext';
export function useChatInputActionContext() {
return useContext(ChatInputActionContext);
}

@ -0,0 +1,45 @@
import React, { useContext } from 'react';
import type { SuggestionDataItem } from 'react-mentions';
/**
* Input Actions
*/
export interface ChatInputActionContextProps {
sendMsg: (message: string) => void;
}
export const ChatInputActionContext =
React.createContext<ChatInputActionContextProps | null>(null);
ChatInputActionContext.displayName = 'ChatInputContext';
export function useChatInputActionContext() {
return useContext(ChatInputActionContext);
}
/**
* Input Mentions
*/
interface ChatInputMentionsContextProps {
users: SuggestionDataItem[];
}
const ChatInputMentionsContext =
React.createContext<ChatInputMentionsContextProps | null>(null);
ChatInputMentionsContext.displayName = 'ChatInputMentionsContext';
export const ChatInputMentionsContextProvider: React.FC<ChatInputMentionsContextProps> =
React.memo((props) => {
return (
<ChatInputMentionsContext.Provider value={{ users: props.users }}>
{props.children}
</ChatInputMentionsContext.Provider>
);
});
ChatInputMentionsContextProvider.displayName =
'ChatInputMentionsContextProvider';
export function useChatInputMentionsContext(): ChatInputMentionsContextProps {
const context = useContext(ChatInputMentionsContext);
return {
users: context?.users ?? [],
};
}

@ -1,18 +1,17 @@
import { getMessageTextDecorators } from '@/plugin/common';
import { isEnterHotkey } from '@/utils/hot-key';
import { Input } from 'antd';
import React, { useCallback, useRef, useState } from 'react';
import { t } from 'tailchat-shared';
import { ChatInputAddon } from './Addon';
import { ClipboardHelper } from './clipboard-helper';
import { ChatInputActionContext } from './context';
import { uploadMessageImage } from './utils';
import { ChatInputBoxInput } from './input';
interface ChatInputBoxProps {
onSendMsg: (msg: string) => void;
}
export const ChatInputBox: React.FC<ChatInputBoxProps> = React.memo((props) => {
const inputRef = useRef<Input>(null);
const inputRef = useRef<HTMLInputElement>(null);
const [message, setMessage] = useState('');
const handleSendMsg = useCallback(() => {
props.onSendMsg(message);
@ -21,7 +20,7 @@ export const ChatInputBox: React.FC<ChatInputBoxProps> = React.memo((props) => {
}, [message]);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
(e: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => {
if (isEnterHotkey(e.nativeEvent)) {
e.preventDefault();
handleSendMsg();
@ -31,7 +30,7 @@ export const ChatInputBox: React.FC<ChatInputBoxProps> = React.memo((props) => {
);
const handlePaste = useCallback(
(e: React.ClipboardEvent<HTMLDivElement>) => {
(e: React.ClipboardEvent<HTMLDivElement | HTMLTextAreaElement>) => {
const helper = new ClipboardHelper(e);
const image = helper.hasImage();
if (image) {
@ -51,12 +50,10 @@ export const ChatInputBox: React.FC<ChatInputBoxProps> = React.memo((props) => {
<ChatInputActionContext.Provider value={{ sendMsg: props.onSendMsg }}>
<div className="px-4 py-2">
<div className="bg-white dark:bg-gray-600 flex rounded-md items-center">
<Input
ref={inputRef}
className="outline-none shadow-none border-0 py-2.5 px-4 flex-1"
placeholder={t('输入一些什么')}
<ChatInputBoxInput
inputRef={inputRef}
value={message}
onChange={(e) => setMessage(e.target.value)}
onChange={setMessage}
onKeyDown={handleKeyDown}
onPaste={handlePaste}
/>

@ -0,0 +1,27 @@
.chatbox-mention-input {
@apply shadow-none border-0 px-4 py-2 flex-1;
&__control {
@apply relative;
}
&__input {
@apply outline-none;
}
&__suggestions {
@apply bg-transparent !important;
&__list {
@apply border border-gray-500 bg-content-light dark:bg-content-dark rounded;
}
&__item {
@apply rounded;
&--focused {
@apply bg-black bg-opacity-20 dark:bg-white dark:bg-opacity-20;
}
}
}
}

@ -0,0 +1,50 @@
import { UserListItem } from '@/components/UserListItem';
import { getMessageTextDecorators } from '@/plugin/common';
import React from 'react';
import { Mention, MentionsInput } from 'react-mentions';
import { t } from 'tailchat-shared';
import { useChatInputMentionsContext } from './context';
import './input.less';
interface ChatInputBoxInputProps
extends Omit<
React.InputHTMLAttributes<HTMLInputElement | HTMLTextAreaElement>,
'value' | 'onChange'
> {
inputRef?: React.Ref<HTMLInputElement>;
value: string;
onChange: (message: string) => void;
}
export const ChatInputBoxInput: React.FC<ChatInputBoxInputProps> = React.memo(
(props) => {
const mentions = useChatInputMentionsContext();
return (
<MentionsInput
inputRef={props.inputRef}
className="chatbox-mention-input"
placeholder={t('输入一些什么')}
singleLine={true}
value={props.value}
onChange={(e) => props.onChange(e.target.value)}
onKeyDown={props.onKeyDown}
onPaste={props.onPaste}
allowSuggestionsAboveCursor={true}
forceSuggestionsAboveCursor={true}
>
<Mention
trigger="@"
data={mentions.users}
displayTransform={(id, display) => `@${display}`}
appendSpaceOnAdd={true}
renderSuggestion={(suggestion) => (
<UserListItem userId={String(suggestion.id)} />
)}
markup={getMessageTextDecorators().mention('__id__', '__display__')}
/>
</MentionsInput>
);
}
);
ChatInputBoxInput.displayName = 'ChatInputBoxInput';

@ -1,6 +1,7 @@
import { ChatBox } from '@/components/ChatBox';
import { ChatInputMentionsContextProvider } from '@/components/ChatBox/ChatInputBox/context';
import React from 'react';
import { useGroupPanel } from 'tailchat-shared';
import { useGroupPanel, useGroupMemberInfos } from 'tailchat-shared';
import { GroupPanelWrapper } from './Wrapper';
interface TextPanelProps {
@ -9,6 +10,7 @@ interface TextPanelProps {
}
export const TextPanel: React.FC<TextPanelProps> = React.memo(
({ groupId, panelId }) => {
const groupMembers = useGroupMemberInfos(groupId);
const panelInfo = useGroupPanel(groupId, panelId);
if (panelInfo === undefined) {
return null;
@ -16,7 +18,14 @@ export const TextPanel: React.FC<TextPanelProps> = React.memo(
return (
<GroupPanelWrapper groupId={groupId} panelId={panelId} showHeader={true}>
<ChatBox converseId={panelId} isGroup={true} groupId={groupId} />
<ChatInputMentionsContextProvider
users={groupMembers.map((m) => ({
id: m._id,
display: m.nickname,
}))}
>
<ChatBox converseId={panelId} isGroup={true} groupId={groupId} />
</ChatInputMentionsContextProvider>
</GroupPanelWrapper>
);
}

@ -7,7 +7,7 @@ import {
isDevelopment,
t,
useFastFormContext,
useGroupMemberUUIDs,
useGroupMemberIds,
} from 'tailchat-shared';
import type { GroupPanelValues } from './types';
import _compact from 'lodash/compact';
@ -43,7 +43,7 @@ export function useGroupPanelFields(
) {
const disableSendMessageWithoutRender = useMemo(() => {
const DisableSendMessageWithoutComponent: React.FC = () => {
const groupMemberUUIDs = useGroupMemberUUIDs(groupId);
const groupMemberUUIDs = useGroupMemberIds(groupId);
const context = useFastFormContext();
return (

@ -101,15 +101,25 @@ export const [getMessageRender, regMessageRender] = buildRegFn<
*
*
*/
export const [getMessageTextDecorators, regMessageTextDecorators] = buildRegFn<
const defaultMessageTextDecorators = {
url: (plain: string) => plain,
image: (plain: string) => plain,
mention: (userId: string, userName: string) => `@${userName}`,
};
const [_getMessageTextDecorators, regMessageTextDecorators] = buildRegFn<
() => {
url: (plain: string) => string;
image: (plain: string, attrs: Record<string, unknown>) => string;
mention: (userId: string, userName: string) => string;
}
>('message-text-decorators', () => ({
url: (plain: string) => plain,
image: (plain: string) => plain,
}));
>('message-text-decorators', () => defaultMessageTextDecorators);
function getMessageTextDecorators() {
return {
...defaultMessageTextDecorators,
..._getMessageTextDecorators(),
};
}
export { getMessageTextDecorators, regMessageTextDecorators };
interface ChatInputAction {
label: string;

Loading…
Cancel
Save