import React, { useEffect, useRef, useState } from "react"; import { EditorProvider, useCurrentEditor } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; import { Color } from "@tiptap/extension-color"; import ListItem from "@tiptap/extension-list-item"; import TextStyle from "@tiptap/extension-text-style"; import Placeholder from "@tiptap/extension-placeholder"; import Image from "@tiptap/extension-image"; import IconButton from "@mui/material/IconButton"; import FormatBoldIcon from "@mui/icons-material/FormatBold"; import FormatItalicIcon from "@mui/icons-material/FormatItalic"; import StrikethroughSIcon from "@mui/icons-material/StrikethroughS"; import FormatClearIcon from "@mui/icons-material/FormatClear"; import FormatListBulletedIcon from "@mui/icons-material/FormatListBulleted"; import FormatListNumberedIcon from "@mui/icons-material/FormatListNumbered"; import CodeIcon from "@mui/icons-material/Code"; import ImageIcon from "@mui/icons-material/Image"; // Import Image icon import FormatQuoteIcon from "@mui/icons-material/FormatQuote"; import HorizontalRuleIcon from "@mui/icons-material/HorizontalRule"; import UndoIcon from "@mui/icons-material/Undo"; import RedoIcon from "@mui/icons-material/Redo"; import FormatHeadingIcon from "@mui/icons-material/FormatSize"; import DeveloperModeIcon from "@mui/icons-material/DeveloperMode"; import Compressor from "compressorjs"; import ImageResize from "tiptap-extension-resize-image"; // Import the ResizeImage extension import { isMobile } from "../../App"; const MenuBar = ({ setEditorRef, isChat }) => { const { editor } = useCurrentEditor(); const fileInputRef = useRef(null); if (!editor) { return null; } useEffect(() => { if (editor && setEditorRef) { setEditorRef(editor); } }, [editor, setEditorRef]); const handleImageUpload = async (file) => { let compressedFile; await new Promise((resolve) => { new Compressor(file, { quality: 0.6, maxWidth: 1200, mimeType: "image/webp", success(result) { compressedFile = new File([result], "image.webp", { type: "image/webp", }); resolve(); }, error(err) { console.error("Image compression error:", err); }, }); }); if (compressedFile) { const reader = new FileReader(); reader.onload = () => { const url = reader.result; editor .chain() .focus() .setImage({ src: url, style: "width: auto" }) .run(); fileInputRef.current.value = ""; }; reader.readAsDataURL(compressedFile); } }; const triggerImageUpload = () => { fileInputRef.current.click(); // Trigger the file input click }; const handlePaste = (event) => { const items = event.clipboardData.items; for (const item of items) { if (item.type.startsWith("image/")) { const file = item.getAsFile(); if (file) { event.preventDefault(); // Prevent the default paste behavior handleImageUpload(file); // Call the image upload function } } } }; useEffect(() => { if (editor) { editor.view.dom.addEventListener("paste", handlePaste); return () => { editor.view.dom.removeEventListener("paste", handlePaste); }; } }, [editor]); return (
editor.chain().focus().toggleBold().run()} disabled={!editor.can().chain().focus().toggleBold().run()} sx={{ color: editor.isActive("bold") ? "white" : "gray", padding: isMobile ? "5px" : "revert", }} > editor.chain().focus().toggleItalic().run()} disabled={!editor.can().chain().focus().toggleItalic().run()} sx={{ color: editor.isActive("italic") ? "white" : "gray", padding: isMobile ? "5px" : "revert", }} > editor.chain().focus().toggleStrike().run()} disabled={!editor.can().chain().focus().toggleStrike().run()} sx={{ color: editor.isActive("strike") ? "white" : "gray", padding: isMobile ? "5px" : "revert", }} > editor.chain().focus().toggleCode().run()} disabled={!editor.can().chain().focus().toggleCode().run()} sx={{ color: editor.isActive("code") ? "white" : "gray", padding: isMobile ? "5px" : "revert", }} > editor.chain().focus().unsetAllMarks().run()} sx={{ color: editor.isActive("bold") || editor.isActive("italic") || editor.isActive("strike") || editor.isActive("code") ? "white" : "gray", padding: isMobile ? "5px" : "revert", }} > editor.chain().focus().toggleBulletList().run()} sx={{ color: editor.isActive("bulletList") ? "white" : "gray", padding: isMobile ? "5px" : "revert", }} > editor.chain().focus().toggleOrderedList().run()} sx={{ color: editor.isActive("orderedList") ? "white" : "gray", padding: isMobile ? "5px" : "revert", }} > editor.chain().focus().toggleCodeBlock().run()} sx={{ color: editor.isActive("codeBlock") ? "white" : "gray", padding: isMobile ? "5px" : "revert", }} > editor.chain().focus().toggleBlockquote().run()} sx={{ color: editor.isActive("blockquote") ? "white" : "gray", padding: isMobile ? "5px" : "revert", }} > editor.chain().focus().setHorizontalRule().run()} disabled={!editor.can().chain().focus().setHorizontalRule().run()} sx={{ color: "gray", padding: isMobile ? "5px" : "revert" }} > editor.chain().focus().toggleHeading({ level: 1 }).run() } sx={{ color: editor.isActive("heading", { level: 1 }) ? "white" : "gray", padding: isMobile ? "5px" : "revert", }} > editor.chain().focus().undo().run()} disabled={!editor.can().chain().focus().undo().run()} sx={{ color: "gray", padding: isMobile ? "5px" : "revert" }} > editor.chain().focus().redo().run()} disabled={!editor.can().chain().focus().redo().run()} sx={{ color: "gray" }} > {!isChat && ( <> handleImageUpload(event.target.files[0])} accept="image/*" /> )}
); }; const extensions = [ Color.configure({ types: [TextStyle.name, ListItem.name] }), TextStyle.configure({ types: [ListItem.name] }), StarterKit.configure({ bulletList: { keepMarks: true, keepAttributes: false, }, orderedList: { keepMarks: true, keepAttributes: false, }, }), Placeholder.configure({ placeholder: "Start typing here...", }), ImageResize, ]; const content = ``; export default ({ setEditorRef, onEnter, disableEnter, isChat, maxHeightOffset, setIsFocusedParent, isFocusedParent, overrideMobile, customEditorHeight, }) => { const [isFocused, setIsFocused] = useState(false); const extensionsFiltered = isChat ? extensions.filter((item) => item?.name !== "image") : extensions; const editorRef = useRef(null); const setEditorRefFunc = (editorInstance) => { editorRef.current = editorInstance; setEditorRef(editorInstance); }; const handleFocus = () => { if (!isMobile) return; setIsFocusedParent(true); }; const handleBlur = () => { const htmlContent = editorRef.current.getHTML(); if (!htmlContent?.trim() || htmlContent?.trim() === "

") { // Set focus state based on content } }; return ( ) } extensions={extensionsFiltered} content={content} onCreate={({ editor }) => { editor.on("focus", handleFocus); // Listen for focus event editor.on("blur", handleBlur); // Listen for blur event }} onUpdate={({ editor }) => { editor.on('focus', handleFocus); // Ensure focus is updated editor.on('blur', handleBlur); // Ensure blur is updated }} editorProps={{ attributes: { class: "tiptap-prosemirror", style: isMobile ? `overflow: auto; min-height: ${ customEditorHeight ? "200px" : "0px" }; max-height:calc(100svh - ${customEditorHeight || "140px"})`: `overflow: auto; max-height: 250px`, }, handleKeyDown(view, event) { if (!disableEnter && event.key === "Enter") { if (event.shiftKey) { view.dispatch( view.state.tr.replaceSelectionWith( view.state.schema.nodes.hardBreak.create() ) ); return true; } else { if (typeof onEnter === "function") { onEnter(); } return true; } } return false; }, }} /> ); };