Files
tolaria/src/utils/sidebarSections.ts
2026-04-13 22:21:42 +02:00

94 lines
3.9 KiB
TypeScript

/**
* Pure functions for building sidebar section groups from vault entries.
* Extracted from Sidebar.tsx for testability.
*/
import type { VaultEntry } from '../types'
import type { SectionGroup } from '../components/SidebarParts'
import { resolveIcon } from './iconRegistry'
import { pluralizeType } from '../hooks/useCommandRegistry'
import { isLegacyJournalingType } from './legacyTypes'
import {
Wrench, Flask, Target, ArrowsClockwise,
Users, CalendarBlank, Tag, StackSimple,
} from '@phosphor-icons/react'
const BUILT_IN_SECTION_GROUPS: SectionGroup[] = [
{ label: 'Projects', type: 'Project', Icon: Wrench },
{ label: 'Experiments', type: 'Experiment', Icon: Flask },
{ label: 'Responsibilities', type: 'Responsibility', Icon: Target },
{ label: 'Procedures', type: 'Procedure', Icon: ArrowsClockwise },
{ label: 'People', type: 'Person', Icon: Users },
{ label: 'Events', type: 'Event', Icon: CalendarBlank },
{ label: 'Topics', type: 'Topic', Icon: Tag },
{ label: 'Types', type: 'Type', Icon: StackSimple },
]
/** Metadata lookup for well-known types (icon/label only — NOT used to determine which sections to show) */
const BUILT_IN_TYPE_MAP = new Map(BUILT_IN_SECTION_GROUPS.map((sg) => [sg.type, sg]))
const isMarkdown = (e: VaultEntry) => e.fileKind === 'markdown' || !e.fileKind
const isActive = (e: VaultEntry) => !e.archived
const isSupportedSectionType = (type: string) => !isLegacyJournalingType(type)
function resolveEntrySectionType(entry: VaultEntry): string {
return entry.isA || 'Note'
}
function shouldCollectActiveType(entry: VaultEntry): boolean {
if (!isActive(entry) || !isMarkdown(entry)) return false
return isSupportedSectionType(resolveEntrySectionType(entry))
}
function shouldIncludeTypeDefinition(name: string, entry: VaultEntry): boolean {
if (name !== entry.title || !isActive(entry)) return false
return isSupportedSectionType(name)
}
/** Collect unique isA values from active (non-archived) markdown entries. Untyped entries count as 'Note'. */
export function collectActiveTypes(entries: VaultEntry[]): Set<string> {
const types = new Set<string>()
for (const e of entries) {
if (!shouldCollectActiveType(e)) continue
types.add(resolveEntrySectionType(e))
}
return types
}
function resolveLabel(type: string, typeEntry: VaultEntry | undefined, builtIn: SectionGroup | undefined): string {
return typeEntry?.sidebarLabel || builtIn?.label || pluralizeType(type)
}
/** Build a single SectionGroup for a type, using built-in metadata or Type entry for icon/label */
export function buildSectionGroup(type: string, typeEntryMap: Record<string, VaultEntry>): SectionGroup {
const builtIn = BUILT_IN_TYPE_MAP.get(type)
const typeEntry = typeEntryMap[type]
const customColor = typeEntry?.color ?? null
const label = resolveLabel(type, typeEntry, builtIn)
const icon = resolveIcon(typeEntry?.icon ?? null)
if (builtIn) {
return { ...builtIn, label, Icon: typeEntry?.icon ? icon : builtIn.Icon, customColor }
}
return { label, type, Icon: icon, customColor }
}
/** Build sections dynamically from vault entries and defined types — types with 0 notes still appear */
export function buildDynamicSections(entries: VaultEntry[], typeEntryMap: Record<string, VaultEntry>): SectionGroup[] {
const activeTypes = collectActiveTypes(entries)
for (const [name, entry] of Object.entries(typeEntryMap)) {
if (!shouldIncludeTypeDefinition(name, entry)) continue
activeTypes.add(name)
}
return Array.from(activeTypes, (type) => buildSectionGroup(type, typeEntryMap))
}
export function sortSections(groups: SectionGroup[], typeEntryMap: Record<string, VaultEntry>): SectionGroup[] {
return [...groups].sort((a, b) => {
const orderA = typeEntryMap[a.type]?.order ?? Infinity
const orderB = typeEntryMap[b.type]?.order ?? Infinity
return orderA !== orderB ? orderA - orderB : a.label.localeCompare(b.label)
})
}
export { BUILT_IN_SECTION_GROUPS }