feat: consolidate to single Architectural Grid view and remove all notesViewMode logic
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m6s

This commit is contained in:
Antigravity
2026-05-10 14:05:12 +00:00
parent b31efac8ba
commit f6880bd0e1
12 changed files with 14 additions and 1801 deletions

View File

@@ -8,19 +8,11 @@ export default async function HomePage() {
getAISettings(), getAISettings(),
]) ])
const notesViewMode =
settings?.notesViewMode === 'masonry'
? ('masonry' as const)
: settings?.notesViewMode === 'tabs'
? ('tabs' as const)
: ('masonry' as const)
return ( return (
<HomeClient <HomeClient
initialNotes={allNotes} initialNotes={allNotes}
initialSettings={{ initialSettings={{
showRecentNotes: settings?.showRecentNotes !== false, showRecentNotes: settings?.showRecentNotes !== false,
notesViewMode,
noteHistory: settings?.noteHistory === true, noteHistory: settings?.noteHistory === true,
noteHistoryMode: (settings?.noteHistoryMode ?? 'manual') as 'manual' | 'auto', noteHistoryMode: (settings?.noteHistoryMode ?? 'manual') as 'manual' | 'auto',
aiAssistantEnabled: settings?.paragraphRefactor !== false, aiAssistantEnabled: settings?.paragraphRefactor !== false,

View File

@@ -14,7 +14,6 @@ export type UserAISettingsData = {
preferredLanguage?: 'auto' | 'en' | 'fr' | 'es' | 'de' | 'fa' | 'it' | 'pt' | 'ru' | 'zh' | 'ja' | 'ko' | 'ar' | 'hi' | 'nl' | 'pl' preferredLanguage?: 'auto' | 'en' | 'fr' | 'es' | 'de' | 'fa' | 'it' | 'pt' | 'ru' | 'zh' | 'ja' | 'ko' | 'ar' | 'hi' | 'nl' | 'pl'
demoMode?: boolean demoMode?: boolean
showRecentNotes?: boolean showRecentNotes?: boolean
notesViewMode?: 'masonry' | 'tabs'
emailNotifications?: boolean emailNotifications?: boolean
desktopNotifications?: boolean desktopNotifications?: boolean
anonymousAnalytics?: boolean anonymousAnalytics?: boolean
@@ -39,7 +38,6 @@ const USER_AI_SETTINGS_PRISMA_KEYS = [
'fontSize', 'fontSize',
'demoMode', 'demoMode',
'showRecentNotes', 'showRecentNotes',
'notesViewMode',
'emailNotifications', 'emailNotifications',
'desktopNotifications', 'desktopNotifications',
'anonymousAnalytics', 'anonymousAnalytics',
@@ -61,13 +59,6 @@ function pickUserAISettingsForDb(input: UserAISettingsData): Partial<Record<User
out[key] = v out[key] = v
} }
} }
if (
out.notesViewMode != null &&
out.notesViewMode !== 'masonry' &&
out.notesViewMode !== 'tabs'
) {
delete out.notesViewMode
}
return out return out
} }
@@ -148,7 +139,6 @@ const getCachedAISettings = unstable_cache(
preferredLanguage: 'auto' as const, preferredLanguage: 'auto' as const,
demoMode: false, demoMode: false,
showRecentNotes: false, showRecentNotes: false,
notesViewMode: 'masonry' as const,
emailNotifications: false, emailNotifications: false,
desktopNotifications: false, desktopNotifications: false,
anonymousAnalytics: false, anonymousAnalytics: false,
@@ -163,13 +153,6 @@ const getCachedAISettings = unstable_cache(
} }
} }
const raw = settings.notesViewMode
const viewMode =
raw === 'masonry'
? ('masonry' as const)
: raw === 'tabs'
? ('tabs' as const)
: ('masonry' as const)
return { return {
titleSuggestions: settings.titleSuggestions, titleSuggestions: settings.titleSuggestions,
@@ -181,7 +164,6 @@ const getCachedAISettings = unstable_cache(
preferredLanguage: (settings.preferredLanguage || 'auto') as 'auto' | 'en' | 'fr' | 'es' | 'de' | 'fa' | 'it' | 'pt' | 'ru' | 'zh' | 'ja' | 'ko' | 'ar' | 'hi' | 'nl' | 'pl', preferredLanguage: (settings.preferredLanguage || 'auto') as 'auto' | 'en' | 'fr' | 'es' | 'de' | 'fa' | 'it' | 'pt' | 'ru' | 'zh' | 'ja' | 'ko' | 'ar' | 'hi' | 'nl' | 'pl',
demoMode: settings.demoMode, demoMode: settings.demoMode,
showRecentNotes: settings.showRecentNotes, showRecentNotes: settings.showRecentNotes,
notesViewMode: viewMode,
emailNotifications: settings.emailNotifications, emailNotifications: settings.emailNotifications,
desktopNotifications: settings.desktopNotifications, desktopNotifications: settings.desktopNotifications,
anonymousAnalytics: settings.anonymousAnalytics, anonymousAnalytics: settings.anonymousAnalytics,
@@ -207,7 +189,6 @@ const getCachedAISettings = unstable_cache(
preferredLanguage: 'auto' as const, preferredLanguage: 'auto' as const,
demoMode: false, demoMode: false,
showRecentNotes: false, showRecentNotes: false,
notesViewMode: 'masonry' as const,
emailNotifications: false, emailNotifications: false,
desktopNotifications: false, desktopNotifications: false,
anonymousAnalytics: false, anonymousAnalytics: false,
@@ -246,7 +227,6 @@ export async function getAISettings(userId?: string) {
preferredLanguage: 'auto' as const, preferredLanguage: 'auto' as const,
demoMode: false, demoMode: false,
showRecentNotes: false, showRecentNotes: false,
notesViewMode: 'masonry' as const,
emailNotifications: false, emailNotifications: false,
desktopNotifications: false, desktopNotifications: false,
anonymousAnalytics: false, anonymousAnalytics: false,

View File

@@ -5,7 +5,6 @@ import { useSearchParams, useRouter } from 'next/navigation'
import dynamic from 'next/dynamic' import dynamic from 'next/dynamic'
import { Note } from '@/lib/types' import { Note } from '@/lib/types'
import { getAllNotes, searchNotes, enableNoteHistory, getNoteById, createNote } from '@/app/actions/notes' import { getAllNotes, searchNotes, enableNoteHistory, getNoteById, createNote } from '@/app/actions/notes'
import { NotesMainSection, type NotesViewMode } from '@/components/notes-main-section'
import { NotesEditorialView } from '@/components/notes-editorial-view' import { NotesEditorialView } from '@/components/notes-editorial-view'
import { MemoryEchoNotification } from '@/components/memory-echo-notification' import { MemoryEchoNotification } from '@/components/memory-echo-notification'
@@ -46,7 +45,6 @@ const NotebookSummaryDialog = dynamic(
type InitialSettings = { type InitialSettings = {
showRecentNotes: boolean showRecentNotes: boolean
notesViewMode: 'masonry' | 'tabs'
noteHistory: boolean noteHistory: boolean
noteHistoryMode: 'manual' | 'auto' noteHistoryMode: 'manual' | 'auto'
aiAssistantEnabled: boolean aiAssistantEnabled: boolean
@@ -66,7 +64,6 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
const [pinnedNotes, setPinnedNotes] = useState<Note[]>( const [pinnedNotes, setPinnedNotes] = useState<Note[]>(
initialNotes.filter(n => n.isPinned) initialNotes.filter(n => n.isPinned)
) )
const [notesViewMode, setNotesViewMode] = useState<NotesViewMode>(initialSettings.notesViewMode)
const [noteHistoryMode] = useState<'manual' | 'auto'>(initialSettings.noteHistoryMode) const [noteHistoryMode] = useState<'manual' | 'auto'>(initialSettings.noteHistoryMode)
const [editingNote, setEditingNote] = useState<{ note: Note; readOnly?: boolean } | null>(null) const [editingNote, setEditingNote] = useState<{ note: Note; readOnly?: boolean } | null>(null)
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
@@ -98,16 +95,15 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
} }
}, [shouldSuggestLabels, suggestNotebookId]) }, [shouldSuggestLabels, suggestNotebookId])
// Sidebar carnet / inbox: forceList → liste éditoriale + fermer l'éditeur plein écran (comme la ref. architectural-grid) // Sidebar carnet / inbox: fermer l'éditeur plein écran (comme la ref. architectural-grid)
useEffect(() => { useEffect(() => {
const forceList = searchParams.get('forceList') if (searchParams.get('forceList') === '1') {
if (forceList !== '1') return setEditingNote(null)
setNotesViewMode(prev => (prev === 'tabs' ? 'masonry' : prev)) const params = new URLSearchParams(searchParams.toString())
setEditingNote(null) params.delete('forceList')
const params = new URLSearchParams(searchParams.toString()) const newUrl = params.toString() ? `/?${params.toString()}` : '/'
params.delete('forceList') router.replace(newUrl, { scroll: false })
const newUrl = params.toString() ? `/?${params.toString()}` : '/' }
router.replace(newUrl, { scroll: false })
}, [searchParams, router]) }, [searchParams, router])
const notebookFilter = searchParams.get('notebook') const notebookFilter = searchParams.get('notebook')
@@ -234,7 +230,6 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
useEffect(() => { useEffect(() => {
const handler = (e: Event) => { const handler = (e: Event) => {
const { name } = (e as CustomEvent).detail const { name } = (e as CustomEvent).detail
if (!name) return
const removeLabel = (note: Note) => { const removeLabel = (note: Note) => {
const currentLabels = note.labels || [] const currentLabels = note.labels || []
const updated = currentLabels.filter((l) => l.toLowerCase() !== name.toLowerCase()) const updated = currentLabels.filter((l) => l.toLowerCase() !== name.toLowerCase())
@@ -346,13 +341,13 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
return trail return trail
}, [currentNotebook, notebooks]) }, [currentNotebook, notebooks])
useEffect(() => { useEffect(() => {
setControls({ setControls({
isTabsMode: notesViewMode === 'tabs',
openNoteComposer: () => handleAddNote(), openNoteComposer: () => handleAddNote(),
}) })
return () => setControls(null) return () => setControls(null)
}, [notesViewMode, setControls]) }, [setControls])
// Apply sort order to notes // Apply sort order to notes
const sortedNotes = useMemo(() => { const sortedNotes = useMemo(() => {
@@ -373,8 +368,6 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
alpha: t('sidebar.sortAlpha') || 'A → Z', alpha: t('sidebar.sortAlpha') || 'A → Z',
} }
const isTabs = notesViewMode === 'tabs'
const isEditorialMode = !isTabs
const handleEditorClose = useCallback(() => { const handleEditorClose = useCallback(() => {
setEditingNote(null) setEditingNote(null)
@@ -399,8 +392,7 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
return ( return (
<div <div
className={cn( className={cn(
'flex w-full min-h-0 flex-1 flex-col', 'flex w-full min-h-0 flex-1 flex-col gap-3 py-1'
isTabs ? 'gap-3 py-1' : 'h-full'
)} )}
> >
{editingNote ? ( {editingNote ? (
@@ -413,10 +405,9 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
/> />
) : ( ) : (
<div className="flex-1 overflow-y-auto min-h-0 bg-memento-paper flex flex-col"> <div className="flex-1 overflow-y-auto min-h-0 bg-memento-paper flex flex-col">
<div className={cn( <div
'px-12 pt-12 pb-8 flex flex-col gap-6', className="px-12 pt-12 pb-8 flex flex-col gap-6 sticky top-0 bg-memento-paper/90 backdrop-blur-md z-30"
isEditorialMode ? 'sticky top-0 bg-memento-paper/90 backdrop-blur-md z-30' : '' >
)}>
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
<div> <div>
{currentNotebook && notebookPath.length > 0 && ( {currentNotebook && notebookPath.length > 0 && (
@@ -571,18 +562,6 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
<div className="px-12 flex-1 pb-20"> <div className="px-12 flex-1 pb-20">
{isLoading ? ( {isLoading ? (
<div className="text-center py-8 text-muted-foreground">{t('general.loading')}</div> <div className="text-center py-8 text-muted-foreground">{t('general.loading')}</div>
) : isTabs ? (
<NotesMainSection
viewMode={notesViewMode}
notes={sortedNotes}
onEdit={(note, readOnly) => setEditingNote({ note, readOnly })}
onSizeChange={handleSizeChange}
currentNotebookId={searchParams.get('notebook')}
noteHistoryMode={noteHistoryMode}
onOpenHistory={handleOpenHistory}
onEnableHistory={handleEnableHistory}
onNoteCreated={handleNoteCreated}
/>
) : ( ) : (
<div className="max-w-3xl space-y-16"> <div className="max-w-3xl space-y-16">
{sortedPinnedNotes.length > 0 && ( {sortedPinnedNotes.length > 0 && (

View File

@@ -1,161 +0,0 @@
/**
* Masonry Grid — Deux modes d'affichage :
* 1. Variable : CSS Grid avec tailles small/medium/large
* 2. Uniform : CSS Columns masonry (comme Google Keep)
*/
/* ─── Container ──────────────────────────────────── */
.masonry-container {
width: 100%;
padding: 0 8px 40px 8px;
}
/* ═══════════════════════════════════════════════════
MODE 1 : VARIABLE (CSS Grid avec tailles différentes)
═══════════════════════════════════════════════════ */
.masonry-css-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
grid-auto-rows: auto;
gap: 12px;
align-items: start;
grid-auto-flow: dense;
}
.masonry-sortable-item[data-size="medium"] {
grid-column: span 2;
}
.masonry-sortable-item[data-size="large"] {
grid-column: span 3;
}
/* ═══════════════════════════════════════════════════
MODE 2 : UNIFORM — CSS Columns masonry (Google Keep)
═══════════════════════════════════════════════════ */
.masonry-container[data-card-size-mode="uniform"] .masonry-css-grid {
display: block;
column-width: 240px;
column-gap: 12px;
orphans: 1;
widows: 1;
}
.masonry-container[data-card-size-mode="uniform"] .masonry-sortable-item,
.masonry-container[data-card-size-mode="uniform"] .masonry-sortable-item[data-size="medium"],
.masonry-container[data-card-size-mode="uniform"] .masonry-sortable-item[data-size="large"] {
break-inside: avoid;
margin-bottom: 12px;
display: inline-block;
width: 100%;
grid-column: unset;
}
/* ─── Sortable items ─────────────────────────────── */
.masonry-sortable-item {
break-inside: avoid;
box-sizing: border-box;
will-change: transform;
transition: opacity 0.15s ease-out;
}
/* ─── Note card base ─────────────────────────────── */
.note-card {
width: 100% !important;
min-width: 0;
box-sizing: border-box;
}
/* ─── Drag overlay ───────────────────────────────── */
.masonry-drag-overlay {
cursor: grabbing;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.25), 0 8px 16px rgba(0, 0, 0, 0.15);
border-radius: 12px;
opacity: 0.95;
pointer-events: none;
}
/* ─── Mobile (< 480px) ───────────────────────────── */
@media (max-width: 479px) {
.masonry-css-grid {
grid-template-columns: 1fr;
gap: 10px;
}
.masonry-sortable-item[data-size="medium"],
.masonry-sortable-item[data-size="large"] {
grid-column: span 1;
}
.masonry-container[data-card-size-mode="uniform"] .masonry-css-grid {
column-width: 100%;
column-gap: 10px;
}
.masonry-container {
padding: 0 4px 16px 4px;
}
}
/* ─── Small tablet (480767px) ───────────────────── */
@media (min-width: 480px) and (max-width: 767px) {
.masonry-css-grid {
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.masonry-sortable-item[data-size="large"] {
grid-column: span 2;
}
.masonry-container {
padding: 0 8px 20px 8px;
}
}
/* ─── Tablet (7681023px) ────────────────────────── */
@media (min-width: 768px) and (max-width: 1023px) {
.masonry-css-grid {
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 12px;
}
}
/* ─── Desktop (10241279px) ─────────────────────── */
@media (min-width: 1024px) and (max-width: 1279px) {
.masonry-css-grid {
grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
gap: 12px;
}
}
/* ─── Large Desktop (1280px+) ───────────────────── */
@media (min-width: 1280px) {
.masonry-css-grid {
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 14px;
}
.masonry-container {
max-width: 1600px;
margin: 0 auto;
padding: 0 12px 32px 12px;
}
}
/* ─── Print ──────────────────────────────────────── */
@media print {
.masonry-sortable-item {
break-inside: avoid;
page-break-inside: avoid;
}
}
/* ─── Reduced motion ─────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
.masonry-sortable-item {
transition: none;
}
}

View File

@@ -1,346 +0,0 @@
'use client'
import { useState, useEffect, useCallback, memo, useMemo, useRef } from 'react';
import {
DndContext,
DragEndEvent,
DragOverlay,
DragStartEvent,
PointerSensor,
TouchSensor,
closestCenter,
useSensor,
useSensors,
} from '@dnd-kit/core';
import {
SortableContext,
arrayMove,
rectSortingStrategy,
useSortable,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { Note } from '@/lib/types';
import { NoteCard } from './note-card';
import { updateFullOrderWithoutRevalidation } from '@/app/actions/notes';
import { useEditorUI } from '@/context/editor-ui-context';
import { useLanguage } from '@/lib/i18n';
import { useCardSizeMode } from '@/hooks/use-card-size-mode';
import dynamic from 'next/dynamic';
import './masonry-grid.css';
// Lazy-load NoteEditor — uniquement chargé au clic
const NoteEditor = dynamic(
() => import('./note-editor').then(m => ({ default: m.NoteEditor })),
{ ssr: false }
);
interface MasonryGridProps {
notes: Note[];
onEdit?: (note: Note, readOnly?: boolean) => void;
onSizeChange?: (noteId: string, size: 'small' | 'medium' | 'large') => void;
isTrashView?: boolean;
noteHistoryEnabled?: boolean;
noteHistoryMode?: 'manual' | 'auto';
onOpenHistory?: (note: Note) => void;
}
// ─────────────────────────────────────────────
// Sortable Note Item
// ─────────────────────────────────────────────
interface SortableNoteProps {
note: Note;
onEdit: (note: Note, readOnly?: boolean) => void;
onSizeChange: (noteId: string, newSize: 'small' | 'medium' | 'large') => void;
onDragStartNote?: (noteId: string) => void;
onDragEndNote?: () => void;
isDragging?: boolean;
isOverlay?: boolean;
isTrashView?: boolean;
noteHistoryEnabled?: boolean;
onOpenHistory?: (note: Note) => void;
}
const SortableNoteItem = memo(function SortableNoteItem({
note,
onEdit,
onSizeChange,
onDragStartNote,
onDragEndNote,
isDragging,
isOverlay,
isTrashView,
noteHistoryEnabled,
onOpenHistory,
}: SortableNoteProps) {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging: isSortableDragging,
} = useSortable({ id: note.id });
const style: React.CSSProperties = {
transform: CSS.Transform.toString(transform),
transition,
opacity: isSortableDragging && !isOverlay ? 0.3 : 1,
};
return (
<div
ref={setNodeRef}
style={style}
{...attributes}
{...listeners}
className="masonry-sortable-item"
data-id={note.id}
data-size={note.size}
>
<NoteCard
note={note}
onEdit={onEdit}
onDragStart={onDragStartNote}
onDragEnd={onDragEndNote}
isDragging={isDragging}
isTrashView={isTrashView}
onSizeChange={(newSize) => onSizeChange(note.id, newSize)}
noteHistoryEnabled={noteHistoryEnabled}
onOpenHistory={onOpenHistory}
/>
</div>
);
})
// ─────────────────────────────────────────────
// Sortable Grid Section (pinned or others)
// ─────────────────────────────────────────────
interface SortableGridSectionProps {
notes: Note[];
onEdit: (note: Note, readOnly?: boolean) => void;
onSizeChange: (noteId: string, newSize: 'small' | 'medium' | 'large') => void;
draggedNoteId: string | null;
onDragStartNote: (noteId: string) => void;
onDragEndNote: () => void;
isTrashView?: boolean;
noteHistoryEnabled?: boolean;
onOpenHistory?: (note: Note) => void;
}
const SortableGridSection = memo(function SortableGridSection({
notes,
onEdit,
onSizeChange,
draggedNoteId,
onDragStartNote,
onDragEndNote,
isTrashView,
noteHistoryEnabled,
onOpenHistory,
}: SortableGridSectionProps) {
const ids = useMemo(() => notes.map(n => n.id), [notes]);
return (
<SortableContext items={ids} strategy={rectSortingStrategy}>
<div className="masonry-css-grid">
{notes.map(note => (
<SortableNoteItem
key={note.id}
note={note}
onEdit={onEdit}
onSizeChange={onSizeChange}
onDragStartNote={onDragStartNote}
onDragEndNote={onDragEndNote}
isDragging={draggedNoteId === note.id}
isTrashView={isTrashView}
noteHistoryEnabled={noteHistoryEnabled}
onOpenHistory={onOpenHistory}
/>
))}
</div>
</SortableContext>
);
});
// ─────────────────────────────────────────────
// Main MasonryGrid component
// ─────────────────────────────────────────────
export function MasonryGrid({
notes,
onEdit,
onSizeChange,
isTrashView,
noteHistoryEnabled = false,
onOpenHistory,
}: MasonryGridProps) {
const { t } = useLanguage();
const [editingNote, setEditingNote] = useState<{ note: Note; readOnly?: boolean } | null>(null);
const { startDrag, endDrag, draggedNoteId } = useEditorUI();
const cardSizeMode = useCardSizeMode();
const isUniformMode = cardSizeMode === 'uniform';
// Local notes state for optimistic size/order updates
const [localNotes, setLocalNotes] = useState<Note[]>(notes);
const prevNotesRef = useRef<Note[]>(notes);
if (notes !== prevNotesRef.current) {
const localSizeMap = new Map(localNotes.map(n => [n.id, n.size]));
const localOrderMap = new Map(localNotes.map((n, i) => [n.id, i]));
const newLocalNotes = notes.map(n => ({ ...n, size: localSizeMap.get(n.id) ?? n.size }));
newLocalNotes.sort((a, b) => {
const oA = localOrderMap.get(a.id)
const oB = localOrderMap.get(b.id)
if (oA !== undefined && oB !== undefined) return oA - oB
if (oA !== undefined) return -1
if (oB !== undefined) return 1
return 0
})
setLocalNotes(newLocalNotes);
prevNotesRef.current = notes;
}
const pinnedNotes = useMemo(() => localNotes.filter(n => n.isPinned), [localNotes]);
const othersNotes = useMemo(() => localNotes.filter(n => !n.isPinned), [localNotes]);
const [activeId, setActiveId] = useState<string | null>(null);
const activeNote = useMemo(
() => localNotes.find(n => n.id === activeId) ?? null,
[localNotes, activeId]
);
const handleEdit = useCallback((note: Note, readOnly?: boolean) => {
if (onEdit) {
onEdit(note, readOnly);
} else {
setEditingNote({ note, readOnly });
}
}, [onEdit]);
const handleSizeChange = useCallback((noteId: string, newSize: 'small' | 'medium' | 'large') => {
setLocalNotes(prev => prev.map(n => n.id === noteId ? { ...n, size: newSize } : n));
onSizeChange?.(noteId, newSize);
}, [onSizeChange]);
// @dnd-kit sensors — pointer (desktop) + touch (mobile)
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: { distance: 8 }, // Évite les activations accidentelles
}),
useSensor(TouchSensor, {
activationConstraint: { delay: 200, tolerance: 8 }, // Long-press sur mobile
})
);
const localNotesRef = useRef<Note[]>(localNotes)
useEffect(() => {
localNotesRef.current = localNotes
}, [localNotes])
const handleDragStart = useCallback((event: DragStartEvent) => {
setActiveId(event.active.id as string);
startDrag(event.active.id as string);
}, [startDrag]);
const handleDragEnd = useCallback(async (event: DragEndEvent) => {
const { active, over } = event;
setActiveId(null);
endDrag();
if (!over || active.id === over.id) return;
const current = localNotesRef.current
const activeIdx = current.findIndex(n => n.id === active.id)
const overIdx = current.findIndex(n => n.id === over.id)
if (activeIdx === -1 || overIdx === -1) return
const activeNote = current[activeIdx]
const overNote = current[overIdx]
if (activeNote.isPinned !== overNote.isPinned) return
const reordered = arrayMove(current, activeIdx, overIdx);
if (reordered.length === 0) return;
setLocalNotes(reordered);
const ids = reordered.map(n => n.id);
updateFullOrderWithoutRevalidation(ids).catch(err => {
console.error('Failed to persist order:', err);
});
}, [endDrag]);
return (
<DndContext
id="masonry-dnd"
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
>
<div className="masonry-container" data-card-size-mode={cardSizeMode}>
{pinnedNotes.length > 0 && (
<div className="mb-8">
<h2 className="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-3 px-2">
{t('notes.pinned')}
</h2>
<SortableGridSection
notes={pinnedNotes}
onEdit={handleEdit}
onSizeChange={handleSizeChange}
draggedNoteId={draggedNoteId}
onDragStartNote={startDrag}
onDragEndNote={endDrag}
isTrashView={isTrashView}
noteHistoryEnabled={noteHistoryEnabled}
onOpenHistory={onOpenHistory}
/>
</div>
)}
{othersNotes.length > 0 && (
<div>
{pinnedNotes.length > 0 && (
<h2 className="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-3 px-2">
{t('notes.others')}
</h2>
)}
<SortableGridSection
notes={othersNotes}
onEdit={handleEdit}
onSizeChange={handleSizeChange}
draggedNoteId={draggedNoteId}
onDragStartNote={startDrag}
onDragEndNote={endDrag}
isTrashView={isTrashView}
noteHistoryEnabled={noteHistoryEnabled}
onOpenHistory={onOpenHistory}
/>
</div>
)}
</div>
{/* DragOverlay — montre une copie flottante pendant le drag */}
<DragOverlay>
{activeNote ? (
<div className="masonry-sortable-item masonry-drag-overlay" data-size={activeNote.size}>
<NoteCard
note={activeNote}
onEdit={handleEdit}
isDragging={true}
onSizeChange={(newSize) => handleSizeChange(activeNote.id, newSize)}
noteHistoryEnabled={noteHistoryEnabled}
onOpenHistory={onOpenHistory}
/>
</div>
) : null}
</DragOverlay>
{editingNote && (
<NoteEditor
note={editingNote.note}
readOnly={editingNote.readOnly}
onClose={() => setEditingNote(null)}
/>
)}
</DndContext>
);
}

View File

@@ -1,72 +0,0 @@
'use client'
import dynamic from 'next/dynamic'
import { Note } from '@/lib/types'
import { NotesTabsView } from '@/components/notes-tabs-view'
const MasonryGridLazy = dynamic(
() => import('@/components/masonry-grid').then((m) => m.MasonryGrid),
{
ssr: false,
loading: () => (
<div
className="min-h-[200px] rounded-xl border border-dashed border-muted-foreground/20 bg-muted/30 animate-pulse"
aria-hidden
/>
),
}
)
export type NotesViewMode = 'masonry' | 'tabs'
interface NotesMainSectionProps {
notes: Note[]
viewMode: NotesViewMode
onEdit?: (note: Note, readOnly?: boolean) => void
onSizeChange?: (noteId: string, size: 'small' | 'medium' | 'large') => void
currentNotebookId?: string | null
noteHistoryMode?: 'manual' | 'auto'
onOpenHistory?: (note: Note) => void
onEnableHistory?: (noteId: string) => Promise<void>
onNoteCreated?: (note: Note) => void
}
export function NotesMainSection({
notes,
viewMode,
onEdit,
onSizeChange,
currentNotebookId,
noteHistoryMode = 'manual',
onOpenHistory,
onEnableHistory,
onNoteCreated,
}: NotesMainSectionProps) {
if (viewMode === 'tabs') {
return (
<div className="flex min-h-0 flex-1 flex-col" data-testid="notes-grid-tabs-wrap">
<NotesTabsView
notes={notes}
onEdit={onEdit}
currentNotebookId={currentNotebookId}
noteHistoryMode={noteHistoryMode}
onOpenHistory={onOpenHistory}
onEnableHistory={onEnableHistory}
onNoteCreated={onNoteCreated}
/>
</div>
)
}
return (
<div data-testid="notes-grid">
<MasonryGridLazy
notes={notes}
onEdit={onEdit}
onSizeChange={onSizeChange}
noteHistoryMode={noteHistoryMode}
onOpenHistory={onOpenHistory}
/>
</div>
)
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,6 @@
import { createContext, useContext, useState, useCallback, useMemo, type ReactNode } from 'react' import { createContext, useContext, useState, useCallback, useMemo, type ReactNode } from 'react'
export type HomeUiControls = { export type HomeUiControls = {
isTabsMode: boolean
openNoteComposer: () => void openNoteComposer: () => void
} }

View File

@@ -162,13 +162,6 @@
"unpinned": "Unpinned", "unpinned": "Unpinned",
"redoShortcut": "Redo (Ctrl+Y)", "redoShortcut": "Redo (Ctrl+Y)",
"undoShortcut": "Undo (Ctrl+Z)", "undoShortcut": "Undo (Ctrl+Z)",
"viewCards": "Cards View",
"viewCardsTooltip": "Card grid with drag-and-drop reorder",
"viewList": "List",
"viewListTooltip": "Scannable list with preview, dates, and labels",
"viewTabs": "Tabs",
"viewTabsTooltip": "Tabs on top, note below — drag tabs to reorder",
"viewModeGroup": "Notes display mode",
"reorderTabs": "Reorder tab", "reorderTabs": "Reorder tab",
"modified": "Modified", "modified": "Modified",
"created": "Created", "created": "Created",

View File

@@ -162,13 +162,6 @@
"unpinned": "Désépinglées", "unpinned": "Désépinglées",
"redoShortcut": "Rétablir (Ctrl+Y)", "redoShortcut": "Rétablir (Ctrl+Y)",
"undoShortcut": "Annuler (Ctrl+Z)", "undoShortcut": "Annuler (Ctrl+Z)",
"viewCards": "Vue par cartes",
"viewCardsTooltip": "Grille de cartes et réorganisation par glisser-déposer",
"viewList": "Liste",
"viewListTooltip": "Liste avec aperçu, dates et étiquettes (style magazine)",
"viewTabs": "Onglets",
"viewTabsTooltip": "Onglets en haut, contenu dessous — glisser les onglets pour réordonner",
"viewModeGroup": "Mode d'affichage des notes",
"reorderTabs": "Réordonner l'onglet", "reorderTabs": "Réordonner l'onglet",
"modified": "Modifiée", "modified": "Modifiée",
"created": "Créée", "created": "Créée",

View File

@@ -279,8 +279,6 @@ model UserAISettings {
fontSize String @default("medium") fontSize String @default("medium")
demoMode Boolean @default(false) demoMode Boolean @default(false)
showRecentNotes Boolean @default(true) showRecentNotes Boolean @default(true)
/// "masonry" = grille cartes Muuri ; "tabs" = onglets + panneau (type OneNote). Ancienne valeur "list" migrée vers "tabs" en lecture.
notesViewMode String @default("masonry")
emailNotifications Boolean @default(false) emailNotifications Boolean @default(false)
desktopNotifications Boolean @default(false) desktopNotifications Boolean @default(false)
anonymousAnalytics Boolean @default(false) anonymousAnalytics Boolean @default(false)

View File

@@ -312,7 +312,6 @@ async function main() {
fontSize: s.fontSize || 'medium', fontSize: s.fontSize || 'medium',
demoMode: s.demoMode === 1 || s.demoMode === true, demoMode: s.demoMode === 1 || s.demoMode === true,
showRecentNotes: s.showRecentNotes === 1 || s.showRecentNotes === true, showRecentNotes: s.showRecentNotes === 1 || s.showRecentNotes === true,
notesViewMode: s.notesViewMode || 'masonry',
emailNotifications: s.emailNotifications === 1 || s.emailNotifications === true, emailNotifications: s.emailNotifications === 1 || s.emailNotifications === true,
desktopNotifications: s.desktopNotifications === 1 || s.desktopNotifications === true, desktopNotifications: s.desktopNotifications === 1 || s.desktopNotifications === true,
anonymousAnalytics: s.anonymousAnalytics === 1 || s.anonymousAnalytics === true, anonymousAnalytics: s.anonymousAnalytics === 1 || s.anonymousAnalytics === true,