feat: configure all notes file visibility

This commit is contained in:
lucaronin
2026-04-30 02:02:59 +02:00
parent 0ad207e318
commit fd3afc2493
31 changed files with 742 additions and 84 deletions

View File

@@ -0,0 +1,51 @@
import type { Settings, VaultEntry } from '../types'
import { filePreviewKind } from './filePreview'
export interface AllNotesFileVisibility {
pdfs: boolean
images: boolean
unsupported: boolean
}
export const DEFAULT_ALL_NOTES_FILE_VISIBILITY: AllNotesFileVisibility = {
pdfs: false,
images: false,
unsupported: false,
}
type AllNotesFileVisibilitySettings = Pick<
Settings,
'all_notes_show_pdfs' | 'all_notes_show_images' | 'all_notes_show_unsupported'
>
export function resolveAllNotesFileVisibility(
settings: AllNotesFileVisibilitySettings | null | undefined,
): AllNotesFileVisibility {
return {
pdfs: settings?.all_notes_show_pdfs === true,
images: settings?.all_notes_show_images === true,
unsupported: settings?.all_notes_show_unsupported === true,
}
}
export function settingsWithAllNotesFileVisibility(
settings: Settings,
visibility: AllNotesFileVisibility,
): Settings {
return {
...settings,
all_notes_show_pdfs: visibility.pdfs,
all_notes_show_images: visibility.images,
all_notes_show_unsupported: visibility.unsupported,
}
}
export function isOptionalAllNotesFileVisible(
entry: Pick<VaultEntry, 'fileKind' | 'filename' | 'path'>,
visibility: AllNotesFileVisibility,
): boolean {
const previewKind = filePreviewKind(entry)
if (previewKind === 'pdf') return visibility.pdfs
if (previewKind === 'image') return visibility.images
return visibility.unsupported
}

View File

@@ -183,7 +183,7 @@ describe('noteListHelpers extra coverage', () => {
},
}]
expect(filterEntries(entries, { kind: 'view', filename: 'work.view' }, undefined, views).map((entry) => entry.title)).toEqual(['Alpha'])
expect(filterEntries(entries, { kind: 'view', filename: 'work.view' }, { views }).map((entry) => entry.title)).toEqual(['Alpha'])
expect(filterEntries(entries, { kind: 'folder', path: 'projects' }).map((entry) => entry.title)).toEqual(['Beta'])
expect(filterEntries(entries, { kind: 'filter', filter: 'favorites' }).map((entry) => entry.title)).toEqual(['Alpha'])
expect(filterEntries(entries, { kind: 'filter', filter: 'pulse' })).toEqual([])

View File

