diff --git a/public/images/icons/horizontal-rule.svg b/public/images/icons/horizontal-rule.svg new file mode 100644 index 0000000..cb84970 --- /dev/null +++ b/public/images/icons/horizontal-rule.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/LexicalEditor/Index.jsx b/src/components/LexicalEditor/Index.jsx index 66d1f4f..501c976 100644 --- a/src/components/LexicalEditor/Index.jsx +++ b/src/components/LexicalEditor/Index.jsx @@ -17,6 +17,8 @@ import { AutoLinkNode, LinkNode } from "@lexical/link"; import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin"; import { ListPlugin } from "@lexical/react/LexicalListPlugin"; import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin"; +import {HorizontalRulePlugin} from '@lexical/react/LexicalHorizontalRulePlugin'; +import {HorizontalRuleNode} from '@lexical/react/LexicalHorizontalRuleNode'; import { TRANSFORMERS } from "@lexical/markdown"; import ListMaxIndentLevelPlugin from "./plugins/ListMaxIndentLevelPlugin"; @@ -25,7 +27,7 @@ import AutoLinkPlugin from "./plugins/AutoLinkPlugin"; import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; import { $getRoot, $getSelection, } from 'lexical'; -import { $generateHtmlFromNodes } from '@lexical/html'; +import { $generateHtmlFromNodes, $generateNodesFromDOM } from '@lexical/html'; import './styles.css'; @@ -53,9 +55,37 @@ const editorConfig = { TableCellNode, TableRowNode, AutoLinkNode, - LinkNode + LinkNode, + HorizontalRuleNode, ] }; + +function LexicalDefaultValuePlugin({ value = "" }= {}) { + const [editor] = useLexicalComposerContext(); + + const updateHTML = (editor, value, clear) => { + const root = $getRoot(); + const parser = new DOMParser(); + const dom = parser.parseFromString(value, "text/html"); + const nodes = $generateNodesFromDOM(editor, dom); + if (clear) { + root.clear(); + } + // console.log(nodes); + + root.append(...nodes.filter(n => n)); + }; + + useEffect(() => { + if (editor && value) { + editor.update(() => { + updateHTML(editor, value, true); + }); + } + }, [value]); + + return null; +} function MyOnChangePlugin({ onChange }) { const [editor] = useLexicalComposerContext(); useEffect(() => { @@ -80,7 +110,7 @@ function MyOnChangePlugin({ onChange }) { }, [editor, onChange]); return null; } -export default function Editor({ isRichText, onChange, ...props }) { +export default function Editor({ isRichText, onChange, initialValue, ...props }) { return (
@@ -94,6 +124,7 @@ export default function Editor({ isRichText, onChange, ...props }) { )} {import.meta.env.DEV && } + @@ -101,6 +132,7 @@ export default function Editor({ isRichText, onChange, ...props }) { +
diff --git a/src/components/LexicalEditor/plugins/ToolbarPlugin.jsx b/src/components/LexicalEditor/plugins/ToolbarPlugin.jsx index 16c22c3..bafc0ef 100644 --- a/src/components/LexicalEditor/plugins/ToolbarPlugin.jsx +++ b/src/components/LexicalEditor/plugins/ToolbarPlugin.jsx @@ -39,6 +39,7 @@ import { getDefaultCodeLanguage, getCodeLanguages } from "@lexical/code"; +import { INSERT_HORIZONTAL_RULE_COMMAND, } from '@lexical/react/LexicalHorizontalRuleNode'; const LowPriority = 1; @@ -413,12 +414,12 @@ function BlockOptionsDropdownList({ - + */} ); } @@ -571,6 +572,13 @@ export default function ToolbarPlugin() { } }, [editor, isLink]); + const insertHorizontalRule = useCallback(() => { + editor.dispatchCommand( + INSERT_HORIZONTAL_RULE_COMMAND, + undefined, + ); + }, [editor]); + return (
- + */}
*/} {/*
{emailOrigin.body}
*/} -
+
{/*
{emailOrigin.attachments.map(attachment =>
{attachment.name}
)}
*/} diff --git a/src/views/Conversations/Online/Input/EmailEditorPopup.jsx b/src/views/Conversations/Online/Input/EmailEditorPopup.jsx index e0fe064..cddeaf5 100644 --- a/src/views/Conversations/Online/Input/EmailEditorPopup.jsx +++ b/src/views/Conversations/Online/Input/EmailEditorPopup.jsx @@ -6,7 +6,8 @@ import '@dckj/react-better-modal/dist/index.css'; import LexicalEditor from '@/components/LexicalEditor'; import { $getRoot, $getSelection, } from 'lexical'; -import {$generateHtmlFromNodes} from '@lexical/html'; +import {$generateHtmlFromNodes, $generateNodesFromDOM} from '@lexical/html'; +import { isEmpty } from '@/utils/commons'; // import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin'; // import { LexicalComposer } from '@lexical/react/LexicalComposer'; @@ -26,7 +27,7 @@ import {$generateHtmlFromNodes} from '@lexical/html'; // function onError(error) { // console.error(error); // } -const EmailEditorPopup = ({ open, setOpen, fromEmail, reference, ...props }) => { +const EmailEditorPopup = ({ open, setOpen, fromEmail, reference, quote={}, ...props }) => { const [form] = Form.useForm(); // const [open, setOpen] = useState(false); @@ -43,6 +44,7 @@ const EmailEditorPopup = ({ open, setOpen, fromEmail, reference, ...props }) => function onHandleCancel() { console.log('onCancel callback'); + form.resetFields(); setOpen(false); } function onStageChange({ state }) { @@ -73,10 +75,10 @@ const EmailEditorPopup = ({ open, setOpen, fromEmail, reference, ...props }) => const onHandleSend = () => { console.log('onSend callback', '\nisRichText', isRichText); - console.log(form.getFieldsValue()); + // console.log(form.getFieldsValue()); const body = structuredClone(form.getFieldsValue()); body.content = isRichText ? htmlContent : textContent; - console.log('body', body); + // console.log('body', body); form .validateFields() .then((values) => {}) @@ -87,6 +89,58 @@ const EmailEditorPopup = ({ open, setOpen, fromEmail, reference, ...props }) => // setOpen(false); }; + const [initialForm, setInitialForm] = useState({}); + const [initialContent, setInitialContent] = useState(''); + useEffect(() => { + if (isEmpty(quote)) { + return () => {}; + } + setShowCc(!isEmpty(quote.cc)); + const { fromEmail, replyToEmail, subject, content } = quote; + const preQuoteBody = `



