feat: auto-save 2s + indicateur save + reminders inline actions (compléter/snooze)
- Auto-save debounce 2s dans note-editor-context - lastSavedAt state + setIsDirty(false) dans handleSave - Indicateur toolbar: ✓ sauvegardé / ● non enregistré avec timer relatif - Reminders sidebar: bouton ✓ compléter + bouton +1h snooze (hover inline) - i18n: clés reminders.markDone/snooze1h + notes.savedJustNow/unsaved (15 locales) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -81,6 +81,7 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
const [color, setColor] = useState(note.color)
|
||||
const [size, setSize] = useState<NoteSize>(note.size || 'small')
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
const [lastSavedAt, setLastSavedAt] = useState<Date | null>(null)
|
||||
const [removedImageUrls, setRemovedImageUrls] = useState<string[]>([])
|
||||
const [isMarkdown, setIsMarkdown] = useState(note.type === 'markdown')
|
||||
const [showMarkdownPreview, setShowMarkdownPreview] = useState(note.type === 'markdown')
|
||||
@@ -691,6 +692,8 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.note(note.id) })
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.notes(note.notebookId) })
|
||||
emitNoteChange({ type: 'updated', note: result })
|
||||
setIsDirty(false)
|
||||
setLastSavedAt(new Date())
|
||||
toast.success(t('notes.saved') || 'Note sauvegardée !')
|
||||
} catch (error) {
|
||||
console.error('[SAVE] updateNote failed:', error)
|
||||
@@ -816,6 +819,7 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.notes(note.notebookId) })
|
||||
emitNoteChange({ type: 'updated', note: result })
|
||||
setIsDirty(false)
|
||||
setLastSavedAt(new Date())
|
||||
toast.success(t('notes.saved') || 'Saved')
|
||||
} catch (error) {
|
||||
console.error('[SAVE] updateNote failed:', error)
|
||||
@@ -830,6 +834,23 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
const handleSaveRef = useRef(handleSave)
|
||||
handleSaveRef.current = handleSave
|
||||
|
||||
// Auto-save : 2s après le dernier changement si isDirty
|
||||
const autoSaveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
useEffect(() => {
|
||||
if (!isDirty || isSaving || readOnly) return
|
||||
if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current)
|
||||
autoSaveTimerRef.current = setTimeout(() => {
|
||||
if (fullPage) {
|
||||
void handleSaveInPlaceRef.current()
|
||||
} else {
|
||||
void handleSaveRef.current()
|
||||
}
|
||||
}, 2000)
|
||||
return () => {
|
||||
if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current)
|
||||
}
|
||||
}, [isDirty, isSaving, readOnly, fullPage])
|
||||
|
||||
useEffect(() => {
|
||||
if (!fullPage) return
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
@@ -870,6 +891,7 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
removedImageUrls,
|
||||
isSaving,
|
||||
isDirty,
|
||||
lastSavedAt,
|
||||
isProcessingAI,
|
||||
aiOpen,
|
||||
infoOpen,
|
||||
@@ -894,7 +916,7 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
quotaExceededFeature,
|
||||
}), [
|
||||
title, content, checkItems, labels, images, links, newLabel, color, size,
|
||||
showMarkdownPreview, removedImageUrls, isSaving, isDirty, isProcessingAI, aiOpen, infoOpen,
|
||||
showMarkdownPreview, removedImageUrls, isSaving, isDirty, lastSavedAt, isProcessingAI, aiOpen, infoOpen,
|
||||
isGeneratingTitles, titleSuggestions, dismissedTitleSuggestions, isReformulating,
|
||||
reformulationModal, previousContentForCopilot, showReminderDialog, currentReminder,
|
||||
showLinkDialog, linkUrl, comparisonNotes, fusionNotes, dismissedTags, filteredSuggestions,
|
||||
|
||||
Reference in New Issue
Block a user