Files
tolaria/src/hooks/useDeleteActions.ts
Test 3172425a16 feat: implement PostHog event tracking plan
Add trackEvent() calls for all user actions in the tracking plan:
- Vault: vault_opened (with has_git, note_count), vault_switched
- Notes: note_created (with has_type, creation_path), note_deleted,
  note_trashed, note_archived, note_favorited, note_unfavorited
- Git: commit_made, sync_triggered
- Search: search_used
- Editor: raw_mode_toggled, wikilink_inserted
- Types & Views: type_created, view_created
- Telemetry: telemetry_opted_in, telemetry_opted_out

All events gated by PostHog initialization (only fires when opted in).
No PII in any event properties — counts, booleans, and enums only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 20:42:43 +02:00

111 lines
3.5 KiB
TypeScript

import { useCallback, useMemo, useState } from 'react'
import { invoke } from '@tauri-apps/api/core'
import { isTauri, mockInvoke } from '../mock-tauri'
import type { VaultEntry } from '../types'
import { trackEvent } from '../lib/telemetry'
interface ConfirmDeleteState {
title: string
message: string
confirmLabel?: string
onConfirm: () => void
}
interface UseDeleteActionsInput {
vaultPath: string
entries: VaultEntry[]
/** Called to deselect the note if it is currently open. */
onDeselectNote: (path: string) => void
removeEntry: (path: string) => void
setToastMessage: (msg: string | null) => void
}
export function useDeleteActions({
vaultPath,
entries,
onDeselectNote,
removeEntry,
setToastMessage,
}: UseDeleteActionsInput) {
const [confirmDelete, setConfirmDelete] = useState<ConfirmDeleteState | null>(null)
const trashedCount = useMemo(() => entries.filter(e => e.trashed).length, [entries])
const deleteNoteFromDisk = useCallback(async (path: string) => {
try {
if (isTauri()) await invoke('delete_note', { path })
else await mockInvoke('delete_note', { path })
onDeselectNote(path)
removeEntry(path)
return true
} catch (e) {
setToastMessage(`Failed to delete note: ${e}`)
return false
}
}, [onDeselectNote, removeEntry, setToastMessage])
const handleDeleteNote = useCallback(async (path: string) => {
setConfirmDelete({
title: 'Delete permanently?',
message: 'This note will be permanently deleted. This cannot be undone.',
onConfirm: async () => {
setConfirmDelete(null)
const ok = await deleteNoteFromDisk(path)
if (ok) {
trackEvent('note_deleted')
setToastMessage('Note permanently deleted')
}
},
})
}, [deleteNoteFromDisk, setToastMessage])
const handleBulkDeletePermanently = useCallback((paths: string[]) => {
const count = paths.length
setConfirmDelete({
title: `Delete ${count} ${count === 1 ? 'note' : 'notes'} permanently?`,
message: `${count === 1 ? 'This note' : `These ${count} notes`} will be permanently deleted. This cannot be undone.`,
onConfirm: async () => {
setConfirmDelete(null)
let ok = 0
for (const path of paths) {
if (await deleteNoteFromDisk(path)) ok++
}
if (ok > 0) setToastMessage(`${ok} note${ok > 1 ? 's' : ''} permanently deleted`)
},
})
}, [deleteNoteFromDisk, setToastMessage])
const handleEmptyTrash = useCallback(() => {
if (trashedCount === 0) return
setConfirmDelete({
title: 'Empty Trash?',
message: `Permanently delete all ${trashedCount} trashed ${trashedCount === 1 ? 'note' : 'notes'}? This cannot be undone.`,
confirmLabel: 'Empty Trash',
onConfirm: async () => {
setConfirmDelete(null)
try {
const tauriInvoke = isTauri() ? invoke : mockInvoke
const deleted = await tauriInvoke<string[]>('empty_trash', { vaultPath })
for (const path of deleted) {
onDeselectNote(path)
removeEntry(path)
}
setToastMessage(`${deleted.length} note${deleted.length !== 1 ? 's' : ''} permanently deleted`)
} catch (e) {
setToastMessage(`Failed to empty trash: ${e}`)
}
},
})
}, [trashedCount, vaultPath, onDeselectNote, removeEntry, setToastMessage])
return {
confirmDelete,
setConfirmDelete,
trashedCount,
deleteNoteFromDisk,
handleDeleteNote,
handleBulkDeletePermanently,
handleEmptyTrash,
}
}