You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
306 lines
14 KiB
JavaScript
306 lines
14 KiB
JavaScript
import { useRef, useEffect, useState, forwardRef, memo } from 'react';
|
|
import { App, Flex, List, Button, } from 'antd';
|
|
import { LoadingOutlined } from '@ant-design/icons';
|
|
import { MessageBox } from 'react-chat-elements';
|
|
import { MESSAGE_PAGE_SIZE, fetchMessagesHistory } from '@/actions/ConversationActions';
|
|
import useFormStore from '@/stores/FormStore';
|
|
import { isEmpty, stringToColour, groupBy, isNotEmpty } from '@/utils/commons';
|
|
import { useShallow } from 'zustand/react/shallow';
|
|
import MergeConversationTo from './MergeConversationTo';
|
|
import BubbleIM from '../Online/Components/BubbleIM';
|
|
import BubbleEmail from '../Online/Components/BubbleEmail';
|
|
|
|
const BIG_PAGE_SIZE = MESSAGE_PAGE_SIZE * 20;
|
|
const MessagesList = ({ ...listProps }) => {
|
|
const { message: appMessage } = App.useApp();
|
|
|
|
const [formValues] = useFormStore((state) => [state.chatHistoryForm]);
|
|
const [selectedConversation] = useFormStore((state) => [state.chatHistorySelectChat]);
|
|
const [paramsForMsgList, setParamsForMsgList] = useFormStore((state) => [state.msgListParams, state.setMsgListParams]);
|
|
const [selectMatch, setSelectedMatch] = useFormStore((state) => [state.msgHistorySelectMatch, state.setMsgHistorySelectMatch]);
|
|
const [setImageAlbumList, setImagePreviewSrc] = useFormStore(useShallow((state) => [state.setImageAlbum, state.setImagePreviewSrc]));
|
|
|
|
const [chatItemMessages, setChatItemMessages] = useState([]);
|
|
const [messageListPreLoading, setMessageListPreLoading] = useState(false);
|
|
const [messageListLoading, setMessageListLoading] = useState(false);
|
|
|
|
const getMessagesPre = async (chatItem) => {
|
|
setMessageListPreLoading(true);
|
|
const data = await fetchMessagesHistory({ ...chatItem, lasttime: chatItem.pretime, pagedir: 'pre', pagesize: BIG_PAGE_SIZE });
|
|
setMessageListPreLoading(false);
|
|
// const prevChatItemMessages = chatItemMessages;
|
|
// setChatItemMessages([].concat(data, prevChatItemMessages));
|
|
setChatItemMessages((prevValue) => [].concat(data, prevValue));
|
|
const loadPrePage = !(data.length === 0 || data.length < BIG_PAGE_SIZE);
|
|
if (data.length > 0) {
|
|
setParamsForMsgList({ loadPrePage, pretime: data[0].orgmsgtime });
|
|
}
|
|
};
|
|
const getMessagesNext = async (chatItem) => {
|
|
setMessageListLoading(true);
|
|
const data = await fetchMessagesHistory({ ...chatItem, pagedir: 'next', pagesize: BIG_PAGE_SIZE });
|
|
setMessageListLoading(false);
|
|
// const prevChatItemMessages = chatItemMessages;
|
|
// setChatItemMessages([].concat(prevChatItemMessages, data));
|
|
setChatItemMessages((prevValue) => [].concat(prevValue, data));
|
|
const loadNextPage = !(data.length === 0 || data.length < BIG_PAGE_SIZE);
|
|
if (data.length > 0) {
|
|
setParamsForMsgList({ loadNextPage, lasttime: data[data.length - 1].orgmsgtime });
|
|
}
|
|
};
|
|
|
|
// 选择指定消息之后, 定位
|
|
const scrollToSelectedMessage = (selected) => {
|
|
const findIndex = chatItemMessages.findIndex((item) => item.id === selected.id);
|
|
if (findIndex !== -1) {
|
|
scrollToMessage(selected.id, findIndex);
|
|
}
|
|
};
|
|
|
|
// 选中会话之后, 获取消息记录
|
|
useEffect(() => {
|
|
setChatItemMessages([]);
|
|
setParamsForMsgList({});
|
|
setSelectedMatch({});
|
|
setFocusMsg('');
|
|
if (isEmpty(selectedConversation.conversationid)) {
|
|
return () => {};
|
|
}
|
|
// opisn: (selectedConversation.opi_sn || 0), whatsappid: selectedConversation.whatsapp_phone_number,
|
|
const firstActionPageParams = { conversationid: selectedConversation.conversationid , loadNextPage: true };
|
|
// if (isEmpty(selectedConversation.matchMsgList)) {
|
|
if (!isEmpty(formValues?.from_date)) {
|
|
firstActionPageParams.lasttime = formValues.from_date;
|
|
firstActionPageParams.loadPrePage = true;
|
|
}
|
|
if (!isEmpty(formValues?.search) && !isEmpty(selectedConversation.matchMsgList)) {
|
|
firstActionPageParams.pretime = selectedConversation.matchMsgList[0].orgmsgtime;
|
|
firstActionPageParams.lasttime = selectedConversation.matchMsgList[0].orgmsgtime;
|
|
firstActionPageParams.loadPrePage = true;
|
|
}
|
|
setParamsForMsgList(firstActionPageParams);
|
|
async function getFirstNext() {
|
|
await getMessagesNext(firstActionPageParams);
|
|
}
|
|
getFirstNext();
|
|
|
|
return () => {};
|
|
}, [selectedConversation.conversationid]);
|
|
|
|
// 点击匹配的记录, 定位滚动
|
|
useEffect(() => {
|
|
async function getFirstPre(_params) {
|
|
await getMessagesPre(_params);
|
|
}
|
|
|
|
const findIndex = chatItemMessages.findIndex((item) => item.id === selectMatch.id);
|
|
if (findIndex === -1) {
|
|
setMessageListLoading(true);
|
|
getFirstPre({ ...paramsForMsgList });
|
|
setMessageListLoading(false);
|
|
}
|
|
scrollToSelectedMessage(selectMatch);
|
|
return () => {};
|
|
}, [selectMatch.id]);
|
|
|
|
// 更新是否需要显示上一页,下一页按钮
|
|
useEffect(() => {
|
|
if (chatItemMessages.length > 0) {
|
|
// setParamsForMsgList({ pretime: chatItemMessages[0].orgmsgtime, lasttime: chatItemMessages[chatItemMessages.length - 1].orgmsgtime });
|
|
const album = chatItemMessages.filter((ele) => ele.whatsapp_msg_type === 'image').map((ele) => ele.data.uri);
|
|
setImageAlbumList(album);
|
|
}
|
|
return () => {};
|
|
}, [chatItemMessages]);
|
|
|
|
const onLoadMore = () => {
|
|
getMessagesNext(paramsForMsgList);
|
|
// window.dispatchEvent(new Event('resize'));
|
|
};
|
|
const loadMore =
|
|
!messageListLoading && paramsForMsgList.loadNextPage ? (
|
|
<div className='text-center pt-3 mb-3 h-8 leading-8 border-dotted border-0 border-t border-slate-300'>
|
|
<Button onClick={onLoadMore}>load more</Button>
|
|
</div>
|
|
) : null;
|
|
const onLoadMorePre = () => {
|
|
getMessagesPre(paramsForMsgList);
|
|
// window.dispatchEvent(new Event('resize'));
|
|
};
|
|
const loadMorePre =
|
|
paramsForMsgList.loadPrePage && chatItemMessages.length > 0 ? (
|
|
<div className='text-center h-8 leading-8 '>
|
|
{messageListPreLoading ? <LoadingOutlined className='text-primary' /> : <Button onClick={onLoadMorePre}>load more previous </Button>}
|
|
</div>
|
|
) : null;
|
|
|
|
const messagesEndRef = useRef(null);
|
|
const messageRefs = useRef([]);
|
|
const [focusMsg, setFocusMsg] = useState('');
|
|
const scrollToMessage = (id, index) => {
|
|
const _i = index || chatItemMessages.findIndex((msg) => msg.id === id);
|
|
if (_i >= 0) {
|
|
messageRefs.current[_i].scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
setFocusMsg(id);
|
|
}
|
|
};
|
|
|
|
const RenderText = memo(function renderText({ str, className, template }) {
|
|
let headerObj, footerObj, buttonsArr;
|
|
if (!isEmpty(template) && !isEmpty(template.components)) {
|
|
const componentsObj = groupBy(template.components, (item) => item.type);
|
|
headerObj = componentsObj?.header?.[0];
|
|
footerObj = componentsObj?.footer?.[0];
|
|
buttonsArr = componentsObj?.buttons?.reduce((r, c) => r.concat(c.buttons), []);
|
|
}
|
|
|
|
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) ? '' : '';
|
|
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={`text-sm leading-5 emoji-text whitespace-pre-wrap ${className} ${extraClass} `} key={'msg-text'}>
|
|
{headerObj ? (
|
|
<div className='text-neutral-500 text-center'>
|
|
{'text' === (headerObj?.parameters?.[0]?.type || '').toLowerCase() && <div>{headerObj.text}</div>}
|
|
{'image' === (headerObj?.parameters?.[0]?.type || '').toLowerCase() && <img src={headerObj.parameters[0].image.link} height={100}></img>}
|
|
{['document', 'video'].includes((headerObj?.parameters?.[0]?.type || '').toLowerCase()) && (
|
|
<a href={headerObj.parameters[0][headerObj.parameters[0].type].link} target='_blank' key={headerObj.format} rel='noreferrer' className='text-sm'>
|
|
[ {headerObj.parameters[0].type} ]
|
|
</a>
|
|
)}
|
|
</div>
|
|
) : null}
|
|
{(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 {
|
|
return part.key;
|
|
}
|
|
})}
|
|
</span>
|
|
);
|
|
});
|
|
|
|
// eslint-disable-next-line react/display-name
|
|
const MessageBoxWithRef = forwardRef((props, ref) => (
|
|
<li ref={ref}>
|
|
{['waba', 'wai'].includes((props.msg_source || '').toLowerCase()) && <MessageBox {...props} />}
|
|
{props.msg_source === 'email' && <BubbleEmail {...props} reposition='left' onOpenEmail={listProps.onOpenEmail} />}
|
|
</li>
|
|
));
|
|
|
|
const handlePreview = (msg) => {
|
|
switch (msg.whatsapp_msg_type) {
|
|
case 'image':
|
|
setImagePreviewSrc(msg.data.uri);
|
|
return false;
|
|
|
|
case 'document':
|
|
window.open(msg.data.link || msg.data.uri, '_blank', 'noopener,noreferrer');
|
|
return false;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Flex vertical className='flex-1'>
|
|
<Flex className='bg-white border-0 border-b border-slate-200' gap={8} justify={'space-between'}>
|
|
<div className='px-2 py-1 text-primary '>
|
|
{selectedConversation.whatsapp_name} {selectedConversation.whatsapp_phone_number}
|
|
</div>
|
|
{selectedConversation.whatsapp_phone_number && <MergeConversationTo currentWAID={selectedConversation.whatsapp_phone_number} opi_sn={selectedConversation.opi_sn} />}
|
|
</Flex>
|
|
|
|
<div style={{ height: 'calc(100% - 30px)' }} className='h-full flex-auto relative border-dashed border-y-0 border-r-0 border-l border-slate-200' ref={messagesEndRef}>
|
|
<List
|
|
loading={messageListLoading}
|
|
header={loadMorePre}
|
|
loadMore={loadMore}
|
|
className='h-full overflow-y-auto px-2 relative'
|
|
itemLayout='vertical'
|
|
dataSource={chatItemMessages}
|
|
renderItem={(message, index) => (
|
|
// <List.Item>
|
|
// <List.Item.Meta avatar={<Avatar src={item.avatarUrl} />} title={item.title} description={item.msgTime} />
|
|
// <div>{item.content}</div>
|
|
// </List.Item>
|
|
<MessageBoxWithRef
|
|
ref={(el) => (messageRefs.current[index] = el)}
|
|
key={message.id}
|
|
{...message}
|
|
// position={message.sender === 'me' ? 'right' : 'left'}
|
|
position={'left'}
|
|
replyButton={false}
|
|
onReplyMessageClick={() => scrollToMessage(message.reply.id)}
|
|
onOpen={() => handlePreview(message)}
|
|
onTitleClick={() => handlePreview(message)}
|
|
notch={false}
|
|
title={message.whatsapp_msg_type === 'text' ? '' : message.title}
|
|
text={<RenderText str={message?.text || ''} className={message.status === 'failed' ? 'line-through text-neutral-400' : ''} template={message.template} />}
|
|
copiableDate={true}
|
|
dateString={`${message.wabaName} - ${message.dateString || message.localDate}`}
|
|
className={[
|
|
'whitespace-pre-wrap mb-2',
|
|
message.whatsapp_msg_type === 'sticker' ? 'bg-transparent' : '',
|
|
message.sender === 'me' ? 'whatsappme-container' : '',
|
|
focusMsg === message.id ? 'message-box-focus' : '',
|
|
message.status === 'failed' ? 'failed-msg' : '',
|
|
message.sender === 'me' ? (message.msg_source.toLowerCase() === 'waba' ? `[&_.rce-mbox]:bg-waba-me` : `[&_.rce-mbox]:bg-whatsapp-me`) : '',
|
|
].join(' ')}
|
|
style={{
|
|
// backgroundColor: message.sender === 'me' ? '#ccd4ae' : '#fff',
|
|
}}
|
|
{...(message.type === 'meetingLink'
|
|
? {
|
|
actionButtons: [
|
|
{
|
|
onClickButton: () => {
|
|
navigator.clipboard.writeText(message.text);
|
|
appMessage.success('复制成功😀');
|
|
},
|
|
Component: () => <div key='copy'>复制</div>,
|
|
},
|
|
],
|
|
}
|
|
: {})}
|
|
renderAddCmp={
|
|
<div key={'msg-prefix'} className='border-dashed border-0 border-t border-slate-300 text-slate-600 space-x-2 emoji ' style={{backgroundColor: 'unset'}}>
|
|
<span
|
|
className={`p-1 rounded-b ${message.msg_direction === 'outbound' ? 'text-white' : ''} `}
|
|
style={{ backgroundColor: message.msg_direction === 'outbound' ? stringToColour(message.senderName) : 'unset' }}>
|
|
{message.msg_direction === 'outbound' ? selectedConversation.OPI_Name : message.senderName}
|
|
</span>
|
|
<span>{message.dateString || message.localDate}</span>
|
|
<span>{message.statusCN}</span>
|
|
</div>
|
|
}
|
|
// date={null}
|
|
// status={null}
|
|
/>
|
|
)}
|
|
/>
|
|
</div>
|
|
</Flex>
|
|
</>
|
|
);
|
|
};
|
|
export default MessagesList;
|