feat: MVP-5 P2 - Export/Import, Settings, Tests y Validaciones
- Ticket 10: Navegación completa de listas por teclado (↑↓ Enter E F P) - Ticket 13: Historial de navegación contextual con recent-context-list - Ticket 17: Exportación mejorada a Markdown con frontmatter - Ticket 18: Exportación HTML simple y legible - Ticket 19: Importador Markdown mejorado con frontmatter, tags, wiki links - Ticket 20: Importador Obsidian-compatible (wiki links, #tags inline) - Ticket 21: Centro de respaldo y portabilidad en Settings - Ticket 22: Configuración visible de feature flags - Ticket 24: Tests de command palette y captura externa - Ticket 25: Harden de validaciones y límites (50MB backup, 10K notas, etc)
This commit is contained in:
87
src/hooks/use-note-list-keyboard.ts
Normal file
87
src/hooks/use-note-list-keyboard.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { useEffect, useCallback, useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Note } from '@/types/note'
|
||||
|
||||
interface UseNoteListKeyboardOptions {
|
||||
notes: Note[]
|
||||
onEdit?: (noteId: string) => void
|
||||
onFavorite?: (noteId: string) => void
|
||||
onPin?: (noteId: string) => void
|
||||
}
|
||||
|
||||
export function useNoteListKeyboard({
|
||||
notes,
|
||||
onEdit,
|
||||
onFavorite,
|
||||
onPin,
|
||||
}: UseNoteListKeyboardOptions) {
|
||||
const [selectedIndex, setSelectedIndex] = useState(-1)
|
||||
const router = useRouter()
|
||||
|
||||
// Reset selection when notes change
|
||||
useEffect(() => {
|
||||
setSelectedIndex(-1)
|
||||
}, [notes.length])
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: KeyboardEvent) => {
|
||||
// Ignore if in input/textarea/contenteditable
|
||||
const target = e.target as HTMLElement
|
||||
if (
|
||||
target.tagName === 'INPUT' ||
|
||||
target.tagName === 'TEXTAREA' ||
|
||||
target.isContentEditable
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
switch (e.key) {
|
||||
case 'ArrowDown':
|
||||
e.preventDefault()
|
||||
setSelectedIndex((prev) =>
|
||||
prev < notes.length - 1 ? prev + 1 : notes.length - 1
|
||||
)
|
||||
break
|
||||
case 'ArrowUp':
|
||||
e.preventDefault()
|
||||
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : -1))
|
||||
break
|
||||
case 'Enter':
|
||||
e.preventDefault()
|
||||
if (selectedIndex >= 0 && notes[selectedIndex]) {
|
||||
router.push(`/notes/${notes[selectedIndex].id}`)
|
||||
}
|
||||
break
|
||||
case 'e':
|
||||
case 'E':
|
||||
if (selectedIndex >= 0 && notes[selectedIndex] && onEdit) {
|
||||
e.preventDefault()
|
||||
onEdit(notes[selectedIndex].id)
|
||||
}
|
||||
break
|
||||
case 'f':
|
||||
case 'F':
|
||||
if (selectedIndex >= 0 && notes[selectedIndex] && onFavorite) {
|
||||
e.preventDefault()
|
||||
onFavorite(notes[selectedIndex].id)
|
||||
}
|
||||
break
|
||||
case 'p':
|
||||
case 'P':
|
||||
if (selectedIndex >= 0 && notes[selectedIndex] && onPin) {
|
||||
e.preventDefault()
|
||||
onPin(notes[selectedIndex].id)
|
||||
}
|
||||
break
|
||||
}
|
||||
},
|
||||
[notes, selectedIndex, router, onEdit, onFavorite, onPin]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
return () => window.removeEventListener('keydown', handleKeyDown)
|
||||
}, [handleKeyDown])
|
||||
|
||||
return { selectedIndex, setSelectedIndex }
|
||||
}
|
||||
Reference in New Issue
Block a user