Replace the multi-tab model with single-note navigation. One note is open at a time; clicking any note (sidebar, wikilink, relationship) replaces the current note in the editor. Back/Forward history still works via Cmd+[/]. Removed: TabBar component, useClosedTabHistory, tab reorder/close/ reopen logic, Cmd+W/Cmd+Shift+T shortcuts, Close Tab and Reopen Closed Tab menu items, tabLayout utility, and all related tests. Simplified: useTabManagement (single-note), useAppNavigation (no tab lookup), useKeyboardNavigation (note nav only), useNoteCreation (no close-tab cleanup), useDeleteActions (deselect instead of close tab), Editor (no TabBar render), Rust menu (removed 2 menu items). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
74 lines
2.3 KiB
TypeScript
74 lines
2.3 KiB
TypeScript
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
|
import { useNavigationHistory } from './useNavigationHistory'
|
|
import { useNavigationGestures } from './useNavigationGestures'
|
|
import type { VaultEntry } from '../types'
|
|
|
|
interface UseAppNavigationParams {
|
|
entries: VaultEntry[]
|
|
activeTabPath: string | null
|
|
onSelectNote: (entry: VaultEntry) => void
|
|
}
|
|
|
|
/**
|
|
* Encapsulates browser-style back/forward navigation for the app:
|
|
* - Navigation history (push on note change, back/forward traversal)
|
|
* - Mouse button & trackpad gesture bindings
|
|
* - O(1) path->entry lookup map
|
|
*/
|
|
export function useAppNavigation({
|
|
entries,
|
|
activeTabPath,
|
|
onSelectNote,
|
|
}: UseAppNavigationParams) {
|
|
const navHistory = useNavigationHistory()
|
|
|
|
// Push to navigation history whenever the active note changes (user-initiated)
|
|
const navFromHistoryRef = useRef(false)
|
|
useEffect(() => {
|
|
if (activeTabPath && !navFromHistoryRef.current) {
|
|
navHistory.push(activeTabPath)
|
|
}
|
|
navFromHistoryRef.current = false
|
|
}, [activeTabPath]) // eslint-disable-line react-hooks/exhaustive-deps -- navHistory.push is stable
|
|
|
|
const isEntryExists = useCallback(
|
|
(path: string) => entries.some(e => e.path === path),
|
|
[entries],
|
|
)
|
|
|
|
const handleGoBack = useCallback(() => {
|
|
const target = navHistory.goBack(isEntryExists)
|
|
if (target) {
|
|
navFromHistoryRef.current = true
|
|
const entry = entries.find(e => e.path === target)
|
|
if (entry) onSelectNote(entry)
|
|
}
|
|
}, [navHistory, isEntryExists, entries, onSelectNote])
|
|
|
|
const handleGoForward = useCallback(() => {
|
|
const target = navHistory.goForward(isEntryExists)
|
|
if (target) {
|
|
navFromHistoryRef.current = true
|
|
const entry = entries.find(e => e.path === target)
|
|
if (entry) onSelectNote(entry)
|
|
}
|
|
}, [navHistory, isEntryExists, entries, onSelectNote])
|
|
|
|
useNavigationGestures({ onGoBack: handleGoBack, onGoForward: handleGoForward })
|
|
|
|
// O(1) path lookup map — rebuilt only when entries change
|
|
const entriesByPath = useMemo(() => {
|
|
const map = new Map<string, VaultEntry>()
|
|
for (const e of entries) map.set(e.path, e)
|
|
return map
|
|
}, [entries])
|
|
|
|
return {
|
|
handleGoBack,
|
|
handleGoForward,
|
|
canGoBack: navHistory.canGoBack,
|
|
canGoForward: navHistory.canGoForward,
|
|
entriesByPath,
|
|
}
|
|
}
|