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 */}
-
-
+
+ {/* 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 */}