|
|
|
|
@ -3,6 +3,7 @@ import { App, Button, Image } from 'antd';
|
|
|
|
|
import { ExportOutlined, CopyOutlined, PhoneOutlined } from '@ant-design/icons';
|
|
|
|
|
import { MessageBox } from 'react-chat-elements';
|
|
|
|
|
import { groupBy, isEmpty, TagColorStyle } from '@/utils/commons';
|
|
|
|
|
import { parseSimpleMarkdown } from '@/channel/bubbleMsgUtils';
|
|
|
|
|
import useConversationStore from '@/stores/ConversationStore';
|
|
|
|
|
import { useShallow } from 'zustand/react/shallow';
|
|
|
|
|
import { ReplyIcon } from '@/components/Icons';
|
|
|
|
|
@ -23,6 +24,34 @@ const BubbleIM = ({ handlePreview, handleContactClick, setNewChatModalVisible, s
|
|
|
|
|
setNewChatModalVisible(true);
|
|
|
|
|
setNewChatFormValues((prev) => ({ ...prev, phone_number: wa_id, name: wa_name }));
|
|
|
|
|
};
|
|
|
|
|
// Render parsed tokens to React elements
|
|
|
|
|
const renderMDTokens = (tokens) => {
|
|
|
|
|
return tokens.map((token, index) => {
|
|
|
|
|
switch (token.type) {
|
|
|
|
|
case 'text':
|
|
|
|
|
return <span key={index}>{token.content}</span>
|
|
|
|
|
case 'bold':
|
|
|
|
|
return <b key={index}>{renderMDTokens(token.content)}</b>
|
|
|
|
|
case 'italic':
|
|
|
|
|
return <i key={index}>{renderMDTokens(token.content)}</i>
|
|
|
|
|
case 'url':
|
|
|
|
|
return (
|
|
|
|
|
<a key={index} href={token.content} target='_blank' rel='noopener noreferrer' className=' underline text-sm'>
|
|
|
|
|
{token.content}
|
|
|
|
|
</a>
|
|
|
|
|
)
|
|
|
|
|
case 'number':
|
|
|
|
|
return (
|
|
|
|
|
<a key={`${index}`} className='text-sm ' onClick={() => openNewChatModal({ wa_id: token.content, wa_name: token.content })}>
|
|
|
|
|
{token.content}
|
|
|
|
|
</a>
|
|
|
|
|
)
|
|
|
|
|
default:
|
|
|
|
|
return <span key={index}>{token.content}</span>
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const RenderText = memo(function renderText({ str, className, template, message }) {
|
|
|
|
|
let headerObj, footerObj, buttonsArr;
|
|
|
|
|
if (!isEmpty(template) && !isEmpty(template.components)) {
|
|
|
|
|
@ -32,26 +61,8 @@ const BubbleIM = ({ handlePreview, handleContactClick, setNewChatModalVisible, s
|
|
|
|
|
buttonsArr = componentsObj?.button; // ?.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'}>
|
|
|
|
|
<div className={`text-sm leading-5 emoji-text whitespace-pre-wrap ${className}`} key={'msg-text'}>
|
|
|
|
|
{headerObj ? (
|
|
|
|
|
<div className='text-neutral-500 text-center'>
|
|
|
|
|
{'text' === (headerObj?.parameters?.[0]?.type || '').toLowerCase() && <div>{headerObj.text}</div>}
|
|
|
|
|
@ -63,24 +74,7 @@ const BubbleIM = ({ handlePreview, handleContactClick, setNewChatModalVisible, s
|
|
|
|
|
)}
|
|
|
|
|
</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
|
|
|
|
|
}
|
|
|
|
|
})}
|
|
|
|
|
{renderMDTokens(parseSimpleMarkdown(str))}
|
|
|
|
|
{footerObj ? <div className=' text-neutral-500'>{footerObj.text}</div> : null}
|
|
|
|
|
{buttonsArr && buttonsArr.length > 0 ? (
|
|
|
|
|
<div className='flex flex-row gap-1'>
|
|
|
|
|
@ -101,7 +95,7 @@ const BubbleIM = ({ handlePreview, handleContactClick, setNewChatModalVisible, s
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
) : null}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
});
|
|
|
|
|
return (
|
|
|
|
|
|