From a58610003d5245e75a7b3ec2bef9676737056f1f Mon Sep 17 00:00:00 2001 From: Antigravity Date: Thu, 7 May 2026 23:52:21 +0000 Subject: [PATCH] fix: remove duplicate useMemo inside handleGenerateTitles (hooks violation = broken save+title), add Save btn + three-dot delete + Ctrl+S to fullPage toolbar --- memento-note/components/note-editor.tsx | 203 +++++++++++++++++------- 1 file changed, 144 insertions(+), 59 deletions(-) diff --git a/memento-note/components/note-editor.tsx b/memento-note/components/note-editor.tsx index 2456866..3f6349d 100644 --- a/memento-note/components/note-editor.tsx +++ b/memento-note/components/note-editor.tsx @@ -25,8 +25,8 @@ import { } from '@/components/ui/dropdown-menu' import { NoteTypeSelector } from '@/components/note-type-selector' import { RichTextEditor } from '@/components/rich-text-editor' -import { X, Plus, Palette, Image as ImageIcon, Bell, Eye, Link as LinkIcon, Sparkles, Maximize2, Copy, Wand2, LogOut, ArrowLeft, ChevronRight, Info, Check, Loader2 } from 'lucide-react' -import { updateNote, createNote, cleanupOrphanedImages, leaveSharedNote } from '@/app/actions/notes' +import { X, Plus, Palette, Image as ImageIcon, Bell, Eye, Link as LinkIcon, Sparkles, Maximize2, Copy, Wand2, LogOut, ArrowLeft, ChevronRight, Info, Check, Loader2, Save, MoreHorizontal, Trash2 } from 'lucide-react' +import { updateNote, createNote, cleanupOrphanedImages, leaveSharedNote, deleteNote } from '@/app/actions/notes' import { format } from 'date-fns' import { useTitleSuggestions } from '@/hooks/use-title-suggestions' import { MarkdownSlashCommands } from './markdown-slash-commands' @@ -329,12 +329,6 @@ export function NoteEditor({ note, readOnly = false, onClose, fullPage = false } .join(' ') .trim() - const allImages = useMemo(() => { - const extracted = noteType === 'richtext' ? extractImagesFromHTML(content) : []; - return Array.from(new Set([...images, ...extracted])); - }, [images, content, noteType]); - - const wordCount = fullContentForAI.split(/\s+/).filter(word => word.length > 0).length @@ -680,7 +674,54 @@ export function NoteEditor({ note, readOnly = false, onClose, fullPage = false } } } - // ── fullPage mode: editorial layout (fidèle au prototype) ── + // Save in place (fullPage) — without closing + const handleSaveInPlace = async () => { + setIsSaving(true) + try { + await updateNote(note.id, { + title: title.trim() || null, + content: noteType !== 'checklist' ? content : '', + checkItems: noteType === 'checklist' ? checkItems : null, + labels, + images, + links, + color, + reminder: currentReminder, + isMarkdown: noteType === 'markdown', + type: noteType, + size, + }) + if (removedImageUrls.length > 0) { + cleanupOrphanedImages(removedImageUrls, note.id).catch(() => {}) + } + await refreshLabels() + triggerRefresh() + setIsDirty(false) + toast.success('Note sauvegardée !') + } catch (error) { + console.error('Failed to save note:', error) + toast.error('Erreur lors de la sauvegarde.') + } finally { + setIsSaving(false) + } + } + + // Ctrl+S / Cmd+S shortcut — save in place in fullPage mode + // Note: this useEffect must be outside the if(fullPage) branch (no conditional hooks) + // eslint-disable-next-line react-hooks/rules-of-hooks + useEffect(() => { + if (!fullPage) return + const handler = (e: KeyboardEvent) => { + if ((e.ctrlKey || e.metaKey) && e.key === 's') { + e.preventDefault() + handleSaveInPlace() + } + } + document.addEventListener('keydown', handler) + return () => document.removeEventListener('keydown', handler) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [fullPage, isSaving]) + if (fullPage) { const notebookName = notebooks.find(nb => nb.id === note.notebookId)?.name || null @@ -704,69 +745,113 @@ export function NoteEditor({ note, readOnly = false, onClose, fullPage = false } Back to collection - {/* Right: status + type + AI + Info */} -
- {/* Save status */} - - {isSaving - ? <>Saving… - : isDirty - ? <>Modified - : <>Saved} - + {/* Right: status + type + AI + Info */} +
+ {/* Save status */} + + {isSaving + ? <>Saving… + : isDirty + ? <>Modified + : <>Saved} + - {/* Note type */} - { setNoteType(newType); setShowMarkdownPreview(newType === 'markdown'); setIsDirty(true) }} - compact - /> + {/* Note type */} + { setNoteType(newType); setShowMarkdownPreview(newType === 'markdown'); setIsDirty(true) }} + compact + /> - {/* Preview toggle — only for text/markdown, in toolbar where it's visible */} - {(noteType === 'text' || noteType === 'markdown') && !readOnly && ( + {/* Preview toggle — only for text/markdown, in toolbar where it's visible */} + {(noteType === 'text' || noteType === 'markdown') && !readOnly && ( + + )} + + {/* AI — rounded-full, exact prototype style */} - )} - {/* AI — rounded-full, exact prototype style */} - + {/* Info — rounded-full */} + - {/* Info — rounded-full */} - )} - > - - Document Info - -
+ + {/* Three-dot options menu */} + {!readOnly && ( + + + + + + { + try { + await deleteNote(note.id) + triggerRefresh() + toast.success('Note supprimée.') + onClose() + } catch { toast.error('Impossible de supprimer.') } + }} + className="text-red-600 dark:text-red-400 focus:text-red-600" + > + + Supprimer la note + + + + )} +
{/* BODY — max-w-4xl, px-12, py-16 */}