删除 消息记录的loading
parent
5f38969d85
commit
ff8a277e11
@ -1,153 +0,0 @@
|
||||
import { useEffect, useState, useRef, useMemo, memo, createRef, forwardRef } from 'react';
|
||||
import { Image, Spin, Dropdown, Button, Affix } from 'antd';
|
||||
import { DownOutlined } from '@ant-design/icons';
|
||||
import { MessageBox } from 'react-chat-elements';
|
||||
import useConversationStore from '@/stores/ConversationStore';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { Emoji } from 'emoji-picker-react';
|
||||
import { isEmpty, olog } from '@/utils/utils';
|
||||
|
||||
const Messages = ({ ...props }) => {
|
||||
// const currentConversation = useConversationStore(useShallow((state) => state.currentConversation));
|
||||
const setReferenceMsg = useConversationStore(useShallow((state) => state.setReferenceMsg));
|
||||
const msgListLoading = useConversationStore(useShallow((state) => state.msgListLoading));
|
||||
const activeMessages = useConversationStore(useShallow((state) => (state.currentConversation.sn ? state.activeConversations[state.currentConversation.sn] : [])));
|
||||
olog('invoke msg list');
|
||||
|
||||
const scrollToMessage = (id, index) => {
|
||||
const _i = index || activeMessages.findIndex((msg) => msg.id === id);
|
||||
if (_i >= 0) {
|
||||
messageRefs.current[_i].current.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
};
|
||||
|
||||
const messageRefs = useRef([]);
|
||||
messageRefs.current = activeMessages.map((_, i) => messageRefs.current[i] ?? createRef());
|
||||
|
||||
const referance = useRef(null);
|
||||
const toBottom = (e) => {
|
||||
if (!referance) return;
|
||||
referance.current.scrollTop = referance.current.scrollHeight;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (activeMessages.length > 0) {
|
||||
// toBottom();
|
||||
scrollToMessage(null, activeMessages.length - 1);
|
||||
}
|
||||
}, [activeMessages]);
|
||||
|
||||
const [previewVisible, setPreviewVisible] = useState(false);
|
||||
const [previewSrc, setPreviewSrc] = useState();
|
||||
const onPreviewClose = () => {
|
||||
setPreviewSrc('');
|
||||
setPreviewVisible(false);
|
||||
};
|
||||
const handlePreview = (msg) => {
|
||||
switch (msg.type) {
|
||||
case 'photo':
|
||||
setPreviewVisible(true);
|
||||
setPreviewSrc(msg.data.uri);
|
||||
return false;
|
||||
|
||||
case 'file':
|
||||
window.open(msg.data.uri, '_blank', 'noopener,noreferrer');
|
||||
return false;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const RenderText = memo(function renderText({ str }) {
|
||||
const parts = str.split(/(https?:\/\/[^\s]+|\p{Emoji_Presentation})/gmu).filter(s => s !== '');
|
||||
const links = str.match(/https?:\/\/[\S]+/gi) || [];
|
||||
const emojis = str.match(/\p{Emoji_Presentation}/gu) || [];
|
||||
const extraClass = isEmpty(emojis) ? '' : 'text-xl leading-5 emoji-text';
|
||||
const objArr = parts.reduce((prev, curr, index) => {
|
||||
if (links.includes(curr)) {
|
||||
prev.push({ type: 'link', key: curr });
|
||||
} else if (emojis.includes(curr)) {
|
||||
prev.push({ type: 'emoji', key: curr });
|
||||
} else {
|
||||
prev.push({type: 'text', key: curr});
|
||||
}
|
||||
return prev;
|
||||
}, []);
|
||||
return (
|
||||
<span className={extraClass}>
|
||||
{(objArr || []).map((part, index) => {
|
||||
if (part.type === 'link') {
|
||||
return (
|
||||
<a href={part.key} target='_blank' key={`${part.key}${index}`} rel='noreferrer' className='text-sm'>
|
||||
{part.key}
|
||||
</a>
|
||||
);
|
||||
} else { // if (part.type === 'emoji')
|
||||
return part.key;
|
||||
}
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
});
|
||||
// eslint-disable-next-line react/display-name
|
||||
const MessageBoxWithRef = forwardRef((props, ref) => (
|
||||
<div ref={ref}>
|
||||
<MessageBox {...props} />
|
||||
</div>
|
||||
));
|
||||
|
||||
return (
|
||||
<div className='relative h-full overflow-y-auto flex'>
|
||||
<div className='relative overflow-y-auto block flex-1' ref={referance}>
|
||||
<Spin spinning={msgListLoading} tip={'正在读取...'} wrapperClassName='pt-8 relative'>
|
||||
{activeMessages.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']}>
|
||||
<MessageBoxWithRef
|
||||
ref={messageRefs.current[index]}
|
||||
key={message.id}
|
||||
{...message}
|
||||
position={message.sender === 'me' ? 'right' : 'left'}
|
||||
onReplyClick={() => setReferenceMsg(message)}
|
||||
onReplyMessageClick={() => scrollToMessage(message.reply.id)}
|
||||
onOpen={() => handlePreview(message)}
|
||||
onTitleClick={() => handlePreview(message)}
|
||||
text={<RenderText str={message?.text || ''} />}
|
||||
{...(message.sender === 'me'
|
||||
? {
|
||||
styles: { backgroundColor: '#ccd4ae' },
|
||||
notchStyle: { fill: '#ccd4ae' },
|
||||
replyButton: ['text', 'document', 'image'].includes(message.whatsapp_msg_type) && message.status !== 'failed' ? true : false,
|
||||
className: 'whatsappme-container whitespace-pre-wrap',
|
||||
}
|
||||
: {
|
||||
replyButton: ['text', 'document', 'image'].includes(message.whatsapp_msg_type) ? true : false,
|
||||
className: 'whitespace-pre-wrap',
|
||||
})}
|
||||
/>
|
||||
// </Dropdown>
|
||||
))}
|
||||
</Spin>
|
||||
<Image src={null} preview={{ visible: previewVisible, src: previewSrc, onClose: onPreviewClose }} />
|
||||
</div>
|
||||
<Button onClick={toBottom} shape={'circle'} className=' absolute bottom-1 right-4' icon={<DownOutlined />} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Messages;
|
@ -0,0 +1,148 @@
|
||||
import { useEffect, useState, useRef, memo, createRef, forwardRef } from 'react';
|
||||
import { Image, Button } from 'antd';
|
||||
import { DownOutlined } from '@ant-design/icons';
|
||||
import { MessageBox } from 'react-chat-elements';
|
||||
import useConversationStore from '@/stores/ConversationStore';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { isEmpty, olog } from '@/utils/utils';
|
||||
|
||||
const MessagesList = ({ reference, ...props }) => {
|
||||
const setReferenceMsg = useConversationStore(useShallow((state) => state.setReferenceMsg));
|
||||
|
||||
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 messageRefs = useRef([]);
|
||||
messageRefs.current = props.dataSource.map((_, i) => messageRefs.current[i] ?? createRef());
|
||||
|
||||
const toBottom = (e) => {
|
||||
if (!reference) return;
|
||||
reference.current.scrollTop = reference.current.scrollHeight - reference.current.offsetHeight;
|
||||
};
|
||||
|
||||
const prevProps = useRef(props);
|
||||
useEffect(() => {
|
||||
if (prevProps.current.dataSource.length !== props.dataSource.length) {
|
||||
toBottom();
|
||||
}
|
||||
|
||||
prevProps.current = props;
|
||||
}, [prevProps, props]);
|
||||
|
||||
const [previewVisible, setPreviewVisible] = useState(false);
|
||||
const [previewSrc, setPreviewSrc] = useState();
|
||||
const onPreviewClose = () => {
|
||||
setPreviewSrc('');
|
||||
setPreviewVisible(false);
|
||||
};
|
||||
const handlePreview = (msg) => {
|
||||
switch (msg.type) {
|
||||
case 'photo':
|
||||
setPreviewVisible(true);
|
||||
setPreviewSrc(msg.data.uri);
|
||||
return false;
|
||||
|
||||
case 'file':
|
||||
window.open(msg.data.uri, '_blank', 'noopener,noreferrer');
|
||||
return false;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const RenderText = memo(function renderText({ str }) {
|
||||
const parts = str.split(/(https?:\/\/[^\s]+|\p{Emoji_Presentation})/gmu).filter((s) => s !== '');
|
||||
const links = str.match(/https?:\/\/[\S]+/gi) || [];
|
||||
const emojis = str.match(/([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g) || [];
|
||||
const extraClass = isEmpty(emojis) ? '' : 'text-base leading-5 emoji-text';
|
||||
const objArr = parts.reduce((prev, curr, index) => {
|
||||
if (links.includes(curr)) {
|
||||
prev.push({ type: 'link', key: curr });
|
||||
} else if (emojis.includes(curr)) {
|
||||
prev.push({ type: 'emoji', key: curr });
|
||||
} else {
|
||||
prev.push({ type: 'text', key: curr });
|
||||
}
|
||||
return prev;
|
||||
}, []);
|
||||
return (
|
||||
<span className={extraClass}>
|
||||
{(objArr || []).map((part, index) => {
|
||||
if (part.type === 'link') {
|
||||
return (
|
||||
<a href={part.key} target='_blank' key={`${part.key}${index}`} rel='noreferrer' className='text-sm'>
|
||||
{part.key}
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
// if (part.type === 'emoji')
|
||||
return part.key;
|
||||
}
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
});
|
||||
// eslint-disable-next-line react/display-name
|
||||
const MessageBoxWithRef = forwardRef((props, ref) => (
|
||||
<div ref={ref}>
|
||||
<MessageBox {...props} />
|
||||
</div>
|
||||
));
|
||||
|
||||
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']}>
|
||||
<MessageBoxWithRef
|
||||
ref={messageRefs.current[index]}
|
||||
key={message.id}
|
||||
{...message}
|
||||
position={message.sender === 'me' ? 'right' : 'left'}
|
||||
onReplyClick={() => setReferenceMsg(message)}
|
||||
onReplyMessageClick={() => scrollToMessage(message.reply.id)}
|
||||
onOpen={() => handlePreview(message)}
|
||||
onTitleClick={() => handlePreview(message)}
|
||||
text={<RenderText str={message?.text || ''} />}
|
||||
{...(message.sender === 'me'
|
||||
? {
|
||||
styles: { backgroundColor: '#ccd4ae' },
|
||||
notchStyle: { fill: '#ccd4ae' },
|
||||
replyButton: ['text', 'document', 'image'].includes(message.whatsapp_msg_type) && message.status !== 'failed' ? true : false,
|
||||
className: 'whatsappme-container whitespace-pre-wrap',
|
||||
}
|
||||
: {
|
||||
replyButton: ['text', 'document', 'image'].includes(message.whatsapp_msg_type) ? true : false,
|
||||
className: 'whitespace-pre-wrap',
|
||||
})}
|
||||
/>
|
||||
// </Dropdown>
|
||||
))}
|
||||
</div>
|
||||
<Button onClick={toBottom} shape={'circle'} className=' absolute bottom-1 right-4' icon={<DownOutlined />} />
|
||||
<Image src={null} preview={{ visible: previewVisible, src: previewSrc, onClose: onPreviewClose }} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MessagesList;
|
@ -0,0 +1,16 @@
|
||||
import { useRef } from 'react';
|
||||
import useConversationStore from '@/stores/ConversationStore';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import MessagesList from './MessagesList';
|
||||
|
||||
const MessagesWrapper = () => {
|
||||
const activeMessages = useConversationStore(useShallow((state) => (state.currentConversation.sn ? state.activeConversations[state.currentConversation.sn] : [])));
|
||||
|
||||
const reference = useRef(null);
|
||||
return (
|
||||
<>
|
||||
<MessagesList dataSource={activeMessages} reference={reference} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default MessagesWrapper;
|
Loading…
Reference in New Issue