feat: configure all notes file visibility
This commit is contained in:
51
src/utils/allNotesFileVisibility.ts
Normal file
51
src/utils/allNotesFileVisibility.ts
Normal 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
|
||||
}
|
||||
@@ -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([])
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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 ---
|
||||
|
||||
Reference in New Issue
Block a user