|
|
|
@ -1,60 +1,61 @@
|
|
|
|
|
import { useEffect, useState, useRef, memo, createRef, forwardRef } from 'react';
|
|
|
|
|
import { Image, Button } from 'antd';
|
|
|
|
|
import { DownOutlined } from '@ant-design/icons';
|
|
|
|
|
import { useEffect, useRef, useState, forwardRef, memo } from 'react';
|
|
|
|
|
import { MessageBox } from 'react-chat-elements';
|
|
|
|
|
import useConversationStore from '@/stores/ConversationStore';
|
|
|
|
|
import { Button } from 'antd';
|
|
|
|
|
import { DownOutlined } from '@ant-design/icons';
|
|
|
|
|
import { useShallow } from 'zustand/react/shallow';
|
|
|
|
|
import useConversationStore from '@/stores/ConversationStore';
|
|
|
|
|
import { isEmpty, olog } from '@/utils/utils';
|
|
|
|
|
|
|
|
|
|
const MessagesList = ({ reference, contactsModalOpen, setContactsModalOpen, ...props }) => {
|
|
|
|
|
const MessagesList = ({ messages, handlePreview, reference }) => {
|
|
|
|
|
const setReferenceMsg = useConversationStore(useShallow((state) => state.setReferenceMsg));
|
|
|
|
|
olog('render message list');
|
|
|
|
|
|
|
|
|
|
const scrollToMessage = (id, index) => {
|
|
|
|
|
const _i = index || props.dataSource.findIndex((msg) => msg.id === id);
|
|
|
|
|
if (_i >= 0) {
|
|
|
|
|
messageRefs.current[_i].current.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// const messagesEndRef = useRef(null);
|
|
|
|
|
const messageRefs = useRef([]);
|
|
|
|
|
messageRefs.current = props.dataSource.map((_, i) => messageRefs.current[i] ?? createRef());
|
|
|
|
|
const [page, setPage] = useState(1);
|
|
|
|
|
let timeout = null;
|
|
|
|
|
|
|
|
|
|
const toBottom = (e) => {
|
|
|
|
|
if (!reference) return;
|
|
|
|
|
reference.current.scrollTop = reference.current.scrollHeight - reference.current.offsetHeight;
|
|
|
|
|
const fetchNextPage = async () => {
|
|
|
|
|
olog('fetchNextPage')
|
|
|
|
|
setPage(page + 1);
|
|
|
|
|
// Fetch next page of messages here
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const prevProps = useRef(props);
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (prevProps.current.dataSource.length !== props.dataSource.length) {
|
|
|
|
|
toBottom();
|
|
|
|
|
const handleScroll = (e) => {
|
|
|
|
|
const { scrollTop } = e.target;
|
|
|
|
|
const delay = 1000; // 1 second
|
|
|
|
|
|
|
|
|
|
if (scrollTop === 0) {
|
|
|
|
|
if (timeout) clearTimeout(timeout);
|
|
|
|
|
timeout = setTimeout(fetchNextPage, delay);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
prevProps.current = props;
|
|
|
|
|
}, [prevProps, props]);
|
|
|
|
|
const scrollToBottom = () => {
|
|
|
|
|
if (reference.current) {
|
|
|
|
|
reference.current.scrollTop = reference.current.scrollHeight;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const [previewVisible, setPreviewVisible] = useState(false);
|
|
|
|
|
const [previewSrc, setPreviewSrc] = useState();
|
|
|
|
|
const onPreviewClose = () => {
|
|
|
|
|
setPreviewSrc('');
|
|
|
|
|
setPreviewVisible(false);
|
|
|
|
|
const scrollToMessage = (id, index) => {
|
|
|
|
|
const _i = index || messages.findIndex((msg) => msg.id === id);
|
|
|
|
|
if (reference.current && messageRefs.current[_i]) {
|
|
|
|
|
reference.current.scrollTop = messageRefs.current[_i].offsetTop;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
const handlePreview = (msg) => {
|
|
|
|
|
switch (msg.type) {
|
|
|
|
|
case 'photo':
|
|
|
|
|
setPreviewVisible(true);
|
|
|
|
|
setPreviewSrc(msg.data.uri);
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
case 'file':
|
|
|
|
|
window.open(msg.data.link || msg.data.uri, '_blank', 'noopener,noreferrer');
|
|
|
|
|
return false;
|
|
|
|
|
useEffect(scrollToBottom, [messages]);
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
return false;
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const messageList = reference.current;
|
|
|
|
|
if (messageList) {
|
|
|
|
|
messageList.addEventListener('scroll', handleScroll);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
return () => {
|
|
|
|
|
if (messageList) {
|
|
|
|
|
messageList.removeEventListener('scroll', handleScroll);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const RenderText = memo(function renderText({ str }) {
|
|
|
|
|
const parts = str.split(/(https?:\/\/[^\s]+|\p{Emoji_Presentation})/gmu).filter((s) => s !== '');
|
|
|
|
@ -88,6 +89,7 @@ const MessagesList = ({ reference, contactsModalOpen, setContactsModalOpen, ...p
|
|
|
|
|
</span>
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// eslint-disable-next-line react/display-name
|
|
|
|
|
const MessageBoxWithRef = forwardRef((props, ref) => (
|
|
|
|
|
<div ref={ref}>
|
|
|
|
@ -96,27 +98,11 @@ const MessagesList = ({ reference, contactsModalOpen, setContactsModalOpen, ...p
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className='relative h-full overflow-y-auto overflow-x-hidden flex'>
|
|
|
|
|
<div className='relative overflow-y-auto block flex-1' ref={reference}>
|
|
|
|
|
{props.dataSource.map((message, index) => (
|
|
|
|
|
// <Dropdown
|
|
|
|
|
// key={message.id}
|
|
|
|
|
// menu={{
|
|
|
|
|
// items: [{ label: '回复', key: 'reply', disabled: !['text'].includes(message.whatsapp_msg_type) }],
|
|
|
|
|
// onClick: ({ key, domEvent }) => {
|
|
|
|
|
// domEvent.stopPropagation();
|
|
|
|
|
// switch (key) {
|
|
|
|
|
// case 'reply':
|
|
|
|
|
// return setReferenceMsg(message);
|
|
|
|
|
|
|
|
|
|
// default:
|
|
|
|
|
// return;
|
|
|
|
|
// }
|
|
|
|
|
// },
|
|
|
|
|
// }}
|
|
|
|
|
// trigger={['contextMenu']}>
|
|
|
|
|
<div className='relative h-full overflow-y-auto overflow-x-hidden flex flex-1'>
|
|
|
|
|
<div ref={reference} className='relative overflow-y-auto overflow-x-hidden block flex-1'>
|
|
|
|
|
{messages.map((message, index) => (
|
|
|
|
|
<MessageBoxWithRef
|
|
|
|
|
ref={messageRefs.current[index]}
|
|
|
|
|
ref={(el) => (messageRefs.current[index] = el)}
|
|
|
|
|
key={message.id}
|
|
|
|
|
{...message}
|
|
|
|
|
position={message.sender === 'me' ? 'right' : 'left'}
|
|
|
|
@ -149,11 +135,9 @@ const MessagesList = ({ reference, contactsModalOpen, setContactsModalOpen, ...p
|
|
|
|
|
}
|
|
|
|
|
: {})}
|
|
|
|
|
/>
|
|
|
|
|
// </Dropdown>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
<Button onClick={toBottom} ghost type={'dashed'} shape={'circle'} className=' absolute bottom-1 right-4' icon={<DownOutlined />} />
|
|
|
|
|
<Image src={null} preview={{ visible: previewVisible, src: previewSrc, onClose: onPreviewClose }} />
|
|
|
|
|
<Button onClick={scrollToBottom} ghost type={'dashed'} shape={'circle'} className=' absolute bottom-1 right-4' icon={<DownOutlined />} />
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|