feat: 邮件编辑
parent
ae5c210c28
commit
7f4cafe724
@ -0,0 +1,187 @@
|
||||
import { createContext, useEffect, useState } from 'react';
|
||||
import { Button, Form, Input, Flex, Checkbox, Switch, Mentions, Popover } from 'antd';
|
||||
import Modal from '@dckj/react-better-modal';
|
||||
import '@dckj/react-better-modal/dist/index.css';
|
||||
|
||||
import LexicalEditor from '@/components/LexicalEditor';
|
||||
|
||||
import { $getRoot, $getSelection, } from 'lexical';
|
||||
import {$generateHtmlFromNodes} from '@lexical/html';
|
||||
|
||||
// import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin';
|
||||
// import { LexicalComposer } from '@lexical/react/LexicalComposer';
|
||||
// import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
|
||||
// import { ContentEditable } from '@lexical/react/LexicalContentEditable';
|
||||
// import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
|
||||
// import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
|
||||
|
||||
// const theme = {
|
||||
// // Theme styling goes here
|
||||
// //...
|
||||
// };
|
||||
|
||||
// // Catch any errors that occur during Lexical updates and log them
|
||||
// // or throw them as needed. If you don't throw them, Lexical will
|
||||
// // try to recover gracefully without losing user data.
|
||||
// function onError(error) {
|
||||
// console.error(error);
|
||||
// }
|
||||
const EmailEditorPopup = ({ open, setOpen, fromEmail, reference, ...props }) => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
// const [open, setOpen] = useState(false);
|
||||
function onHandleMove(e) {
|
||||
console.log(e, '--->>> onHandleMove');
|
||||
}
|
||||
function onHandleResize(e) {
|
||||
console.log(e, '--->>> onHandleResize');
|
||||
}
|
||||
|
||||
function onHandleOk() {
|
||||
console.log('onOk callback');
|
||||
}
|
||||
|
||||
function onHandleCancel() {
|
||||
console.log('onCancel callback');
|
||||
setOpen(false);
|
||||
}
|
||||
function onStageChange({ state }) {
|
||||
console.log(state);
|
||||
}
|
||||
|
||||
const [isRichText, setIsRichText] = useState(true);
|
||||
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', textContent.substring(0, 20));
|
||||
};
|
||||
|
||||
const onHandleSend = () => {
|
||||
console.log('onSend callback', '\nisRichText', isRichText);
|
||||
console.log(form.getFieldsValue());
|
||||
const body = structuredClone(form.getFieldsValue());
|
||||
body.content = isRichText ? htmlContent : textContent;
|
||||
console.log('body', body);
|
||||
form
|
||||
.validateFields()
|
||||
.then((values) => {})
|
||||
.catch((err) => {});
|
||||
|
||||
form.resetFields();
|
||||
|
||||
// setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
visible={open}
|
||||
keyboard={false}
|
||||
draggable
|
||||
resizable
|
||||
mask={false}
|
||||
maskClosable={false}
|
||||
// theme='dark'
|
||||
// className={'!border !border-solid !border-indigo-500 rounded !p-2' }
|
||||
className='rounded-t rounded-b-none border border-solid border-indigo-300 shadow-heavy '
|
||||
titleBarClassName='bg-neutral-100 rounded rounded-b-none border-none p-3 font-bold text-slate-600'
|
||||
contentClassName='p-2'
|
||||
footerClassName='p-2'
|
||||
zIndex={2}
|
||||
initialWidth={680}
|
||||
initialHeight={600}
|
||||
initialTop={74}
|
||||
initialLeft={window.innerWidth - 700}
|
||||
title={`${reference ? '回复: ' : '写邮件: '} ${fromEmail || ''}`}
|
||||
minimizeButton={<></>}
|
||||
onMove={onHandleMove}
|
||||
onResize={onHandleResize}
|
||||
onCancel={onHandleCancel}
|
||||
// onOk={onHandleOk}
|
||||
onStageChange={onStageChange}
|
||||
footer={
|
||||
<div className='w-full flex gap-8 justify-start items-center text-indigo-600'>
|
||||
<Button type='primary' className='bg-indigo-500 shadow shadow-indigo-300 hover:!bg-indigo-400 active:bg-indigo-400 focus:bg-indigo-400'
|
||||
onClick={onHandleSend}
|
||||
>
|
||||
发送
|
||||
</Button>
|
||||
<Popover>
|
||||
{/* <Switch checkedChildren='纯文本' unCheckedChildren='HTML' /> */}
|
||||
<Checkbox checked={!isRichText} onChange={e => setIsRichText(!(e.target.checked))}>纯文本</Checkbox>
|
||||
</Popover>
|
||||
</div>
|
||||
}>
|
||||
<Form
|
||||
form={form} preserve={false}
|
||||
name='conversation_filter_form'
|
||||
size='small'
|
||||
variant={'borderless'}
|
||||
initialValues={{ }}
|
||||
// onFinish={() => {}}
|
||||
className='*:mb-2 *:border-b *:border-t-0 *:border-x-0 *:border-indigo-100 *:border-solid '
|
||||
requiredMark={false}
|
||||
labelCol={{ span: 3 }}>
|
||||
<Form.Item label='To' name={'to'} rules={[{required: true}]}>
|
||||
{/* <Mentions
|
||||
split='; '
|
||||
options={[
|
||||
{ value: 'afc163', label: 'afc163' },
|
||||
{ value: 'zombieJ', label: 'zombieJ' },
|
||||
{ value: 'yesmeck', label: 'yesmeck' },
|
||||
]}
|
||||
/> */}
|
||||
<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='Cc' name={'cc'} hidden={!showCc}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label='Bcc' name={'bcc'} hidden={!showBcc}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label='Subject' name={'subject'} rules={[{required: true}]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='content' hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='abstract' hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<LexicalEditor {...{isRichText}} onChange={handleEditorChange} />
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default EmailEditorPopup;
|
@ -0,0 +1,49 @@
|
||||
import React, { useState } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Rnd } from 'react-rnd';
|
||||
|
||||
const DraggableResizableModal = ({ children }) => {
|
||||
const [width, setWidth] = useState(320);
|
||||
const [height, setHeight] = useState(240);
|
||||
const [x, setX] = useState(300);
|
||||
const [y, setY] = useState(72);
|
||||
const [isMaximized, setIsMaximized] = useState(false);
|
||||
|
||||
const handleMaximize = () => {
|
||||
if (isMaximized) {
|
||||
setWidth(320);
|
||||
setHeight(240);
|
||||
setX(0);
|
||||
setY(0);
|
||||
} else {
|
||||
setWidth(window.innerWidth-20);
|
||||
setHeight(window.innerHeight-20);
|
||||
setX(0);
|
||||
setY(0);
|
||||
}
|
||||
|
||||
setIsMaximized(!isMaximized);
|
||||
};
|
||||
|
||||
const render = (
|
||||
<Rnd className='bg-indigo-100 z-20 !fixed'
|
||||
size={{ width: width, height: height }}
|
||||
position={{ x: x, y: y }}
|
||||
onDragStop={(e, d) => { setX(d.x); setY(d.y); }}
|
||||
onResizeStop={(e, direction, ref) => {
|
||||
setWidth(ref.style.width);
|
||||
setHeight(ref.style.height);
|
||||
}}
|
||||
>
|
||||
<button onClick={handleMaximize}>Maximize</button>
|
||||
{children}
|
||||
</Rnd>
|
||||
);
|
||||
|
||||
return ReactDOM.createPortal(
|
||||
render,
|
||||
document.body
|
||||
);
|
||||
};
|
||||
|
||||
export default DraggableResizableModal;
|
Loading…
Reference in New Issue