Keep/keep-notes/hooks/useUndoRedo.ts
sepehr 355ffb59bb feat: Add robust Undo/Redo system and improve note input
- Implement useUndoRedo hook with proper state management (max 50 history)
- Add Undo/Redo buttons with keyboard shortcuts (Ctrl+Z/Ctrl+Y)
- Fix image upload with proper sizing (max-w-full max-h-96 object-contain)
- Add image validation (type and 5MB size limit)
- Implement reminder system with date validation
- Add comprehensive input validation with user-friendly error messages
- Improve error handling with try-catch blocks
- Add MCP-GUIDE.md with complete MCP documentation and examples

Breaking changes: None
Production ready: Yes
2026-01-04 14:22:36 +01:00

116 lines
2.8 KiB
TypeScript

import { useState, useCallback, useRef } from 'react'
export interface UndoRedoState<T> {
past: T[]
present: T
future: T[]
}
interface UseUndoRedoReturn<T> {
state: T
setState: (newState: T | ((prev: T) => T)) => void
undo: () => void
redo: () => void
canUndo: boolean
canRedo: boolean
clear: () => void
}
const MAX_HISTORY_SIZE = 50
export function useUndoRedo<T>(initialState: T): UseUndoRedoReturn<T> {
const [history, setHistory] = useState<UndoRedoState<T>>({
past: [],
present: initialState,
future: [],
})
// Track if we're in an undo/redo operation to prevent adding to history
const isUndoRedoAction = useRef(false)
const setState = useCallback((newState: T | ((prev: T) => T)) => {
// Skip if this is an undo/redo action
if (isUndoRedoAction.current) {
isUndoRedoAction.current = false
return
}
setHistory((currentHistory) => {
const resolvedNewState =
typeof newState === 'function'
? (newState as (prev: T) => T)(currentHistory.present)
: newState
// Don't add to history if state hasn't changed
if (JSON.stringify(resolvedNewState) === JSON.stringify(currentHistory.present)) {
return currentHistory
}
const newPast = [...currentHistory.past, currentHistory.present]
// Limit history size
if (newPast.length > MAX_HISTORY_SIZE) {
newPast.shift()
}
return {
past: newPast,
present: resolvedNewState,
future: [], // Clear future on new action
}
})
}, [])
const undo = useCallback(() => {
setHistory((currentHistory) => {
if (currentHistory.past.length === 0) return currentHistory
const previous = currentHistory.past[currentHistory.past.length - 1]
const newPast = currentHistory.past.slice(0, currentHistory.past.length - 1)
isUndoRedoAction.current = true
return {
past: newPast,
present: previous,
future: [currentHistory.present, ...currentHistory.future],
}
})
}, [])
const redo = useCallback(() => {
setHistory((currentHistory) => {
if (currentHistory.future.length === 0) return currentHistory
const next = currentHistory.future[0]
const newFuture = currentHistory.future.slice(1)
isUndoRedoAction.current = true
return {
past: [...currentHistory.past, currentHistory.present],
present: next,
future: newFuture,
}
})
}, [])
const clear = useCallback(() => {
setHistory({
past: [],
present: initialState,
future: [],
})
}, [initialState])
return {
state: history.present,
setState,
undo,
redo,
canUndo: history.past.length > 0,
canRedo: history.future.length > 0,
clear,
}
}