import { useState, useMemo, useCallback, useEffect, memo } from 'react' import type { VaultEntry, SidebarSelection, ModifiedFile, NoteStatus, InboxPeriod } from '../types' import type { NoteListFilter } from '../utils/noteListHelpers' import { countByFilter, countAllByFilter, countInboxByPeriod } from '../utils/noteListHelpers' import { NoteItem } from './NoteItem' import { prefetchNoteContent } from '../hooks/useTabManagement' import { BulkActionBar } from './BulkActionBar' import { useMultiSelect } from '../hooks/useMultiSelect' import { useNoteListKeyboard } from '../hooks/useNoteListKeyboard' import { NoteListHeader } from './note-list/NoteListHeader' import { FilterPills } from './note-list/FilterPills' import { InboxFilterPills } from './note-list/InboxFilterPills' import { EntityView, ListView } from './note-list/NoteListViews' import { DeletedNotesBanner } from './note-list/TrashWarningBanner' import { routeNoteClick, toggleSetMember, resolveHeaderTitle } from './note-list/noteListUtils' import { useTypeEntryMap, useNoteListData, useNoteListSearch, useNoteListSort, useMultiSelectKeyboard, useModifiedFilesState, } from './note-list/noteListHooks' interface NoteListProps { entries: VaultEntry[] selection: SidebarSelection selectedNote: VaultEntry | null noteListFilter: NoteListFilter onNoteListFilterChange: (filter: NoteListFilter) => void inboxPeriod?: InboxPeriod onInboxPeriodChange?: (period: InboxPeriod) => void modifiedFiles?: ModifiedFile[] modifiedFilesError?: string | null getNoteStatus?: (path: string) => NoteStatus sidebarCollapsed?: boolean onSelectNote: (entry: VaultEntry) => void onReplaceActiveTab: (entry: VaultEntry) => void onCreateNote: () => void onBulkArchive?: (paths: string[]) => void onBulkTrash?: (paths: string[]) => void onBulkRestore?: (paths: string[]) => void onBulkDeletePermanently?: (paths: string[]) => void onEmptyTrash?: () => void onUpdateTypeSort?: (path: string, key: string, value: string | number | boolean | string[] | null) => void updateEntry?: (path: string, patch: Partial) => void onOpenInNewWindow?: (entry: VaultEntry) => void } function NoteListInner({ entries, selection, selectedNote, noteListFilter, onNoteListFilterChange, inboxPeriod = 'month', onInboxPeriodChange, modifiedFiles, modifiedFilesError, getNoteStatus, sidebarCollapsed, onSelectNote, onReplaceActiveTab, onCreateNote, onBulkArchive, onBulkTrash, onBulkRestore, onBulkDeletePermanently, onEmptyTrash, onUpdateTypeSort, updateEntry, onOpenInNewWindow }: NoteListProps) { const { modifiedPathSet, modifiedSuffixes, resolvedGetNoteStatus } = useModifiedFilesState(modifiedFiles, getNoteStatus) const isSectionGroup = selection.kind === 'sectionGroup' const isFolderView = selection.kind === 'folder' const isInboxView = selection.kind === 'filter' && selection.filter === 'inbox' const isAllNotesView = selection.kind === 'filter' && selection.filter === 'all' const showFilterPills = isSectionGroup || isFolderView || isAllNotesView const subFilter = showFilterPills ? noteListFilter : undefined const filterCounts = useMemo( () => isSectionGroup ? countByFilter(entries, selection.type) : (isAllNotesView || isFolderView) ? countAllByFilter(entries) : { open: 0, archived: 0, trashed: 0 }, [entries, isSectionGroup, isAllNotesView, isFolderView, selection], ) const inboxCounts = useMemo( () => isInboxView ? countInboxByPeriod(entries) : { week: 0, month: 0, quarter: 0, all: 0 }, [entries, isInboxView], ) const { listSort, listDirection, customProperties, handleSortChange, sortPrefs, typeDocument } = useNoteListSort({ entries, selection, modifiedPathSet, modifiedSuffixes, subFilter, inboxPeriod: isInboxView ? inboxPeriod : undefined, onUpdateTypeSort, updateEntry }) const { search, setSearch, query, searchVisible, toggleSearch } = useNoteListSearch() const [collapsedGroups, setCollapsedGroups] = useState>(new Set()) const typeEntryMap = useTypeEntryMap(entries) const { isEntityView, isTrashView, isArchivedView, searched, searchedGroups, expiredTrashCount } = useNoteListData({ entries, selection, query, listSort, listDirection, modifiedPathSet, modifiedSuffixes, subFilter, inboxPeriod: isInboxView ? inboxPeriod : undefined }) const isChangesView = selection.kind === 'filter' && selection.filter === 'changes' const deletedCount = useMemo( () => isChangesView ? (modifiedFiles ?? []).filter((f) => f.status === 'deleted').length : 0, [isChangesView, modifiedFiles], ) const entitySelection = isEntityView && selection.kind === 'entity' ? selection : null const noteListKeyboard = useNoteListKeyboard({ items: searched, selectedNotePath: selectedNote?.path ?? null, onOpen: onReplaceActiveTab, enabled: !isEntityView }) const multiSelect = useMultiSelect(searched, selectedNote?.path ?? null) useEffect(() => { multiSelect.clear() }, [selection, noteListFilter]) // eslint-disable-line react-hooks/exhaustive-deps -- clear on selection/filter change const handleClickNote = useCallback((entry: VaultEntry, e: React.MouseEvent) => { routeNoteClick(entry, e, { onReplace: onReplaceActiveTab, onSelect: onSelectNote, onOpenInNewWindow, multiSelect }) }, [onReplaceActiveTab, onSelectNote, onOpenInNewWindow, multiSelect]) const handleBulkArchive = useCallback(() => { const paths = [...multiSelect.selectedPaths]; multiSelect.clear(); onBulkArchive?.(paths) }, [multiSelect, onBulkArchive]) const handleBulkTrash = useCallback(() => { const paths = [...multiSelect.selectedPaths]; multiSelect.clear(); onBulkTrash?.(paths) }, [multiSelect, onBulkTrash]) const handleBulkRestore = useCallback(() => { const paths = [...multiSelect.selectedPaths]; multiSelect.clear(); onBulkRestore?.(paths) }, [multiSelect, onBulkRestore]) const handleBulkDeletePermanently = useCallback(() => { const paths = [...multiSelect.selectedPaths]; multiSelect.clear(); onBulkDeletePermanently?.(paths) }, [multiSelect, onBulkDeletePermanently]) const handleBulkUnarchive = useCallback(() => { const paths = [...multiSelect.selectedPaths]; multiSelect.clear(); onBulkRestore?.(paths) }, [multiSelect, onBulkRestore]) const bulkArchiveOrRestore = isTrashView ? handleBulkRestore : isArchivedView ? handleBulkUnarchive : handleBulkArchive const bulkTrashOrDelete = isTrashView ? handleBulkDeletePermanently : handleBulkTrash useMultiSelectKeyboard(multiSelect, isEntityView, bulkArchiveOrRestore, bulkTrashOrDelete) const renderItem = useCallback((entry: VaultEntry) => ( ), [selectedNote?.path, handleClickNote, typeEntryMap, resolvedGetNoteStatus, multiSelect.selectedPaths, noteListKeyboard.highlightedPath]) const toggleGroup = useCallback((label: string) => { setCollapsedGroups((prev) => toggleSetMember(prev, label)) }, []) const title = resolveHeaderTitle(selection, typeDocument) return (
{entitySelection ? ( ) : ( )}
{isChangesView && deletedCount > 0 && } {showFilterPills && } {isInboxView && onInboxPeriodChange && }
{multiSelect.isMultiSelecting && ( )}
) } export const NoteList = memo(NoteListInner)