diff --git a/packages/editor/App.tsx b/packages/editor/App.tsx index 06f8690d..39cf8829 100644 --- a/packages/editor/App.tsx +++ b/packages/editor/App.tsx @@ -530,6 +530,12 @@ const App: React.FC = () => { if (selectedAnnotationId === id) setSelectedAnnotationId(null); }; + const handleEditAnnotation = (id: string, updates: Partial) => { + setAnnotations(prev => prev.map(ann => + ann.id === id ? { ...ann, ...updates } : ann + )); + }; + const handleIdentityChange = (oldIdentity: string, newIdentity: string) => { setAnnotations(prev => prev.map(ann => ann.author === oldIdentity ? { ...ann, author: newIdentity } : ann @@ -712,6 +718,7 @@ const App: React.FC = () => { selectedId={selectedAnnotationId} onSelect={setSelectedAnnotationId} onDelete={handleDeleteAnnotation} + onEdit={handleEditAnnotation} shareUrl={shareUrl} /> diff --git a/packages/ui/components/AnnotationPanel.tsx b/packages/ui/components/AnnotationPanel.tsx index db922350..9cae034d 100644 --- a/packages/ui/components/AnnotationPanel.tsx +++ b/packages/ui/components/AnnotationPanel.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import { Annotation, AnnotationType, Block } from '../types'; import { isCurrentUser } from '../utils/identity'; import { ImageThumbnail } from './ImageThumbnail'; @@ -9,6 +9,7 @@ interface PanelProps { blocks: Block[]; onSelect: (id: string) => void; onDelete: (id: string) => void; + onEdit?: (id: string, updates: Partial) => void; selectedId: string | null; shareUrl?: string; } @@ -19,6 +20,7 @@ export const AnnotationPanel: React.FC = ({ blocks, onSelect, onDelete, + onEdit, selectedId, shareUrl }) => { @@ -73,6 +75,7 @@ export const AnnotationPanel: React.FC = ({ isSelected={selectedId === ann.id} onSelect={() => onSelect(ann.id)} onDelete={() => onDelete(ann.id)} + onEdit={onEdit ? (updates: Partial) => onEdit(ann.id, updates) : undefined} /> )) )} @@ -128,7 +131,54 @@ const AnnotationCard: React.FC<{ isSelected: boolean; onSelect: () => void; onDelete: () => void; -}> = ({ annotation, isSelected, onSelect, onDelete }) => { + onEdit?: (updates: Partial) => void; +}> = ({ annotation, isSelected, onSelect, onDelete, onEdit }) => { + const [isEditing, setIsEditing] = useState(false); + const [editText, setEditText] = useState(annotation.text || ''); + const textareaRef = useRef(null); + + useEffect(() => { + if (isEditing && textareaRef.current) { + textareaRef.current.focus(); + textareaRef.current.select(); + } + }, [isEditing]); + + // Update editText when annotation.text changes + useEffect(() => { + if (!isEditing) { + setEditText(annotation.text || ''); + } + }, [annotation.text, isEditing]); + + const handleStartEdit = (e: React.MouseEvent) => { + e.stopPropagation(); + setEditText(annotation.text || ''); + setIsEditing(true); + }; + + const handleSaveEdit = () => { + if (onEdit) { + onEdit({ text: editText }); + } + setIsEditing(false); + }; + + const handleCancelEdit = () => { + setEditText(annotation.text || ''); + setIsEditing(false); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + handleSaveEdit(); + } else if (e.key === 'Escape') { + e.preventDefault(); + handleCancelEdit(); + } + }; + const typeConfig = { [AnnotationType.DELETION]: { label: 'Delete', @@ -215,7 +265,7 @@ const AnnotationCard: React.FC<{ )} - {/* Type Badge + Timestamp + Delete */} + {/* Type Badge + Timestamp + Actions */}
@@ -230,21 +280,66 @@ const AnnotationCard: React.FC<{ {formatTimestamp(annotation.createdA)}
- +
+ {onEdit && annotation.type !== AnnotationType.DELETION && !isEditing && ( + + )} + +
{/* Global Comment - show text directly */} {annotation.type === AnnotationType.GLOBAL_COMMENT ? ( -
- {annotation.text} -
+ isEditing ? ( +
+