import React, { useCallback } from 'react'; import { useEditor, EditorContent } from '@tiptap/react'; import StarterKit from '@tiptap/starter-kit'; import Image from '@tiptap/extension-image'; import Link from '@tiptap/extension-link'; import Highlight from '@tiptap/extension-highlight'; import TextStyle from '@tiptap/extension-text-style'; import Color from '@tiptap/extension-color'; import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'; import { lowlight } from 'lowlight'; import { Bold, Italic, Underline, Strikethrough, Code, Quote, List, ListOrdered, Image as ImageIcon, Link as LinkIcon, Undo, Redo, } from 'lucide-react'; interface RichTextEditorProps { content?: any; onChange?: (content: any) => void; placeholder?: string; editable?: boolean; className?: string; } const MenuButton: React.FC<{ onClick: () => void; isActive?: boolean; disabled?: boolean; children: React.ReactNode; title?: string; }> = ({ onClick, isActive, disabled, children, title }) => ( {children} ); const RichTextEditor: React.FC = ({ content, onChange, placeholder = 'Start writing...', editable = true, className = '', }) => { const editor = useEditor({ extensions: [ StarterKit.configure({ codeBlock: false, }), Image.configure({ HTMLAttributes: { class: 'max-w-full h-auto rounded-lg', }, }), Link.configure({ openOnClick: false, HTMLAttributes: { class: 'text-lfg-lavender hover:text-white underline', }, }), Highlight.configure({ HTMLAttributes: { class: 'bg-lfg-lavender text-lfg-black px-1 rounded', }, }), TextStyle, Color, CodeBlockLowlight.configure({ lowlight, HTMLAttributes: { class: 'bg-lfg-black text-lfg-lavender p-4 rounded-lg overflow-x-auto', }, }), ], content, editable, onUpdate: ({ editor }) => { onChange?.(editor.getJSON()); }, }); const addImage = useCallback(() => { const url = window.prompt('Enter image URL:'); if (url && editor) { editor.chain().focus().setImage({ src: url }).run(); } }, [editor]); const setLink = useCallback(() => { const previousUrl = editor?.getAttributes('link').href; const url = window.prompt('Enter URL:', previousUrl); if (url === null) { return; } if (url === '') { editor?.chain().focus().extendMarkRange('link').unsetLink().run(); return; } editor?.chain().focus().extendMarkRange('link').setLink({ href: url }).run(); }, [editor]); if (!editor) { return ; } return ( {editable && ( editor.chain().focus().toggleBold().run()} isActive={editor.isActive('bold')} title="Bold" > editor.chain().focus().toggleItalic().run()} isActive={editor.isActive('italic')} title="Italic" > editor.chain().focus().toggleStrike().run()} isActive={editor.isActive('strike')} title="Strikethrough" > editor.chain().focus().toggleCode().run()} isActive={editor.isActive('code')} title="Code" > editor.chain().focus().toggleHeading({ level: 1 }).run()} isActive={editor.isActive('heading', { level: 1 })} title="Heading 1" > H1 editor.chain().focus().toggleHeading({ level: 2 }).run()} isActive={editor.isActive('heading', { level: 2 })} title="Heading 2" > H2 editor.chain().focus().toggleHeading({ level: 3 }).run()} isActive={editor.isActive('heading', { level: 3 })} title="Heading 3" > H3 editor.chain().focus().toggleBulletList().run()} isActive={editor.isActive('bulletList')} title="Bullet List" > editor.chain().focus().toggleOrderedList().run()} isActive={editor.isActive('orderedList')} title="Numbered List" > editor.chain().focus().toggleBlockquote().run()} isActive={editor.isActive('blockquote')} title="Quote" > editor.chain().focus().undo().run()} disabled={!editor.can().chain().focus().undo().run()} title="Undo" > editor.chain().focus().redo().run()} disabled={!editor.can().chain().focus().redo().run()} title="Redo" > )} {editable && ( Tips: Use **bold**,{' '} *italic*,{' '} `code`,{' '} or paste images directly )} ); }; export default RichTextEditor;
Tips: Use **bold**,{' '} *italic*,{' '} `code`,{' '} or paste images directly