import { type Editor } from '@tiptap/react' import { useState, useEffect, useCallback, useRef } from 'react' import { Bold, Italic, Underline, Strikethrough, ListOrdered, List, Quote, Undo, Redo, Link, Image, AlignLeft, AlignCenter, AlignRight, AlignJustify, Subscript, Superscript, Table, Highlighter, CheckSquare, Type, } from 'lucide-react' import { Toggle } from '../ui/toggle' import { Separator } from '../ui/separator' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip" interface ToolbarProps { editor: Editor | null; disabled?: boolean; onImageUpload: (e: React.ChangeEvent) => void; } export function Toolbar({ editor, disabled, onImageUpload }: ToolbarProps) { // 툴바 상태 관리 const [toolbarState, setToolbarState] = useState({ bold: false, italic: false, underline: false, strike: false, bulletList: false, orderedList: false, blockquote: false, link: false, highlight: false, taskList: false, table: false, subscript: false, superscript: false, heading: false, textAlign: 'left' as 'left' | 'center' | 'right' | 'justify', }); // 디바운스를 위한 ref const updateTimeoutRef = useRef(null); // 툴바 상태 업데이트 함수 (디바운스 적용) const updateToolbarState = useCallback(() => { if (!editor) return; if (updateTimeoutRef.current) { clearTimeout(updateTimeoutRef.current); } updateTimeoutRef.current = setTimeout(() => { try { const newState = { bold: editor.isActive('bold'), italic: editor.isActive('italic'), underline: editor.isActive('underline'), strike: editor.isActive('strike'), bulletList: editor.isActive('bulletList'), orderedList: editor.isActive('orderedList'), blockquote: editor.isActive('blockquote'), link: editor.isActive('link'), highlight: editor.isActive('highlight'), taskList: editor.isActive('taskList'), table: editor.isActive('table'), subscript: editor.isActive('subscript'), superscript: editor.isActive('superscript'), heading: editor.isActive('heading'), textAlign: editor.isActive({ textAlign: 'center' }) ? 'center' : editor.isActive({ textAlign: 'right' }) ? 'right' : editor.isActive({ textAlign: 'justify' }) ? 'justify' : 'left', }; setToolbarState(prevState => { // 상태가 실제로 변경된 경우에만 업데이트 const hasChanged = Object.keys(newState).some( key => prevState[key as keyof typeof prevState] !== newState[key as keyof typeof newState] ); if (hasChanged) { console.log('Toolbar state updated:', newState); return newState; } return prevState; }); } catch (error) { console.error('툴바 상태 업데이트 에러:', error); } }, 50); // 50ms 디바운스 }, [editor]); // editor 이벤트 리스너 설정 useEffect(() => { if (!editor) return; // 초기 상태 설정 updateToolbarState(); // 이벤트 리스너 등록 (selectionUpdate만 사용 - transaction은 너무 자주 발생) editor.on('selectionUpdate', updateToolbarState); editor.on('focus', updateToolbarState); editor.on('blur', updateToolbarState); return () => { if (updateTimeoutRef.current) { clearTimeout(updateTimeoutRef.current); } editor.off('selectionUpdate', updateToolbarState); editor.off('focus', updateToolbarState); editor.off('blur', updateToolbarState); }; }, [editor, updateToolbarState]); // 명령 실행 헬퍼 함수 const executeCommand = useCallback((command: () => void) => { if (editor && !disabled) { command(); // 명령 실행 후 즉시 상태 업데이트 updateToolbarState(); } }, [editor, disabled, updateToolbarState]); if (!editor) { return null; } return (
{/* 텍스트 스타일 */} executeCommand(() => editor.chain().focus().toggleBold().run() )} disabled={disabled} >

굵게 (Ctrl+B)

executeCommand(() => editor.chain().focus().toggleItalic().run() )} disabled={disabled} >

기울임 (Ctrl+I)

executeCommand(() => editor.chain().focus().toggleUnderline().run() )} disabled={disabled} >

밑줄

executeCommand(() => editor.chain().focus().toggleStrike().run() )} disabled={disabled} >

취소선

{/* 제목 및 단락 */} executeCommand(() => editor.chain().focus().toggleHeading({ level: 1 }).run() )} className="flex items-center" > 제목 1 executeCommand(() => editor.chain().focus().toggleHeading({ level: 2 }).run() )} className="flex items-center" > 제목 2 executeCommand(() => editor.chain().focus().toggleHeading({ level: 3 }).run() )} className="flex items-center" > 제목 3 executeCommand(() => editor.chain().focus().setParagraph().run() )} className="flex items-center" > 본문 {/* 리스트 */} executeCommand(() => editor.chain().focus().toggleBulletList().run() )} disabled={disabled} >

글머리 기호

executeCommand(() => editor.chain().focus().toggleOrderedList().run() )} disabled={disabled} >

번호 매기기

executeCommand(() => editor.chain().focus().toggleBlockquote().run() )} disabled={disabled} >

인용문

{/* 텍스트 정렬 */} {toolbarState.textAlign === 'center' ? ( ) : toolbarState.textAlign === 'right' ? ( ) : toolbarState.textAlign === 'justify' ? ( ) : ( )}

텍스트 정렬

executeCommand(() => editor.chain().focus().setTextAlign('left').run() )} className="flex items-center" > 왼쪽 정렬 executeCommand(() => editor.chain().focus().setTextAlign('center').run() )} className="flex items-center" > 가운데 정렬 executeCommand(() => editor.chain().focus().setTextAlign('right').run() )} className="flex items-center" > 오른쪽 정렬 executeCommand(() => editor.chain().focus().setTextAlign('justify').run() )} className="flex items-center" > 양쪽 정렬
{/* 링크 */} { if (toolbarState.link) { executeCommand(() => editor.chain().focus().unsetLink().run()); } else { const url = window.prompt('URL을 입력하세요:'); if (url) { executeCommand(() => editor.chain().focus().setLink({ href: url }).run()); } } }} disabled={disabled} >

링크 {toolbarState.link ? '제거' : '삽입'}

{/* 이미지 업로드 */}
{ document.getElementById('image-upload')?.click(); }} disabled={disabled} >

이미지 삽입

{/* 첨자 */} executeCommand(() => editor.chain().focus().toggleSubscript().run() )} disabled={disabled} >

아래 첨자

executeCommand(() => editor.chain().focus().toggleSuperscript().run() )} disabled={disabled} >

위 첨자

{/* 하이라이트 */} executeCommand(() => editor.chain().focus().toggleHighlight().run() )} disabled={disabled} >

하이라이트

{/* 체크리스트 */} executeCommand(() => editor.chain().focus().toggleTaskList().run() )} disabled={disabled} >

체크리스트

{/* 테이블 */} { if (toolbarState.table) { executeCommand(() => editor.chain().focus().deleteTable().run()); } else { executeCommand(() => editor.chain().focus().insertTable({ rows: 3, cols: 3 }).run()); } }} disabled={disabled} >

{toolbarState.table ? '테이블 삭제' : '테이블 삽입'}

{/* 실행 취소/다시 실행 */} executeCommand(() => editor.chain().focus().undo().run() )} disabled={!editor.can().undo() || disabled} >

실행 취소 (Ctrl+Z)

executeCommand(() => editor.chain().focus().redo().run() )} disabled={!editor.can().redo() || disabled} >

다시 실행 (Ctrl+Y)

) }