import { useEffect, useState, useRef, useCallback } from 'react' import { useParams, useNavigate, useLocation } from 'react-router-dom'; import { App, ConfigProvider, Button, Form, Input, Flex, Checkbox, Popconfirm, Select, Space, Upload, Divider, Modal, Tabs, Radio } from 'antd' import { UploadOutlined, LoadingOutlined, SaveOutlined, SendOutlined, CheckCircleOutlined } from '@ant-design/icons' import '@dckj/react-better-modal/dist/index.css' import DnDModal from '@/components/DnDModal' import useStyleStore from '@/stores/StyleStore' // import useConversationStore from '@/stores/ConversationStore' import useAuthStore from '@/stores/AuthStore' import LexicalEditor from '@/components/LexicalEditor' import { v4 as uuid } from 'uuid' import { cloneDeep, debounce, isEmpty, writeIndexDB } from '@/utils/commons' import '@/views/Conversations/Online/Input/EmailEditor.css' import { postSendEmail } from '@/actions/EmailActions' import { sentMsgTypeMapped } from '@/channel/bubbleMsgUtils' import { EmailBuilder, useEmailDetail, useEmailSignature } from '@/hooks/useEmail' import useSnippetStore from '@/stores/SnippetStore' // import { useOrderStore } from '@/stores/OrderStore' import PaymentlinkBtn from '@/views/Conversations/Online/Input/PaymentlinkBtn' import { TextIcon } from '@/components/Icons'; import GenerateAutoDocDrawer from './Conversations/Online/Components/GenerateAutoDocDrawer'; import { POPUP_FEATURES } from '@/config'; // 禁止上传的附件类型 // .application, .exe, .app const disallowedAttachmentTypes = [ '.ps1', '.msi', 'application/x-msdownload', 'application/x-ms-dos-executable', 'application/x-ms-wmd', 'application/x-ms-wmz', 'application/x-ms-xbap', 'application/x-msaccess', ] const getAbstract = (longtext) => { const lines = longtext.split('\n') const firstLine = lines[0] const abstract = firstLine.substring(0, 20) return abstract } const parseHTMLText = (html) => { const parser = new DOMParser() const dom = parser.parseFromString(html, 'text/html') // Replace
and

with line breaks Array.from(dom.body.querySelectorAll('br, p')).forEach((el) => { el.textContent = '\n' + el.textContent }) // Replace


