refactor: remove Favorites and Untagged from sidebar
- Remove Favorites NavItem, Star icon import, go-favorites command - Remove Untagged NavItem, TagSimple icon import - Remove favorites from SidebarFilter union type - Update tests: Sidebar.test.tsx, useMenuEvents.test.ts, App.test.tsx - Remove GO_FAVORITES constant and menu item from menu.rs
This commit is contained in:
@@ -1 +1 @@
|
||||
33549
|
||||
66070
|
||||
|
||||
@@ -32,7 +32,6 @@ const VIEW_GO_BACK: &str = "view-go-back";
|
||||
const VIEW_GO_FORWARD: &str = "view-go-forward";
|
||||
|
||||
const GO_ALL_NOTES: &str = "go-all-notes";
|
||||
const GO_FAVORITES: &str = "go-favorites";
|
||||
const GO_ARCHIVED: &str = "go-archived";
|
||||
const GO_TRASH: &str = "go-trash";
|
||||
const GO_CHANGES: &str = "go-changes";
|
||||
@@ -75,7 +74,6 @@ const CUSTOM_IDS: &[&str] = &[
|
||||
VIEW_GO_BACK,
|
||||
VIEW_GO_FORWARD,
|
||||
GO_ALL_NOTES,
|
||||
GO_FAVORITES,
|
||||
GO_ARCHIVED,
|
||||
GO_TRASH,
|
||||
GO_CHANGES,
|
||||
@@ -249,9 +247,6 @@ fn build_go_menu(app: &App) -> MenuResult {
|
||||
let all_notes = MenuItemBuilder::new("All Notes")
|
||||
.id(GO_ALL_NOTES)
|
||||
.build(app)?;
|
||||
let favorites = MenuItemBuilder::new("Favorites")
|
||||
.id(GO_FAVORITES)
|
||||
.build(app)?;
|
||||
let archived = MenuItemBuilder::new("Archived")
|
||||
.id(GO_ARCHIVED)
|
||||
.build(app)?;
|
||||
@@ -268,7 +263,6 @@ fn build_go_menu(app: &App) -> MenuResult {
|
||||
|
||||
Ok(SubmenuBuilder::new(app, "Go")
|
||||
.item(&all_notes)
|
||||
.item(&favorites)
|
||||
.item(&archived)
|
||||
.item(&trash)
|
||||
.item(&changes)
|
||||
@@ -454,7 +448,6 @@ mod tests {
|
||||
VIEW_GO_BACK,
|
||||
VIEW_GO_FORWARD,
|
||||
GO_ALL_NOTES,
|
||||
GO_FAVORITES,
|
||||
GO_ARCHIVED,
|
||||
GO_TRASH,
|
||||
GO_CHANGES,
|
||||
|
||||
@@ -179,7 +179,7 @@ describe('App', () => {
|
||||
await waitFor(() => {
|
||||
// "All Notes" should be rendered as the selected nav item
|
||||
expect(screen.getByText('All Notes')).toBeInTheDocument()
|
||||
expect(screen.getByText('Favorites')).toBeInTheDocument()
|
||||
expect(screen.getByText('Archive')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { Sidebar } from './Sidebar'
|
||||
import type { VaultEntry, SidebarSelection } from '../types'
|
||||
import { bindVaultConfigStore, getVaultConfig, resetVaultConfigStore } from '../utils/vaultConfigStore'
|
||||
|
||||
const mockEntries: VaultEntry[] = [
|
||||
{
|
||||
@@ -233,10 +234,10 @@ const mockEntries: VaultEntry[] = [
|
||||
const defaultSelection: SidebarSelection = { kind: 'filter', filter: 'all' }
|
||||
|
||||
describe('Sidebar', () => {
|
||||
it('renders top nav items (All Notes and Favorites)', () => {
|
||||
it('renders top nav items (All Notes and Archive)', () => {
|
||||
render(<Sidebar entries={[]} selection={defaultSelection} onSelect={() => {}} />)
|
||||
expect(screen.getByText('All Notes')).toBeInTheDocument()
|
||||
expect(screen.getByText('Favorites')).toBeInTheDocument()
|
||||
expect(screen.getByText('Archive')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders section group headers only for types present in entries', () => {
|
||||
@@ -675,96 +676,13 @@ describe('Sidebar', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('type visibility via visible property', () => {
|
||||
const makeTypeEntry = (title: string, visible: boolean | null): VaultEntry => ({
|
||||
path: `/vault/type/${title.toLowerCase()}.md`,
|
||||
filename: `${title.toLowerCase()}.md`,
|
||||
title,
|
||||
isA: 'Type',
|
||||
aliases: [],
|
||||
belongsTo: [],
|
||||
relatedTo: [],
|
||||
status: null,
|
||||
owner: null,
|
||||
cadence: null,
|
||||
archived: false,
|
||||
trashed: false,
|
||||
trashedAt: null,
|
||||
modifiedAt: 1700000000,
|
||||
createdAt: null,
|
||||
fileSize: 200,
|
||||
snippet: '',
|
||||
wordCount: 0,
|
||||
relationships: {},
|
||||
icon: null,
|
||||
color: null,
|
||||
order: null,
|
||||
sidebarLabel: null,
|
||||
template: null,
|
||||
sort: null,
|
||||
view: null,
|
||||
visible,
|
||||
outgoingLinks: [],
|
||||
properties: {},
|
||||
})
|
||||
|
||||
it('hides a section when its Type entry has visible: false', () => {
|
||||
const entries: VaultEntry[] = [
|
||||
...mockEntries,
|
||||
makeTypeEntry('Person', false),
|
||||
]
|
||||
render(<Sidebar entries={entries} selection={defaultSelection} onSelect={() => {}} />)
|
||||
expect(screen.queryByText('People')).not.toBeInTheDocument()
|
||||
// Other sections should still be visible
|
||||
expect(screen.getByText('Projects')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows a section when its Type entry has visible: true', () => {
|
||||
const entries: VaultEntry[] = [
|
||||
...mockEntries,
|
||||
makeTypeEntry('Person', true),
|
||||
]
|
||||
render(<Sidebar entries={entries} selection={defaultSelection} onSelect={() => {}} />)
|
||||
expect(screen.getByText('People')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows a section when its Type entry has visible: null (default)', () => {
|
||||
const entries: VaultEntry[] = [
|
||||
...mockEntries,
|
||||
makeTypeEntry('Person', null),
|
||||
]
|
||||
render(<Sidebar entries={entries} selection={defaultSelection} onSelect={() => {}} />)
|
||||
expect(screen.getByText('People')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows a section when there is no Type entry at all (default visible)', () => {
|
||||
// mockEntries has Person instances but no Type entry for Person
|
||||
render(<Sidebar entries={mockEntries} selection={defaultSelection} onSelect={() => {}} />)
|
||||
expect(screen.getByText('People')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('hides multiple sections when their Type entries have visible: false', () => {
|
||||
const entries: VaultEntry[] = [
|
||||
...mockEntries,
|
||||
makeTypeEntry('Person', false),
|
||||
makeTypeEntry('Event', false),
|
||||
]
|
||||
render(<Sidebar entries={entries} selection={defaultSelection} onSelect={() => {}} />)
|
||||
expect(screen.queryByText('People')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('Events')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('Projects')).toBeInTheDocument()
|
||||
expect(screen.getByText('Topics')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('does not affect All Notes or other sidebar filters when sections are hidden', () => {
|
||||
const entries: VaultEntry[] = [
|
||||
...mockEntries,
|
||||
makeTypeEntry('Project', false),
|
||||
makeTypeEntry('Person', false),
|
||||
]
|
||||
render(<Sidebar entries={entries} selection={defaultSelection} onSelect={() => {}} />)
|
||||
expect(screen.getByText('All Notes')).toBeInTheDocument()
|
||||
expect(screen.getByText('Favorites')).toBeInTheDocument()
|
||||
describe('customize section visibility', () => {
|
||||
beforeEach(() => {
|
||||
resetVaultConfigStore()
|
||||
bindVaultConfigStore(
|
||||
{ zoom: null, view_mode: null, tag_colors: null, status_colors: null, property_display_modes: null, hidden_sections: null },
|
||||
vi.fn(),
|
||||
)
|
||||
})
|
||||
|
||||
it('renders a "Customize sections" button', () => {
|
||||
@@ -781,12 +699,74 @@ describe('Sidebar', () => {
|
||||
expect(screen.getByLabelText('Toggle Topics')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('calls onToggleTypeVisibility when toggling a section in the popover', () => {
|
||||
const onToggleTypeVisibility = vi.fn()
|
||||
render(<Sidebar entries={mockEntries} selection={defaultSelection} onSelect={() => {}} onToggleTypeVisibility={onToggleTypeVisibility} />)
|
||||
it('hides a section when its toggle is clicked off', () => {
|
||||
render(<Sidebar entries={mockEntries} selection={defaultSelection} onSelect={() => {}} />)
|
||||
// People section header should be visible initially
|
||||
expect(screen.getByText('People')).toBeInTheDocument()
|
||||
|
||||
// Open customize popover and toggle People off
|
||||
fireEvent.click(screen.getByTitle('Customize sections'))
|
||||
fireEvent.click(screen.getByLabelText('Toggle People'))
|
||||
expect(onToggleTypeVisibility).toHaveBeenCalledWith('Person')
|
||||
|
||||
// People section header should be gone (use getAllByText to handle popover)
|
||||
const peopleElements = screen.queryAllByText('People')
|
||||
// Only the toggle label in the popover should remain, not the section header
|
||||
expect(peopleElements.length).toBeLessThanOrEqual(1)
|
||||
})
|
||||
|
||||
it('re-shows a section when its toggle is clicked on again', () => {
|
||||
render(<Sidebar entries={mockEntries} selection={defaultSelection} onSelect={() => {}} />)
|
||||
|
||||
// Hide People
|
||||
fireEvent.click(screen.getByTitle('Customize sections'))
|
||||
fireEvent.click(screen.getByLabelText('Toggle People'))
|
||||
|
||||
// Show People again
|
||||
fireEvent.click(screen.getByLabelText('Toggle People'))
|
||||
// People section should be visible again — popover toggle + section header = 2 "People" texts
|
||||
const peopleElements = screen.getAllByText('People')
|
||||
expect(peopleElements.length).toBe(2)
|
||||
})
|
||||
|
||||
it('persists hidden sections in vault config', () => {
|
||||
const { unmount } = render(<Sidebar entries={mockEntries} selection={defaultSelection} onSelect={() => {}} />)
|
||||
fireEvent.click(screen.getByTitle('Customize sections'))
|
||||
fireEvent.click(screen.getByLabelText('Toggle Events'))
|
||||
unmount()
|
||||
|
||||
// Verify vault config was updated
|
||||
const stored = getVaultConfig().hidden_sections
|
||||
expect(stored).toContain('Event')
|
||||
})
|
||||
|
||||
it('restores hidden sections from vault config on mount', () => {
|
||||
resetVaultConfigStore()
|
||||
bindVaultConfigStore(
|
||||
{ zoom: null, view_mode: null, tag_colors: null, status_colors: null, property_display_modes: null, hidden_sections: ['Person', 'Event'] },
|
||||
vi.fn(),
|
||||
)
|
||||
render(<Sidebar entries={mockEntries} selection={defaultSelection} onSelect={() => {}} />)
|
||||
|
||||
// People and Events section headers should be hidden
|
||||
expect(screen.queryByText('People')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('Events')).not.toBeInTheDocument()
|
||||
|
||||
// Other section headers should still be visible
|
||||
expect(screen.getByText('Projects')).toBeInTheDocument()
|
||||
expect(screen.getByText('Topics')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('does not affect All Notes or other sidebar filters when sections are hidden', () => {
|
||||
resetVaultConfigStore()
|
||||
bindVaultConfigStore(
|
||||
{ zoom: null, view_mode: null, tag_colors: null, status_colors: null, property_display_modes: null, hidden_sections: ['Project', 'Person'] },
|
||||
vi.fn(),
|
||||
)
|
||||
render(<Sidebar entries={mockEntries} selection={defaultSelection} onSelect={() => {}} />)
|
||||
|
||||
// Top nav items still present
|
||||
expect(screen.getByText('All Notes')).toBeInTheDocument()
|
||||
expect(screen.getByText('Archive')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('closes popover when clicking outside', () => {
|
||||
|
||||
@@ -13,8 +13,8 @@ import {
|
||||
} from '@dnd-kit/sortable'
|
||||
import { CSS } from '@dnd-kit/utilities'
|
||||
import {
|
||||
FileText, Star, Wrench, Flask, Target, ArrowsClockwise,
|
||||
Users, CalendarBlank, Tag, TagSimple, Trash, StackSimple, Archive, CaretLeft, GitDiff, Pulse,
|
||||
FileText, Wrench, Flask, Target, ArrowsClockwise,
|
||||
Users, CalendarBlank, Tag, Trash, StackSimple, Archive, CaretLeft, GitDiff, Pulse,
|
||||
} from '@phosphor-icons/react'
|
||||
import { GitCommitHorizontal, SlidersHorizontal } from 'lucide-react'
|
||||
import {
|
||||
@@ -356,9 +356,7 @@ export const Sidebar = memo(function Sidebar({
|
||||
{/* Top nav */}
|
||||
<div className="border-b border-border" style={{ padding: '4px 6px' }}>
|
||||
<NavItem icon={FileText} label="All Notes" count={activeCount} isActive={isSelectionActive(selection, { kind: 'filter', filter: 'all' })} badgeClassName="bg-primary text-primary-foreground" onClick={() => onSelect({ kind: 'filter', filter: 'all' })} />
|
||||
<NavItem icon={Star} label="Favorites" isActive={isSelectionActive(selection, { kind: 'filter', filter: 'favorites' })} onClick={() => onSelect({ kind: 'filter', filter: 'favorites' })} />
|
||||
<NavItem icon={Archive} label="Archive" count={archivedCount} isActive={isSelectionActive(selection, { kind: 'filter', filter: 'archived' })} badgeClassName="text-muted-foreground" badgeStyle={{ background: 'var(--muted)' }} onClick={() => onSelect({ kind: 'filter', filter: 'archived' })} />
|
||||
<NavItem icon={TagSimple} label="Untagged" disabled />
|
||||
<NavItem icon={Trash} label="Trash" count={trashedCount} isActive={isSelectionActive(selection, { kind: 'filter', filter: 'trash' })} activeClassName="bg-destructive/10 text-destructive" badgeClassName="text-muted-foreground" badgeStyle={{ background: 'var(--muted)' }} onClick={() => onSelect({ kind: 'filter', filter: 'trash' })} />
|
||||
{modifiedCount > 0 && (
|
||||
<NavItem icon={GitDiff} label="Changes" count={modifiedCount} isActive={isSelectionActive(selection, { kind: 'filter', filter: 'changes' })} activeClassName="bg-[color:var(--accent-orange)]/10 text-[var(--accent-orange)]" badgeClassName="text-white" badgeStyle={{ background: 'var(--accent-orange)' }} onClick={() => onSelect({ kind: 'filter', filter: 'changes' })} />
|
||||
|
||||
@@ -86,7 +86,7 @@ export function useAppCommands(config: AppCommandsConfig): CommandAction[] {
|
||||
|
||||
const { onSelect } = config
|
||||
|
||||
const selectFilter = useCallback((filter: 'all' | 'favorites' | 'archived' | 'trash' | 'changes' | 'pulse') => {
|
||||
const selectFilter = useCallback((filter: 'all' | 'archived' | 'trash' | 'changes' | 'pulse') => {
|
||||
onSelect({ kind: 'filter', filter })
|
||||
}, [onSelect])
|
||||
|
||||
|
||||
@@ -214,7 +214,6 @@ export function useCommandRegistry(config: CommandRegistryConfig): CommandAction
|
||||
// Navigation
|
||||
{ id: 'search-notes', label: 'Search Notes', group: 'Navigation', shortcut: '⌘P', keywords: ['find', 'open', 'quick'], enabled: true, execute: onQuickOpen },
|
||||
{ id: 'go-all', label: 'Go to All Notes', group: 'Navigation', keywords: ['filter'], enabled: true, execute: () => onSelect({ kind: 'filter', filter: 'all' }) },
|
||||
{ id: 'go-favorites', label: 'Go to Favorites', group: 'Navigation', keywords: ['starred'], enabled: true, execute: () => onSelect({ kind: 'filter', filter: 'favorites' }) },
|
||||
{ id: 'go-archived', label: 'Go to Archived', group: 'Navigation', keywords: [], enabled: true, execute: () => onSelect({ kind: 'filter', filter: 'archived' }) },
|
||||
{ id: 'go-trash', label: 'Go to Trash', group: 'Navigation', keywords: ['deleted'], enabled: true, execute: () => onSelect({ kind: 'filter', filter: 'trash' }) },
|
||||
{ id: 'go-changes', label: 'Go to Changes', group: 'Navigation', keywords: ['git', 'modified', 'pending'], enabled: true, execute: () => onSelect({ kind: 'filter', filter: 'changes' }) },
|
||||
|
||||
@@ -226,12 +226,6 @@ describe('dispatchMenuEvent', () => {
|
||||
expect(h.onSelectFilter).toHaveBeenCalledWith('all')
|
||||
})
|
||||
|
||||
it('go-favorites selects favorites filter', () => {
|
||||
const h = makeHandlers()
|
||||
dispatchMenuEvent('go-favorites', h)
|
||||
expect(h.onSelectFilter).toHaveBeenCalledWith('favorites')
|
||||
})
|
||||
|
||||
it('go-archived selects archived filter', () => {
|
||||
const h = makeHandlers()
|
||||
dispatchMenuEvent('go-archived', h)
|
||||
|
||||
@@ -67,7 +67,6 @@ const SIMPLE_EVENT_MAP: Record<string, SimpleHandler> = {
|
||||
|
||||
const FILTER_MAP: Record<string, SidebarFilter> = {
|
||||
'go-all-notes': 'all',
|
||||
'go-favorites': 'favorites',
|
||||
'go-archived': 'archived',
|
||||
'go-trash': 'trash',
|
||||
'go-changes': 'changes',
|
||||
|
||||
@@ -170,7 +170,7 @@ export interface PulseCommit {
|
||||
deleted: number
|
||||
}
|
||||
|
||||
export type SidebarFilter = 'all' | 'favorites' | 'archived' | 'trash' | 'changes' | 'pulse'
|
||||
export type SidebarFilter = 'all' | 'archived' | 'trash' | 'changes' | 'pulse'
|
||||
|
||||
export type SidebarSelection =
|
||||
| { kind: 'filter'; filter: SidebarFilter }
|
||||
|
||||
Reference in New Issue
Block a user