From 9440c5e543592d0508cc0dbbde28700ebe9f0cab Mon Sep 17 00:00:00 2001 From: hugo Date: Mon, 16 Feb 2026 08:01:33 +0100 Subject: [PATCH] feat: pages shortcut feature --- .../components/ActivityBar/ActivityBar.tsx | 15 +++- src/renderer/components/Sidebar/Sidebar.tsx | 82 +++++++++++++---- src/renderer/store/appStore.ts | 4 +- .../components/PagesShortcut.test.tsx | 90 +++++++++++++++++++ 4 files changed, 170 insertions(+), 21 deletions(-) create mode 100644 tests/renderer/components/PagesShortcut.test.tsx diff --git a/src/renderer/components/ActivityBar/ActivityBar.tsx b/src/renderer/components/ActivityBar/ActivityBar.tsx index e5b6263..d0d313e 100644 --- a/src/renderer/components/ActivityBar/ActivityBar.tsx +++ b/src/renderer/components/ActivityBar/ActivityBar.tsx @@ -10,6 +10,12 @@ const PostsIcon = () => ( ); +const PagesIcon = () => ( + + + +); + const MediaIcon = () => ( @@ -62,7 +68,7 @@ export const ActivityBar: React.FC = () => { const isImportActive = activeView === 'import' && sidebarVisible; // Handle view click - toggle sidebar if clicking on active view, otherwise switch view - const handleViewClick = (view: 'posts' | 'media' | 'chat') => { + const handleViewClick = (view: 'posts' | 'pages' | 'media' | 'chat') => { if (activeView === view && sidebarVisible) { // Clicking on active view toggles sidebar off toggleSidebar(); @@ -118,6 +124,13 @@ export const ActivityBar: React.FC = () => { > +
- POSTS + {isPagesMode ? 'PAGES' : 'POSTS'}
)} - {posts.length === 0 && !isFiltered && ( + {postSubset.length === 0 && !isFiltered && (
-

No posts yet

+

{isPagesMode ? 'No pages yet' : 'No posts yet'}

)} @@ -1541,7 +1582,12 @@ export const Sidebar: React.FC = () => { return (
- {activeView === 'posts' && } +
+ +
+
+ +
{activeView === 'media' && } {activeView === 'settings' && } {activeView === 'tags' && } diff --git a/src/renderer/store/appStore.ts b/src/renderer/store/appStore.ts index 558a402..e2ab29f 100644 --- a/src/renderer/store/appStore.ts +++ b/src/renderer/store/appStore.ts @@ -50,7 +50,7 @@ interface AppState { activeTabId: string | null; // UI State - activeView: 'posts' | 'media' | 'settings' | 'tags' | 'chat' | 'import'; + activeView: 'posts' | 'pages' | 'media' | 'settings' | 'tags' | 'chat' | 'import'; sidebarVisible: boolean; panelVisible: boolean; selectedPostId: string | null; @@ -96,7 +96,7 @@ interface AppState { restoreTabState: (state: TabState) => void; // Actions - setActiveView: (view: 'posts' | 'media' | 'settings' | 'tags' | 'chat' | 'import') => void; + setActiveView: (view: 'posts' | 'pages' | 'media' | 'settings' | 'tags' | 'chat' | 'import') => void; toggleSidebar: () => void; togglePanel: () => void; setSelectedPost: (id: string | null) => void; diff --git a/tests/renderer/components/PagesShortcut.test.tsx b/tests/renderer/components/PagesShortcut.test.tsx new file mode 100644 index 0000000..4659fd1 --- /dev/null +++ b/tests/renderer/components/PagesShortcut.test.tsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { render, screen, within } from '@testing-library/react'; +import { ActivityBar } from '../../../src/renderer/components/ActivityBar/ActivityBar'; +import { Sidebar } from '../../../src/renderer/components/Sidebar/Sidebar'; +import { useAppStore, type PostData } from '../../../src/renderer/store'; + +const createMockPost = (overrides: Partial = {}): PostData => ({ + id: `post-${Math.random().toString(36).slice(2)}`, + projectId: 'project-1', + title: 'Test Post', + slug: 'test-post', + content: 'content', + status: 'published', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + tags: [], + categories: [], + ...overrides, +}); + +describe('Pages shortcut UI', () => { + beforeEach(() => { + const now = new Date().toISOString(); + + useAppStore.setState({ + activeView: 'posts', + sidebarVisible: true, + tabs: [], + activeTabId: null, + posts: [ + createMockPost({ + id: 'post-page', + title: 'About Page', + categories: ['page'], + updatedAt: now, + }), + createMockPost({ + id: 'post-article', + title: 'Regular Article', + categories: ['article'], + updatedAt: now, + }), + ], + hasMorePosts: false, + totalPosts: 2, + }); + + window.electronAPI.posts.getTags = vi.fn().mockResolvedValue([]); + window.electronAPI.posts.getCategories = vi.fn().mockResolvedValue(['page', 'article']); + window.electronAPI.posts.getByYearMonth = vi.fn().mockResolvedValue([]); + (window.electronAPI as any).tags = { + getAll: vi.fn().mockResolvedValue([]), + }; + window.electronAPI.posts.search = vi.fn().mockResolvedValue([]); + window.electronAPI.posts.filter = vi.fn().mockResolvedValue([]); + window.electronAPI.posts.get = vi.fn().mockResolvedValue(null); + }); + + it('shows a pages button in the activity bar', () => { + render(); + + expect(screen.getByTitle('Pages (click again to toggle sidebar)')).toBeInTheDocument(); + }); + + it('uses a distinct pages icon shape', () => { + render(); + + const pagesButton = screen.getByTitle('Pages (click again to toggle sidebar)'); + const pagesSvg = pagesButton.querySelector('svg'); + + expect(pagesSvg).not.toBeNull(); + expect(pagesSvg?.querySelector('path')?.getAttribute('d')).toBe( + 'M4 4h10v4h6v12H4V4zm10 1.5V9h4.5L14 5.5zM7 12h10v1.5H7V12zm0 3h10v1.5H7V15z' + ); + }); + + it('shows only page-category posts when pages view is active', async () => { + useAppStore.setState({ activeView: 'pages', sidebarVisible: true }); + + render(); + + const pagesHeader = await screen.findByText('PAGES'); + const pagesPanel = pagesHeader.closest('.sidebar-content'); + + expect(pagesPanel).not.toBeNull(); + expect(within(pagesPanel as HTMLElement).getByText('About Page')).toBeInTheDocument(); + expect(within(pagesPanel as HTMLElement).queryByText('Regular Article')).not.toBeInTheDocument(); + }); +}); \ No newline at end of file