2026-04-16 13:58:17 +02:00
|
|
|
import type { ComponentType, CSSProperties, MouseEvent as ReactMouseEvent, MouseEventHandler, ReactNode, SVGAttributes } from 'react'
|
2026-03-27 16:20:31 +01:00
|
|
|
import type { VaultEntry, NoteStatus } from '../types'
|
2026-02-22 10:18:05 +01:00
|
|
|
import { cn } from '@/lib/utils'
|
|
|
|
|
import {
|
|
|
|
|
Wrench, Flask, Target, ArrowsClockwise,
|
|
|
|
|
Users, CalendarBlank, Tag, FileText, StackSimple,
|
2026-05-05 01:08:33 +02:00
|
|
|
File, FileDashed, FilePdf, ImageSquare, SpeakerHigh, Video,
|
2026-02-22 10:18:05 +01:00
|
|
|
} from '@phosphor-icons/react'
|
|
|
|
|
import { getTypeColor, getTypeLightColor } from '../utils/typeColors'
|
2026-04-08 21:28:28 +02:00
|
|
|
import { resolveIcon } from '../utils/iconRegistry'
|
2026-05-11 13:46:18 +02:00
|
|
|
import { getDisplayDate } from '../utils/noteListHelpers'
|
2026-05-12 06:57:42 +02:00
|
|
|
import { formatTimestampForDateDisplay } from '../utils/dateDisplay'
|
2026-04-29 13:14:44 +02:00
|
|
|
import { filePreviewKind, type FilePreviewKind } from '../utils/filePreview'
|
2026-04-07 21:09:06 +02:00
|
|
|
import { NoteTitleIcon } from './NoteTitleIcon'
|
2026-04-08 21:28:28 +02:00
|
|
|
import { PropertyChips } from './note-item/PropertyChips'
|
2026-04-13 15:22:21 +02:00
|
|
|
import { ChangeNoteContent } from './note-item/ChangeNoteContent'
|
2026-05-08 00:45:21 +02:00
|
|
|
import { workspaceForEntry } from '../utils/workspaces'
|
2026-05-11 16:37:06 +02:00
|
|
|
import { WorkspaceInitialsBadge } from './WorkspaceInitialsBadge'
|
2026-05-12 06:57:42 +02:00
|
|
|
import { useDateDisplayFormat } from '../hooks/useAppPreferences'
|
2026-02-22 10:18:05 +01:00
|
|
|
|
|
|
|
|
const TYPE_ICON_MAP: Record<string, ComponentType<SVGAttributes<SVGSVGElement>>> = {
|
|
|
|
|
Project: Wrench,
|
|
|
|
|
Experiment: Flask,
|
|
|
|
|
Responsibility: Target,
|
|
|
|
|
Procedure: ArrowsClockwise,
|
|
|
|
|
Person: Users,
|
|
|
|
|
Event: CalendarBlank,
|
|
|
|
|
Topic: Tag,
|
|
|
|
|
Type: StackSimple,
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 13:11:38 +01:00
|
|
|
// eslint-disable-next-line react-refresh/only-export-components -- utility co-located with component
|
2026-02-22 10:18:05 +01:00
|
|
|
export function getTypeIcon(isA: string | null, customIcon?: string | null): ComponentType<SVGAttributes<SVGSVGElement>> {
|
|
|
|
|
if (customIcon) return resolveIcon(customIcon)
|
2026-05-07 20:44:45 +02:00
|
|
|
return (isA && (Reflect.get(TYPE_ICON_MAP, isA) as ComponentType<SVGAttributes<SVGSVGElement>> | undefined)) || FileText
|
2026-02-22 10:18:05 +01:00
|
|
|
}
|
|
|
|
|
|
2026-05-06 13:42:04 +02:00
|
|
|
type VisibleNoteStatus = Exclude<NoteStatus, 'clean'>
|
|
|
|
|
|
|
|
|
|
const NOTE_STATUS_DOT: Record<VisibleNoteStatus, { color: string; testId: string; title: string }> = {
|
2026-02-27 14:11:31 +01:00
|
|
|
pendingSave: { color: 'var(--accent-green)', testId: 'pending-save-indicator', title: 'Saving to disk…' },
|
2026-05-06 13:42:04 +02:00
|
|
|
unsaved: { color: 'var(--accent-green)', testId: 'unsaved-indicator', title: 'Saving to disk…' },
|
2026-02-24 22:20:22 +01:00
|
|
|
new: { color: 'var(--accent-green)', testId: 'new-indicator', title: 'New (uncommitted)' },
|
2026-02-24 15:54:24 +01:00
|
|
|
modified: { color: 'var(--accent-orange)', testId: 'modified-indicator', title: 'Modified (uncommitted)' },
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 13:42:04 +02:00
|
|
|
function hasStatusDot(noteStatus: NoteStatus): noteStatus is VisibleNoteStatus {
|
|
|
|
|
return noteStatus !== 'clean'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function StatusDot({ noteStatus }: { noteStatus: VisibleNoteStatus }) {
|
2026-05-07 20:44:45 +02:00
|
|
|
const dot = Reflect.get(NOTE_STATUS_DOT, noteStatus) as { color: string; testId: string; title: string }
|
feat: keyboard-first navigation — menu bar shortcuts, note list nav, inspector Tab/Enter (#158)
* feat: keyboard-first navigation — menu bar shortcuts, note list nav, inspector Tab/Enter
- Add missing menu bar items: Archive Note (⌘E), Trash Note (⌘⌫),
Find in Vault (⌘⇧F), Go Back (⌘[), Go Forward (⌘])
- Wire new menu events through useMenuEvents → useAppCommands
- Note list: ↑↓ moves highlight, Enter opens selected note (useNoteListKeyboard hook)
- Inspector properties: Tab between rows, Enter to start editing
- Settings panel: auto-focus first input on open
- Extract StatusDot, StateBadge, noteItemStyle from NoteItem (reduces complexity)
- Extract dispatchActiveTabEvent/dispatchOptionalEvent from useMenuEvents
- 21 new tests (useNoteListKeyboard + useMenuEvents for new events)
- All 1275 frontend tests pass, 319 Rust tests pass
- CodeScene quality gates: passed (NoteItem improved from 22→18 complexity)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: keyboard-first-nav wireframes (note list highlight, menu shortcuts, inspector tab nav)
* test: add search.rs coverage for fallback branches (qmd_uri_to_vault_path, extract_clean_snippet)
* chore: exclude search.rs and lib.rs from coverage (qmd integration + Tauri setup code)
---------
Co-authored-by: Test <test@test.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:32:15 +01:00
|
|
|
return (
|
|
|
|
|
<span
|
2026-05-06 13:42:04 +02:00
|
|
|
className="mr-1.5 inline-block align-middle"
|
feat: keyboard-first navigation — menu bar shortcuts, note list nav, inspector Tab/Enter (#158)
* feat: keyboard-first navigation — menu bar shortcuts, note list nav, inspector Tab/Enter
- Add missing menu bar items: Archive Note (⌘E), Trash Note (⌘⌫),
Find in Vault (⌘⇧F), Go Back (⌘[), Go Forward (⌘])
- Wire new menu events through useMenuEvents → useAppCommands
- Note list: ↑↓ moves highlight, Enter opens selected note (useNoteListKeyboard hook)
- Inspector properties: Tab between rows, Enter to start editing
- Settings panel: auto-focus first input on open
- Extract StatusDot, StateBadge, noteItemStyle from NoteItem (reduces complexity)
- Extract dispatchActiveTabEvent/dispatchOptionalEvent from useMenuEvents
- 21 new tests (useNoteListKeyboard + useMenuEvents for new events)
- All 1275 frontend tests pass, 319 Rust tests pass
- CodeScene quality gates: passed (NoteItem improved from 22→18 complexity)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: keyboard-first-nav wireframes (note list highlight, menu shortcuts, inspector tab nav)
* test: add search.rs coverage for fallback branches (qmd_uri_to_vault_path, extract_clean_snippet)
* chore: exclude search.rs and lib.rs from coverage (qmd integration + Tauri setup code)
---------
Co-authored-by: Test <test@test.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:32:15 +01:00
|
|
|
style={{ width: 6, height: 6, borderRadius: '50%', background: dot.color, verticalAlign: 'middle' }}
|
|
|
|
|
data-testid={dot.testId}
|
|
|
|
|
title={dot.title}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 12:21:56 +02:00
|
|
|
function StateBadge({ archived }: { archived: boolean }) {
|
feat: keyboard-first navigation — menu bar shortcuts, note list nav, inspector Tab/Enter (#158)
* feat: keyboard-first navigation — menu bar shortcuts, note list nav, inspector Tab/Enter
- Add missing menu bar items: Archive Note (⌘E), Trash Note (⌘⌫),
Find in Vault (⌘⇧F), Go Back (⌘[), Go Forward (⌘])
- Wire new menu events through useMenuEvents → useAppCommands
- Note list: ↑↓ moves highlight, Enter opens selected note (useNoteListKeyboard hook)
- Inspector properties: Tab between rows, Enter to start editing
- Settings panel: auto-focus first input on open
- Extract StatusDot, StateBadge, noteItemStyle from NoteItem (reduces complexity)
- Extract dispatchActiveTabEvent/dispatchOptionalEvent from useMenuEvents
- 21 new tests (useNoteListKeyboard + useMenuEvents for new events)
- All 1275 frontend tests pass, 319 Rust tests pass
- CodeScene quality gates: passed (NoteItem improved from 22→18 complexity)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: keyboard-first-nav wireframes (note list highlight, menu shortcuts, inspector tab nav)
* test: add search.rs coverage for fallback branches (qmd_uri_to_vault_path, extract_clean_snippet)
* chore: exclude search.rs and lib.rs from coverage (qmd integration + Tauri setup code)
---------
Co-authored-by: Test <test@test.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:32:15 +01:00
|
|
|
if (archived) {
|
|
|
|
|
return (
|
|
|
|
|
<span className="ml-1.5 inline-block align-middle text-muted-foreground" style={{ fontSize: 9, fontWeight: 500, background: 'var(--muted)', borderRadius: 4, padding: '1px 4px', verticalAlign: 'middle' }}>
|
|
|
|
|
ARCHIVED
|
|
|
|
|
</span>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-08 00:45:21 +02:00
|
|
|
function WorkspaceBadge({ entry, allEntries }: { entry: VaultEntry; allEntries: VaultEntry[] }) {
|
|
|
|
|
const workspace = workspaceForEntry(entry)
|
|
|
|
|
const hasMultipleWorkspaces = new Set(allEntries.map((candidate) => candidate.workspace?.alias).filter(Boolean)).size > 1
|
|
|
|
|
if (!workspace || !hasMultipleWorkspaces) return null
|
2026-05-11 16:37:06 +02:00
|
|
|
return <WorkspaceInitialsBadge workspace={workspace} className="-mr-1.5" testId="workspace-badge" />
|
2026-05-08 00:45:21 +02:00
|
|
|
}
|
|
|
|
|
|
2026-04-16 13:58:17 +02:00
|
|
|
type NoteItemVisualState = {
|
2026-04-27 00:36:24 +02:00
|
|
|
isUnavailableBinary: boolean
|
2026-04-08 21:28:28 +02:00
|
|
|
isSelected: boolean
|
|
|
|
|
isMultiSelected: boolean
|
|
|
|
|
isHighlighted: boolean
|
2026-04-16 13:58:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type NoteItemRowState = 'binary' | 'multiSelected' | 'selected' | 'highlighted' | 'default'
|
|
|
|
|
|
|
|
|
|
type NoteItemSurfaceProps = {
|
|
|
|
|
className: string
|
|
|
|
|
style: CSSProperties
|
2026-05-29 00:46:59 +02:00
|
|
|
onClick: MouseEventHandler<HTMLDivElement>
|
|
|
|
|
onContextMenu?: MouseEventHandler<HTMLDivElement>
|
2026-04-16 13:58:17 +02:00
|
|
|
onMouseEnter?: () => void
|
|
|
|
|
title?: string
|
|
|
|
|
testId?: string
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-26 10:11:53 +02:00
|
|
|
const NOTE_ITEM_BASE_CLASS_NAME = 'relative w-full border-0 border-b border-[var(--border)] bg-transparent p-0 text-left transition-colors'
|
2026-04-16 13:58:17 +02:00
|
|
|
const BINARY_NOTE_STYLE: CSSProperties = { padding: '14px 16px' }
|
|
|
|
|
const NOTE_ITEM_ROW_CLASS_NAMES: Record<NoteItemRowState, string> = {
|
|
|
|
|
binary: 'cursor-default opacity-50',
|
|
|
|
|
multiSelected: 'cursor-pointer',
|
|
|
|
|
selected: 'cursor-pointer border-l-[3px]',
|
|
|
|
|
highlighted: 'cursor-pointer bg-muted hover:bg-muted',
|
|
|
|
|
default: 'cursor-pointer hover:bg-muted',
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-27 00:36:24 +02:00
|
|
|
function resolveNoteItemRowState({ isUnavailableBinary, isSelected, isMultiSelected, isHighlighted }: NoteItemVisualState): NoteItemRowState {
|
|
|
|
|
if (isUnavailableBinary) return 'binary'
|
2026-04-16 13:58:17 +02:00
|
|
|
if (isMultiSelected) return 'multiSelected'
|
|
|
|
|
if (isSelected) return 'selected'
|
|
|
|
|
if (isHighlighted) return 'highlighted'
|
|
|
|
|
return 'default'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function noteItemClassName(state: NoteItemVisualState) {
|
|
|
|
|
return cn(NOTE_ITEM_BASE_CLASS_NAME, NOTE_ITEM_ROW_CLASS_NAMES[resolveNoteItemRowState(state)])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function NoteTypeIndicator({
|
|
|
|
|
TypeIcon,
|
|
|
|
|
typeColor,
|
2026-04-27 00:36:24 +02:00
|
|
|
filePreviewKind,
|
2026-04-16 13:58:17 +02:00
|
|
|
}: {
|
|
|
|
|
TypeIcon: ComponentType<SVGAttributes<SVGSVGElement>>
|
|
|
|
|
typeColor: string
|
2026-04-29 13:14:44 +02:00
|
|
|
filePreviewKind?: FilePreviewKind
|
2026-04-08 21:28:28 +02:00
|
|
|
}) {
|
2026-04-27 00:36:24 +02:00
|
|
|
return (
|
|
|
|
|
<TypeIcon
|
|
|
|
|
width={14}
|
|
|
|
|
height={14}
|
|
|
|
|
className="absolute right-3 top-2.5"
|
|
|
|
|
style={{ color: typeColor }}
|
|
|
|
|
data-testid="type-icon"
|
|
|
|
|
data-file-preview-kind={filePreviewKind}
|
|
|
|
|
/>
|
|
|
|
|
)
|
2026-04-16 13:58:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function NoteSnippet({ snippet }: { snippet?: string | null }) {
|
|
|
|
|
if (!snippet) return null
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
className="text-[12px] leading-[1.5] text-muted-foreground"
|
|
|
|
|
data-testid="note-snippet"
|
|
|
|
|
style={{ display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}
|
|
|
|
|
>
|
|
|
|
|
{snippet}
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function NotePropertySection({
|
|
|
|
|
entry,
|
|
|
|
|
displayProps,
|
|
|
|
|
allEntries,
|
|
|
|
|
typeEntryMap,
|
|
|
|
|
onClickNote,
|
|
|
|
|
}: {
|
|
|
|
|
entry: VaultEntry
|
|
|
|
|
displayProps: string[]
|
|
|
|
|
allEntries: VaultEntry[]
|
|
|
|
|
typeEntryMap: Record<string, VaultEntry>
|
|
|
|
|
onClickNote: NoteItemProps['onClickNote']
|
|
|
|
|
}) {
|
|
|
|
|
if (displayProps.length === 0) return null
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<PropertyChips
|
|
|
|
|
entry={entry}
|
|
|
|
|
displayProps={displayProps}
|
|
|
|
|
allEntries={allEntries}
|
|
|
|
|
typeEntryMap={typeEntryMap}
|
|
|
|
|
onOpenNote={onClickNote}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function InteractiveNoteDetails({
|
|
|
|
|
entry,
|
|
|
|
|
noteStatus,
|
|
|
|
|
isSelected,
|
|
|
|
|
displayProps,
|
|
|
|
|
allEntries,
|
|
|
|
|
typeEntryMap,
|
|
|
|
|
onClickNote,
|
|
|
|
|
}: {
|
|
|
|
|
entry: VaultEntry
|
|
|
|
|
noteStatus: NoteStatus
|
|
|
|
|
isSelected: boolean
|
|
|
|
|
displayProps: string[]
|
|
|
|
|
allEntries: VaultEntry[]
|
|
|
|
|
typeEntryMap: Record<string, VaultEntry>
|
|
|
|
|
onClickNote: NoteItemProps['onClickNote']
|
|
|
|
|
}) {
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<NoteTitleRow
|
|
|
|
|
entry={entry}
|
|
|
|
|
isBinary={false}
|
|
|
|
|
isSelected={isSelected}
|
|
|
|
|
noteStatus={noteStatus}
|
|
|
|
|
/>
|
|
|
|
|
<NoteSnippet snippet={entry.snippet} />
|
|
|
|
|
<NotePropertySection
|
|
|
|
|
entry={entry}
|
|
|
|
|
displayProps={displayProps}
|
|
|
|
|
allEntries={allEntries}
|
|
|
|
|
typeEntryMap={typeEntryMap}
|
|
|
|
|
onClickNote={onClickNote}
|
|
|
|
|
/>
|
2026-05-12 06:57:42 +02:00
|
|
|
<NoteDateRow entry={entry} allEntries={allEntries} />
|
2026-04-16 13:58:17 +02:00
|
|
|
</>
|
2026-04-08 21:28:28 +02:00
|
|
|
)
|
2026-04-07 22:09:21 +02:00
|
|
|
}
|
|
|
|
|
|
2026-04-16 13:58:17 +02:00
|
|
|
function resolveNoteTypeIcon(entry: VaultEntry, customIcon?: string | null): ComponentType<SVGAttributes<SVGSVGElement>> {
|
2026-04-29 13:14:44 +02:00
|
|
|
const previewKind = filePreviewKind(entry)
|
|
|
|
|
if (previewKind === 'image') return ImageSquare
|
|
|
|
|
if (previewKind === 'pdf') return FilePdf
|
2026-05-05 01:08:33 +02:00
|
|
|
if (previewKind === 'audio') return SpeakerHigh
|
|
|
|
|
if (previewKind === 'video') return Video
|
2026-04-16 13:58:17 +02:00
|
|
|
if (entry.fileKind && entry.fileKind !== 'markdown') return getFileKindIcon(entry.fileKind)
|
|
|
|
|
return getTypeIcon(entry.isA, customIcon)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 21:28:28 +02:00
|
|
|
function StandardNoteContent({
|
2026-04-07 22:09:21 +02:00
|
|
|
entry,
|
2026-04-08 21:28:28 +02:00
|
|
|
isBinary,
|
2026-04-27 00:36:24 +02:00
|
|
|
isUnavailableBinary,
|
2026-04-08 21:28:28 +02:00
|
|
|
noteStatus,
|
|
|
|
|
isSelected,
|
|
|
|
|
typeColor,
|
2026-04-07 22:09:21 +02:00
|
|
|
displayProps,
|
|
|
|
|
allEntries,
|
|
|
|
|
typeEntryMap,
|
2026-04-08 21:28:28 +02:00
|
|
|
onClickNote,
|
2026-04-07 22:09:21 +02:00
|
|
|
}: {
|
|
|
|
|
entry: VaultEntry
|
2026-04-08 21:28:28 +02:00
|
|
|
isBinary: boolean
|
2026-04-27 00:36:24 +02:00
|
|
|
isUnavailableBinary: boolean
|
2026-04-08 21:28:28 +02:00
|
|
|
noteStatus: NoteStatus
|
|
|
|
|
isSelected: boolean
|
|
|
|
|
typeColor: string
|
2026-04-07 22:09:21 +02:00
|
|
|
displayProps: string[]
|
|
|
|
|
allEntries: VaultEntry[]
|
|
|
|
|
typeEntryMap: Record<string, VaultEntry>
|
2026-04-08 21:28:28 +02:00
|
|
|
onClickNote: NoteItemProps['onClickNote']
|
2026-04-07 22:09:21 +02:00
|
|
|
}) {
|
2026-04-08 21:28:28 +02:00
|
|
|
const te = typeEntryMap[entry.isA ?? '']
|
2026-04-16 13:58:17 +02:00
|
|
|
const TypeIcon = resolveNoteTypeIcon(entry, te?.icon)
|
2026-04-29 13:14:44 +02:00
|
|
|
const previewKind = filePreviewKind(entry) ?? undefined
|
2026-04-04 12:52:54 +02:00
|
|
|
|
|
|
|
|
return (
|
2026-04-08 21:28:28 +02:00
|
|
|
<>
|
2026-04-29 13:14:44 +02:00
|
|
|
<NoteTypeIndicator TypeIcon={TypeIcon} typeColor={typeColor} filePreviewKind={previewKind} />
|
2026-04-11 20:30:48 +02:00
|
|
|
<div className="space-y-2" data-testid="note-content-stack">
|
2026-04-16 13:58:17 +02:00
|
|
|
{isBinary ? (
|
|
|
|
|
<NoteTitleRow
|
2026-04-10 13:02:25 +02:00
|
|
|
entry={entry}
|
2026-04-27 00:36:24 +02:00
|
|
|
isBinary={isUnavailableBinary}
|
2026-04-16 13:58:17 +02:00
|
|
|
isSelected={isSelected}
|
|
|
|
|
noteStatus={noteStatus}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<InteractiveNoteDetails
|
|
|
|
|
entry={entry}
|
|
|
|
|
noteStatus={noteStatus}
|
|
|
|
|
isSelected={isSelected}
|
2026-04-10 13:02:25 +02:00
|
|
|
displayProps={displayProps}
|
|
|
|
|
allEntries={allEntries}
|
|
|
|
|
typeEntryMap={typeEntryMap}
|
2026-04-16 13:58:17 +02:00
|
|
|
onClickNote={onClickNote}
|
2026-04-10 13:02:25 +02:00
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2026-04-08 21:28:28 +02:00
|
|
|
</>
|
2026-04-05 02:58:26 +02:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 13:02:25 +02:00
|
|
|
function NoteTitleRow({
|
|
|
|
|
entry,
|
|
|
|
|
isBinary,
|
|
|
|
|
isSelected,
|
|
|
|
|
noteStatus,
|
|
|
|
|
}: {
|
|
|
|
|
entry: VaultEntry
|
|
|
|
|
isBinary: boolean
|
|
|
|
|
isSelected: boolean
|
|
|
|
|
noteStatus: NoteStatus
|
|
|
|
|
}) {
|
|
|
|
|
return (
|
2026-05-11 16:37:06 +02:00
|
|
|
<div
|
|
|
|
|
className={cn('truncate pr-5 text-[13px]', isBinary ? 'text-muted-foreground' : 'text-foreground', isSelected && !isBinary ? 'font-semibold' : 'font-medium')}
|
|
|
|
|
data-testid="note-title-row"
|
|
|
|
|
>
|
2026-05-06 13:42:04 +02:00
|
|
|
{hasStatusDot(noteStatus) && !isBinary && <StatusDot noteStatus={noteStatus} />}
|
2026-04-10 13:02:25 +02:00
|
|
|
<NoteTitleIcon icon={entry.icon} size={15} className="mr-1" testId="note-title-icon" />
|
|
|
|
|
{entry.title}
|
|
|
|
|
{!isBinary && <StateBadge archived={entry.archived} />}
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-11 13:46:18 +02:00
|
|
|
function NoteDateRow({
|
|
|
|
|
entry,
|
2026-05-11 16:37:06 +02:00
|
|
|
allEntries,
|
2026-05-11 13:46:18 +02:00
|
|
|
}: {
|
|
|
|
|
entry: VaultEntry
|
2026-05-11 16:37:06 +02:00
|
|
|
allEntries: VaultEntry[]
|
2026-05-11 13:46:18 +02:00
|
|
|
}) {
|
2026-05-12 06:57:42 +02:00
|
|
|
const dateDisplayFormat = useDateDisplayFormat()
|
2026-05-11 13:46:18 +02:00
|
|
|
const modifiedLabel = formatTimestampForDateDisplay(getDisplayDate(entry), dateDisplayFormat)
|
|
|
|
|
const createdLabel = entry.createdAt ? `Created ${formatTimestampForDateDisplay(entry.createdAt, dateDisplayFormat)}` : null
|
2026-04-10 13:02:25 +02:00
|
|
|
|
|
|
|
|
if (!modifiedLabel && !createdLabel) return null
|
|
|
|
|
|
|
|
|
|
return (
|
2026-04-11 20:30:48 +02:00
|
|
|
<div className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-3 text-[10px] text-muted-foreground" data-testid="note-date-row">
|
2026-04-10 13:02:25 +02:00
|
|
|
<span>{modifiedLabel}</span>
|
2026-05-11 16:37:06 +02:00
|
|
|
<span className="flex min-w-0 items-center justify-end gap-1.5 text-right">
|
|
|
|
|
{createdLabel && <span>{createdLabel}</span>}
|
|
|
|
|
<WorkspaceBadge entry={entry} allEntries={allEntries} />
|
|
|
|
|
</span>
|
2026-04-10 13:02:25 +02:00
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 13:58:17 +02:00
|
|
|
function noteItemStyle(isSelected: boolean, isMultiSelected: boolean, typeColor: string, typeLightColor: string): CSSProperties {
|
|
|
|
|
const base: CSSProperties = { padding: isSelected && !isMultiSelected ? '14px 16px 14px 13px' : '14px 16px' }
|
feat: keyboard-first navigation — menu bar shortcuts, note list nav, inspector Tab/Enter (#158)
* feat: keyboard-first navigation — menu bar shortcuts, note list nav, inspector Tab/Enter
- Add missing menu bar items: Archive Note (⌘E), Trash Note (⌘⌫),
Find in Vault (⌘⇧F), Go Back (⌘[), Go Forward (⌘])
- Wire new menu events through useMenuEvents → useAppCommands
- Note list: ↑↓ moves highlight, Enter opens selected note (useNoteListKeyboard hook)
- Inspector properties: Tab between rows, Enter to start editing
- Settings panel: auto-focus first input on open
- Extract StatusDot, StateBadge, noteItemStyle from NoteItem (reduces complexity)
- Extract dispatchActiveTabEvent/dispatchOptionalEvent from useMenuEvents
- 21 new tests (useNoteListKeyboard + useMenuEvents for new events)
- All 1275 frontend tests pass, 319 Rust tests pass
- CodeScene quality gates: passed (NoteItem improved from 22→18 complexity)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: keyboard-first-nav wireframes (note list highlight, menu shortcuts, inspector tab nav)
* test: add search.rs coverage for fallback branches (qmd_uri_to_vault_path, extract_clean_snippet)
* chore: exclude search.rs and lib.rs from coverage (qmd integration + Tauri setup code)
---------
Co-authored-by: Test <test@test.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:32:15 +01:00
|
|
|
if (isMultiSelected) base.backgroundColor = 'color-mix(in srgb, var(--accent-blue) 10%, transparent)'
|
|
|
|
|
else if (isSelected) { base.borderLeftColor = typeColor; base.backgroundColor = typeLightColor }
|
|
|
|
|
return base
|
|
|
|
|
}
|
|
|
|
|
|
feat: show all files in folder view, open text files in raw editor, gray out binary
Vault scanner now includes all files (not just .md). Each VaultEntry has a
fileKind field ("markdown", "text", or "binary") that controls how the
frontend handles it:
- Folder view shows all file kinds; other views (All Notes, type sections)
continue to show only markdown files
- Text files (.yml, .json, .txt, .py, etc.) open in the raw CodeMirror editor
- Binary files (.png, .jpg, .pdf, etc.) appear grayed out and are not clickable
- Non-markdown files use filename as title, skip frontmatter parsing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 17:38:09 +02:00
|
|
|
function getFileKindIcon(fileKind: string | undefined): ComponentType<SVGAttributes<SVGSVGElement>> {
|
|
|
|
|
if (fileKind === 'text') return File
|
|
|
|
|
if (fileKind === 'binary') return FileDashed
|
|
|
|
|
return FileText
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 20:31:08 +02:00
|
|
|
function resolveDisplayProps(entry: VaultEntry, typeEntryMap: Record<string, VaultEntry>, displayPropsOverride?: string[] | null): string[] {
|
|
|
|
|
if (displayPropsOverride && displayPropsOverride.length > 0) return displayPropsOverride
|
|
|
|
|
return typeEntryMap[entry.isA ?? '']?.listPropertiesDisplay ?? []
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 21:28:28 +02:00
|
|
|
type NoteItemProps = {
|
2026-02-22 10:18:05 +01:00
|
|
|
entry: VaultEntry
|
|
|
|
|
isSelected: boolean
|
2026-02-27 15:14:21 +01:00
|
|
|
isMultiSelected?: boolean
|
feat: keyboard-first navigation — menu bar shortcuts, note list nav, inspector Tab/Enter (#158)
* feat: keyboard-first navigation — menu bar shortcuts, note list nav, inspector Tab/Enter
- Add missing menu bar items: Archive Note (⌘E), Trash Note (⌘⌫),
Find in Vault (⌘⇧F), Go Back (⌘[), Go Forward (⌘])
- Wire new menu events through useMenuEvents → useAppCommands
- Note list: ↑↓ moves highlight, Enter opens selected note (useNoteListKeyboard hook)
- Inspector properties: Tab between rows, Enter to start editing
- Settings panel: auto-focus first input on open
- Extract StatusDot, StateBadge, noteItemStyle from NoteItem (reduces complexity)
- Extract dispatchActiveTabEvent/dispatchOptionalEvent from useMenuEvents
- 21 new tests (useNoteListKeyboard + useMenuEvents for new events)
- All 1275 frontend tests pass, 319 Rust tests pass
- CodeScene quality gates: passed (NoteItem improved from 22→18 complexity)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: keyboard-first-nav wireframes (note list highlight, menu shortcuts, inspector tab nav)
* test: add search.rs coverage for fallback branches (qmd_uri_to_vault_path, extract_clean_snippet)
* chore: exclude search.rs and lib.rs from coverage (qmd integration + Tauri setup code)
---------
Co-authored-by: Test <test@test.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:32:15 +01:00
|
|
|
isHighlighted?: boolean
|
2026-02-24 15:54:24 +01:00
|
|
|
noteStatus?: NoteStatus
|
2026-04-05 02:58:26 +02:00
|
|
|
/** When set, renders in Changes-view style: filename + change type icon */
|
|
|
|
|
changeStatus?: 'modified' | 'added' | 'deleted' | 'untracked' | 'renamed'
|
2026-02-22 10:18:05 +01:00
|
|
|
typeEntryMap: Record<string, VaultEntry>
|
2026-04-07 22:09:21 +02:00
|
|
|
allEntries?: VaultEntry[]
|
2026-04-07 20:31:08 +02:00
|
|
|
displayPropsOverride?: string[] | null
|
2026-04-16 13:58:17 +02:00
|
|
|
onClickNote: (entry: VaultEntry, e: ReactMouseEvent) => void
|
2026-05-01 23:14:36 +02:00
|
|
|
onPrefetch?: (entry: VaultEntry) => void
|
2026-04-16 13:58:17 +02:00
|
|
|
onContextMenu?: (entry: VaultEntry, e: ReactMouseEvent) => void
|
2026-04-08 21:28:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createNoteItemClickHandler(
|
|
|
|
|
entry: VaultEntry,
|
2026-04-27 00:36:24 +02:00
|
|
|
isUnavailableBinary: boolean,
|
2026-04-08 21:28:28 +02:00
|
|
|
onClickNote: NoteItemProps['onClickNote'],
|
|
|
|
|
) {
|
2026-05-26 10:11:53 +02:00
|
|
|
const isPropertyChipTarget = (event: ReactMouseEvent) =>
|
|
|
|
|
event.target instanceof Element && event.target.closest('[data-property-chip="true"]') !== null
|
|
|
|
|
|
2026-04-27 00:36:24 +02:00
|
|
|
if (isUnavailableBinary) {
|
2026-04-16 13:58:17 +02:00
|
|
|
return (event: ReactMouseEvent) => {
|
2026-04-08 21:28:28 +02:00
|
|
|
event.preventDefault()
|
|
|
|
|
event.stopPropagation()
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-26 10:11:53 +02:00
|
|
|
return (event: ReactMouseEvent) => {
|
|
|
|
|
if (isPropertyChipTarget(event)) return
|
|
|
|
|
onClickNote(entry, event)
|
|
|
|
|
}
|
2026-04-16 13:58:17 +02:00
|
|
|
}
|
|
|
|
|
|
2026-04-27 00:36:24 +02:00
|
|
|
function resolveNoteItemSurfaceStyle({
|
|
|
|
|
isUnavailableBinary,
|
|
|
|
|
isSelected,
|
|
|
|
|
isMultiSelected,
|
|
|
|
|
typeColor,
|
|
|
|
|
typeLightColor,
|
|
|
|
|
}: Pick<NoteItemVisualState, 'isUnavailableBinary' | 'isSelected' | 'isMultiSelected'> & {
|
|
|
|
|
typeColor: string
|
|
|
|
|
typeLightColor: string
|
|
|
|
|
}) {
|
|
|
|
|
if (isUnavailableBinary) return BINARY_NOTE_STYLE
|
|
|
|
|
return noteItemStyle(isSelected, isMultiSelected, typeColor, typeLightColor)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resolveNoteItemTestId({
|
|
|
|
|
isMultiSelected,
|
2026-04-29 13:14:44 +02:00
|
|
|
previewKind,
|
2026-04-27 00:36:24 +02:00
|
|
|
isUnavailableBinary,
|
|
|
|
|
}: Pick<NoteItemVisualState, 'isMultiSelected' | 'isUnavailableBinary'> & {
|
2026-04-29 13:14:44 +02:00
|
|
|
previewKind: FilePreviewKind | null
|
2026-04-27 00:36:24 +02:00
|
|
|
}) {
|
|
|
|
|
if (isMultiSelected) return 'multi-selected-item'
|
2026-04-29 13:14:44 +02:00
|
|
|
if (previewKind) return `${previewKind}-file-item`
|
2026-04-27 00:36:24 +02:00
|
|
|
return isUnavailableBinary ? 'binary-file-item' : undefined
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resolveNoteItemTitle({
|
2026-04-29 13:14:44 +02:00
|
|
|
previewKind,
|
2026-04-27 00:36:24 +02:00
|
|
|
isUnavailableBinary,
|
|
|
|
|
}: Pick<NoteItemVisualState, 'isUnavailableBinary'> & {
|
2026-04-29 13:14:44 +02:00
|
|
|
previewKind: FilePreviewKind | null
|
2026-04-27 00:36:24 +02:00
|
|
|
}) {
|
2026-04-29 13:14:44 +02:00
|
|
|
if (previewKind === 'image') return 'Open image preview'
|
|
|
|
|
if (previewKind === 'pdf') return 'Open PDF preview'
|
2026-05-05 01:08:33 +02:00
|
|
|
if (previewKind === 'audio') return 'Open audio preview'
|
|
|
|
|
if (previewKind === 'video') return 'Open video preview'
|
2026-04-27 00:36:24 +02:00
|
|
|
return isUnavailableBinary ? 'Cannot open this file type' : undefined
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 13:58:17 +02:00
|
|
|
function resolveNoteItemSurfaceProps({
|
|
|
|
|
entry,
|
2026-04-27 00:36:24 +02:00
|
|
|
isUnavailableBinary,
|
2026-04-29 13:14:44 +02:00
|
|
|
previewKind,
|
2026-04-16 13:58:17 +02:00
|
|
|
isSelected,
|
|
|
|
|
isMultiSelected,
|
|
|
|
|
isHighlighted,
|
|
|
|
|
onClickNote,
|
|
|
|
|
onPrefetch,
|
|
|
|
|
onContextMenu,
|
|
|
|
|
typeColor,
|
|
|
|
|
typeLightColor,
|
|
|
|
|
}: NoteItemVisualState & {
|
|
|
|
|
entry: VaultEntry
|
2026-04-29 13:14:44 +02:00
|
|
|
previewKind: FilePreviewKind | null
|
2026-04-16 13:58:17 +02:00
|
|
|
onClickNote: NoteItemProps['onClickNote']
|
|
|
|
|
onPrefetch?: NoteItemProps['onPrefetch']
|
|
|
|
|
onContextMenu?: NoteItemProps['onContextMenu']
|
|
|
|
|
typeColor: string
|
|
|
|
|
typeLightColor: string
|
|
|
|
|
}): NoteItemSurfaceProps {
|
|
|
|
|
return {
|
2026-04-27 00:36:24 +02:00
|
|
|
className: noteItemClassName({ isUnavailableBinary, isSelected, isMultiSelected, isHighlighted }),
|
|
|
|
|
style: resolveNoteItemSurfaceStyle({ isUnavailableBinary, isSelected, isMultiSelected, typeColor, typeLightColor }),
|
|
|
|
|
onClick: createNoteItemClickHandler(entry, isUnavailableBinary, onClickNote),
|
2026-04-16 13:58:17 +02:00
|
|
|
onContextMenu: onContextMenu ? (event) => onContextMenu(entry, event) : undefined,
|
2026-05-01 23:14:36 +02:00
|
|
|
onMouseEnter: entry.fileKind !== 'binary' && onPrefetch ? () => onPrefetch(entry) : undefined,
|
2026-04-29 13:14:44 +02:00
|
|
|
testId: resolveNoteItemTestId({ isMultiSelected, previewKind, isUnavailableBinary }),
|
|
|
|
|
title: resolveNoteItemTitle({ previewKind, isUnavailableBinary }),
|
2026-04-16 13:58:17 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function NoteItemRow({
|
|
|
|
|
surfaceProps,
|
|
|
|
|
entryPath,
|
2026-05-29 00:46:59 +02:00
|
|
|
isSelected,
|
|
|
|
|
isMultiSelected,
|
2026-04-16 13:58:17 +02:00
|
|
|
isHighlighted,
|
|
|
|
|
changeStatus,
|
|
|
|
|
children,
|
|
|
|
|
}: {
|
|
|
|
|
surfaceProps: NoteItemSurfaceProps
|
|
|
|
|
entryPath: string
|
2026-05-29 00:46:59 +02:00
|
|
|
isSelected: boolean
|
|
|
|
|
isMultiSelected: boolean
|
2026-04-16 13:58:17 +02:00
|
|
|
isHighlighted: boolean
|
|
|
|
|
changeStatus: NoteItemProps['changeStatus']
|
|
|
|
|
children: ReactNode
|
|
|
|
|
}) {
|
|
|
|
|
return (
|
2026-05-29 00:46:59 +02:00
|
|
|
<div
|
|
|
|
|
role="option"
|
|
|
|
|
aria-selected={isSelected || isMultiSelected}
|
2026-04-16 13:58:17 +02:00
|
|
|
className={surfaceProps.className}
|
|
|
|
|
style={surfaceProps.style}
|
|
|
|
|
onClick={surfaceProps.onClick}
|
|
|
|
|
onContextMenu={surfaceProps.onContextMenu}
|
|
|
|
|
onMouseEnter={surfaceProps.onMouseEnter}
|
|
|
|
|
data-testid={surfaceProps.testId}
|
|
|
|
|
data-highlighted={isHighlighted || undefined}
|
|
|
|
|
data-note-path={entryPath}
|
|
|
|
|
data-change-status={changeStatus}
|
|
|
|
|
title={surfaceProps.title}
|
|
|
|
|
>
|
|
|
|
|
{children}
|
2026-05-29 00:46:59 +02:00
|
|
|
</div>
|
2026-04-16 13:58:17 +02:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function NoteItemContent({
|
|
|
|
|
entry,
|
|
|
|
|
isBinary,
|
2026-04-27 00:36:24 +02:00
|
|
|
isUnavailableBinary,
|
2026-04-16 13:58:17 +02:00
|
|
|
isSelected,
|
|
|
|
|
noteStatus,
|
|
|
|
|
changeStatus,
|
|
|
|
|
typeColor,
|
|
|
|
|
displayProps,
|
|
|
|
|
allEntries,
|
|
|
|
|
typeEntryMap,
|
|
|
|
|
onClickNote,
|
|
|
|
|
}: {
|
|
|
|
|
entry: VaultEntry
|
|
|
|
|
isBinary: boolean
|
2026-04-27 00:36:24 +02:00
|
|
|
isUnavailableBinary: boolean
|
2026-04-16 13:58:17 +02:00
|
|
|
isSelected: boolean
|
|
|
|
|
noteStatus: NoteStatus
|
|
|
|
|
changeStatus?: NoteItemProps['changeStatus']
|
|
|
|
|
typeColor: string
|
|
|
|
|
displayProps: string[]
|
|
|
|
|
allEntries: VaultEntry[]
|
|
|
|
|
typeEntryMap: Record<string, VaultEntry>
|
|
|
|
|
onClickNote: NoteItemProps['onClickNote']
|
|
|
|
|
}) {
|
|
|
|
|
if (changeStatus) {
|
|
|
|
|
return (
|
|
|
|
|
<ChangeNoteContent
|
|
|
|
|
entry={entry}
|
|
|
|
|
changeStatus={changeStatus}
|
|
|
|
|
isSelected={isSelected}
|
|
|
|
|
isDeletedChange={changeStatus === 'deleted'}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<StandardNoteContent
|
|
|
|
|
entry={entry}
|
|
|
|
|
isBinary={isBinary}
|
2026-04-27 00:36:24 +02:00
|
|
|
isUnavailableBinary={isUnavailableBinary}
|
2026-04-16 13:58:17 +02:00
|
|
|
noteStatus={noteStatus}
|
|
|
|
|
isSelected={isSelected}
|
|
|
|
|
typeColor={typeColor}
|
|
|
|
|
displayProps={displayProps}
|
|
|
|
|
allEntries={allEntries}
|
|
|
|
|
typeEntryMap={typeEntryMap}
|
|
|
|
|
onClickNote={onClickNote}
|
|
|
|
|
/>
|
|
|
|
|
)
|
2026-04-08 21:28:28 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 06:57:42 +02:00
|
|
|
export function NoteItem({ entry, isSelected, isMultiSelected = false, isHighlighted = false, noteStatus = 'clean', changeStatus, typeEntryMap, allEntries, displayPropsOverride, onClickNote, onPrefetch, onContextMenu }: NoteItemProps) {
|
feat: show all files in folder view, open text files in raw editor, gray out binary
Vault scanner now includes all files (not just .md). Each VaultEntry has a
fileKind field ("markdown", "text", or "binary") that controls how the
frontend handles it:
- Folder view shows all file kinds; other views (All Notes, type sections)
continue to show only markdown files
- Text files (.yml, .json, .txt, .py, etc.) open in the raw CodeMirror editor
- Binary files (.png, .jpg, .pdf, etc.) appear grayed out and are not clickable
- Non-markdown files use filename as title, skip frontmatter parsing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 17:38:09 +02:00
|
|
|
const isBinary = entry.fileKind === 'binary'
|
2026-04-29 13:14:44 +02:00
|
|
|
const previewKind = filePreviewKind(entry)
|
|
|
|
|
const isPreviewableFile = previewKind !== null
|
|
|
|
|
const isUnavailableBinary = isBinary && !isPreviewableFile
|
2026-02-22 10:18:05 +01:00
|
|
|
const te = typeEntryMap[entry.isA ?? '']
|
2026-04-07 20:31:08 +02:00
|
|
|
const displayProps = resolveDisplayProps(entry, typeEntryMap, displayPropsOverride)
|
2026-04-29 13:14:44 +02:00
|
|
|
const typeColor = isPreviewableFile ? 'var(--accent-blue)' : isBinary ? 'var(--muted-foreground)' : getTypeColor(entry.isA ?? 'Note', te?.color)
|
2026-02-22 10:18:05 +01:00
|
|
|
const typeLightColor = getTypeLightColor(entry.isA ?? 'Note', te?.color)
|
2026-04-16 13:58:17 +02:00
|
|
|
const surfaceProps = resolveNoteItemSurfaceProps({
|
|
|
|
|
entry,
|
2026-04-27 00:36:24 +02:00
|
|
|
isUnavailableBinary,
|
2026-04-29 13:14:44 +02:00
|
|
|
previewKind,
|
2026-04-16 13:58:17 +02:00
|
|
|
isSelected,
|
|
|
|
|
isMultiSelected,
|
|
|
|
|
isHighlighted,
|
|
|
|
|
onClickNote,
|
|
|
|
|
onPrefetch,
|
|
|
|
|
onContextMenu,
|
|
|
|
|
typeColor,
|
|
|
|
|
typeLightColor,
|
|
|
|
|
})
|
2026-02-22 10:18:05 +01:00
|
|
|
|
|
|
|
|
return (
|
2026-04-16 13:58:17 +02:00
|
|
|
<NoteItemRow
|
|
|
|
|
surfaceProps={surfaceProps}
|
|
|
|
|
entryPath={entry.path}
|
2026-05-29 00:46:59 +02:00
|
|
|
isSelected={isSelected}
|
|
|
|
|
isMultiSelected={isMultiSelected}
|
2026-04-16 13:58:17 +02:00
|
|
|
isHighlighted={isHighlighted}
|
|
|
|
|
changeStatus={changeStatus}
|
2026-02-22 10:18:05 +01:00
|
|
|
>
|
2026-04-16 13:58:17 +02:00
|
|
|
<NoteItemContent
|
|
|
|
|
entry={entry}
|
|
|
|
|
isBinary={isBinary}
|
2026-04-27 00:36:24 +02:00
|
|
|
isUnavailableBinary={isUnavailableBinary}
|
2026-04-16 13:58:17 +02:00
|
|
|
isSelected={isSelected}
|
|
|
|
|
noteStatus={noteStatus}
|
|
|
|
|
changeStatus={changeStatus}
|
|
|
|
|
typeColor={typeColor}
|
|
|
|
|
displayProps={displayProps}
|
|
|
|
|
allEntries={allEntries ?? [entry]}
|
|
|
|
|
typeEntryMap={typeEntryMap}
|
|
|
|
|
onClickNote={onClickNote}
|
|
|
|
|
/>
|
|
|
|
|
</NoteItemRow>
|
2026-02-22 10:18:05 +01:00
|
|
|
)
|
|
|
|
|
}
|