fix: Add debounced Undo/Redo system to avoid character-by-character history
- Add debounced state updates for title and content (500ms delay) - Immediate UI updates with delayed history saving - Prevent one-letter-per-undo issue - Add cleanup for debounce timers on unmount
This commit is contained in:
108
keep-notes/components/note-grid.tsx
Normal file
108
keep-notes/components/note-grid.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
'use client'
|
||||
|
||||
import { Note } from '@/lib/types'
|
||||
import { NoteCard } from './note-card'
|
||||
import { useState } from 'react'
|
||||
import { NoteEditor } from './note-editor'
|
||||
import { reorderNotes } from '@/app/actions/notes'
|
||||
|
||||
interface NoteGridProps {
|
||||
notes: Note[]
|
||||
}
|
||||
|
||||
export function NoteGrid({ notes }: NoteGridProps) {
|
||||
const [editingNote, setEditingNote] = useState<Note | null>(null)
|
||||
const [draggedNote, setDraggedNote] = useState<Note | null>(null)
|
||||
const [dragOverNote, setDragOverNote] = useState<Note | null>(null)
|
||||
|
||||
const pinnedNotes = notes.filter(note => note.isPinned).sort((a, b) => a.order - b.order)
|
||||
const unpinnedNotes = notes.filter(note => !note.isPinned).sort((a, b) => a.order - b.order)
|
||||
|
||||
const handleDragStart = (note: Note) => {
|
||||
setDraggedNote(note)
|
||||
}
|
||||
|
||||
const handleDragEnd = async () => {
|
||||
if (draggedNote && dragOverNote && draggedNote.id !== dragOverNote.id) {
|
||||
// Reorder notes
|
||||
const sourceIndex = notes.findIndex(n => n.id === draggedNote.id)
|
||||
const targetIndex = notes.findIndex(n => n.id === dragOverNote.id)
|
||||
|
||||
await reorderNotes(draggedNote.id, dragOverNote.id)
|
||||
}
|
||||
setDraggedNote(null)
|
||||
setDragOverNote(null)
|
||||
}
|
||||
|
||||
const handleDragOver = (note: Note) => {
|
||||
if (draggedNote && draggedNote.id !== note.id) {
|
||||
setDragOverNote(note)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-8">
|
||||
{pinnedNotes.length > 0 && (
|
||||
<div>
|
||||
<h2 className="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-3 px-2">
|
||||
Pinned
|
||||
</h2>
|
||||
<div className="columns-1 sm:columns-2 lg:columns-3 xl:columns-4 2xl:columns-5 gap-4 space-y-4">
|
||||
{pinnedNotes.map(note => (
|
||||
<div key={note.id} className="break-inside-avoid mb-4">
|
||||
<NoteCard
|
||||
note={note}
|
||||
onEdit={setEditingNote}
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragOver={handleDragOver}
|
||||
isDragging={draggedNote?.id === note.id}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{unpinnedNotes.length > 0 && (
|
||||
<div>
|
||||
{pinnedNotes.length > 0 && (
|
||||
<h2 className="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-3 px-2">
|
||||
Others
|
||||
</h2>
|
||||
)}
|
||||
<div className="columns-1 sm:columns-2 lg:columns-3 xl:columns-4 2xl:columns-5 gap-4 space-y-4">
|
||||
{unpinnedNotes.map(note => (
|
||||
<div key={note.id} className="break-inside-avoid mb-4">
|
||||
<NoteCard
|
||||
note={note}
|
||||
onEdit={setEditingNote}
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragOver={handleDragOver}
|
||||
isDragging={draggedNote?.id === note.id}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{notes.length === 0 && (
|
||||
<div className="text-center py-16">
|
||||
<p className="text-gray-500 dark:text-gray-400 text-lg">No notes yet</p>
|
||||
<p className="text-gray-400 dark:text-gray-500 text-sm mt-2">Create your first note to get started</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{editingNote && (
|
||||
<NoteEditor
|
||||
note={editingNote}
|
||||
onClose={() => setEditingNote(null)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user