143 lines
4.5 KiB
TypeScript
143 lines
4.5 KiB
TypeScript
import * as React from "react";
|
|
// --- Lib ---
|
|
import { Highlight } from "@tiptap/extension-highlight";
|
|
import { Image } from "@tiptap/extension-image";
|
|
import { TaskItem, TaskList } from "@tiptap/extension-list";
|
|
import { Subscript } from "@tiptap/extension-subscript";
|
|
import { Superscript } from "@tiptap/extension-superscript";
|
|
import { TextAlign } from "@tiptap/extension-text-align";
|
|
import { Typography } from "@tiptap/extension-typography";
|
|
import { Selection } from "@tiptap/extensions";
|
|
import { EditorContent, EditorContext, useEditor } from "@tiptap/react";
|
|
// --- Tiptap Core Extensions ---
|
|
import { StarterKit } from "@tiptap/starter-kit";
|
|
|
|
import { handleImageUpload, MAX_FILE_SIZE } from "@/lib/tiptap-utils";
|
|
import { useCursorVisibility } from "@/hooks/use-cursor-visibility";
|
|
// --- Hooks ---
|
|
import { useIsMobile } from "@/hooks/use-mobile";
|
|
import { useWindowSize } from "@/hooks/use-window-size";
|
|
import { HorizontalRule } from "@/components/shared/tiptap/tiptap-node/horizontal-rule-node/horizontal-rule-node-extension";
|
|
// --- Tiptap Node ---
|
|
import { ImageUploadNode } from "@/components/shared/tiptap/tiptap-node/image-upload-node/image-upload-node-extension";
|
|
// import content from "@/components/shared/tiptap/tiptap-templates/simple/data/content.json";
|
|
import { Toolbar } from "@/components/shared/tiptap/tiptap-ui-primitive/toolbar";
|
|
|
|
import { Icons } from "../shared/icons";
|
|
import {
|
|
MainToolbarContent,
|
|
MobileToolbarContent,
|
|
} from "../shared/tiptap/tiptap-templates/simple/simple-editor";
|
|
|
|
export function EmailEditor({
|
|
onGetEditorValue,
|
|
}: {
|
|
onGetEditorValue: (html: string, text: string) => void;
|
|
}) {
|
|
const isMobile = useIsMobile();
|
|
const { height } = useWindowSize();
|
|
const [mobileView, setMobileView] = React.useState<
|
|
"main" | "highlighter" | "link"
|
|
>("main");
|
|
const toolbarRef = React.useRef<HTMLDivElement>(null);
|
|
|
|
const editor = useEditor({
|
|
immediatelyRender: false,
|
|
shouldRerenderOnTransaction: false,
|
|
editorProps: {
|
|
attributes: {
|
|
autocomplete: "off",
|
|
autocorrect: "off",
|
|
autocapitalize: "off",
|
|
"aria-label": "Main content area, start typing to enter text.",
|
|
class: "simple-editor",
|
|
},
|
|
},
|
|
extensions: [
|
|
StarterKit.configure({
|
|
horizontalRule: false,
|
|
link: {
|
|
openOnClick: false,
|
|
enableClickSelection: true,
|
|
},
|
|
}),
|
|
HorizontalRule,
|
|
TextAlign.configure({ types: ["heading", "paragraph"] }),
|
|
TaskList,
|
|
TaskItem.configure({ nested: true }),
|
|
Highlight.configure({ multicolor: true }),
|
|
Image,
|
|
Typography,
|
|
Superscript,
|
|
Subscript,
|
|
Selection,
|
|
ImageUploadNode.configure({
|
|
accept: "image/*",
|
|
maxSize: MAX_FILE_SIZE,
|
|
limit: 3,
|
|
upload: handleImageUpload,
|
|
onError: (error) => console.error("Upload failed:", error),
|
|
}),
|
|
],
|
|
content: "Hi",
|
|
onUpdate: ({ editor }) => {
|
|
const html = editor.getHTML();
|
|
const text = editor.getText();
|
|
onGetEditorValue(html, text);
|
|
},
|
|
});
|
|
|
|
const rect = useCursorVisibility({
|
|
editor,
|
|
overlayHeight: toolbarRef.current?.getBoundingClientRect().height ?? 0,
|
|
});
|
|
|
|
React.useEffect(() => {
|
|
if (!isMobile && mobileView !== "main") {
|
|
setMobileView("main");
|
|
}
|
|
}, [isMobile, mobileView]);
|
|
editor?.getHTML();
|
|
return (
|
|
<div className="simple-editor-wrapper">
|
|
<EditorContext.Provider value={{ editor }}>
|
|
<Toolbar
|
|
ref={toolbarRef}
|
|
style={{
|
|
...(isMobile
|
|
? {
|
|
bottom: `calc(100% - ${height - rect.y}px)`,
|
|
}
|
|
: {}),
|
|
}}
|
|
>
|
|
{mobileView === "main" ? (
|
|
<MainToolbarContent
|
|
onHighlighterClick={() => setMobileView("highlighter")}
|
|
onLinkClick={() => setMobileView("link")}
|
|
isMobile={isMobile}
|
|
/>
|
|
) : (
|
|
<MobileToolbarContent
|
|
type={mobileView === "highlighter" ? "highlighter" : "link"}
|
|
onBack={() => setMobileView("main")}
|
|
/>
|
|
)}
|
|
<button
|
|
className="ml-2"
|
|
onClick={() => editor?.chain().clearContent().run()}
|
|
>
|
|
<Icons.eraser className="size-4 hover:text-red-500" />
|
|
</button>
|
|
</Toolbar>
|
|
|
|
<EditorContent
|
|
editor={editor}
|
|
role="presentation"
|
|
className="simple-editor-content"
|
|
/>
|
|
</EditorContext.Provider>
|
|
</div>
|
|
);
|
|
}
|