with a line of dashes Array.from(dom.body.querySelectorAll('hr')).forEach((el) => { el.textContent = '\n------------------------------------------------------------------\n' }) return dom.body.textContent || '' } const generateQuoteContent = (mailData, isRichText = true) => { const html = `


From: ${(mailData.info?.MAI_From || '') .replace(//g, '>')}

Sent: ${ mailData.info?.MAI_SendDate || '' }

To: ${(mailData.info?.MAI_To || '') .replace(//g, '>')}

Subject: ${mailData.info?.MAI_Subject || ''}

${ mailData.info?.MAI_ContentType === 'text/html' ? mailData.content : mailData.content.replace(/\r\n/g, '
') }

` return isRichText ? html : parseHTMLText(html) } const generateMailContent = (mailData) => `

${mailData.content}

` /** * ! 无状态管理 */ const NewEmail = ({ ...props }) => { const pageParam = useParams(); const editorKey = pageParam.action==='new' ? `new-${Date.now().toString(32)}` : `${pageParam.action}-${pageParam.quoteid}` const { notification, message } = App.useApp() const [form] = Form.useForm() const [mobile] = useStyleStore((state) => [state.mobile]) const [userId, username, emailList] = useAuthStore((state) => [state.loginUser.userId, state.loginUser.username, state.loginUser.emailList]) const emailListOption = emailList?.map((ele) => ({ ...ele, label: ele.email, key: ele.email, value: ele.email })) || [] // const emailListMapped = emailListOption?.reduce((r, v) => ({ ...r, [v.opi_sn]: v }), {}); const emailListAddrMapped = emailListOption?.reduce((r, v) => ({ ...r, [v.email]: v }), {}) const emailListMatMapped = emailListOption?.reduce((r, v) => ({ ...r, [v.mat_sn]: v }), {}) // console.log('emailListMapped', emailListOption, emailListAddrMapped); // const emailEdiorProps = useConversationStore((state) => state.emailEdiorProps) // const [open, setOpen, closeEditor1, currentEditKey, setCurrentEditKey] = useConversationStore((state) => [ // state.editorOpen, // state.setEditorOpen, // state.closeEditor1, // state.currentEditKey, // state.setCurrentEditKey, // ]) // const propsKeysArr = Array.from(emailEdiorProps.keys()) // const propsArr = Array.from(emailEdiorProps.values()) // const [activeEdit, setActiveEdit] = useState(emailEdiorProps.get(editorKey) || {}) // const { fromEmail, fromUser, fromOrder, oid, toEmail, conversationid, quoteid, initial = {}, mailData: _mailData, action = 'reply', draft = {}, receiverName, ...props } = emailEdiorProps.get(currentEditKey) || {}; const mai_sn = pageParam.quoteid // activeEdit.quoteid const { loading: getLoading, mailData, orderDetail } = useEmailDetail(mai_sn) const [newFromEmail, setNewFromEmail] = useState('') const [newToEmail, setNewToEmail] = useState('') const [emailOPI, setEmailOPI] = useState('') const [emailOrder, setEmailOrder] = useState('') const [emailOrderSN, setEmailOrderSN] = useState('') const [emailMat, setEmailMat] = useState('') const stateReset = () => { setEmailOrder('') setEmailOPI('') setNewFromEmail('') setNewToEmail('') } const [contentPrefix, setContentPrefix] = useState('') // const [stickToCid, setStickToCid] = useState(pageParam.conversationid) useEffect(() => { console.log('useeffect\n', orderDetail.order_no); // const propsObj = { ...activeEdit, mai: activeEdit.mailData?.info?.MAI_MAT_SN } setContentPrefix(orderDetail.order_no ? `

Dear Mr./Ms. ${orderDetail.contact?.[0]?.name || ''}

Reference Number: ${orderDetail.order_no}

` : '') return () => {} }, [orderDetail.order_no]) const handleSwitchEmail = (labelValue) => { const { value } = labelValue setNewFromEmail(value) const _findMat = emailListAddrMapped?.[value] setEmailMat(_findMat?.mat_sn) setEmailOPI(_findMat?.opi_sn) } const { signature } = useEmailSignature(emailOPI) const [isRichText, setIsRichText] = useState(mobile === false) // const [isRichText, setIsRichText] = useState(false); // 默认纯文本 const [htmlContent, setHtmlContent] = useState('') const [textContent, setTextContent] = useState('') const [showCc, setShowCc] = useState(true) const [showBcc, setShowBcc] = useState(false) const handleShowCc = () => { setShowCc(true) } const handleShowBcc = () => { setShowBcc(true) } const handleEditorChange = ({ editorStateJSON, htmlContent, textContent }) => { // console.log('textContent', textContent); // console.log('html', html); setHtmlContent(htmlContent) setTextContent(textContent) form.setFieldValue('content', htmlContent) // form.setFieldValue('abstract', getAbstract(textContent)) debouncedSave({ htmlContent, ...form.getFieldsValue() }) } const [initialForm, setInitialForm] = useState({}) const [initialContent, setInitialContent] = useState('') const [showQuoteContent, setShowQuoteContent] = useState(false) const [mergeQuote, setMergeQuote] = useState(true) const [quoteContent, setQuoteContent] = useState('') const setPreFillInProperty = () => { }; useEffect(() => { console.log('useEffect 2---- \nform.setFieldsValue '); setTimeout(() => { document.title = mailData.info?.MAI_Subject || 'New Email-' }, 1500); // 没有引用邮件 if (isEmpty(pageParam.quoteid)) { // setEmailOrder(orderDetail.order_no) // setEmailOPI(orderDetail.opi_sn) // setNewFromEmail(activeEdit.fromEmail) // setNewToEmail(activeEdit.toEmail) // const _findMat = emailListAddrMapped?.[activeEdit.fromEmail]?.mat_sn // setEmailMat(_findMat) // if (open !== true) { // form.resetFields() // } } // 转发/回复时, 使用详情的账户信息 if (mailData.info?.MAI_MAT_SN) { const reEmailO = mailData.info?.MAI_COLI_SN const reEmailUser = mailData.info?.MAI_OPI_SN const reEmailUserMat = mailData.info?.MAI_MAT_SN setEmailOrder((prev) => reEmailO || prev ) // activeEdit.fromOrder setEmailOPI((prev) => reEmailUser || prev) setEmailMat((prev) => reEmailUserMat || prev) const _findMatOld = emailListMatMapped?.[reEmailUserMat] if (_findMatOld) { setNewFromEmail(_findMatOld.email) setEmailOPI(_findMatOld.opi_sn) setEmailMat(_findMatOld.mat_sn) } } setShowQuoteContent(false) setMergeQuote(true) setQuoteContent('') if (isEmpty(pageParam.quoteid) && pageParam.action !== 'new') { return () => {} } const { info } = mailData // setShowCc(!isEmpty(mailData.info?.MAI_CS)); const signatureBody = generateMailContent({ content: signature }) // const preQuoteBody = generateQuoteContent(mailData) // const _initialContent = isEmpty(mailData.info) ? signatureBody : signatureBody+preQuoteBody if (!isEmpty(mailData.info) && pageParam.action !== 'edit') { setInitialContent(contentPrefix + signatureBody) } const forwardValues = { from: newFromEmail, subject: `Fw: ${info.MAI_Subject || ''}` } if (pageParam.action === 'reply') { const _formValues = { to: info?.replyTo || newFromEmail, cc: info?.MAI_CS || '', // bcc: quote.bcc || '', subject: `Re: ${info.MAI_Subject || ''}`, } form.setFieldsValue(_formValues) setInitialForm(_formValues) } else if (pageParam.action === 'replyall') { const _formValues = { to: info?.replyToAll || newFromEmail, cc: info?.MAI_CS || '', // bcc: quote.bcc || '', subject: `Re: ${info.MAI_Subject || ''}`, } form.setFieldsValue(_formValues) setInitialForm(_formValues) } else if (pageParam.action === 'forward') { form.setFieldsValue(forwardValues) setInitialForm(forwardValues) } else if (pageParam.action === 'edit') { const thisFormValues = { to: info?.MAI_To || '', cc: info?.MAI_CS || '', subject: info?.MAI_Subject || '', } form.setFieldsValue(thisFormValues) setInitialForm(thisFormValues) const thisBody = generateMailContent(mailData) // console.log('thisBody', thisBody); setInitialContent(thisBody) } else if (pageParam.action === 'new') { const newEmail = { to: newToEmail, subject: pageParam.draft?.subject || '' } form.setFieldsValue(newEmail) setInitialForm(newEmail) setInitialContent((pageParam.draft?.content || contentPrefix || '') + signatureBody) } return () => {} }, [ mailData.info, signature, newToEmail]) const [openPlainTextConfirm, setOpenPlainTextConfirm] = useState(false) const handlePlainTextOpenChange = ({ target }) => { const { value: newChecked } = target if (newChecked === true) { setIsRichText(true) setOpenPlainTextConfirm(false) return } setOpenPlainTextConfirm(true) } const confirmPlainText = () => { setIsRichText(false) setOpenPlainTextConfirm(false) } // 附件: // 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) => { // console.log('beforeUpload', file); const lastDotIndex = file.name.lastIndexOf('.') const extension = file.name.slice(lastDotIndex).toLocaleLowerCase() if (disallowedAttachmentTypes.includes(file.type) || disallowedAttachmentTypes.includes(extension)) { message.warning('不支持的文件格式: ' + extension) return false } setFileList((prev) => [...prev, file]) return false // 阻止默认上传, 附件不上传阿里云 }, onRemove: (file) => { const index = fileList.indexOf(file) const newFileList = fileList.slice() newFileList.splice(index, 1) setFileList(newFileList) }, onPreview: (file) => { // console.log('pn preview', file); return new Promise((resolve, reject) => { const reader = new FileReader() reader.onloadend = (e) => { if (file.size > 1.5 * 1024 * 1024) { message.info('附件太大,无法预览') // message.info('附件太大,无法预览, 请下载后查看') // var downloadLink = document.createElement('a'); // downloadLink.href = e.target.result; // downloadLink.download = file.name; // downloadLink.click(); resolve(e.target.result) return } var win = window.open('', file.uid, POPUP_FEATURES) win.document.body.style.margin = '0' if (file.type.startsWith('image/')) { win.document.write("') } else if (file.type.startsWith('text/') || file.type === 'application/html' || file.type === 'application/xhtml+xml') { var iframe = win.document.createElement('iframe') iframe.srcdoc = e.target.result iframe.style.width = '100%' iframe.style.height = '100%' iframe.style.border = 'none' win.document.body.appendChild(iframe) win.document.body.style.margin = '0' } else if (file.type === 'application/pdf') { // win.document.write(""); win.document.write("") win.document.body.style.margin = '0' } else if (file.type.startsWith('audio/')) { win.document.write("') } else if (file.type.startsWith('video/')) { win.document.write("') } else { win.document.write('

Preview not available for this file type

') } // win.document.write(""); resolve(reader.result) } if (file.type.startsWith('text/') || file.type === 'application/html' || file.type === 'application/xhtml+xml') { reader.readAsText(file) } else { reader.readAsDataURL(file) } // reader.readAsDataURL(file); reader.onerror = (error) => reject(error) }) }, } /** * 保存成功, 推一个气泡 * 再从异步通知更新消息发送状态 */ // const sentOrReceivedNewMessage = useConversationStore((state) => state.sentOrReceivedNewMessage) const invokeEmailMessage = (msgObj) => { const msgObjMerge = { sender: 'me', senderName: 'me', // to: currentConversation.whatsapp_phone_number, date: new Date(), status: 'waiting', // accepted ...msgObj, // id: `${currentConversation.sn}.${msgObj.id}`, // id: `${stickToCid}.${msgObj.id}`, // conversationid: stickToCid, msg_source: 'email', } // 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.from = newFromEmail body.attaList = fileList body.opi_sn = emailOPI body.mat_sn = emailMat body.coli_sn = emailOrder || '' // console.log('body', body, '\n', emailOrder); const values = await form.validateFields() const preQuoteBody = pageParam.quoteid ? (quoteContent ? quoteContent : generateQuoteContent(mailData, isRichText)) : '' body.mailcontent = isRichText ? EmailBuilder({ subject: values.subject, content: htmlContent + preQuoteBody }) : textContent + preQuoteBody body.cc = values.cc || '' body.bcc = values.bcc || '' const msgObj = { type: 'email', id: uuid(), from: body.from, to: values.to, cc: values.cc || '', bcc: values.bcc || '', subject: values.subject, content: body.mailcontent, email: { subject: values.subject, content: body.mailcontent, }, coli_id: orderDetail.order_no || (emailOrder ? `{${emailOrder}}` : ''), } setSendLoading(true) // body.externalID = stickToCid // body.actionID = `${stickToCid}.${msgObj.id}` body.contenttype = isRichText ? 'text/html' : 'text/plain' try { const bubbleMsg = cloneDeep(msgObj) // bubbleMsg.id = `${stickToCid}.${msgObj.id}` // bubbleMsg.email.mai_sn = ''; bubbleMsg.content = undefined // invokeEmailMessage(bubbleMsg); // console.log('postSendEmail', body, '\n', msgObj); // return; const result = await postSendEmail(body) const mailSavedId = result.id || '' bubbleMsg.email.mai_sn = mailSavedId // console.log('invokeEmailMessage', bubbleMsg); invokeEmailMessage(bubbleMsg) // setSendLoading(false); // setOpen(false) } catch (error) { notification.error({ message: '邮件保存失败', description: error.message, placement: 'top', duration: 3, }) } finally { setSendLoading(false) } } const [openDrawerSnippet] = useSnippetStore((state) => [state.openDrawer]) // const [openPaymentDrawer] = useOrderStore((state) => [state.openDrawer]) const [bakData, setBakData] = useState({}) const idleCallbackId = useRef(null) const debouncedSave = useCallback( debounce((data) => { idleCallbackId.current = window.requestIdleCallback(() => { console.log('Saving data (idle, debounced):', data) writeIndexDB([{ ...data, key: editorKey }], 'draft', 'EmailEditor') }) }, 1500), // 1.5s [], ) const onEditChange = (changedValues, allValues) => { console.log('onEditChange', changedValues, '\n', allValues) // const { name, value } = e.target; // setBakData(prevData => ({ ...prevData, [name]: value })); // debouncedSave(bakData); } useEffect(() => { return () => { if (idleCallbackId.current && window.cancelIdleCallback) { window.cancelIdleCallback(idleCallbackId.current) } } }, [debouncedSave]) return ( <>
{!showCc && ( )} {!showBcc && ( )} {/* 更多工具 */} {/*
} trigger='click' > */} {pageParam.quoteid && !showQuoteContent && (
{/* */}
)} {showQuoteContent && (
setQuoteContent(`
${e.target.innerHTML}
`)} dangerouslySetInnerHTML={{ __html: generateQuoteContent(mailData) }}>
)}
) } export default NewEmail