import React, { useEffect, useCallback } from "react"; import { useEditor, EditorContent, ReactNodeViewRenderer } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; import Link from "@tiptap/extension-link"; import Image from "@tiptap/extension-image"; import { Table } from "@tiptap/extension-table"; import { TableRow } from "@tiptap/extension-table-row"; import { TableHeader } from "@tiptap/extension-table-header"; import { TableCell } from "@tiptap/extension-table-cell"; import { TaskList } from "@tiptap/extension-task-list"; import { TaskItem } from "@tiptap/extension-task-item"; import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight"; import { common, createLowlight } from "lowlight"; import CodeBlockComponent from "./CodeBlockComponent"; import { Markdown } from "tiptap-markdown"; import { Bold, Italic, Strikethrough, Code, Heading1, Heading2, Heading3, List, ListOrdered, CheckSquare, Quote, Link2, Image as ImageIcon, Table as TableIcon, Minus, } from "lucide-react"; // Set up lowlight for syntax highlighting in Tiptap const lowlight = createLowlight(common); const MenuBar = ({ editor }) => { if (!editor) return null; return (
); }; const RichMarkdownEditor = ({ initialContent, onChange, className = "", height = "600px", isFullscreen = false, }) => { const editor = useEditor({ extensions: [ StarterKit.configure({ codeBlock: false, // We'll use our own codeblock extension }), CodeBlockLowlight.extend({ addNodeView() { return ReactNodeViewRenderer(CodeBlockComponent); }, }).configure({ lowlight, }), Link.configure({ openOnClick: false, }), Image, Table.configure({ resizable: true, }), TableRow, TableHeader, TableCell, TaskList, TaskItem.configure({ nested: true, }), Markdown.configure({ html: true, tightLists: true, tightListClass: "tight", bulletListMarker: "-", linkify: true, breaks: false, }), ], content: initialContent, onUpdate: ({ editor }) => { // Serialize back to markdown and send to parent const markdownOutput = editor.storage.markdown.getMarkdown(); const htmlOutput = editor.getHTML(); onChange(markdownOutput, htmlOutput); }, editorProps: { attributes: { class: "prose prose-sm sm:prose dark:prose-invert prose-blue focus:outline-none w-full max-w-none", }, }, }); // Update editor content when initialContent prop completely changes from outside (e.g. loading a template) useEffect(() => { if (editor && initialContent !== undefined) { const currentMarkdown = editor.storage.markdown.getMarkdown(); if (initialContent !== currentMarkdown) { editor.commands.setContent(initialContent); } } }, [editor, initialContent]); return (
); }; export default RichMarkdownEditor;