diff --git a/package.json b/package.json index 08537f2..de1f344 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "preview": "vite preview" }, "dependencies": { + "@dckj/react-better-modal": "^0.1.2", "@lexical/react": "^0.17.1", "@vonage/client-sdk": "^1.6.0", "antd": "^5.14.0", @@ -19,11 +20,13 @@ "dayjs": "^1.11.10", "emoji-picker-react": "^4.8.0", "lexical": "^0.17.1", + "re-resizable": "^6.9.18", "react": "^18.2.0", "react-chat-elements": "^12.0.11", "react-dom": "^18.2.0", "react-draggable": "^4.4.6", "react-quill": "^2.0.0", + "react-rnd": "^10.4.12", "react-router-dom": "^6.21.1", "rxjs": "^7.8.1", "uuid": "^9.0.1", diff --git a/src/components/Icons.jsx b/src/components/Icons.jsx index 36fc9e6..1ad9232 100644 --- a/src/components/Icons.jsx +++ b/src/components/Icons.jsx @@ -27,3 +27,8 @@ const Sent = () => ( ) export const SentIcon = (props) => ; + +const Filter = () => ( + +) +export const FilterIcon = (props) => ; diff --git a/src/components/LexicalEditor/Index.jsx b/src/components/LexicalEditor/Index.jsx index d853665..66d1f4f 100644 --- a/src/components/LexicalEditor/Index.jsx +++ b/src/components/LexicalEditor/Index.jsx @@ -1,6 +1,8 @@ +import { createContext, useEffect, useState } from 'react'; import ExampleTheme from "./themes/ExampleTheme"; import { LexicalComposer } from "@lexical/react/LexicalComposer"; import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin"; +import {PlainTextPlugin} from '@lexical/react/LexicalPlainTextPlugin'; import { ContentEditable } from "@lexical/react/LexicalContentEditable"; import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin"; import { AutoFocusPlugin } from "@lexical/react/LexicalAutoFocusPlugin"; @@ -20,6 +22,10 @@ import { TRANSFORMERS } from "@lexical/markdown"; import ListMaxIndentLevelPlugin from "./plugins/ListMaxIndentLevelPlugin"; import CodeHighlightPlugin from "./plugins/CodeHighlightPlugin"; import AutoLinkPlugin from "./plugins/AutoLinkPlugin"; +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; + +import { $getRoot, $getSelection, } from 'lexical'; +import { $generateHtmlFromNodes } from '@lexical/html'; import './styles.css'; @@ -50,19 +56,42 @@ const editorConfig = { LinkNode ] }; +function MyOnChangePlugin({ onChange }) { + const [editor] = useLexicalComposerContext(); + useEffect(() => { + return editor.registerUpdateListener(({ editorState }) => { + // const editorStateJSON = editorState.toJSON(); + let html; + let textContent; + editorState.read(() => { + const root = $getRoot(); + const textContent = root.getTextContent(); + // console.log('textContent', textContent); + + const html = $generateHtmlFromNodes(editor); + // console.log('html', html); -export default function Editor() { + // setEditorContent(content); + if (typeof onChange === 'function') { + onChange({ editorState, html, textContent }); + } + }); + }); + }, [editor, onChange]); + return null; +} +export default function Editor({ isRichText, onChange, ...props }) { return ( -
- -
+
+ {isRichText && } +
{/* */} - } - placeholder={} - ErrorBoundary={LexicalErrorBoundary} - /> + {isRichText ? ( + } placeholder={} ErrorBoundary={LexicalErrorBoundary} /> + ) : ( + } ErrorBoundary={LexicalErrorBoundary} /> + )} {import.meta.env.DEV && } @@ -72,6 +101,7 @@ export default function Editor() { +
diff --git a/src/components/LexicalEditor/plugins/ToolbarPlugin.jsx b/src/components/LexicalEditor/plugins/ToolbarPlugin.jsx index 52aa8a0..16c22c3 100644 --- a/src/components/LexicalEditor/plugins/ToolbarPlugin.jsx +++ b/src/components/LexicalEditor/plugins/ToolbarPlugin.jsx @@ -48,6 +48,7 @@ const supportedBlockTypes = new Set([ "code", "h1", "h2", + "h3", "ul", "ol" ]); @@ -346,6 +347,18 @@ function BlockOptionsDropdownList({ } setShowBlockOptionsDropDown(false); }; + const formatSmallHeading3 = () => { + if (blockType !== "h3") { + editor.update(() => { + const selection = $getSelection(); + + if ($isRangeSelection(selection)) { + $wrapNodes(selection, () => $createHeadingNode("h3")); + } + }); + } + setShowBlockOptionsDropDown(false); + }; const formatBulletList = () => { if (blockType !== "ul") { @@ -408,6 +421,11 @@ function BlockOptionsDropdownList({ Small Heading {blockType === "h2" && } +
diff --git a/src/views/Conversations/Online/Input/EmailComposer.jsx b/src/views/Conversations/Online/Input/EmailChannelTab.jsx similarity index 54% rename from src/views/Conversations/Online/Input/EmailComposer.jsx rename to src/views/Conversations/Online/Input/EmailChannelTab.jsx index d759385..44c5c16 100644 --- a/src/views/Conversations/Online/Input/EmailComposer.jsx +++ b/src/views/Conversations/Online/Input/EmailChannelTab.jsx @@ -3,26 +3,33 @@ import { Button, Flex } from 'antd'; import 'react-quill/dist/quill.snow.css'; import EmailEditor from './EmailEditor'; import { WABIcon } from '@/components/Icons'; +import EmailEditorPopup from './EmailEditorPopup'; +import EmailEditorPopup2 from './EmailEditorPopup1'; const EmailComposer = ({ ...props }) => { const [open, setOpen] = useState(false); const [fromEmail, setFromEmail] = useState(''); - const openEditor = (email_addr) => { + const openEditor = (email_addr, i) => { setOpen(true); setFromEmail(email_addr); }; return ( - {[{ email: 'lyt@hainatravel.com', name: 'LYT' }].map(({ email, name }, i) => ( + {[ + { email: 'lyt@hainatravel.com', name: 'LYT' }, + { email: 'lot@hainatravel.com', name: 'LOT' }, + ].map(({ email, name }, i) => ( ))} - + {/* */} + + {/* */} ); }; diff --git a/src/views/Conversations/Online/Input/EmailEditor.jsx b/src/views/Conversations/Online/Input/EmailEditor.jsx index c41f9ff..df1a0c3 100644 --- a/src/views/Conversations/Online/Input/EmailEditor.jsx +++ b/src/views/Conversations/Online/Input/EmailEditor.jsx @@ -1,5 +1,6 @@ import { createContext, useEffect, useState, useRef } from 'react'; import { Button, Flex, Modal } from 'antd'; +import { FilterOutlined, FilterTwoTone, CloseOutlined, CloseCircleOutlined, ExpandAltOutlined, ArrowsAltOutlined, ShrinkOutlined } from '@ant-design/icons'; import Draggable from 'react-draggable'; import 'react-quill/dist/quill.snow.css'; @@ -41,7 +42,7 @@ const LexicalEditor1 = () => { ); }; -const EmailEditor = ({ mobile, open, setOpen, fromEmail, ...props }) => { +const EmailEditor = ({ mobile, open, setOpen, fromEmail, reference, ...props }) => { const [dragDisabled, setDragDisabled] = useState(true); const [bounds, setBounds] = useState({ @@ -69,11 +70,7 @@ const EmailEditor = ({ mobile, open, setOpen, fromEmail, ...props }) => { return ( { if (dragDisabled) { setDragDisabled(false); @@ -88,9 +85,11 @@ const EmailEditor = ({ mobile, open, setOpen, fromEmail, ...props }) => { onBlur={() => {}} // end > - 写邮件: {fromEmail} + + {reference ? '回复: ' : '写邮件: ' }{fromEmail}
} + // closeIcon={<> } open={open} mask={false} maskClosable={false} diff --git a/src/views/Conversations/Online/Input/EmailEditorPopup.jsx b/src/views/Conversations/Online/Input/EmailEditorPopup.jsx new file mode 100644 index 0000000..e0fe064 --- /dev/null +++ b/src/views/Conversations/Online/Input/EmailEditorPopup.jsx @@ -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 ( + <> + } + onMove={onHandleMove} + onResize={onHandleResize} + onCancel={onHandleCancel} + // onOk={onHandleOk} + onStageChange={onStageChange} + footer={ +
+ + + {/* */} + setIsRichText(!(e.target.checked))}>纯文本 + +
+ }> +
{}} + className='*:mb-2 *:border-b *:border-t-0 *:border-x-0 *:border-indigo-100 *:border-solid ' + requiredMark={false} + labelCol={{ span: 3 }}> + + {/* */} + + {!showCc && ( + + )} + {!showBcc && ( + + )} + + } + /> + + + + + + + + +
+ +
+ + ); +}; +export default EmailEditorPopup; diff --git a/src/views/Conversations/Online/Input/EmailEditorPopup1.jsx b/src/views/Conversations/Online/Input/EmailEditorPopup1.jsx new file mode 100644 index 0000000..c440567 --- /dev/null +++ b/src/views/Conversations/Online/Input/EmailEditorPopup1.jsx @@ -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 = ( + { setX(d.x); setY(d.y); }} + onResizeStop={(e, direction, ref) => { + setWidth(ref.style.width); + setHeight(ref.style.height); + }} + > + + {children} + + ); + + return ReactDOM.createPortal( + render, + document.body + ); +}; + +export default DraggableResizableModal; diff --git a/src/views/Conversations/Online/MessagesWrapper.jsx b/src/views/Conversations/Online/MessagesWrapper.jsx index a717774..d4655ac 100644 --- a/src/views/Conversations/Online/MessagesWrapper.jsx +++ b/src/views/Conversations/Online/MessagesWrapper.jsx @@ -10,6 +10,7 @@ import ConversationNewItem from './ConversationsNewItem'; import emailItem from './Components/emailSent.json'; import emailReItem from './Components/emailRe.json'; import EmailEditor from './Input/EmailEditor'; +import EmailEditorPopup from './Input/EmailEditorPopup'; const MessagesWrapper = ({ updateRead = true, forceGetMessages }) => { const userId = useAuthStore((state) => state.loginUser.userId); @@ -127,6 +128,7 @@ const MessagesWrapper = ({ updateRead = true, forceGetMessages }) => { const [openEmailEditor, setOpenEmailEditor] = useState(false); const [fromEmail, setFromEmail] = useState(''); + const [ReferEmailMsg, setReferEmailMsg] = useState(''); const onOpenEditor = (email_addr) => { setOpenEmailEditor(true); setFromEmail(email_addr); @@ -148,6 +150,7 @@ const MessagesWrapper = ({ updateRead = true, forceGetMessages }) => { getMoreMessages, loadNextPage: currentConversation?.loadNextPage ?? true, onOpenEditor, + setRefernce: setReferEmailMsg, }} /> { }} onCancel={() => setNewChatModalVisible(false)} /> - - + {/* */} + + ); }; export default MessagesWrapper; diff --git a/src/views/Conversations/Online/ReplyWrapper.jsx b/src/views/Conversations/Online/ReplyWrapper.jsx index a7c578f..fdbff58 100644 --- a/src/views/Conversations/Online/ReplyWrapper.jsx +++ b/src/views/Conversations/Online/ReplyWrapper.jsx @@ -2,7 +2,7 @@ import { Children, createContext, useEffect, useState } from 'react'; import { Tabs } from 'antd'; import { MailFilled, MailOutlined, WhatsAppOutlined } from '@ant-design/icons'; import InputComposer from './Input/InputComposer'; -import EmailComposer from './Input/EmailComposer'; +import EmailComposer from './Input/EmailChannelTab'; import { WABIcon } from '@/components/Icons'; import useConversationStore from '@/stores/ConversationStore'; import { useShallow } from 'zustand/react/shallow'; diff --git a/tailwind.config.js b/tailwind.config.js index 097e739..d7d5084 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -29,6 +29,10 @@ export default { // gridTemplateColumns: { // 'responsive':repeat(autofill,minmax('300px',1fr)) // } + boxShadow: { + 'heavy': '0 1px 7px 1px rgba(0, 0, 0, 0.3)', + '3xl': '0 35px 60px -15px rgba(0, 0, 0, 0.3)', + } }, }, plugins: [],