|
|
|
import { createContext, useEffect, useState } from 'react';
|
|
|
|
import { ConfigProvider, Button, Form, Input, Flex, Checkbox, Switch, Mentions, Popover, Popconfirm, Select, Space, Upload, Divider } from 'antd';
|
|
|
|
import { DashOutlined, EllipsisOutlined, MenuOutlined, MoreOutlined, UploadOutlined } from '@ant-design/icons';
|
|
|
|
import Modal from '@dckj/react-better-modal';
|
|
|
|
import '@dckj/react-better-modal/dist/index.css';
|
|
|
|
import DnDModal from '@/components/DndModal';
|
|
|
|
import useStyleStore from '@/stores/StyleStore';
|
|
|
|
import useConversationStore from '@/stores/ConversationStore';
|
|
|
|
|
|
|
|
import LexicalEditor from '@/components/LexicalEditor';
|
|
|
|
|
|
|
|
import { v4 as uuid } from 'uuid';
|
|
|
|
import { isEmpty } from '@/utils/commons';
|
|
|
|
import './EmailEditor.css';
|
|
|
|
import { postSendEmail } from '@/actions/EmailActions';
|
|
|
|
import { sentMsgTypeMapped, whatsappSupportFileTypes, uploadProgressSimulate } from '@/channel/bubbleMsgUtils';
|
|
|
|
|
|
|
|
const getAbstract = (longtext) => {
|
|
|
|
const lines = longtext.split('\n');
|
|
|
|
const firstLine = lines[0];
|
|
|
|
const abstract = firstLine.substring(0, 20);
|
|
|
|
return abstract;
|
|
|
|
};
|
|
|
|
|
|
|
|
const EmailEditorPopup = ({ open, setOpen, fromEmail, reference, quote = {}, initial = {}, action = 'reply', ...props }) => {
|
|
|
|
const [mobile] = useStyleStore((state) => [state.mobile]);
|
|
|
|
|
|
|
|
const [form] = Form.useForm();
|
|
|
|
|
|
|
|
const [isRichText, setIsRichText] = useState(mobile === false);
|
|
|
|
// const [isRichText, setIsRichText] = useState(false); // 默认纯文本
|
|
|
|
const [htmlContent, setHtmlContent] = useState('');
|
|
|
|
const [textContent, setTextContent] = useState('');
|
|
|
|
|
|
|
|
const [showCc, setShowCc] = useState(false);
|
|
|
|
const [showBcc, setShowBcc] = useState(false);
|
|
|
|
const handleShowCc = () => {
|
|
|
|
setShowCc(true);
|
|
|
|
};
|
|
|
|
const handleShowBcc = () => {
|
|
|
|
setShowBcc(true);
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleEditorChange = ({ editorState, html, textContent }) => {
|
|
|
|
// console.log('textContent', textContent);
|
|
|
|
// console.log('html', html);
|
|
|
|
setHtmlContent(html);
|
|
|
|
setTextContent(textContent);
|
|
|
|
form.setFieldValue('content', html);
|
|
|
|
form.setFieldValue('abstract', getAbstract(textContent));
|
|
|
|
};
|
|
|
|
|
|
|
|
const [newFromEmail, setNewFromEmail] = useState('');
|
|
|
|
const [initialForm, setInitialForm] = useState({});
|
|
|
|
const [initialContent, setInitialContent] = useState('');
|
|
|
|
useEffect(() => {
|
|
|
|
if (isEmpty(quote)) {
|
|
|
|
return () => {};
|
|
|
|
}
|
|
|
|
setShowCc(!isEmpty(quote.cc));
|
|
|
|
const { from, email: { subject, content, mai_sn } } = quote;
|
|
|
|
const preQuoteBody = `<br><br>
|
|
|
|
<hr>
|
|
|
|
<p>
|
|
|
|
<b>
|
|
|
|
<strong >From: </strong>
|
|
|
|
</b>
|
|
|
|
<span >${quote.from} </span>
|
|
|
|
</p>
|
|
|
|
<p>
|
|
|
|
<b>
|
|
|
|
<strong >Sent: </strong>
|
|
|
|
</b>
|
|
|
|
<span >${quote.sendTime || ''}</span>
|
|
|
|
</p>
|
|
|
|
<p>
|
|
|
|
<b>
|
|
|
|
<strong >To: </strong>
|
|
|
|
</b>
|
|
|
|
<span >${quote.to}</span>
|
|
|
|
</p>
|
|
|
|
<p>
|
|
|
|
<b>
|
|
|
|
<strong >Subject: </strong>
|
|
|
|
</b>
|
|
|
|
<span >${subject}</span>
|
|
|
|
</p>
|
|
|
|
<p>
|
|
|
|
${content}
|
|
|
|
</p>
|
|
|
|
`;
|
|
|
|
|
|
|
|
setInitialContent(preQuoteBody);
|
|
|
|
const _formValues = {
|
|
|
|
to: from || fromEmail,
|
|
|
|
cc: quote.cc || '',
|
|
|
|
bcc: quote.bcc || '',
|
|
|
|
subject: `Re: ${subject}`,
|
|
|
|
};
|
|
|
|
const forwardValues = { subject: `Fw: ${subject}` };
|
|
|
|
if (action === 'reply') {
|
|
|
|
form.setFieldsValue(_formValues);
|
|
|
|
setInitialForm(_formValues);
|
|
|
|
} else if (action === 'forward') {
|
|
|
|
form.setFieldsValue(forwardValues);
|
|
|
|
setInitialForm(forwardValues);
|
|
|
|
}
|
|
|
|
|
|
|
|
return () => {};
|
|
|
|
}, [quote, open]);
|
|
|
|
|
|
|
|
const [openPlainTextConfirm, setOpenPlainTextConfirm] = useState(false);
|
|
|
|
const handlePlainTextOpenChange = ({ target }) => {
|
|
|
|
const { checked: newChecked } = target;
|
|
|
|
if (!newChecked) {
|
|
|
|
setIsRichText(true);
|
|
|
|
setOpenPlainTextConfirm(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
setOpenPlainTextConfirm(true);
|
|
|
|
};
|
|
|
|
const confirmPlainText = () => {
|
|
|
|
setIsRichText(false);
|
|
|
|
setOpenPlainTextConfirm(false);
|
|
|
|
};
|
|
|
|
|
|
|
|
// todo: 附件:
|
|
|
|
// 1. 直接上传返回地址
|
|
|
|
// 2. 发送文件信息
|
|
|
|
const [fileList, setFileList] = useState([]);
|
|
|
|
const handleChange = (info) => {
|
|
|
|
let newFileList = [...info.fileList];
|
|
|
|
// 2. Read from response and show file link
|
|
|
|
newFileList = newFileList.map((file) => {
|
|
|
|
if (file.response) {
|
|
|
|
// Component will show file.url as link
|
|
|
|
file.url = file.response.url;
|
|
|
|
}
|
|
|
|
return file;
|
|
|
|
});
|
|
|
|
setFileList(newFileList);
|
|
|
|
};
|
|
|
|
const normFile = (e) => {
|
|
|
|
console.log('Upload event:', e);
|
|
|
|
if (Array.isArray(e)) {
|
|
|
|
return e;
|
|
|
|
}
|
|
|
|
return e?.fileList;
|
|
|
|
};
|
|
|
|
const uploadProps = {
|
|
|
|
// action: 'https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload',
|
|
|
|
// onChange: handleChange,
|
|
|
|
// multiple: true,
|
|
|
|
fileList,
|
|
|
|
beforeUpload: (file) => {
|
|
|
|
setFileList(prev => [...prev, file]);
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
onRemove: (file) => {
|
|
|
|
const index = fileList.indexOf(file);
|
|
|
|
const newFileList = fileList.slice();
|
|
|
|
newFileList.splice(index, 1);
|
|
|
|
setFileList(newFileList);
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 先推到消息记录上面, 再发送
|
|
|
|
*/
|
|
|
|
const sentOrReceivedNewMessage = useConversationStore((state) => state.sentOrReceivedNewMessage);
|
|
|
|
const invokeEmailMessage = (msgObj) => {
|
|
|
|
const msgObjMerge = {
|
|
|
|
sender: 'me',
|
|
|
|
senderName: 'me',
|
|
|
|
msg_source: 'email',
|
|
|
|
// to: currentConversation.whatsapp_phone_number,
|
|
|
|
date: new Date(),
|
|
|
|
status: 'waiting',
|
|
|
|
...msgObj,
|
|
|
|
// id: `${currentConversation.sn}.${msgObj.id}`,
|
|
|
|
id: `1148.${msgObj.id}`,
|
|
|
|
};
|
|
|
|
// olog('invoke upload', msgObjMerge)
|
|
|
|
const contentToRender = sentMsgTypeMapped[msgObjMerge.type].contentToRender(msgObjMerge);
|
|
|
|
// console.log(contentToRender, 'contentToRender sendMessage------------------');
|
|
|
|
sentOrReceivedNewMessage(contentToRender.conversationid, contentToRender);
|
|
|
|
};
|
|
|
|
|
|
|
|
const [sendLoading, setSendLoading] = useState(false);
|
|
|
|
|
|
|
|
const onHandleSend = async () => {
|
|
|
|
console.log('onSend callback', '\nisRichText', isRichText);
|
|
|
|
// console.log(form.getFieldsValue());
|
|
|
|
const body = structuredClone(form.getFieldsValue());
|
|
|
|
body.mailcontent = isRichText ? htmlContent : textContent;
|
|
|
|
body.from = newFromEmail || fromEmail;
|
|
|
|
body.attaList = fileList;
|
|
|
|
body.opi_sn = 29; // debug:
|
|
|
|
body.mat_sn = 278;
|
|
|
|
// console.log('body', body);
|
|
|
|
const values = await form.validateFields();
|
|
|
|
const msgObj = {
|
|
|
|
type: 'email',
|
|
|
|
id: uuid(),
|
|
|
|
from: body.from,
|
|
|
|
to: values.to,
|
|
|
|
cc: values.cc,
|
|
|
|
bcc: values.bcc,
|
|
|
|
email: {
|
|
|
|
subject: values.subject,
|
|
|
|
content: body.mailcontent,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
invokeEmailMessage(msgObj);
|
|
|
|
setSendLoading(true);
|
|
|
|
const result = await postSendEmail(body);
|
|
|
|
|
|
|
|
setSendLoading(false);
|
|
|
|
setOpen(false);
|
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<ConfigProvider theme={{ token: { colorPrimary: '#6366f1' } }}>
|
|
|
|
<DnDModal
|
|
|
|
rootClassName='email-editor-wrapper !border-indigo-300 '
|
|
|
|
open={open}
|
|
|
|
setOpen={setOpen}
|
|
|
|
initial={{ top: isEmpty(reference) ? 20 : 74 }}
|
|
|
|
onCancel={() => {
|
|
|
|
form.resetFields();
|
|
|
|
}}
|
|
|
|
title={initialForm.subject || `${isEmpty(quote) ? '回复: ' : '写邮件: '} ${fromEmail || ''}`}
|
|
|
|
footer={
|
|
|
|
<div className='w-full flex gap-6 justify-start items-center text-indigo-600'>
|
|
|
|
<Button type='primary' onClick={onHandleSend} loading={sendLoading}>
|
|
|
|
发送
|
|
|
|
</Button>
|
|
|
|
<Popconfirm description='切换内容为纯文本格式将丢失信件核签名的格式, 确定使用纯文本?' onConfirm={confirmPlainText} open={openPlainTextConfirm} onCancel={() => setOpenPlainTextConfirm(false)}>
|
|
|
|
<Checkbox checked={!isRichText} onChange={handlePlainTextOpenChange}>
|
|
|
|
纯文本
|
|
|
|
</Checkbox>
|
|
|
|
</Popconfirm>
|
|
|
|
<Select
|
|
|
|
options={[
|
|
|
|
{ key: 'lyt@hainatravel.com', value: 'lyt@hainatravel.com', label: 'LYT <lyt@hainatravel.com>' },
|
|
|
|
{ key: 'XXX@chinahighlights.com', value: 'XXX@chinahighlights.com', label: 'XXX <XXX@chinahighlights.com>' },
|
|
|
|
]}
|
|
|
|
value={newFromEmail || fromEmail}
|
|
|
|
onChange={(val) => setNewFromEmail(val)}
|
|
|
|
variant={'borderless'}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
}>
|
|
|
|
<Form
|
|
|
|
form={form}
|
|
|
|
preserve={false}
|
|
|
|
name='conversation_filter_form'
|
|
|
|
size='small'
|
|
|
|
layout={'inline'}
|
|
|
|
variant={'borderless'}
|
|
|
|
// initialValues={{}}
|
|
|
|
// onFinish={() => {}}
|
|
|
|
className='email-editor-wrapper *:mb-2 *:border-b *:border-t-0 *:border-x-0 *:border-indigo-100 *:border-solid '
|
|
|
|
requiredMark={false}
|
|
|
|
// labelCol={{ span: 3 }}
|
|
|
|
>
|
|
|
|
<Form.Item label='收件人' className='w-full'>
|
|
|
|
<Space.Compact className='w-full'>
|
|
|
|
<Form.Item name={'to'} rules={[{ required: true }]} className='!flex-1'>
|
|
|
|
<Input className='w-full' />
|
|
|
|
</Form.Item>
|
|
|
|
<Flex gap={4}>
|
|
|
|
{!showCc && (
|
|
|
|
<Button type='text' onClick={handleShowCc}>
|
|
|
|
Cc
|
|
|
|
</Button>
|
|
|
|
)}
|
|
|
|
{!showBcc && (
|
|
|
|
<Button type='text' hidden={showBcc} onClick={handleShowBcc}>
|
|
|
|
Bcc
|
|
|
|
</Button>
|
|
|
|
)}
|
|
|
|
</Flex>
|
|
|
|
</Space.Compact>
|
|
|
|
{/* <Input
|
|
|
|
addonAfter={
|
|
|
|
<Flex gap={4}>
|
|
|
|
{!showCc && (
|
|
|
|
<Button type='text' onClick={handleShowCc}>
|
|
|
|
Cc
|
|
|
|
</Button>
|
|
|
|
)}
|
|
|
|
{!showBcc && (
|
|
|
|
<Button type='text' hidden={showBcc} onClick={handleShowBcc}>
|
|
|
|
Bcc
|
|
|
|
</Button>
|
|
|
|
)}
|
|
|
|
</Flex>
|
|
|
|
}
|
|
|
|
/> */}
|
|
|
|
</Form.Item>
|
|
|
|
<Form.Item label='抄送' name={'cc'} hidden={!showCc} className='w-full pt-1'>
|
|
|
|
<Input />
|
|
|
|
</Form.Item>
|
|
|
|
<Form.Item label='密送' name={'bcc'} hidden={!showBcc} className='w-full pt-1'>
|
|
|
|
<Input />
|
|
|
|
</Form.Item>
|
|
|
|
<Form.Item label='主题' name={'subject'} rules={[{ required: true }]} className='w-full pt-1'>
|
|
|
|
<Input />
|
|
|
|
</Form.Item>
|
|
|
|
<Form.Item
|
|
|
|
name='atta'
|
|
|
|
label=''
|
|
|
|
className='w-full py-1 border-b-0'
|
|
|
|
valuePropName='fileList'
|
|
|
|
getValueFromEvent={normFile}
|
|
|
|
>
|
|
|
|
<Flex justify='space-between'>
|
|
|
|
<Upload {...uploadProps} name='file' className='w-full' >
|
|
|
|
<Button icon={<UploadOutlined />}>附件</Button>
|
|
|
|
</Upload>
|
|
|
|
<Flex align={'center'} className='absolute right-0'>
|
|
|
|
{/* todo: 添加图文集、支付链接 */}
|
|
|
|
<Divider type='vertical' />
|
|
|
|
<Button type={'link'}>图文集</Button>
|
|
|
|
<Button type={'link'}>支付链接</Button>
|
|
|
|
{/* 更多工具 */}
|
|
|
|
{/* <Popover
|
|
|
|
content={
|
|
|
|
<div className='flex flex-col gap-2'>
|
|
|
|
<Button type={'link'}>??</Button>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
trigger='click'
|
|
|
|
><MoreOutlined /></Popover> */}
|
|
|
|
</Flex>
|
|
|
|
</Flex>
|
|
|
|
</Form.Item>
|
|
|
|
<Form.Item name='content' hidden>
|
|
|
|
<Input />
|
|
|
|
</Form.Item>
|
|
|
|
<Form.Item name='abstract' hidden>
|
|
|
|
<Input />
|
|
|
|
</Form.Item>
|
|
|
|
</Form>
|
|
|
|
<LexicalEditor {...{ isRichText }} onChange={handleEditorChange} initialValue={initialContent} />
|
|
|
|
</DnDModal>
|
|
|
|
</ConfigProvider>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
export default EmailEditorPopup;
|