Files
tolaria/src/components/BreadcrumbBar.tsx
Test e581ad3691 refactor: remove Trash system — delete is now permanent with confirm modal
Remove all vestiges of the abandoned Trash system: trashed/trashedAt fields
from types, frontmatter parsing, sidebar filtering, editor banners, inspector
components, mock data, and all related tests. Delete is already permanent via
useDeleteActions with a confirmation dialog. Notes with trashed:true in
existing vault frontmatter are now treated as normal notes (the flag is
ignored by the parser).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 12:21:56 +02:00

219 lines
7.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { memo } from 'react'
import type { VaultEntry } from '../types'
import { cn } from '@/lib/utils'
import {
MagnifyingGlass,
GitBranch,
Code,
CursorText,
Sparkle,
SlidersHorizontal,
DotsThree,
Trash,
Archive,
ArrowUUpLeft,
Star,
CheckCircle,
} from '@phosphor-icons/react'
interface BreadcrumbBarProps {
entry: VaultEntry
wordCount: number
showDiffToggle: boolean
diffMode: boolean
diffLoading: boolean
onToggleDiff: () => void
rawMode?: boolean
onToggleRaw?: () => void
/** When true, raw mode is forced (non-markdown file) — hide the toggle. */
forceRawMode?: boolean
showAIChat?: boolean
onToggleAIChat?: () => void
inspectorCollapsed?: boolean
onToggleInspector?: () => void
onToggleFavorite?: () => void
onToggleOrganized?: () => void
onDelete?: () => void
onArchive?: () => void
onUnarchive?: () => void
/** Ref for direct DOM manipulation — avoids re-render on scroll. */
barRef?: React.Ref<HTMLDivElement>
}
const DISABLED_ICON_STYLE = { opacity: 0.4, cursor: 'not-allowed' } as const
function RawToggleButton({ rawMode, onToggleRaw }: { rawMode?: boolean; onToggleRaw?: () => void }) {
return (
<button
className={cn(
'flex items-center justify-center border-none bg-transparent p-0 cursor-pointer transition-colors',
rawMode ? 'text-foreground' : 'text-muted-foreground hover:text-foreground',
)}
onClick={onToggleRaw}
title={rawMode ? 'Back to editor' : 'Raw editor'}
>
<Code size={16} />
</button>
)
}
function BreadcrumbActions({ entry, showDiffToggle, diffMode, diffLoading, onToggleDiff,
rawMode, onToggleRaw, forceRawMode,
showAIChat, onToggleAIChat, inspectorCollapsed, onToggleInspector,
onToggleFavorite, onToggleOrganized, onDelete, onArchive, onUnarchive,
}: Omit<BreadcrumbBarProps, 'wordCount'>) {
return (
<div className="breadcrumb-bar__actions ml-auto flex items-center" style={{ gap: 12 }}>
<button
className={cn(
"flex items-center justify-center border-none bg-transparent p-0 cursor-pointer transition-colors",
entry.favorite ? "text-yellow-500" : "text-muted-foreground hover:text-foreground"
)}
onClick={onToggleFavorite}
title={entry.favorite ? 'Remove from favorites' : 'Add to favorites'}
>
<Star size={16} weight={entry.favorite ? 'fill' : 'regular'} />
</button>
<button
className={cn(
"flex items-center justify-center border-none bg-transparent p-0 cursor-pointer transition-colors",
entry.organized ? "text-green-600" : "text-muted-foreground hover:text-foreground"
)}
onClick={onToggleOrganized}
title={entry.organized ? 'Mark as unorganized (back to Inbox)' : 'Mark as organized (remove from Inbox)'}
>
<CheckCircle size={16} weight={entry.organized ? 'fill' : 'regular'} />
</button>
<button
className="flex items-center justify-center border-none bg-transparent p-0 text-muted-foreground cursor-pointer hover:text-foreground transition-colors"
title="Search in file"
>
<MagnifyingGlass size={16} />
</button>
{showDiffToggle ? (
<button
className={cn(
"flex items-center justify-center border-none bg-transparent p-0 cursor-pointer transition-colors",
diffMode ? "text-foreground" : "text-muted-foreground hover:text-foreground"
)}
onClick={onToggleDiff}
disabled={diffLoading}
title={diffLoading ? 'Loading diff...' : diffMode ? 'Back to editor' : 'Show diff'}
>
<GitBranch size={16} />
</button>
) : (
<button
className="flex items-center justify-center border-none bg-transparent p-0 text-muted-foreground"
style={DISABLED_ICON_STYLE}
title="No changes"
tabIndex={-1}
>
<GitBranch size={16} />
</button>
)}
{!forceRawMode && <RawToggleButton rawMode={rawMode} onToggleRaw={onToggleRaw} />}
<button
className="flex items-center justify-center border-none bg-transparent p-0 text-muted-foreground"
style={DISABLED_ICON_STYLE}
title="Coming soon"
tabIndex={-1}
>
<CursorText size={16} />
</button>
<button
className={cn(
"flex items-center justify-center border-none bg-transparent p-0 cursor-pointer transition-colors",
showAIChat ? "" : "text-muted-foreground hover:text-foreground"
)}
style={showAIChat ? { color: 'var(--primary)' } : undefined}
onClick={onToggleAIChat}
title={showAIChat ? 'Close AI Chat' : 'Open AI Chat'}
>
<Sparkle size={16} weight={showAIChat ? 'fill' : 'regular'} />
</button>
{entry.archived ? (
<button
className="flex items-center justify-center border-none bg-transparent p-0 cursor-pointer transition-colors text-muted-foreground hover:text-foreground"
onClick={onUnarchive}
title="Unarchive (Cmd+E)"
>
<ArrowUUpLeft size={16} />
</button>
) : (
<button
className="flex items-center justify-center border-none bg-transparent p-0 cursor-pointer transition-colors text-muted-foreground hover:text-foreground"
onClick={onArchive}
title="Archive (Cmd+E)"
>
<Archive size={16} />
</button>
)}
<button
className="flex items-center justify-center border-none bg-transparent p-0 cursor-pointer transition-colors text-muted-foreground hover:text-destructive"
onClick={onDelete}
title="Delete (Cmd+Delete)"
>
<Trash size={16} />
</button>
{inspectorCollapsed && (
<button
className="flex items-center justify-center border-none bg-transparent p-0 text-muted-foreground cursor-pointer hover:text-foreground transition-colors"
onClick={onToggleInspector}
title="Properties (⌘⇧I)"
>
<SlidersHorizontal size={16} />
</button>
)}
<button
className="flex items-center justify-center border-none bg-transparent p-0 text-muted-foreground"
style={DISABLED_ICON_STYLE}
title="Coming soon"
tabIndex={-1}
>
<DotsThree size={16} />
</button>
</div>
)
}
function BreadcrumbTitle({ entry }: { entry: VaultEntry }) {
const typeLabel = entry.isA ?? 'Note'
const icon = entry.icon
const emojiIcon = icon && /^\p{Emoji}/u.test(icon) ? icon : null
return (
<div className="flex items-center gap-1.5 min-w-0 text-sm text-muted-foreground">
<span className="shrink-0">{typeLabel}</span>
<span className="shrink-0 text-border"></span>
{emojiIcon && <span className="shrink-0">{emojiIcon}</span>}
<span className="truncate font-medium text-foreground">{entry.title}</span>
</div>
)
}
export const BreadcrumbBar = memo(function BreadcrumbBar({
entry, barRef, ...actionProps
}: BreadcrumbBarProps) {
// In raw/diff mode the title section is not rendered — always show title in breadcrumb.
// Using a prop-driven attribute avoids the timing issues of DOM mutation in useEffect.
const titleAlwaysVisible = actionProps.rawMode || actionProps.diffMode
return (
<div
ref={barRef}
data-tauri-drag-region
{...(titleAlwaysVisible ? { 'data-title-hidden': '' } : {})}
className="breadcrumb-bar flex shrink-0 items-center"
style={{
height: 52,
background: 'var(--background)',
padding: '6px 16px',
}}
>
<div className="breadcrumb-bar__title flex-1 min-w-0">
<BreadcrumbTitle entry={entry} />
</div>
<BreadcrumbActions entry={entry} {...actionProps} />
</div>
)
})