|
1 | | -import { useState, useCallback, useEffect } from 'react'; |
| 1 | +import { useState, useCallback, useEffect, useRef } from 'react'; |
2 | 2 | import { |
3 | 3 | Star, |
4 | 4 | Archive, |
@@ -107,6 +107,9 @@ export default function Index({ |
107 | 107 | const [isUploading, setIsUploading] = useState(false); |
108 | 108 | const [uploadProgress, setUploadProgress] = useState(0); |
109 | 109 | const [deletingIds, setDeletingIds] = useState<string[]>([]); |
| 110 | + const [localTitle, setLocalTitle] = useState(note?.title || ''); |
| 111 | + const titleTimeoutRef = useRef<NodeJS.Timeout | null>(null); |
| 112 | + const isEditingTitleRef = useRef(false); |
110 | 113 |
|
111 | 114 | // Use custom hook for editor state management |
112 | 115 | const { |
@@ -355,33 +358,74 @@ export default function Index({ |
355 | 358 | } |
356 | 359 | }, [editor, note]); |
357 | 360 |
|
358 | | - // Cleanup timeout on unmount |
| 361 | + // Cleanup timeouts on unmount |
359 | 362 | useEffect(() => { |
360 | 363 | return () => { |
361 | 364 | if (saveTimeoutRef.current) { |
362 | 365 | clearTimeout(saveTimeoutRef.current); |
363 | 366 | } |
| 367 | + if (titleTimeoutRef.current) { |
| 368 | + clearTimeout(titleTimeoutRef.current); |
| 369 | + } |
364 | 370 | }; |
365 | 371 | }, [saveTimeoutRef]); |
366 | 372 |
|
| 373 | + // Sync local title when note changes, but not when actively editing |
| 374 | + useEffect(() => { |
| 375 | + if (!isEditingTitleRef.current) { |
| 376 | + setLocalTitle(note?.title || ''); |
| 377 | + } |
| 378 | + // Reset editing state when switching notes |
| 379 | + if (note?.id) { |
| 380 | + isEditingTitleRef.current = false; |
| 381 | + } |
| 382 | + }, [note?.id, note?.title]); |
| 383 | + |
| 384 | + const saveTitleToServer = useCallback((title: string) => { |
| 385 | + if (!note || title === note.title) return; |
| 386 | + |
| 387 | + setSaveStatus('saving'); |
| 388 | + try { |
| 389 | + onUpdateNote(note.id, { title }); |
| 390 | + setTimeout(() => { |
| 391 | + setSaveStatus('saved'); |
| 392 | + isEditingTitleRef.current = false; // Mark editing as complete |
| 393 | + }, 500); |
| 394 | + } catch (error) { |
| 395 | + setSaveStatus('error'); |
| 396 | + console.error('Failed to save title:', error); |
| 397 | + isEditingTitleRef.current = false; // Mark editing as complete even on error |
| 398 | + } |
| 399 | + }, [note, onUpdateNote, setSaveStatus]); |
| 400 | + |
367 | 401 | const handleTitleChange = useCallback( |
368 | 402 | (e: React.ChangeEvent<HTMLInputElement>) => { |
369 | | - if (!note) return; |
370 | 403 | const newTitle = e.target.value; |
371 | | - setSaveStatus('saving'); |
372 | | - try { |
373 | | - onUpdateNote(note.id, { title: newTitle }); |
374 | | - setTimeout(() => { |
375 | | - setSaveStatus('saved'); |
376 | | - }, 500); |
377 | | - } catch (error) { |
378 | | - setSaveStatus('error'); |
379 | | - console.error('Failed to save title:', error); |
| 404 | + setLocalTitle(newTitle); |
| 405 | + isEditingTitleRef.current = true; // Mark as actively editing |
| 406 | + |
| 407 | + // Clear existing timeout |
| 408 | + if (titleTimeoutRef.current) { |
| 409 | + clearTimeout(titleTimeoutRef.current); |
380 | 410 | } |
| 411 | + |
| 412 | + // Debounce the save operation |
| 413 | + titleTimeoutRef.current = setTimeout(() => { |
| 414 | + saveTitleToServer(newTitle); |
| 415 | + }, 1000); // Save after 1 second of no typing |
381 | 416 | }, |
382 | | - [note, onUpdateNote, setSaveStatus] |
| 417 | + [saveTitleToServer] |
383 | 418 | ); |
384 | 419 |
|
| 420 | + const handleTitleBlur = useCallback(() => { |
| 421 | + // Clear timeout and save immediately on blur |
| 422 | + if (titleTimeoutRef.current) { |
| 423 | + clearTimeout(titleTimeoutRef.current); |
| 424 | + titleTimeoutRef.current = null; |
| 425 | + } |
| 426 | + saveTitleToServer(localTitle); |
| 427 | + }, [localTitle, saveTitleToServer]); |
| 428 | + |
385 | 429 | const handleMoveNote = useCallback( |
386 | 430 | (targetFolderId: string | null) => { |
387 | 431 | if (note) { |
@@ -575,8 +619,9 @@ export default function Index({ |
575 | 619 | )} |
576 | 620 | <input |
577 | 621 | type="text" |
578 | | - value={note.title || ''} |
| 622 | + value={localTitle} |
579 | 623 | onChange={handleTitleChange} |
| 624 | + onBlur={handleTitleBlur} |
580 | 625 | className="text-foreground placeholder-muted-foreground min-w-0 flex-1 border-none bg-transparent text-2xl font-bold outline-none" |
581 | 626 | placeholder="Untitled Note" |
582 | 627 | disabled={note.hidden} |
|
0 commit comments