"use client" import * as React from "react" import { EditorContent, EditorContext, useEditor } from "@tiptap/react" // --- Tiptap Core Extensions --- import { StarterKit } from "@tiptap/starter-kit" import { Image } from "@tiptap/extension-image" import { TaskItem, TaskList } from "@tiptap/extension-list" import { TextAlign } from "@tiptap/extension-text-align" import { Typography } from "@tiptap/extension-typography" import { Highlight } from "@tiptap/extension-highlight" import { Subscript } from "@tiptap/extension-subscript" import { Superscript } from "@tiptap/extension-superscript" import { Selection } from "@tiptap/extensions" // --- UI Primitives --- import { Button } from "@/components/tiptap-ui-primitive/button" import { Spacer } from "@/components/tiptap-ui-primitive/spacer" import { Toolbar, ToolbarGroup, ToolbarSeparator, } from "@/components/tiptap-ui-primitive/toolbar" // --- Tiptap Node --- import { ImageUploadNode } from "@/components/tiptap-node/image-upload-node/image-upload-node-extension" import { HorizontalRule } from "@/components/tiptap-node/horizontal-rule-node/horizontal-rule-node-extension" import "@/components/tiptap-node/blockquote-node/blockquote-node.scss" import "@/components/tiptap-node/code-block-node/code-block-node.scss" import "@/components/tiptap-node/horizontal-rule-node/horizontal-rule-node.scss" import "@/components/tiptap-node/list-node/list-node.scss" import "@/components/tiptap-node/image-node/image-node.scss" import "@/components/tiptap-node/heading-node/heading-node.scss" import "@/components/tiptap-node/paragraph-node/paragraph-node.scss" // --- Tiptap UI --- import { HeadingDropdownMenu } from "@/components/tiptap-ui/heading-dropdown-menu" import { ImageUploadButton } from "@/components/tiptap-ui/image-upload-button" import { ListDropdownMenu } from "@/components/tiptap-ui/list-dropdown-menu" import { BlockquoteButton } from "@/components/tiptap-ui/blockquote-button" import { CodeBlockButton } from "@/components/tiptap-ui/code-block-button" import { ColorHighlightPopover, ColorHighlightPopoverContent, ColorHighlightPopoverButton, } from "@/components/tiptap-ui/color-highlight-popover" import { LinkPopover, LinkContent, LinkButton, } from "@/components/tiptap-ui/link-popover" import { MarkButton } from "@/components/tiptap-ui/mark-button" import { TextAlignButton } from "@/components/tiptap-ui/text-align-button" import { UndoRedoButton } from "@/components/tiptap-ui/undo-redo-button" // --- Icons --- import { ArrowLeftIcon } from "@/components/tiptap-icons/arrow-left-icon" import { HighlighterIcon } from "@/components/tiptap-icons/highlighter-icon" import { LinkIcon } from "@/components/tiptap-icons/link-icon" // --- Hooks --- import { useIsMobile } from "@/hooks/use-mobile" import { useWindowSize } from "@/hooks/use-window-size" import { useCursorVisibility } from "@/hooks/use-cursor-visibility" // --- Components --- import { ThemeToggle } from "@/components/tiptap-templates/simple/theme-toggle" // --- Lib --- import { handleImageUpload, MAX_FILE_SIZE } from "@/lib/tiptap-utils" // --- Styles --- import "@/components/tiptap-templates/simple/simple-editor.scss" const MainToolbarContent = ({ onHighlighterClick, onLinkClick, isMobile, }: { onHighlighterClick: () => void onLinkClick: () => void isMobile: boolean }) => { return ( <> {!isMobile ? ( ) : ( )} {!isMobile ? : } {isMobile && } ) } const MobileToolbarContent = ({ type, onBack, }: { type: "highlighter" | "link" onBack: () => void }) => ( <> {type === "highlighter" ? ( ) : ( )} ) interface EnhancedSimpleEditorProps { content?: any onChange?: (content: any) => void } export function EnhancedSimpleEditor({ content, onChange }: EnhancedSimpleEditorProps) { const isMobile = useIsMobile() const { height } = useWindowSize() const [mobileView, setMobileView] = React.useState< "main" | "highlighter" | "link" >("main") const toolbarRef = React.useRef(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: content || "", onUpdate: ({ editor }) => { if (onChange) { onChange(editor.getJSON()) } } }) // Update editor content when prop changes React.useEffect(() => { if (editor && content !== undefined) { const currentContent = editor.getJSON() const newContent = content || "" // Only update if content actually changed to avoid infinite loops if (JSON.stringify(currentContent) !== JSON.stringify(newContent)) { editor.commands.setContent(newContent, false) } } }, [editor, content]) const rect = useCursorVisibility({ editor, overlayHeight: toolbarRef.current?.getBoundingClientRect().height ?? 0, }) React.useEffect(() => { if (!isMobile && mobileView !== "main") { setMobileView("main") } }, [isMobile, mobileView]) return (
{mobileView === "main" ? ( setMobileView("highlighter")} onLinkClick={() => setMobileView("link")} isMobile={isMobile} /> ) : ( setMobileView("main")} /> )}
) }