|
|
|
import { createContext, useEffect, useState, memo } from 'react';
|
|
|
|
import { App, Button } from 'antd';
|
|
|
|
import { MailFilled, MailOutlined, WhatsAppOutlined } from '@ant-design/icons';
|
|
|
|
import { MessageBox } from 'react-chat-elements';
|
|
|
|
import { groupBy, isEmpty } from '@/utils/commons';
|
|
|
|
import useConversationStore from '@/stores/ConversationStore';
|
|
|
|
import { useShallow } from 'zustand/react/shallow';
|
|
|
|
import { WABIcon } from '@/components/Icons';
|
|
|
|
import ChannelLogo from './ChannelLogo';
|
|
|
|
|
|
|
|
const outboundStyle = {
|
|
|
|
'waba': { color: '#ccd4ae' },
|
|
|
|
'whatsapp': { color: '#d9fdd3' },
|
|
|
|
'wai': { color: '#d9fdd3' },
|
|
|
|
}
|
|
|
|
|
|
|
|
const BubbleIM = ({ handlePreview, handleContactClick, setNewChatModalVisible, setNewChatFormValues, scrollToMessage, focusMsg, ...message }) => {
|
|
|
|
const { message: appMessage } = App.useApp();
|
|
|
|
const setReferenceMsg = useConversationStore(useShallow((state) => state.setReferenceMsg));
|
|
|
|
|
|
|
|
const openNewChatModal = ({ wa_id, wa_name }) => {
|
|
|
|
setNewChatModalVisible(true);
|
|
|
|
setNewChatFormValues((prev) => ({ ...prev, phone_number: wa_id, name: wa_name }));
|
|
|
|
};
|
|
|
|
const RenderText = memo(function renderText({ str, className, template, message }) {
|
|
|
|
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}|\d{4,})/gmu).filter((s) => s !== '');
|
|
|
|
const links = str.match(/https?:\/\/[^\s()]+/gi) || [];
|
|
|
|
const numbers = str.match(/\d{4,}/g) || [];
|
|
|
|
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 (numbers.includes(curr)) {
|
|
|
|
prev.push({ type: 'number', 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 if (part.type === 'number') {
|
|
|
|
return (
|
|
|
|
<a key={`${part.key}${index}`} className='text-sm' onClick={() => openNewChatModal({ wa_id: part.key, wa_name: part.key })}>
|
|
|
|
{part.key}
|
|
|
|
</a>
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
// if (part.type === 'emoji')
|
|
|
|
return part.key
|
|
|
|
}
|
|
|
|
})}
|
|
|
|
{footerObj ? <div className=' text-neutral-500'>{footerObj.text}</div> : null}
|
|
|
|
{buttonsArr && buttonsArr.length > 0 ? (
|
|
|
|
<div className='flex flex-row gap-1'>
|
|
|
|
{buttonsArr.map((btn, index) =>
|
|
|
|
btn.type.toLowerCase() === 'url' ? (
|
|
|
|
<Button className='text-blue-500' size={'small'} href={btn.url} target={'_blank'} key={btn.url} rel='noreferrer'>
|
|
|
|
{btn.text}
|
|
|
|
</Button>
|
|
|
|
) : btn.type.toLowerCase() === 'phone_number' ? (
|
|
|
|
<Button className='text-blue-500' size={'small'} key={btn.phone_number} rel='noreferrer'>
|
|
|
|
{btn.text} ({btn.phone_number})
|
|
|
|
</Button>
|
|
|
|
) : (
|
|
|
|
<Button className='text-blue-500' size={'small'} key={btn.type}>
|
|
|
|
{btn.text}
|
|
|
|
</Button>
|
|
|
|
),
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
) : null}
|
|
|
|
</span>
|
|
|
|
)
|
|
|
|
});
|
|
|
|
return (
|
|
|
|
<MessageBox
|
|
|
|
{...message}
|
|
|
|
key={`IM.${message.id}`}
|
|
|
|
position={message.sender === 'me' ? 'right' : 'left'}
|
|
|
|
onReplyClick={() => setReferenceMsg(message)}
|
|
|
|
onReplyMessageClick={() => scrollToMessage(message.reply.id)}
|
|
|
|
onOpen={() => handlePreview(message)}
|
|
|
|
onTitleClick={() => handlePreview(message)}
|
|
|
|
// title={<div className='flex justify-around items-center gap-1'><WABIcon />{message.title}</div>}
|
|
|
|
text={<RenderText str={message?.text || ''} className={message.status === 'failed' ? 'line-through text-neutral-400' : ''} template={message.template} message={message} />}
|
|
|
|
replyButton={['text', 'document', 'image'].includes(message.whatsapp_msg_type) && message.replyButton}
|
|
|
|
{...(message.sender === 'me'
|
|
|
|
? {
|
|
|
|
// styles: { backgroundColor: '#ccd4ae' },
|
|
|
|
notchStyle: { fill: outboundStyle[message.msg_source.toLowerCase()].color },
|
|
|
|
title: <><ChannelLogo channel={message.msg_source} />{message.wabaName ? ` ${message.wabaName} - ${message.title || ''}` : ` ${message.title || message.from}`}</>,
|
|
|
|
}
|
|
|
|
: {
|
|
|
|
// title: <> <ChannelLogo channel={message.msg_source} /> {message.title}</>,
|
|
|
|
dateString: `${message.wabaName} - ${message.dateString}`,
|
|
|
|
})}
|
|
|
|
className={[
|
|
|
|
'whitespace-pre-wrap',
|
|
|
|
message.whatsapp_msg_type === 'sticker' ? 'bg-transparent' : '',
|
|
|
|
// message.sender === 'me' ? 'whatsappme-container' : '',
|
|
|
|
focusMsg === message.id ? 'message-box-focus' : '',
|
|
|
|
message.status === 'failed' ? 'failed-msg' : '',
|
|
|
|
// '*:bg-waba-me'
|
|
|
|
message.sender === 'me' ? (message.msg_source.toLowerCase() === 'waba' ? `*:!bg-waba-me` : `*:!bg-whatsapp-me`) : '',
|
|
|
|
].join(' ')}
|
|
|
|
{...(message.type === 'meetingLink'
|
|
|
|
? {
|
|
|
|
actionButtons: [
|
|
|
|
...(message.waBtn
|
|
|
|
? [
|
|
|
|
{
|
|
|
|
onClickButton: () => handleContactClick(message.data),
|
|
|
|
Component: () => <div key={'talk-now'}>发消息</div>,
|
|
|
|
},
|
|
|
|
]
|
|
|
|
: []),
|
|
|
|
{
|
|
|
|
onClickButton: () => {
|
|
|
|
navigator.clipboard.writeText(message.text);
|
|
|
|
appMessage.success('复制成功😀');
|
|
|
|
},
|
|
|
|
Component: () => <div key={'copy'}>复制</div>,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
}
|
|
|
|
: {})}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
export default BubbleIM;
|