import "./SpecEditor.css"; import { useState, useCallback, useEffect, useRef } from "react"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; interface SpecEditorProps { content: string; readOnly?: boolean; onSave?: (content: string) => Promise; onRequestRevision?: (feedback: string) => Promise; isSaving?: boolean; isRequesting?: boolean; } export function SpecEditor({ content, readOnly = false, onSave, onRequestRevision, isSaving = false, isRequesting = false, }: SpecEditorProps) { const [isEditing, setIsEditing] = useState(false); const [editContent, setEditContent] = useState(content); const [feedback, setFeedback] = useState(""); const textareaRef = useRef(null); const feedbackRef = useRef(null); // Update edit content when external content changes (only when not editing) useEffect(() => { if (!isEditing) { setEditContent(content); } }, [content, isEditing]); const hasChanges = editContent !== content; const canSave = isEditing && hasChanges && !isSaving && onSave; const handleSave = useCallback(async () => { if (!canSave) return; await onSave(editContent); setIsEditing(false); }, [canSave, onSave, editContent]); const handleRequestRevision = useCallback(async () => { if (!onRequestRevision || !feedback.trim() || isRequesting) return; await onRequestRevision(feedback.trim()); setFeedback(""); }, [onRequestRevision, feedback, isRequesting]); // Keyboard shortcut: Ctrl/Cmd+Enter to save in edit mode, Escape to cancel useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (isEditing && (e.metaKey || e.ctrlKey) && e.key === "Enter") { e.preventDefault(); if (canSave) { void handleSave(); } } else if (isEditing && e.key === "Escape") { e.preventDefault(); handleCancelEdit(); } }; document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); }, [isEditing, canSave, handleSave]); const handleEnterEditMode = () => { setIsEditing(true); setEditContent(content); }; const handleCancelEdit = () => { setIsEditing(false); setEditContent(content); }; const handleDoubleClick = useCallback((e: React.MouseEvent) => { if (!readOnly && !isEditing) { e.stopPropagation(); handleEnterEditMode(); } }, [readOnly, isEditing, content]); const stripLeadingHeading = (text: string): string => { return text.replace(/^#\s+[^\n]*\n+/, ""); }; return (
{/* Toolbar */}
{!readOnly && ( )}
{isEditing && (
)}
{/* Content Area */}
{isEditing ? (