diff --git a/apps/web/src/routes/_app/notes.tsx b/apps/web/src/routes/_app/notes.tsx index 8fbef2c..f78d455 100644 --- a/apps/web/src/routes/_app/notes.tsx +++ b/apps/web/src/routes/_app/notes.tsx @@ -49,6 +49,33 @@ const NoteEditor = memo(function NoteEditor({ content, onChange, placeholder = " ); }); +// Separate component for title input to prevent focus stealing on parent re-render +const NoteTitleInput = memo(function NoteTitleInput({ noteId, initialTitle }: { noteId: string; initialTitle: string }) { + const [localTitle, setLocalTitle] = useState(initialTitle); + const queryClient = useQueryClient(); + + useEffect(() => { + setLocalTitle(initialTitle); + }, [initialTitle, noteId]); + + const handleBlur = () => { + if (localTitle !== initialTitle) { + api.patch(/notes/ + noteId, { title: localTitle }).then(() => { + queryClient.invalidateQueries({ queryKey: [notes] }); + }); + } + }; + + return ( + setLocalTitle(e.target.value)} + onBlur={handleBlur} + className=flex h-9 w-full rounded-md border-0 bg-transparent px-0 py-1 text-lg font-semibold text-base shadow-none transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0 disabled:cursor-not-allowed disabled:opacity-50 + /> + ); +}); + function NotesPage() { const queryClient = useQueryClient(); const [search, setSearch] = useState(""); @@ -109,7 +136,6 @@ function NotesPage() { selectedNoteRef.current = selectedNote; const handleContentChange = useCallback((html: string) => { - setEditorContent(html); if (saveTimerRef.current) clearTimeout(saveTimerRef.current); saveTimerRef.current = setTimeout(() => { const note = selectedNoteRef.current; diff --git a/fix_notes3.py b/fix_notes3.py new file mode 100644 index 0000000..5f7629a --- /dev/null +++ b/fix_notes3.py @@ -0,0 +1,64 @@ +import sys + +with open(sys.argv[1], 'r') as f: + content = f.read() + +# Add a memoized TitleInput component after NoteEditor +old = '''function NotesPage() {''' +new = '''// Memoized title input that doesn't re-render when parent re-renders +const TitleInput = memo(function TitleInput({ note, onSave }: { note: Note | null; onSave: (id: string, title: string) => void }) { + const inputRef = useRef(null); + + useEffect(() => { + if (inputRef.current) { + inputRef.current.value = note?.title || ''; + } + }, [note?.id]); + + return ( + e.stopPropagation()} + onBlur={(e) => { + if (note && e.target.value !== note.title) { + onSave(note.id, e.target.value); + } + }} + className="flex h-9 w-full rounded-md border-0 bg-transparent px-0 py-1 text-lg font-semibold text-base shadow-none transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0 disabled:cursor-not-allowed disabled:opacity-50" + /> + ); +}); + +function NotesPage() {''' + +content = content.replace(old, new, 1) + +# Replace the title input section +old_title = ''' e.stopPropagation()} + onBlur={(e) => { + if (selectedNote && e.target.value !== selectedNote.title) { + updateMutation.mutate({ id: selectedNote.id, data: { title: e.target.value } }); + setSelectedNote({ ...selectedNote, title: e.target.value }); + } + }} + className="flex h-9 w-full rounded-md border-0 bg-transparent px-0 py-1 text-lg font-semibold text-base shadow-none transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0 disabled:cursor-not-allowed disabled:opacity-50" + />''' + +new_title = ''' { + updateMutation.mutate({ id, data: { title } }); + setSelectedNote({ ...selectedNote, title } as Note); + }} + />''' + +content = content.replace(old_title, new_title, 1) + +with open(sys.argv[1], 'w') as f: + f.write(content) +print('Done')