feat: pinned properties — inline bar in editor + values in note list

- PinnedPropertiesBar: horizontal bar below title with icon + label +
  editable value chips, overflow popover for hidden properties
- PinnedPropertyChip: inline-editable chip with status/relationship colors
- NoteListPinnedValues: compact value-only chips under note titles
- Pin/unpin context menu (right-click) in Properties panel with highlight
- Real-time sync: _pinned_properties changes propagate via frontmatterToEntryPatch
- Default pinned properties (status, belongs_to, related_to) for types without config
- Per-type config stored in type definition frontmatter as _pinned_properties

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
lucaronin
2026-03-25 10:18:11 +01:00
parent caaa582ddd
commit d263e43025
11 changed files with 594 additions and 8 deletions

View File

@@ -1,7 +1,8 @@
import type React from 'react'
import { useCallback } from 'react'
import { useCallback, useMemo } from 'react'
import type { VaultEntry, NoteStatus } from '../types'
import type { useCreateBlockNote } from '@blocknote/react'
import type { FrontmatterValue } from './Inspector'
import { DiffView } from './DiffView'
import { BreadcrumbBar } from './BreadcrumbBar'
import { TitleField } from './TitleField'
@@ -13,6 +14,8 @@ import { RawEditorView } from './RawEditorView'
import { countWords } from '../utils/wikilinks'
import { SingleEditorView } from './SingleEditorView'
import { isEmoji } from '../utils/emoji'
import { parseFrontmatter } from '../utils/frontmatter'
import { PinnedPropertiesBar } from './PinnedPropertiesBar'
interface Tab {
entry: VaultEntry
@@ -60,6 +63,7 @@ interface EditorContentProps {
onKeepMine?: (path: string) => void
/** Resolve conflict by keeping the remote version. */
onKeepTheirs?: (path: string) => void
onUpdateFrontmatter?: (path: string, key: string, value: FrontmatterValue) => Promise<void>
}
function EditorLoadingSkeleton() {
@@ -156,6 +160,7 @@ export function EditorContent({
onDeleteNote, rawLatestContentRef, onTitleChange,
onSetNoteIcon, onRemoveNoteIcon,
isConflicted, onKeepMine, onKeepTheirs,
onUpdateFrontmatter,
...breadcrumbProps
}: EditorContentProps) {
// Look up trashed/archived from the latest vault entries, not the tab snapshot,
@@ -175,6 +180,12 @@ export function EditorContent({
if (activeTab) onRemoveNoteIcon?.(activeTab.entry.path)
}, [activeTab, onRemoveNoteIcon])
const frontmatter = useMemo(
() => parseFrontmatter(activeTab?.content ?? null),
[activeTab?.content],
)
const currentEntry = freshEntry ?? activeTab?.entry ?? null
return (
<div className="flex flex-1 flex-col min-w-0 min-h-0">
{activeTab && (
@@ -215,6 +226,15 @@ export function EditorContent({
editable={!isTrashed}
onTitleChange={(newTitle) => onTitleChange?.(activeTab.entry.path, newTitle)}
/>
{currentEntry && (
<PinnedPropertiesBar
entry={currentEntry}
entries={entries}
frontmatter={frontmatter}
onUpdateFrontmatter={onUpdateFrontmatter}
onNavigate={onNavigateWikilink}
/>
)}
<div className="title-section__separator" />
</div>
<SingleEditorView editor={editor} entries={entries} onNavigateWikilink={onNavigateWikilink} onChange={onEditorChange} vaultPath={vaultPath} editable={!isTrashed} />