fix: propagate frontmatter changes from raw editor to vault entries
When editing type/status/color/icon in the raw editor (Cmd+\), changes now immediately flow into the reactive VaultEntry state, updating the Properties panel, note list, and sidebar without save/reload. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import type { VaultEntry } from '../types'
|
||||
import type { FrontmatterValue } from '../components/Inspector'
|
||||
import { updateMockFrontmatter, deleteMockFrontmatterProperty } from './mockFrontmatterHelpers'
|
||||
import { updateMockContent, trackMockChange } from '../mock-tauri'
|
||||
import { parseFrontmatter } from '../utils/frontmatter'
|
||||
|
||||
const ENTRY_DELETE_MAP: Record<string, Partial<VaultEntry>> = {
|
||||
type: { isA: null }, is_a: { isA: null }, status: { status: null }, color: { color: null },
|
||||
@@ -65,6 +66,17 @@ export function frontmatterToEntryPatch(
|
||||
return { patch: updates[k] ?? {}, relationshipPatch }
|
||||
}
|
||||
|
||||
/** Parse frontmatter from full content and return a merged VaultEntry patch for all known fields. */
|
||||
export function contentToEntryPatch(content: string): Partial<VaultEntry> {
|
||||
const fm = parseFrontmatter(content)
|
||||
const merged: Partial<VaultEntry> = {}
|
||||
for (const [key, value] of Object.entries(fm)) {
|
||||
const { patch } = frontmatterToEntryPatch('update', key, value)
|
||||
Object.assign(merged, patch)
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
async function invokeFrontmatter(command: string, args: Record<string, unknown>): Promise<string> {
|
||||
return invoke<string>(command, args)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useRef } from 'react'
|
||||
import { useEditorSave } from './useEditorSave'
|
||||
import { extractOutgoingLinks, extractSnippet, countWords } from '../utils/wikilinks'
|
||||
import { contentToEntryPatch } from './frontmatterOps'
|
||||
import type { VaultEntry } from '../types'
|
||||
|
||||
export function useEditorSaveWithLinks(config: {
|
||||
@@ -21,6 +22,7 @@ export function useEditorSaveWithLinks(config: {
|
||||
const editor = useEditorSave({ updateVaultContent: saveContent, setTabs: config.setTabs, setToastMessage: config.setToastMessage, onAfterSave: config.onAfterSave, onNotePersisted: config.onNotePersisted })
|
||||
const { handleContentChange: rawOnChange } = editor
|
||||
const prevLinksKeyRef = useRef('')
|
||||
const prevFmKeyRef = useRef('')
|
||||
const handleContentChange = useCallback((path: string, content: string) => {
|
||||
rawOnChange(path, content)
|
||||
const links = extractOutgoingLinks(content)
|
||||
@@ -29,6 +31,12 @@ export function useEditorSaveWithLinks(config: {
|
||||
prevLinksKeyRef.current = key
|
||||
updateEntry(path, { outgoingLinks: links })
|
||||
}
|
||||
const fmPatch = contentToEntryPatch(content)
|
||||
const fmKey = JSON.stringify(fmPatch)
|
||||
if (fmKey !== prevFmKeyRef.current) {
|
||||
prevFmKeyRef.current = fmKey
|
||||
if (Object.keys(fmPatch).length > 0) updateEntry(path, fmPatch)
|
||||
}
|
||||
}, [rawOnChange, updateEntry])
|
||||
return { ...editor, handleContentChange }
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
resolveTemplate,
|
||||
} from './useNoteCreation'
|
||||
import { needsRenameOnSave } from './useNoteRename'
|
||||
import { frontmatterToEntryPatch, applyRelationshipPatch } from './frontmatterOps'
|
||||
import { frontmatterToEntryPatch, applyRelationshipPatch, contentToEntryPatch } from './frontmatterOps'
|
||||
import { useNoteActions } from './useNoteActions'
|
||||
import type { NoteActionsConfig } from './useNoteActions'
|
||||
|
||||
@@ -440,6 +440,36 @@ describe('applyRelationshipPatch', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('contentToEntryPatch', () => {
|
||||
it('extracts type from frontmatter', () => {
|
||||
const content = '---\ntype: Project\nstatus: Active\n---\nBody text'
|
||||
expect(contentToEntryPatch(content)).toEqual({ isA: 'Project', status: 'Active' })
|
||||
})
|
||||
|
||||
it('returns empty patch when no frontmatter', () => {
|
||||
expect(contentToEntryPatch('Just a body')).toEqual({})
|
||||
})
|
||||
|
||||
it('returns empty patch for empty content', () => {
|
||||
expect(contentToEntryPatch('')).toEqual({})
|
||||
})
|
||||
|
||||
it('extracts color, icon, and aliases', () => {
|
||||
const content = '---\ncolor: red\nicon: star\naliases:\n - Foo\n - Bar\n---\n'
|
||||
expect(contentToEntryPatch(content)).toEqual({ color: 'red', icon: 'star', aliases: ['Foo', 'Bar'] })
|
||||
})
|
||||
|
||||
it('handles is_a as alias for type', () => {
|
||||
const content = '---\nis_a: Essay\n---\n'
|
||||
expect(contentToEntryPatch(content)).toEqual({ isA: 'Essay' })
|
||||
})
|
||||
|
||||
it('ignores unknown frontmatter keys', () => {
|
||||
const content = '---\ntype: Note\ncustom: value\n---\n'
|
||||
expect(contentToEntryPatch(content)).toEqual({ isA: 'Note' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('todayDateString', () => {
|
||||
it('returns date in YYYY-MM-DD format', () => {
|
||||
const result = todayDateString()
|
||||
|
||||
Reference in New Issue
Block a user