feat(renderer): Breadcrumb shows heading symbols (settings toggleable)

This commit is contained in:
2026-06-06 08:28:24 +05:30
parent fbaff37505
commit 2d77fa5456
2 changed files with 83 additions and 2 deletions
+33 -2
View File
@@ -1,7 +1,38 @@
import { useFileStore } from '@/stores/file-store';
import { useEditorStore } from '@/stores/editor-store';
import { useSettingsStore } from '@/stores/settings-store';
function extractHeadings(content: string): { level: number; text: string }[] {
const lines = content.split('\n');
const headings: { level: number; text: string }[] = [];
for (const line of lines) {
const m = line.match(/^(#{1,6})\s+(.+)$/);
if (m) headings.push({ level: m[1].length, text: m[2].trim() });
}
return headings;
}
export function Breadcrumb() {
const activeTabId = useFileStore((s) => s.activeTabId);
const openTabs = useFileStore((s) => s.openTabs);
const buffer = useEditorStore((s) => (activeTabId ? s.buffers.get(activeTabId) : undefined));
const showSymbols = useSettingsStore((s) => s.breadcrumbSymbols);
const tab = activeTabId ? openTabs.find((t) => t.id === activeTabId) : null;
const headings = showSymbols && buffer ? extractHeadings(buffer.content).slice(0, 3) : [];
return (
<nav aria-label="File path" className="flex h-7 items-center border-b border-border bg-card/10 px-3 text-xs text-muted-foreground">
<span>No file selected</span>
<nav
aria-label="File path"
className="flex h-7 items-center gap-1 border-b border-border bg-card/10 px-3 text-xs text-muted-foreground"
>
<span>{tab ? tab.title : 'No file selected'}</span>
{headings.map((h, i) => (
<span key={i} className="flex items-center gap-1">
<span aria-hidden="true"></span>
<span className="truncate">{'#'.repeat(h.level)} {h.text}</span>
</span>
))}
</nav>
);
}
@@ -0,0 +1,50 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { render, screen } from '@testing-library/react';
import { Breadcrumb } from '@/components/layout/Breadcrumb';
import { useFileStore } from '@/stores/file-store';
import { useEditorStore } from '@/stores/editor-store';
import { useSettingsStore } from '@/stores/settings-store';
describe('Breadcrumb', () => {
beforeEach(() => {
localStorage.clear();
useSettingsStore.setState({ ...useSettingsStore.getInitialState(), breadcrumbSymbols: true });
useFileStore.setState({ activeTabId: '/test.md', openTabs: [{ id: '/test.md', path: '/test.md', title: 'test.md', dirty: false }] } as any);
useEditorStore.setState({ buffers: new Map([['/test.md', { id: '/test.md', path: '/test.md', content: '# H1\n## H2', dirty: false }]]) } as any);
});
it('shows "No file selected" when no tab is active', () => {
useFileStore.setState({ activeTabId: null, openTabs: [] } as any);
render(<Breadcrumb />);
expect(screen.getByText('No file selected')).toBeInTheDocument();
});
it('shows tab title when a tab is active', () => {
render(<Breadcrumb />);
expect(screen.getByText('test.md')).toBeInTheDocument();
});
it('shows heading symbols when breadcrumbSymbols is true', () => {
render(<Breadcrumb />);
expect(screen.getByText('test.md')).toBeInTheDocument();
expect(screen.getByText(/# H1/)).toBeInTheDocument();
});
it('does not show heading symbols when breadcrumbSymbols is false', () => {
useSettingsStore.setState({ ...useSettingsStore.getInitialState(), breadcrumbSymbols: false });
render(<Breadcrumb />);
expect(screen.getByText('test.md')).toBeInTheDocument();
expect(screen.queryByText(/# H1/)).not.toBeInTheDocument();
});
it('limits heading symbols to 3', () => {
useFileStore.setState({ activeTabId: '/test.md', openTabs: [{ id: '/test.md', path: '/test.md', title: 'test.md', dirty: false }] } as any);
useEditorStore.setState({ buffers: new Map([['/test.md', { id: '/test.md', path: '/test.md', content: '# H1\n## H2\n### H3\n#### H4', dirty: false }]]) } as any);
render(<Breadcrumb />);
// Should show H1, H2, H3 but not H4
expect(screen.getByText(/# H1/)).toBeInTheDocument();
expect(screen.getByText(/# H2/)).toBeInTheDocument();
expect(screen.getByText(/# H3/)).toBeInTheDocument();
expect(screen.queryByText(/# H4/)).not.toBeInTheDocument();
});
});