From e0fd5393cab4d7daf2d59632e17f990ae66fa452 Mon Sep 17 00:00:00 2001 From: Antigravity Date: Sun, 30 Aug 2026 08:30:06 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20note=20en=20double=20au=20retour=20et=20?= =?UTF-8?q?barre=20Gras/Italique=20=C3=A0=20l=E2=80=99ouverture?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ne plus réinjecter une note déjà dans la liste, et n’afficher le menu de formatage que sur une vraie sélection de texte. Co-authored-by: Cursor --- memento-note/components/home-client.tsx | 17 ++++++++-- memento-note/components/rich-text-editor.tsx | 33 ++++++++++++-------- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/memento-note/components/home-client.tsx b/memento-note/components/home-client.tsx index a2f5076..a4d61f9 100644 --- a/memento-note/components/home-client.tsx +++ b/memento-note/components/home-client.tsx @@ -318,6 +318,8 @@ export function HomeClient({ } setNotes((prevNotes) => { + if (prevNotes.some((n) => n.id === note.id)) return prevNotes + const notebookFilter = searchParams.get('notebook') const labelFilter = searchParams.get('labels')?.split(',').filter(Boolean) || [] const colorFilter = searchParams.get('color') @@ -630,12 +632,16 @@ export function HomeClient({ removeNoteFromList(detail.noteId) setPinnedNotes(prev => prev.filter(n => n.id !== detail.noteId)) } else if (detail.type === 'created' && detail.note) { - setNotes(prev => [detail.note, ...prev]) + setNotes((prev) => { + if (prev.some((n) => n.id === detail.note.id)) return prev + if (filterNotesForCurrentView([detail.note]).length === 0) return prev + return [detail.note, ...prev] + }) } } window.addEventListener(NOTE_CHANGE_EVENT, onNoteChange) return () => window.removeEventListener(NOTE_CHANGE_EVENT, onNoteChange) - }, [patchNoteInList]) + }, [patchNoteInList, filterNotesForCurrentView]) const handleGridReorder = useCallback( async (orderedIds: string[]) => { @@ -787,7 +793,12 @@ export function HomeClient({ // Apply sort order to notes const sortedNotes = useMemo(() => { - let sorted = [...notes] + const seen = new Set() + let sorted = notes.filter((n) => { + if (seen.has(n.id)) return false + seen.add(n.id) + return true + }) if (sortOrder === 'newest') sorted.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()) if (sortOrder === 'oldest') sorted.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()) if (sortOrder === 'alpha') sorted.sort((a, b) => (a.title || '').localeCompare(b.title || '')) diff --git a/memento-note/components/rich-text-editor.tsx b/memento-note/components/rich-text-editor.tsx index 27c78ec..cbcb591 100644 --- a/memento-note/components/rich-text-editor.tsx +++ b/memento-note/components/rich-text-editor.tsx @@ -61,8 +61,7 @@ import { applyClipRtlDirection } from '@/lib/editor/apply-clip-rtl-direction' import { NOTE_REQUEST_SAVE_EVENT } from '@/lib/note-change-sync' import { openNotePeek } from '@/lib/note-peek-sync' import { useAiConsent } from '@/components/legal/ai-consent-provider' -import type { Editor } from '@tiptap/core' -import type { EditorState } from '@tiptap/pm/state' +import { isTextSelection, type Editor } from '@tiptap/core' import { Bold, Italic, Underline as UnderlineIcon, Strikethrough, Code, Heading1, Heading2, Heading3, List, ListOrdered, CheckSquare, @@ -75,7 +74,7 @@ import { } from 'lucide-react' import { cn } from '@/lib/utils' import { toast } from 'sonner' -import { NodeSelection } from '@tiptap/pm/state' +import { AllSelection, NodeSelection } from '@tiptap/pm/state' export interface RichTextEditorHandle { getEditor: () => Editor | null @@ -1403,17 +1402,25 @@ export const RichTextEditor = forwardRef document.body, - zIndex: 99999, - fallbackPlacements: ['bottom', 'top'] + appendTo={() => document.body} + shouldShow={({ editor: e, state, from, to, view, element }) => { + if (!e.isEditable || e.isActive('codeBlock')) return false + + const { selection } = state + if (selection instanceof AllSelection) return false + if (selection instanceof NodeSelection) { + return selection.node.type.name === 'image' } - } as any)} - shouldShow={({ editor: e, state }: { editor: Editor; state: EditorState }) => { - const { from, to } = state.selection - const isImage = e.isActive('image') - return (from !== to && !e.isActive('codeBlock')) || isImage + if (!isTextSelection(selection) || selection.empty) return false + + const selectedText = state.doc.textBetween(from, to) + if (!selectedText.length) return false + + // setContent à l’ouverture sélectionne parfois tout le document + const docSize = state.doc.content.size + if (docSize > 4 && from <= 1 && to >= docSize - 1) return false + + return view.hasFocus() || element.contains(document.activeElement) }} >