import { useMemo, useCallback, useEffect, useState } from 'react' import type { VaultEntry } from '../types' import type { FrontmatterValue } from './Inspector' import type { ParsedFrontmatter } from '../utils/frontmatter' import { usePropertyPanelState } from '../hooks/usePropertyPanelState' import { getEffectiveDisplayMode, detectPropertyType } from '../utils/propertyTypes' import { SmartPropertyValueCell, DisplayModeSelector } from './PropertyValueCells' import { TypeSelector } from './TypeSelector' import { AddPropertyForm } from './AddPropertyForm' import type { PropertyDisplayMode } from '../utils/propertyTypes' import { FOCUS_NOTE_ICON_PROPERTY_EVENT } from './noteIconPropertyEvents' function toSentenceCase(key: string): string { const spaced = key.replace(/[_-]/g, ' ') if (!spaced) return spaced return spaced.charAt(0).toUpperCase() + spaced.slice(1) } // eslint-disable-next-line react-refresh/only-export-components -- utility co-located with component export function containsWikilinks(value: FrontmatterValue): boolean { if (typeof value === 'string') return /^\[\[.*\]\]$/.test(value) if (Array.isArray(value)) return value.some(v => typeof v === 'string' && /^\[\[.*\]\]$/.test(v)) return false } function PropertyRow({ propKey, value, editingKey, displayMode, autoMode, vaultStatuses, vaultTags, onStartEdit, onSave, onSaveList, onUpdate, onDelete, onDisplayModeChange }: { propKey: string; value: FrontmatterValue; editingKey: string | null displayMode: PropertyDisplayMode; autoMode: PropertyDisplayMode vaultStatuses: string[]; vaultTags: string[] onStartEdit: (key: string | null) => void; onSave: (key: string, value: string) => void onSaveList: (key: string, items: string[]) => void onUpdate?: (key: string, value: FrontmatterValue) => void; onDelete?: (key: string) => void onDisplayModeChange: (key: string, mode: PropertyDisplayMode | null) => void }) { const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && editingKey !== propKey) { e.preventDefault() onStartEdit(propKey) } } return (
{toSentenceCase(propKey)} {onDelete && ( )}
) } function AddPropertyButton({ onClick, disabled }: { onClick: () => void; disabled: boolean }) { return ( ) } const SUGGESTED_PROPERTIES = [ { key: 'Status', label: 'Status' }, { key: 'Date', label: 'Date' }, { key: 'URL', label: 'URL' }, { key: 'icon', label: 'Icon' }, ] as const const SUGGESTED_PROPERTY_MODES: Record = { Status: 'status', Date: 'date', URL: 'url', icon: 'text', } function getSuggestedDisplayMode(key: string): PropertyDisplayMode { return SUGGESTED_PROPERTY_MODES[key] ?? 'text' } function SuggestedPropertySlot({ label, onAdd }: { label: string; onAdd: () => void }) { return ( ) } export function DynamicPropertiesPanel({ entry, frontmatter, entries, onUpdateProperty, onDeleteProperty, onAddProperty, onNavigate, }: { entry: VaultEntry content?: string | null frontmatter: ParsedFrontmatter entries?: VaultEntry[] onUpdateProperty?: (key: string, value: FrontmatterValue) => void onDeleteProperty?: (key: string) => void onAddProperty?: (key: string, value: FrontmatterValue) => void onNavigate?: (target: string) => void }) { const { editingKey, setEditingKey, showAddDialog, setShowAddDialog, displayOverrides, availableTypes, customColorKey, typeColorKeys, typeIconKeys, vaultStatuses, vaultTagsByKey, propertyEntries, handleSaveValue, handleSaveList, handleAdd, handleDisplayModeChange, } = usePropertyPanelState({ entries, entryIsA: entry.isA, frontmatter, onUpdateProperty, onDeleteProperty, onAddProperty }) const [pendingSuggestedKey, setPendingSuggestedKey] = useState(null) const existingKeys = useMemo(() => { const keys = new Set(propertyEntries.map(([k]) => k.toLowerCase())) // Also check full frontmatter for relationship keys that are filtered out of propertyEntries for (const k of Object.keys(frontmatter)) keys.add(k.toLowerCase()) return keys }, [propertyEntries, frontmatter]) const missingSuggested = onAddProperty ? SUGGESTED_PROPERTIES.filter( p => !existingKeys.has(p.key.toLowerCase()) && p.key !== pendingSuggestedKey, ) : [] const handleSuggestedAdd = useCallback((key: string) => { if (!onAddProperty) return setPendingSuggestedKey(key) setEditingKey(key) }, [onAddProperty, setEditingKey]) const handlePendingSuggestedEdit = useCallback((key: string | null) => { setEditingKey(key) if (key === null) setPendingSuggestedKey(null) }, [setEditingKey]) const handleSaveSuggestedValue = useCallback((key: string, newValue: string) => { setEditingKey(null) setPendingSuggestedKey(null) if (!onAddProperty) { return } const trimmed = newValue.trim() if (!trimmed) { return } onAddProperty(key, trimmed) }, [onAddProperty, setEditingKey]) useEffect(() => { const handleFocusNoteIcon = () => { const existingIconKey = propertyEntries.find(([key]) => key.toLowerCase() === 'icon')?.[0] if (!existingIconKey) { if (!onAddProperty) return onAddProperty('icon', '') } setEditingKey(existingIconKey ?? 'icon') } window.addEventListener(FOCUS_NOTE_ICON_PROPERTY_EVENT, handleFocusNoteIcon) return () => window.removeEventListener(FOCUS_NOTE_ICON_PROPERTY_EVENT, handleFocusNoteIcon) }, [onAddProperty, propertyEntries, setEditingKey]) return (
{propertyEntries.map(([key, value]) => ( ))} {pendingSuggestedKey && editingKey === pendingSuggestedKey && ( )} {missingSuggested.map(({ key, label }) => ( handleSuggestedAdd(key)} /> ))}
{showAddDialog ? setShowAddDialog(false)} vaultStatuses={vaultStatuses} /> : setShowAddDialog(true)} disabled={!onAddProperty} /> }
) }