diff --git a/src/hooks/frontmatterOps.ts b/src/hooks/frontmatterOps.ts index c80dca9c..deef37f7 100644 --- a/src/hooks/frontmatterOps.ts +++ b/src/hooks/frontmatterOps.ts @@ -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> = { 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 { + const fm = parseFrontmatter(content) + const merged: Partial = {} + 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): Promise { return invoke(command, args) } diff --git a/src/hooks/useEditorSaveWithLinks.ts b/src/hooks/useEditorSaveWithLinks.ts index d4bb2dad..6f2084db 100644 --- a/src/hooks/useEditorSaveWithLinks.ts +++ b/src/hooks/useEditorSaveWithLinks.ts @@ -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 } } diff --git a/src/hooks/useNoteActions.test.ts b/src/hooks/useNoteActions.test.ts index 8061c588..de7d50ad 100644 --- a/src/hooks/useNoteActions.test.ts +++ b/src/hooks/useNoteActions.test.ts @@ -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()