import { useEffect, useState, useRef, RefObject } from 'react'; interface InsertToolbarProps { textareaRef: RefObject; onInsertLink: (text: string, url: string) => void; onInsertTodoItem: () => void; onInsertTable: () => void; onInsertFile: () => void; isUploading?: boolean; } interface LinkModalState { isOpen: boolean; text: string; url: string; } export function InsertToolbar({ textareaRef, onInsertLink, onInsertTodoItem, onInsertTable, onInsertFile, isUploading }: InsertToolbarProps) { const [position, setPosition] = useState<{ top: number; left: number } | null>(null); const [isVisible, setIsVisible] = useState(false); const [linkModal, setLinkModal] = useState({ isOpen: false, text: '', url: '' }); const toolbarRef = useRef(null); const modalRef = useRef(null); const urlInputRef = useRef(null); useEffect(() => { const textarea = textareaRef.current; if (!textarea) return; const updatePosition = () => { const textarea = textareaRef.current; if (!textarea || linkModal.isOpen) return; const start = textarea.selectionStart; const end = textarea.selectionEnd; // Only show when cursor is placed (no selection) if (start !== end) { setIsVisible(false); return; } const textareaRect = textarea.getBoundingClientRect(); const computedStyle = window.getComputedStyle(textarea); const lineHeight = parseFloat(computedStyle.lineHeight) || 24; const paddingTop = parseFloat(computedStyle.paddingTop) || 0; const paddingLeft = parseFloat(computedStyle.paddingLeft) || 0; const fontSize = parseFloat(computedStyle.fontSize) || 16; const textBeforeCursor = textarea.value.substring(0, start); const lines = textBeforeCursor.split('\n'); const currentLineIndex = lines.length - 1; const currentLineText = lines[currentLineIndex]; const charWidth = fontSize * 0.6; const scrollTop = textarea.scrollTop; // Position to the right of cursor const top = textareaRect.top + paddingTop + (currentLineIndex * lineHeight) - scrollTop + lineHeight / 2; const left = textareaRect.left + paddingLeft + (currentLineText.length * charWidth) + 20; // Keep toolbar within viewport const toolbarWidth = 196; const adjustedLeft = Math.min(left, window.innerWidth - toolbarWidth - 20); let adjustedTop = top - 16; // Center vertically with cursor line if (adjustedTop < 10) { adjustedTop = 10; } setPosition({ top: adjustedTop, left: adjustedLeft }); setIsVisible(true); }; const handleClick = () => { setTimeout(updatePosition, 10); }; const handleKeyUp = (e: KeyboardEvent) => { // Update on arrow keys or other navigation if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(e.key)) { updatePosition(); } }; const handleInput = () => { // Hide briefly during typing, show after a pause setIsVisible(false); }; const handleBlur = () => { // Don't hide if clicking on toolbar or modal setTimeout(() => { const activeElement = document.activeElement; if ( activeElement !== textarea && !toolbarRef.current?.contains(activeElement) && !modalRef.current?.contains(activeElement) ) { setIsVisible(false); } }, 150); }; textarea.addEventListener('click', handleClick); textarea.addEventListener('keyup', handleKeyUp); textarea.addEventListener('input', handleInput); textarea.addEventListener('blur', handleBlur); return () => { textarea.removeEventListener('click', handleClick); textarea.removeEventListener('keyup', handleKeyUp); textarea.removeEventListener('input', handleInput); textarea.removeEventListener('blur', handleBlur); }; }, [textareaRef, linkModal.isOpen]); const handleLinkClick = () => { setLinkModal({ isOpen: true, text: '', url: '' }); setTimeout(() => urlInputRef.current?.focus(), 50); }; const handleLinkSubmit = () => { if (linkModal.url) { onInsertLink(linkModal.text || linkModal.url, linkModal.url); setLinkModal({ isOpen: false, text: '', url: '' }); setIsVisible(false); textareaRef.current?.focus(); } }; const handleLinkCancel = () => { setLinkModal({ isOpen: false, text: '', url: '' }); textareaRef.current?.focus(); }; const handleFileClick = () => { onInsertFile(); setIsVisible(false); }; const handleTodoClick = () => { onInsertTodoItem(); setIsVisible(false); }; const handleTableClick = () => { onInsertTable(); setIsVisible(false); }; if (!isVisible || !position) return null; // Link Modal if (linkModal.isOpen) { return (
Insert Link
setLinkModal({ ...linkModal, url: e.target.value })} onKeyDown={(e) => { if (e.key === 'Enter') handleLinkSubmit(); if (e.key === 'Escape') handleLinkCancel(); }} placeholder="https://example.com" className="w-full px-3 py-1.5 text-sm rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
setLinkModal({ ...linkModal, text: e.target.value })} onKeyDown={(e) => { if (e.key === 'Enter') handleLinkSubmit(); if (e.key === 'Escape') handleLinkCancel(); }} placeholder="Link text" className="w-full px-3 py-1.5 text-sm rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
); } // Insert Toolbar return (
); }