feat: configure all notes file visibility
This commit is contained in:
@@ -19,6 +19,9 @@ const emptySettings: Settings = {
|
||||
ui_language: null,
|
||||
default_ai_agent: null,
|
||||
hide_gitignored_files: null,
|
||||
all_notes_show_pdfs: null,
|
||||
all_notes_show_images: null,
|
||||
all_notes_show_unsupported: null,
|
||||
}
|
||||
|
||||
function installPointerCapturePolyfill() {
|
||||
@@ -107,6 +110,9 @@ describe('SettingsPanel', () => {
|
||||
release_channel: null,
|
||||
theme_mode: 'light',
|
||||
hide_gitignored_files: true,
|
||||
all_notes_show_pdfs: false,
|
||||
all_notes_show_images: false,
|
||||
all_notes_show_unsupported: false,
|
||||
}))
|
||||
expect(onClose).toHaveBeenCalled()
|
||||
})
|
||||
@@ -125,6 +131,56 @@ describe('SettingsPanel', () => {
|
||||
expect(onClose).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('renders All Notes file visibility checkboxes off by default', () => {
|
||||
render(
|
||||
<SettingsPanel open={true} settings={emptySettings} onSave={onSave} onClose={onClose} />
|
||||
)
|
||||
|
||||
expect(screen.getByText('All Notes visibility')).toBeInTheDocument()
|
||||
expect(within(screen.getByTestId('settings-all-notes-show-pdfs')).getByRole('checkbox')).toHaveAttribute('aria-checked', 'false')
|
||||
expect(within(screen.getByTestId('settings-all-notes-show-images')).getByRole('checkbox')).toHaveAttribute('aria-checked', 'false')
|
||||
expect(within(screen.getByTestId('settings-all-notes-show-unsupported')).getByRole('checkbox')).toHaveAttribute('aria-checked', 'false')
|
||||
})
|
||||
|
||||
it('preserves saved All Notes file visibility checkboxes', () => {
|
||||
render(
|
||||
<SettingsPanel
|
||||
open={true}
|
||||
settings={{
|
||||
...emptySettings,
|
||||
all_notes_show_pdfs: true,
|
||||
all_notes_show_images: true,
|
||||
all_notes_show_unsupported: false,
|
||||
}}
|
||||
onSave={onSave}
|
||||
onClose={onClose}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(within(screen.getByTestId('settings-all-notes-show-pdfs')).getByRole('checkbox')).toHaveAttribute('aria-checked', 'true')
|
||||
expect(within(screen.getByTestId('settings-all-notes-show-images')).getByRole('checkbox')).toHaveAttribute('aria-checked', 'true')
|
||||
expect(within(screen.getByTestId('settings-all-notes-show-unsupported')).getByRole('checkbox')).toHaveAttribute('aria-checked', 'false')
|
||||
})
|
||||
|
||||
it('saves All Notes file visibility from keyboard toggles before Escape close', () => {
|
||||
render(
|
||||
<SettingsPanel open={true} settings={emptySettings} onSave={onSave} onClose={onClose} />
|
||||
)
|
||||
|
||||
const pdfCheckbox = within(screen.getByTestId('settings-all-notes-show-pdfs')).getByRole('checkbox')
|
||||
pdfCheckbox.focus()
|
||||
fireEvent.keyDown(pdfCheckbox, { key: ' ', code: 'Space' })
|
||||
fireEvent.keyUp(pdfCheckbox, { key: ' ', code: 'Space' })
|
||||
fireEvent.keyDown(screen.getByTestId('settings-panel'), { key: 'Escape' })
|
||||
|
||||
expect(onSave).toHaveBeenCalledWith(expect.objectContaining({
|
||||
all_notes_show_pdfs: true,
|
||||
all_notes_show_images: false,
|
||||
all_notes_show_unsupported: false,
|
||||
}))
|
||||
expect(onClose).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('defaults the color mode control to light', () => {
|
||||
render(
|
||||
<SettingsPanel open={true} settings={emptySettings} onSave={onSave} onClose={onClose} />
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
type KeyboardEvent as ReactKeyboardEvent,
|
||||
type MouseEvent as ReactMouseEvent,
|
||||
type ReactNode,
|
||||
type RefObject,
|
||||
} from 'react'
|
||||
import { Moon, Sun, X } from '@phosphor-icons/react'
|
||||
import { Copy } from 'lucide-react'
|
||||
@@ -38,6 +39,11 @@ import {
|
||||
import { normalizeReleaseChannel, serializeReleaseChannel, type ReleaseChannel } from '../lib/releaseChannel'
|
||||
import { shouldHideGitignoredFiles } from '../lib/gitignoredVisibility'
|
||||
import { trackEvent } from '../lib/telemetry'
|
||||
import {
|
||||
resolveAllNotesFileVisibility,
|
||||
settingsWithAllNotesFileVisibility,
|
||||
type AllNotesFileVisibility,
|
||||
} from '../utils/allNotesFileVisibility'
|
||||
import { Button } from './ui/button'
|
||||
import { Checkbox, type CheckedState } from './ui/checkbox'
|
||||
import { Input } from './ui/input'
|
||||
@@ -76,6 +82,7 @@ interface SettingsDraft {
|
||||
uiLanguage: UiLanguagePreference
|
||||
initialH1AutoRename: boolean
|
||||
hideGitignoredFiles: boolean
|
||||
allNotesFileVisibility: AllNotesFileVisibility
|
||||
crashReporting: boolean
|
||||
analytics: boolean
|
||||
explicitOrganization: boolean
|
||||
@@ -110,6 +117,8 @@ interface SettingsBodyProps {
|
||||
setInitialH1AutoRename: (value: boolean) => void
|
||||
hideGitignoredFiles: boolean
|
||||
setHideGitignoredFiles: (value: boolean) => void
|
||||
allNotesFileVisibility: AllNotesFileVisibility
|
||||
setAllNotesFileVisibility: (value: AllNotesFileVisibility) => void
|
||||
explicitOrganization: boolean
|
||||
setExplicitOrganization: (value: boolean) => void
|
||||
crashReporting: boolean
|
||||
@@ -149,6 +158,7 @@ function createSettingsDraft(
|
||||
uiLanguage: settings.ui_language ?? SYSTEM_UI_LANGUAGE,
|
||||
initialH1AutoRename: settings.initial_h1_auto_rename_enabled ?? true,
|
||||
hideGitignoredFiles: shouldHideGitignoredFiles(settings),
|
||||
allNotesFileVisibility: resolveAllNotesFileVisibility(settings),
|
||||
crashReporting: settings.crash_reporting_enabled ?? false,
|
||||
analytics: settings.analytics_enabled ?? false,
|
||||
explicitOrganization: explicitOrganizationEnabled,
|
||||
@@ -175,7 +185,7 @@ function resolveAnonymousId(settings: Settings, draft: SettingsDraft): string |
|
||||
}
|
||||
|
||||
function buildSettingsFromDraft(settings: Settings, draft: SettingsDraft): Settings {
|
||||
return {
|
||||
const nextSettings = {
|
||||
auto_pull_interval_minutes: draft.pullInterval,
|
||||
autogit_enabled: draft.autoGitEnabled,
|
||||
autogit_idle_threshold_seconds: draft.autoGitIdleThresholdSeconds,
|
||||
@@ -192,6 +202,7 @@ function buildSettingsFromDraft(settings: Settings, draft: SettingsDraft): Setti
|
||||
default_ai_agent: draft.defaultAiAgent,
|
||||
hide_gitignored_files: draft.hideGitignoredFiles,
|
||||
}
|
||||
return settingsWithAllNotesFileVisibility(nextSettings, draft.allNotesFileVisibility)
|
||||
}
|
||||
|
||||
function trackTelemetryConsentChange(previousAnalytics: boolean, nextAnalytics: boolean): void {
|
||||
@@ -213,6 +224,16 @@ function applyThemeModeSelection(value: ThemeMode): void {
|
||||
if (typeof window !== 'undefined') writeStoredThemeMode(window.localStorage, value)
|
||||
}
|
||||
|
||||
function useSettingsPanelAutofocus(panelRef: RefObject<HTMLDivElement | null>): void {
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
const focusTarget = panelRef.current?.querySelector<HTMLElement>('[data-settings-autofocus="true"]')
|
||||
focusTarget?.focus()
|
||||
}, 50)
|
||||
return () => clearTimeout(timer)
|
||||
}, [panelRef])
|
||||
}
|
||||
|
||||
export function SettingsPanel({
|
||||
open,
|
||||
settings,
|
||||
@@ -272,13 +293,7 @@ function SettingsPanelInner({
|
||||
setDraft(createSettingsDraft(settings, explicitOrganizationEnabled))
|
||||
}, [explicitOrganizationEnabled, settings])
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
const focusTarget = panelRef.current?.querySelector<HTMLElement>('[data-settings-autofocus="true"]')
|
||||
focusTarget?.focus()
|
||||
}, 50)
|
||||
return () => clearTimeout(timer)
|
||||
}, [])
|
||||
useSettingsPanelAutofocus(panelRef)
|
||||
|
||||
const updateDraft = useCallback(
|
||||
<Key extends keyof SettingsDraft>(key: Key, value: SettingsDraft[Key]) => {
|
||||
@@ -292,6 +307,11 @@ function SettingsPanelInner({
|
||||
onSave({ ...settings, hide_gitignored_files: value })
|
||||
}, [onSave, settings, updateDraft])
|
||||
|
||||
const handleAllNotesFileVisibilityChange = useCallback((value: AllNotesFileVisibility) => {
|
||||
updateDraft('allNotesFileVisibility', value)
|
||||
onSave(settingsWithAllNotesFileVisibility(settings, value))
|
||||
}, [onSave, settings, updateDraft])
|
||||
|
||||
const handleThemeModeChange = useCallback((value: ThemeMode) => {
|
||||
updateDraft('themeMode', value)
|
||||
applyThemeModeSelection(value)
|
||||
@@ -368,6 +388,8 @@ function SettingsPanelInner({
|
||||
setInitialH1AutoRename={(value) => updateDraft('initialH1AutoRename', value)}
|
||||
hideGitignoredFiles={draft.hideGitignoredFiles}
|
||||
setHideGitignoredFiles={handleGitignoredVisibilityChange}
|
||||
allNotesFileVisibility={draft.allNotesFileVisibility}
|
||||
setAllNotesFileVisibility={handleAllNotesFileVisibilityChange}
|
||||
explicitOrganization={draft.explicitOrganization}
|
||||
setExplicitOrganization={(value) => updateDraft('explicitOrganization', value)}
|
||||
crashReporting={draft.crashReporting}
|
||||
@@ -401,7 +423,17 @@ function SettingsHeader({ onClose, t }: { onClose: () => void; t: Translate }) {
|
||||
)
|
||||
}
|
||||
|
||||
function SettingsBody({
|
||||
function SettingsBody(props: SettingsBodyProps) {
|
||||
return (
|
||||
<div style={{ padding: 24, display: 'flex', flexDirection: 'column', gap: 0, overflow: 'auto' }}>
|
||||
<SettingsSyncAndAppearanceSections {...props} />
|
||||
<SettingsContentSections {...props} />
|
||||
<SettingsAgentWorkflowSections {...props} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function SettingsSyncAndAppearanceSections({
|
||||
t,
|
||||
locale,
|
||||
systemLocale,
|
||||
@@ -414,31 +446,15 @@ function SettingsBody({
|
||||
setAutoGitIdleThresholdSeconds,
|
||||
autoGitInactiveThresholdSeconds,
|
||||
setAutoGitInactiveThresholdSeconds,
|
||||
autoAdvanceInboxAfterOrganize,
|
||||
setAutoAdvanceInboxAfterOrganize,
|
||||
aiAgentsStatus,
|
||||
defaultAiAgent,
|
||||
setDefaultAiAgent,
|
||||
onCopyMcpConfig,
|
||||
releaseChannel,
|
||||
setReleaseChannel,
|
||||
themeMode,
|
||||
setThemeMode,
|
||||
uiLanguage,
|
||||
setUiLanguage,
|
||||
initialH1AutoRename,
|
||||
setInitialH1AutoRename,
|
||||
hideGitignoredFiles,
|
||||
setHideGitignoredFiles,
|
||||
explicitOrganization,
|
||||
setExplicitOrganization,
|
||||
crashReporting,
|
||||
setCrashReporting,
|
||||
analytics,
|
||||
setAnalytics,
|
||||
}: SettingsBodyProps) {
|
||||
return (
|
||||
<div style={{ padding: 24, display: 'flex', flexDirection: 'column', gap: 0, overflow: 'auto' }}>
|
||||
<>
|
||||
<SettingsSection showDivider={false}>
|
||||
<SyncAndUpdatesSection
|
||||
t={t}
|
||||
@@ -448,7 +464,6 @@ function SettingsBody({
|
||||
setReleaseChannel={setReleaseChannel}
|
||||
/>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection>
|
||||
<AutoGitSettingsSection
|
||||
t={t}
|
||||
@@ -479,7 +494,21 @@ function SettingsBody({
|
||||
setUiLanguage={setUiLanguage}
|
||||
/>
|
||||
</SettingsSection>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function SettingsContentSections({
|
||||
t,
|
||||
initialH1AutoRename,
|
||||
setInitialH1AutoRename,
|
||||
hideGitignoredFiles,
|
||||
setHideGitignoredFiles,
|
||||
allNotesFileVisibility,
|
||||
setAllNotesFileVisibility,
|
||||
}: SettingsBodyProps) {
|
||||
return (
|
||||
<>
|
||||
<SettingsSection>
|
||||
<TitleSettingsSection
|
||||
t={t}
|
||||
@@ -493,9 +522,31 @@ function SettingsBody({
|
||||
t={t}
|
||||
hideGitignoredFiles={hideGitignoredFiles}
|
||||
setHideGitignoredFiles={setHideGitignoredFiles}
|
||||
allNotesFileVisibility={allNotesFileVisibility}
|
||||
setAllNotesFileVisibility={setAllNotesFileVisibility}
|
||||
/>
|
||||
</SettingsSection>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function SettingsAgentWorkflowSections({
|
||||
t,
|
||||
autoAdvanceInboxAfterOrganize,
|
||||
setAutoAdvanceInboxAfterOrganize,
|
||||
aiAgentsStatus,
|
||||
defaultAiAgent,
|
||||
setDefaultAiAgent,
|
||||
onCopyMcpConfig,
|
||||
explicitOrganization,
|
||||
setExplicitOrganization,
|
||||
crashReporting,
|
||||
setCrashReporting,
|
||||
analytics,
|
||||
setAnalytics,
|
||||
}: SettingsBodyProps) {
|
||||
return (
|
||||
<>
|
||||
<SettingsSection>
|
||||
<AiAgentSettingsSection
|
||||
t={t}
|
||||
@@ -525,7 +576,7 @@ function SettingsBody({
|
||||
setAnalytics={setAnalytics}
|
||||
/>
|
||||
</SettingsSection>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -779,7 +830,16 @@ function VaultContentSettingsSection({
|
||||
t,
|
||||
hideGitignoredFiles,
|
||||
setHideGitignoredFiles,
|
||||
}: Pick<SettingsBodyProps, 't' | 'hideGitignoredFiles' | 'setHideGitignoredFiles'>) {
|
||||
allNotesFileVisibility,
|
||||
setAllNotesFileVisibility,
|
||||
}: Pick<
|
||||
SettingsBodyProps,
|
||||
't' | 'hideGitignoredFiles' | 'setHideGitignoredFiles' | 'allNotesFileVisibility' | 'setAllNotesFileVisibility'
|
||||
>) {
|
||||
const updateAllNotesFileVisibility = (patch: Partial<AllNotesFileVisibility>) => {
|
||||
setAllNotesFileVisibility({ ...allNotesFileVisibility, ...patch })
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SectionHeading
|
||||
@@ -794,6 +854,35 @@ function VaultContentSettingsSection({
|
||||
onChange={setHideGitignoredFiles}
|
||||
testId="settings-hide-gitignored-files"
|
||||
/>
|
||||
|
||||
<div className="space-y-3 pt-1">
|
||||
<SectionHeading
|
||||
title={t('settings.allNotesVisibility.title')}
|
||||
description={t('settings.allNotesVisibility.description')}
|
||||
/>
|
||||
|
||||
<SettingsCheckboxRow
|
||||
label={t('settings.allNotesVisibility.pdfs')}
|
||||
description={t('settings.allNotesVisibility.pdfsDescription')}
|
||||
checked={allNotesFileVisibility.pdfs}
|
||||
onChange={(checked) => updateAllNotesFileVisibility({ pdfs: checked })}
|
||||
testId="settings-all-notes-show-pdfs"
|
||||
/>
|
||||
<SettingsCheckboxRow
|
||||
label={t('settings.allNotesVisibility.images')}
|
||||
description={t('settings.allNotesVisibility.imagesDescription')}
|
||||
checked={allNotesFileVisibility.images}
|
||||
onChange={(checked) => updateAllNotesFileVisibility({ images: checked })}
|
||||
testId="settings-all-notes-show-images"
|
||||
/>
|
||||
<SettingsCheckboxRow
|
||||
label={t('settings.allNotesVisibility.unsupported')}
|
||||
description={t('settings.allNotesVisibility.unsupportedDescription')}
|
||||
checked={allNotesFileVisibility.unsupported}
|
||||
onChange={(checked) => updateAllNotesFileVisibility({ unsupported: checked })}
|
||||
testId="settings-all-notes-show-unsupported"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1084,6 +1173,40 @@ function SettingsSwitchRow({
|
||||
)
|
||||
}
|
||||
|
||||
function SettingsCheckboxRow({
|
||||
label,
|
||||
description,
|
||||
checked,
|
||||
onChange,
|
||||
testId,
|
||||
}: {
|
||||
label: string
|
||||
description: string
|
||||
checked: boolean
|
||||
onChange: (value: boolean) => void
|
||||
testId: string
|
||||
}) {
|
||||
return (
|
||||
<label className="flex items-start gap-3" style={{ cursor: 'pointer' }} data-testid={testId}>
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
onCheckedChange={(value) => onChange(isChecked(value))}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key !== ' ' && event.key !== 'Spacebar') return
|
||||
event.preventDefault()
|
||||
onChange(!checked)
|
||||
}}
|
||||
aria-label={label}
|
||||
className="mt-0.5"
|
||||
/>
|
||||
<span className="space-y-1">
|
||||
<span className="block" style={{ fontSize: 13, fontWeight: 500, color: 'var(--foreground)' }}>{label}</span>
|
||||
<span className="block" style={{ fontSize: 11, color: 'var(--muted-foreground)' }}>{description}</span>
|
||||
</span>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
|
||||
function TelemetryToggle({
|
||||
label,
|
||||
description,
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
import { useSidebarTypeInteractions } from './sidebar/useSidebarTypeInteractions'
|
||||
import type { AppLocale } from '../lib/i18n'
|
||||
import type { FolderFileActions } from '../hooks/useFileActions'
|
||||
import type { AllNotesFileVisibility } from '../utils/allNotesFileVisibility'
|
||||
|
||||
interface SidebarProps {
|
||||
entries: VaultEntry[]
|
||||
@@ -60,6 +61,7 @@ interface SidebarProps {
|
||||
vaultRootPath?: string
|
||||
showInbox?: boolean
|
||||
inboxCount?: number
|
||||
allNotesFileVisibility?: AllNotesFileVisibility
|
||||
locale?: AppLocale
|
||||
onCollapse?: () => void
|
||||
loading?: boolean
|
||||
@@ -489,10 +491,11 @@ function useSidebarRuntime({
|
||||
onReorderSections,
|
||||
onRenameSection,
|
||||
onToggleTypeVisibility,
|
||||
allNotesFileVisibility,
|
||||
locale = 'en',
|
||||
}: SidebarProps) {
|
||||
const { typeEntryMap, allSectionGroups, visibleSections, sectionIds } = useSidebarSections(entries)
|
||||
const { activeCount, archivedCount } = useEntryCounts(entries)
|
||||
const { activeCount, archivedCount } = useEntryCounts(entries, allNotesFileVisibility)
|
||||
const { collapsed: groupCollapsed, toggle: toggleGroup } = useSidebarCollapsed()
|
||||
const typeInteractions = useSidebarTypeInteractions({
|
||||
allSectionGroups,
|
||||
|
||||
@@ -18,6 +18,7 @@ import { useMultiSelect, type MultiSelectState } from '../../hooks/useMultiSelec
|
||||
import { useNoteListKeyboard } from '../../hooks/useNoteListKeyboard'
|
||||
import { prefetchNoteContent } from '../../hooks/useTabManagement'
|
||||
import type { NoteListPropertiesScope } from './noteListPropertiesEvents'
|
||||
import type { AllNotesFileVisibility } from '../../utils/allNotesFileVisibility'
|
||||
|
||||
// --- useTypeEntryMap ---
|
||||
|
||||
@@ -36,6 +37,7 @@ interface FilteredEntriesParams {
|
||||
subFilter?: NoteListFilter
|
||||
inboxPeriod?: InboxPeriod
|
||||
views?: ViewFile[]
|
||||
allNotesFileVisibility?: AllNotesFileVisibility
|
||||
}
|
||||
|
||||
function buildFilteredEntries({
|
||||
@@ -50,6 +52,7 @@ function buildFilteredEntries({
|
||||
subFilter,
|
||||
inboxPeriod,
|
||||
views,
|
||||
allNotesFileVisibility,
|
||||
}: FilteredEntriesParams & {
|
||||
isEntityView: boolean
|
||||
isChangesView: boolean
|
||||
@@ -61,7 +64,11 @@ function buildFilteredEntries({
|
||||
return entries.filter((entry) => isModifiedEntry(entry.path, modifiedPathSet, modifiedSuffixes))
|
||||
}
|
||||
if (isInboxView) return filterInboxEntries(entries, inboxPeriod ?? 'month')
|
||||
return filterEntries(entries, selection, subFilter, views)
|
||||
return filterEntries(entries, selection, {
|
||||
subFilter,
|
||||
views,
|
||||
allNotesFileVisibility,
|
||||
})
|
||||
}
|
||||
|
||||
export function useFilteredEntries({
|
||||
@@ -73,6 +80,7 @@ export function useFilteredEntries({
|
||||
subFilter,
|
||||
inboxPeriod,
|
||||
views,
|
||||
allNotesFileVisibility,
|
||||
}: FilteredEntriesParams) {
|
||||
const isEntityView = selection.kind === 'entity'
|
||||
const isChangesView = selection.kind === 'filter' && selection.filter === 'changes'
|
||||
@@ -90,8 +98,9 @@ export function useFilteredEntries({
|
||||
subFilter,
|
||||
inboxPeriod,
|
||||
views,
|
||||
allNotesFileVisibility,
|
||||
})
|
||||
}, [entries, inboxPeriod, isChangesView, isEntityView, isInboxView, modifiedFiles, modifiedPathSet, modifiedSuffixes, selection, subFilter, views])
|
||||
}, [allNotesFileVisibility, entries, inboxPeriod, isChangesView, isEntityView, isInboxView, modifiedFiles, modifiedPathSet, modifiedSuffixes, selection, subFilter, views])
|
||||
}
|
||||
|
||||
// --- useNoteListData ---
|
||||
@@ -104,9 +113,23 @@ interface NoteListDataParams {
|
||||
subFilter?: NoteListFilter
|
||||
inboxPeriod?: InboxPeriod
|
||||
views?: ViewFile[]
|
||||
allNotesFileVisibility?: AllNotesFileVisibility
|
||||
}
|
||||
|
||||
export function useNoteListData({ entries, selection, query, listSort, listDirection, modifiedPathSet, modifiedSuffixes, modifiedFiles, subFilter, inboxPeriod, views }: NoteListDataParams) {
|
||||
export function useNoteListData({
|
||||
entries,
|
||||
selection,
|
||||
query,
|
||||
listSort,
|
||||
listDirection,
|
||||
modifiedPathSet,
|
||||
modifiedSuffixes,
|
||||
modifiedFiles,
|
||||
subFilter,
|
||||
inboxPeriod,
|
||||
views,
|
||||
allNotesFileVisibility,
|
||||
}: NoteListDataParams) {
|
||||
const isEntityView = selection.kind === 'entity'
|
||||
const isArchivedView = (selection.kind === 'filter' && selection.filter === 'archived') || subFilter === 'archived'
|
||||
const entityEntry = useMemo(() => {
|
||||
@@ -123,6 +146,7 @@ export function useNoteListData({ entries, selection, query, listSort, listDirec
|
||||
subFilter,
|
||||
inboxPeriod,
|
||||
views,
|
||||
allNotesFileVisibility,
|
||||
})
|
||||
|
||||
const searched = useMemo(() => {
|
||||
@@ -530,8 +554,8 @@ function useAllNotesPropertyPickerState(
|
||||
const allNotesEntries = useMemo(
|
||||
() => isAllNotesView
|
||||
? [
|
||||
...filterEntries(entries, selection, 'open'),
|
||||
...filterEntries(entries, selection, 'archived'),
|
||||
...filterEntries(entries, selection, { subFilter: 'open' }),
|
||||
...filterEntries(entries, selection, { subFilter: 'archived' }),
|
||||
]
|
||||
: [],
|
||||
[entries, isAllNotesView, selection],
|
||||
@@ -588,7 +612,7 @@ function useViewPropertyPickerState(
|
||||
[selection, views],
|
||||
)
|
||||
const viewEntries = useMemo(
|
||||
() => selectedView ? filterEntries(entries, selection, undefined, views) : [],
|
||||
() => selectedView ? filterEntries(entries, selection, { views }) : [],
|
||||
[entries, selection, selectedView, views],
|
||||
)
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import type {
|
||||
import type { AppLocale } from '../../lib/i18n'
|
||||
import type { NoteListFilter } from '../../utils/noteListHelpers'
|
||||
import { countByFilter, countAllByFilter, countAllNotesByFilter } from '../../utils/noteListHelpers'
|
||||
import type { AllNotesFileVisibility } from '../../utils/allNotesFileVisibility'
|
||||
import { NoteItem } from '../NoteItem'
|
||||
import { prefetchNoteContent } from '../../hooks/useTabManagement'
|
||||
import type { MultiSelectState } from '../../hooks/useMultiSelect'
|
||||
@@ -108,13 +109,19 @@ function useBulkActions(
|
||||
}
|
||||
}
|
||||
|
||||
function useFilterCounts(entries: VaultEntry[], selection: SidebarSelection) {
|
||||
function useFilterCounts(
|
||||
entries: VaultEntry[],
|
||||
selection: SidebarSelection,
|
||||
allNotesFileVisibility?: AllNotesFileVisibility,
|
||||
) {
|
||||
return useMemo(() => {
|
||||
if (selection.kind === 'sectionGroup') return countByFilter(entries, selection.type)
|
||||
if (selection.kind === 'folder') return countAllByFilter(entries)
|
||||
if (selection.kind === 'filter' && selection.filter === 'all') return countAllNotesByFilter(entries)
|
||||
if (selection.kind === 'filter' && selection.filter === 'all') {
|
||||
return countAllNotesByFilter(entries, allNotesFileVisibility)
|
||||
}
|
||||
return { open: 0, archived: 0 }
|
||||
}, [entries, selection])
|
||||
}, [allNotesFileVisibility, entries, selection])
|
||||
}
|
||||
|
||||
interface UseNoteListContentParams {
|
||||
@@ -136,6 +143,7 @@ interface UseNoteListContentParams {
|
||||
updateEntry?: (path: string, patch: Partial<VaultEntry>) => void
|
||||
views?: ViewFile[]
|
||||
visibleNotesRef?: React.MutableRefObject<VaultEntry[]>
|
||||
allNotesFileVisibility?: AllNotesFileVisibility
|
||||
}
|
||||
|
||||
function useNoteListContent({
|
||||
@@ -157,6 +165,7 @@ function useNoteListContent({
|
||||
updateEntry,
|
||||
views,
|
||||
visibleNotesRef,
|
||||
allNotesFileVisibility,
|
||||
}: UseNoteListContentParams) {
|
||||
const subFilter = (selection.kind === 'sectionGroup' || selection.kind === 'folder')
|
||||
? noteListFilter
|
||||
@@ -217,6 +226,7 @@ function useNoteListContent({
|
||||
subFilter,
|
||||
inboxPeriod: effectiveInboxPeriod,
|
||||
views,
|
||||
allNotesFileVisibility,
|
||||
})
|
||||
const searched = useMemo(() => filterEntriesByNoteListQuery(sortedEntries, query, {
|
||||
allEntries: entries,
|
||||
@@ -465,6 +475,7 @@ export interface NoteListProps {
|
||||
onUpdateViewDefinition?: (filename: string, patch: Partial<ViewDefinition>) => void
|
||||
views?: ViewFile[]
|
||||
visibleNotesRef?: React.MutableRefObject<VaultEntry[]>
|
||||
allNotesFileVisibility?: AllNotesFileVisibility
|
||||
locale?: AppLocale
|
||||
}
|
||||
|
||||
@@ -574,12 +585,13 @@ export function useNoteListModel({
|
||||
onUpdateViewDefinition,
|
||||
views,
|
||||
visibleNotesRef,
|
||||
allNotesFileVisibility,
|
||||
locale = 'en',
|
||||
}: NoteListProps) {
|
||||
const selectedNotePath = selectedNote?.path ?? null
|
||||
const { modifiedPathSet, modifiedSuffixes, resolvedGetNoteStatus } = useModifiedFilesState(modifiedFiles, getNoteStatus)
|
||||
const { isInboxView } = useViewFlags(selection)
|
||||
const filterCounts = useFilterCounts(entries, selection)
|
||||
const filterCounts = useFilterCounts(entries, selection, allNotesFileVisibility)
|
||||
const content = useNoteListContent({
|
||||
entries,
|
||||
selection,
|
||||
@@ -599,6 +611,7 @@ export function useNoteListModel({
|
||||
updateEntry,
|
||||
views,
|
||||
visibleNotesRef,
|
||||
allNotesFileVisibility,
|
||||
})
|
||||
const interaction = useNoteListInteractionState({
|
||||
searched: content.searched,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { APP_STORAGE_KEYS, LEGACY_APP_STORAGE_KEYS, getAppStorageItem } from '..
|
||||
import { buildTypeEntryMap } from '../../utils/typeColors'
|
||||
import { countAllNotesByFilter } from '../../utils/noteListHelpers'
|
||||
import { buildDynamicSections, sortSections } from '../../utils/sidebarSections'
|
||||
import type { AllNotesFileVisibility } from '../../utils/allNotesFileVisibility'
|
||||
|
||||
export type SidebarGroupKey = 'favorites' | 'views' | 'sections' | 'folders'
|
||||
|
||||
@@ -57,11 +58,14 @@ export function useSidebarCollapsed() {
|
||||
return { collapsed, toggle }
|
||||
}
|
||||
|
||||
export function useEntryCounts(entries: VaultEntry[]) {
|
||||
export function useEntryCounts(
|
||||
entries: VaultEntry[],
|
||||
allNotesFileVisibility?: AllNotesFileVisibility,
|
||||
) {
|
||||
return useMemo(() => {
|
||||
const counts = countAllNotesByFilter(entries)
|
||||
const counts = countAllNotesByFilter(entries, allNotesFileVisibility)
|
||||
return { activeCount: counts.open, archivedCount: counts.archived }
|
||||
}, [entries])
|
||||
}, [allNotesFileVisibility, entries])
|
||||
}
|
||||
|
||||
export function computeReorder(sectionIds: string[], activeId: string, overId: string): string[] | null {
|
||||
|
||||
Reference in New Issue
Block a user