mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-02-13 00:10:27 +00:00
208 lines
6.1 KiB
TypeScript
208 lines
6.1 KiB
TypeScript
import { Button } from '@heroui/button';
|
|
import type { Range } from 'quill';
|
|
import 'quill/dist/quill.core.css';
|
|
import { useRef } from 'react';
|
|
import toast from 'react-hot-toast';
|
|
|
|
import { useCustomQuill } from '@/hooks/use_custom_quill';
|
|
import useShowStructuredMessage from '@/hooks/use_show_strcuted_message';
|
|
|
|
import { quillToMessage } from '@/utils/onebot';
|
|
|
|
import type { OB11Segment } from '@/types/onebot';
|
|
|
|
import AudioInsert from './components/audio_insert';
|
|
import DiceInsert from './components/dice_insert';
|
|
import EmojiPicker from './components/emoji_picker';
|
|
import FileInsert from './components/file_insert';
|
|
import ImageInsert from './components/image_insert';
|
|
import MusicInsert from './components/music_insert';
|
|
import ReplyInsert from './components/reply_insert';
|
|
import RPSInsert from './components/rps_insert';
|
|
import VideoInsert from './components/video_insert';
|
|
import EmojiBlot from './formats/emoji_blot';
|
|
import type { EmojiValue } from './formats/emoji_blot';
|
|
import ImageBlot from './formats/image_blot';
|
|
import ReplyBlock from './formats/reply_blot';
|
|
|
|
const ChatInput = () => {
|
|
const memorizedRange = useRef<Range | null>(null);
|
|
|
|
const showStructuredMessage = useShowStructuredMessage();
|
|
const formats: string[] = ['image', 'emoji', 'reply'];
|
|
const modules = {
|
|
toolbar: '#toolbar',
|
|
};
|
|
const { quillRef, quill, Quill } = useCustomQuill({
|
|
modules,
|
|
formats,
|
|
placeholder: '请输入消息',
|
|
});
|
|
|
|
if (Quill && !quill) {
|
|
Quill.register('formats/emoji', EmojiBlot);
|
|
Quill.register('formats/image', ImageBlot, true);
|
|
Quill.register('formats/reply', ReplyBlock);
|
|
}
|
|
|
|
if (quill) {
|
|
quill.on('selection-change', (range) => {
|
|
if (range) {
|
|
const editorContent = quill.getContents();
|
|
const firstOp = editorContent.ops[0];
|
|
|
|
if (
|
|
typeof firstOp?.insert !== 'string' &&
|
|
firstOp?.insert?.reply &&
|
|
range.index === 0 &&
|
|
range.length !== quill.getLength()
|
|
) {
|
|
quill.setSelection(1, Quill.sources.SILENT);
|
|
}
|
|
}
|
|
});
|
|
|
|
quill.on('text-change', () => {
|
|
const editorContent = quill.getContents();
|
|
const firstOp = editorContent.ops[0];
|
|
if (
|
|
firstOp &&
|
|
typeof firstOp.insert !== 'string' &&
|
|
firstOp.insert?.reply &&
|
|
quill.getLength() === 1
|
|
) {
|
|
quill.insertText(1, '\n', Quill.sources.SILENT);
|
|
}
|
|
});
|
|
|
|
quill.on('editor-change', (eventName: string) => {
|
|
if (eventName === 'text-change') {
|
|
const editorContent = quill.getContents();
|
|
const firstOp = editorContent.ops[0];
|
|
if (
|
|
firstOp &&
|
|
typeof firstOp.insert !== 'string' &&
|
|
firstOp.insert?.reply &&
|
|
quill.getLength() === 1
|
|
) {
|
|
quill.insertText(1, '\n', Quill.sources.SILENT);
|
|
}
|
|
}
|
|
});
|
|
|
|
quill.root.addEventListener('compositionstart', () => {
|
|
const editorContent = quill.getContents();
|
|
const firstOp = editorContent.ops[0];
|
|
if (
|
|
firstOp &&
|
|
typeof firstOp.insert !== 'string' &&
|
|
firstOp.insert?.reply &&
|
|
quill.getLength() === 1
|
|
) {
|
|
quill.insertText(1, '\n', Quill.sources.SILENT);
|
|
}
|
|
});
|
|
}
|
|
|
|
const onOpenChange = (open: boolean) => {
|
|
if (open) {
|
|
const selection = quill?.getSelection();
|
|
if (selection) memorizedRange.current = selection;
|
|
}
|
|
};
|
|
|
|
const insertImage = (url: string) => {
|
|
const selection = memorizedRange.current || quill?.getSelection();
|
|
quill?.deleteText(selection?.index || 0, selection?.length || 0);
|
|
quill?.insertEmbed(selection?.index || 0, 'image', {
|
|
src: url,
|
|
alt: '图片',
|
|
});
|
|
quill?.setSelection((selection?.index || 0) + 1, 0);
|
|
};
|
|
function insertReplyBlock (messageId: string) {
|
|
const isNumberReg = /^(?:0|(?:-?[1-9]\d*))$/;
|
|
if (!isNumberReg.test(messageId)) {
|
|
toast.error('请输入正确的消息ID');
|
|
return;
|
|
}
|
|
const editorContent = quill?.getContents();
|
|
const firstOp = editorContent?.ops[0];
|
|
const currentSelection = quill?.getSelection();
|
|
if (
|
|
firstOp &&
|
|
typeof firstOp.insert !== 'string' &&
|
|
firstOp.insert?.reply
|
|
) {
|
|
const delta = quill?.getContents();
|
|
if (delta) {
|
|
delta.ops[0] = {
|
|
insert: { reply: { messageId } },
|
|
};
|
|
quill?.setContents(delta, Quill.sources.USER);
|
|
}
|
|
} else {
|
|
quill?.insertEmbed(0, 'reply', { messageId }, Quill.sources.USER);
|
|
}
|
|
quill?.setSelection((currentSelection?.index || 0) + 1, 0);
|
|
quill?.blur();
|
|
}
|
|
const onInsertEmoji = (emoji: EmojiValue) => {
|
|
const selection = memorizedRange.current || quill?.getSelection();
|
|
quill?.deleteText(selection?.index || 0, selection?.length || 0);
|
|
quill?.insertEmbed(selection?.index || 0, 'emoji', {
|
|
alt: emoji.alt,
|
|
src: emoji.src,
|
|
id: emoji.id,
|
|
});
|
|
quill?.setSelection((selection?.index || 0) + 1, 0);
|
|
};
|
|
|
|
const getChatMessage = () => {
|
|
const delta = quill?.getContents();
|
|
const ops =
|
|
delta?.ops?.filter((op) => {
|
|
return op.insert !== '\n';
|
|
}) ?? [];
|
|
const messages: OB11Segment[] = ops.map((op) => {
|
|
return quillToMessage(op);
|
|
});
|
|
return messages;
|
|
};
|
|
|
|
return (
|
|
<div>
|
|
<div
|
|
ref={quillRef}
|
|
className='border border-default-200 rounded-md !mb-2 !text-base !h-64'
|
|
/>
|
|
<div id='toolbar' className='!border-none flex gap-2'>
|
|
<ImageInsert insertImage={insertImage} onOpenChange={onOpenChange} />
|
|
<EmojiPicker
|
|
onInsertEmoji={onInsertEmoji}
|
|
onOpenChange={onOpenChange}
|
|
/>
|
|
<ReplyInsert insertReply={insertReplyBlock} />
|
|
<FileInsert />
|
|
<AudioInsert />
|
|
<VideoInsert />
|
|
<MusicInsert />
|
|
<DiceInsert />
|
|
<RPSInsert />
|
|
<Button
|
|
color='primary'
|
|
onPress={() => {
|
|
const messages = getChatMessage();
|
|
showStructuredMessage(messages);
|
|
}}
|
|
className='ml-auto'
|
|
>
|
|
获取JSON格式
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ChatInput;
|