refactor: extract frontmatterOps, update imports for useNoteCreation/useNoteRename split
useNoteActions.ts reduced from 213 to 125 lines by extracting frontmatter helpers into frontmatterOps.ts and removing re-exports. Consumers now import directly from the extracted modules. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,8 @@ import { WelcomeScreen } from './components/WelcomeScreen'
|
||||
import { useMcpStatus } from './hooks/useMcpStatus'
|
||||
import { useVaultLoader } from './hooks/useVaultLoader'
|
||||
import { useSettings } from './hooks/useSettings'
|
||||
import { useNoteActions, needsRenameOnSave } from './hooks/useNoteActions'
|
||||
import { useNoteActions } from './hooks/useNoteActions'
|
||||
import { needsRenameOnSave } from './hooks/useNoteRename'
|
||||
import { useCommitFlow } from './hooks/useCommitFlow'
|
||||
import { useViewMode } from './hooks/useViewMode'
|
||||
import { useEntryActions } from './hooks/useEntryActions'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useCallback, useRef, useEffect } from 'react'
|
||||
import { slugify } from '../hooks/useNoteActions'
|
||||
import { slugify } from '../hooks/useNoteCreation'
|
||||
|
||||
interface TitleFieldProps {
|
||||
title: string
|
||||
|
||||
77
src/hooks/frontmatterOps.ts
Normal file
77
src/hooks/frontmatterOps.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { isTauri } from '../mock-tauri'
|
||||
import type { VaultEntry } from '../types'
|
||||
import type { FrontmatterValue } from '../components/Inspector'
|
||||
import { updateMockFrontmatter, deleteMockFrontmatterProperty } from './mockFrontmatterHelpers'
|
||||
import { updateMockContent, trackMockChange } from '../mock-tauri'
|
||||
|
||||
const ENTRY_DELETE_MAP: Record<string, Partial<VaultEntry>> = {
|
||||
type: { isA: null }, is_a: { isA: null }, status: { status: null }, color: { color: null },
|
||||
icon: { icon: null }, owner: { owner: null }, cadence: { cadence: null },
|
||||
aliases: { aliases: [] }, belongs_to: { belongsTo: [] }, related_to: { relatedTo: [] },
|
||||
archived: { archived: false }, trashed: { trashed: false }, order: { order: null },
|
||||
template: { template: null }, sort: { sort: null }, visible: { visible: null },
|
||||
}
|
||||
|
||||
/** Map a frontmatter key+value to the corresponding VaultEntry field(s). */
|
||||
export function frontmatterToEntryPatch(
|
||||
op: 'update' | 'delete', key: string, value?: FrontmatterValue,
|
||||
): Partial<VaultEntry> {
|
||||
const k = key.toLowerCase().replace(/\s+/g, '_')
|
||||
if (op === 'delete') return ENTRY_DELETE_MAP[k] ?? {}
|
||||
const str = value != null ? String(value) : null
|
||||
const arr = Array.isArray(value) ? value.map(String) : []
|
||||
const updates: Record<string, Partial<VaultEntry>> = {
|
||||
type: { isA: str }, is_a: { isA: str }, status: { status: str }, color: { color: str },
|
||||
icon: { icon: str }, owner: { owner: str }, cadence: { cadence: str },
|
||||
aliases: { aliases: arr }, belongs_to: { belongsTo: arr }, related_to: { relatedTo: arr },
|
||||
archived: { archived: Boolean(value) }, trashed: { trashed: Boolean(value) },
|
||||
order: { order: typeof value === 'number' ? value : null },
|
||||
template: { template: str },
|
||||
sort: { sort: str },
|
||||
view: { view: str },
|
||||
visible: { visible: value === false ? false : null },
|
||||
}
|
||||
return updates[k] ?? {}
|
||||
}
|
||||
|
||||
async function invokeFrontmatter(command: string, args: Record<string, unknown>): Promise<string> {
|
||||
return invoke<string>(command, args)
|
||||
}
|
||||
|
||||
function applyMockFrontmatterUpdate(path: string, key: string, value: FrontmatterValue): string {
|
||||
const content = updateMockFrontmatter(path, key, value)
|
||||
updateMockContent(path, content)
|
||||
trackMockChange(path)
|
||||
return content
|
||||
}
|
||||
|
||||
function applyMockFrontmatterDelete(path: string, key: string): string {
|
||||
const content = deleteMockFrontmatterProperty(path, key)
|
||||
updateMockContent(path, content)
|
||||
trackMockChange(path)
|
||||
return content
|
||||
}
|
||||
|
||||
async function executeFrontmatterOp(op: 'update' | 'delete', path: string, key: string, value?: FrontmatterValue): Promise<string> {
|
||||
if (op === 'update') {
|
||||
return isTauri() ? invokeFrontmatter('update_frontmatter', { path, key, value }) : applyMockFrontmatterUpdate(path, key, value!)
|
||||
}
|
||||
return isTauri() ? invokeFrontmatter('delete_frontmatter_property', { path, key }) : applyMockFrontmatterDelete(path, key)
|
||||
}
|
||||
|
||||
/** Run a frontmatter update/delete and apply the result to state. */
|
||||
export async function runFrontmatterAndApply(
|
||||
op: 'update' | 'delete', path: string, key: string, value: FrontmatterValue | undefined,
|
||||
callbacks: { updateTab: (p: string, c: string) => void; updateEntry: (p: string, patch: Partial<VaultEntry>) => void; toast: (m: string | null) => void },
|
||||
): Promise<void> {
|
||||
try {
|
||||
callbacks.updateTab(path, await executeFrontmatterOp(op, path, key, value))
|
||||
const patch = frontmatterToEntryPatch(op, key, value)
|
||||
if (Object.keys(patch).length > 0) callbacks.updateEntry(path, patch)
|
||||
callbacks.toast(op === 'update' ? 'Property updated' : 'Property deleted')
|
||||
} catch (err) {
|
||||
console.error(`Failed to ${op} frontmatter:`, err)
|
||||
callbacks.toast(`Failed to ${op} property`)
|
||||
}
|
||||
}
|
||||
@@ -5,22 +5,22 @@ import { isTauri, mockInvoke } from '../mock-tauri'
|
||||
import type { VaultEntry } from '../types'
|
||||
import {
|
||||
slugify,
|
||||
needsRenameOnSave,
|
||||
buildNewEntry,
|
||||
generateUntitledName,
|
||||
entryMatchesTarget,
|
||||
buildNoteContent,
|
||||
resolveNewNote,
|
||||
resolveNewType,
|
||||
frontmatterToEntryPatch,
|
||||
todayDateString,
|
||||
buildDailyNoteContent,
|
||||
resolveDailyNote,
|
||||
findDailyNote,
|
||||
useNoteActions,
|
||||
DEFAULT_TEMPLATES,
|
||||
resolveTemplate,
|
||||
} from './useNoteActions'
|
||||
} from './useNoteCreation'
|
||||
import { needsRenameOnSave } from './useNoteRename'
|
||||
import { frontmatterToEntryPatch } from './frontmatterOps'
|
||||
import { useNoteActions } from './useNoteActions'
|
||||
import type { NoteActionsConfig } from './useNoteActions'
|
||||
|
||||
// Mock dependencies
|
||||
|
||||
@@ -1,24 +1,14 @@
|
||||
import { useCallback } from 'react'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { isTauri } from '../mock-tauri'
|
||||
import type { VaultEntry } from '../types'
|
||||
import type { FrontmatterValue } from '../components/Inspector'
|
||||
import { useTabManagement } from './useTabManagement'
|
||||
import { updateMockFrontmatter, deleteMockFrontmatterProperty } from './mockFrontmatterHelpers'
|
||||
import { updateMockContent, trackMockChange } from '../mock-tauri'
|
||||
import { resolveEntry } from '../utils/wikilink'
|
||||
import { useNoteCreation } from './useNoteCreation'
|
||||
import {
|
||||
useNoteRename,
|
||||
performRename, loadNoteContent, renameToastMessage, reloadTabsAfterRename,
|
||||
} from './useNoteRename'
|
||||
|
||||
// Re-export pure functions so existing consumers don't need to change imports.
|
||||
export { buildNewEntry, slugify, generateUntitledName, entryMatchesTarget } from './useNoteCreation'
|
||||
export { buildNoteContent, resolveNewNote, resolveNewType, resolveTemplate, DEFAULT_TEMPLATES } from './useNoteCreation'
|
||||
export { todayDateString, buildDailyNoteContent, resolveDailyNote, findDailyNote } from './useNoteCreation'
|
||||
export { needsRenameOnSave } from './useNoteRename'
|
||||
export type { NewEntryParams } from './useNoteCreation'
|
||||
import { runFrontmatterAndApply } from './frontmatterOps'
|
||||
|
||||
export interface NoteActionsConfig {
|
||||
addEntry: (entry: VaultEntry) => void
|
||||
@@ -37,82 +27,10 @@ export interface NoteActionsConfig {
|
||||
replaceEntry?: (oldPath: string, patch: Partial<VaultEntry> & { path: string }) => void
|
||||
}
|
||||
|
||||
/** Check if a frontmatter key represents the note title. */
|
||||
function isTitleKey(key: string): boolean {
|
||||
return key.toLowerCase().replace(/\s+/g, '_') === 'title'
|
||||
}
|
||||
|
||||
const ENTRY_DELETE_MAP: Record<string, Partial<VaultEntry>> = {
|
||||
type: { isA: null }, is_a: { isA: null }, status: { status: null }, color: { color: null },
|
||||
icon: { icon: null }, owner: { owner: null }, cadence: { cadence: null },
|
||||
aliases: { aliases: [] }, belongs_to: { belongsTo: [] }, related_to: { relatedTo: [] },
|
||||
archived: { archived: false }, trashed: { trashed: false }, order: { order: null },
|
||||
template: { template: null }, sort: { sort: null }, visible: { visible: null },
|
||||
}
|
||||
|
||||
/** Map a frontmatter key+value to the corresponding VaultEntry field(s). */
|
||||
export function frontmatterToEntryPatch(
|
||||
op: 'update' | 'delete', key: string, value?: FrontmatterValue,
|
||||
): Partial<VaultEntry> {
|
||||
const k = key.toLowerCase().replace(/\s+/g, '_')
|
||||
if (op === 'delete') return ENTRY_DELETE_MAP[k] ?? {}
|
||||
const str = value != null ? String(value) : null
|
||||
const arr = Array.isArray(value) ? value.map(String) : []
|
||||
const updates: Record<string, Partial<VaultEntry>> = {
|
||||
type: { isA: str }, is_a: { isA: str }, status: { status: str }, color: { color: str },
|
||||
icon: { icon: str }, owner: { owner: str }, cadence: { cadence: str },
|
||||
aliases: { aliases: arr }, belongs_to: { belongsTo: arr }, related_to: { relatedTo: arr },
|
||||
archived: { archived: Boolean(value) }, trashed: { trashed: Boolean(value) },
|
||||
order: { order: typeof value === 'number' ? value : null },
|
||||
template: { template: str },
|
||||
sort: { sort: str },
|
||||
view: { view: str },
|
||||
visible: { visible: value === false ? false : null },
|
||||
}
|
||||
return updates[k] ?? {}
|
||||
}
|
||||
|
||||
async function invokeFrontmatter(command: string, args: Record<string, unknown>): Promise<string> {
|
||||
return invoke<string>(command, args)
|
||||
}
|
||||
|
||||
function applyMockFrontmatterUpdate(path: string, key: string, value: FrontmatterValue): string {
|
||||
const content = updateMockFrontmatter(path, key, value)
|
||||
updateMockContent(path, content)
|
||||
trackMockChange(path)
|
||||
return content
|
||||
}
|
||||
|
||||
function applyMockFrontmatterDelete(path: string, key: string): string {
|
||||
const content = deleteMockFrontmatterProperty(path, key)
|
||||
updateMockContent(path, content)
|
||||
trackMockChange(path)
|
||||
return content
|
||||
}
|
||||
|
||||
async function executeFrontmatterOp(op: 'update' | 'delete', path: string, key: string, value?: FrontmatterValue): Promise<string> {
|
||||
if (op === 'update') {
|
||||
return isTauri() ? invokeFrontmatter('update_frontmatter', { path, key, value }) : applyMockFrontmatterUpdate(path, key, value!)
|
||||
}
|
||||
return isTauri() ? invokeFrontmatter('delete_frontmatter_property', { path, key }) : applyMockFrontmatterDelete(path, key)
|
||||
}
|
||||
|
||||
/** Run a frontmatter update/delete and apply the result to state. */
|
||||
async function runFrontmatterAndApply(
|
||||
op: 'update' | 'delete', path: string, key: string, value: FrontmatterValue | undefined,
|
||||
callbacks: { updateTab: (p: string, c: string) => void; updateEntry: (p: string, patch: Partial<VaultEntry>) => void; toast: (m: string | null) => void },
|
||||
): Promise<void> {
|
||||
try {
|
||||
callbacks.updateTab(path, await executeFrontmatterOp(op, path, key, value))
|
||||
const patch = frontmatterToEntryPatch(op, key, value)
|
||||
if (Object.keys(patch).length > 0) callbacks.updateEntry(path, patch)
|
||||
callbacks.toast(op === 'update' ? 'Property updated' : 'Property deleted')
|
||||
} catch (err) {
|
||||
console.error(`Failed to ${op} frontmatter:`, err)
|
||||
callbacks.toast(`Failed to ${op} property`)
|
||||
}
|
||||
}
|
||||
|
||||
interface TitleRenameDeps {
|
||||
vaultPath: string
|
||||
tabsRef: React.MutableRefObject<{ entry: VaultEntry; content: string }[]>
|
||||
@@ -124,7 +42,6 @@ interface TitleRenameDeps {
|
||||
updateTabContent: (path: string, content: string) => void
|
||||
}
|
||||
|
||||
/** After a frontmatter title change, rename the file and update all tabs. */
|
||||
async function renameAfterTitleChange(path: string, newTitle: string, deps: TitleRenameDeps): Promise<void> {
|
||||
const oldTitle = deps.tabsRef.current.find(t => t.entry.path === path)?.entry.title
|
||||
const result = await performRename(path, newTitle, deps.vaultPath, oldTitle)
|
||||
@@ -146,13 +63,8 @@ function shouldRenameOnTitleUpdate(key: string, value: FrontmatterValue): value
|
||||
return isTitleKey(key) && typeof value === 'string' && value !== ''
|
||||
}
|
||||
|
||||
function findWikilinkTarget(entries: VaultEntry[], target: string): VaultEntry | undefined {
|
||||
return resolveEntry(entries, target)
|
||||
}
|
||||
|
||||
/** Navigate to a wikilink target, logging a warning if not found. */
|
||||
function navigateWikilink(entries: VaultEntry[], target: string, selectNote: (e: VaultEntry) => void): void {
|
||||
const found = findWikilinkTarget(entries, target)
|
||||
const found = resolveEntry(entries, target)
|
||||
if (found) selectNote(found)
|
||||
else console.warn(`Navigation target not found: ${target}`)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user