import {EditorContent, useEditor} from '@tiptap/react'; import StarterKit from '@tiptap/starter-kit'; import Link from '@tiptap/extension-link'; import Underline from '@tiptap/extension-underline'; import TextAlign from '@tiptap/extension-text-align'; import Placeholder from '@tiptap/extension-placeholder'; import Image from '@tiptap/extension-image'; import {Table} from '@tiptap/extension-table'; import {TableCell} from '@tiptap/extension-table-cell'; import {TableHeader} from '@tiptap/extension-table-header'; import {TableRow} from '@tiptap/extension-table-row'; import {useEffect} from 'react'; type RichTextEditorProps = { value: string; onChange: (value: string) => void; placeholder?: string; disabled?: boolean; }; type ToolbarButtonProps = { label: string; title: string; active?: boolean; disabled?: boolean; onClick: () => void; }; const ToolbarButton = ({label, title, active = false, disabled = false, onClick}: ToolbarButtonProps) => ( ); export const RichTextEditor = ({ value, onChange, placeholder = '내용을 입력하세요.', disabled = false, }: RichTextEditorProps) => { const editor = useEditor({ editable: !disabled, extensions: [ StarterKit, Underline, Link.configure({ openOnClick: false, autolink: true, defaultProtocol: 'https', }), TextAlign.configure({ types: ['heading', 'paragraph'], }), Placeholder.configure({ placeholder, }), Image.configure({ allowBase64: true, }), Table.configure({ resizable: true, }), TableRow, TableHeader, TableCell, ], content: value || '', editorProps: { attributes: { class: 'rich_text_editor_body', }, }, onUpdate: ({editor}) => { onChange(editor.getHTML()); }, }); useEffect(() => { if (!editor) { return; } editor.setEditable(!disabled); }, [disabled, editor]); useEffect(() => { if (!editor || value === editor.getHTML()) { return; } editor.commands.setContent(value || '', {emitUpdate: false}); }, [editor, value]); if (!editor) { return null; } const setLink = () => { const previousUrl = editor.getAttributes('link').href as string | undefined; const url = window.prompt('링크 URL을 입력하세요.', previousUrl ?? ''); if (url === null) { return; } if (!url.trim()) { editor.chain().focus().extendMarkRange('link').unsetLink().run(); return; } editor.chain().focus().extendMarkRange('link').setLink({href: url.trim()}).run(); }; const addImage = () => { const url = window.prompt('이미지 URL을 입력하세요.'); if (!url?.trim()) { return; } editor.chain().focus().setImage({src: url.trim()}).run(); }; return (
editor.chain().focus().toggleHeading({level: 2}).run()} /> editor.chain().focus().toggleBold().run()} /> editor.chain().focus().toggleItalic().run()} /> editor.chain().focus().toggleUnderline().run()} /> editor.chain().focus().toggleStrike().run()} /> editor.chain().focus().toggleBulletList().run()} /> editor.chain().focus().toggleOrderedList().run()} /> editor.chain().focus().setTextAlign('left').run()} /> editor.chain().focus().setTextAlign('center').run()} /> editor.chain().focus().setTextAlign('right').run()} /> editor.chain().focus().insertTable({rows: 3, cols: 3, withHeaderRow: true}).run()} /> editor.chain().focus().undo().run()} /> editor.chain().focus().redo().run()} />
); };