import { useMemo, useCallback } from 'react' import { useDragRegion } from '../hooks/useDragRegion' import type { VaultEntry, GitCommit } from '../types' import { cn } from '@/lib/utils' import { SlidersHorizontal, X } from '@phosphor-icons/react' import { parseFrontmatter } from '../utils/frontmatter' import { DynamicPropertiesPanel } from './DynamicPropertiesPanel' import { DynamicRelationshipsPanel, BacklinksPanel, ReferencedByPanel, GitHistoryPanel } from './InspectorPanels' import { wikilinkTarget } from '../utils/wikilink' import { extractBacklinkContext } from '../utils/wikilinks' import type { ReferencedByItem, BacklinkItem } from './InspectorPanels' export type FrontmatterValue = string | number | boolean | string[] | null interface InspectorProps { collapsed: boolean onToggle: () => void entry: VaultEntry | null content: string | null entries: VaultEntry[] allContent?: Record gitHistory: GitCommit[] onNavigate: (target: string) => void onViewCommitDiff?: (commitHash: string) => void onUpdateFrontmatter?: (path: string, key: string, value: FrontmatterValue) => Promise onDeleteProperty?: (path: string, key: string) => Promise onAddProperty?: (path: string, key: string, value: FrontmatterValue) => Promise } function useBacklinks( entry: VaultEntry | null, entries: VaultEntry[], referencedBy: ReferencedByItem[], allContent?: Record, ): BacklinkItem[] { return useMemo(() => { if (!entry) return [] const matchTargets = new Set([ entry.title, ...entry.aliases, entry.filename.replace(/\.md$/, ''), entry.path.replace(/^.*\/Laputa\//, '').replace(/\.md$/, ''), ]) const referencedByPaths = new Set(referencedBy.map((item) => item.entry.path)) return entries .filter((e) => { if (e.path === entry.path) return false if (referencedByPaths.has(e.path)) return false return e.outgoingLinks.some((target) => matchTargets.has(target) || matchTargets.has(target.split('/').pop() ?? '') ) }) .map((e) => ({ entry: e, context: allContent?.[e.path] ? extractBacklinkContext(allContent[e.path], matchTargets) : null, })) }, [entry, entries, referencedBy, allContent]) } function refsMatchTargets(refs: string[], targets: Set): boolean { return refs.some((ref) => { const target = wikilinkTarget(ref) return targets.has(target) || targets.has(target.split('/').pop() ?? '') }) } function useReferencedBy(entry: VaultEntry | null, entries: VaultEntry[]): ReferencedByItem[] { return useMemo(() => { if (!entry) return [] const pathStem = entry.path.replace(/^.*\/Laputa\//, '').replace(/\.md$/, '') const filenameStem = entry.filename.replace(/\.md$/, '') const matchTargets = new Set([pathStem, filenameStem, entry.title, ...entry.aliases]) const results: ReferencedByItem[] = [] for (const other of entries) { if (other.path === entry.path) continue for (const [key, refs] of Object.entries(other.relationships)) { if (key !== 'Type' && refsMatchTargets(refs, matchTargets)) { results.push({ entry: other, viaKey: key }) } } } return results }, [entry, entries]) } function InspectorHeader({ collapsed, onToggle }: { collapsed: boolean; onToggle: () => void }) { const { onMouseDown } = useDragRegion() return (
{collapsed ? ( ) : ( <> Properties )}
) } function EmptyInspector() { return (

No note selected

) } export function Inspector({ collapsed, onToggle, entry, content, entries, allContent, gitHistory, onNavigate, onViewCommitDiff, onUpdateFrontmatter, onDeleteProperty, onAddProperty, }: InspectorProps) { const referencedBy = useReferencedBy(entry, entries) const backlinks = useBacklinks(entry, entries, referencedBy, allContent) const frontmatter = useMemo(() => parseFrontmatter(content), [content]) const typeEntryMap = useMemo(() => { const map: Record = {} for (const e of entries) { if (e.isA === 'Type') map[e.title] = e } return map }, [entries]) const handleUpdateProperty = useCallback((key: string, value: FrontmatterValue) => { if (entry && onUpdateFrontmatter) onUpdateFrontmatter(entry.path, key, value) }, [entry, onUpdateFrontmatter]) const handleDeleteProperty = useCallback((key: string) => { if (entry && onDeleteProperty) onDeleteProperty(entry.path, key) }, [entry, onDeleteProperty]) const handleAddProperty = useCallback((key: string, value: FrontmatterValue) => { if (entry && onAddProperty) onAddProperty(entry.path, key, value) }, [entry, onAddProperty]) return ( ) }