@@ -15,7 +15,7 @@ describe('filterEntries', () => {
makeEntry({ path: '/4.md', title: 'Other', isA: 'Note' }),
]
const result = filterEntries(entries, { kind: 'sectionGroup', type: 'Project' }, 'open')
const result = filterEntries(entries, { kind: 'sectionGroup', type: 'Project' }, { subFilter: 'open' })
expect(result.map((entry) => entry.title)).toEqual(['Active'])
})
@@ -26,7 +26,7 @@ describe('filterEntries', () => {
makeEntry({ path: '/4.md', title: 'Other', isA: 'Note' }),
]
const result = filterEntries(entries, { kind: 'sectionGroup', type: 'Project' }, 'archived')
const result = filterEntries(entries, { kind: 'sectionGroup', type: 'Project' }, { subFilter: 'archived' })
expect(result.map((entry) => entry.title)).toEqual(['Archived'])
})
@@ -48,7 +48,7 @@ describe('filterEntries', () => {
makeEntry({ path: '/4.md', title: 'Other', isA: 'Note' }),
]
const result = filterEntries(entries, allSelection, 'open')
const result = filterEntries(entries, allSelection, { subFilter: 'open' })
expect(result.map((entry) => entry.title)).toEqual(['Active', 'Other'])
})
@@ -59,7 +59,7 @@ describe('filterEntries', () => {
makeEntry({ path: '/4.md', title: 'Other', isA: 'Note' }),
]
const result = filterEntries(entries, allSelection, 'archived')
const result = filterEntries(entries, allSelection, { subFilter: 'archived' })
expect(result.map((entry) => entry.title)).toEqual(['Archived'])
})
@@ -71,10 +71,65 @@ describe('filterEntries', () => {
makeEntry({ path: 'C:\\Users\\luca\\Vault\\attachments\\windows.md', title: 'Windows Attachment Markdown', isA: 'Note' }),
]
const result = filterEntries(entries, allSelection, 'open')
const result = filterEntries(entries, allSelection, { subFilter: 'open' })
expect(result.map((entry) => entry.title)).toEqual(['Real Note'])
})
it('hides PDFs, images, and unsupported files from All Notes by default', () => {
const entries = [
makeEntry({ path: '/vault/note.md', filename: 'note.md', title: 'Note', fileKind: 'markdown' }),
makeEntry({ path: '/vault/Guide.PDF', filename: 'Guide.PDF', title: 'PDF', fileKind: 'binary' }),
makeEntry({ path: '/vault/Cover.JpG', filename: 'Cover.JpG', title: 'Image', fileKind: 'binary' }),
makeEntry({ path: '/vault/config.yml', filename: 'config.yml', title: 'Text', fileKind: 'text' }),
makeEntry({ path: '/vault/archive.zip', filename: 'archive.zip', title: 'Archive', fileKind: 'binary' }),
]
const result = filterEntries(entries, allSelection, { subFilter: 'open' })
expect(result.map((entry) => entry.title)).toEqual(['Note'])
})
it('shows selected non-Markdown categories in All Notes without swallowing PDFs or images into unsupported files', () => {
const entries = [
makeEntry({ path: '/vault/note.md', filename: 'note.md', title: 'Note', fileKind: 'markdown' }),
makeEntry({ path: '/vault/Guide.PDF', filename: 'Guide.PDF', title: 'PDF', fileKind: 'binary' }),
makeEntry({ path: '/vault/Cover.JpG', filename: 'Cover.JpG', title: 'Image', fileKind: 'binary' }),
makeEntry({ path: '/vault/config.yml', filename: 'config.yml', title: 'Text', fileKind: 'text' }),
makeEntry({ path: '/vault/archive.zip', filename: 'archive.zip', title: 'Archive', fileKind: 'binary' }),
]
expect(
filterEntries(entries, allSelection, {
subFilter: 'open',
allNotesFileVisibility: {
pdfs: true,
images: false,
unsupported: false,
},
}).map((entry) => entry.title),
).toEqual(['Note', 'PDF'])
expect(
filterEntries(entries, allSelection, {
subFilter: 'open',
allNotesFileVisibility: {
pdfs: false,
images: true,
unsupported: false,
},
}).map((entry) => entry.title),
).toEqual(['Note', 'Image'])
expect(
filterEntries(entries, allSelection, {
subFilter: 'open',
allNotesFileVisibility: {
pdfs: false,
images: false,
unsupported: true,
},
}).map((entry) => entry.title),
).toEqual(['Note', 'Text', 'Archive'])
})
it('matches slash-based folder selections against Windows entry paths', () => {
const entries = [
makeEntry({ path: 'C:\\Users\\luca\\Vault\\Client Work\\Alpha.md', title: 'Alpha' }),
@@ -99,6 +154,18 @@ describe('filterEntries', () => {
expect(result.map((entry) => entry.title)).toEqual(['Root Note', 'config.json'])
})
it('keeps folder browsing independent from All Notes file visibility', () => {
const entries = [
makeEntry({ path: '/vault/assets/spec.PDF', filename: 'spec.PDF', title: 'Spec', fileKind: 'binary' }),
makeEntry({ path: '/vault/assets/logo.PNG', filename: 'logo.PNG', title: 'Logo', fileKind: 'binary' }),
makeEntry({ path: '/vault/assets/data.sqlite', filename: 'data.sqlite', title: 'Data', fileKind: 'binary' }),
]
const result = filterEntries(entries, { kind: 'folder', path: 'assets' })
expect(result.map((entry) => entry.title)).toEqual(['Spec', 'Logo', 'Data'])
})
})
describe('countByFilter', () => {
@@ -152,6 +219,21 @@ describe('countAllNotesByFilter', () => {
expect(countAllNotesByFilter(entries)).toEqual({ open: 1, archived: 1 })
})
it('counts enabled All Notes file categories by archive status', () => {
const entries = [
makeEntry({ path: '/vault/note.md', filename: 'note.md', fileKind: 'markdown' }),
makeEntry({ path: '/vault/spec.pdf', filename: 'spec.pdf', fileKind: 'binary' }),
makeEntry({ path: '/vault/photo.PNG', filename: 'photo.PNG', fileKind: 'binary', archived: true }),
makeEntry({ path: '/vault/data.bin', filename: 'data.bin', fileKind: 'binary', archived: true }),
]
expect(countAllNotesByFilter(entries, {
pdfs: true,
images: true,
unsupported: false,
})).toEqual({ open: 2, archived: 1 })
})
})
describe('buildRelationshipGroups', () => {

View File

@@ -4,11 +4,22 @@ import {
orderInverseRelationshipLabels as sortInverseRelationshipLabels,
resolveInverseRelationshipLabel,
} from './inverseRelationshipLabels'
import {
DEFAULT_ALL_NOTES_FILE_VISIBILITY,
isOptionalAllNotesFileVisible,
type AllNotesFileVisibility,
} from './allNotesFileVisibility'
import { evaluateView } from './viewFilters'
import { wikilinkTarget, resolveEntry } from './wikilink'
export type NoteListFilter = 'open' | 'archived'
export interface FilterEntriesOptions {
subFilter?: NoteListFilter
views?: ViewFile[]
allNotesFileVisibility?: AllNotesFileVisibility
}
export interface RelationshipGroup {
label: string
entries: VaultEntry[]
@@ -407,8 +418,12 @@ function normalizeFolderPath(path: string): string {
return path.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '')
}
export function isAllNotesEntry(entry: VaultEntry): boolean {
return isMarkdown(entry) && !isInFolder(entry.path, ATTACHMENTS_FOLDER)
export function isAllNotesEntry(
entry: VaultEntry,
allNotesFileVisibility: AllNotesFileVisibility = DEFAULT_ALL_NOTES_FILE_VISIBILITY,
): boolean {
if (isMarkdown(entry)) return !isInFolder(entry.path, ATTACHMENTS_FOLDER)
return isOptionalAllNotesFileVisible(entry, allNotesFileVisibility)
}
function filterViewEntries(entries: VaultEntry[], filename: string, views?: ViewFile[]): VaultEntry[] {
@@ -446,21 +461,25 @@ function filterSectionGroupEntries(entries: VaultEntry[], type: string, subFilte
function filterTopLevelEntries(
entries: VaultEntry[],
selection: Extract<SidebarSelection, { kind: 'filter' }>,
subFilter?: NoteListFilter,
options: FilterEntriesOptions,
): VaultEntry[] {
const filterableEntries = selection.filter === 'all'
? entries.filter(isAllNotesEntry)
? entries.filter((entry) => isAllNotesEntry(entry, options.allNotesFileVisibility))
: entries.filter(isMarkdown)
if (selection.filter === 'all' && subFilter) return applySubFilter(filterableEntries, subFilter)
if (selection.filter === 'all' && options.subFilter) return applySubFilter(filterableEntries, options.subFilter)
return filterByFilterType(filterableEntries, selection.filter)
}
function filterByKind(entries: VaultEntry[], selection: SidebarSelection, subFilter?: NoteListFilter, views?: ViewFile[]): VaultEntry[] {
function filterByKind(
entries: VaultEntry[],
selection: SidebarSelection,
options: FilterEntriesOptions,
): VaultEntry[] {
if (selection.kind === 'entity') return []
if (selection.kind === 'view') return filterViewEntries(entries, selection.filename, views)
if (selection.kind === 'folder') return filterFolderEntries(entries, selection, subFilter)
if (selection.kind === 'sectionGroup') return filterSectionGroupEntries(entries, selection.type, subFilter)
if (selection.kind === 'filter') return filterTopLevelEntries(entries, selection, subFilter)
if (selection.kind === 'view') return filterViewEntries(entries, selection.filename, options.views)
if (selection.kind === 'folder') return filterFolderEntries(entries, selection, options.subFilter)
if (selection.kind === 'sectionGroup') return filterSectionGroupEntries(entries, selection.type, options.subFilter)
if (selection.kind === 'filter') return filterTopLevelEntries(entries, selection, options)
return []
}
@@ -472,8 +491,12 @@ function filterByFilterType(entries: VaultEntry[], filter: string): VaultEntry[]
return []
}
export function filterEntries(entries: VaultEntry[], selection: SidebarSelection, subFilter?: NoteListFilter, views?: ViewFile[]): VaultEntry[] {
return filterByKind(entries, selection, subFilter, views)
export function filterEntries(
entries: VaultEntry[],
selection: SidebarSelection,
options: FilterEntriesOptions = {},
): VaultEntry[] {
return filterByKind(entries, selection, options)
}
/** Count notes per sub-filter for a given type. */
@@ -501,9 +524,14 @@ export function countAllByFilter(entries: VaultEntry[]): Record<NoteListFilter,
return countEntriesByArchiveStatus(entries.filter(isMarkdown))
}
/** Count All Notes-eligible documents per sub-filter, excluding files under attachments/. */
export function countAllNotesByFilter(entries: VaultEntry[]): Record<NoteListFilter, number> {
return countEntriesByArchiveStatus(entries.filter(isAllNotesEntry))
/** Count All Notes-eligible documents per sub-filter using the current file visibility policy. */
export function countAllNotesByFilter(
entries: VaultEntry[],
allNotesFileVisibility?: AllNotesFileVisibility,
): Record<NoteListFilter, number> {
return countEntriesByArchiveStatus(
entries.filter((entry) => isAllNotesEntry(entry, allNotesFileVisibility)),
)
}
// --- Inbox ---