+


+

+ + From: + + ${quote.fromName} <${quote.fromEmail}> +

+

+ + Sent: + + ${quote.sent} +

+

+ + To: + + ${quote.toName} <${quote.toEmail}> +

+

+ + Subject: + + ${subject} +

+

${content}

+ `; + //
+ //
+ + setInitialContent(preQuoteBody); + const _formValues = { + to: replyToEmail || fromEmail, + cc: quote.cc, + subject: `Re: ${subject}`, + }; + form.setFieldsValue(_formValues); + setInitialForm(_formValues); + + return () => {}; + }, [quote]) + + return ( <> initialHeight={600} initialTop={74} initialLeft={window.innerWidth - 700} - title={`${reference ? '回复: ' : '写邮件: '} ${fromEmail || ''}`} + title={initialForm.subject || `${reference ? '回复: ' : '写邮件: '} ${fromEmail || ''}`} minimizeButton={<>} onMove={onHandleMove} onResize={onHandleResize} @@ -132,7 +186,7 @@ const EmailEditorPopup = ({ open, setOpen, fromEmail, reference, ...props }) => name='conversation_filter_form' size='small' variant={'borderless'} - initialValues={{ }} + initialValues={{}} // onFinish={() => {}} className='*:mb-2 *:border-b *:border-t-0 *:border-x-0 *:border-indigo-100 *:border-solid ' requiredMark={false} @@ -179,7 +233,7 @@ const EmailEditorPopup = ({ open, setOpen, fromEmail, reference, ...props }) => - + ); diff --git a/src/views/Conversations/Online/MessagesWrapper.jsx b/src/views/Conversations/Online/MessagesWrapper.jsx index 2ad4bab..ebf2987 100644 --- a/src/views/Conversations/Online/MessagesWrapper.jsx +++ b/src/views/Conversations/Online/MessagesWrapper.jsx @@ -130,9 +130,11 @@ const MessagesWrapper = ({ updateRead = true, forceGetMessages }) => { const [openEmailEditor, setOpenEmailEditor] = useState(false); const [fromEmail, setFromEmail] = useState(''); const [ReferEmailMsg, setReferEmailMsg] = useState(''); - const onOpenEditor = (email_addr) => { + const onOpenEditor = (emailOrigin) => { + const { replyToEmail: email_addr, content } = emailOrigin; setOpenEmailEditor(true); setFromEmail(email_addr); + setReferEmailMsg(emailOrigin); }; const [openEmailDetail, setOpenEmailDetail] = useState(false); @@ -191,7 +193,7 @@ const MessagesWrapper = ({ updateRead = true, forceGetMessages }) => { onCancel={() => setNewChatModalVisible(false)} /> {/* */} - + );